以太坊 ABI 编码机制

          以太坊智能合约的 ABI(Application Binary Interface),即应用程序二进制接口,是合约与外界交互的桥梁。ABI 编码是将函数调用及其参数序列化为以太坊虚拟机(EVM)可识别的二进制格式的关键步骤。它是一种 规则,用来定义外部如何与智能合约交互,包括函数调用数据编码。

1、什么是 ABI 编码?

     以太坊的 ABI 编码定义了智能合约如何接收输入参数,以及如何返回结果。ABI 编码的主要特点是:

          1.定长类型(如 uint256address)直接编码为固定的 32 字节。

          2.动态长度类型(如 stringbytes)使用偏移量指向实际数据存储位置,确保灵活性和一致性。

2、ABI 编码规则

     在调用智能合约函数时,数据编码的组成包括两部分:

1.函数选择器(4 字节)

  • 它是函数签名(函数名 + 参数类型)的哈希值的前 4 字节。

  • 例如,transfer(address,uint256) 的函数选择器是 0xa9059cbb。

2.参数编码

  • 固定大小参数(如 uint256、address):编码为 32 字节 的数据,左对齐。

  • 动态大小参数(如 string、bytes):先编码偏移量(位置指针),再编码实际数据。

3、具体说明

(1) 固定长度类型

固定长度类型直接编码为 32 字节:

  • uint256:数字以十六进制形式表示,左侧补零。

  • address:地址固定为 20 字节,同样以十六进制表示,左侧补零,右对齐。

示例:

function transfer(address _to, uint256 _amount) public;

调用这个函数的参数是:

  • _to: 0x000000000000000000000000000000000000dEaD   

  • _amount: 1000

编码过程

1. 计算函数选择器

     • Keccak256("transfer(address,uint256)")[:4] = 0xa9059cbb

2. 编码参数

     • _to(address):编码为 0x000000000000000000000000000000000000dEaD(32 字节,左对齐)。

     • _amount(uint256):编码为 0x00000000000000000000000000000000000000000000000000000000000003e8(1000 的十六进制)。

最终编码结果:

1、为什么1000的16进制后,在最终编码的时候要把0x去掉?

0x 是一种 人类可读的表示形式,用来表示数据是十六进制数字。在 EVM 和 ABI 编码中:

• 数据需要以 纯二进制或十六进制格式存储和传输。

• 为了节约空间并与规范兼容,ABI 编码不保留 0x 前缀。

因此:

• 1000 转换为十六进制是 0x03e8。

• 在编码时去掉 0x,只保留 有效数字部分(03e8),然后左对齐填充到 32 字节。

2、为什么 address 要转换为小写,并且长度固定?

小写转换:

以太坊地址不区分大小写,但 ABI 编码要求地址统一为小写,这是为了:

• 避免大小写不一致导致错误(例如手动输入时)。

• 符合常见的编码和解析工具的要求。

长度变化(固定为 32 字节):

address 在以太坊中占用 20 字节(160 位),但 ABI 编码规则规定:

所有固定大小类型(如 address, uint256, bool)在编码时必须扩展为 32 字节

• 这是为了在 EVM 中保持统一的内存对齐,便于处理和解析数据。

例如:

• 地址 0x000000000000000000000000000000000000dEaD(20 字节)。

• 编码时填充到 32 字节:

000000000000000000000000000000000000000000000000000000000000dead

注意这里的 dEaD 自动转换为小写 dead。

总结

去掉 0x 是因为 ABI 编码传输的是数据本身,而不是表示形式。

小写地址 是为了规范化和工具兼容性。

32 字节对齐 是 EVM 内部操作的设计要求。

这两点遵循的都是 ABI 编码的基本原则:一致性、简洁性和高效性

(2) 动态大小类型

     动态大小类型的编码在以太坊 ABI 中遵循严格的规则,其核心思想是:使用偏移量和实际数据分离存储

动态类型包括:

  • 字符串 (string)

  • 字节数组 (bytes)

  • 动态大小数组 (T[],如 uint256[])

这些类型长度不固定,因此需要额外的信息来表示数据的大小和位置。

编码规则

     对于动态类型:

     1. 存储偏移量

          • 动态数据的起始位置存储在 32 字节的偏移量中(相对于整个编码起始位置)。

          • 偏移量通常紧接着固定类型的编码。

     2. 存储数据长度

          • 偏移量指向的实际数据区域,第一段存储的是动态类型的长度(以字节为单位)。

     3. 存储数据本体

          • 数据紧随长度信息进行存储。

          • 如果数据长度不足 32 字节,使用零填充对齐到 32 字节。

举例说明

假设有以下函数:

function example(string memory name, uint256 age) public pure {}

调用 example("Alice", 25),其编码步骤如下:

1. 函数选择器(4 字节)

example(string,uint256) 的函数签名哈希为:

keccak256("example(string,uint256)") => 0xcdcd77c0

2. 编码固定类型 uint256

age 的值是 25,编码为 32 字节:

• 类型:uint256

• 值:25

• 固定类型在 ABI 编码中,总是编码为一个 32 字节的固定长度

• 编码过程:

     • 将十进制 25 转为十六进制:0x19

     • 填充到 32 字节(左侧补零):

3. 编码动态类型 string

动态数据的编码分三部分:

偏移量

假设动态数据起始偏移量为 64(从整个编码起点开始计算),编码为:

• 类型:string(动态类型)

• 动态类型的值存储在编码的后部,为了找到动态类型的值,需要一个 偏移量

• 偏移量是相对于整个编码起点的字节位置。

偏移量计算:

• 假设编码结构如下:

     • 第 0-31 字节:age 的值

     • 第 32-63 字节:name 的偏移量

     • 动态数据从 第 64 字节 开始存储。

因此:

• name 的偏移量为 64,编码为 32 字节(十六进制):

长度

"Alice" 长度为 5,编码为:

• 类型:string(动态类型)

• 动态类型的实际数据存储包括:

     • 数据的长度(以字节为单位)。

     • 数据本体(对齐到 32 字节)。

长度计算:

     • "Alice" 的长度为 5 字节(每个字符占 1 字节)。

     • 编码为 32 字节(十六进制):

数据

"Alice" 的 ASCII 值编码为:

• 动态类型的本体存储在长度字段之后,需要对齐到 32 字节。

数据编码:

• "Alice" 的 ASCII 值为:

A: 0x41, l: 0x6c, i: 0x69, c: 0x63, e: 0x65

• 拼接成字节序列:

• 对齐到 32 字节(右侧补零):

完整编码

整合所有部分:

动态类型(如 string)在 ABI 编码中的处理方式:

1. 偏移量 指向数据在编码中的位置。

2. 长度 说明数据的字节数。

3. 数据本体 对齐到 32 字节存储。

总结

     以太坊 ABI 编码机制为智能合约与外部交互提供了一种标准化的方法。通过固定长度类型和动态长度类型的分段处理,既保证了灵活性,又兼顾了高效性。

从开发者的角度,理解 ABI 编码有助于:

          1. 手动调试智能合约调用数据

          2. 优化参数传递,减少 Gas 消耗

3. 设计更加兼容的合约接口