什么是ABI?ABI=ApplicationBinaryInterface用于定义智能合约接口的规范。它定义了智能合约的函数、事件和数据结构的编码和解码规则,以便其他应用程序可以与智能合约进行交互。简言之,就是以太坊的调用合约时的接口说明。有点类似于Web2API。
ABI = Application Binary Interface
用于定义智能合约接口的规范。它定义了智能合约的函数、事件和数据结构的编码和解码规则,以便其他应用程序可以与智能合约进行交互。
简言之,就是以太坊的调用合约时的接口说明。
有点类似于 Web2 API。
具体来说,Solidity ABI 定义了以下内容:
Smart contract ABIs enable communication and interaction with external apps and other contracts.
遵循 ABI 的规范,其他应用程序可以与智能合约进行数据传输和交互,包括调用合约的函数、监听合约的事件和读取合约中的数据。
所谓的调用合约函数本质上就是向合约地址(即合约账户)提交了一个交易,这个交易有一个附加的数据,这个附加数据就是 ABI 的编码数据。
小结一下:即“调用合约函数 = 向合约地址发送交易”。
调用合约函数示例演示 下面一个Counter合约为例,看看调用 setNumber 函数时,这个交易附带的数据是什么。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
contract Counter {
uint256 public number;
address public owner;
event Counter_SetNumbet(uint256 number);
function setNumber(uint256 newValue) public {
number = newValue;
}
function getNumber() public view returns (uint256) {
return number;
}
}
ABI 如下:
[
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "number",
"type": "uint256"
}
],
"name": "Counter_SetNumbet",
"type": "event"
},
{
"inputs": [
],
"name": "getNumber",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
],
"name": "number",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "newValue",
"type": "uint256"
}
],
"name": "setNumber",
"outputs": [
],
"stateMutability": "nonpayable",
"type": "function"
}
]
6080604052348015600e575f80fd5b506101e68061001c5f395ff3fe608060405234801561000f575f80fd5b506004361061003f575f3560e01c80638381f58a146100435780638da5cb5b14610061578063c874936c1461007f575b5f80fd5b61004b61009b565b60405161005891906100e6565b60405180910390f35b6100696100a0565b604051610076919061013e565b60405180910390f35b61009960048036038101906100949190610185565b6100c5565b005b5f5481565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b805f8190555050565b5f819050919050565b6100e0816100ce565b82525050565b5f6020820190506100f95f8301846100d7565b92915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f610128826100ff565b9050919050565b6101388161011e565b82525050565b5f6020820190506101515f83018461012f565b92915050565b5f80fd5b610164816100ce565b811461016e575f80fd5b50565b5f8135905061017f8161015b565b92915050565b5f6020828403121561019a57610199610157565b5b5f6101a784828501610171565b9150509291505056fea2646970667358221220ee772914cef194a9e7d2e75a6404ae20f227006e7b82f8a7b22f0701d1657fd064736f6c63430008190033
小结一下:
我们使用 https://remix.ethereum.org/ 在测试网 Sepolia 部署一下合约,然后用 “2”作为参数调用一下 setNumber, 如下图: 然后打开etherscan查看交易详情数据, 可以看到其附加数据如下图: 下面就是 ABI 编码数据:
0x3fb5c1cb0000000000000000000000000000000000000000000000000000000000000002
上面的 ABI 编码数据包含 2 个部分:
函数选择器的值怎么来的呢?其实就是对函数签名的字符串进行 keccak256 哈希运算后,取前 4 个字节,代码如下:
function getSetNumberSelector() external pure returns (bytes4) {
return bytes4(keccak256(bytes("setNumber(uint256)")));
}
参数部分则是使用对应的16进制数,即数字 2,用 32 字节表示。
现在我们就明白了附加数据如何转换为对应的函数调用。小结一下:前 4 个字节为函数选择器,后面的数据为调用函数的参数值。
【小工具】推荐一个实用的网站:https://www.4byte.directory/,可以使用前 4 个字节的 ABI 编码快速查询函数选择器。
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0; contract ABIDemo { // 获取函数选择器的值 function getSetNumberSelector() external pure returns (bytes4) { return bytes4(keccak256(bytes("setNumber(uint256)"))); }
// 获取 ABI 编码(带有参数值)
function abiEncode() external pure returns (bytes memory) {
// abi.encode(2); // 计算2的ABI编码
//计算函数setNumber(uint256)及参数 2 的 ABI 编码
return abi.encodeWithSignature("setNumber(uint256)", 2);
}
}
- 方式2:使用 cast 命令行
补充:如果你的电脑安装了 foundry,也可以使用下面的快捷命令达到同样的效果。
注意 cast calldata 包含了函数选择器。
➜ cast sig "setNumber(uint256)" 0x3fb5c1cb ➜ cast abi-encode "setNumber(uint256)" 2 0x0000000000000000000000000000000000000000000000000000000000000002 ➜ cast calldata "setNumber(uint256)" 2 0x3fb5c1cb0000000000000000000000000000000000000000000000000000000000000002
## 解码 Decode
- 方式1:使用 solidity 解码
function encodeString() public pure returns (bytes memory) {
bytes memory someString = abi.encode("some string");
return someString;
}
function decodeString() public pure returns (string memory) {
string memory someString = abi.decode(encodeString(), (string));
return someString;
}
- 方式2:使用 cast 命令行
➜ cast 4byte-decode 0x3fb5c1cb0000000000000000000000000000000000000000000000000000000000000002 1) "setNumber(uint256)" ➜ solidity_foundry git:(main) ✗ cast abi-decode "balanceOf(address)(uint256)" 0x000000000000000000000000000000000000000000000000000000000000000a 10 ➜ cast calldata-decode "setNumber(uint256)" 0x3fb5c1cb0000000000000000000000000000000000000000000000000000000000000002 2
## Solidity ABI 编码函数
solidity 提供了ABI 的相关 API, 用来直接得到ABI编码信息,这些函数有:
- abi.encode(...) returns (bytes):计算参数的ABI编码。
- abi.encodePacked(...) returns (bytes):计算参数的紧密打包编码
- abi. encodeWithSelector(bytes4 selector, ...) returns (bytes): 计算函数选择器和参数的ABI编码
- abi.encodeWithSignature(string signature, ...) returns (bytes): 等价于* abi.encodeWithSelector(bytes4(keccak256(signature), ...)
There are no shortcuts to any place worth going.Together we progress.
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!