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 程序共有四种类型:contract、library、script和predicate。其中 Predicate 被定义为:
"A set of preconditions to the construction of a transaction, the result of which must be a Boolean value of
truein order for the transaction to be considered valid."
即:Predicate 是构造交易时必须满足的一组前置条件,其执行结果必须为true,交易才被认为有效。这与合约、脚本有着本质区别——Predicate 不执行业务逻辑、不存储状态,它只回答一个问题:"这笔交易里的 UTXO 是否被允许花费?"
与 Contract / Script 的对比
| 维度 | Predicate | Script | Contract |
|---|---|---|---|
| 是否需要部署 | 否,仅在交易执行期间存在 | 否,仅在交易执行期间存在 | 是,通过交易部署上链 |
| 链上持久形态 | 仅 Predicate Root 作为 UTXO 拥有者 | 无 | 字节码与状态常驻链上 |
| 入口 | main()函数,返回bool | main()函数 | ABI(abi声明 +impl实现) |
| 是否可读/写合约状态 | 不可以 | 无状态,但可通过合约交互存储 | 可以 |
| 主要用途 | 交易花费条件(多签、时间锁等) | 复杂多步链上交互 | 协议与业务系统 |
从仓库示例看,三者文件头声明不同:Predicate 以predicate;开头(见 simple_predicate/src/main.sw),脚本以script;开头,合约以contract;开头。入口方面,合约暴露 ABI,而 Script 与 Predicate 都暴露main()函数作为执行入口(见 program-types/index.md)。
Predicate 的核心运行约束
根据官方文档,Predicate 有三条关键约束:
- 无需部署:Predicate 不需要部署到区块链,因为它只在交易执行期间存在。但注意,Predicate Root(谓词根)会留在链上,作为一条或多条 UTXO 的拥有者(owner)。
- 不能访问合约状态:Predicate 既不能读取、也不能写入任何合约状态(contract state)。
- 禁止使用合约指令:Predicate 不能使用 FuelVM 指令集中的合约指令(contract instructions),这类指令用于
CALL、RETD等与合约交互相关的操作。
从语义分析实现上看,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。
也就是说,花费流程包含两个必须同时满足的条件:
- Root 匹配:交易中提交的 Predicate 字节码计算出的根,必须等于该 UTXO 的 owner;
- 评估通过: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 的典型工作流如下:
- 初始化项目:
forc new --predicate my_predicate创建带predicate;文件头的模板项目; - 编写逻辑:在
src/main.sw中实现main()花费条件; - 构建:
forc build编译为字节码; - 计算 Predicate Root:使用
forc predicate-root(见 forc/src/ops/forc_predicate_root.rs)计算字节码根,得到资产接收地址; - 转账:向该 Root 地址发送资产,UTXO 的 owner 即为此 Predicate;
- 花费:构造交易时附带 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),仅供参考