目录
1. 调用已部署的智能合约
相关截图来自:
1.1. ABI是payload编码的依据
ABI,应用二进制接口(Application Binary Interface)。它是从区块链外部与合约进行交互以及合约与合约间进行交互的一种标准方式。根据它可以得到函数签名编码,同样根据它以及实际参数得到参数编码,两部分的字节序列拼接而得payload。
1.2. 函数签名编码
函数签名编码又叫函数选择器,是函数签名的 Keccak(SHA-3)哈希的前4字节。函数签名:函数名称加上由括号括起来的参数类型列表,参数类型间由一个逗号分隔开,且没有空格。函数的返回类型并不是这个签名的一部分。
1.3. 参数编码
参数类型可以分为:
l 基础类型,如uint、int、address、uint、int、bool、fixedx、ufixedx、fixed、ufixed、bytes、function。
l 定长数组类型:[M]。
l 非定长数组类型:bytes、string(UTF-8)、[]。
以下类型被称为“动态”:
l bytes
l string
l 任意类型 T 的变长数组T[]
l 任意动态类型 T 的定长数组T[k](k >= 0)
l 由动态的 Ti (1 <= i <= k)构成的元组 (T1,...,Tk)
所有其他类型都被称为“静态”。
不同类型的参数编码方式不一样,下面以实际例子讲解。
1.4. payload编码原理
合约如下:
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 {}
}
想用 69 和true做参数调用baz,我们总共需要传送68字节,可以分解为:
l 0xcdcd77c0:方法ID。这源自ASCII格式的baz(uint32,bool)签名的Keccak哈希的前4字节。
l 0x0000000000000000000000000000000000000000000000000000000000000045:第一个参数,一个被用0值字节补充到32字节的uint32值69。
l 0x0000000000000000000000000000000000000000000000000000000000000001:第二个参数,一个被用0值字节补充到32字节的boolean值true。
合起来就是:
0xcdcd77c000000000000000000000000000000000000000000000000000000000000000450000000000000000000000000000000000000000000000000000000000000001
如它返回 false,那么它的输出将是一个字节数组,一个bool值:
0x0000000000000000000000000000000000000000000000000000000000000000
想用 ["abc", "def"] 做参数调用bar,我们总共需要传送68字节,可以分解为:
l 0xfce353f6:方法ID。源自bar(bytes3[2])的签名。
l 0x6162630000000000000000000000000000000000000000000000000000000000:第一个参数的第一部分,一个bytes3值"abc"(左对齐)。
l 0x6465660000000000000000000000000000000000000000000000000000000000:第一个参数的第二部分,一个bytes3值"def"(左对齐)。
合起来就是:
0xfce353f661626300000000000000000000000000000000000000000000000000000000006465660000000000000000000000000000000000000000000000000000000000
想用 "dave"、true和[1,2,3]作为参数调用sam,我们总共需要传送292字节,可以分解为:
l 0xa5643bf2:方法ID。源自sam(bytes,bool,uint256[])的签名。注意,uint被替换为了它的权威代表uint256。
l 0x0000000000000000000000000000000000000000000000000000000000000060:第一个参数(动态类型)的数据部分的位置,即从参数编码块开始位置算起的字节数。在这里,是0x60。
l 0x0000000000000000000000000000000000000000000000000000000000000001:第二个参数:boolean的true。
l 0x00000000000000000000000000000000000000000000000000000000000000a0:第三个参数(动态类型)的数据部分的位置,由字节数计量。在这里,是0xa0。
l 0x0000000000000000000000000000000000000000000000000000000000000004:第一个参数的数据部分,以字节数组的元素个数作为开始,在这里,是4。
l 0x6461766500000000000000000000000000000000000000000000000000000000:第一个参数的内容:"dave"的UTF-8编码(在这里等同于ASCII编码),并在右侧(低位)用0值字节补充到32字节。
l 0x0000000000000000000000000000000000000000000000000000000000000003:第三个参数的数据部分,以数组的元素个数作为开始,在这里,是3。
l 0x0000000000000000000000000000000000000000000000000000000000000001:第三个参数的第一个数组元素。
l 0x0000000000000000000000000000000000000000000000000000000000000002:第三个参数的第二个数组元素。
l 0x0000000000000000000000000000000000000000000000000000000000000003:第三个参数的第三个数组元素。
合起来就是:
0xa5643bf20000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000464617665000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003
实际上,函数返回值和事件的参数也会被用同样的方式进行编码。
1.5. ABI的JSON表示
一个描述函数的ABI的JSON表示含如下构成:
当源码被编译后,就能得到函数的ABI,如Remix上的情况如下:
1.6. EthereumJ内置的payload编码工具
Java版Ethereum已经实现了编码功能的内置:
可以看到,内置的功能可以直接输入ABI的JSON表示,然后给出实际的参数就可以得到整个的payload。
1.7. 合约执行过程及结果解析
合约执行过程分析:
如果data小于4字节,则返回程序结果Return为空,并置为REVERT。
加载data,取出前4字节,与合约的所有方法的4字节码编码对比,匹配上则跳转到指定方法的入口并执行。然后执行,如果有返回值,则得到后放到程序结果的Return中。
Abi.Function的decodeResult方法就可以解析返回的Return数据,得到的是List>。
1.8. 参考资料