什么是abi
ABI是Application Binary Interface
的缩写,字面意思应用二进制接口
,可以通俗的理解为合约的接口说明。当合约被编译后,那么它的abi
也就确定了。
接口选择
合约交互中函数调用的调用数据的前四字节指定要调用的函数。0x后的前四个字节,这个是Keccak(SHA-3)函数
的签名散列。签名定义为基本原型的规范表达,即函数名和参数类型列表中的圆括号。参数类型用一个逗号分隔——不使用空格。那么函数调用数据就是0x+函数名称(函数类型N,函数类型N1,..)
的Keccak(SHA-3)值的前4个字节
例如:
function issue(address account, uint amount) {
if (msg.sender != issuer) throw;
balances[account] += amount;
}
这个函数名为issue
,存在两个参数,分别为address
和uint
类型,那么调用这个函数需要我们传递的数据为0x+issue(address,uint256)
的sha3值的前4个字节。
web3_sha3 issue(address,uint256)
"0x867904b44b606800f6f10498e11292d04ea19bfc7fe4bc0f1695aa516381f73d"
那么截取前4个字节,一个字节=2个16进制字符,所以为867904b4
,那么最后调用此函数需要传递参数为0x867904b4
。
参数编码
对于调用没有参数的函数,上面就可以了,对于存在参数的函数调用就需要从第5个字节开始添加参数,参数的编码与参数的基本类型关联。
基本类型
- uint
:M为integer类型代表M bits,0 < M <= 256, M % 8 == 0,如uint32,uint8,uint256。 - int
:同上。同为从8到256位的无符号整数。 - uint和int:整型,分别是uint256和int256的别名。这也是上面的例子中函数参数类型是uint,转sha3码时要变成uint256的原因。
- address:地址,20个字节,160bits,一个Ethereum地址,地址的类型也可以有成员作为所有合约的base。
- bool:布尔类型,1个字节,true:1,false:0。
- bytes
:固定大小的字节数组,0<M<=32,byte都是bytes1的别名。 - bytes:动态分配大小字节数组。不是一个值类型!
- string:动态大小UTF8编码的字符串,不是一个值类型!
作为一个常识,使用bytes来表示任意长度原始字节数据,使用string来表示特定长度字符串数据(utf-8编码)。如果你想限定特定数量的字节长度, 就使用bytes1到bytes32, 因为这样占用的存储空间更少。
例子1
合约代码如下:
pragma solidity ^0.4.4;
contract test {
string public a;
function modify_a(string val) {
a = val;
}
}
经过编译之后,它的字节码如下:
6060604052341561000c57fe5b5b60e18061001b6000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630dbe671f146044578063efa3b144146067575bfe5b3415604b57fe5b60516098565b6040518082815260200191505060405180910390f35b3415606e57fe5b60826004808035906020019091905050609e565b6040518082815260200191505060405180910390f35b60005481565b60006005820260008190555060005490505b9190505600a165627a7a72305820e7910925075fb58319dd637b47520b821714dd11ad896cb6d8272151aff714690029
abi如下,是json格式的
[
{
"constant": true,
"inputs": [
],
"name": "a",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "val",
"type": "uint256"
}
],
"name": "modify_a",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"type": "function"
}
]
可以看到,这是一个json格式的数组,它包含两个对象,每个对象都对应着一个合约方法,这里因为a是public类型的,编译的时候会自动为它生成get()方法,所以这个合约实际是包含两个方法的,我们对方法的参数做一下整理。
constant
: 布尔值,如果为true说明方法不会修改合约的状态变量inputs
: 方法参数,它是一个对应数组,数组里的每个对象都是一个参数说明name: 参数名 type: 参数类型
type
: 方法类型,包括function
,constructor
,fallback(缺省方法)
可以缺省,默认为function
。name
: 方法名outputs
: 方法返回值,格式和inputs类型一样,如果没有返回值可以缺省payable
: 布尔值,标明方法是否可以接受ether
构造方法和缺省方法不能有name和outputs,缺省方法也不能有inputs,向一个没有payable标注的方法发送ether会抛异常。
例子2,编译码解释
function sam(bytes name, bool z, uint[] data)
如果我们想调用sam这个函数方法,并输入"dave", true and [1,2,3]这样的三个请求参数。生成ABI编码的方法分如下几个步骤。
一、对函数方法的编码。去掉参数名称,只保留参数类型:sam(bytes,bool,uint256[]),再进行(Keccak哈希值取前4字节)。编码后的结果为0xa5643bf2
二、分析第一个请求参数bytes属于动态类型,对于动态类型的请求参数编码,首先需要编码一个请求参数的放置位置的参数(有点拗口,意思就是你要传“dave”,那么你先确认下这个“dave”放在最终的ABI请求体中的哪一位传入)。开始分析:
第一个32位属于第一个参数的,但是它为动态类型,所以此处需编码“dave”的参数的放置位置的值(不是输入参数的编码),先不确定位置在哪儿,暂时站着32位
第二个32位属于第二个请求参数的,为bool类型,属于静态类型。可以直接转换输入值,此处为true,编码后的值为:0x0000000000000000000000000000000000000000000000000000000000000001(
第二个32位
)第三个32位属于第三个请求参数的,它也为动态类型,所以此处也需要放[1,2,3]的参数放置位置的值(不是输入参数的编码),此时也不确定到底是多少,咱接着往下走。
第四个32位。按照静态类型来算的话,sam方法共3个请求参数,每个32位,上边已经有3个32位的编码了,算编码完了的,但是这里有2个动态类型,第一个32位和第三个32位被参数的所放位置的值给占了,第一个和第三个动态类型的值还没编码的,所以第四个32位就用该放“dave”参数的编码信息了,注意:此处就确定了“dave”的编码位置为32*3=96,对96进行16进制编码并补齐32位的结果为:0x0000000000000000000000000000000000000000000000000000000000000060(
第一个32位
)。确定了第一个32位的值后,我们再来看下第四位该传"dave"参数的什么值。注意它是动态类型,还不能直接传“dave”的编码值,此处传的是动态类型的长度,“dave”的长度为4,所以16进制编码并补齐32位后的结果为:0x0000000000000000000000000000000000000000000000000000000000000004(第四个32位
)第五个32位。上边第四个传了“dave”的参数长度值,所以第五个就是“dave”的编码值,utf8编码”dave”的Hex值,右补齐32位,结果为: 0x6461766500000000000000000000000000000000000000000000000000000000(
第五个32位
)第六个32位。经过上边的步骤第一个和第二个输入参数都编码完成了,此处就应该放第三个参数[1,2,3]的长度值了。注意:此时就确定了第三个32位的值为5*32=160,换算为16进制并补齐32位的值就为:0x00000000000000000000000000000000000000000000000000000000000000a0(
第三个32位
)。然后接着还是第六个32位的值长度为3,转换为16进制编码并补齐32位后的结果为:0x0000000000000000000000000000000000000000000000000000000000000003(第六个32位
)第七个32位。因为[1,2,3]是3个参数,所以此处得分开为3个uint256。第七位就为参数1的16进制编码并补齐32位的结果为:0x0000000000000000000000000000000000000000000000000000000000000001(
第七个32位
)第八个32位。为参数2的16进制编码并补齐32位的结果为:0x0000000000000000000000000000000000000000000000000000000000000002(
第八个32位
)第九个32位。为参数3的16进制编码并补齐32位的结果为:0x0000000000000000000000000000000000000000000000000000000000000003(
第九个32位
)
最终的转换结果为:
0xa5643bf2
0000000000000000000000000000000000000000000000000000000000000060
0000000000000000000000000000000000000000000000000000000000000001
00000000000000000000000000000000000000000000000000000000000000a0
0000000000000000000000000000000000000000000000000000000000000004
6461766500000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000003
0000000000000000000000000000000000000000000000000000000000000001
0000000000000000000000000000000000000000000000000000000000000002
0000000000000000000000000000000000000000000000000000000000000003
以太坊官方ABI介绍地址:https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI