Skip to content

Latest commit

 

History

History
250 lines (175 loc) · 9.35 KB

File metadata and controls

250 lines (175 loc) · 9.35 KB
title tags
07. ABI编码公式
solidity
abi

WTF Solidity 内部标准: 07. ABI 编码公式

《WTF Solidity 内部标准》教程将介绍 Solidity 智能合约中的存储布局,内存布局,以及 ABI 编码规则,帮助大家理解 Solidity 的内部规则。

推特:@0xAA_Science

社区:Discord微信群官网 wtf.academy

所有代码和教程开源在 github: github.com/AmazingAng/WTF-Solidity-Internals


这一讲,我们介绍基于Solidity 文档总结的 ABI 编码公式。

符号说明

首先,我们定义公式会使用到的符号的意义:

  1. T: 任意变量类型。
  2. (T1, T2,...,Tn): 由T1...Tn等类型的变量组成的元组。
  3. len(a): a的字节长度,用于计算数据偏移量。
  4. e(X): 变量X的 ABI 编码。
  5. head(X): X变量的头部编码。
  6. tail(X): X变量的尾部编码。
  7. pad_right(X): 在X的值的右侧补若干个0,使其长度成为32字节。

ABI 编码公式

我们将 Solidity 合约的 ABI 编码公式分为两部分,第一部分为递归编码公式,第二部分为类型编码公式。逻辑就是将复杂类型使用递归公式转换成简单类型进行编码。

递归编码公式

递归编码公式会将复杂类型的编码以递归的方式转换为简单类型的编码。

  1. 元组(结构体也会转换成元组进行编码)

对于元组X = (T1,...,Tk),其中k>=0并且T1...Tn为任意类型,有:

e(X) = head(X(1)) ... head(X(k)) tail(X(1)) ... tail(X(k))

其中X(n)为元组X的第n个元素。也就是说,元组X的编码由两部分组成,第一部分是头部编码,第二部分是尾部编码,它们都按照元素在元组中的顺序排列。

那么head(Xi)tail(Xi)如何定义?

  • 如果X(i)的类型Ti为静态类型,则直接在头部进行编码,没有尾部编码:
    • head(X(i)) = e(X(i))
    • tail(X(i)) = ""(为空)
  • 如果X(i)的类型Ti为动态类型,那么会在尾部进行编码,头部编码为尾部编码tail(X(i))的偏移量:
    • head(X(i)) = e(len(head(X(1)) ... head(X(k)) tail(X(1)) ... tail(X(i-1))))(这是从head(X1)tail(X(i-1))的字节长度,也是tail(X(i))的偏移量)
    • tail(X(i)) = e(X(i))
  1. 定长数组

对于长度为k的定长数组X = T[k],其中k>=0并且T为任意类型,有:

e(X) = e((X[0], ..., X[k-1]))

其中X[n]为数组X的第n个元素。也就是说,将它当作由相同类型的k个元素组成的元组那样被编码的。

  1. 不定长数组

对于长度为k的不定长数组X = T[],其中其中k>=0并且T为任意类型,有:

e(X) = e(k) e((X[0], ..., X[k-1]))

其中长度k当作uint256类型编码。也就是说,它的编码有两部分,第一部分为长度k的编码,第二部分为等效的定长数组的编码。

类型编码公式

类型编码公式以穷举的方式给出简单类型的 ABI 编码规则。

  1. uint<M>M位的无符号整数,M的取值为0256之间的可以整除 8 的整数,比如uint8uint32uint256uintuint256的同义词)。编码时,会在它们左侧补充若干0以使其长度成为32字节。

  2. address:地址类型,与uint160的编码方式相同。address payablecontract类型的变量也使用相同的编码方式。

  3. bool: 1表示true0表示false,编码方式与uint8的情况相同。

  4. bytes<M>:长度为M字节的定长字节数组,0 < M <= 32,编码时会在右侧补若干0使其长度成为32字节。

  5. bytes: 若X是长度为kk的类型为uint256)的不定长字节数组,则enc(X) = enc(k) pad_right(X),也就是先编码长度k,再编码内容。编码内容时会在右侧补若干0使其长度成为32字节的倍数。

  6. string: 会先用UTF-8编码为bytes,然后使用bytes的规则进行 ABI 编码。

以上是常用类型的 ABI 编码规则,不常用类型(如intfixedufixed)的规则见文档

示例

示例 1: (uint x)

function testAbiUintTuple() public pure returns (bytes memory){
    uint x = 1;
    return abi.encode((x));
}

(x)是元组,根据 ABI 编码公式,e((x)) = head(x) tail(x)

xuint类型,所以head(x) = e(x)tail(x) = "",所以(x)的 ABI 编码为

0x0000000000000000000000000000000000000000000000000000000000000001

示例 2: (uint[] x)

function testAbiArray() public pure returns (bytes memory){
    uint[] memory x = new uint[](3);
    x[0] = 1;
    x[1] = 2;
    x[2] = 3;
    return abi.encode(x);
}

(x)是元组,根据 ABI 编码公式,e((x)) = head(x) tail(x)xuint[]类型(不定长数组),是动态类型,因此:

  • head(x) = len(head(x))也就是0x20

  • tail(x) = e(x) = e(k) e(x[0]) e(x[1]) e(x[2]),其中k = 3是数组的长度。

所以(x)的 ABI 编码为:

0x
0000000000000000000000000000000000000000000000000000000000000020
0000000000000000000000000000000000000000000000000000000000000003
0000000000000000000000000000000000000000000000000000000000000001
0000000000000000000000000000000000000000000000000000000000000002
0000000000000000000000000000000000000000000000000000000000000003

示例 3: ((uint x, uint[] y, string z))

function testAbiDynamicArray() public pure returns (bytes memory){
    uint x = 99;
    uint[] memory y = new uint[](3);
    y[0] = 1;
    y[1] = 2;
    y[2] = 3;
    string memory z = "WTF";

    bytes memory encodedX = abi.encode(x);
    bytes memory encodedY = abi.encode(y);
    bytes memory encodedZ = abi.encode(z);

    return abi.encodePacked(encodedX, encodedY, encodedZ);
}

由于yz都是动态类型,因此(x, y, z)元组为动态类型,((x, y, z))元组也是动态类型,根据 ABI 编码公式,e(((x, y, z))) = head(((x, y, z))) tail(((x, y, z)))

其中head(((x, y, z))) = len(head(((x, y, z))))0x20,因为((x, y, z))只有一个元素(x, y, z),虽然这个元素也是一个元组。tail(((x, y, z))) = e((x, y, z))(这里我们脱了一层括号,开始编码元组的元素了)。

到目前为止的编码为:

0x
0000000000000000000000000000000000000000000000000000000000000020
e((x, y, z))

下面我们来计算e((x, y, z)),由于它是动态元组,因此e((x, y, z)) = head(x) head(y) head(z) tail(x) tail(y) tail(z)。到目前为止的编码为:

0x
0000000000000000000000000000000000000000000000000000000000000020
head(x)
head(y)
head(z)
tail(x)
tail(y)
tail(z)

下面我们依次计算xyz的编码。xuint类型,属于静态类型,值为99(也就是0x63)。因此,head(x) = e(x)tail(x) = ""。到目前为止的编码为:

0x
0000000000000000000000000000000000000000000000000000000000000020
0000000000000000000000000000000000000000000000000000000000000063
head(y)
head(z)
tail(y)
tail(z)

接下来,我们来看y的编码。yuint[]类型,是动态类型,长度为3。因此有

  • head(y) = e(len(head(x) head(y) head(z) tail(x))) = 0x60

  • tail(y) = e(y) = e(3) e(y[0]) e(y[1]) e(y[2])

到目前为止的编码为:

0x
0000000000000000000000000000000000000000000000000000000000000020
0000000000000000000000000000000000000000000000000000000000000063
0000000000000000000000000000000000000000000000000000000000000060
head(z)
0000000000000000000000000000000000000000000000000000000000000003
0000000000000000000000000000000000000000000000000000000000000001
0000000000000000000000000000000000000000000000000000000000000002
0000000000000000000000000000000000000000000000000000000000000003
tail(z)

最后,我们来看z的编码。zstring类型,是动态类型;WTFUTF-8编码为575446,长度为3字节。因此有:

  • head(z) = e(len(head(x) head(y) head(z) tail(x) tail(y))) = 0xe0

  • tail(z) = e(z) = e(3) pad_right(575446)

呼,最后,我们就得到了((uint x, uint[] y, string z))的 ABI 编码:

0x
0000000000000000000000000000000000000000000000000000000000000020
0000000000000000000000000000000000000000000000000000000000000063
0000000000000000000000000000000000000000000000000000000000000060
00000000000000000000000000000000000000000000000000000000000000e0
0000000000000000000000000000000000000000000000000000000000000003
0000000000000000000000000000000000000000000000000000000000000001
0000000000000000000000000000000000000000000000000000000000000002
0000000000000000000000000000000000000000000000000000000000000003
0000000000000000000000000000000000000000000000000000000000000003
5754460000000000000000000000000000000000000000000000000000000000

如果你能自己推导出((uint x, uint[] y, string z))的编码,说明你已经掌握了 ABI 编码的规律!

总结

这一讲,我们介绍了 Solidity 合约的 ABI 编码公式,有了它,再复杂的编码也不怕。它的核心思想是使用递归的方法把复杂类型的编码转换成简单类型的编码。