了解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
在添加行或读取行时不会产生错误。