以太坊ABI介绍

半兽人 发表于: 2018-05-21   最后更新时间: 2020-03-16 10:45:21  
{{totalSubscript}} 订阅, 6,088 游览

什么是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,存在两个参数,分别为addressuint类型,那么调用这个函数需要我们传递的数据为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: 方法类型,包括functionconstructorfallback(缺省方法)可以缺省,默认为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请求体中的哪一位传入)。开始分析:

  1. 第一个32位属于第一个参数的,但是它为动态类型,所以此处需编码“dave”的参数的放置位置的值(不是输入参数的编码),先不确定位置在哪儿,暂时站着32位

  2. 第二个32位属于第二个请求参数的,为bool类型,属于静态类型。可以直接转换输入值,此处为true,编码后的值为:0x0000000000000000000000000000000000000000000000000000000000000001(第二个32位

  3. 第三个32位属于第三个请求参数的,它也为动态类型,所以此处也需要放[1,2,3]的参数放置位置的值(不是输入参数的编码),此时也不确定到底是多少,咱接着往下走。

  4. 第四个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位

  5. 第五个32位。上边第四个传了“dave”的参数长度值,所以第五个就是“dave”的编码值,utf8编码”dave”的Hex值,右补齐32位,结果为: 0x6461766500000000000000000000000000000000000000000000000000000000(第五个32位

  6. 第六个32位。经过上边的步骤第一个和第二个输入参数都编码完成了,此处就应该放第三个参数[1,2,3]的长度值了。注意:此时就确定了第三个32位的值为5*32=160,换算为16进制并补齐32位的值就为:0x00000000000000000000000000000000000000000000000000000000000000a0(第三个32位)。然后接着还是第六个32位的值长度为3,转换为16进制编码并补齐32位后的结果为:0x0000000000000000000000000000000000000000000000000000000000000003(第六个32位

  7. 第七个32位。因为[1,2,3]是3个参数,所以此处得分开为3个uint256。第七位就为参数1的16进制编码并补齐32位的结果为:0x0000000000000000000000000000000000000000000000000000000000000001(第七个32位

  8. 第八个32位。为参数2的16进制编码并补齐32位的结果为:0x0000000000000000000000000000000000000000000000000000000000000002(第八个32位

  9. 第九个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

更新于 2020-03-16

查看ethereumj更多相关的文章或提一个关于ethereumj的问题,也可以与我们一起分享文章