Skip to main content

4. Calldata

Calldata也就是我们发送给函数的编码参数。也就是发送给EVM的数据。

每个calldata长度为32字节或者64个字符,一般calldata分为两种:

  • Static
  • Dynamic

如何编码

如果对类型进行编码,直接传递到abi.encode()中来生成原始调用数据

如果对特定接口函数,我们需要使用abi.encodeWithSelector(selector, parameters)

interface A {
function transfer(uint256[] memory ids, address to) virtual external;
}

contract B {
function a(uint256[] memory ids, address to) external pure returns(bytes memory) {
return abi.encodeWithSelector(A.transfer.selector, ids, to);
}
}

如何解码

如果使用abi.encode编码的,我们可以使用abi.decode来解码:

(uint256 a, uint256 b) = abi.decode(data, (uint256, uint256))

Static Variables

假如我们在和下面的合约交互:

pragma solidity 0.8.17;
contract Example {
function transfer(uint256 amount, address to) external;
}

我们想要传递的参数是:

amount: 1300655506
address: 0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45

那将会生成一段:0x000000000000000000000000000000000000000000000000000000004d866d9200000000000000000000000068b3465833fb72a70ecdf485e0e4c7bd8665fc45我们该怎么阅读呢,其实我们只需要吧0x删去,然后以32字节为一组分开就好:

0x
// uint256
000000000000000000000000000000000000000000000000000000004d866d92
// address
00000000000000000000000068b3465833fb72a70ecdf485e0e4c7bd8665fc45

Function

我们都知道函数选择器是函数加上参数然后进行keccak256哈希之后的前四个字节,所以如果我们想抗药调用transfer(uint256,address),我们就需要先:

keccak256("transfer(uint256,address)");

然后将结果的前四个字节b7760c8f拼接到calldata中:

0xb7760c8f000000000000000000000000000000000000000000000000000000004d866d9200000000000000000000000068b3465833fb72a70ecdf485e0e4c7bd8665fc45

Dynamic Variables

动态变量是非固定大小的类型,包括 bytesstring 和动态数组 <T>[] ,以及固定数组 <T>[N]

动态类型的结构始终以偏移量开始,偏移量是动态类型开始位置的十六进制表示。例如,十六进制 20 表示 32-bytes 。一旦到达偏移量,就会有一个较小的数字表示类型的长度。也就是说第一个32字节是偏移量,第二个32字节是长度,其余为元素

我们用一个例子形象的表明一下,比如一个字符串string类型,值是Hello World!:

0x
0000000000000000000000000000000000000000000000000000000000000020
000000000000000000000000000000000000000000000000000000000000000c
48656c6c6f20576f726c64210000000000000000000000000000000000000000

第一个表明偏移量是20也就是十进制的32,所以我们跳过直接去下一行,值是0c,十进制的12,表明我们的字符串有12个字符,最后一行就是元素。