浅析 Solidity ABI -应用程序二进制接口
什么是 ABI ?
ABI = Application Binary Interface
用于定义智能合约接口的规范。它定义了智能合约的函数、事件和数据结构的编码和解码规则,以便其他应用程序可以与智能合约进行交互。
简言之,就是以太坊的调用合约时的接口说明。
有点类似于 Web2 API。
具体来说,Solidity ABI 定义了以下内容:
- 函数编码和解码规则: a. 规定了如何将函数名和参数编码为字节数组,以便在智能合约之间进行调用。 b. 规定了如何解码传入的字节数组,以获取函数名和参数值。
- 事件编码和解码规则: a. 规定了如何将事件名称和参数编码为字节数组,以便在智能合约中触发事件。 b. 规定了如何解码传入的字节数组,以获取事件名称和参数值。
- 数据结构编码和解码规则: a. 规定了如何将结构体、数组和映射等复杂的数据类型编码为字节数组,以便在智能合约之间进行传输和存储。 b. 规定了如何解码传入的字节数组,还原复杂的数据类型。
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" } ]
- ByteCode 如下:
6080604052348015600e575f80fd5b506101e68061001c5f395ff3fe608060405234801561000f575f80fd5b506004361061003f575f3560e01c80638381f58a146100435780638da5cb5b14610061578063c874936c1461007f575b5f80fd5b61004b61009b565b60405161005891906100e6565b60405180910390f35b6100696100a0565b604051610076919061013e565b60405180910390f35b61009960048036038101906100949190610185565b6100c5565b005b5f5481565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b805f8190555050565b5f819050919050565b6100e0816100ce565b82525050565b5f6020820190506100f95f8301846100d7565b92915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f610128826100ff565b9050919050565b6101388161011e565b82525050565b5f6020820190506101515f83018461012f565b92915050565b5f80fd5b610164816100ce565b811461016e575f80fd5b50565b5f8135905061017f8161015b565b92915050565b5f6020828403121561019a57610199610157565b5b5f6101a784828501610171565b9150509291505056fea2646970667358221220ee772914cef194a9e7d2e75a6404ae20f227006e7b82f8a7b22f0701d1657fd064736f6c63430008190033
小结一下:
- ABI 可以被人类和外部的应用所理解
- Bytecode 是人类不可读的
我们使用 https://remix.ethereum.org/ 在测试网 Sepolia 部署一下合约,然后用 “2”作为参数调用一下 setNumber, 如下图: 然后打开etherscan查看交易详情数据, 可以看到其附加数据如下图:
下面就是 ABI 编码数据:
0x3fb5c1cb0000000000000000000000000000000000000000000000000000000000000002
ABI 编码分析
上面的 ABI 编码数据包含 2 个部分:
- 函数选择器(前 4 字节) 0x3fb5c1cb
- 第一个参数(32 字节) 0000000000000000000000000000000000000000000000000000000000000002
函数选择器的值怎么来的呢?其实就是对函数签名的字符串进行 keccak256 哈希运算后,取前 4 个字节,代码如下:
function getSetNumberSelector() external pure returns (bytes4) {
return bytes4(keccak256(bytes("setNumber(uint256)")));
}
参数部分则是使用对应的16进制数,即数字 2,用 32 字节表示。
现在我们就明白了附加数据如何转换为对应的函数调用。小结一下:前 4 个字节为函数选择器,后面的数据为调用函数的参数值。
编码 Encode
【小工具】推荐一个实用的网站:https://www.4byte.directory/,可以使用前 4 个字节的 ABI 编码快速查询函数选择器。
- 方式1:使用 solidity 编码
// 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.
版权声明
本文仅代表作者观点,不代表区块链技术网立场。
本文系作者授权本站发表,未经许可,不得转载。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。