以太坊合约应用程序二进制接口(ABI)
在计算机软件中,应用程序二进制接口(ABI)是两个程序模块之间的接口;通常,一个在机器代码级别,另一个在用户运行的程序级别。ABI定义了如何在*机器码*中访问数据结构和功能;不要与API混淆,API以高级的,通常是人类可读的格式将访问定义为*源代码*。因此,ABI是将数据编码到机器码,和从机器码解码数据的主要方式。
在以太坊中,ABI用于编码EVM的合约调用,并从交易中读取数据。ABI的目的是定义合约中的哪些函数可以被调用,并描述函数如何接受参数并返回数据。
合约ABI的JSON格式由一系列函数描述(参见[solidity_functions])和事件(参见[solidity_events])的数组给出。函数描述是一个JSON对象,它包含`type`,name
,inputs
,outputs
,constant`和`payable`字段。事件描述对象具有`type
,name
,`inputs`和`anonymous`的字段。
我们使用+solc+命令行Solidity编译器为我们的+Faucet.sol+示例合约生成ABI:
solc --abi Faucet.sol
======= Faucet.sol:Faucet =======
Contract JSON ABI
[{"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_,指示编译器程序需要特定的编译器(和语言)版本。我们来看一个例子:
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
// Version of Solidity compiler this program was written for
pragma solidity ^0.4.19;
// Our first contract is a faucet!
contract Faucet {
// Give out ether to anyone who asks
function withdraw(uint withdraw_amount) public {
// Limit withdrawal amount
require(withdraw_amount <= 100000000000000000);
// Send the amount to the address that requested it
msg.sender.transfer(withdraw_amount);
}
// Accept any incoming amount
function () public payable {}
}
添加版本 pragma 是最佳实践,因为它避免了编译器和语言版本不匹配的问题。我们将探索其他最佳实践,并在本章中继续改进+Faucet+合约。