以太坊合约应用程序二进制接口(ABI)

在计算机软件中,应用程序二进制接口(ABI)是两个程序模块之间的接口;通常,一个在机器代码级别,另一个在用户运行的程序级别。ABI定义了如何在*机器码*中访问数据结构和功能;不要与API混淆,API以高级的,通常是人类可读的格式将访问定义为*源代码*。因此,ABI是将数据编码到机器码,和从机器码解码数据的主要方式。

在以太坊中,ABI用于编码EVM的合约调用,并从交易中读取数据。ABI的目的是定义合约中的哪些函数可以被调用,并描述函数如何接受参数并返回数据。

合约ABI的JSON格式由一系列函数描述(参见[solidity_functions])和事件(参见[solidity_events])的数组给出。函数描述是一个JSON对象,它包含`type`,nameinputsoutputsconstant`和`payable`字段。事件描述对象具有`typename,`inputs`和`anonymous`的字段。

我们使用+solc+命令行Solidity编译器为我们的+Faucet.sol+示例合约生成ABI:

  1. solc --abi Faucet.sol
  2. ======= Faucet.sol:Faucet =======
  3. Contract JSON ABI
  4. [{"constant":false,"inputs":[{"name":"withdraw_amount","type":"uint256"}],"name":"withdraw","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"payable":true,"stateMutability":"payable","type":"fallback"}]

如你所见,编译器会生成一个描述由 Faucet.sol 定义的两个函数的JSON对象。这个JSON对象可以被任何希望在部署时访问 Faucet 合约的应用程序使用。使用ABI,应用程序(如钱包或DApp浏览器)可以使用正确的参数和参数类型构造调用 Faucet 中的函数的交易。例如,钱包会知道要调用函数+withdraw+,它必须提供名为 withdraw_amount 的 uint256 参数。钱包可以提示用户提供该值,然后创建一个编码它并执行+withdraw+功能的交易。

应用程序与合约进行交互所需的全部内容是ABI以及合约的部署地址。

选择Solidity编译器和语言版本

正如我们在 Compiling Faucet.sol with solc 中看到的,我们的 Faucet 合约在Solidity 0.4.21版本中成功编译。但是如果我们使用了不同版本的Solidity编译器呢?语言仍然不断变化,事情可能会以意想不到的方式发生变化。我们的合约非常简单,但如果我们的程序使用了仅添加到Solidity版本+0.4.19+中的功能,并且我们尝试使用+0.4.18+进行编译,该怎么办?

为了解决这些问题,Solidity提供了一个_compiler指令_,称为_version pragma_,指示编译器程序需要特定的编译器(和语言)版本。我们来看一个例子:

  1. pragma solidity ^0.4.19;

Solidity编译器读取版本编译指示,如果编译器版本与版本编译指示不兼容,将会产生错误。在这种情况下,我们的版本编译指出,这个程序可以由Solidity编译器编译,最低版本为+0.4.19+。但是,符号^表示我们允许编译任何_minor修订版_在+0.4.19+之上的,例如+0.4.20+,但不是+0.5.0+(这是一个主要版本,不是小修订版) 。Pragma指令不会编译为EVM字节码。它们仅由编译器用来检查兼容性。

让我们在我们的 Faucet 合约中添加一条编译指示。我们将命名新文件 Faucet2.sol,以便在我们继续处理这些示例时跟踪我们的更改:

Faucet2.sol : Adding the version pragma to Faucet

  1. // Version of Solidity compiler this program was written for
  2. pragma solidity ^0.4.19;
  3. // Our first contract is a faucet!
  4. contract Faucet {
  5. // Give out ether to anyone who asks
  6. function withdraw(uint withdraw_amount) public {
  7. // Limit withdrawal amount
  8. require(withdraw_amount <= 100000000000000000);
  9. // Send the amount to the address that requested it
  10. msg.sender.transfer(withdraw_amount);
  11. }
  12. // Accept any incoming amount
  13. function () public payable {}
  14. }

添加版本 pragma 是最佳实践,因为它避免了编译器和语言版本不匹配的问题。我们将探索其他最佳实践,并在本章中继续改进+Faucet+合约。