Skip to content

Instantly share code, notes, and snippets.

@RileyGe
Last active March 25, 2024 06:49
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save RileyGe/d4baaf64a6872fce7d7f8d3ce2b04ac0 to your computer and use it in GitHub Desktop.
Save RileyGe/d4baaf64a6872fce7d7f8d3ce2b04ac0 to your computer and use it in GitHub Desktop.
函数ABI编码规则

函数ABI编码规则

昨天学习了函数ABI编码的一些规则,记录下来,以防后面忘记。

两个工具

  1. Online ABI Encoder 此工具可以把函数Encode成ABI,是学习的利器。
  2. Ethereum Function Signature Database 此工具提供了将函数名编码转成4位的hash的功能。也提供部分函数的反查功能。

ABI编码方法

函数的ABI编码一般是长度为4bytes+32bypes*n。其中前4bytes为函数选择器,决定了参数为传统给合约中的哪一个函数。后面的32bypes*n为函数的参数。

a. 函数编码

在EVM中,每个函数都由4个byte长度的16进制值来唯一标识,这4个bytes叫做函数签名。函数签名是对函数名,函数参数做Keccak(SHA-3) 运算后,获得的hash值的前4个bytes.

如下面increaseAge这个函数,直接通过web3提供的sha3,取前4bytes即可获得函数签名F9EA5E79. EVM会通过这个函数签名找到对应的函数,通过JUMPI跳转到对应的函数。

例如在solidity中一个函数:

function increaseAge(string name, uint num)returns (uint){
  return ++age;
}

使用web3:

web3.sha3("increaseAge(string,uint256)");
//"0xf9ea5e79ef6e6ddd1e44553d57e3b65ed51ffb4fb7956f7b4b09eea27ef1056f"

b. 函数参数编码

每个参数都是以一个32byte长度hash值的形式传入的,长度不够用0补,如uint8的长度是8bytes,前面不足的24bytes都用0来补。用下面的合约来举例:

pragma solidity ^0.4.16;

contract Foo {
  function baz(uint32 x, bool y) public pure returns (bool r) { r = x > 32 || y; }
  function bar(bytes3[2]) public pure {}
  function sam(bytes, bool, uint[]) public pure {}
}

1. baz函数

0xcdcd77c0是baz的函数签名,如果要传入69和true两个参数,69首先会被编码成0x0000000000000000000000000000000000000000000000000000000000000045,true则被编码成0x0000000000000000000000000000000000000000000000000000000000000001,最后总的传入参数为0xcdcd77c0 0000000000000000000000000000000000000000000000000000000000000045 0000000000000000000000000000000000000000000000000000000000000001,两个参数加上函数签名一共68bytes。 这个函数会返回一个bool类型的值,假设返回值是false,那么在调用方会收到:0x0000000000000000000000000000000000000000000000000000000000000000。

2. bar函数

bar函数的参数虽然为数组,但为定长数组,所以在传入时和普通参数一样。假设要传入的值为["abc","def"],"abc"对应的hash值为0x6162630000000000000000000000000000000000000000000000000000000000,"def"对应的值为0x6465660000000000000000000000000000000000000000000000000000000000,bytes采用的是左端对齐的方式,以区分uint。

3. sam函数

存在两个动态类型bytes和uint[],对于动态类型在编码时,会先存入一个32bytes的值,代表这个动态类型存储的开始位置。比如要传入bytes传入abc,bool传入true,unit[]传入[5,6]。编码会遵循如下步骤:

3.1. 前三个32bytes字节

第一个字节为0000000000000000000000000000000000000000000000000000000000000060,表示bytes存储于0x60位置。第二个32bytes为0x0000000000000000000000000000000000000000000000000000000000000001,表示true。第三信32bytes为00000000000000000000000000000000000000000000000000000000000000a0,表示第三个变量放在0xa0的位置。

3.2. 然后接下来2*32bytse

然后接下来2*32bytse表示bytes的具体数值,第一个32bytes为0000000000000000000000000000000000000000000000000000000000000003,表示此bytes为3个字节的长度。第二个字节为6162630000000000000000000000000000000000000000000000000000000000,将abc进行了16进制的编码。

3.3. 接下来的4*32bytes

接下来的4*32bytes表示uint[]的具体值。与3.2类似,第一个32bytes为0000000000000000000000000000000000000000000000000000000000000003,表示此uint[]为3*32bytes个字节的长度。然后剩下的3*32bytes为000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000007,对5,6,7三个数字进行了编码。

本文引用了Solidity原理(三):abi编码以及与EVM交互的原理中的部分内容。但原文中有一些写的不明白的地方,对其加以改正。

@leiiiooo
Copy link

非常有帮助,看完也很通透,感谢🙏

@leiiiooo
Copy link

http://www.metools.info/learn/l120.html 这个例子是不是应该穿入5、6、7, 这样才能和后面的http://www.metools.info/learn/l120.html 这部分内容对应起来

@ByteSecurity
Copy link

0xcdcd77c0是bar的函数签名 是不是要改成 0xcdcd77c0是baz的函数签名

一共68bytes。 这个函数会返回一个 改成 加上函数签名一共68bytes。 这个函数会返回一个

@RileyGe
Copy link
Author

RileyGe commented Jul 12, 2023

@ByteSecurity 你是正确的,已经修改了。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment