简介:本文将介绍Solidity语言的调用数据的布局和ABI详解。其中调用数据的布局将主要介绍以太坊合约间调用时的消息格式ABI。
好久时间没有更新文章,前文中我们介绍了Solidity的特性与内部机制,本文我们将Solidity的调用数据的布局和ABI详解。
调用数据的布局(Layout of CallData)
当Solidity合约被部署后,从某个帐户调用这个合约,需要输入的数据是需要符合the ABI specification,ABI是以太坊的一种合约间调用时的一个消息格式。类似Webservice里的SOAP协议一样;也就是定义操作函数签名,参数编码,返回结果编码等。
ABI详解
函数
基本设计思想
使用ABI协议时必须要求在编译时知道类型,也就是说不支持动态语言那样的声明的变量还会变类型的情况。由于协议假设合约在编译期间知道另一个合约的接口定义,所以协议内没有明确定义存的内容类型(协议非类型内省)。
所以这个协议并不支持合约接口是动态的,或者是仅在运行时才知道类型的情况。如果这些情况很重要,可以使用以太坊生态系统建立自己的基础设施来解决这个问题。
函数选择器
一个函数调用的前四个字节数据指定了要调用的函数签名。计算方式是使用函数签名的keccak256的哈希,取4个字节。
bytes4(keccak256("foo(uint32,bool)"))
函数签名使用基本类型的典型格式(canonical expression)定义,如果有多个参数使用,隔开,要去掉表达式中的所有空格。
参数编码
由于前面的函数签名使用了四个字节,参数的数据将从第五个字节开始。参数的编码方式与返回值,事件的编码方式一致,后面一起介绍。
支持的类型
支持的类型可参考原文2。支持的类型里面有一些比较特殊的是动态内容的类型,比如string,需要存储的空间是不固定的。
编码方式
针对数组参数中的嵌套数组的优化:
访问一个参数属性需要的读取次数,在一个数组结构中最多是数组的深度,比如a_i[k][l][r],最多4次。在之前的ABI协议版本中,在最差情况下读取次数会随着总的动态类型的参数量线性增长。
变量的值或数组的元素间不应该是隔开存储的,可支持重定位,比如使用相对地址来定位。
区分了动态内容类型和固定大小的类型。固定大小的类型按原位置存储到当前块。动态类型的数据则独立存储在其它数据块。
动态内容类型的定义
bytes
string
T[] 某个类型的不定长数组
T[k] 某个类型的定长数组
所有其它类型则称为固定大小的类型。
长度函数的定义
len(a)是二进制字符串a的中的字节数。len(a)的结果类型是uint256。
我们定义enc,编码函数,是一个ABI类型值到二进制串的映射函数,也就是ABI类型到二进制字符串的映射函数。由此len(enc(X))的结果也将因为X是不是动态内容类型而有所不同(也就是说动态内容类型的编码方式稍有不同)。
进一步定义
对于任何ABI的值,根据X的类型不同,我们递归定义enc(X),如下:
对于X是任意类型的T和长度值k,T[k]。
enc(X) = head(X[0]) ... head(x[k-1] tail(X[0]) ... tail(X[k-1])
对于X[i],如果其为固定大小的类型,head函数定义为,head(X[i]) = enc(X[i])。tail函数定义为tail(X[i]) = ""。
对于动态内容类型