系列文章:
1. Hello World!
2. 部署token合約併發行、交易幣
3. 解析abi文件(本文)
正文
通過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”]。同上。