拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Sway 程序类型(Program Types)完全指南:Contract、Library、Script 与 Predicate 深入解析

Sway 程序类型Program Types完全指南Contract、Library、Script 与 Predicate 深入解析【免费下载链接】sway Empowering everyone to build reliable and efficient smart contracts.项目地址: https://gitcode.com/GitHub_Trending/sw/swaySway 是一种用于构建智能合约的领域特定语言其程序文件以.sw为扩展名如main.sw而文件的第一行必须声明该程序的类型。本篇技术指南以官方参考文档 docs/reference/src/documentation/language/program-types/index.md 为核心骨架系统讲解 Sway 的四种程序类型contract、library、script、predicate、项目类型与入口点规则并结合仓库内真实示例代码与 forc 源码帮助你理解每种类型的适用场景、文件结构、代码写法与底层约束。读完本文你将能够准确判断一个业务需求应选用哪种 Sway 程序类型并正确组织项目结构与入口函数。一、Sway 程序的四种类型总览在 Sway 中一个程序文件以.sw结尾且文件第一行必须声明程序类型。总共有四种类型类型关键字声明典型用途是否可部署上链合约contractcontract;在固定规则集内运作的协议或系统如质押合约、去中心化交易所等是通过交易部署字节码库librarylibrary;封装通用操作的复用代码否作为其他程序的依赖被引用脚本scriptscript;复杂、多步骤、且不持久化的链上交互如通过 DEX 创建杠杆仓位借款、兑换、再抵押否仅存在于交易执行期间谓词predicatepredicate;交易构造的前置条件集合求值结果必须为true交易才有效如多签谓词否谓词根root在链上作为 UTXO 所有者存在以上四种类型在仓库的 forc 源码中有明确对应。参见 forc/src/utils/program_type.rs 中定义的枚举#[derive(Debug)] pub enum ProgramType { Contract, Script, Predicate, Library, }该枚举同时实现了Display将四种类型映射为字符串contract、script、predicate、library这解释了为什么 Sway 源文件第一行要写出contract;、script;这类声明语句——它直接决定了 forc 如何解析、编译与处理该程序。从编译器的角度理解Sway 语言本身只提供一套语法但程序类型决定了生成的字节码形态、可用的指令集如 predicate 禁止合约指令、以及对外暴露的入口形态ABI 还是main()。二、Sway 项目类型Project Types与约束规则项目类型指的是**项目主文件entry 指定的主文件**所属的程序类型。因此 Sway 项目同样有四种类型contracts合约项目libraries库项目scripts脚本项目predicates谓词项目关键规则如下所有四种项目都可以在src目录下包含多个库文件。也就是说无论项目主文件是合约、脚本还是谓词你都可以在src目录中放若干个.sw库文件来组织公共代码。关于合约、脚本与谓词的唯一限制一个项目最多只能包含合约、脚本、谓词中的任意一种。一个项目不能包含多个合约、多个脚本或多个谓词也不能在同一项目中混用它们例如不能同时包含一个合约和一个脚本。这条约束决定了项目的边界设计如果你想同时拥有一个合约和一个脚本必须拆分为两个独立的 forc 项目例如仓库中 examples/multi_contract_calls 将callee被调用合约与caller调用方脚本拆成了两个独立子项目每个子项目有自己的Forc.toml与src/main.sw。而项目主文件由Forc.toml的[project]段中的entry字段指定例如[project] authors [Fuel Labs contactfuel.sh] entry main.sw license Apache-2.0 name counter [dependencies] std { path ../../sway-lib-std }参见 examples/counter/Forc.toml。三、入口点Entry Points规则入口点是程序开始执行的位置不同类型有完全不同的入口规则库library不可直接部署到区块链因此没有入口点。库的代码被导出供其他程序使用。合约contract有入口点对外暴露Application Binary InterfaceABI——即一组可被外部调用的接口端点。脚本script有入口点暴露一个main()函数。谓词predicate有入口点暴露一个main()函数且该函数必须返回bool。从代码示例可以直观看到这种差异。脚本与谓词的主文件都以main()为入口见下文第四、五节而合约则以 ABI 的实现为入口见下文第六节。四、脚本Script详解4.1 什么是脚本脚本是一个可执行程序但不需要部署因为它只在一次交易执行期间存在。脚本可以复刻合约的功能例如路由器却无需承担部署成本也不会增加链的大小。脚本的若干属性不能被合约调用无状态stateless但可以通过合约与链上存储交互参见 docs/reference/src/documentation/operations/storage/index.md可以调用多个合约这也是多合约交互场景如 examples/multi_contract_calls 使用脚本的原因。4.2 脚本示例下面的脚本接收一个参数并返回布尔值true。完整源码位于 docs/reference/src/code/language/program-types/scripts/simple_script/src/main.swscript; // All scripts require a main function. The return type is optional. fn main(amount: u64) - bool { true }注意脚本的要点第一行声明script;所有脚本都必须有一个main函数其返回类型是可选的即可以不写返回类型但通常用于返回交易结果main函数的参数来自交易的脚本数据区可以灵活携带调用数据。4.3 脚本在仓库中的实战佐证仓库中多个示例都体现了脚本多步、多合约、无持久化的典型用法例如examples/wallet_contract_caller_script/src/main.sw一个脚本调用钱包合约演示如何通过脚本对合约发起调用examples/multi_contract_calls/caller/src/main.sw一个脚本在单次执行中调用多个合约正是文档所述can call multiple contracts的落地案例。五、谓词Predicate详解5.1 什么是谓词谓词是一个表示 UTXO 花费条件的可执行程序例如多签谓词multisig predicate。它对可用的 VM 指令有限制。关键特性不需要部署到区块链因为它只在交易期间存在但谓词根predicate root在链上作为某个或多个 UTXO 的所有者不能读写任何合约状态不能使用任何合约指令contract instructions。5.2 向谓词转账在 Fuel 中币可以被发送到一个唯一表示某特定谓词字节码的地址——即字节码根bytecode root。这意味着给谓词地址转账就等于让该谓词拥有这笔资产。5.3 花费谓词资产币的 UTXO 变得可花费不是基于提供了有效签名而是基于以下两点同时成立提供的谓词其根root与 UTXO 的所有者匹配谓词求值结果为true。如果谓词回滚revert或尝试访问不纯impure的 VM 操作码则求值结果自动为false。5.4 花费条件Spending Conditions谓词可以检查花费其资产的那笔交易inputs、outputs、脚本字节码等并且可以接收运行时参数predicateData。上述任意一项或两者共同影响谓词的求值结果。5.5 谓词示例与脚本类似谓词由一个main()函数构成可以接收任意数量的参数但必须返回bool只有返回true谓词才有效。完整源码位于 docs/reference/src/code/language/program-types/predicates/simple_predicate/src/main.swpredicate; // All predicates require a main function which return a Boolean value. fn main(amount: u64) - bool { true }可以看到谓词与脚本在文件结构上非常接近核心区别在于声明关键字不同predicate;与script;谓词被用作交易的花费条件而脚本是主动发起链上操作谓词受限于不能使用合约指令、不能访问合约状态。从编译与测试角度看仓库中 forc-test/test_data/test_predicate/src/main.sw 也提供了可编译、可测试的谓词工程样例可用于验证谓词构建与求值行为。六、合约Contract详解6.1 什么是合约智能合约是一段可以通过交易部署到区块链的字节码。它可以像调用 API 一样被调用用于执行计算并像数据库一样存取数据。一个智能合约由两部分组成Application Binary InterfaceABI定义合约对外暴露的调用端点ABI 的实现对接口的具体实现逻辑。6.2 Application Binary InterfaceABIABI 是一种结构定义了合约对外暴露的调用端点。也就是说在 ABI 中定义的函数被视为external外部函数合约不能调用自身的这些函数。下面的例子演示了一个能够接收和发送资金的钱包接口。结构以关键字abi开头后跟合约名内部是函数签名、存储交互注解annotations和文档注释。完整源码位于 docs/reference/src/code/language/program-types/contracts/interface/src/lib.swlibrary; abi Wallet { /// When the BASE_ASSET is sent to this function the internal contract balance is incremented #[storage(read, write)] fn receive_funds(); /// Sends amount_to_send of the BASE_ASSET to recipient /// /// # Arguments /// /// - amount_to_send: amount of BASE_ASSET to send /// - recipient: user to send the BASE_ASSET to /// /// # Reverts /// /// * When the caller is not the owner of the wallet /// * When the amount being sent is greater than the amount in the contract #[storage(read, write)] fn send_funds(amount_to_send: u64, recipient: Identity); }可注意到的 ABI 关键要素abi关键字 名称声明接口函数签名只声明参数与返回类型不写函数体#[storage(read, write)]注解说明该函数与合约存储的交互方式只读/读写用于编译器分析与静态检查文档注释描述功能、参数、回滚条件属于 ABI 元数据的一部分。6.3 实现 ABIImplementing the ABI实现 ABI 的语法与 Rust 中实现 trait 类似impl abi-name for Contract。ABI 中定义的所有函数都必须在实现中声明由于接口通常定义在合约之外如上面的Wallet接口在独立的lib.sw库中实现前需要先用use语法导入。完整实现源码位于 docs/reference/src/code/language/program-types/contracts/wallet/src/main.swcontract; use interface::Wallet; impl Wallet for Contract { #[storage(read, write)] fn receive_funds() { // function implementation } #[storage(read, write)] fn send_funds(amount_to_send: u64, recipient: Identity) { // function implementation } }6.4 仓库中的完整合约实战样例仓库提供了大量完整的合约实现其中最贴近上述文档示例的是 examples/wallet_smart_contract/src/main.sw完整钱包合约与 examples/wallet_abi/src/main.sw钱包 ABI 定义。前者展示了真实的 ABI 实现细节包括contract; use std::{asset::transfer, call_frames::msg_asset_id, context::msg_amount}; use wallet_abi::Wallet; const OWNER_ADDRESS Address::from(0x8900c5bec4ca97d4febf9ceb4754a60d782abbf3cd815836c1872116f203f861); storage { balance: u64 0, } impl Wallet for Contract { #[storage(read, write), payable] fn receive_funds() { if msg_asset_id() AssetId::base() { // 收到基础资产时累计余额 storage.balance.write(storage.balance.read() msg_amount()); } } // ... }这个示例补充说明了文档之外的几个要点合约可以通过storage { ... }块声明持久化状态此处为balance这正是合约有状态、脚本与谓词无状态差异的体现ABI 函数可以附加payable等注解合约实现中可以访问msg_asset_id()、msg_amount()等上下文函数而谓词则被禁止使用这类合约相关指令。此外examples/counter、examples/storage_map、examples/ownership 等示例覆盖了合约存储、映射与权限控制等常见场景可作为进一步研读的材料。七、库Library详解7.1 库的定义库用于封装执行常见操作的代码以避免代码重复。库通过文件开头的library;关键字定义library;完整示例见 docs/reference/src/code/language/program-types/libraries/internal/my_lib/src/my_library.sw。7.2 可见性与pub关键字代码可见性规则库内部——更宽泛地说Sway 项目内部任何位置——定义的代码默认是private私有的其他文件无法访问除非被显式暴露。代码暴露需要两步流程在代码开头加上pub关键字在Forc.toml文件中将目标库声明为依赖然后通过use导入pub声明。以下结构可以被标记为pub全局常量globally defined constants结构体Structs枚举Enums函数Functions特征Traits下面是一个完整的库示例同时展示了pub的用法与不写pub的私有项library; // 缺少 pub 关键字无法被导入 fn foo() {} // 下面这些因为使用了 pub 关键字都可以被导入 pub const ONE __to_str_array(1); pub struct MyStruct {} impl MyStruct { pub fn my_function() {} } pub enum MyEnum { Variant: (), } pub fn bar() {} pub trait MyTrait { fn my_function(); }7.3 库的部署库不能直接部署到区块链但可以作为合约的一部分随合约部署——即通过依赖关系被打包进使用它的合约中。7.4 内部库Internal Libraries如果库与项目的其他程序文件位于同一个src目录下它就是项目的内部库$ tree . ├── Cargo.toml ├── Forc.toml └── src ├── lib.sw └── my_library.sw要在lib.sw中使用内部库my_library.sw需要两步使用mod关键字后跟库名将库引入作用域使用use关键字选择性导入库中的各项。示例见 docs/reference/src/code/language/program-types/libraries/internal/my_lib/src/lib.swlibrary; mod my_library; use my_library::bar; // bar from my_library is now available throughout the file7.5 外部库External Libraries外部库是位于src目录之外通常完全是另一个项目的库$ tree . ├── my_library │ ├── Cargo.toml │ ├── Forc.toml │ └── src │ └── lib.sw │ └── my_other_library ├── Cargo.toml ├── Forc.toml └── src └── lib.sw以文档示例为例my_other_library其中定义了使用pub关键字导出的函数quix()因此可以被my_library导入。源码见 docs/reference/src/code/language/program-types/libraries/external/my_other_library/src/lib.swlibrary; pub fn quix() {}my_library要在其中使用quix()同样需要两步。第一步添加到依赖dependencies。在my_library的Forc.toml文件的[dependencies]段中添加my_other_library。真实配置见 docs/reference/src/code/language/program-types/libraries/external/my_library/Forc.toml[project] authors [Fuel Labs contactfuel.sh] entry lib.sw license Apache-2.0 name my_library [dependencies] my_other_library { path ../my_other_library } std { path ../../../../../../../../../sway-lib-std }注意此处依赖通过path指定本地相对路径实际项目中也可以替换为 git 依赖或注册中心registry依赖forc 的依赖解析实现参见 forc-pkg/src/source 目录下的git、path、reg等模块。第二步导入Import。使用use关键字选择性导入my_other_library中的代码。源码见 docs/reference/src/code/language/program-types/libraries/external/my_library/src/lib.swlibrary; use my_other_library::quix; // quix from my_other_library is now available throughout the file7.6 标准库仓库中库的典型形态仓库自带的 sway-lib-std 就是库这一程序类型的规模化实践它由 sway-lib-std/src/lib.sw 作为入口通过模块组织起address.sw、asset.sw、storage.sw、vec.sw、bytes.sw等大量功能模块。各示例工程的Forc.toml都通过std { path ../../sway-lib-std }或std { git ... }将其声明为依赖再以use std::...导入具体功能——这正是外部库 pub导出 依赖声明机制的日常体现。八、如何选择程序类型决策对照综合以上内容在实际开发中可参考以下决策路径需要持久化状态、被其他合约/脚本/外部调用、承载业务规则→ 选择合约contract通过 ABI 暴露接口注意合约不能调用自身 ABI 函数这一约束需要封装可复用逻辑常量、结构体、函数、trait→ 选择库library按需声明pub导出内部库用moduse外部库用依赖 use需要执行一次性、多步骤、跨多合约的链上操作且不需要持久化→ 选择脚本script编写main()需要定义 UTXO 的花费条件如多签、时间锁通过求值true/false控制资产花费→ 选择谓词predicate编写返回bool的main()并牢记其不能使用合约指令、不能访问合约状态的限制。同时要遵守项目级约束一个项目最多只能包含合约、脚本、谓词中的任意一种且不能混用但所有项目都可以自由包含多个库文件。九、总结Sway 的四种程序类型合约、库、脚本、谓词构成了 Sway 区块链编程的基本骨架合约是唯一可部署上链、拥有持久状态并以 ABI 为入口的类型库是纯复用代码载体通过pub 依赖声明对外暴露能力内部库用mod引入、外部库用[dependencies]引入脚本提供无需部署的一次性多步链上操作入口main()谓词以返回bool的main()定义 UTXO 花费条件受限于指令集且不能访问合约状态。理解这些类型的差异与约束是正确设计 Sway 工程结构、写出可编译可运行代码的前提。可进一步阅读的仓库资料包括程序类型分类的源码定义 forc/src/utils/program_type.rs、官方参考文档各子章节合约、脚本、谓词、库以及 examples 目录下覆盖各种类型的可运行示例工程。【免费下载链接】sway Empowering everyone to build reliable and efficient smart contracts.项目地址: https://gitcode.com/GitHub_Trending/sw/sway创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门