接口(Interface)类是以太坊网络上的与合约交互所需的编码和解码的一种抽象。
许多标准都是随着Solidity语言而发展的,其他语言也采用这种方式去与现有部署的合约的保持兼容。
EVM本身并不理解ABI是什么。它只是一组商定的格式,用于编码合约所需的各种类型的数据,以便它们可以相互交互。
new ethers.utils.Interface( abi ) 从表示abi的JSON字符串或对象创建一个新的接口。
abi可以是JSON字符串或Solidity 编译器(或其他兼容语言)使用JSON.parse生产的对象。
abi 也可以是Human-Readable Abi, 它是Ethers创建的一种格式,以简化将abi手动输入到源文件中的操作, 这样就可以在同一个源文件中简单地引用Contract abi。
const iface = new Interface([
"constructor(string symbol, string name)",
"function transferFrom(address from, address to, uint amount)",
"function mint(uint amount) payable",
"function balanceOf(address owner) view returns (uint)",
"event Transfer(address indexed from, address indexed to, uint256 amount)",
"error AccountLocked(address owner, uint256 balance)",
"function addUser(tuple(string name, address addr) user) returns (uint id)",
"function addUsers(tuple(string name, address addr)[] user) returns (uint[] id)",
"function getUser(uint id) view returns (tuple(string name, address addr) user)"
]);
interface.format( [ format ] ) ⇒ string | Array< string > 返回格式化的接口。如果格式类型是json
,则返回一个字符串,否则返回一个由human-readable(人类可读)字符串组成的数组。
const FormatTypes = ethers.utils.FormatTypes;
iface.format(FormatTypes.json)
// '[{"type":"constructor","payable":false,"inputs":[{"type":"string","name":"symbol"},{"type":"string","name":"name"}]},{"type":"function","name":"transferFrom","constant":false,"payable":false,"inputs":[{"type":"address","name":"from"},{"type":"address","name":"to"},{"type":"uint256","name":"amount"}],"outputs":[]},{"type":"function","name":"mint","constant":false,"stateMutability":"payable","payable":true,"inputs":[{"type":"uint256","name":"amount"}],"outputs":[]},{"type":"function","name":"balanceOf","constant":true,"stateMutability":"view","payable":false,"inputs":[{"type":"address","name":"owner"}],"outputs":[{"type":"uint256"}]},{"type":"event","anonymous":false,"name":"Transfer","inputs":[{"type":"address","name":"from","indexed":true},{"type":"address","name":"to","indexed":true},{"type":"uint256","name":"amount"}]},{"type":"error","name":"AccountLocked","inputs":[{"type":"address","name":"owner"},{"type":"uint256","name":"balance"}]},{"type":"function","name":"addUser","constant":false,"payable":false,"inputs":[{"type":"tuple","name":"user","components":[{"type":"string","name":"name"},{"type":"address","name":"addr"}]}],"outputs":[{"type":"uint256","name":"id"}]},{"type":"function","name":"addUsers","constant":false,"payable":false,"inputs":[{"type":"tuple[]","name":"user","components":[{"type":"string","name":"name"},{"type":"address","name":"addr"}]}],"outputs":[{"type":"uint256[]","name":"id"}]},{"type":"function","name":"getUser","constant":true,"stateMutability":"view","payable":false,"inputs":[{"type":"uint256","name":"id"}],"outputs":[{"type":"tuple","name":"user","components":[{"type":"string","name":"name"},{"type":"address","name":"addr"}]}]}]'
iface.format(FormatTypes.full)
// [
// 'constructor(string symbol, string name)',
// 'function transferFrom(address from, address to, uint256 amount)',
// 'function mint(uint256 amount) payable',
// 'function balanceOf(address owner) view returns (uint256)',
// 'event Transfer(address indexed from, address indexed to, uint256 amount)',
// 'error AccountLocked(address owner, uint256 balance)',
// 'function addUser(tuple(string name, address addr) user) returns (uint256 id)',
// 'function addUsers(tuple(string name, address addr)[] user) returns (uint256[] id)',
// 'function getUser(uint256 id) view returns (tuple(string name, address addr) user)'
// ]
iface.format(FormatTypes.minimal)
// [
// 'constructor(string,string)',
// 'function transferFrom(address,address,uint256)',
// 'function mint(uint256) payable',
// 'function balanceOf(address) view returns (uint256)',
// 'event Transfer(address indexed,address indexed,uint256)',
// 'error AccountLocked(address,uint256)',
// 'function addUser(tuple(string,address)) returns (uint256)',
// 'function addUsers(tuple(string,address)[]) returns (uint256[])',
// 'function getUser(uint256) view returns (tuple(string,address))'
// ]
iface.getFunction("transferFrom(address, address, uint256)");
iface.getFunction("transferFrom");
iface.getFunction("0x23b872dd");
iface.getFunction("doesNotExist()");
// [Error: no matching function] {
// argument: 'signature',
// code: 'INVALID_ARGUMENT',
// reason: 'no matching function',
// value: 'doesNotExist()'
// }
iface.getError("AccountLocked(address, uint256)");
iface.getError("AccountLocked");
iface.getError("0xf7c3865a");
iface.getError("DoesNotExist()");
// [Error: no matching error] {
// argument: 'signature',
// code: 'INVALID_ARGUMENT',
// reason: 'no matching error',
// value: 'DoesNotExist()'
// }
iface.getEvent("Transfer(address, address, uint256)");
iface.getEvent("Transfer");
iface.getEvent("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef");
iface.getEvent("DoesNotExist()");
// [Error: no matching event] {
// argument: 'signature',
// code: 'INVALID_ARGUMENT',
// reason: 'no matching event',
// value: 'DoesNotExist()'
// }
签名(Signature) 和 主题(Topic) 的哈希
iface.getSighash("balanceOf");
// '0x70a08231'
iface.getSighash("balanceOf(address)");
// '0x70a08231'
const fragment = iface.getFunction("balanceOf")
iface.getSighash(fragment);
// '0x70a08231'
iface.getEventTopic("Transfer");
// '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'
iface.getEventTopic("Transfer(address, address, uint)");
// '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'
const fragment = iface.getEvent("Transfer")
iface.getEventTopic(fragment);
// '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'
返回已编码的部署数据,这些数据可以连接到合约的部署字节码,以便将值(values)传递到合约构造函数中。
iface.encodeDeploy([ "SYM", "Some Name" ])
// '0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000353594d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009536f6d65204e616d650000000000000000000000000000000000000000000000'
interface.encodeErrorResult( fragment [ , values ] ) ⇒ string< DataHexString > 返回编码后的错误结果,这通常是对给定值的fragment(参考Specifying Fragments))的reverted调用的响应结果。
大多数开发人员将不需要此方法,但对于那些模拟自己的区块链的开发人员很有用。
iface.encodeErrorResult("AccountLocked", [
"0x8ba1f109551bD432803012645Ac136ddd64DBA72",
parseEther("1.0")
]);
// '0xf7c3865a0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba720000000000000000000000000000000000000000000000000de0b6b3a7640000'
interface.encodeFilterTopics( fragment , values ) ⇒ Array< topic | Array< topic > > 返回已编码的主题过滤器,可以在fragment(参考Specifying Fragments)将values值传递给getLogs函数。
每个主题是一个32字节(64nibble)的DataHexString。
iface.encodeFilterTopics("Transfer", [])
// [
// '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'
// ]
iface.encodeFilterTopics("Transfer", [
"0x8ba1f109551bD432803012645Ac136ddd64DBA72"
])
// [
// '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
// '0x0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72'
// ]
iface.encodeFilterTopics("Transfer", [
null,
"0x8ba1f109551bD432803012645Ac136ddd64DBA72"
])
// [
// '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
// null,
// '0x0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72'
// ]
iface.encodeFunctionData("transferFrom", [
"0x8ba1f109551bD432803012645Ac136ddd64DBA72",
"0xaB7C8803962c0f2F5BBBe3FA8bf41cd82AA1923C",
parseEther("1.0")
])
// '0x23b872dd0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72000000000000000000000000ab7c8803962c0f2f5bbbe3fa8bf41cd82aa1923c0000000000000000000000000000000000000000000000000de0b6b3a7640000'
user = [
"Richard Moore",
"0x8ba1f109551bD432803012645Ac136ddd64DBA72"
];
iface.encodeFunctionData("addUser", [ user ]);
// '0x43967833000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000400000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72000000000000000000000000000000000000000000000000000000000000000d52696368617264204d6f6f726500000000000000000000000000000000000000'
user = {
name: "Richard Moore",
addr: "0x8ba1f109551bD432803012645Ac136ddd64DBA72"
};
iface.encodeFunctionData("addUser", [ user ]);
// '0x43967833000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000400000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72000000000000000000000000000000000000000000000000000000000000000d52696368617264204d6f6f726500000000000000000000000000000000000000'
interface.encodeFunctionResult( fragment [ , values ] ) ⇒ string< DataHexString > Most developers will not need this method, but may be useful for authors of a mock blockchain. 返回编码后的结果,这通常是对给定值的fragment(参考Specifying Fragments))的调用的响应结果。
大多数开发人员将不需要此方法,但对于那些模拟自己的区块链的开发人员很有用。
iface.encodeFunctionResult("balanceOf", [
"0x8ba1f109551bD432803012645Ac136ddd64DBA72"
])
// '0x0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72'
interface.decodeErrorResult( fragment , data ) ⇒ Result 在对给定数据的fragment(参考Specifying Fragments))revert期间,返回此调用的结果中已解码的值。
大多数开发人员不需要这个数据,因为如果数据表示revert,decodeFunctionResult
将自动解码errors。
errorData = "0xf7c3865a0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba720000000000000000000000000000000000000000000000000de0b6b3a7640000";
iface.decodeErrorResult("AccountLocked", errorData)
// [
// '0x8ba1f109551bD432803012645Ac136ddd64DBA72',
// { BigNumber: "1000000000000000000" },
// balance: { BigNumber: "1000000000000000000" },
// owner: '0x8ba1f109551bD432803012645Ac136ddd64DBA72'
// ]
interface.decodeEventLog( fragment , data [ , topics ] ) ⇒ Result 从事件日志中为带有可选主题(topics)的给定数据fragment(参考Specifying Fragments))返回已解码的事件值。
如果没有指定主题(topics),将在结果中插入占位符。
大多数开发人员会发现parsing methods更便于解码事件数据,因为它们会自动检测匹配的事件。
const data = "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000";
const topics = [
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"0x0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72",
"0x000000000000000000000000ab7c8803962c0f2f5bbbe3fa8bf41cd82aa1923c"
];
iface.decodeEventLog("Transfer", data, topics);
// [
// '0x8ba1f109551bD432803012645Ac136ddd64DBA72',
// '0xaB7C8803962c0f2F5BBBe3FA8bf41cd82AA1923C',
// { BigNumber: "1000000000000000000" },
// amount: { BigNumber: "1000000000000000000" },
// from: '0x8ba1f109551bD432803012645Ac136ddd64DBA72',
// to: '0xaB7C8803962c0f2F5BBBe3FA8bf41cd82AA1923C'
// ]
const txData = "0x23b872dd0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72000000000000000000000000ab7c8803962c0f2f5bbbe3fa8bf41cd82aa1923c0000000000000000000000000000000000000000000000000de0b6b3a7640000";
iface.decodeFunctionData("transferFrom", txData);
// [
// '0x8ba1f109551bD432803012645Ac136ddd64DBA72',
// '0xaB7C8803962c0f2F5BBBe3FA8bf41cd82AA1923C',
// { BigNumber: "1000000000000000000" },
// amount: { BigNumber: "1000000000000000000" },
// from: '0x8ba1f109551bD432803012645Ac136ddd64DBA72',
// to: '0xaB7C8803962c0f2F5BBBe3FA8bf41cd82AA1923C'
// ]
resultData = "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000";
iface.decodeFunctionResult("balanceOf", resultData)
// [
// { BigNumber: "1000000000000000000" }
// ]
errorData = "0xf7c3865a0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba720000000000000000000000000000000000000000000000000de0b6b3a7640000";
iface.decodeFunctionResult("balanceOf", errorData)
// [Error: call revert exception [ See: https://links.ethers.org/v5-errors-CALL_EXCEPTION ]] {
// code: 'CALL_EXCEPTION',
// data: '0xf7c3865a0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba720000000000000000000000000000000000000000000000000de0b6b3a7640000',
// errorArgs: [
// '0x8ba1f109551bD432803012645Ac136ddd64DBA72',
// { BigNumber: "1000000000000000000" },
// balance: { BigNumber: "1000000000000000000" },
// owner: '0x8ba1f109551bD432803012645Ac136ddd64DBA72'
// ],
// errorName: 'AccountLocked',
// errorSignature: 'AccountLocked(address,uint256)',
// method: 'balanceOf(address)',
// reason: null
// }
resultData = "0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000400000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72000000000000000000000000000000000000000000000000000000000000000d52696368617264204d6f6f726500000000000000000000000000000000000000";
result = iface.decodeFunctionResult("getUser", resultData);
// [
// [
// 'Richard Moore',
// '0x8ba1f109551bD432803012645Ac136ddd64DBA72',
// addr: '0x8ba1f109551bD432803012645Ac136ddd64DBA72',
// name: 'Richard Moore'
// ],
// user: [
// 'Richard Moore',
// '0x8ba1f109551bD432803012645Ac136ddd64DBA72',
// addr: '0x8ba1f109551bD432803012645Ac136ddd64DBA72',
// name: 'Richard Moore'
// ]
// ]
result[0][0];
// 'Richard Moore'
result.user.name
// 'Richard Moore'
对于大多数开发人员来说,这些函数通常是最有用的。 它们将自动在ABI中搜索匹配的事件(Event)或函数(Function),并将组件解码为完整的描述信息。
在数据中搜索与错误选择器匹配的错误,并解析出详细信息。
const data = "0xf7c3865a0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba720000000000000000000000000000000000000000000000000de0b6b3a7640000";
iface.parseError(data);
// ErrorDescription {
// args: [
// '0x8ba1f109551bD432803012645Ac136ddd64DBA72',
// { BigNumber: "1000000000000000000" },
// balance: { BigNumber: "1000000000000000000" },
// owner: '0x8ba1f109551bD432803012645Ac136ddd64DBA72'
// ],
// errorFragment: [Function: ErrorFragment],
// name: 'AccountLocked',
// sighash: '0xf7c3865a',
// signature: 'AccountLocked(address,uint256)'
// }
搜索与日志主题哈希匹配的事件,并解析日志表示的值。
const data = "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000";
const topics = [
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"0x0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72",
"0x000000000000000000000000ab7c8803962c0f2f5bbbe3fa8bf41cd82aa1923c"
];
iface.parseLog({ data, topics });
// LogDescription {
// args: [
// '0x8ba1f109551bD432803012645Ac136ddd64DBA72',
// '0xaB7C8803962c0f2F5BBBe3FA8bf41cd82AA1923C',
// { BigNumber: "1000000000000000000" },
// amount: { BigNumber: "1000000000000000000" },
// from: '0x8ba1f109551bD432803012645Ac136ddd64DBA72',
// to: '0xaB7C8803962c0f2F5BBBe3FA8bf41cd82AA1923C'
// ],
// eventFragment: [Function: EventFragment],
// name: 'Transfer',
// signature: 'Transfer(address,address,uint256)',
// topic: '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'
// }
搜索与交易数据签名哈希匹配的函数,并解析出交易的属性。
const data = "0x23b872dd0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72000000000000000000000000ab7c8803962c0f2f5bbbe3fa8bf41cd82aa1923c0000000000000000000000000000000000000000000000000de0b6b3a7640000";
const value = parseEther("1.0");
iface.parseTransaction({ data, value });
// TransactionDescription {
// args: [
// '0x8ba1f109551bD432803012645Ac136ddd64DBA72',
// '0xaB7C8803962c0f2F5BBBe3FA8bf41cd82AA1923C',
// { BigNumber: "1000000000000000000" },
// amount: { BigNumber: "1000000000000000000" },
// from: '0x8ba1f109551bD432803012645Ac136ddd64DBA72',
// to: '0xaB7C8803962c0f2F5BBBe3FA8bf41cd82AA1923C'
// ],
// functionFragment: [Function: FunctionFragment],
// name: 'transferFrom',
// sighash: '0x23b872dd',
// signature: 'transferFrom(address,address,uint256)',
// value: { BigNumber: "1000000000000000000" }
// }
Result
inherits Array<any>
一个Result是一个数组,因此每个值都可以作为位置参数访问。
此外,如果值已命名,则可以通过名称访问与其位置值相同的对象。
但是对象中的name为length
是不可用的,因为它是Array的一部分, 所以这个键的任何命名值都被重命名为_length。
如果存在名称冲突,则只有第一个键可用。
errorDescription.args ⇒ Result errorDescription.name ⇒ string error的name。(例如AccountLocked
)
errorDescription.signature ⇒ string error的签名。(例如AccountLocked(address,uint256)
)
errorDescription.sighash ⇒ string
logDescription.name ⇒ string logDescription.signature ⇒ string 事件的签名。(如Transfer(address,address,uint256)
)
logDescription.topic ⇒ string
transactionDescription.args ⇒ Result transactionDescription.name ⇒ string transactionDescription.sighash ⇒ string transactionDescription.signature ⇒ string 函数的签名。(如transfer(address,uint256)
)
当指定一个fragment到接口中的函数时,可以使用以下任意一个:
- 事件或函数的名称(name),如果它在ABI中是唯一确定无歧义(non-ambiguous,例如
transfer
) - 事件或函数的签名。签名是标准化的,例如
uint
和uint256
是等价的(例如transfer(address, uint)
) - 函数的 签名哈希 或 主题哈希 。签名哈希通常指的是Solidity中的函数选择器(例如
0xa9059cbb
) - 一个Fragment