通过eosio.cdt提供的eosin-cpp 工具可以生成ABI文件。为什么要理解ABI,因为在开发的时候,自定义类型等可能会导致生成的ABI文件错误,为了能够修复错误,我们需要先理解ABI。
ABI全称是Application Binary Interface,它是一个基于JSON格式的说明文件,用来描述action在JSON和二进制之间的转换。同时,它还用来描述怎么用JSON表示数据库状态,或根据JSON得到数据库状态。与说明文档类似,有了ABI,开发人员可以通过它理解合约。
注:ABI只是一个说明文件,传递给合约的消息或action不一定得完全符合它。
为了更好理解abi,现在,自己来写一个ABI文件。
在任意一个位置,创建一个空白文件,叫eosio.token.abi,写入下列代码。
{
"version": "eosio::abi/1.0",
"types": [],
"structs": [],
"actions": [],
"tables": [],
"ricardian_clauses": [],
"abi_extensions": [],
"___comment" : ""
}
接下来会逐个解释每一行。
1. Type
这里的types是对于自定义类型的说明。在编写代码时,有些类型的名字可能比较长,或者我们想要让类型名称更加具体,我们给这样的类型起一个别名。举一个例子,我们在编写Hello World的时候,用到了eosio中的类型name,我们现在想让它更具体一点,我们给它起个别名叫username。修改完的代码如下。
#include <eosio/eosio.hpp>
using namespace eosio;
typedef name username; //给name取个别名叫username
class [[eosio::contract]] helloWorld : public contract {
public:
using contract::contract;
[[eosio::action]]
void hello( username user ) { //将之前的name修改为username
require_auth( user );
print( "Hello World", user );
}
};
编译该文件(eosio-cpp helloWorld.cpp -o helloWorld.wasm)。打开abi文件,会发现types变变成了如下。
"types": [
{
"new_type_name": "username",
"type": "name"
}
],
很直观,new_type_name是我们新起类型别名,type是类型原本的名称。
需要注意的是,eosio内建的类型不会在abi文件中显示出来。那什么是内建类型呢,就是eosio已经帮我们定义好了的类型,上面用到到name就是一个内建类型。具体的内建类型可以在此查看。
2. Struct
显然,这里的struct是对于合约中结构体的说明。举个例子说明。在之前eosio.token合约的hpp文件中,在代码的一百多行的位置,可以看到以下代码。
struct [[eosio::table]] account {
asset balance;
uint64_t primary_key()const { return balance.symbol.code().raw(); }
};
这就是其中的一个结构体。同时,打开该合同的abi文件,可以看到对应的说明,如下。name是结构体名,base是该结构体的基类(这里没有,所以为空),fields是结构体内变量的名称及其类型。
"structs": [
{
"name": "account",
"base": "",
"fields": [
{
"name": "balance",
"type": "asset"
}
]
},
...]
但是你会发现,eosio.token.hpp里明明只定义了两个结构体,abi文件里structs却写着很多个。这是因为还有一种叫做隐性结构体(implicit struct)的东西,它们对应着合约action及其参数。
同样用eosio.token合约举例。对比abi文件的structs和eosio.token.hpp文件的action方法名跟参数,可以发现,name就是action方法的名称,fields里边则是action方法的参数及参数对应的类型。
3. Action
Action这一块用于描述该合约中可供外部调用的动作。同样,举例eosio.token合约。
hpp文件中action内容如下。
[[eosio::action]]
void close( const name& owner, const symbol& symbol );
[[eosio::action]]
void create( const name& issuer,
const asset& maximum_supply);
[[eosio::action]]
void issue( const name& to, const asset& quantity, const string& memo );
...
abi文件对应内容如下。
"actions": [
{
"name": "close",
"type": "close",
"ricardian_contract": ""
},
{
"name": "create",
"type": "create",
"ricardian_contract": ""
},
{
"name": "issue",
"type": "issue",
"ricardian_contract": ""
},
...
]
name明显就是action的名称了,type则是该方法对应的隐性结构体的名称(上面有讲到),richardian_contract是李嘉图合约(后续会解释)。
4. Table
同样用eosio.token合约举例。hpp文件中对于表account的定义如下。
struct [[eosio::table]] account {
asset balance;
uint64_t primary_key()const { return balance.symbol.code().raw(); }
};
typedef eosio::multi_index< "accounts"_n, account > accounts;
abi的table内容如下。
{
"name": "accounts", //表的名称,在实例化时确定;
"type": "account", //表对应的结构体;
"index_type": "i64", //表的主键的类型;
"key_names" : ["primary_key"], //字段名称,长度需与下面的key_types相同;
"key_types" : ["uint64"] //字段的类型,需与上面的key_names对应,并且长度相同;
}
(1) name:表的名称为accounts。官方开发文档的说法是“******The eosio.token contract instantiates two tables, accounts and stat.*** ***”(关于multi_index的具体解释请看这里 )。意思是上面hpp部分的最后一行代码实例化了一个叫accounts的表。
(2) type:这个accounts的表是基于account结构体的,所以type为account。
(3) index_type: 索引类型为i64。
(4) key_names:字段为"[primary_key]"。上面hpp部分的第三行决定了一个类型为uint64主键。
(5) key_type:字段类型为["uint64"]。同上。
注:转自CSDN文章。
网友评论