Sway 智能合约错误处理实战:Result 可恢复错误、panic 表达式与基于 ABI JSON 的零成本回溯机制

发布时间:2026/9/11 19:55:09
Sway 智能合约错误处理实战:Result 可恢复错误、panic 表达式与基于 ABI JSON 的零成本回溯机制 Sway 智能合约错误处理实战Result 可恢复错误、panic 表达式与基于 ABI JSON 的零成本回溯机制【免费下载链接】sway Empowering everyone to build reliable and efficient smart contracts.项目地址: https://gitcode.com/GitHub_Trending/sw/sway错误处理是编写可靠智能合约的核心能力之一。本指南基于 Sway 语言官方文档docs/book/src/basics/error_handling.md系统讲解 Sway 的两大类错误处理手段面向预期故障的可恢复错误ResultT, E与面向程序缺陷/不变量违背的不可恢复错误panic表达式。你将掌握panic的完整工作链路——编译器如何把错误信息与调用位置离线存放进 ABI JSON 的errorCodes与panickingCalls段、如何通过#[error_type]与#[trace]注解、以及backtrace构建选项控制回溯内容的粒度从而在几乎零链上成本的前提下获得丰富的故障诊断信息。一、错误处理的两大分类与 Rust 的设计哲学一致Sway 把错误分为两类可恢复错误Recoverable Errors代表预期中的故障例如余额不足、身份校验失败、参数非法。程序应当有机会捕获并处理这些错误而不是让整个交易直接回滚。不可恢复错误Irrecoverable Errors代表程序缺陷或违背不变量的情况例如断言失败、逻辑不可能到达的分支。此时程序已无法有意义地继续执行。选择哪种方式取决于语义可恢复错误用类型系统表达并传播不可恢复错误则触发 VM 级的 revert原子性地回滚交易中的全部状态变更且无法在 Sway 代码中被捕获或处理。二、可恢复错误ResultT, E2.1 类型定义类似 RustSway 通过标准库中的std::result::Result枚举来表达可恢复错误。其定义位于 sway-lib-std/src/result.sw/// Result is a type that represents either success (Ok) or failure (Err). pub enum ResultT, E { /// Contains the success value. Ok: T, /// Contains the error value. Err: E, }T与E是类型参数使Result可以泛化地与任何类型搭配使用。函数返回Result即表明此操作可能失败且失败是可恢复的。在std标准库中Result最典型的使用场景是Identity交互与密码学操作。2.2 使用示例定义一个返回Result的函数enum Version { Version1, Version2, } enum VersionError { InvalidNumber, } fn parse_version(version_number: u8) - ResultVersion, VersionError { match version_number { 1 Ok(Version::Version1), 2 Ok(Version::Version2), _ Err(VersionError::InvalidNumber), } }2.3 常用方法除了模式匹配标准库为Result提供了丰富的内建方法全部可在 sway-lib-std/src/result.sw 中查看实现方法行为是否可能 revertis_ok()判断是否为Ok变体返回bool否is_err()判断是否为Err变体返回bool否unwrap()提取Ok中的值若是Err则revert(0)是因此官方文档明确不鼓励使用unwrap_or(default)提取Ok中的值若是Err则返回提供的默认值否expect(msg)提取Ok中的值若是Err则先 log(msg, err)再 revert是从 result.sw 的源码可以看到unwrap的底层实现正是匹配到Err时调用revert(0)pub fn unwrap(self) - T { match self { Self::Ok(v) v, _ revert(0), } }更多关于Result与Option的细节可阅读 docs/book/src/basics/commonly_used_library_types.md。三、不可恢复错误panic表达式3.1 基本语法表达不可恢复错误的首选方式是panic表达式它接受一个参数可以是文本消息、任意值或错误类型实例if some_error_occurred { panic Some error has occurred.; }3.2 运行时与编译时行为panic在运行时中止并回滚整个程序的执行触发 VM 级 revert原子回滚交易中的所有状态变更。而其编译时行为才是它的精髓每个panic获得唯一的 revert code编译器为代码中遇到的每一个panic生成一个唯一的 revert 码并在 ABI JSON 的errorCodes段中创建一条条目记录该panic发生的源码位置包名、函数、文件、行、列以及错误消息。每个可能 panic 的函数调用生成条目对于每一个最终可能沿调用链触发panic的函数调用编译器在 ABI JSON 的panickingCalls段生成条目记录调用点位置与被调用的可能 panic 的函数。这两段 ABI JSON 条目errorCodes与panickingCalls组合起来可以在几乎为零的额外链上成本下提供包含错误位置和部分回溯partial backtrace的丰富排障信息。关键的成本原理在于生成的字节码中只包含 revert 指令错误消息与错误位置全部离线存储在 ABI JSON 文件中——这使得panic成为零链上成本的操作而回溯backtrace只有微乎其微的链上成本且可通过backtrace构建选项选择启用与否详见下文第五节。所谓部分回溯是指回溯最多包含五层函数调用。实际项目中更深的调用链很少见。此外你也可以通过backtrace构建选项与#[trace]属性精确选择哪些函数进入回溯报告。3.3 工具链的识别与输出Rust SDK、TypeScript SDK 以及forc test都能识别panic表达式生成的 revert code。例如某个 Sway 单元测试因上面的panic行而失败时forc test会输出类似以下的信息test some_test, /tests.sw:42 revert code: 8100000000000000 ├─ panic message: Some error has occurred. ├─ panicked: in some_package::some_error_occurred │ └─ at some_package1.2.3, src/some_module.sw:13:9 └─ backtrace: called in some_other_package::some_other_module::some_function └─ at some_other_package1.2.3, src/some_other_module.sw:106:11 called in my_project::some_test └─ at my_project, src/tests.sw:48:8上述输出的解读some_test调用了some_function后者又调用了some_error_occurred而some_error_occurred以消息 Some error has occurred. 触发了panic。forc test打印这些信息的渲染逻辑位于 forc/src/cli/commands/test.rs它从 revert 收据中解析出revert code再根据panic message、panic value、panicked位置与backtrace数组逐层打印其中内部实现细节__entry函数会被过滤掉不展示给用户。四、错误类型Error Types为 panic 附加结构化信息直接传文本消息是提供错误提示最方便的方式对许多场景已经足够。但有时我们需要为错误附带额外的运行时信息将某一族相关的错误分组。此时应使用错误类型error types。4.1 定义方式错误类型是以#[error_type]属性注解的枚举其所有变体都带#[error(m error message)]属性。每个变体代表一个特定错误整个枚举代表一族错误。命名约定是为错误类型枚举名加Error后缀。例如检查某个Identity是否拥有合约的特定访问权限时表示访问权违背的错误类型枚举可写成#[error_type] pub enum AccessRightError { #[error(m The provided identity is not an administrator.)] NotAnAdmin: Identity, #[error(m The provided identity is not an owner.)] NotAnOwner: Identity, #[error(m The provided identity does not have write access.)] NoWriteAccess: Identity, }其中每个Identity字段承载实际传入的身份值作为panic时的运行时附加信息。4.2 在代码中使用在代码中检查访问权并在违背时以错误类型实例触发panicfn do_something_that_requires_admin_access(admin: Identity) { if !is_admin(admin) { panic AccessRightError::NotAnAdmin(admin); } // ... }若上述函数存在失败的测试测试输出不仅会展示错误消息还会以panic value的形式展示传入的Identity。例如test some_test_for_admin_access, /test.sw:42 revert code: 8100000000000000 ├─ panic message: The provided identity is not an administrator. ├─ panic value: NotAnAdmin(Address(Address(79fa8779bed2f36c3581d01c79df8da45eee09fac1fd76a5a656e16326317ef0))) ├─ panicked: in auth_package::only_admin │ └─ at auth_package0.1.0, src/admin_access.sw:11:9 └─ backtrace: ...4.3 编译器的属性校验从源码看Sway 编译器在语义分析阶段sway-core/src/semantic_analysis/module.rs 的check_is_valid_error_type_enum对这两个属性做严格校验#[error_type]属性只能注解枚举且该枚举不能没有变体#[error(m ...)]属性只能注解带#[error_type]注解的枚举的变体见 sway-core/src/transform/attribute.rs 中的错误信息定义。校验通过后编译器还会为该枚举自动生成error_typemarker trait 的实现sway-core/src/semantic_analysis/ast_node/declaration/auto_impl/marker_traits.rs使错误类型可被编码、解码与 log。五、errorCodes与panickingCallsABI JSON 中的错误信息中心5.1 工作机制示例为说明这两段 ABI JSON 条目的作用考虑如下调用结构auth_package中定义了函数only_admin身份不是管理员时它触发paniconly_admin被guards_package中多个前置条件检查函数调用例如check_access_rights这些守卫函数被funds_contract使用。编译时ABI JSON 中会加入类似下面的errorCodes与panickingCalls条目errorCodes: { 0: { // Unique ID of a panic call. pos: { // Location in code, at which the panic call occurs. pkg: auth_package1.2.3, function: auth_package::admin_access::only_admin, file: src/admin_access.sw, line: 13, column: 9 }, logId: 10098701174489624218, // Log ID representing the AccessRightError enum // passed as an argument to panic. msg: null, }, // Other error codes for other panic calls. }, panickingCalls: { 1: { // Unique ID of a potentially panicking function call. pos: { // Location in code, at which the function call that might panic occurs. function: guards_package::preconditions::check_admin, // The caller function, check_admin. pkg: guards_package0.1.0, // Position within the check_admin where only_admin is called. file: src/preconditions.sw, line: 4, column: 9 }, function: auth_package::admin_access::only_admin // The called function, only_admin. }, // Other panicking calls. }要点errorCodes中每条pos记录panic发生的源码位置msg存放直接传入的文本消息字符串 panic 时非空logId存放传入的错误类型的 Log ID错误类型 panic 时非空用于解码 panic value。panickingCalls中每条pos记录可能触发 panic 的函数调用点的位置function字段记录被调用的、可能 panic 的函数全名。这些唯一的错误/调用 ID 会被编译器嵌入到对应panic所生成的 revert code 中。发生 revert 时SDK 与forc test等工具从收到的 revert code 中提取这些 ID再借助 ABI JSON 中的信息还原出丰富的排障细节。沿用上面的例子若测试失败forc test可能输出test some_test, /tests.sw:42 revert code: 8280000000000003 ├─ panic message: The provided identity is not an administrator. ├─ panic value: NotAnAdmin(Address(Address(79fa8779bed2f36c3581d01c79df8da45eee09fac1fd76a5a656e16326317ef0))) ├─ panicked: in auth_package::admin_access::only_admin │ └─ at auth_package1.2.3, src/admin_access.sw:13:9 └─ backtrace: called in guards_package::preconditions::check_admin └─ at guards_package0.1.0, src/preconditions.sw:4:9 called in guards_package::preconditions::check_access_rights └─ at guards_package0.1.0, src/preconditions.sw:23:13 called in guards_package::preconditions::check_preconditions └─ at guards_package0.1.0, src/preconditions.sw:57:9 called in Contract as Funds::transfer_funds └─ at funds_contract0.2.5, src/main.sw:22:95.2 源码级实现佐证在编译器内部这两段条目的数据源是类型检查与 IR 生成阶段收集的两组出现点occurrencePanicOccurrence代码中某个panic表达式的一次出现。sway-core/src/lib.rsPanicOccurrence中说明一条panic表达式可以对应多个 occurrence——panic message只有一条且带msgpanic concrete_value只有一条且带log_idpanic generic_value则会为每个单态化后的类型生成多条带不同log_id的 occurrence。每条PanicOccurrence恰好分配一个 revert code。PanickingCallOccurrence一次可能 panic 的函数调用。function字段是被调用的可能 panic 函数全名caller_function与位置信息记录调用点sway-core/src/lib.rs。泛型被调用方或泛型调用方同样可能为每个单态化类型生成多条 occurrence。每条PanickingCallOccurrence恰好分配一个 panicking call code。这两组映射PanicOccurrences、PanickingCallOccurrences类型定义见 sway-core/src/lib.rs由 IR 生成阶段填充sway-core/src/ir_generation/function.rs中相关的PanicOccurrence/PanickingCallOccurrence处理逻辑最终在 ABI 生成阶段被转换为 JSON 条目errorCodes由 sway-core/src/abi_generation/fuel_abi.rs 附近的逻辑生成panickingCalls由其中的generate_panicking_calls函数fuel_abi.rs生成。值得注意的是标准库的assert、assert_eq、require等宏最终也是通过带专用信号值的 revert 实现见 sway-lib-std/src/assert.sw 与 sway-lib-std/src/error_signals.sw因此同样会进入errorCodes/panickingCalls体系可以被工具链解析出友好信息。六、配置回溯内容Configuring the Backtrace Content6.1 默认构建debug / release下的差异回溯只带来极小的链上成本体现在字节码大小与 gas 消耗上。为让你可以连这点成本也按需取舍回溯通过backtrace构建选项配置且在debug与release构建中具有不同的默认值。考虑如下示例五个函数first、second、……、fifth依次相互调用最后的fifth调用一个失败的assert_eq从而触发panic。默认debug构建下失败的forc test输出类似为简洁省略包名与代码位置test some_test, test.sw:42 revert code: 8280000000000003 ├─ panic message: The provided expected and actual values are not equal. ├─ panic value: AssertEq(AssertEq { expected: 42, actual: 43 }) ├─ panicked: in std::assert::assert_eq │ └─ at std0.99.0, src/assert.sw:80:9 └─ backtrace: called in fifth └─ at ... called in fourth └─ at ... called in third └─ at ... called in second └─ at ... called in first └─ at ...默认release构建下回溯只包含assert_eq的直接调用者fifth... ├─ panicked: in std::assert::assert_eq │ └─ at std0.99.0, src/assert.sw:80:9 └─ backtrace: called in fifth └─ at ...差异从何而来关键在于#[trace]属性与backtrace构建选项的组合。6.2 用#[trace]属性直接影响回溯与#[inline]类似#[trace]属性可以标注所有有实现的函数同样带always与never两个参数#[trace(always)]指示编译器在默认release构建中也把被标注函数的调用纳入回溯。它应当用于标注守卫类函数例如assert、assert_eq、require、only_owner以及Option::unwrap这类方法。当这些函数 panic 时我们真正关心的是代码中哪一次调用失败了——例如给assert标注#[trace(always)]就能看到具体是哪一条assert调用失败。在标准库实现中assert等函数也正是这类带信号值 revert 的守卫函数见 sway-lib-std/src/assert.sw。#[trace(never)]指示编译器不要把某个函数纳入回溯——即使是在默认debug构建中它默认包含所有 panicking 调用。考虑到回溯最多只能包含五个函数如果某个中间函数调用很容易推断出来就有理由把它排除在回溯之外。例如假设fourth与second被标注为#[trace(never)]默认debug构建的回溯输出变为... ├─ panicked: in std::assert::assert_eq │ └─ at std0.99.0, src/assert.sw:80:9 └─ backtrace: called in fifth └─ at ... called in third └─ at ... called in first └─ at ...fifth与first之间跳过了被排除的fourth、third之后的second回溯链条依旧清晰。6.3 自定义构建backtrace构建选项若要改变默认行为使用backtrace构建选项可选值与含义如下表ValueMeaningallBacktrace all function calls, even of functions annotated with#[trace(never)].all_except_neverBacktrace all function calls, except those of functions annotated with#[trace(never)]. This is the default value fordebugbuilds.only_alwaysBacktrace only calls of functions annotated with#[trace(always)]. This is the default value forreleasebuilds.noneDo not backtrace any function calls. Use this option only if you need to fully remove the on-chain cost of backtracing. Considering how negligible the cost is, this will very likely never be needed.核心结论默认release构建只把标注了#[trace(always)]的函数调用纳入回溯从而把本就极低的回溯计算链上成本进一步压缩到只针对这些函数调用几乎零链上影响同时仍然提供有价值的排障信息。从源码看Backtrace是定义在 sway-core/src/build_config.rs 的枚举四个变体与上表一一对应其默认值为AllExceptNever#[default]标注forc-pkg在构造构建配置时debug与release配置分别使用Backtrace::AllExceptNever与Backtrace::OnlyAlways见 forc-pkg/src/manifest/build_profile.rs 与 build_profile.rs并最终在forc-pkg/src/pkg.rs的编译入口把该配置传给sway_core。6.4 在 Forc.toml 中自定义回溯行为自定义构建在Forc.toml的[build-profile.*]段配置完整字段说明见 docs/book/src/forc/manifest_reference.md。backtrace字段支持all、all_except_never、only_always、none四个值默认在debug与release配置中分别为all_except_never与only_always。示例覆盖默认debug配置把回溯收敛为仅标注#[trace(always)]的函数同时在自定义 profile 中彻底关闭回溯[project] authors [user] entry main.sw organization Fuel_Labs license Apache-2.0 name wallet_contract [build-profile.debug] backtrace only_always [build-profile.my_no_backtrace] backtrace none使用方法不传任何参数时使用默认debug配置传--release使用默认release配置使用自定义 profileforc build --build-profile my_no_backtrace或forc test --build-profile my_no_backtrace注意同时传入的对应 CLI 选项会覆盖所选 profile 中的对应设置。七、实战要点小结预期故障用ResultT, E把可能的失败建模为Ok(T)/Err(E)并传播、转换交易不立即回滚。程序缺陷用panicpanic表达式原子回滚整个交易且字节码中只含 revert 指令错误消息与位置全部离线存入 ABI JSON链上成本为零。需要结构化错误信息时用#[error_type]枚举为错误族分组、附加运行时值panic value并享受工具链自动解码。调试与线上权衡回溯成本debug默认回溯所有all_except_neverrelease默认仅回溯#[trace(always)]标注的守卫函数only_always必要时通过backtrace构建选项微调或为中间函数标注#[trace(never)]以压缩五层回溯的利用效率。善用工具链输出forc test、Rust/TypeScript SDK 会自动解析 revert code 中的错误 ID并结合 ABI JSON 渲染出错误位置与回溯可作为合约故障定位的一线手段。【免费下载链接】sway Empowering everyone to build reliable and efficient smart contracts.项目地址: https://gitcode.com/GitHub_Trending/sw/sway创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考