编码解码 abi

本章讲解在 Solidity 中,什么是 ABI,以及如何使用 ABI 编码和解码函数。

官网binschool.app
推特@BinSchool    DiscordBinDAO   微信:bkra50 

以太坊 ABIApplication Binary Interface),中文译为“应用二进制接口”。 

ABI 用于描述以太坊上智能合约的接口的规范。它定义了智能合约与外部世界之间的通信方式,规定了智能合约的函数、参数、返回值的数据类型和编码方式,以及如何将这些数据进行序列化和反序列化,便于合约之间能够互相调用,以及外部与区块链网络进行交互。

ABI 等同于其它编程语言中的 API,不过 ABI 是以二进制形式存在的。

Solidity 语言支持编码或者解析 ABI 数据。

编码函数有 4 个,包括:

abi.encodeabi.encodePackedabi.encodeWithSignatureabi.encodeWithSelector

解码函数有 1 个:abi.decode

1. abi.encode、abi.decode

abi.encode 常用于对智能合约函数的参数进行编码。

外部合约或者客户端在调用智能合约函数的时候,需要对函数的参数进行编码,打包到一起。当以太坊上的智能合约收到这些调用数据后,会进行解码,还原为原来的参数。

Solidityabi.encode 函数,就是用于将多个数据按照上述规范进行编码的。它包含了各个数据项的类型、位置、长度信息,便于反向解码。

abi.encode 在智能合约中,常用于在调用其它合约时对参数进行编码。

abi.decode 是与编码函数对应的解码函数,它将打包编码后的数据还原为原始数据项。

我们看下面的合约例子:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract ABIEncode {
  uint8 x = 2; // 8位整形
  string name = "BinSchool.app"; // 字符串
    
  // 编码
  function encode() external view returns(bytes memory) {
    // 将 x,name 打包编码为字节数组
    return abi.encode(x, name);
  }   
  
  // 解码
  function decode() public view returns(uint8, string memory) {
    // 将 x,name 打包编码为字节数组 data
    bytes memory data = abi.encode(x, name);
    // 将 data 解码还原为整数和字符串
    return abi.decode(data, (uint8, string));
  }
}

encode 函数将一个整数和一个字符串打包为字节数组。

decode 函数将字节数组还原为一个整数和一个字符串。

我们将这个合约部署到 Remix 上,调用 encode 函数后,得到了一个字节数组。

 

在这个字节数组中,每 32 个字节作为一行,分别代表:

2. abi.encodePacked

abi.encodePacked 称为压缩编码函数,它与 abi.encode 类似,也是对多个数据项进行打包编码。

abi.encodePacked 与 abi.encode 不同,它不包含各个数据项的类型、位置、长度信息,只对内容进行编码,所以得到的结果会很短小。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract ABIEncodePacked {
  uint8 x = 2; // 8位整形
  string name = "BinSchool.app"; // 字符串
    
  // 编码
  function encodePacked() external view returns(bytes memory) {
    // 将 x,name 打包编码为字节数组
    return abi.encodePacked(x, name);
  }   

我们将这个合约部署到 Remix 上,调用 encodPacked 函数后,得到了一个字节数组。

 

这个字节数组非常简短,里面的内容分别代表:

由于 abi.encodePacked 编码的结果只包含内容,没有位置和长度等信息,所以编码结构无法反向解码的。也就是说,它并不存在解码函数。

abi.encodePacked 的使用场景,就是把多个数据项打包,生成一个哈希值,用来作为唯一标识。

3. abi.encodeWithSelector

abi.encodeWithSelectorSolidity 语言中的一个函数,它用于将函数选择器和函数参数打包编码为一个字节数组。这个函数通常用于构建函数调用数据,以便与智能合约进行交互。

a) abi.encodeWithSelector 的语法

function abi.encodeWithSelector(bytes4 selector, argument1, argument2, ...) returns (bytes memory)

其中,参数 selector:要调用函数的函数选择器,它的长度为 4 个字节。

argument1, argument2, ... :要传递给函数的参数。

abi.encodeWithSelector 返回的结果是编码后的字节数组。

b) abi.encodeWithSelector 的用法

假设智能合约中有一个 set 函数,我们可以使用 abi.encodeWithSelector生成一段数据,来调用 set 函数:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract EncodeWithSelector {
  uint256 public value; // 状态变量

  // 编码
  function encode() external pure returns(bytes memory) {
    // 计算函数选择器
    bytes4 selector = bytes4(keccak256("set(uint256)")); 
    // 参数值为 888
    uint256 param = 888; 
    // 构建函数调用数据
    bytes memory data = abi.encodeWithSelector(selector,  param);
    return data;
  }

  // 设置状态变量 value
  function set(uint256 _value) external {
    value = _value;
  }

  fallback() external  { }
}

在这个示例中的 encode 函数,首先计算了 set 函数的函数选择器,再使用 abi.encodeWithSelector 将函数选择器和参数值编码为一个字节数组。

encode 函数的计算结果就是一条调用合约函数 set 的请求数据,可以用来直接与智能合约进行交互。

我们将这个合约部署到 Remix 上,调用 encode 函数后,得到了一个字节数组。

我们把这个字节数组的内容,复制到下方的 CALLDATA 中,然后点击 Transact。这实际上就是模拟了直接调用 set 函数的过程。

 

我们点击 value,可以看到它的值变为了 888。这说明调用 set 函数成功。

4. abi.encodeWithSignature

abi.encodeWithSignature 也是 Solidity 语言中的一个函数,它与 abi.encodeWithSelector 的用途相同,只是使用方法略微不同。

a) abi.encodeWithSignature 的语法

function abi.encodeWithSignature(string memory signature, argument1, argument2, ...) returns (bytes memory)

其中,参数 signature:要调用函数的函数签名。

argument1, argument2, ... :要传递给函数的参数。

abi.encodeWithSignature 返回的结果是编码后的字节数组。

b) abi.encodeWithSignature 的用法

我们使用 abi.encodeWithSignature 来构造请求数据,调用 set 函数:

  function encode() external pure returns(bytes memory) {
    // 参数值为 888
    uint256 param = 888; 
    // 构建函数调用数据
    bytes memory data = abi.encodeWithSignature("set(uint256)",  param);
    return data;
  }

使用 abi.encodeWithSignature 来构建函数调用的数据,比使用 abi.encodeWithSelector 更为简单。