浅析 Solidity ABI -应用程序二进制接口

  • YCat
  • 更新于 2024-05-01 17:12
  • 阅读 1280

什么是ABI?ABI=ApplicationBinaryInterface用于定义智能合约接口的规范。它定义了智能合约的函数、事件和数据结构的编码和解码规则,以便其他应用程序可以与智能合约进行交互。简言之,就是以太坊的调用合约时的接口说明。有点类似于Web2API。

什么是 ABI ?

ABI = Application Binary Interface

用于定义智能合约接口的规范。它定义了智能合约的函数、事件和数据结构的编码和解码规则,以便其他应用程序可以与智能合约进行交互。

简言之,就是以太坊的调用合约时的接口说明。

有点类似于 Web2 API。

具体来说,Solidity ABI 定义了以下内容:

  1. 函数编码和解码规则: a. 规定了如何将函数名和参数编码为字节数组,以便在智能合约之间进行调用。 b. 规定了如何解码传入的字节数组,以获取函数名和参数值。
  2. 事件编码和解码规则: a. 规定了如何将事件名称和参数编码为字节数组,以便在智能合约中触发事件。 b. 规定了如何解码传入的字节数组,以获取事件名称和参数值。
  3. 数据结构编码和解码规则: a. 规定了如何将结构体、数组和映射等复杂的数据类型编码为字节数组,以便在智能合约之间进行传输和存储。 b. 规定了如何解码传入的字节数组,还原复杂的数据类型。

Smart contract ABIs enable communication and interaction with external apps and other contracts.

image.png

适用于什么场景

遵循 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;
  }
}

image (1).png

  • 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, 如下图: image (2).png 然后打开etherscan查看交易详情数据, 可以看到其附加数据如下图: image (3).png 下面就是 ABI 编码数据:

0x3fb5c1cb0000000000000000000000000000000000000000000000000000000000000002

ABI 编码分析

上面的 ABI 编码数据包含 2 个部分:

  • 函数选择器(前 4 字节) 0x3fb5c1cb
  • 第一个参数(32 字节) 0000000000000000000000000000000000000000000000000000000000000002

函数选择器的值怎么来的呢?其实就是对函数签名的字符串进行 keccak256 哈希运算后,取前 4 个字节,代码如下:

    function getSetNumberSelector() external pure returns (bytes4) {
        return bytes4(keccak256(bytes("setNumber(uint256)")));
    }

image (4).png

参数部分则是使用对应的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.
点赞 1
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
YCat
YCat
熟悉 java/Go/Solidity 智能合约开发 互联网从业者,熟悉 Web2 服务端开发,全职 Web3 从业者