ABI 格式

有几种格式可以用来为智能合约指定ABI,它将向底层库指定存在哪些方法、事件和错误, 以便库可以处理网络上接受的和发送出去的数据的编码和解码。

所支持的 ABI 类型有:

Human-Readable ABI

Human-Readable ABI 是早期被 ethers 所描述提出的, 它允许使用一个Solidity式签名(Solidity signature)来描述每个方法(method)、事件(event)和错误(error)。

需要注意的是,一个Solidity式签名完整地描述了ABI需要的全部属性:

这样就实现了一种简单的方式既是机器可读的(因为解析器是一台机器),也是人类可读的(至少是开发人员可读的), 而且便于人们编写嵌入到代码中,提高了代码的可读性。 Human-Readable ABI也相当小,有助于减少代码大小。

Huamn-Readable ABI是一个简单的字符串数组,其中每个字符串都是Solidity式签名。

签名可以被最小限度地指定(即输入和输出的名称可以被省略),也可以被完全指定(即所有的属性名称),并且空格会被忽略。

Solidity中的几个修饰符在ethers内部被摒弃了,因为ABI不需要,在Solidity的语义检查系统(semantic checking system)使用的是旧修饰符,比如输入参数数据"calldata""memory"

Human-Readable ABI 例子
const humanReadableAbi = [ // 简易的类型 "constructor(string symbol, string name)", "function transferFrom(address from, address to, uint value)", "function balanceOf(address owner) view returns (uint balance)", "event Transfer(address indexed from, address indexed to, address value)", "error InsufficientBalance(account owner, uint balance)", // 在一些带结构体类型的例子中,我们使用元组关键字(tuple keyword): // 注意,元组关键字是可选的,简单地使用花括号就可以完成 // struct Person { // string name; // uint16 age; // } "function addPerson(tuple(string name, uint16 age) person)", "function addPeople(tuple(string name, uint16 age)[] person)", "function getPerson(uint id) view returns (tuple(string name, uint16 age))", "event PersonAdded(uint indexed id, tuple(string name, uint16 age) person)" ];

Solidity JSON ABI

Solidity JSON ABI是许多工具导出的标准格式,包括Solidity编译器。 有关完整规范,请参阅Solidity compiler documentation

不同版本的键和值略有不同。例如,早期的编译器只包含一个布尔值"constant"来表示可变性(mutability), 而新版本包含了一个字符串"mutabilityState",它包含了几个旧的属性。

当使用JSON ABI创建一个Fragment实例时,它会自动为new-age ABIs推断出所有的legacy properties, 而对于legacy ABIs将推断出new-age properties。 所有属性都将被填充,因此它与用Human-Readable ABI fragment创建是等价的。

The same ABI as JSON ABI
const jsonAbi = `[ { "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": "value" } ], "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": "address", "name": "value"} ] }, { "type": "error", "name": "InsufficientBalance", "inputs": [ { "type": "account", "name": "owner"}, { "type": "uint256", "name": "balance"} ] }, { "type": "function", "name": "addPerson", "constant": false, "payable": false, "inputs": [ { "type": "tuple", "name": "person", "components": [ { "type": "string", "name": "name" }, { "type": "uint16", "name": "age" } ] } ], "outputs": [] }, { "type": "function", "name": "addPeople", "constant": false, "payable": false, "inputs": [ { "type": "tuple[]", "name": "person", "components": [ { "type": "string", "name": "name" }, { "type": "uint16", "name": "age" } ] } ], "outputs": [] }, { "type": "function", "name": "getPerson", "constant": true, "stateMutability": "view", "payable": false, "inputs": [ { "type": "uint256", "name": "id" } ], "outputs": [ { "type": "tuple", "components": [ { "type": "string", "name": "name" }, { "type": "uint16", "name": "age" } ] } ] }, { "type": "event", "anonymous": false, "name": "PersonAdded", "inputs": [ { "type": "uint256", "name": "id", "indexed": true }, { "type": "tuple", "name": "person", "components": [ { "type": "string", "name": "name", "indexed": false }, { "type": "uint16", "name": "age", "indexed": false } ] } ] } ]`;

Solidity Object ABI

使用JSON.parse解析Solidity JSON ABI的结果完全与Interface类兼容, 且该对象的每个方法、事件和错误都与Fragment类兼容。

一些开发人员可能更喜欢这种方式,因为它允许将ABI属性作为普通的JavaScript对象访问,并且Solidity ABI与JSON ABI非常匹配。

在格式之间相互转换

Fragment对象使重新格式化单个方法、事件或错误变得简单,但是大多数开发人员会对转换整个ABI感兴趣。

对于生产环境下的代码,建议内联Human-Readable ABI 的方式,因为这样可以很容易地看到哪些方法、事件和错误是可用的。 还强烈建议去掉ABI中未使用的部分(如管理方法),以进一步减少代码大小。

转换成完整的 Human-Readable ABI
// 使用"full"格式将确保Result对象具有named属性,这将提高代码的可读性 const iface = new Interface(jsonAbi); iface.format(FormatTypes.full); // [ // 'constructor(string symbol, string name)', // 'function transferFrom(address from, address to, uint256 value)', // 'function balanceOf(address owner) view returns (uint256)', // 'event Transfer(address indexed from, address indexed to, address value)', // 'error InsufficientBalance(account owner, uint256 balance)', // 'function addPerson(tuple(string name, uint16 age) person)', // 'function addPeople(tuple(string name, uint16 age)[] person)', // 'function getPerson(uint256 id) view returns (tuple(string name, uint16 age))', // 'event PersonAdded(uint256 indexed id, tuple(string name, uint16 age) person)' // ]
Converting to Minimal Human-Readable ABI
//使用"minimal"格式将节省少量的空间,但通常不值得,因为在Results上的named属性将不可用 const iface = new Interface(jsonAbi); iface.format(FormatTypes.minimal); // [ // 'constructor(string,string)', // 'function transferFrom(address,address,uint256)', // 'function balanceOf(address) view returns (uint256)', // 'event Transfer(address indexed,address indexed,address)', // 'error InsufficientBalance(account,uint256)', // 'function addPerson(tuple(string,uint16))', // 'function addPeople(tuple(string,uint16)[])', // 'function getPerson(uint256) view returns (tuple(string,uint16))', // 'event PersonAdded(uint256 indexed,tuple(string,uint16))' // ]
转换成 JSON ABI
// 有时你可能需要将Human-Readable ABI 导出成JSON,因为有些工具不支持Human-Readable // 为了简洁紧密凑合,JSON保留了最少的空格 const iface = new Interface(humanReadableAbi); jsonAbi = 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":"value"}],"outputs":[]},{"type":"function","name":"balanceOf","constant":true,"stateMutability":"view","payable":false,"inputs":[{"type":"address","name":"owner"}],"outputs":[{"type":"uint256","name":"balance"}]},{"type":"event","anonymous":false,"name":"Transfer","inputs":[{"type":"address","name":"from","indexed":true},{"type":"address","name":"to","indexed":true},{"type":"address","name":"value"}]},{"type":"error","name":"InsufficientBalance","inputs":[{"type":"account","name":"owner"},{"type":"uint256","name":"balance"}]},{"type":"function","name":"addPerson","constant":false,"payable":false,"inputs":[{"type":"tuple","name":"person","components":[{"type":"string","name":"name"},{"type":"uint16","name":"age"}]}],"outputs":[]},{"type":"function","name":"addPeople","constant":false,"payable":false,"inputs":[{"type":"tuple[]","name":"person","components":[{"type":"string","name":"name"},{"type":"uint16","name":"age"}]}],"outputs":[]},{"type":"function","name":"getPerson","constant":true,"stateMutability":"view","payable":false,"inputs":[{"type":"uint256","name":"id"}],"outputs":[{"type":"tuple","components":[{"type":"string","name":"name"},{"type":"uint16","name":"age"}]}]},{"type":"event","anonymous":false,"name":"PersonAdded","inputs":[{"type":"uint256","name":"id","indexed":true},{"type":"tuple","name":"person","components":[{"type":"string","name":"name","indexed":false},{"type":"uint16","name":"age","indexed":false}]}]}]' // 结合格式化的参数去使用 JSON.parse 和 JSON.stringify可提高可读性 JSON.stringify(JSON.parse(jsonAbi), null, 2); // `[ // { // "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": "value" // } // ], // "outputs": [] // }, // { // "type": "function", // "name": "balanceOf", // "constant": true, // "stateMutability": "view", // "payable": false, // "inputs": [ // { // "type": "address", // "name": "owner" // } // ], // "outputs": [ // { // "type": "uint256", // "name": "balance" // } // ] // }, // { // "type": "event", // "anonymous": false, // "name": "Transfer", // "inputs": [ // { // "type": "address", // "name": "from", // "indexed": true // }, // { // "type": "address", // "name": "to", // "indexed": true // }, // { // "type": "address", // "name": "value" // } // ] // }, // { // "type": "error", // "name": "InsufficientBalance", // "inputs": [ // { // "type": "account", // "name": "owner" // }, // { // "type": "uint256", // "name": "balance" // } // ] // }, // { // "type": "function", // "name": "addPerson", // "constant": false, // "payable": false, // "inputs": [ // { // "type": "tuple", // "name": "person", // "components": [ // { // "type": "string", // "name": "name" // }, // { // "type": "uint16", // "name": "age" // } // ] // } // ], // "outputs": [] // }, // { // "type": "function", // "name": "addPeople", // "constant": false, // "payable": false, // "inputs": [ // { // "type": "tuple[]", // "name": "person", // "components": [ // { // "type": "string", // "name": "name" // }, // { // "type": "uint16", // "name": "age" // } // ] // } // ], // "outputs": [] // }, // { // "type": "function", // "name": "getPerson", // "constant": true, // "stateMutability": "view", // "payable": false, // "inputs": [ // { // "type": "uint256", // "name": "id" // } // ], // "outputs": [ // { // "type": "tuple", // "components": [ // { // "type": "string", // "name": "name" // }, // { // "type": "uint16", // "name": "age" // } // ] // } // ] // }, // { // "type": "event", // "anonymous": false, // "name": "PersonAdded", // "inputs": [ // { // "type": "uint256", // "name": "id", // "indexed": true // }, // { // "type": "tuple", // "name": "person", // "components": [ // { // "type": "string", // "name": "name", // "indexed": false // }, // { // "type": "uint16", // "name": "age", // "indexed": false // } // ] // } // ] // } // ]`