【Solidity Yul Assembly】3.3 | Dynamic Length Arguments

  • 0xE
  • 更新于 2024-08-30 10:41
  • 阅读 478

可变长度的数据,ABI 采用了一种指针和数据分离的方式进行编码。

在 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 {}
}

1. threeArgs 函数调用

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, 然后依次是数组内的元素。

2. threeArgsStruct 函数调用

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 是一个固定长度的结构体,因此可以直接依次存放各个字段的值,而无需使用指针。

3. fiveArgs 函数调用

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 的例子,先放置固定长度的参数,再放置数组的指针。指针指向的内存位置开始存放数组长度,然后依次存放数组元素。

4. oneArg 函数调用

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 仍然使用指针来标明数组的位置,指针指向的位置先存数组长度,再存元素。

5. allVariable 函数调用

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 会在调用数据的开始部分为其创建一个指针,这个指针指向调用数据中实际存放数据的位置。在实际数据存放位置,先存储数据的长度,然后依次存储每个数据元素的值。

关于作者 0xE: Twitter Telegram

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
0xE
0xE
0x59f6...a17e
17年进入币圈,做过FHE,联盟链,现在是智能合约开发者。 刨根问底探链上真相,品味坎坷悟Web3人生。