首页
/ fuels-rs ABI 编码详解:ABIEncoder、calldata! 宏与 EncoderConfig 资源限制

fuels-rs ABI 编码详解:ABIEncoder、calldata! 宏与 EncoderConfig 资源限制

2026-09-05 19:28:49作者:羿妍玫Ivan

本篇技术指南基于 fuels-rs 官方文档中的 Encoding 章节展开,系统讲解 Fuel Network Rust SDK 中参数 ABI 编码的完整链路:从 Tokenizable 前置转换、ABIEncoder 基础用法、calldata! 快捷宏,到 EncoderConfigmax_depth/max_tokens 资源限制机制,以及如何为合约调用(contract calls)单独配置编码器。读完本文,你将掌握复制即可运行的编码示例、字节级编码规则的来源依据,以及从源码层面理解编码器如何防止资源耗尽。

编码前置条件:先转换为 Token

编码与解码均遵循 Fuel 官方的 ABI argument encoding 规范。在 fuels-rs 中,编码一个类型的前提是先把它转换成 Token,而这通常通过实现 Tokenizable trait 来完成;解码时则还需要 ParamType 描述类型结构,通常由 Parameterize trait 提供。详见 Codec 章节前置说明

具体而言:

  • 所有由 abigen! 宏生成的类型都同时实现了 TokenizableParameterize,因此合约方法参数可以直接参与编码;
  • fuels 为自身拥有的类型(AddressAssetIdContractIdBits256 等)以及部分外部类型(u8u16std::vec::Vec<T: Tokenizable> 等)提供了 Tokenizable 实现,定义见 tokenizable.rs

Tokenizable 的核心接口只有两个方法(见 traits 定义):

  • from_token(token: Token) -> Result<Self>:从 Token 还原出预期类型(解码方向使用);
  • into_token(self) -> Token:把类型转换为 Token(编码方向使用)。

此外,两个 trait 都可以通过派生宏用于 structenum(前提是所有内层类型也实现了对应 trait),派生细节参见 Codec 章节的 Deriving the traits 小节

使用 ABIEncoder 编码参数

编码由 ABIEncoder 完成。最典型的用法是:先对结构体派生 Tokenizable,再用 into_token() 将实例转为 Token,最后交给编码器:

use fuels::{
    core::{codec::ABIEncoder, traits::Tokenizable},
    macros::Tokenizable,
};

#[derive(Tokenizable)]
struct MyStruct {
    field: u64,
}

let instance = MyStruct { field: 101 };
let _encoded: Vec<u8> = ABIEncoder::default().encode(&[instance.into_token()])?;

以上代码完整来自官方示例 examples/codec/src/lib.rsencoding_a_type 测试),可直接复制运行。

从源码结构看,ABIEncoder 只是一个持有 EncoderConfig 的轻量包装器:

#[derive(Default, Clone, Debug)]
pub struct ABIEncoder {
    pub config: EncoderConfig,
}

impl ABIEncoder {
    pub fn new(config: EncoderConfig) -> Self {
        Self { config }
    }

    /// Encodes `Token`s following the ABI specs defined
    /// [here](https://github.com/FuelLabs/fuel-specs/blob/master/specs/protocol/abi.md)
    pub fn encode(&self, tokens: &[Token]) -> Result<Vec<u8>> {
        BoundedEncoder::new(self.config).encode(tokens)
    }
}

abi_encoder.rs。真正干活的是 BoundedEncoder(见下文“资源限制”一节),它逐 token 序列化并按配置检查深度与 token 数上限。

calldata! 快捷宏

对多个实现 Tokenizable 的类型,可以直接使用 calldata! 宏一步完成“转 Token + 编码”,免去手写 into_token()

use fuels::{core::codec::calldata, macros::Tokenizable};

#[derive(Tokenizable)]
struct MyStruct {
    field: u64,
}

let _: Vec<u8> = calldata!(MyStruct { field: 101 }, MyStruct { field: 102 })?;

示例位于 examples/codec/src/lib.rs。该宏的展开逻辑非常简单(见 function_selector.rs):

#[macro_export]
macro_rules! calldata {
    ( $($arg: expr),* ) => {
        ::fuels::core::codec::ABIEncoder::default().encode(&[$(::fuels::core::traits::Tokenizable::into_token($arg)),*])
    }
}

值得注意的是源码注释明确说明:此宏固定使用默认的 EncoderConfig,不能自定义限制——如果需要在调用链中使用自定义配置,应走 ABIEncoder::new(config) 或后文的 with_encoder_config

编码器产出的字节长什么样

fuels-rs 在 abi_encoder.rs 的测试模块 中用大量用例锁定了编码的字节级行为,这些测试既是官方实现事实,也可以作为你排查编码问题时的参照:

类型 编码结果 依据(测试名)
bool 1 字节(1 表示 true) encode_bool
u8/u16/u32/u64/u128/u256 大端序裸字节,1/2/4/8/16/32 字节,无长度前缀 encode_multiple_uint
b256 32 字节原始数据 encode_b256
bytes / String / RawSlice 8 字节长度前缀 + 数据 encode_bytesencode_stringencode_raw_slice
str[N](固定长度字符串) 定长字节,无长度前缀 encode_string_array
字符串切片 8 字节长度前缀 + 数据(与 bytes 一致) encode_string_slice
元组 / 数组 / 结构体 各成员顺序拼接,无前缀 encode_tupleencode_arrayencode_nested_structs
vector 8 字节长度前缀 + 元素顺序拼接 vec_in_structvec_in_vec
枚举 8 字节判别符(大端)+ 选中变体载荷 enum_in_vec
仅含单元变体的枚举 只编码 8 字节判别符(“one word”) enums_with_only_unit_variants_are_encoded_in_one_word

encode_multiple_uint 为例,把 u8::MAXU256::MAX 依次编码后,输出是各自位宽对应的全 0xFF 字节序列,总长度 1+2+4+8+16+32 = 63 字节。而 encode_enum_with_deeply_nested_types 展示了嵌套场景:TopLevelEnum::v1(StructA{...}) 的最终字节 = 顶层判别符(8B,值 0)+ 内层枚举判别符(8B,值 1)+ str[10] 内容 + u32 字段,共 30 字节,与预期数组逐一相等。

配置编码器:EncoderConfig 与资源限制

编码器可以被配置以限制其资源消耗,防止恶意或意外的深层嵌套/token 爆炸导致编码过程失控:

use fuels::core::codec::ABIEncoder;

ABIEncoder::new(EncoderConfig {
    max_depth: 5,
    max_tokens: 100,
});

示例来自 examples/codec/src/lib.rsconfiguring_the_encoder 测试)。

EncoderConfig 的默认值定义在 abi_encoder.rs

impl Default for EncoderConfig {
    fn default() -> Self {
        Self {
            max_depth: 45,
            max_tokens: 10_000,
        }
    }
}

两个字段的确切语义由源码注释给出(见 EncoderConfig 定义):

  • max_depth(默认 45):每进入一个 struct、数组、元组、enumvector 深度加 1;若当前深度超过该值,编码立即失败;
  • max_tokens(默认 10,000):每个被编码的参数都会增加 token 计数;若超过该值,编码失败。

底层实现是 BoundedEncoder 中的两个计数器(见 bounded_encoder.rs):

  • encode_token每个 token 先调用 token_tracker.increase(),超限即返回 Error::Codec
  • TupleArrayVectorStructEnum 等复合 token,则通过 run_w_depth_trackingdepth_tracker.increase() 再递归编码、编码后 decrease(),保证深度是“栈式”的进出计数;
  • 计数器逻辑在 utils.rsincrease()count > max 时返回统一格式的错误信息,例如 depth limit 2 reached while encoding. Try increasing it

这个错误行为有专门的测试验证:max_depth_surpassed(见 abi_encoder.rs)构造了嵌套 3 层(深度 3)的 struct / enum / tuple / array,在 max_depth: 2 下断言编码必然失败且错误信息精确匹配。也就是说:当你在生产环境中看到 “depth limit ... reached while encoding” 时,解法就是把该次编码使用的 max_depth 调大

为合约/脚本调用配置编码器

实际业务中编码通常发生在合约方法调用内部。fuels-rs 允许为合约实例单独指定编码器配置,官方示例如下(来自 examples/contracts/src/lib.rs):

let _ = contract_instance
    .with_encoder_config(EncoderConfig {
        max_depth: 10,
        max_tokens: 2_000,
    })
    .methods()
    .initialize_counter(42)
    .call()
    .await?;

该示例的完整上下文是一个 #[tokio::test]configure_encoder_configexamples/contracts/src/lib.rs):通过 setup_program_test! 生成钱包、abigen! 合约并部署后,在调用链上以 builder 风格插入 with_encoder_config,本次 initialize_counter(42) 调用的参数编码就会使用 max_depth: 10max_tokens: 2_000 的限制,而全局默认值(45 / 10,000)不受影响。

从代码生成器角度看,这是 abigen! 生成绑定代码的内建能力:生成的合约绑定中,实例持有 encoder_config 字段并在构造时初始化为 EncoderConfig::default(),同时暴露 with_encoder_config 方法进行替换(见 contract.rs 代码生成模板);生成的 methods() 会携带该配置的克隆进入每次方法调用。scripts.rs 的生成模板同样包含该机制,因此脚本调用(script calls)也提供完全相同的方法——这与文档“The same method is available for script calls”的说明一致。

小结与延伸阅读

  • 编码流程固定为:类型 →(Tokenizable::into_token)→ Token →(ABIEncoder/calldata!)→ Vec<u8>,遵循 Fuel ABI 规范;
  • ABIEncoder::new(EncoderConfig { .. }) 用于显式限制单次编码的深度与 token 数,默认值为 max_depth: 45max_tokens: 10_000,超限会以 Error::Codec 报错并提示调大限制;
  • 合约/脚本绑定自带 with_encoder_config,可为单次调用链定制编码限制,而不影响其他路径;
  • 字节级编码规则(定长无前缀、可变长 8 字节长度前缀、枚举判别符等)可由 abi_encoder.rs 测试 逐条验证。

继续学习:

登录后查看全文
热门项目推荐
相关项目推荐