昨天学习了函数ABI编码的一些规则,记录下来,以防后面忘记。
- Online ABI Encoder 此工具可以把函数Encode成ABI,是学习的利器。
- Ethereum Function Signature Database 此工具提供了将函数名编码转成4位的hash的功能。也提供部分函数的反查功能。
函数的ABI编码一般是长度为4bytes+32bypes*n。其中前4bytes为函数选择器,决定了参数为传统给合约中的哪一个函数。后面的32bypes*n为函数的参数。
在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"
每个参数都是以一个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 {}
}
0xcdcd77c0是baz的函数签名,如果要传入69和true两个参数,69首先会被编码成0x0000000000000000000000000000000000000000000000000000000000000045,true则被编码成0x0000000000000000000000000000000000000000000000000000000000000001,最后总的传入参数为0xcdcd77c0 0000000000000000000000000000000000000000000000000000000000000045 0000000000000000000000000000000000000000000000000000000000000001,两个参数加上函数签名一共68bytes。 这个函数会返回一个bool类型的值,假设返回值是false,那么在调用方会收到:0x0000000000000000000000000000000000000000000000000000000000000000。
bar函数的参数虽然为数组,但为定长数组,所以在传入时和普通参数一样。假设要传入的值为["abc","def"],"abc"对应的hash值为0x6162630000000000000000000000000000000000000000000000000000000000,"def"对应的值为0x6465660000000000000000000000000000000000000000000000000000000000,bytes采用的是左端对齐的方式,以区分uint。
存在两个动态类型bytes和uint[],对于动态类型在编码时,会先存入一个32bytes的值,代表这个动态类型存储的开始位置。比如要传入bytes传入abc,bool传入true,unit[]传入[5,6]。编码会遵循如下步骤:
第一个字节为0000000000000000000000000000000000000000000000000000000000000060,表示bytes存储于0x60位置。第二个32bytes为0x0000000000000000000000000000000000000000000000000000000000000001,表示true。第三信32bytes为00000000000000000000000000000000000000000000000000000000000000a0,表示第三个变量放在0xa0的位置。
然后接下来2*32bytse表示bytes的具体数值,第一个32bytes为0000000000000000000000000000000000000000000000000000000000000003,表示此bytes为3个字节的长度。第二个字节为6162630000000000000000000000000000000000000000000000000000000000,将abc进行了16进制的编码。
接下来的4*32bytes表示uint[]的具体值。与3.2类似,第一个32bytes为0000000000000000000000000000000000000000000000000000000000000003,表示此uint[]为3*32bytes个字节的长度。然后剩下的3*32bytes为000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000007,对5,6,7三个数字进行了编码。
本文引用了Solidity原理(三):abi编码以及与EVM交互的原理中的部分内容。但原文中有一些写的不明白的地方,对其加以改正。
非常有帮助,看完也很通透,感谢🙏