教程 | 【Ethereum 智能合约开发笔记】深入 Ethereum 智能合约 ABI

Ajian   |     |   4755 次阅读

img-以太猫合约 ABI-

开发 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 和程式的关系。

img


编译和部署智能合约

话说在 Ethereum 智能合约可以被大家使用前,必须先被部署到区块链上。从智能合约的原始码到使用智能合约,大概包含几个步骤:

  1. 撰写智能合约的原始码(一般是用 Solidity 写)
  2. 编译智能合约的原始码成可在 EVM 上执行的 bytecode(binary code)。同时可以透过编译取得智能合约的 ABI
  3. 部署智能合约,实际上是把 bytecode 储存在链上(透过一个 transaction),并取得一个专属这个合约的地址
  4. 如果要写个程式呼叫这个智能合约,就要把资料发送到这个合约的地址(一样透过一个 transaction)。Ethereum 节点会根据输入的资料,选择要执行合约中的哪一个 function 和要输入的参数

而要如何知道这个智能合约提供哪些 function 以及应该要传入什么样的参数呢?这些资讯就是记录在智能合约的 ABI。


Ethereum 智能合约 ABI

Ethereum 智能合约 ABI 用一个 array 表示,其中会包含数个用 JSON 格式表示的 FunctionEvent。根据最新的 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 使用相同表示方式。如果没有回传值可忽略,值为 []
  • payabletrue,如果 function 可收 Ether,预设为 false
  • constanttrue,如果 function 不会改写区块链的状态,反之为 false
  • stateMutability:a string,其值可能为以下其中之一:"pure"(不会读和写区块链状态)、"view"(会读区块链状态,但不会改写区块链状态)、"payable" and "nonpayable"(会改写区块链状态,且如可收 Ether 为 "payable",反之为 "nonpayable")

仔细看会发现 payableconstant 这两个栏位所描述的内容,似乎已包含在 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 这个栏位统一表示,payableconstant 目前保留是为了向后兼容。这个更新详细的内容和讨论可参考: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。

img-event Set(address indexed _from, uint value)-

要详细了解 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 版本。

img-Remix-

Etherscan

许多知名合约会把合约 source code 放上 Etherscan 做验证,可以同时看到合约 ABI。

img-Etherscan-

另外有提供 API 可以取得经验证的合约 ABI。


References

相关套件


原文链接: https://www.jianshu.com/p/fb1bd44f7b9b
作者: 安德森_Anderson

本文首发于作者的简书,作者授权 EthFans 转载。


Ethereum 智能合约开发笔记系列:

使用 Infura 和 web3.js 呼叫合约
使用 Remix


你可能还会喜欢:

教程 | 以太坊编程简单介绍,Part 2
引介 | 以太坊交易的生命周期
以太坊生态系统中的工具和技术

 
3 人喜欢