Sway Predicate 编程指南:用 UTXO 花费条件构建无需部署的链上可执行程序
2026/9/18 8:30:54 网站建设 项目流程

Sway Predicate 编程指南:用 UTXO 花费条件构建无需部署的链上可执行程序

【免费下载链接】sway🌴 Empowering everyone to build reliable and efficient smart contracts.项目地址: https://gitcode.com/GitHub_Trending/sw/sway

本文基于 Fuel 生态 Sway 语言仓库中的官方文档 docs/reference/src/documentation/language/program-types/predicate.md 编写,并以仓库源码、示例项目与端到端测试为佐证。Predicate(谓词)是 Sway 四种程序类型之一,常用于多签钱包、时间锁、条件支付等"交易预置条件"场景。读完本文你将掌握 Predicate 的定位、运行约束、UTXO 转账与花费机制,以及如何编写、配置并验证一个可用的 Predicate 程序。

Predicate 是什么

Predicate 是一段可执行程序,它表示的是一种UTXO 花费条件(spending condition),典型场景如多签 Predicate——需要多把私钥共同授权才能花费对应资产。在 Sway Program Types 总览 中,Sway 程序共有四种类型:contractlibraryscriptpredicate。其中 Predicate 被定义为:

"A set of preconditions to the construction of a transaction, the result of which must be a Boolean value oftruein order for the transaction to be considered valid."

即:Predicate 是构造交易时必须满足的一组前置条件,其执行结果必须为true,交易才被认为有效。这与合约、脚本有着本质区别——Predicate 不执行业务逻辑、不存储状态,它只回答一个问题:"这笔交易里的 UTXO 是否被允许花费?"

与 Contract / Script 的对比

维度PredicateScriptContract
是否需要部署否,仅在交易执行期间存在否,仅在交易执行期间存在是,通过交易部署上链
链上持久形态仅 Predicate Root 作为 UTXO 拥有者字节码与状态常驻链上
入口main()函数,返回boolmain()函数ABI(abi声明 +impl实现)
是否可读/写合约状态不可以无状态,但可通过合约交互存储可以
主要用途交易花费条件(多签、时间锁等)复杂多步链上交互协议与业务系统

从仓库示例看,三者文件头声明不同:Predicate 以predicate;开头(见 simple_predicate/src/main.sw),脚本以script;开头,合约以contract;开头。入口方面,合约暴露 ABI,而 Script 与 Predicate 都暴露main()函数作为执行入口(见 program-types/index.md)。

Predicate 的核心运行约束

根据官方文档,Predicate 有三条关键约束:

  1. 无需部署:Predicate 不需要部署到区块链,因为它只在交易执行期间存在。但注意,Predicate Root(谓词根)会留在链上,作为一条或多条 UTXO 的拥有者(owner)。
  2. 不能访问合约状态:Predicate 既不能读取、也不能写入任何合约状态(contract state)。
  3. 禁止使用合约指令:Predicate 不能使用 FuelVM 指令集中的合约指令(contract instructions),这类指令用于CALLRETD等与合约交互相关的操作。

从语义分析实现上看,Sway 编译器在 sway-core/src/semantic_analysis/module.rs 等模块中会对 Predicate 程序进行专门的类型检查,确保其符合上述限制;同时在 sway-core/src/semantic_analysis/cei_pattern_analysis.rs 中可以看到对 Predicate 代码路径的处理。若 Predicate 试图执行不允许的操作,会被编译期或 VM 执行期拒绝。

此外还有一条自动失败规则:

如果 Predicate 在执行时发生 revert(回滚),或尝试访问不纯(impure)的 VM 操作码,则其评估结果自动为false

这意味着 Predicate 是一种"保守失败"的设计:任何异常情况都不会被当作成功,未通过验证的交易将无法花费对应 UTXO。

如何向 Predicate 转账

在 Fuel 中,普通交易可以把资产发送到某个地址。而 Predicate 的字节码会计算出一个字节码根(bytecode root),这个根就是 Predicate 在链上的唯一身份。因此,向 Predicate 转账的方式是:

将 coins 发送到一个唯一表示该 Predicate 字节码的地址,即 bytecode root 对应的地址。

这样,链上 UTXO 的 owner 字段就会指向该 Predicate Root。资金到达后,Predicate 字节码本身并不上链——链上只保留了它的根(哈希)。这种设计与合约地址不同:合约地址是部署后的固定地址,而 Predicate 地址是字节码的哈希派生物,任何人生成相同的字节码就能推导出相同的地址,无需任何部署操作。

如何花费 Predicate 持有的资金

Predicate 持有的 coin UTXO 的可花费条件与传统签名不同:

coin UTXO 变得可花费,不是靠提供有效签名,而是要求:所提供的 Predicate 的 Root 与 UTXO 的 owner 匹配,并且 Predicate 执行评估结果为true

也就是说,花费流程包含两个必须同时满足的条件:

  1. Root 匹配:交易中提交的 Predicate 字节码计算出的根,必须等于该 UTXO 的 owner;
  2. 评估通过:Predicate 的main()执行返回true

若 Predicate 在执行过程中 revert,或试图访问不纯 VM 操作码,评估结果自动为false,交易将无法花费该 UTXO。

花费条件(Spending Conditions)

Predicate 的能力不止于"返回 true"。它可以:

  • 内省(introspect)当前交易:读取正在花费其 coins 的交易信息,包括输入(inputs)、输出(outputs)、脚本字节码等;
  • 接收运行时参数predicateData:在提交交易时附带的自定义数据。

这两者(或两者结合)都可以影响 Predicate 的最终评估结果。例如一个多签 Predicate 可以检查交易中的签名数量是否达到阈值;一个时间锁 Predicate 可以检查交易的date/time输入是否晚于某个时间点;一个条件支付 Predicate 可以验证predicateData中携带的接收者地址是否与输出匹配。正因为 Predicate 可以"看"到整笔交易和运行时数据,它才能实现灵活、可组合的花费策略。

编写一个 Predicate:完整示例

官方文档给出与 Script 类似的写法:

Predicate 由一个main()函数组成,可以接受任意数量的参数,但必须返回bool。要使 Predicate 有效,返回的布尔值必须为true

仓库中的示例位于 docs/reference/src/code/language/program-types/predicates/simple_predicate/src/main.sw:

predicate; // All predicates require a main function which return a Boolean value. fn main(amount: u64) -> bool { true }

要点解析:

  • predicate;:文件第一行声明程序类型为 Predicate(与contract;script;library;对应);
  • fn main(...) -> bool:唯一入口函数,可接收任意数量、任意类型的参数;
  • 返回值true:本例无条件放行,是最简单的"白名单" Predicate;实际项目中应在此处编写真实的花费条件逻辑。

项目配置(Forc.toml)

该示例的 Forc.toml 展示了 Predicate 项目的基本配置:

[project] authors = ["Fuel Labs <contact@fuel.sh>"] entry = "main.sw" license = "Apache-2.0" name = "simple_predicate" [dependencies] std = { path = "../../../../../../../../sway-lib-std" }
  • entry = "main.sw"指定入口文件;
  • name = "simple_predicate"为项目名,也是后续计算 Predicate Root 时的标识基础;
  • [dependencies]引入std标准库,路径以仓库根目录为基准(本文示例使用相对路径引用本仓库的 sway-lib-std 标准库)。

一个带真实条件的 Predicate

仓库测试目录中提供了更贴近实战的变体,例如 test/src/e2e_vm_tests/test_programs/should_pass/language/predicate_while/src/main.sw:

predicate; fn main() -> bool { let mut x = 10; while x != 0 { x -= 1; } true }

该示例演示了 Predicate 中可以使用循环等常规流程控制指令(只要不触碰合约指令与不纯操作码)。而 forc-test/test_data/test_predicate/src/main.sw 则展示了一个返回false的 Predicate 及其内联单元测试写法:

predicate; fn main() -> bool { false } #[test] fn test_bam() { assert(1 == 1) } #[test] fn test_bum() { assert(1 == 1) }

这也印证了官方文档的规则:Predicate 的main()必须返回布尔值,false即拒绝花费。

端到端测试与验证

仓库的端到端测试系统(test/src/e2e_vm_tests)为 Predicate 提供了完整的验证闭环。以基础 Predicate 测试 basic_predicate/src/main.sw 为例:

predicate; fn main() -> bool { true }

其对应的测试配置 test.toml 声明了期望结果:

category = "run" expected_result = { action = "return", value = 1 } expected_result_new_encoding = { action = "return", value = 1 }
  • category = "run"表示该测试会实际运行 Predicate 字节码;
  • expected_result.action = "return"value = 1表明期望 Predicate 正常返回且返回值为1(即true),验证其通过花费验证。

这类测试与 forc-test 配合,可以在本地对 Predicate 进行构建、运行和单元测试,无需部署到任何网络。

构建与使用 Predicate 的命令流程

结合 forc(Fuel Orchestrator)CLI,Predicate 的典型工作流如下:

  1. 初始化项目forc new --predicate my_predicate创建带predicate;文件头的模板项目;
  2. 编写逻辑:在src/main.sw中实现main()花费条件;
  3. 构建forc build编译为字节码;
  4. 计算 Predicate Root:使用forc predicate-root(见 forc/src/ops/forc_predicate_root.rs)计算字节码根,得到资产接收地址;
  5. 转账:向该 Root 地址发送资产,UTXO 的 owner 即为此 Predicate;
  6. 花费:构造交易时附带 Predicate 字节码与predicateData运行时参数,链上校验 Root 匹配且评估为true后放行。

注意:上述命令中的--predicate模板参数与forc predicate-root子命令以当前仓库 forc 的实际实现为准;完整的命令列表可查阅 forc 命令文档 与 forc/src/cli/commands 源码。

实战注意事项

  • 永远返回布尔值:Predicate 的main()签名必须返回bool,且只有true才能通过验证;false、revert 或访问不纯指令都会被判定为无效;
  • 不要在 Predicate 中依赖合约状态:它既不能读也不能写合约状态,需要状态信息时应通过交易输入(inputs)或predicateData传入;
  • Predicate Root 即地址:理解"链上只有根、没有字节码"这一点,有助于设计资金回收、升级与审计方案——字节码变更会改变 Root,从而改变地址;
  • 可以内省交易:充分利用对 inputs/outputs/脚本字节码的读取能力,实现多签、时间锁、条件支付等复杂策略;
  • 组合使用:Predicate 与 Script、Contract 可以组合,例如由 Script 调用多个合约的同时,用 Predicate 守护特定 UTXO 的花费权。

小结

Predicate 是 Sway 语言中用于表达 UTXO 花费条件的专用程序类型:无需部署、只在交易期间存在、不能访问合约状态与合约指令、评估失败即拒绝。通过"Predicate Root 匹配 + 评估为true"的双重校验机制,开发者可以在 Fuel 上构建多签钱包、时间锁、条件支付等无需智能合约状态即可运行的资产控制方案。仓库中的 simple_predicate 示例、e2e 测试 与 forc-test 夹具 提供了从编写、配置到验证的完整参照,可直接作为新 Predicate 项目的起点。

【免费下载链接】sway🌴 Empowering everyone to build reliable and efficient smart contracts.项目地址: https://gitcode.com/GitHub_Trending/sw/sway

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询