开发 DApp 时要呼叫在区块链上的 Ethereum 智能合约,就需要智能合约的 ABI。本篇希望更多了解 ABI,像是为什么需要 ABI?如何解读 Ethereum 的智能合约 ABI?以及如何取得智能合约的 ABI?
ABI(Application Binary Interface)
如果理解 API 就很容易了解 ABI。简单来说,API 是程式间互动的介面。这个介面包含程式提供外界存取的 functions、variables 等。ABI 也是程式间互动的介面,但程式是被编译后的 binary code。所以同样的介面,但传递是 binary 格式的资料。所以 ABI 就要描述如何 decode/encode 程式间传递的 binary 资料。下图以 Linux 为例,描述 Linux 中 API、ABI 和程式的关系。
编译和部署智能合约
话说在 Ethereum 智能合约可以被大家使用前,必须先被部署到区块链上。从智能合约的原始码到使用智能合约,大概包含几个步骤:
- 撰写智能合约的原始码(一般是用 Solidity 写)
- 编译智能合约的原始码成可在 EVM 上执行的 bytecode(binary code)。同时可以透过编译取得智能合约的 ABI
- 部署智能合约,实际上是把 bytecode 储存在链上(透过一个 transaction),并取得一个专属这个合约的地址
- 如果要写个程式呼叫这个智能合约,就要把资料发送到这个合约的地址(一样透过一个 transaction)。Ethereum 节点会根据输入的资料,选择要执行合约中的哪一个 function 和要输入的参数
而要如何知道这个智能合约提供哪些 function 以及应该要传入什么样的参数呢?这些资讯就是记录在智能合约的 ABI。
Ethereum 智能合约 ABI
Ethereum 智能合约 ABI 用一个 array 表示,其中会包含数个用 JSON 格式表示的 Function 或 Event。根据最新的 Solidity 文件:
Function
共有 7 个栏位:
-
name
:a string,function 名称 -
type
:a string,"function", "constructor", or "fallback" -
inputs
:an array,function 输入的参数,包含:-
name
:a string,参数名称 -
type
:a string,参数的 data type(e.g. uint256) -
components
:an array,如果输入的参数是 tuple(struct) type 才会有这个栏位。描述 struct 中包含的资料型态
-
-
outputs
:an array,function 的回传值,和inputs
使用相同表示方式。如果没有回传值可忽略,值为[]
-
payable
:true
,如果 function 可收 Ether,预设为false
-
constant
:true
,如果 function 不会改写区块链的状态,反之为false
-
stateMutability
:a string,其值可能为以下其中之一:"pure"(不会读和写区块链状态)、"view"(会读区块链状态,但不会改写区块链状态)、"payable" and "nonpayable"(会改写区块链状态,且如可收 Ether 为 "payable",反之为 "nonpayable")
仔细看会发现
payable
和constant
这两个栏位所描述的内容,似乎已包含在stateMutability
中。
事实的确是这样,在 Solidity v0.4.16 中把 constant
这个修饰 function 的 key words 分成 view
(neither reads from nor writes to the state)和 pure
(does not modify the state),并从 v0.4.17 开始 Type Checker 会强制检查。constant
改为只用来修饰不能被修改的 variable。并在 ABI 中加入 stateMutability
这个栏位统一表示,payable
和 constant
目前保留是为了向后兼容。这个更新详细的内容和讨论可参考:Introduce a real constant keyword and rename the current behaviour #992。
Event
共有 4 个栏位:
-
name
: a string,event 的名称 -
type
: a string,always "event" -
inputs
: an array,输入的参数,包含:-
name
: a string,参数名称 -
type
: a string,参数的 data type(e.g. uint256) -
components
: an array,如果输入的参数是 tuple(struct) type 才会有这个栏位。描述 struct 中包含的资料型态 -
indexed
:true
,如果这个参数被宣告为 indexed(被存在 log 的 topics 中),反之为false
(储存在 log 的 data 中)
-
-
anonymous
:true
,如果 event 被宣告为 anonymous
更新智能合约状态需要发送 transaction,transaction 需要等待验证,所以更新合约状态是非同步的,无法马上取得回传值。使用 Event 可以在状态更新成功后,将相关资讯记录到 Log,并让监听这个 Event 的 DApp 或任何使用介面收到通知。每一笔 transaction 都有对应的 Log。
所以简单来说,Event 可用来:1. 取得 function 更新合约状态后的回传值 2. 也可作为合约另外的储存空间。
Event 的参数分为:有 indexed
,和其他没有 indexed
的。有 indexed
的参数可以使用 filter,例如同一个 Event,我可以选择只监听从特定 address 发出来的交易。每笔 Log 的资料同样分为两部分:Topics(长度最多为 4 的 array) 和 Data。有 indexed 的参数会储存在 Log 的 Topics,其他的存在 Data。如果宣告为 anonymous
,就不会产生以下范例中的 Topics[0],其值为 Event signature 的 hash,作为这个 Event 的 ID。
要详细了解 Solidity Event 可参考 Solidity的event事件(二十一)|入门系列、Solidity的event的支持指引(二十二)|入门系列。
用一个简单的智能合约举例
这个智能合约包含:
-
data
:一个可改写的 state variable,会自动产生一个只能读取的data()
function -
set()
:一个改写data
值的 function -
Set()
:一个在每次更新data
纪录 log 的 event
智能合约 Source Code:
pragma solidity ^0.4.20;
contract SimpleStorage {
uint public data;
event Set(address indexed _from, uint value);
function set(uint x) public {
data = x;
Set(msg.sender, x);
}
}
智能合约 ABI:
[{
"constant": true,
"inputs": [],
"name": "data",
"outputs": [{"name": "","type": "uint256"}],
"payable": false,
"stateMutabㄒility": "view",
"type": "function"
},
{
"anonymous": false,
"inputs": [{"indexed": true,"name": "_from","type": "address"},{"indexed": false,"name": "value","type": "uint256"}],
"name": "Set",
"type": "event"
},
{
"constant": false,
"inputs": [{"name": "x","type": "uint256"}],
"name": "set",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
}]
取得 Ethereum 智能合约 ABI
Solidity Compiler
可以用 Solidity Compiler 取得合约 ABI,我使用 JavaScript 版本的 Compiler 为例。
安装:
npm install solc -g
取得合约 ABI:
solcjs simpleStorage.sol --abi
会产生一个 simpleStorage_sol_SimpleStorage.abi 档案,里面就是合约 ABI 内容。
也可以取得合约 binary code:
solcjs your_contract.sol --bin
Remix
同样使用 Solidity Compiler,也可以用 Remix。在合约的 Details 可以看到完整的 ABI。可以在 Settings 中指定 Compiler 版本。
Etherscan
许多知名合约会把合约 source code 放上 Etherscan 做验证,可以同时看到合约 ABI。
另外有提供 API 可以取得经验证的合约 ABI。
References
- Latest Solidity Document
- Ethereum contract Application Binary Interface(Video)
- What is an application binary interface (ABI)?
- What is an ABI and why is it needed to interact with contracts?
- Solidity撰寫智能合約與注意事項(二) — 介紹如何使用 Event
相关套件
- https://github.com/ethereum/solc-js
- https://github.com/ethereumjs/ethereumjs-abi
- https://github.com/ConsenSys/abi-decoder