智能合约的 ABI(应用程序二进制接口)用于定义与以太坊智能合约交互的标准方法。本文详细介绍了 ABI 的构成,如何生成和使用 ABI,包括具体的示例代码,帮助开发者理解如何与智能合约进行有效的交互。
智能合约的 ABI 是 “Application Binary Interface” 的缩写。它定义了在以太坊生态系统中与合约进行交互的标准方式;
智能合约的 “ABI” 是 “Application Binary Interface” 的缩写。它定义了从区块链外部由人类以及合约与合约之间的交互标准方式。
在本指南中,你将了解有关智能合约 ABI 的所有知识,以及如何获取它以开始与之交互。
让我们直接进入一个示例。
正如我们之前所说,智能合约的 “ABI” 定义了与它进行交互的标准方式,既可以从区块链外部由人类进行,也可以用于合约间交互— 让我们来看以下智能合约:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
contract Counter {
uint256 public number;
function setNumber(uint256 newNumber) public {
number = newNumber;
}
function increment() public {
number++;
}
}
当我们编译这个智能合约并在链上部署时,合约 实际上 是什么?所有这些英文单词都很好,但它们并不是存储在区块链上的内容。
相反,这就是:
0x608060405234801561001057600080fd5b5060f78061001f6000396000f3fe6080604052348015600f57600080fd5b5060043610603c5760003560e01c80633fb5c1cb1460415780638381f58a146053578063d09de08a14606d575b600080fd5b6051604c3660046083565b600055565b005b605b60005481565b60405190815260200160405180910390f35b6051600080549080607c83609b565b9190505550565b600060208284031215609457600080fd5b5035919050565b60006001820160ba57634e487b7160e01b600052601160045260246000fd5b506001019056fea26469706673582212202d7dee18f1939c15cddbe7e354ba70087303b1f831d9e3192e0fcd25106ce31064736f6c63430008160033
一堆十六进制和/或二进制。因此,当我们与这堆十六进制进行互动时,我们应该大概知道:
智能合约 ABI 就是照亮我们如何与这堆无意义的十六进制进行交互的指引,因为请记住,人类可读的 Solidity 或 Viper 并不是存储在链上的内容;而是十六进制的数据!
使用上面的合约,ABI 看起来像这样:
{"abi": [
{
"type": "function",
"name": "increment",
"inputs": [],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"type": "function",
"name": "number",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "setNumber",
"inputs": [
{
"name": "newNumber",
"type": "uint256",
"internalType": "uint256"
}
],
"outputs": [],
"stateMutability": "nonpayable"
}
]
}
这是允许我们了解如何与此智能合约进行交互并发送十六进制数据的 JSON。假设我想调用这个合约的 setNumber
函数,那么我需要发送什么确切的数据以调用该函数?好吧,我可以查看智能合约 ABI 中的 setNumber
函数来弄清楚:
{
"type": "function",
"name": "setNumber",
"inputs": [
{
"name": "newNumber",
"type": "uint256",
"internalType": "uint256"
}
],
"outputs": [],
"stateMutability": "nonpayable"
}
我可以看到这是一个接受 uint256
作为输入的函数,没有输出,并且不是可支付的。根据这些信息,我可以使用 Foundry 的 cast
来制作我需要发送给这个合约的原始数据:
cast calldata "setNumber(uint256)" 8
## 返回:
0x3fb5c1cb0000000000000000000000000000000000000000000000000000000000000008
因为我可以访问智能合约 ABI,所以我能够推导出创建与该合约交互所需的数据的信息。
ABI 还使我们能够将这堆机器可读的代码转换 回 为人类可读的代码。
cast calldata-decode "setNumber(uint256)" 0x3fb5c1cb0000000000000000000000000000000000000000000000000000000000000008
## 返回
8
我们进入了一些关于函数选择器与函数签名的领域,但那是另一个话题。你可以在 Cyfrin Updraft 上了解关于函数选择器和函数签名的所有知识。
正如你所看到的,ABI 帮助人类将人类可读的代码转换为计算机代码,并将计算机代码转换回人类可读的代码。如果没有 ABI,我们将不得不使用原始的低级十六进制或字节码编写所有代码,这会使阅读复杂的代码库变得非常困难。
ABI 包含每个智能合约中的多个函数的信息:
开发者可以使用 Solidity 编译器 (solc
)、Remix 或其他开发框架,如 Foundry 或 Hardhat,生成智能合约 ABI。
通常,这些工具在编译智能合约时会自动创建 ABI。
在 Remix 上编译智能合约时,你可以看到一个 ABI 复制按钮。
对于 Foundry,成功运行 forge build
后,你可以通过查看 out
文件夹中的编译详细信息找到任何智能合约的 ABI。
对于 Hardhat,我们可以在 artifacts/build-info
文件夹中找到 ABI。成功运行 npx hardhat compile
后,这些文件夹将被生成。
你可能在你的 Solidity 智能合约代码中见过接口。当在 Solidity 中调用合约函数时,我们的智能合约需要确定如何将原始数据发送到合约。在 Solidity 中,提供合约接口是一种隐性提供 ABI 的好方法。例如:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
interface ICounter {
function setNumber(uint256 newNumber) external;
function increment() external;
}
上面的接口可以编译,产生一个 ABI,可以帮助基于 Solidity 的合约构建与我们的 Counter
合约交互所需的原始数据。事实上,如果你编译这个接口,你将获得与原始合约几乎完全相同的 ABI!
并不完全相同,因为我们为了简单起见省略了 number
部分。
如果我们有这个接口,那么我们就可以调用 Counter
合约的 setNumber
函数,因为在底层,Solidity 将使用从这个接口生成的 ABI 来帮助生成与我们的 Counter
合约调用所需的原始数据。
ICounter(counter_address).setNumber(8);
我们可以 100% 硬编码使用原始数据调用这个 setNumber
函数:
counter_address.call(0x3fb5c1cb0000000000000000000000000000000000000000000000000000000000000008);
然而,你可以很容易地看到,这对于人类来讲是可怕的阅读体验,所以我们使用接口使我们的代码库更具可读性。
此外,在我们所有的基于 Javascript 的应用中,我们也需要 ABI 来知道发送给合约的数据。如果你使用的是 ethers 包,你可能见过类似这样的设置:
const provider = new ethers.providers.JsonRpcProvider();
const counterAddress = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"
const counterABI = [
{
"type": "function",
"name": "increment",
"inputs": [],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"type": "function",
"name": "number",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "setNumber",
"inputs": [
{
"name": "newNumber",
"type": "uint256",
"internalType": "uint256"
}
],
"outputs": [],
"stateMutability": "nonpayable"
}
]
// 合约对象
const counterContract = new ethers.Contract(counterAddress, counterABI, provider);
使用 ABI,我们现在可以将 setNumber
作为我们 counterContract
对象上的函数进行调用。
await counterContract.setNumber(8);
通过提供上述 ABI,ethers 包可以将 setNumber(8)
转换为智能合约可以处理的原始十六进制数据。否则,我们将不得不再次手动制作原始数据,这是不可读的,并且编码时会让人感到烦恼!
ABI,或者称“应用程序二进制接口”,允许人类和机器双向编码或解码数据。我们的包用于与智能合约进行交互,知道如何编码我们的人类可读代码,当人类收到一堆机器可读的字节码时,我们可以将其转换回可读的形式。
- 原文链接: cyfrin.io/blog/what-is-a...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!