Sway 智能合约断言机制(二):深入理解 `require` 的日志与回滚语义
2026/9/12 16:32:47 网站建设 项目流程

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的调用方式、泛型消息类型、底层回滚信号以及它与assertrevert系列函数的区别,帮助你写出更健壮、更易排查的智能合约。

一、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};

也就是说,requirerevertrevert_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 }

运行逻辑如下:

  1. 调用subtract(10, 5)b <= atrue,函数继续执行并返回5
  2. 调用subtract(5, 10)b <= afalse,日志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_SIGNALassert触发)、FAILED_ASSERT_EQ_SIGNALassert_eq触发)、FAILED_ASSERT_NE_SIGNALassert_ne触发)和REVERT_WITH_LOG_SIGNALrevert_with_log触发),它们共同构成了一套可区分的链上错误语义。

六、requireassertrevert家族对比

Sway 的断言体系在 Assertions 文档 中统称为"防止不期望计算发生"的手段,共包含五个函数。require与它们的关系如下:

函数行为失败时是否附带日志回滚信号
assert条件为假则回滚FAILED_ASSERT_SIGNAL
require条件为假则记录消息并回滚是(泛型消息)FAILED_REQUIRE_SIGNAL
revert无条件回滚,可指定退出码用户指定码
assert_eqa != b则回滚并记录两值是(两个输入值)FAILED_ASSERT_EQ_SIGNAL
assert_nea == b则回滚并记录两值是(两个输入值)FAILED_ASSERT_NE_SIGNAL

从实现上看,assert.sw 中的assertrequire结构几乎相同,只是省去了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时有几点值得注意:

  1. 失败即原子回滚require失败后整个事务状态回滚到调用前,不存在"部分成功"的中间态,因此非常适合前置校验;
  2. 日志不会被回滚:Fuel 的日志(receipt)是回滚时仍会保留的记录信息,这正是require能"既回滚又留下错误线索"的原理所在;
  3. 消息内容选择:由于消息会留在链上日志中,建议写入简洁、稳定的错误码或短句,避免把敏感或易变的数据直接放入日志。

八、小结

require是 Sway 合约编写中最基础也最实用的安全原语:它由 prelude 自动导入、支持任意泛型类型的错误消息、失败时以专属信号FAILED_REQUIRE_SIGNAL回滚并留下日志。通过对照标准库 revert.sw、assert.sw 与 error_signals.sw 的源码实现,可以清晰看到它在整个断言体系中的定位与取舍。掌握requireassertrevertassert_eqassert_ne的适用场景,是写出可靠、高效且易于诊断的 Sway 智能合约的第一步。

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

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

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

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

立即咨询