跳到主要内容

Interface 类

提示

Interface 是一个核心的类,用于处理智能合约的 ABI(Application Binary Interface,应用二进制接口),以便开发者能够与以太坊智能合约进行交互。它主要用于解析合约的 ABI,并生成可以调用合约函数、解码事件日志或编码交易数据的工具方法。

ethers.Interfaceethers.js 提供的一个类,用于解析和操作智能合约的 ABI。ABI 是一个 JSON 格式的描述文件,定义了智能合约的函数、事件、错误等接口信息。通过 ethers.Interface,开发者可以:

  • 编码:将函数调用或数据编码为以太坊区块链可识别的格式(如 data 字段)。
  • 解码:将区块链返回的原始数据(如交易数据或事件日志)解码为人类可读的格式。
  • 调用合约:通过生成的接口方法与合约交互,构造函数调用或解析事件日志。

简单来说,ethers.Interface 是一个桥梁,将开发者的高级代码与底层的以太坊字节码交互连接起来,属于比较底层的 API。

主要用途

  • 解析合约 ABI:
    • 接受 ABI(JSON 格式或人类可读的格式)并解析为结构化的接口。
    • 支持函数、事件和错误的定义,允许开发者以编程方式访问这些接口。
  • 编码函数调用:
    • 将函数调用(包括函数名和参数)编码为以太坊交易的 data 字段。
    • 例如,调用 transfer(address to, uint256 amount) 会生成对应的字节码。
  • 解码函数调用或事件日志:
    • 将区块链返回的原始字节数据(如 datalogs)解码为函数参数或事件数据。
    • 常用于解析交易结果或监听合约事件。
  • 构造静态调用:
    • 允许开发者在不实际发送交易的情况下,模拟调用合约函数以获取返回值。
  • 错误处理:
    • 解析合约抛出的自定义错误(Custom Errors)或标准错误。

创建 Interface 实例

需要提供合约的 ABI。ABI 通常是一个 JSON 数组,包含函数、事件和错误的定义。

import { Interface } from "ethers";

// 示例 ABI(部分)
const abi = [
"function transfer(address to, uint256 amount) returns (bool)",
"event Transfer(address indexed from, address indexed to, uint256 amount)",
];

// 创建 Interface 实例
const iface = new Interface(abi);

核心方法和属性

属性

  • fragments: 返回 ABI 中所有解析后的片段(Fragment),包括函数、事件和错误。

方法

encodeFunctionData(functionFragment, values):将函数调用编码为 data 字段的字节码。

  • 参数
    • functionFragment: 函数名或签名(如 "transfer(address,uint256)")。
    • values: 函数参数的数组。
  • 返回:编码后的字节字符串(以 0x 开头)。
const abi = [
"function transfer(address to, uint256 amount) returns (bool)",
"event Transfer(address indexed from, address indexed to, uint256 amount)",
];

const iface = new ethers.Interface(abi);
const data = iface.encodeFunctionData("transfer", [
"0x2cFC43B94126595E8B636fed9fB585fF220Bc97d",
1000n,
]);
// 0xa9059cbb0000000000000000000000002cfc43b94126595e8b636fed9fb585ff220bc97d00000000000000000000000000000000000000000000000000000000000003e8

encodeFunctionResult(functionFragment, values):将函数的返回值编码为字节格式。

  • 参数
    • functionFragment: 函数名或签名。
    • values: 返回值的数组。
  • 返回:编码后的字节字符串。

transfer 为例它的返回值是一个布尔值,所以第二个参数传入 truefalse

const abi = [
"function transfer(address to, uint256 amount) returns (bool)",
"event Transfer(address indexed from, address indexed to, uint256 amount)",
];
const values = await form.getFieldsValue();
const iface = new ethers.Interface(abi);
const data = iface.encodeFunctionResult("transfer", [true]);
// 0x0000000000000000000000000000000000000000000000000000000000000001

encodeEventLog(eventFragment, values):将事件数据编码为日志格式。

  • 参数
    • eventFragment: 事件名或签名。
    • values: 事件参数的数组。
  • 返回:编码后的日志数据。
const abi = [
"function transfer(address to, uint256 amount) returns (bool)",
"event Transfer(address indexed from, address indexed to, uint256 amount)",
];
const iface = new ethers.Interface(abi);
const data = iface.encodeEventLog("Transfer", [
"0x2cFC43B94126595E8B636fed9fB585fF220Bc97d",
"0x817C6Ef5f2EF3CC56ce87942BF7ed74138EC284C",
100n,
]);

// {
// "data": "0x0000000000000000000000000000000000000000000000000000000000000064",
// "topics": [
// "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
// "0x0000000000000000000000002cfc43b94126595e8b636fed9fb585ff220bc97d",
// "0x000000000000000000000000817c6ef5f2ef3cc56ce87942bf7ed74138ec284c"
// ]
// }

decodeFunctionData(functionFragment, data): 将交易的 data 字段解码为函数参数。

  • 参数
    • functionFragment: 函数名或签名。
    • data: 字节数据(以 0x 开头)。
  • 返回:解码后的参数数组。
const abi = [
"function transfer(address to, uint256 amount) returns (bool)",
"event Transfer(address indexed from, address indexed to, uint256 amount)",
];
const iface = new ethers.Interface(values.abi);
const data = iface.decodeFunctionData(
`transfer`,
`0xa9059cbb0000000000000000000000002cfc43b94126595e8b636fed9fb585ff220bc97d00000000000000000000000000000000000000000000000000000000000003e8`
);
console.log(data[0]); // 0x2cFC43B94126595E8B636fed9fB585fF220Bc97d
console.log(data[1]); // 1000n

decodeFunctionResult(functionFragment, data):将函数调用的返回值解码。

  • 参数
    • functionFragment: 函数名或签名。
    • data: 字节数据。
  • 返回:解码后的返回值数组。
const abi = [
"function transfer(address to, uint256 amount) returns (bool)",
"event Transfer(address indexed from, address indexed to, uint256 amount)",
];
const iface = new ethers.Interface(values.abi);
const data = iface.decodeFunctionResult(
`transfer`,
`0x0000000000000000000000000000000000000000000000000000000000000001`
);
// true

decodeEventLog(eventFragment, data, topics):将事件日志解码为事件参数。

  • 参数
    • eventFragment: 事件名或签名。
    • data: 事件的 data 字段(非索引参数)。
    • topics: 事件的 topics 数组(索引参数)。
  • 返回:解码后的事件参数对象。
const abi = [
"function transfer(address to, uint256 amount) returns (bool)",
"event Transfer(address indexed from, address indexed to, uint256 amount)",
];
const iface = new ethers.Interface(values.abi);
const data = iface.decodeEventLog(
`Transfer`,
`0x0000000000000000000000000000000000000000000000000000000000000064`,
[
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"0x0000000000000000000000002cfc43b94126595e8b636fed9fb585ff220bc97d",
"0x000000000000000000000000817c6ef5f2ef3cc56ce87942bf7ed74138ec284c",
]
);
console.log(data[0]); // 0x2cFC43B94126595E8B636fed9fB585fF220Bc97d
console.log(data[1]); // 0x817C6Ef5f2EF3CC56ce87942BF7ed74138EC284C
console.log(data[2]); // 100n

使用场景

转账 ERC-20

当你需要手动构造交易的 data 字段时,可以使用 encodeFunctionData

下面使用 buildbear 平台提供的水龙头使用 USDC 合约测试,会调起 MetaMask 钱包进行转账。

const abi = [
"function transfer(address to, uint256 amount) returns (bool)",
"event Transfer(address indexed from, address indexed to, uint256 amount)",
];
const provider = new ethers.BrowserProvider(window.ethereum);

await window.ethereum.request({
method: "eth_requestAccounts",
});

// 获取 signer
const signer = await provider.getSigner();
const iface = new ethers.Interface(abi);
const data = iface.encodeFunctionData("transfer", [
"0x817C6Ef5f2EF3CC56ce87942BF7ed74138EC284C", // 转账到这个地址
ethers.parseUnits("0.001", 6), // 6 是 USDC 的 decimals
]);
const tx = await signer.sendTransaction({
// 合约地址, USDC 合约地址
to: `0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48`,

// 构造交易字节数据
data,
});

// 等待交易确认
await tx.wait();

静态调用合约

使用 encodeFunctionDataprovider.call 模拟调用合约函数,获取返回值。

const data = iface.encodeFunctionData("balanceOf", [
"0x2cFC43B94126595E8B636fed9fB585fF220Bc97d",
]);
const result = await provider.call({ to: "合约地址", data });
const balance = iface.decodeFunctionResult("balanceOf", result);
console.log(balance[0]); // 输出: 10098919970n

解析事件日志

当监听区块链上的事件(如 Transfer 事件)时,可以用 decodeEventLog 解析日志。

const provider = new ethers.JsonRpcProvider(
"https://rpc.buildbear.io/outstanding-juggernaut-05cd9cc5"
);
const logs = await provider.getLogs({ address: "合约地址" });
const event = iface.decodeEventLog("Transfer", logs[0].data, logs[0].topics);
console.log(event); // 输出: { from: "...", to: "...", amount: 1000n }

处理合约错误

当交易失败并返回错误数据时,可以用 decodeErrorResult 解析错误。

try {
// 代码块,可能会抛出错误
} catch (error) {
const errorData = error.data;
const decodedError = iface.decodeErrorResult("Error", errorData);
console.log(decodedError); // 输出错误信息
}

总结

本章介绍了 ethers.Interface 的核心作用和常用方法,包括如何编码/解码函数调用、事件日志、处理合约错误等。通过丰富的代码示例,帮助你掌握了 ABI 解析、数据编码、事件监听、错误处理等高级用法。掌握这些内容后,你可以更灵活地与以太坊智能合约进行交互,提升开发效率和排查问题的能力。

本章所有示例代码,均可在 GitHub 中找到。