EOS合約簡介
一、語言
基于EOSIO的塊鍊使用的是WebAssembly (WASM)來執行使用者編寫的智能合約。WASM是一種新興的Web标準,廣泛支援于谷歌、微軟、蘋果等。對編寫WASM标準的智能合約來說使用clang/llvm和它的C/C++編譯器是目前最為成熟的編譯工具鍊。
其他的第三方工具鍊在開發中,包括:Rust, Python, and Solidity。雖然這些語言可能看起來相對簡單,但它們可能會影響您所編寫的智能性能。我們認為,對于開發高性能和安全的智能合約,C++是最好的語言,将來eos的智能合約也還會繼續支援C++。
Linux / Mac OS Experience
EOSIO 支援下面的作業系統: - Amazon 2017.09 and higher - Centos 7 - Fedora 25 and higher (Fedora 27 推薦使用) - Mint 18 - Ubuntu 16.04 (Ubuntu 16.10 推薦使用) - MacOS Darwin 10.12 and higher (MacOS 10.13.x 推薦使用)
二、Action vs Transaction
Action表示單個操作,而transaction是一個或多個action的集合。Action是合約和賬戶之間進行通信的方式。Action可以單獨執行,或者組合組合起來作為一個整體執行。
僅有一個action的transaction.
{
"expiration": "2018-04-01T15:20:44",
"region": 0,
"ref_block_num": 42580,
"ref_block_prefix": 3987474256,
"net_usage_words": 21,
"kcpu_usage": 1000,
"delay_sec": 0,
"context_free_actions": [],
"actions": [{
"account": "eosio.token",
"name": "issue",
"authorization": [{
"actor": "eosio",
"permission": "active"
}
],
"data": "00000000007015d640420f000000000004454f5300000000046d656d6f"
}
],
"signatures": [
""
],
"context_free_data": []
}
包含多個action的transaction, 這些action要麼全部成功要麼全部失敗.
{
"expiration": "...",
"region": 0,
"ref_block_num": ...,
"ref_block_prefix": ...,
"net_usage_words": ..,
"kcpu_usage": ..,
"delay_sec": 0,
"context_free_actions": [],
"actions": [{
"account": "...",
"name": "...",
"authorization": [{
"actor": "...",
"permission": "..."
}
],
"data": "..."
}, {
"account": "...",
"name": "...",
"authorization": [{
"actor": "...",
"permission": "..."
}
],
"data": "..."
}
],
"signatures": [
""
],
"context_free_data": []
}
三、智能合約檔案
從簡單易用的角度出發,我們編寫了一個工具eosiocpp ,它可以建立一個新的智能合約。eosiocpp也可以建立3個合約檔案,它們僅僅包含了合約的架構。
$ eosiocpp -n ${contract}
上面的指令會在./${project}目錄下建立一個空的項目,它包含3個檔案
${contract}.abi ${contract}.hpp ${contract}.cpp
hpp
${contract}.hpp 這是合約的頭檔案,可以包含一些變量,常量和函數的聲明。
cpp
The ${contract}.cpp 這是合約的源碼檔案,包含合約的具體實作。
如果你用eosiocpp生成了一個 .cpp, 那它的内容大概類似如下:
#include <${contract}.hpp>
extern "C" {
void init() {
eosio::print( "Init World!\n" ); // Replace with actual code
}
/// The apply method implements the dispatch of actions to this contract
void apply( uint64_t code, uint64_t action ) {
eosio::print( "Hello World: ", eosio::name(code), "->", eosio::name(action), "\n" );
}
} // extern "C"
在這個例子裡,我們可以看到兩個函數,init和apply。它們會列印log并且不做任何檢查。任何人都可以在任何時刻執行BP允許的所有action。在不需要任何簽名的情況下,合約将被計入帶寬消耗。(Absent any required signatures, the contract will be billed for the bandwidth consumed.)
init
init 僅當合約第一次被部署的時候執行。 在這個函數裡可以初始化變量, 比如,在currency合約中總體的token的供應量。
apply
apply 是一個中轉函數, 他監聽所有傳入的action,并且根據action調用合約相應的函數。apply函數需要兩個參數, code 和 action。
code filter
這個參數是為了對action做出回應,比如下面的apply函數,你可以構造一個通用響應去忽略code。 (In order to respond to a particular action, structure the apply function as follows. You may also construct a response to general actions by omitting the code filter.)
if (code == N(${contract_name}) {
// your handler to respond to particular action
}
當然你也可以為每個action構造各自的一個響應。
action filter
為了響應每一個action,比如構造比如下面的apply函數。通常和code filter一起使用
if (action == N(${action_name}) {
//your handler to respond to a particular action
}
wast
任何合約程式想要部署到EOSIO的區塊鍊網絡中都必須編譯成WASM格式。這是EOS的支援唯一個的格式。
一旦你的CPP檔案寫好了,有就可以用eosiocpp把它編譯成WASM (.wast)檔案了
$ eosiocpp -o ${contract}.wast ${contract}.cpp
abi
ABI( Application Binary Interface)檔案是一個JSON格式的描述檔案,說明了如何在他們的JSON和二進制之間轉化使用者的action。ABI檔案也同時說明了如何轉換資料庫的狀态。一旦你用了ABI描述了你的合約,開發人員就和使用者就可以和你的合約通過JSON進行互動。
ABI可以通過.hpp檔案用eosiocpp生成。
$ eosiocpp -g ${contract}.abi ${contract}.hpp
下面這個例子展示了一個ABI檔案的架構:
{
"types": [{
"new_type_name": "account_name",
"type": "name"
}
],
"structs": [{
"name": "transfer",
"base": "",
"fields": {
"from": "account_name",
"to": "account_name",
"quantity": "uint64"
}
},{
"name": "account",
"base": "",
"fields": {
"account": "name",
"balance": "uint64"
}
}
],
"actions": [{
"action": "transfer",
"type": "transfer"
}
],
"tables": [{
"table": "account",
"type": "account",
"index_type": "i64",
"key_names" : ["account"],
"key_types" : ["name"]
}
]
}
你會注意到這個ABI定義了一個actoin名字是transfer,類型是transfer。這就是告訴EOSIO,當調用的action是transfer時,它的格式是transfer,定義如下:
...
"structs": [{
"name": "transfer",
"base": "",
"fields": {
"from": "account_name",
"to": "account_name",
"quantity": "uint64"
}
},{
...
ABI檔案有很多的部分組成,比如from,to和quantity。每個部分都有自己的類型,比如account_name和uint64。account_name是一個内建類型用base32字元串表示為uint64。想要看到更多的内建類型可以點選這裡
{
"types": [{
"new_type_name": "account_name",
"type": "name"
}
],
...
在上面types 數組裡,我們為已經存在的account_name類型定義了一個别名name 。
四、調試智能合約
為了能夠調試智能合約,你需要在你的本地環境中啟動一個nodeos。這個本地的nodeos可以是一個EOS私有的測試網絡或者是公網的測試網絡。
當你第一次建立智能合約的時候,推薦你最好在你自己的私有測試網絡中調試好,因為你對你自己的私有測試網絡有完全的掌控權。這可以讓你無限制的使用EOS(币)也可以随時複位它的狀态。當合約調試完畢,就可以部署到公共測試網絡了,本地先運作一個連接配接到公共測試網絡的nodeos,然後連接配接到這個節點就可以獲得log輸出了。
步驟是一樣的,是以下面這個手冊也适用于私有測試網絡中的測試。
如果你還沒有一個本地的nodeos環境,可以參考這個連接配接。預設情況下,你的本地nodes會運作在一個私有網絡中,除非你修改了config.ini檔案,讓他去連接配接公共測試網絡,如何修改可以參考這裡。
方法
調試最主要的方法就是用Caveman Debugging,我們增強了printing的功能,他可以去輸出變量的值并且檢查合約的流程。Printing可以通過下面API被合約使用: 這是c 這是 C++). C++的API是對C的封裝,是以大多數我們使用C++的API。
Print C API 支援如下資料類型: - prints - a null terminated char array (string) - prints_l - any char array (string) with given size - printi - 64-bit unsigned integer - printi128 - 128-bit unsigned integer - printd - double encoded as 64-bit unsigned integer - printn - base32 string encoded as 64-bit unsigned integer - printhex - hex given binary of data and its size
同時 Print C++ API 對上面的C API進行了封裝,是以使用者不需要指定應該使用哪種類型的Print。Print C++ API 支援 - a null terminated char array (string) - integer (128-bit unsigned, 64-bit unsigned, 32-bit unsigned, signed, unsigned) - base32 string encoded as 64-bit unsigned integer - struct that has print() method