这里讲述什么是 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字节)
第一个参数(32字节)
00000000000000000000000000000000000000000000000000000000000000001
函数选择器值,实际是对函数签名字符串进行sha3
(keccak256
)哈希运算之后,取前4
个字节,用代码表示就是:
bytes4(sha3(“set(uint256)”)) == 0x60fe47b1
python
代码为
pip install pysha3
1 2 3 4 5 import sha3s = 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 c 2 f872 fc3 b6743294 f262 d3e2 bb259925 d2 d8 fc02e5244 c 40 cb3 c 0 c 7e8 ce540565337 a44 b6098 b450 f3 b1 a4 f3 f0477 dc0672 d90 a6002 cc 1 ad62 c 19 a585 f5 b5551 b
abi
的结构一般如下
可以验证
1 2 3 4 5 import sha3 s = sha3.keccak_256() s.update(b"cashCheque(uint256,bytes)" ) print (s.hexdigest() )
输出
b0c702fdd83cf9414264f976f4c28f9d8a42e11b49612183d5f68f87b23398f2
正好取前八位,组成
0xb0c702fd
特别需要注意的是,编码的时候,参数之间没有空格。
然后, decode input data
的 1792250382
的 16
进制编码正好是
1 000000000000000000000000000000000000000000000000000000006 ad3920e
而,经过多次转账发现
1 2 [1 ]: 0000000000000000000000000000000000000000000000000000000000000040 [2 ]: 0000000000000000000000000000000000000000000000000000000000000041
这两个是固定的,剩下的参数和 decode input data
的 issuerSig
一样,经过,几次分析发现,该参数是原网站上的一个加密参数,并且可以通过 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
填上。