0%

eth | 什么是 abi

这里讲述什么是 abi


参考资料


另外,可以参考进阶版本的 ABI

ps: 2021-6-7

我来说一下什么是 abi 吧。

合约也是各种方法组成的,各种合约架设在不同的公链上,那么如何调用合约代码吗?

首先,合约本身就有各种默认的方法,每个人又可以在自己合约上定义各种方法。那么,如何让别人知道呢?一般来说,合约都会开源,我们能从区块链浏览器上看到。如果,没有开源的话,只能靠抓包来分析。

所谓的 abi 就是合约本身定义的各种方法,由于各种公链本身并不知道相关合约的方法,所以,我们要进行事先声明,在事先声明的时候,可以声明全部的 abi 也可以只声明用到的那部分 abi

以这个合约分析,因为其都已经开源了,所以,可以很容易的找到公开的 abi

那么,一般来说,如何调用相关的合约事件呢?一般来说是通过转账解决的。

你向合约地址发一个 value = 0 的转账,并且,在 input data 中包含相关的方法即可,这个可以在下面的案例分析中,看的比较清楚。


ABI 是什么


ABI 全称是 Application Binary Interface,翻译过来就是:应用程序二进制接口,简单来说就是 以太坊的调用合约时的接口说明。还不是很理解,没关系。


调用合约函数发生了什么


从外部施加给以太坊的行为都称之为向以太坊网络提交了一个交易, 调用合约函数其实是向合约地址(账户)提交了一个交易,这个交易有一个附加数据,这个附加的数据就是ABI的编码数据。

比特币的交易也可以附加数据,以太坊革命性的地方就是能把附加数据转化为都函数的执行。

因此要想和合约交互,就离不开ABI数据。


演示调用函数


以下面以个最简单的合约为例,我们看看用参数 1 调用 set(uint x),这个交易附带的数据是什么。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
pragma solidity ^0.4.0;

contract SimpleStorage {

uint storedData;

function set(uint x) public {
storedData = x;
}

function get() public constant returns (uint) {
return storedData;
}
}

pragma solidity ^0.4.0;

contract SimpleStorage {

uint storedData;

function set(uint x) public {
storedData = x;
}

function get() public constant returns (uint) {
return storedData;
}
}

当然第一步需要先把合约部署到以太坊网络(其实部署也是一个)上,然后用 1 作为参数调用 set,如下图:

其中 input data 如下

1
2
3
4
Function: set(uint256 x) ***

MethodID: 0x60fe47b1
[0]: 0000000000000000000000000000000000000000000000000000000000000001

全部的为

0x60fe47b10000000000000000000000000000000000000000000000000000000000000001

ABI 编码分析


我把上面交易的附加数据拷贝出来分析一下,这个数据可以分成两个子部分:

  • 函数选择器(4字节)
    • 0x60fe47b1
  • 第一个参数(32字节)
    • 00000000000000000000000000000000000000000000000000000000000000001

函数选择器值,实际是对函数签名字符串进行sha3keccak256)哈希运算之后,取前4个字节,用代码表示就是:

bytes4(sha3(“set(uint256)”)) == 0x60fe47b1

python 代码为

pip install pysha3
1
2
3
4
5
import sha3

s = sha3.keccak_256()
s.update(b"set(uint256)")
print(s.hexdigest())

输入

60fe47b16ed402aae66ca03d2bfc51478ee897c26a1158669c7058d5f24898f4

参数部分则是使用对应的16进制数。

现在就好理解 附加数据怎么转化为对应的函数调用。


案例分析


案例一 「合约没有开源」

参考

案例二 「合约开源」

input data 中,我们可以看到,原来的 input data

1
0xb0c702fd000000000000000000000000000000000000000000000000000000006ad3920e00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000041c2f872fc3b6743294f262d3e2bb259925d2d8fc02e5244c40cb3c0c7e8ce540565337a44b6098b450f3b1a4f3f0477dc0672d90a6002cc1ad62c19a585f5b5551b00000000000000000000000000000000000000000000000000000000000000

经过 view 分析,可以看到

1
2
3
4
5
6
7
8
9
Function: cashCheque(uint256 cumulativePayout, bytes issuerSig)

MethodID: 0xb0c702fd
[0]: 000000000000000000000000000000000000000000000000000000006ad3920e
[1]: 0000000000000000000000000000000000000000000000000000000000000040
[2]: 0000000000000000000000000000000000000000000000000000000000000041
[3]: c2f872fc3b6743294f262d3e2bb259925d2d8fc02e5244c40cb3c0c7e8ce5405
[4]: 65337a44b6098b450f3b1a4f3f0477dc0672d90a6002cc1ad62c19a585f5b555
[5]: 1b00000000000000000000000000000000000000000000000000000000000000

也就是说,这个方法是调用了合约的 cashCheque 方法。

经过,合约开源的 abi ,查到该方法是

1
2
3
4
5
6
7
8
9
{
"inputs": [
{"internalType": "uint256", "name": "cumulativePayout", "type": "uint256"},
{"internalType": "bytes", "name": "issuerSig", "type": "bytes"}],
"name": "cashCheque",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}

其中有两个输出,再回到原来的浏览器,使用 decode input data 查看,发现

1
2
3
#   Name                Type      Data
0 cumulativePayout uint256 1792250382
1 issuerSig bytes c2f872fc3b6743294f262d3e2bb259925d2d8fc02e5244c40cb3c0c7e8ce540565337a44b6098b450f3b1a4f3f0477dc0672d90a6002cc1ad62c19a585f5b5551b

abi 的结构一般如下

  • 10 「方法的 256编码

可以验证

1
2
3
4
5
import sha3

s = sha3.keccak_256()
s.update(b"cashCheque(uint256,bytes)")
print(s.hexdigest())

输出

b0c702fdd83cf9414264f976f4c28f9d8a42e11b49612183d5f68f87b23398f2

正好取前八位,组成

0xb0c702fd

特别需要注意的是,编码的时候,参数之间没有空格。

然后, decode input data179225038216 进制编码正好是

1
000000000000000000000000000000000000000000000000000000006ad3920e

而,经过多次转账发现

1
2
[1]:  0000000000000000000000000000000000000000000000000000000000000040
[2]: 0000000000000000000000000000000000000000000000000000000000000041

这两个是固定的,剩下的参数和 decode input dataissuerSig 一样,经过,几次分析发现,该参数是原网站上的一个加密参数,并且可以通过 restful的形式取得,也就是,所有的参数,都已经分析完成,这个时候,就可以写方法构建了。

该案例是 gpfstools 中的一段。

OK,接下来我们就开始构建代码。

这里只写关键性的代码,想要看全局还需要自己看上面的项目。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
let w3 = new Web3(new Web3.providers.HttpProvider("https://bsc-dataseed4.binance.org"));
let contractAddr = "0x5e772acf0f20b0315391021e0884cb1f1aa4545c";
let tokenContractABI = [
{
"constant": true,
"inputs": [{"name": "who", "type": "address"}],
"name": "balanceOf",
"outputs": [{"name": "", "type": "uint256"}],
"payable": false,
"stateMutability": "view",
"type": "function"
}, {
"inputs": [
{"internalType": "uint256", "name": "cumulativePayout", "type": "uint256"},
{"internalType": "bytes", "name": "issuerSig", "type": "bytes"}],
"name": "cashCheque",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}]
let tokenContract = new w3.eth.Contract(tokenContractABI, contractAddr);

... // 获取余额等一系列判断条件
...

w3.eth.getTransactionCount(address, ((error, count) => {
if (error) {
let element = '<tr><th scope="row">' + 1 + '</th><td>' + 0 + '</td><td>' + address + '</td><td>' + 0 + '</td><td>' + 0 + '</td><td>' + "未知错误" + '</td></tr>'
$("#hex").append(element)
return
}

let rawTx = {
from: address,
to: '0x5e772acf0f20b0315391021e0884cb1f1aa4545c',
value: w3.utils.toHex(w3.utils.toWei('0', 'ether')),
nonce: w3.utils.toHex(count),
gasLimit: w3.utils.toHex(100000),
gasPrice: w3.utils.toHex(w3.utils.toWei('5', 'gwei')),
data: tokenContract.methods.cashCheque(amount_16, signature_16).encodeABI()
}


let tx = new Tx(rawTx);
tx.sign(new Buffer(private_key, 'hex'));
let serializedTx = tx.serialize();
let raw = '0x' + serializedTx.toString('hex')
w3.eth.sendSignedTransaction(raw, (err, txHash) => {
if(err){
...
}else {
...
}
})

}))

上面的 data 会自动进行扩展和填充,比如

会自己补零,会自己把

1
2
[1]:  0000000000000000000000000000000000000000000000000000000000000040
[2]: 0000000000000000000000000000000000000000000000000000000000000041

填上。

请我喝杯咖啡吧~