ABI编码规则

合约的ABI(Application Binary Interface,应用二进制接口)是与合约交互的标准方式。交互中的数据需要按照规则进行编码,本文将简要描述这种编码规则。

请在Solidity官方文档中的ABI规范查看详细的编码规则。

函数选择器(Function Selector)

在函数调用的数据中,前四个字节是函数选择器,它们指定了需要被调用的函数。

函数选择器是函数签名(Function Signature)进行Keccak-256运算后,左起的前四个字节。

函数签名中只包含函数名和参数类型,没有参数名和空格。以tranfer(address _to, uint256 _value)为例,其函数签名是transfer(address,uint256)

参数编码

从第五个字节开始是函数调用的参数。这种编码规则在处理返回值和事件时同样适用。

参数类型

有静态和动态两种参数类型。

静态参数即定长参数,如uint256、bytes32、bool(布尔型是uint8,只能是0或1)。以uint256为例,对于一个类型是uint256的参数,即使值为1,也需要用0补齐到256位,所以该参数的长度是固定的,与值无关。

动态参数的长度是不定的。

静态参数编码

以下面这个函数为例

function baz(uint32 x, bool y) public pure returns (bool r) { r = x > 32 || y; }

这个函数的签名是baz(uint32,bool),Keccak-256的计算结果是0xcdcd77c0992ec5bbfc459984220f8c45084cc24d9b6efed1fae540db8de801d2,其函数选择器就是0xcdcd77c0

参数编码以十六进制的形式体现,每个十六进制数占四位。由于静态参数最长为256位,在编码时,每个静态参数长度都是256位,不足时左边用0补齐。

传递一组参数(69,true)baz方法,其编码结果如下:

  • 将十进制数69转成十六进制45,并在其左边补0,结果是0x0000000000000000000000000000000000000000000000000000000000000045

  • 布尔型的true即为uint8的1,其十六进制值也是1,在左边补0,结果是0x0000000000000000000000000000000000000000000000000000000000000001

编码完成后,用于传递的完整数据是

0xcdcd77c000000000000000000000000000000000000000000000000000000000000000450000000000000000000000000000000000000000000000000000000000000001

同理,对于bytes型的静态类型数据,在编码时将其补足至32字节即可。不同的是,bytes型数据需要在右边补齐。以下面这个函数为例

function bar(bytes3[2] memory) public pure {}

函数签名bar(bytes3[2]),选择器0xfce353f6

传递一组参数(abc,def)给这个函数,其编码结果如下:

  • abc在ASCII表中分别对应97、98、99,转至十六进制为61、62、63。不足32字节用0在右边补齐,结果是0x6162630000000000000000000000000000000000000000000000000000000000

  • def在ASCII表中分别对应100、101、102,转至十六进制为64、65、666。不足32字节用0在右边补齐,结果是0x6465660000000000000000000000000000000000000000000000000000000000

编码完成后,用于传递的完整参数是

0xfce353f661626300000000000000000000000000000000000000000000000000000000006465660000000000000000000000000000000000000000000000000000000000

动态参数编码

对于动态参数,由于其长度不定,需要先用定长的offset来占位,并指定其长度,再对数据进行编码。

f(uint,uint32[],bytes10,bytes)这个函数为例,向其传递(0x123, [0x456, 0x789], "1234567890", "Hello, world!")这几个参数时,编码结果如下:

  • 未标注长度的uint均视为uint256,0x123编码后结果是0x0000000000000000000000000000000000000000000000000000000000000123

  • 对于uint32[]来说,由于数组长度是未知的,所以它是一个动态参数。首先用offset占位,offset记录的是这个参数起始位置的字节数。在这个uint32参数的正式编码前,分别有:uint的编码(32字节)、uint32[]的offset(32字节)、bytes10的编码(32字节)、bytes的offset(32字节),因此,参数开始的位置,字节数应该是128,即0x80,编码结果是0x0000000000000000000000000000000000000000000000000000000000000080

  • 对uint32[]传递了一个长度为2的数组[0x456, 0x789]。对于动态参数,首先要记录它的长度,即0x2,之后再对数值进行编码。这个参数的编码结果是

0000000000000000000000000000000000000000000000000000000000000002 
0000000000000000000000000000000000000000000000000000000000000456
0000000000000000000000000000000000000000000000000000000000000789
  • "1234567890"是一个静态的bytes10参数,转至十六进制并用0补齐,结果是0x3132333435363738393000000000000000000000000000000000000000000000

  • 对于最后一个bytes参数,由于其是动态类型,首先先用offset占位。在参数之前的内容依次是:uint的编码(32字节)、uint32[]的offset(32字节)、bytes10的编码(32字节)、bytes的offset(32字节),uint32[]的编码(96字节)。因此offset应该是224,即0xe0,0x00000000000000000000000000000000000000000000000000000000000000e0

  • 对于bytes参数"Hello, world!",首先声明其长度13,即0xd。再将字符串转为十六进制字符,即0x48656c6c6f2c20776f726c642100000000000000000000000000000000000000。这个参数编码结果是

00000000000000000000000000000000000000000000000000000000000000e0
48656c6c6f2c20776f726c642100000000000000000000000000000000000000

至此所有参数都编码结束。最终传递的数据是

0x8be65246 - function selector
  0000000000000000000000000000000000000000000000000000000000000123 - encoding of 0x123
  0000000000000000000000000000000000000000000000000000000000000080 - offset of [0x456, 0x789]
  3132333435363738393000000000000000000000000000000000000000000000 - encoding of "1234567890"
  00000000000000000000000000000000000000000000000000000000000000e0 - offset of "Hello, world!"
  0000000000000000000000000000000000000000000000000000000000000002 - length of [0x456, 0x789]
  0000000000000000000000000000000000000000000000000000000000000456 - encoding of 0x456
  0000000000000000000000000000000000000000000000000000000000000789 - encoding of 0x789
  000000000000000000000000000000000000000000000000000000000000000d - length of "Hello, world!"
  48656c6c6f2c20776f726c642100000000000000000000000000000000000000 - encoding of "Hello, world!"