资讯详情

资讯详情

Sway 属性(Attributes)全解析:用元数据驱动 ABI 兼容、条件编译与安全校验

Sway 属性Attributes全解析用元数据驱动 ABI 兼容、条件编译与安全校验【免费下载链接】sway Empowering everyone to build reliable and efficient smart contracts.项目地址: https://gitcode.com/GitHub_Trending/sw/sway导读SwayFuel 智能合约语言的编译器支持以#[attribute]形式出现的元数据用来额外指示编译器以及forc test等工具的行为。本指南以官方文档 docs/book/src/reference/attributes.md 为骨架结合仓库源码逐条讲解 Sway 支持的 14 类属性包括控制 ABI JSON 输出的#[abi_name]、条件编译的#[cfg]、事件与索引日志、函数纯度#[storage]、可支付性#[payable]、回退函数#[fallback]以及测试与错误报告等。读完本文你将掌握每种属性的语法、适用目标、校验规则以及它们在编译期与 ABI 生成阶段的实际作用从而写出 ABI 向后兼容、行为可控、易于测试的智能合约。属性机制概述属性在编译器中的表示与校验在 Sway 中属性是附加在语言元素item、枚举变体、结构体字段等上的一种元数据形式。从源码看属性的语法模型定义在 sway-ast/src/attribute.rsAnnotatedT携带属性列表与被标注的值即带属性的元素。AttributeDecl一次属性声明一个#[...]可以包含任意多个Attribute每个Attribute又可以有任意多个AttributeArg参数参数还可以带值。AttributeHashKind区分内部属性#![...]标注其所在元素与外部属性#[...]标注紧随其后的元素。目前内部属性仅支持模块级 doc 注释//!。AttributeArg形如arg或arg valuevalue 是字面量如字符串、布尔。属性声明的合法写法包括#[attribute] #[attribute_1, attribute_2] #[attribute()] #[attribute(arg)] #[attribute(arg_1, arg_2)] #[attribute(arg_1 value, arg_2 true)] #[attribute_1, attribute_2(arg_1), attribute_3(arg_1, arg_2 true)]同一元素可以多次声明相同属性编译器取参数并集例如#[foo(bar)]与#[foo(baz, xyzzy)]等价于#[foo(bar, baz, xyzzy)]。AttributeKind 与校验管线编译期每个属性名会被映射为AttributeKind枚举定义于 sway-core/src/transform/attribute.rs包括Storage、Inline、Test、Payable、Allow、Cfg、Deprecated、Fallback、ErrorType、Error、Trace、AbiName、Event、Indexed、Require等。其中值得注意的机制Unknown 属性编译器不认识的属性会被归为AttributeKind::Unknown生成警告但仍会传递到类型化语法树typed tree以便第三方静态分析工具利用专有属性进行代码检查。多重性multiplicity某些属性只能标注一次如#[inline]、#[test]、#[fallback]而#[cfg]、#[allow]可以多次标注。当同一元素上出现多个同种类属性如重复#[deprecated]时编译器采用last-wins最后声明者生效策略。参数多重性每种属性对参数个数有硬性约束例如#[storage]允许 1~2 个参数、#[inline]恰好 1 个、#[payable]0 个、#[allow]至少 1 个、#[cfg]恰好 1 个。参数名校验ExpectedArgs枚举规定参数必须MustBeIn或应当ShouldBeIn是哪些名字违规时分别报错或告警。参数值约束ArgsExpectValues规定参数是否必须带值Yes如#[cfg(target fuel)]、No如#[storage(read, write)]、Maybe如#[test(should_revert)]可带可不带。这些校验贯穿于每个属性可标注的目标item 类型、结构体/枚举字段、ABI/trait 接口函数、impl 块内函数等检查中例如#[abi_name]只能标注结构体与枚举#[inline]只能标注有实现的函数ABI/trait 接口签名不能标注#[inline]因为接口没有实现。ABI Name重命名类型而不破坏 ABI 兼容性#[abi_name(name ...)]用于显式指定某个 item 在生成的 ABI JSON 中的名称。当代码中重命名了结构体或枚举又希望合约的 ABI 层保持不变例如保证下游消费者仍按旧名调用时即可用此属性钉住 ABI 名称。注意目前该属性仅支持枚举enum与结构体struct类型。原文档示例——将MyStruct/MyEnum重命名为RenamedMyStruct/RenamedMyEnum并用#[abi_name]保持 ABI 兼容contract; #[abi_name(name MyStruct)] struct RenamedMyStruct {} #[abi_name(name MyEnum)] enum RenamedMyEnum { A: () } abi MyAbi { fn my_struct() - RenamedMyStruct; fn my_enum() - RenamedMyEnum; } impl MyAbi for Contract { fn my_struct() - RenamedMyStruct { RenamedMyStruct{} } fn my_enum() - RenamedMyEnum { RenamedMyEnum::A } }生成的 ABI JSON 中concreteTypes仍以旧名出现{ concreteTypes: [ { concreteTypeId: 215af2bca9e1aa8fec647dab22a0cd36c63ce5ed051a132d51323807e28c0d67, metadataTypeId: 1, type: enum MyEnum }, { concreteTypeId: d31db280ac133d726851d8003bd2f06ec2d3fc76a46f1007d13914088fbd0791, type: struct MyStruct } ], ... }重命名前后生成的 ABI JSON 完全一致从而在 ABI 层面保持了向后兼容合约的消费者始终拿到的是原始名称。源码视角ABI 名称如何被读取与校验在 sway-core/src/abi_generation/fuel_abi.rs 中TypeId::get_abi_name_and_span_from_type_id会读取枚举/结构体声明的attributes.abi_name()取出第一个参数即name的字符串值随后在生成concreteTypes时若该名称是空字符串、不是合法标识符或路径、或以::开头编译器会报ABIInvalidName错误若两个不同类型映射到同一 ABI 名称会报ABIDuplicateName错误保证 ABI 名称唯一性。extract_abi_name_inner还会定位到name ...中字符串字面量的精确 span用于生成更精确的诊断信息。这正是重命名后 ABI 名称保持不变的底层实现依据。Event 与 Indexed可检索的合约事件#[event]将结构体或枚举标记为合约可发出的事件#[indexed]则应用于#[event]结构体的字段使该字段在事件日志中可被高效过滤与检索。#[event] struct MyEventStruct { #[indexed] id: u64, sender: Identity, } #[event] enum MyEventEnum { A: (), B: (), }使用约束顺序约束#[indexed]字段必须连续地位于结构体最前面的一组字段中之后才能出现非 indexed 字段。违反此约束将触发编译错误。类型约束只有精确尺寸exact size的 ABI 类型才能被标记为#[indexed]包括boolu8、u16、u32、u64、u256numericb256Addressstr[N]只包含精确尺寸类型的元组只包含精确尺寸类型的结构体长度是字面量的精确尺寸类型数组指向精确尺寸类型的类型别名另外#[event]会使事件类型被包含在合约的 JSON ABI 表示loggedTypes中。源码视角indexed 的三重校验与 ABI 输出在 sway-core/src/semantic_analysis/ast_node/declaration/struct.rs 中对每个字段依次检查若字段带#[indexed]但所在结构体没有#[event]报IndexedFieldInNonEventStruct错误若已出现过非 indexed 字段后再遇到 indexed 字段报IndexedFieldMustPrecedeNonIndexedField错误若 indexed 字段的类型不是精确尺寸 ABI 类型AbiEncodeSizeHint::Exact(_)报IndexedFieldIsNotFixedSizeABIType错误。在 sway-core/src/abi_generation/fuel_abi.rs 的generate_logged_types中合约的所有logged_types会被去重按log_id后写入 ABI JSON 的loggedTypes数组而 indexed 字段的大小信息update_indexed_field_offset用于在 IR 生成阶段sway-core/src/ir_generation/function.rs计算事件索引区的偏移布局。仓库自带的端到端测试 test/src/e2e_vm_tests/test_programs/should_pass/events/src/main.sw 展示了同时使用普通事件结构与双 indexed 字段事件结构并通过log(...)发出事件的完整用法。Cfg条件编译#[cfg(...)]使被标注的代码元素仅在条件为真时参与编译支持以下三种条件文档以恰好一个参数为约束#[cfg(target target)]target取evm或fuel#[cfg(program_type program_type)]program_type取predicate、script、contract或library#[cfg(experimental_feature_flag true/false)]feature_flag是已收录的实验特性之一。与其它属性不同#[cfg]允许在同一元素上多次标注allows_multiple为true便于叠加多个编译条件。在 sway-core/src/transform/attribute.rs 中Cfg属性期望的参数列表由program_type、target及Feature::CFG中登记的所有实验特性标志按字母序构成参数必须带值ArgsExpectValues::Yes例如#[cfg(target fuel, experimental_new_encoding false)]。Allow 与 Deprecated警告抑制与弃用标记Allow#[allow(...)]用于关闭编译器的部分检查使对应警告不再上报。目前支持两种#[allow(dead_code)]关闭死代码未使用的代码警告#[allow(deprecated)]关闭对已弃用元素如结构体、函数、枚举变体等使用的警告。#[allow]需要至少一个参数at_least(1)参数名采用应当为ShouldBeIn校验即传未知参数名时仅告警而非报错这为编译器未来新增可禁用的警告类型保留了兼容空间。死代码与弃用警告的检查逻辑分别位于 sway-core/src/control_flow_analysis/dead_code_analysis.rs 等分析模块中。Deprecated#[deprecated]将元素标记为已弃用编译器对每一处使用都会发出警告。可附加说明信息以改善警告可读性#[deprecated(note Your deprecation message.)]该警告可通过#[allow(deprecated)]关闭。源码中DEPRECATED_NOTE_ARG_NAME即note参数必须带值ArgsExpectValues::Yes。目前弃用属性只支持结构体、枚举、函数、常量以及错误Error等元素尚未覆盖模块、trait、impl、存储storage、configurable、类型别名等目标对应实现中的 TODO 项。Error 与 Error Type富错误报告#[error_type]将一个枚举标记为错误类型枚举其所有变体都必须用#[error]标注错误消息。此类枚举用于panic表达式以实现丰富的错误报告。#[error_type] enum SomeErrors { #[error(m An unexpected error occurred.)] UnexpectedError: (), ... }#[error_type]只能标注枚举且不能带参数#[error]只能标注#[error_type]枚举的变体在字段层面仅允许枚举字段参数m是错误消息字符串必须带值语义分析器通过Attributes::error_message()见 sway-core/src/transform/attribute.rs读取#[error]的m参数将其作为 panic 时的错误说明。关于panic与不可恢复错误的更多细节参见 不可恢复错误。Fallback合约调用回退函数#[fallback]将标记的函数指定为合约的调用回退函数。当外部调用某合约方法时若方法选择method selection失败将改而调用该回退函数。该属性只能标注合约模块中的模块级函数且不能带参数。完整的行为说明见 调用合约。Inline函数内联提示#[inline(...)]建议编译器将标注函数的一份拷贝内联到调用点而不是生成对函数定义处的调用#[inline(never)]建议永不进行内联展开#[inline(always)]建议总是进行内联展开。注意#[inline(..)]的任何形式都只是提示hint编译器没有义务照做——Sway 编译器会根据内部启发式自动决定内联。错误的内联可能反而使程序变慢因此应谨慎使用。该属性恰好带一个参数never或always只能标注有实现的函数ABI/trait 接口签名没有实现体因此不能标注。在源码中Attributes::inline()按 last-wins 解析参数并映射为Inline::Never/Inline::Always。Payable可支付方法与零费用转发检查Sway 的 ABI 方法默认是不可支付non-payable的当调用一个非 payable 的 ABI 方法时若编译器无法保证调用所转发的币数量为零就会报错。注意这只是编译期检查不会带来任何运行时开销。只有被#[payable]标注的方法才允许转发非零数量的代币。abi MyAbi { #[payable] fn mint(); fn burn(); // 非 payable调用时转发的币数量必须保证为零 }源码约束sway-core/src/transform/attribute.rs#[payable]只能标注——ABI 函数签名及其在合约中的实现、以及 ABI 提供的函数不能标注 trait 接口函数且不能带任何参数。仓库示例 examples/native_asset/src/main.sw 中的铸币函数即使用了#[payable]允许在铸造原生资产时附带代币。Storage函数纯度声明Sway 中函数默认是**纯pure**的通过storage函数属性显式声明不纯#[storage(read)]表示函数需要读取存储#[storage(write)]表示需要写入存储。两者可以组合#[storage(read)] fn get_counter() - u64 { ... } #[storage(read, write)] fn increment() { ... }#[storage]允许 1~2 个参数参数名为read与write均不可带值。源码中Attributes::purity()会把read/write组合解析为Purity::Reads、Purity::Writes或Purity::ReadsWritesread write缺省为Purity::Pure若声明了两个相同参数如read, read按 last-wins 只会得到一个结果。纯度机制如何影响存储访问与 CEI 检查详见 纯度。Test单元测试标记#[test]将函数标记为测试函数由forc test等工具执行#[test]作为普通测试执行#[test(should_revert)]作为应当回滚的测试执行——函数执行后必须发生 revert 才算通过。#[test]最多带一个参数should_revert且可以附加期望的 revert 码值例如#[test(should_revert 18446744073709486084)]参数值可选。测试只能标注模块级函数。完整的单元测试编写与运行方式见 单元测试。Tracingpanic 回溯Backtrace控制tracing 属性告诉编译器当panic表达式引发 revert 时某函数是否应出现在回溯backtrace中。文档章节名为 Tracing实际属性名为#[trace(...)]源码常量TRACE_ATTRIBUTE_NAME trace见 sway-ast/src/attribute.rs#[trace(never)]告知编译器不要将该函数纳入回溯除非backtrace构建选项被设为all#[trace(always)]告知编译器总是将该函数纳入回溯除非backtrace构建选项被设为none。该属性恰好带一个参数never或always只能标注有实现的函数ABI/trait 接口签名不能标注。更多关于 panic 回溯的行为说明参见 不可恢复错误。属性速查表下表汇总各属性的关键约束依据 sway-core/src/transform/attribute.rs 中args_multiplicity/expected_args/args_expect_values与can_annotate_*系列方法整理属性用途参数可标注目标#[abi_name(name ...)]指定 ABI JSON 中的类型名恰好 1 个name必带值结构体、枚举#[allow(dead_code \| deprecated)]关闭指定警告至少 1 个不可带值大部分 item、字段、接口元素#[cfg(...)]条件编译恰好 1 个必带值可重复标注大部分 item、字段、接口元素#[deprecated(note ...)]弃用标记0~1 个note带值时必须为字符串结构体、枚举、函数、常量等#[error(m ...)]错误枚举变体的消息恰好 1 个m必带值#[error_type]枚举的变体#[error_type]标记错误类型枚举0 个枚举#[event]标记可发出的事件类型0 个结构体、枚举#[indexed]事件字段可检索0 个#[event]结构体的前置字段#[fallback]合约调用回退函数0 个合约模块中的函数#[inline(never \| always)]内联提示恰好 1 个不可带值有实现的函数#[payable]允许转发非零代币0 个ABI 函数签名及其合约实现、ABI 提供的函数#[storage(read[, write])]声明存储访问纯度1~2 个不可带值函数含 ABI/trait 接口签名#[test(should_revert?)]标记测试函数0~1 个值可选模块级函数#[trace(never \| always)]控制 panic 回溯恰好 1 个不可带值有实现的函数从源码到实践属性校验的完整路径一个属性从书写到生效大致经历以下阶段可依此排查属性不生效或报错的问题词法/语法解析sway-parse依据 sway-ast/src/attribute.rs 的模型将#[...]解析为AttributeDecl序列转换与归类sway-core/src/transform/attribute.rs 将属性名映射为AttributeKind并执行参数个数、参数名、参数值三类校验同时检查该属性是否允许标注当前目标item / 字段 / 接口函数 / impl 内函数等语义分析各声明检查器如结构体检查器对#[indexed]的三重校验进一步做类型级约束检查代码生成 / ABI 生成属性影响 IR 生成如内联、纯度、事件索引布局与 ABI JSON 输出如abi_name重命名、loggedTypes收录事件类型。仓库的端到端测试目录test/src/e2e_vm_tests/test_programs/should_fail/下还有多组专门验证属性校验规则的用例如attributes_invalid_args、attributes_invalid_multiplicity、attributes_invalid_args_multiplicity、attributes_invalid_args_expect_values它们以应失败should_fail的方式锁定上述校验行为should_pass/events则从正面验证了事件与索引属性的正确用法。小结Sway 属性系统为合约开发提供了从 ABI 兼容#[abi_name]、事件检索#[event]#[indexed]、条件编译#[cfg]、安全校验#[payable]、#[storage]、运行时兜底#[fallback]到质量保障#[test]、#[deprecated]、#[error_type]、#[trace]的一整套编译期元数据能力。掌握每种属性的参数约束与适用目标并在源码校验规则sway-core/src/transform/attribute.rs的指导下使用即可充分利用编译器保障写出更健壮、更易维护、对下游更友好的 Sway 智能合约。【免费下载链接】sway Empowering everyone to build reliable and efficient smart contracts.项目地址: https://gitcode.com/GitHub_Trending/sw/sway创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →