Sway 智能合约断言机制(二):深入理解require的日志与回滚语义
【免费下载链接】sway🌴 Empowering everyone to build reliable and efficient smart contracts.项目地址: https://gitcode.com/GitHub_Trending/sw/sway
require是 Sway 语言中最常用的运行时条件检查函数,它由标准库 prelude 自动导入每个程序,用于在条件不满足时记录一条日志并让 Fuel 虚拟机(VM)安全回滚(revert)。本篇技术指南以 Sway 官方文档的require章节为骨架,结合 sway-lib-std 标准库的源码实现,完整讲解require的调用方式、泛型消息类型、底层回滚信号以及它与assert、revert系列函数的区别,帮助你写出更健壮、更易排查的智能合约。
一、require是什么
require是 Sway 标准库提供的一个运行时断言函数。它接受一个必须求值为 Boolean 类型的表达式:如果该表达式求值为true,什么都不会发生,程序继续执行后续代码;如果求值为false,标准库会先发出一条日志(log),随后让虚拟机回滚(revert)。
与普通assert的最大区别在于:require允许你附带一条有意义的错误消息,该消息会作为日志被记录到链上,供链下索引、区块浏览器或前端应用读取,从而极大提升合约失败时的可诊断性。
二、自动导入:prelude 的作用
require并不需要你手动use引入。在 Sway 中,每个程序都会隐式导入标准库的 prelude,其中声明了对require的导出。这一点可以从 prelude.sw 源码中得到直接验证:
// Error handling pub use ::assert::{assert, assert_eq, assert_ne}; pub use ::option::Option::{self, *}; pub use ::result::Result::{self, *}; pub use ::revert::{require, revert, revert_with_log};也就是说,require、revert、revert_with_log三个函数全部由 revert 模块 定义,并经 prelude 自动暴露给所有 Sway 程序。完整的 prelude 内容可参考官方文档 Standard Library Prelude。
三、核心示例:安全的减法函数
官方文档给出的经典示例是一个减法函数。u64是无符号 64 位整数,不可能为负数,因此必须断言b小于等于a,否则减法会下溢(underflow)导致错误结果。该示例的完整源码位于 req.sw:
library; fn subtract(a: u64, b: u64) -> u64 { require(b <= a, "b is too large"); a - b }运行逻辑如下:
- 调用
subtract(10, 5):b <= a为true,函数继续执行并返回5; - 调用
subtract(5, 10):b <= a为false,日志b is too large被发出,虚拟机立即回滚,函数不返回任何值,链上状态也回到调用前的快照。
这个模式在真实合约中非常典型:任何依赖外部输入且对取值范围有硬性要求的计算,都应当用require在入口处把守。
四、消息类型是泛型的
require的第二参数value是泛型的(即文档所说的 generic message),这意味着它可以是任意类型:字符串、数值、结构体、枚举,甚至自定义类型。例如:
// 数值作为错误码 require(amount > 0, 1001); // 枚举作为错误类型 enum Error { InsufficientBalance: (), Overflow: (), } require(balance >= amount, Error::InsufficientBalance(()));泛型的能力来自标准库中require的声明方式。在 revert.sw 中可以看到它有两个#[cfg(experimental_new_encoding = ...)]条件下的实现变体:
#[cfg(experimental_new_encoding = false)] pub fn require<T>(condition: bool, value: T) { if !condition { log(value); revert(FAILED_REQUIRE_SIGNAL) } } #[cfg(experimental_new_encoding = true)] pub fn require<T>(condition: bool, value: T) where T: AbiEncode, { if !condition { log(value); revert(FAILED_REQUIRE_SIGNAL) } }两种变体的行为完全一致——条件为假时先log(value)再revert(FAILED_REQUIRE_SIGNAL)。差异仅在于:当开启experimental_new_encoding特性时,要求消息类型T实现AbiEncodetrait,以保证新的编码方案能正确序列化该日志值。
五、底层原理:日志 + 专属回滚信号
理解require的关键在于它回滚时使用的专属错误信号。该常量定义在 error_signals.sw:
/// A revert with this value signals that it was caused by a failing call to `std::revert::require`. /// /// # Additional Information /// /// The value is: 18446744073709486080 pub const FAILED_REQUIRE_SIGNAL = 0xffff_ffff_ffff_0000;也就是说,当require失败时,虚拟机回滚的退出码是0xffff_ffff_ffff_0000(十进制 18446744073709486080)。这个值并非随机选择:
- 高位的
0xffff_ffff_ffff前缀使它与普通用户自定义回滚码区分开,专门标识"由标准库内置函数触发的失败"; - 链下工具(如区块浏览器、SDK、索引服务)可以通过检查回滚码是否为
FAILED_REQUIRE_SIGNAL,快速判定失败是否由某个require断言引起,进而解析出附带的消息日志。
同一文件中还定义了其他标准库回滚信号,例如FAILED_ASSERT_SIGNAL(assert触发)、FAILED_ASSERT_EQ_SIGNAL(assert_eq触发)、FAILED_ASSERT_NE_SIGNAL(assert_ne触发)和REVERT_WITH_LOG_SIGNAL(revert_with_log触发),它们共同构成了一套可区分的链上错误语义。
六、require与assert、revert家族对比
Sway 的断言体系在 Assertions 文档 中统称为"防止不期望计算发生"的手段,共包含五个函数。require与它们的关系如下:
| 函数 | 行为 | 失败时是否附带日志 | 回滚信号 |
|---|---|---|---|
assert | 条件为假则回滚 | 否 | FAILED_ASSERT_SIGNAL |
require | 条件为假则记录消息并回滚 | 是(泛型消息) | FAILED_REQUIRE_SIGNAL |
revert | 无条件回滚,可指定退出码 | 否 | 用户指定码 |
assert_eq | a != b则回滚并记录两值 | 是(两个输入值) | FAILED_ASSERT_EQ_SIGNAL |
assert_ne | a == b则回滚并记录两值 | 是(两个输入值) | FAILED_ASSERT_NE_SIGNAL |
从实现上看,assert.sw 中的assert与require结构几乎相同,只是省去了log(value)这一步:
pub fn assert(condition: bool) { if !condition { revert(FAILED_ASSERT_SIGNAL); } }而assert_eq/assert_ne则在失败时把参与比较的两个值都记录下来,帮助开发者定位是哪对值不匹配。标准库自身的注释也给出了使用建议(见 assert.sw):如果条件可能不为真(例如依赖外部输入),应优先使用require并携带错误信息;assert更适合表达"理应永远为真"的不变式。
实践选型建议
- 参数合法性校验(如余额不足、额度超限、地址非空):用
require,附带可读错误消息,方便前端与用户反馈; - 内部不变量(如算法中间步骤的正确性):用
assert,节省一条日志的 gas 开销; - 不可达分支 / 显式失败:用
revert(无条件回滚)或revert_with_log(无条件回滚并附带日志); - 需要对比两个值时:用
assert_eq/assert_ne,它们会自动记录双方取值。
七、实战要点:CEI 模式与外部调用中的require
在合约开发中,require最常见的应用场景是"检查-生效-交互"(Checks-Effects-Interactions,CEI)模式的第一环,即函数入口处的守卫条件。例如在转账类函数中:
fn withdraw(amount: u64) { require(amount > 0, "amount must be greater than zero"); require(balance_of(msg_sender().unwrap()) >= amount, "insufficient balance"); // 状态更新与外部交互…… }使用require时有几点值得注意:
- 失败即原子回滚:
require失败后整个事务状态回滚到调用前,不存在"部分成功"的中间态,因此非常适合前置校验; - 日志不会被回滚:Fuel 的日志(receipt)是回滚时仍会保留的记录信息,这正是
require能"既回滚又留下错误线索"的原理所在; - 消息内容选择:由于消息会留在链上日志中,建议写入简洁、稳定的错误码或短句,避免把敏感或易变的数据直接放入日志。
八、小结
require是 Sway 合约编写中最基础也最实用的安全原语:它由 prelude 自动导入、支持任意泛型类型的错误消息、失败时以专属信号FAILED_REQUIRE_SIGNAL回滚并留下日志。通过对照标准库 revert.sw、assert.sw 与 error_signals.sw 的源码实现,可以清晰看到它在整个断言体系中的定位与取舍。掌握require与assert、revert、assert_eq、assert_ne的适用场景,是写出可靠、高效且易于诊断的 Sway 智能合约的第一步。
【免费下载链接】sway🌴 Empowering everyone to build reliable and efficient smart contracts.项目地址: https://gitcode.com/GitHub_Trending/sw/sway
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考