写在前面:HiBlock区块链社区成立了翻译小组,翻译区块链相关的技术文档及资料,本文为Solidity文档翻译的第七部分《应用二进制接口(ABI) 说明》,特发布出来邀请solidity爱好者、开发者做公开的审校,您可以添加微信baobaotalk_com,验证输入“solidity”,然后将您的意见和建议发送给我们,也可以在文末“留言”区留言,有效的建议我们会采纳及合并进下一版本,同时将送一份小礼物给您以示感谢。
1
基本设计
在 以太坊Ethereum 生态系统中, 应用二进制接口Application Binary Interface(ABI) 是从区块链外部与合约进行交互以及合约与合约间进行交互的一种标准方式。 数据会根据其类型按照这份手册中说明的方法进行编码。这种编码并不是可以自描述的,而是需要一种特定的概要(schema)来进行解码。
我们假定合约函数的接口都是强类型的,且在编译时是可知的和静态的;不提供自我检查机制。我们假定在编译时,所有合约要调用的其他合约接口定义都是可用的。
这份手册并不针对那些动态合约接口或者仅在运行时才可获知的合约接口。如果这种场景变得很重要,你可以使用 以太坊Ethereum 生态系统中其他更合适的基础设施来处理它们。
2
函数选择器
一个函数调用数据的前 4 字节,指定了要调用的函数。这就是某个函数签名的 Keccak(SHA-3)哈希的前 4 字节(高位在左的大端序)(译注:这里的“高位在左的大端序“,指最高位字节存储在最低位地址上的一种串行化编码方式,即高位字节在左)。 这种签名被定义为基础原型的规范表达,基础原型即是函数名称加上由括号括起来的参数类型列表,参数类型间由一个逗号分隔开,且没有空格。
3
参数编码
从第5字节开始是被编码的参数。这种编码也被用在其他地方,比如,返回值和事件的参数也会被用同样的方式进行编码,而用来指定函数的4个字节则不需要再进行编码。
4
类 型
以下是基础类型:
uint:M 位的无符号整数,0 < M <= 256、M % 8 == 0。例如:uint32,uint8,uint256。
int:以 2 的补码作为符号的 M 位整数,0 < M <= 256、M % 8 == 0。
address:除了字面上的意思和语言类型的区别以外,等价于 uint160。在计算和 函数选择器Function Selector中,通常使用 address。
uint、int:uint256、int256 各自的同义词。在计算和 函数选择器Function Selector中,通常使用 uint256 和 int256。
bool:等价于 uint8,取值限定为 0 或 1 。在计算和 函数选择器Function Selector中,通常使用 bool。
fixedx:M 位的有符号的固定小数位的十进制数字 8 <= M <= 256、M % 8 ==0、且 0 < N <= 80。其值 v 即是 v / (10 ** N)。(也就是说,这种类型是由 M 位的二进制数据所保存的,有 N 位小数的十进制数值。译者注。)
ufixedx:无符号的 fixedx。
fixed、ufixed:fixed128x19、ufixed128x19 各自的同义词。在计算和 函数选择器Function Selector中,通常使用 fixed128x19 和 ufixed128x19。
bytes:M 字节的二进制类型,0 < M <= 32。
function:一个地址(20 字节)之后紧跟一个 函数选择器Function Selector(4 字节)。编码之后等价于 bytes24。
以下是定长数组类型:
- [M]:有 M 个元素的定长数组,M > 0,数组元素为给定类型。
以下是非定长类型:
bytes:动态大小的字节序列。
string:动态大小的 unicode 字符串,通常呈现为 UTF-8 编码。
[]:元素为给定类型的变长数组。
可以将有限的若干类型放到一对括号中,用逗号分隔开,以此来构成一个 元组tuple:
- (T1,T2,…,Tn):由 T1,…,Tn,n >= 0 构成的 元组tuple。
用 元组tuple 构成 元组tuple、用 元组tuple 构成数组等等也是可能的。
5
编码的形式化说明
我们现在来正式讲述编码,它具有如下属性,如果参数是嵌套的数组,这些属性非常有用:
属性:
1、读取的次数取决于参数数组结构中的最大深度;也就是说,要取得 a_i[k][l][r] 需要读取 4 次。在先前的ABI版本中,在最糟的情况下,读取的次数会随着动态参数的总数而线性地增长。
2、一个变量或数组元素的数据,不会被插入其他的数据,并且是可以再定位的;也就是说,它们只会使用相对的“地址”。
我们需要区分静态和动态类型。静态类型会被直接编码,动态类型则会在当前数据块之后单独分配的位置被编码。
定义: 以下类型被称为“动态”:
bytes
string
任意类型 T 的变长数组 T[]
任意动态类型 T 的定长数组 T[k] (k > 0)
Ti (1 <= i <= k)为任意动态类型的元组tuple (T1,…,Tk)
所有其他类型都被称为“静态”。
定义: len(a) 是一个二进制字符串 a 的字节长度。len(a) 的类型被呈现为 uint256。
我们把实际的编码 enc 定义为一个由ABI类型到二进制字符串的值的映射;因而,当且仅当 X 的类型是动态的,len(enc(X)) (即 X 经编码后的实际长度,译者注)才会依赖于 X 的值。
定义: 对任意ABI值 X,我们根据 X 的实际类型递归地定义 enc(X)。
(T1,…,Tk) 对于 k >= 0 且任意类型 T1 ,…, Tk
enc(X) = head(X(1)) … head(X(k-1)) tail(X(0)) … tail(X(k-1))
这里,X(i) 是 元组tuple 的第 i 个要素,并且 当 Ti 为静态类型时,head 和 tail 被定义为head(X(i)) = enc(X(i)) and tail(X(i)) = “” (空字符串)
否则,比如 Ti 是动态类型时,它们被定义为head(X(i)) = enc(len(head(X(0)) … head(X(k-1)) tail(X(0)) … tail(X(i-1))))tail(X(i)) = enc(X(i))
注意,在动态类型的情况下,由于 head 部分的长度仅取决于类型而非值,所以 head(X(i)) 是定义明确的。它的值是从 enc(X) 的开头算起的,tail(X(i)) 的起始位在 enc(X) 中的偏移量。
T[k] 对于任意 T 和 k:enc(X) = enc((X[0], …, X[k-1]))
即是说,它就像是个由相同类型的 k 个元素组成的 元组tuple 那样被编码的。
T[] 当 X 有 k 个元素(k 被呈现为类型 uint256):enc(X) = enc(k) enc([X[1], …, X[k]])
即是说,它就像是个由静态大小 k 的数组那样被编码的,且由元素的个数作为前缀。
具有 k (呈现为类型 uint256)长度的 bytes:enc(X) = enc(k) pad_right(X),即是说,字节数被编码为 uint256,紧跟着实际的 X 的字节码序列,再在前边(左边)补上可以使 len(enc(X)) 成为 32 的倍数的最少数量的 0 值字节数据。
string:enc(X) = enc(enc_utf8(X)),即是说,X 被 utf-8 编码,且在后续编码中将这个值解释为 bytes类型。注意,在随后的编码中使用的长度是其 utf-8 编码的字符串的字节数,而不是其字符数。
uint