了解 ABI 文件
介绍
之前,你使用提供的 ABI 文件部署了 eosio.token 合约,本教程将概述 ABI 文件如何与 eosio.token 合约相关联。
可以使用 eosio.cdt 提供的 eosio-cpp 实用程序生成 ABI 文件,但是,有几种情况可能导致 ABI 的生成出现故障或完全失败,高级 C ++ 模式可以将其提升,自定义类型有时会导致 ABI 生成的问题,因此,你必须了解 ABI 文件的工作原理,以便在必要时进行调试和修复。
什么是 ABI?
应用程序二进制接口(ABI)是一个基于 JSON 的描述,介绍如何在 JSON 和二进制表示之间转换用户操作,ABI 还描述了如何将数据库状态转换为 JSON 或从 JSON 转换,通过 ABI 描述合约后,开发人员和用户将能够通过 JSON 无缝地与你的合约进行交互。
安全说明
执行交易时可以绕过 ABI,传递给合约的消息和操作不必符合 ABI,ABI 是一个指南,而不是看门人。
创建一个 ABI 文件
从空的 ABI 开始,将其命名为 eosio.token.abi
{
“version”: “eosio::abi/1.0”,
“types”: [],
“structs”: [],
“actions”: [],
“tables”: [],
“ricardian_clauses”: [],
“abi_extensions”: [],
“___comment” : “”
}
types
ABI 允许任何客户端或接口解释甚至为你的合约生成 GUI,为了使其以一致的方式工作,请描述在 ABI 中需要描述的任何公共操作或结构中用作参数的自定义类型。
内置类型
EOSIO 实现了许多自定义内置类型,不需要在 ABI 文件中描述内置类型,如果你想熟悉 EOSIO 的内置类型,它们定义在这里。
{
“new_type_name”: “name”,
“type”: “name”
}
ABI 现在看起来像这样:
{
“version”: “eosio::abi/1.0”,
“types”: [{
“new_type_name”: “name”,
“type”: “name”
}],
“structs”: [],
“actions”: [],
“tables”: [],
“ricardian_clauses”: [],
“abi_extensions”: []
}
structs
暴露于 ABI 的结构也需要描述,通过查看 eosio.token.hpp,可以快速确定公共操作使用了哪些结构,这对下一步尤为重要。
JSON 中的结构对象定义如下所示:
{
“name”: “issue”, //The name
“base”: “”, //Inheritance, parent struct
“fields”: [] //Array of field objects describing the struct’s fields.
}
fields
{
“name”:””, // The field’s name
“type”:”” // The field’s type
}
在 eosio.token 合约中,有许多结构需要定义,请注意,并非所有结构都是显式定义的,有些结构对应于操作的参数,以下是需要对 eosio.token 合约进行 ABI 描述的结构列表。
隐式结构
以下结构是隐式的,因为结构从未在合约中显式定义,查看 create 操作,你将找到两个参数,类型为 name 的 issuer 和 asset 类型的 maximum_supply,为简洁起见,本教程不会分解每个结构,但应用相同的逻辑,你将得到以下结果:
create
{
“name”: “create”,
“base”: “”,
“fields”: [
{
“name”:”issuer”,
“type”:”name”
},
{
“name”:”maximum_supply”,
“type”:”asset”
}
]
}
issue
{
“name”: “issue”,
“base”: “”,
“fields”: [
{
“name”:”to”,
“type”:”name”
},
{
“name”:”quantity”,
“type”:”asset”
},
{
“name”:”memo”,
“type”:”string”
}
]
}
retire
{
“name”: “retire”,
“base”: “”,
“fields”: [
{
“name”:”quantity”,
“type”:”asset”
},
{
“name”:”memo”,
“type”:”string”
}
]
}
transfer
{
“name”: “transfer”,
“base”: “”,
“fields”: [
{
“name”:”from”,
“type”:”name”
},
{
“name”:”to”,
“type”:”name”
},
{
“name”:”quantity”,
“type”:”asset”
},
{
“name”:”memo”,
“type”:”string”
}
]
}
close
{
“name”: “close”,
“base”: “”,
“fields”: [
{
“name”:”owner”,
“type”:”name”
},
{
“name”:”symbol”,
“type”:”symbol”
}
]
}
显式结构
这些结构是显式定义的,因为它们是实例化多索引表的条件,描述它们与定义如上所示的隐式结构没有什么不同。
account
{
“name”: “account”,
“base”: “”,
“fields”: [
{
“name”:”balance”,
“type”:”asset”
}
]
}
currency_stats
{
“name”: “currency_stats”,
“base”: “”,
“fields”: [
{
“name”:”supply”,
“type”:”asset”
},
{
“name”:”max_supply”,
“type”:”asset”
},
{
“name”:”issuer”,
“type”:”account_name”
}
]
}
actions
操作的 JSON 对象定义如下所示:
{
“name”: “transfer”, //The name of the action as defined in the contract
“type”: “transfer”, //The name of the implicit struct as described in the ABI
“ricardian_contract”: “” //An optional ricardian clause to associate to this action describing its intended functionality.
}
通过聚合 eosio.token 合约头文件中描述的所有公共函数来描述 eosio.token 合约的操作。
然后根据之前描述的结构描述每个操作的类型,在大多数情况下,函数名称和结构名称将相等,但不必相等。
下面是链接到其源代码的操作列表,其中提供了示例 JSON,以了解每个操作的描述方式。
create
{
“name”: “create”,
“type”: “create”,
“ricardian_contract”: “”
}
issue
{
“name”: “issue”,
“type”: “issue”,
“ricardian_contract”: “”
}
retire
{
“name”: “retire”,
“type”: “retire”,
“ricardian_contract”: “”
}
transfer
{
“name”: “transfer”,
“type”: “transfer”,
“ricardian_contract”: “”
}
close
{
“name”: “close”,
“type”: “close”,
“ricardian_contract”: “”
}
tables
描述表,这是表的 JSON 对象定义:
{
“name”: “”, //The name of the table, determined during instantiation.
“type”: “”, //The table’s corresponding struct
“index_type”: “”, //The type of primary index of this table
“key_names” : [], //An array of key names, length must equal length of key_types member
“key_types” : [] //An array of key types that correspond to key names array member, length of array must equal length of key names array.
}
eosio.token 合约实例化两个表,accounts 和 stats。
accounts 表是一个 i64 索引,基于 account struct,有一个 uint64 作为它的主键。
以下是如何在 ABI 中描述 accounts 表。
{
“name”: “accounts”,
“type”: “account”, // Corresponds to previously defined struct
“index_type”: “i64”,
“key_names” : [“primary_key”],
“key_types” : [“uint64”]
}
stat 表是一个 i64 索引,基于 currenct_stats struct,有一个 uint64 作为它的主键。
以下是如何在 ABI 中描述 stat 表。
{
“name”: “stat”,
“type”: “currency_stats”,
“index_type”: “i64”,
“key_names” : [“primary_key”],
“key_types” : [“uint64”]
}
你会注意到上面的表格具有相同的“key name”,将键命名为相似的名称是象征性的,因为它可能暗示一种主观关系,与此实现一样,这意味着可以使用任何给定的值来查询不同的表。
把它们放在一起
最后,一个准确描述 eosio.token 合约的 ABI 文件。
{
“version”: “eosio::abi/1.0”,
“types”: [
{
“new_type_name”: “name”,
“type”: “name”
}
],
“structs”: [
{
“name”: “create”,
“base”: “”,
“fields”: [
{
“name”:”issuer”,
“type”:”name”
},
{
“name”:”maximum_supply”,
“type”:”asset”
}
]
},
{
“name”: “issue”,
“base”: “”,
“fields”: [
{
“name”:”to”,
“type”:”name”
},
{
“name”:”quantity”,
“type”:”asset”
},
{
“name”:”memo”,
“type”:”string”
}
]
},
{
“name”: “retire”,
“base”: “”,
“fields”: [
{
“name”:”quantity”,
“type”:”asset”
},
{
“name”:”memo”,
“type”:”string”
}
]
},
{
“name”: “close”,
“base”: “”,
“fields”: [
{
“name”:”owner”,
“type”:”name”
},
{
“name”:”symbol”,
“type”:”symbol”
}
]
},
{
“name”: “transfer”,
“base”: “”,
“fields”: [
{
“name”:”from”,
“type”:”name”
},
{
“name”:”to”,
“type”:”name”
},
{
“name”:”quantity”,
“type”:”asset”
},
{
“name”:”memo”,
“type”:”string”
}
]
},
{
“name”: “account”,
“base”: “”,
“fields”: [
{
“name”:”balance”,
“type”:”asset”
}
]
},
{
“name”: “currency_stats”,
“base”: “”,
“fields”: [
{
“name”:”supply”,
“type”:”asset”
},
{
“name”:”max_supply”,
“type”:”asset”
},
{
“name”:”issuer”,
“type”:”name”
}
]
}
],
“actions”: [
{
“name”: “transfer”,
“type”: “transfer”,
“ricardian_contract”: “”
},
{
“name”: “issue”,
“type”: “issue”,
“ricardian_contract”: “”
},
{
“name”: “retire”,
“type”: “retire”,
“ricardian_contract”: “”
},
{
“name”: “create”,
“type”: “create”,
“ricardian_contract”: “”
},
{
“name”: “close”,
“type”: “close”,
“ricardian_contract”: “”
}
],
“tables”: [
{
“name”: “accounts”,
“type”: “account”,
“index_type”: “i64”,
“key_names” : [“currency”],
“key_types” : [“uint64”]
},
{
“name”: “stat”,
“type”: “currency_stats”,
“index_type”: “i64”,
“key_names” : [“currency”],
“key_types” : [“uint64”]
}
],
“ricardian_clauses”: [],
“abi_extensions”: []
}
代币合约未涵盖的情况
向量
在 ABI 文件中描述向量时,只需使用 [] 附加类型,因此如果需要描述权限级别的向量,你可以这样描述:permission_level[]。
结构基础
这是一个很少使用的属性,值得一提,你可以使用 base ABI 结构属性来引用另一个继承结构,只要该结构也在同一个 ABI 文件中描述,如果你的智能合约逻辑不支持继承,Base 将不执行任何操作或可能抛出错误。
你可以在系统合约源代码和 ABI 中看到正在使用的 base 的示例。
此处未涵盖额外的 ABI 属性
为简洁起见,此处省略了 ABI 规范的一些属性,但是,有一个待定的 ABI 规范将完整地概述 ABI 的每个属性。
李嘉图条款
李嘉图条款描述了特定行为的预期结果,它也可用于在发件人和合约之间建立条款。
ABI 扩展
一个通用的“面向未来”的层,允许旧客户端跳过解析扩展数据的“块”,目前,此属性尚未使用,将来,每个扩展在该向量中都有自己的“块”,以便旧客户端跳过它,以及了解如何解释它的新客户端。
维护
每次更改结构、添加表、添加操作或向操作添加参数、使用新类型时,你都需要记住更新 ABI 文件,在许多情况下,更新 ABI 文件失败不会产生任何错误。
故障排除
表不返回任何行
检查你的表是否在 GLOSSARY:ABI 文件中准确描述,例如,如果使用 cleos 在具有格式错误的 GLOSSARY:ABI 定义的合约上添加表,然后从该表中获取行,则将收到空结果。当合约未能在其 GLOSSARY:ABI 文件中正确描述其表时,cleos 在添加行或读取行时不会产生错误。
上一篇:部署、发行和转移代币