在 Solidity 中,函数的调用数据需要根据 ABI 规范进行编码。对于可变长度的数据(例如数组和结构体),ABI 采用了一种指针和数据分离的方式进行编码。以下通过几个具体的例子来展示不同类型的参数在 ABI 中的编码方式。
contract VariableLength {
struct Example {
uint256 a;
uint256 b;
uint256 c;
}
function threeArgs(uint256 a, uint256[] calldata b, uint256 c) external {}
function threeArgsStruct(uint256 a, Example calldata b, uint256 c) external {}
function fiveArgs(uint256 a, uint256[] calldata b, uint256 c, uint256[] calldata d, uint256 e) external {}
function oneArg(uint256[] calldata a) external {}
function allVariable(uint256[] calldata a, uint256[] calldata b, uint256[] calldata c) external {}
}
function threeArgs(uint256 a, uint256[] calldata b, uint256 c) external {}
假设调用此函数,输入参数为:
- a: 7
- b: [1,2,3]
- c: 9
编码后的 calldata 为:
0xc6f922d0
0000000000000000000000000000000000000000000000000000000000000007 // a = 7
0000000000000000000000000000000000000000000000000000000000000060 // b 的指针 = 0x60
0000000000000000000000000000000000000000000000000000000000000009 // c = 9
0000000000000000000000000000000000000000000000000000000000000003 // b 的长度 = 3
0000000000000000000000000000000000000000000000000000000000000001 // b[0] = 1
0000000000000000000000000000000000000000000000000000000000000002 // b[1] = 2
0000000000000000000000000000000000000000000000000000000000000003 // b[2] = 3
其中,7
b 的指针
9
依次放在 0x00
0x20
0x40
的内存槽中,b 的指针
是 0x60
意味着 0x60
内存槽开始存放数组 b 的相关内容。可以看到 0x60
存放了数组长度 3, 然后依次是数组内的元素。
function threeArgsStruct(uint256 a, Example calldata b, uint256 c) external {}
假设调用此函数,输入参数为:
- a: 7
- b: [1,2,3]
- c: 9
calldata 中的内容为:
0x01e58fb4
0000000000000000000000000000000000000000000000000000000000000007 // a = 7
0000000000000000000000000000000000000000000000000000000000000001 // b.a = 1
0000000000000000000000000000000000000000000000000000000000000002 // b.b = 2
0000000000000000000000000000000000000000000000000000000000000003 // b.c = 3
0000000000000000000000000000000000000000000000000000000000000009 // c = 9
在这个例子中,b 是一个固定长度的结构体,因此可以直接依次存放各个字段的值,而无需使用指针。
function fiveArgs(uint256 a, uint256[] calldata b, uint256 c, uint256[] calldata d, uint256 e) external {}
假设调用此函数,输入参数为:
- a: 5
- b: [2,4]
- c: 7
- d: [10,11,12]
- e:9
编码后的 calldata 为:
0x37701841
0000000000000000000000000000000000000000000000000000000000000005 // a = 5
00000000000000000000000000000000000000000000000000000000000000a0 // b 的指针 = 0xa0
0000000000000000000000000000000000000000000000000000000000000007 // c = 7
0000000000000000000000000000000000000000000000000000000000000100 // d 的指针 = 0x100
0000000000000000000000000000000000000000000000000000000000000009 // e = 9
0000000000000000000000000000000000000000000000000000000000000002 // b 的长度 = 2
0000000000000000000000000000000000000000000000000000000000000002 // b[0] = 2
0000000000000000000000000000000000000000000000000000000000000004 // b[1] = 4
0000000000000000000000000000000000000000000000000000000000000003 // d 的长度 = 3
000000000000000000000000000000000000000000000000000000000000000a // d[0] = 10
000000000000000000000000000000000000000000000000000000000000000b // d[1] = 11
000000000000000000000000000000000000000000000000000000000000000c // d[2] = 12
类似于 threeArgs 的例子,先放置固定长度的参数,再放置数组的指针。指针指向的内存位置开始存放数组长度,然后依次存放数组元素。
function oneArg(uint256[] calldata a) external {}
假设调用此函数,输入参数为:
- a: [1,2,3]
编码后的 calldata 为:
0xda02ff3c
0000000000000000000000000000000000000000000000000000000000000020 // a 的指针 = 0x20
0000000000000000000000000000000000000000000000000000000000000003 // a 的长度 = 3
0000000000000000000000000000000000000000000000000000000000000001 // a[0] = 1
0000000000000000000000000000000000000000000000000000000000000002 // a[1] = 2
0000000000000000000000000000000000000000000000000000000000000003 // a[2] = 3
即使只有一个数组参数,ABI 仍然使用指针来标明数组的位置,指针指向的位置先存数组长度,再存元素。
function allVariable(uint256[] calldata a, uint256[] calldata b, uint256[] calldata c) external {}
假设调用此函数,输入参数为:
- a: [1,2]
- b: [3,4,5]
- c: [6,7,8,9]
编码后的 calldata 为:
0x1fd3b26b
0000000000000000000000000000000000000000000000000000000000000060 // a 的指针 = 0x60
00000000000000000000000000000000000000000000000000000000000000c0 // b 的指针 = 0xc0
0000000000000000000000000000000000000000000000000000000000000140 // c 的指针 = 0x140
0000000000000000000000000000000000000000000000000000000000000002 // a 的长度 = 2
0000000000000000000000000000000000000000000000000000000000000001 // a[0] = 1
0000000000000000000000000000000000000000000000000000000000000002 // a[1] = 2
0000000000000000000000000000000000000000000000000000000000000003 // b 的长度 = 3
0000000000000000000000000000000000000000000000000000000000000003 // b[0] = 3
0000000000000000000000000000000000000000000000000000000000000004 // b[1] = 4
0000000000000000000000000000000000000000000000000000000000000005 // b[2] = 5
0000000000000000000000000000000000000000000000000000000000000004 // c 的长度 = 4
0000000000000000000000000000000000000000000000000000000000000006 // c[0] = 6
0000000000000000000000000000000000000000000000000000000000000007 // c[1] = 7
0000000000000000000000000000000000000000000000000000000000000008 // c[2] = 8
0000000000000000000000000000000000000000000000000000000000000009 // c[3] = 9
总结:
本节的例子展示了如何使用 ABI 编码处理可变长度的参数。对于每一个可变长度的参数,ABI 会在调用数据的开始部分为其创建一个指针,这个指针指向调用数据中实际存放数据的位置。在实际数据存放位置,先存储数据的长度,然后依次存储每个数据元素的值。