以太坊智能合约的 ABI(Application Binary Interface),即应用程序二进制接口,是合约与外界交互的桥梁。ABI 编码是将函数调用及其参数序列化为以太坊虚拟机(EVM)可识别的二进制格式的关键步骤。它是一种 规则,用来定义外部如何与智能合约交互,包括函数调用和数据编码。
以太坊的 ABI 编码定义了智能合约如何接收输入参数,以及如何返回结果。ABI 编码的主要特点是:
1.定长类型(如 uint256、address)直接编码为固定的 32 字节。
2.动态长度类型(如 string、bytes)使用偏移量指向实际数据存储位置,确保灵活性和一致性。
在调用智能合约函数时,数据编码的组成包括两部分:
1.函数选择器(4 字节)
它是函数签名(函数名 + 参数类型)的哈希值的前 4 字节。
例如,transfer(address,uint256) 的函数选择器是 0xa9059cbb。
2.参数编码
固定大小参数(如 uint256、address):编码为 32 字节 的数据,左对齐。
动态大小参数(如 string、bytes):先编码偏移量(位置指针),再编码实际数据。
(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. 设计更加兼容的合约接口。
