资讯动态

fuels-rs 脚本交易解码指南:用 ScriptType 与 AbiFormatter 让 Fuel 交易可读可查

发布时间:2026/9/10 14:56:45 来源:尧图企业网站定制
fuels-rs 脚本交易解码指南用 ScriptType 与 AbiFormatter 让 Fuel 交易可读可查【免费下载链接】fuels-rsFuel Network Rust SDK项目地址: https://gitcode.com/GitHub_Trending/fu/fuels-rsFuel Network Rust SDKfuels-rs中智能合约方法调用最终会被编译为链上的脚本交易Script Transaction一段由 FuelVM 指令构成的script字节码加上打包了调用参数的script_data数据段。对开发者而言这类交易本质上是一堆难以阅读的字节。本指南将讲解 SDK 提供的解码工具——ScriptType::detect与ABIFormatter它们能把脚本交易归类普通合约调用 / 带 Blob ID 的 Loader 脚本 / 其他脚本并在持有 ABI 文件的前提下把函数选择器与编码参数还原为人类可读的文本。读完本文你将能在拿到任意Transaction对象后快速分析其内部动作例如还原出“这条脚本调用了initialize_counter(42)”。为什么需要解码脚本交易Fuel 的脚本交易结构对人不透明Transaction中Script变体由以下关键字段构成见 debug.rs 中的parse_script_callscript脚本字节码Vecu8包含若干条 FuelVM 指令script_data随脚本携带的输入数据段合约调用的金额、资产 ID、合约 ID、函数选择器与编码参数等都打包在这里。直接阅读这些字节几乎不可能还原业务语义。SDK 因此在packages/fuels-programs/src/debug.rs提供了脚本“翻译层”让交易中的脚本对调试与审计场景更友好。三类脚本的判定ScriptType枚举判定入口是ScriptType::detect(script, script_data)定义于 debug.rs。它把任何脚本交易归入三种情况之一pub enum ScriptType { ContractCall(VecContractCallData), // 调用了合约方法携带解码后的调用数据 Loader { // 是 Loader 脚本能看到对应 Blob script: ScriptCallData, blob_id: [u8; 32], }, Other(ScriptCallData), // 两者皆非的普通脚本 }ScriptType::detect的判定顺序debug.rs如下尝试把脚本解析为合约调用序列parse_contract_calls若成功则返回ContractCall否则尝试解析为Loader 脚本parse_loader_script并取出其中的 Blob ID都不匹配时兜底为Other仅保留原始字节与数据段偏移信息。也就是说一次调用能让你同时回答文档中的三个问题这条脚本调用了合约方法吗它是 Loader 脚本吗若是Blob ID 是什么还是两者都不是合约调用的识别原理parse_contract_calls首先用fuel_asm::from_bytes把脚本字节解码为指令序列随后通过 contract_call.rs 中的ContractCallInstructions::extract_from反复匹配 SDK 为合约调用生成的固定指令模板。该模板以CALL指令为核心op::call(0x10, 0x11, 0x12, 0x13)配套的MOVI/LW指令分别从script_data中加载 call data 偏移、转账金额、资产 ID 与可选的转发 gas。SDK 会同时尝试“无转发 gas”与“有转发 gas”两种变体contract_call.rs。识别出指令序列后再按偏移把script_data切分为每一笔调用的独立数据块交给ContractCallData::decode解析contract_call.rs。ContractCallData结构清晰对应编码布局pub struct ContractCallData { pub amount: u64, // 转发金额 pub asset_id: AssetId, // 转发资产 ID pub contract_id: ContractId, // 被调用的合约 ID pub fn_selector_encoded: Vecu8, // 编码后的函数选择器方法名字符串 pub encoded_args: Vecu8, // 编码后的方法参数 pub gas_forwarded: Optionu64, // 转发的 gas可选 }script_data中每段调用数据的布局为详见ContractCallData::decode的消费顺序金额、资产 ID、合约 ID、函数选择器偏移、参数偏移、函数选择器长度、函数选择器本体、编码参数最后是可选的前转 gas。字段的消费通过 cursor.rs 中的WasmFriendlyCursor完成确保在wasm目标下同样可用。健壮性设计解析器对“不标准”的输入并不苛刻但有一定底线这点可从 debug.rs 的单元测试读出空脚本与随机损坏字节不会 panic而是归入Other脚本中混入额外指令除末尾的RET外会被拒绝识别为合约调用函数选择器含非法 UTF-8 时decode_fn_selector会返回 Codec 错误而不是崩溃数据段不足如 Blob ID 缺失会返回带“not enough data”说明的错误越界的 call data offset 会返回明确的数据长度诊断信息。Loader 脚本与 Blob ID当一条交易加载的是以 Blob 形式部署的合约/脚本即所谓 Loader 脚本时ScriptType::detect会返回Loader { script, blob_id }变体。识别通过 script_and_predicate_loader.rs 中的LoaderCode::from_loader_binary完成它把脚本前若干条指令与 SDK 生成的 Loader 指令模板无 configurables 时为 8 条、含 configurables 时为 12 条逐字节比对匹配成功后紧跟其后的 32 字节即 Blob IDscript_and_predicate_loader.rs。需要留意的是这些解析逻辑均假定二进制由 Sway 编译器生成——模块文档明确提示见 script_and_predicate_loader.rs手工构造的非标准二进制可能导致意外结果。检测到 Loader 脚本后得到的blob_id: [u8; 32]可直接用于链上定位对应的 Blob进而追踪被加载的大体积合约代码。实战从交易还原方法调用完整示例位于 examples/contracts/src/lib.rs 的decoding_script_transactions测试本文下方代码即该示例正文。流程分四步发起一次合约调用拿到交易 ID通过 Provider 拉取交易并取出TransactionType::Script变体用ScriptType::detect识别其中的合约调用序列读取合约 ABI 文件用ABIFormatter把函数选择器与参数解码成字符串。use fuels::prelude::*; // 1. 发起合约调用并拿到交易 ID此处已部署 contract_test 合约 let tx_id contract_instance .methods() .initialize_counter(42) .call() .await? .tx_id .unwrap(); let provider: Provider wallet.provider(); // 2. 取回交易并断言它是脚本交易 let TransactionType::Script(tx) provider .get_transaction_by_id(tx_id) .await? .unwrap() .transaction else { panic!(Transaction is not a script transaction); }; // 3. 判定脚本属于合约调用 let ScriptType::ContractCall(calls) ScriptType::detect(tx.script(), tx.script_data())? else { panic!(Script is not a contract call); }; // 4. 读 ABI 并解码函数名与参数 let json_abi std::fs::read_to_string( ../../e2e/sway/contracts/contract_test/out/release/contract_test-abi.json, )?; let abi_formatter ABIFormatter::from_json_abi(json_abi)?; let call calls[0]; let fn_selector call.decode_fn_selector()?; let decoded_args abi_formatter.decode_fn_args(fn_selector, call.encoded_args.as_slice())?; eprintln!( The script called: {fn_selector}({}), decoded_args.join(, ) );运行后打印The script called: initialize_counter(42)示例依赖的合约源码位于 e2e/sway/contracts/contract_test其 ABI JSON 由forc build生成在out/release/contract_test-abi.json。这里的fn_selector取自ContractCallData::decode_fn_selector它把脚本数据中的选择器字节按 UTF-8 还原为 Sway 方法名字符串Fuel 的函数选择器是方法名(参数类型)的哈希截断具体生成方式见 文档 与其配套示例若该脚本实际调用了多个方法calls会包含多个元素遍历即可逐个还原。ABIFormatter参数级解码的关键在只拿到原始字节时“42到底代表什么”无从得知。这正是ABIFormatter位于 abi_formatter.rs的职责——它把 ABI 文件解析成可供解码的类型表然后依据函数签名把encoded_args中的原始字节解码为可打印字符串。ABIFormatter提供两条构造入口// 从已解析的 ABI 程序对象构造 pub fn from_abi(abi: UnifiedProgramABI) - ResultSelf // 直接读取 JSON 格式的 ABI 字符串即 -abi.json 文件内容 pub fn from_json_abi(abi: impl AsRefstr) - ResultSelf构造过程会把 ABI 中每个函数的inputs参数类型应用与configurables分别解析为ParamType列表缓存起来并用默认ABIDecoder准备解码。核心解码方法pub fn decode_fn_argsR: Read(self, fn_name: str, data: R) - ResultVecStringfn_name期望调用的方法名实际编码中即ContractCallData::fn_selector_encoded解码出的字符串dataContractCallData::encoded_args原始字节切片返回值每个参数解码后的调试字符串VecString可直接用join(, )拼成initialize_counter(42)这样人类可读的调用。底层通过ABIDecoder::decode_multiple_as_debug_str完成abi_formatter.rs它会对VecParamType与数据逐类型执行解码并格式化为调试文本。需要注意的错误语义如果传入的fn_name不在 ABI 中decode_fn_args会返回Codec错误Function {name} not found in the ABI这一点由 abi_formatter.rs 中的单测gracefully_handles_missing_fn明确固化可以作为你程序里的错误分支判断依据。同时解码 ConfigurablesABIFormatter不只处理函数参数也能解码合约的configurables可配置常量。对应方法为pub fn decode_configurablesR: Read(self, configurable_data: R) - ResultVec(String, String)它把存储了可配置常量值的数据段按 ABI 中声明按offset升序解码返回(常量名, 解码值)的列表abi_formatter.rs。文档同时提示可参考rust docs获取关于 configurables 解码的进一步说明对于 Loader 脚本场景ScriptCallData会记录data_section_offset指向数据段含 configurables 段在脚本字节中的偏移debug.rs可辅助定位待解码数据。from_abi构造时还支持通过with_decoder_config(DecoderConfig)自定义解码器的行为如最大解码深度等满足对嵌套类型或大数据的限制需求。使用建议与边界综合源码与示例给出几条务实建议ABI 是“人可读”的前提ScriptType::detect不依赖 ABI能可靠地告诉你交易“调用了合约”还是“加载了 Blob”但要把参数还原成initialize_counter(42)这样的文本必须持有对应合约的-abi.json。合约源码发生变化导致 ABI 与链上历史交易不匹配时解码会失败或失真因此在离线分析历史交易时请使用与部署版本一致的 ABI 文件。tx.script()与tx.script_data()直接来自交易示例展示的是“先调用、再拉取”的在线分析同样地把任意已上链的 Script 交易取回后即可离线执行ScriptType::detect适合做区块浏览、审计与调试工具。多调用脚本会返回VecContractCallData逐元素解码即可。区分“能解析”与“是合约调用”从源码测试可见只要脚本含无法匹配的额外指令SDK 就不会把它识别为合约调用见extra_instructions_in_contract_calling_scripts_not_tolerated测试。解析失败不代表交易有问题只是它不属于 SDK 生成的调用模式会落入Other。错误消息已经面向排障优化数据段被截断、偏移越界等场景都带有明确的诊断信息见 debug.rs 与catches_missing_data测试的报错文案调试时可以直接引用这些错误字符串做定位。小结ScriptType::detect与ABIFormatter构成了 fuels-rs 的脚本交易“可读化”能力前者基于指令模式匹配与数据段解析把脚本归类为合约调用 / Loader 脚本 / 其他三类并还原ContractCallData后者借助 ABI 类型信息把函数选择器和编码参数解码为可读文本并支持解码 configurables。二者组合即可在拿到一条 Fuel 脚本交易后快速回答“它调了什么、传了什么参数、是否加载了哪个 Blob”非常适合用于交易调试、链上数据审计与工具链开发。核心实现分别位于 debug.rs、contract_call.rs、script_and_predicate_loader.rs 与 abi_formatter.rs配套端到端示例见 examples/contracts/src/lib.rs。【免费下载链接】fuels-rsFuel Network Rust SDK项目地址: https://gitcode.com/GitHub_Trending/fu/fuels-rs创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价