EOS 教程

1. 賬戶與錢包

注意 本教程是基于 測試私網,但稍作修改就可以運用在測試公網上。

您將學到

您將學到如何創(chuàng)建錢包、管理錢包及其keys并通過eosc使用錢包和區(qū)塊鏈交互。

本教程的目標群體

本教程目標群體是希望學習錢包和賬戶管理的人。我們將盡可能地介紹eosc以及EOS錢包和賬戶是如何交互的。有一定基礎的用戶可查看參考命令

前提條件

  • 在您的系統(tǒng)上構建并運行eosceos-walletd。
  • 命令行操作的基本知識。

注意: 當使用docker安裝時,命令可能需要稍作改動。

1.1 創(chuàng)建并管理錢包

打開終端,進入EOS目錄

這會是我們更方便地操作eosc,它是一個與eosdeos-walletd交互的命令行工具。

$ cd /path_to_eos/build/programs/eosc

首先您要用eoscwallet create創(chuàng)造一個錢包

$ eosc wallet create
Creating wallet: default
Save password to use in the future to unlock this wallet.
Without password imported keys will not be retrievable.
"A MASTER PASSWORD"

一個叫default的錢包現在已經在eos-walletd里了,并且返回了一個該錢包的一個master password。請將這個密碼安全地保存起來。這個密碼是用來解鎖(解密)您的錢包文件的。

該錢包文件叫做default.wallet,被保存在了您的EOS目錄(您也可以在啟動eos-walletd--data-dir制定特定目錄)下的data-dir文件夾里。

管理多個錢包和錢包名

eosc能夠管理多個錢包。每個錢包被各自的master password保護起來。下面的例子創(chuàng)建了另一個錢包并且展示了如何用 -n 參數給他命名

$ eosc wallet create -n periwinkle
Creating wallet: periwinkle
Save password to use in the future to unlock this wallet.
Without password imported keys will not be retrievable.
"A MASTER PASSWORD"

現在確認一下錢包已經用您指定的名字創(chuàng)建出來了。

$ eosc wallet list
Wallets:
[
  "default *",
  "periwinkle *"
]

每個錢包后面的星號 (*) 很重要,他們表示錢包已解鎖。方便起見,我們用create wallet創(chuàng)建出來的錢包默認是解鎖的。

wallet lock鎖住第二個錢包

$ eosc wallet lock -n periwinkle
Locked: 'periwinkle'

再次運行wallet list,您就可以看到第二個星號不見了,表示該錢包已上鎖。

$ eosc wallet list
Wallets:
[
  "default *",
  "periwinkle"
]

解鎖一個有名字的錢包需要用wallet unlock命令并用-n參數指定錢包名,然后輸入錢包的 master密碼(您可以粘貼密碼)。下面我們復制第二個錢包的master密碼,執(zhí)行此命令并粘貼密碼后回車。然后您需要確認操作。

$ eosc wallet unlock -n periwinkle

eosc會告訴您錢包上鎖了

Unlocked: 'periwinkle'

注意: 您也可以用 --password 參數后跟master密碼,但是這會導致您的密碼在控制臺歷史當中被明文地記錄下來。

現在查看一下錢包

$ eosc wallet list
Wallets:
[
  "default *",
  "periwinkle *"
]

好的,periwinkle錢包后面有星號,表示它解鎖了。

注意: 使用'default'錢包不需要使用-n參數

現在重啟 eos-walletd,退回到您調用eosc的路徑下運行以下命令

$ eosc wallet list
Wallets:
[]

有意思,錢包去哪了呢?

錢包需要被打開,因為您關閉過eos-walletd,錢包并不在打開狀態(tài),運行以下命令:

$ eosc wallet open
$ eosc wallet list
Wallets:
[
  "default"
]

好多了。

注意: 如果您希望打開一個有名字的錢包,您可以$ eosc wallet open -n periwinkle,學會了嗎? ;)

從上面的信息中您可以看到錢包是默認鎖住的,把它解鎖才能進行下面的操作。
執(zhí)行wallet unlock命令并在要求輸入密碼時粘貼上default 錢包的master密碼。

$ eosc wallet unlock
Unlocked: 'default'

然后檢查錢包是否已解鎖。

$ eosc wallet list
Wallets:
[
  "default *"
]

錢包名后面有星號,已解鎖,非常好。

您已經學會如何創(chuàng)建多個錢包及如何用eosc操作他們了。但空錢包沒什么意義,現在讓我們導入keys。

1.2 生成并導入EOS Keys

生成EOS key對有好幾種方法,本教程主要講eosccreate key命令的方法。

生成兩個密鑰對

$ eosc create key
Private key:###
Public key: ###
$ eosc create key
Private key:###
Public key: ###

現在您有兩個EOS 密鑰對了。此時,他們只是最初始的密鑰對,并沒有authority。

如果您一直根據上面來操作,您的default錢包應該是打開且解鎖的。

下面,我們執(zhí)行wallet import命令兩次,每次導入我們之前所生成的一個私鑰到您的 default錢包。

$ eosc wallet import ${private_key_1}

然后是第二個私鑰

$ eosc wallet import ${private_key_2}

如果順利,每次wallet import命令都會返回您的私鑰對應的公鑰,您的控制臺會是這樣的:

$ eosc wallet import ${private_key_1}
imported private key for: ${public_key_1}
$ eosc wallet import ${private_key_2}
imported private key for: ${public_key_2}

我們用wallet keys看看加載了哪些密鑰

$ eosc wallet keys
[[
    "EOS6....",
    "5KQwr..."
  ],
  [
    "EOS3....",
    "5Ks0e..."
  ]
]

錢包鎖起來的時候,這些密鑰也會被保護起來。要從一個被鎖住的錢包中拿到密鑰需要有錢包創(chuàng)建時的master密碼。因為錢包文件本身是加密的,備份密鑰對并不是一定要做的,但最好還是在一個安全的地方備份您的錢包文件。

1.3 備份錢包

現在您的錢包里已經有密鑰對了,您最好養(yǎng)成備份但習慣,以防各種各樣的原因造成錢包丟失。比如使用u盤。沒有密碼,錢包是強熵加密的,想拿到里面的密鑰是非常難的 (基本不可能的)。

您可以在data-dir文件夾下找到您的錢包文件。如果您在啟動eos時用--data-dir參數指定過,您可以在/path/to/eos/build/programs/eosd中找到(eos的具體路徑因系統(tǒng)不同而有不同)。

$ cd /path_to_eos/build/programs/eosd && ls
blockchain   blocks   config.ini   default.wallet   periwinkle.wallet

進入文件夾后您將看到兩個文件:default.walletperiwinkle.wallet。把他們保存起來(熟能生巧?。?。

1.4 創(chuàng)建賬戶

如果您用的是測試公網,您需要有一個創(chuàng)世allocation或者從水龍頭賬戶申請一個賬戶。下面操作時請進行適當改動 (提示:應當用您自己的賬戶替換 inita 賬戶)

首先,我們看看 create account 命令及其必需參數:

$ eosc create account inita ${desired_account_name} ${public_key_1} ${public_key_2}

create account命令必需參數的解讀

  • inita 是執(zhí)行新建操作的賬戶名
  • desired_account_name 是您希望新建的賬戶名。
  • public_key_1public_key_2是公鑰,第一個是用于獲取您賬戶owner authority的, 第二個是用戶獲取active authority的。

您之前生成了兩個密鑰對,您可以翻看控制臺前面的記錄或者執(zhí)行wallet keys來查看。

$ eosc wallet keys
[[
    "EOS6....",
    "5KQwr..."
  ],
  [
    "EOS3....",
    "5Ks0e..."
  ]
]

提醒一下,公鑰是以EOS...開頭。在您給密鑰分配authority前,上面的密鑰都是初始的。which one you decide to user for active and owner are inconsequential until you have created your account.

注意, 您的owner密鑰等于對您賬戶的全面控制,而active密鑰等于對您賬戶資金的全面控制。

用您之前所學的,替換命令中的占位符然后回車:

$ eosc create account inita ${desired_account_name} ${public_key_1} ${public_key_2}

您看到了一個提到"authorities"的報錯了嗎?不用著急,我是故意讓您這么做的。您看到報錯是因為您沒有加載@inita這個賬戶的密鑰。

inita 的密鑰存在 config.ini里。但為方便起見,我將其復制了出來放在了下面。直接運行下面的命令即可。

$ eosc wallet import 5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3

將會返回

imported private key for: EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV

現在 @inita 賬戶的密鑰已經加載,重新回到報錯之前的create account 命令并回車。

順利的話 eosc 將返回一個含有transaction ID的JSON對象,類似于下面:

{
  "transaction_id": "6acd2ece68c4b86c1fa209c3989235063384020781f2c67bbb80bc8d540ca120",
  "processed": {
    "refBlockNum": "25217",
    "refBlockPrefix": "2095475630",
    "expiration": "2017-07-25T17:54:55",
    "scope": [
      "eos"...

太好了!您現在已經在區(qū)塊鏈上已經有一個賬戶了。

您做的很棒,您創(chuàng)建了一個錢包,學習了一些錢包是如何工作、生成密鑰及如何把密鑰導入錢包的知識。

2. 貨幣合約概覽

目標

下面的教程將幫助用戶了解github倉庫中的樣例貨幣合約。

概覽

貨幣合約處理的是將貨幣從一個賬戶轉到另一個賬戶的工作,而不同賬戶的余額保存在每個用戶的本地scope中。

Action

目前本合約只有一個action:
currency_transfer:將貨幣從一個賬戶轉到另一個賬戶。

開始!

智能合約分為三個文件:

currency.hpp 合約中的聲明和數據結構信息存在頭文件中
currency.cpp 合約的邏輯和實現
currency.abi 提供給用戶交互的接口定義

頭文件: currency.hpp

首先導入所需庫并定義您的命名空間

// 導入所需庫

#include <eoslib/eos.hpp>   // Generic eos library, i.e. print, type, math, etc
#include <eoslib/token.hpp> // Token usage
#include <eoslib/db.hpp>    // Database access

namespace currency {
    // Your code here
}

然后加入一個貨幣token。 It’s in fact a uin64_t wrapper which checks for proper types and under/overflows for standard-compatible token messages

typedef eosio::token<uint64_t,N(currency)> currency_tokens;

我們action的結構如下所示:

struct transfer {
    account_name from;          //轉出賬戶
    account_name to;            //轉入賬戶
    currency_tokens quantity;   //轉賬金額
};

另外我們把余額信息存在表里。表是如下定義的:

using accounts = eosio::table<N(defaultscope),N(currency),N(account),account,uint64_t>;
  • 第一個參數定義表的默認scope,比如當有沒有指定scope的數據存入表中時,它就會使用這個賬戶。

  • 第二個參數定義表的所有者 (比如合約的名字)

  • 第三個參數定義表的名字

  • 第四個參數定義存儲數據結構(將在后面定義)

  • 第五個參數定義表中key的類型

一旦表定義了,需要儲存的數據結構(在我們的例子中是“賬戶”)也需要被定義。這是在另一個struct中完成的:

struct account {
    //Constructor
    account( currency_tokens b = currency_tokens() ):balance(b){}

    //key是常量,因為每個scope/currency/accounts只有一條記錄
    const uint64_t key = N(account);

    //賬戶的token數量
    currency_tokens balance;

    // 用于檢查賬戶是否為空的方法
    // 如果余額為0返回true
    bool is_empty()const { return balance.quantity == 0; }
};

這個結構包含一個構造器和一個用于判斷賬戶是否為空的標準函數。

需要注意的是,key的變量類型需要與之前在定義表時 (第五個函數)定義的類型一致。

為方便起見,我們增加了一個存取器函數來獲取所有者的賬戶信息,返回存在owner/TOKEN_NAME/account/account的信息。此函數存在頭文件中以提供第三方獲取用戶余額的能力。

inline account get_account( account_name owner ) {
    account owned_account;
    accounts::get( owned_account, owner );
    return owned_account;
}

注意: accounts:get函數返回賬戶所有者。為應對賬戶不存在的情況,它返回一個默認結構的賬戶。

源代碼文件:currency.cpp

#include <currency/currency.hpp>

// The init() and apply() methods must have C calling convention

extern "C" {
    // Only called once
    void init() {
    }

    // The apply method implements the dispatch of events to this contract
    void apply( uint64_t code, uint64_t action_name ) {
        // Put your message handler here
    }
} // extern "C"

所有的合約都有以上的骨架,每個合約都需要有以上的函數:

Init() 在一個合約的生命周期開始時被調用一次。可用它來設置環(huán)境來讓合約正確運行。

Apply( uint64_t code, uint64_t action_name) 被用作一個message的槽子。每次有message發(fā)給合約時,此函數即開始調用。它的兩個參數含義如下:

  • code: 合約名稱
  • action_name: action名稱

在貨幣合約中,init() 函數如下所示:

void init() {
    account owned_account;

    //初始化貨幣賬戶,除非賬戶不存在
    if ( !accounts::get( owned_account, N(currency) )) {
        store_account( N(currency),
        account( currency_tokens(1000ll\*1000ll\*1000ll) ) );
    }
}

合約第一次運行時,它會檢查currency賬戶是否有建立表且貨幣余額記錄在表中。如果沒有建立表就會生成一個新表,余額為1000,000,000,這樣貨幣合約就成為了總量1000,000,000的貨幣單位的第一個所有者。

message槽如下所示:

void apply( uint64_t code, uint64_t action ) {
    if( code == N(currency) ) {
        if( action == N(transfer) )
            account::apply_currency_transfer( current_message<account::transfer >() );
    }
}

最好在上面的樣例代碼中實現一個message過濾器,使得合約只處理那些正確的messages并在過濾后調用message處理器。

注意 current_message() 會在message傳給特定處理器之前調用,它是用來將合約收到的message轉為struct T的。

Message處理器

實際上的貨幣轉賬是在這里操作的:

void apply_currency_transfer( const account::transfer& transfer_msg )
{
    require_notice( transfer_msg.to, transfer_msg.from );
    require_auth( transfer_msg.from );

    auto from = get_account( transfer_msg.from );
    auto to = get_account( transfer_msg.to );

    from.balance -= transfer_msg.quantity;
    to.balance += transfer_msg.quantity;

    store_account( transfer_msg.from, from );
    store_account( transfer_msg.to, to );
}

代碼非常直接,從轉出賬戶扣除轉賬金額并增加到轉入賬戶。

require_notice函數是一個inline action,使得把收到的message轉到另一個賬戶成為可能。此例中message被轉發(fā)給了轉入賬戶和轉出賬戶。這是非常有用的功能,因為它把那些“被通知的賬戶”引入鏈上并發(fā)揮功能。
require_auth函數使得message被正確地簽名。在這個例子中,轉出賬戶需要簽名,這個transaction才能被正確地處理。

注意我們正在使用頭文件里的get_account函數來獲得正確的賬戶對象。

Since we are using tokens, automatic over and underflow assertions are being backed into the actual subtraction and addition operations.

最后通過store_account函數更新余額。

Store_account

這個函數是用來實際處理余額的儲存的:

void store_account( account_name current_account, const account& value ) {
    if( a.is_empty() ) {
        accounts::remove( value, current_account);
    } else {
        accounts::store( value, current_account);
    }
}

有趣的是,如果賬戶(也就是在current_account的scope下創(chuàng)建的表)是空的,他就會被移除,這是因為只要有錢轉到不存在的賬戶里,表就會被新建出來。

移除不需要的表是一種節(jié)約資源的做法,是一種寫智能合約的最佳實踐。

注意: 當把上面的樣例代碼和倉庫里的實際代碼比較時,請注意為了賬戶可以更簡單的重命名,我們使用了TOKEN_NAME作為一種#define。上面的代碼中,我們用賬戶名替代了TOKEN_NAME以使得代碼更清晰。

ABI文件: currency.abi

Abi (即Application Binary Interface) 發(fā)送的message和二進制版本的智能合約之間的接口。我們先來看一個的通用版本,它包括如下對象:

  • struct: 合約中action/ table用到的數據結構的列表

  • actions: 合約中可用的actions的列表

  • tables: 合約中可用的tables的列表

{
    "structs": \[{
        "name": "...",
        "base": "...",  
        "fields": { ... }
    }, ...\],
    "actions": \[{
        "action_name": "...",
        "type": "..."
    }, ...\],
    "tables": \[{
        "table_name": "...",
        "type": "...",
        "key_names" : \[...\],
        "key_types" : \[...\]
    }, ...\]
}

struct對象

根據合約中頭文件的信息,可以創(chuàng)建大多數ABI。因此我們從數據結構開始。頭文件中有兩個結構:

struct transfer {
    account_name from;
    account_name to;
    currency_tokens quantity;
};

struct account {
    account( currency_tokens b = currency_tokens() ):balance(b){}
    const uint64_t key = N(account);
    currency_tokens balance;
    bool is_empty()const { return balance.quantity == 0; }
};

這些結構就生成了如下ABI信息:

"structs": \[{
    "name": "transfer",
    "base": "",
    "fields": {
        "from": "account_name",
        "to": "account_name",
        "quantity": "uint64"
    }
},{
    "name": "account",
    "base": "",
    "fields": {
        "key": "name",
        "balance": "uint64"
    }
}\]

action對象

Action 對象也是類似的對應。在這里我們在貨幣合約中有一個叫 “transfer” action??雌饋砗拖旅娴腁BI文件類似:

"actions": \[{
    "action_name": "transfer",
    "type": "transfer"
}\]

table對象

頭文件中, a single index called “account” table定義如下:

eosio::table<N(defaultscope),N(currency),N(account),account,uint64_t>;

這張表就轉為下面的ABI對象:

"tables": \[{
    "table_name": "account",
    "type": "account",
    "index_type": "i64",
    "key_names" : \["key"\],
    "key_types" : \["name"\]
}\]

這樣就組成了ABI文件。

部署與運行

現在三個文件 (currency.hpp, currency.cpp, currency.abi) 都可以通過命令行部署了:

$ eosc set contract currency currency.wast currency.abi

請確認錢包已經解鎖且含有 currency 的密鑰。部署后合約的action可以通過命令行這樣觸發(fā):

$ eosc push message currency transfer ‘{“from”:“currency”,“to”:“tester”,“quantity”:50}’ -S currency -S tester -p currency@active

3. “Hello World”智能合約

為方便起見,我們創(chuàng)造了一個叫eoscpp的工具來引導產生新的智能合約。您需要先安裝eosio/eos并把${CMAKE_INSTALL_PREFIX}/bin放入您的環(huán)境變量,它才能正常工作。

$ eoscpp -n hello
$ cd hello
$ ls

上面在'./hello'文件夾創(chuàng)建了一個新的空工程,里面有三個文件:

hello.abi hello.hpp hello.cpp

我們看一下最簡單的合約:

$ cat hello.cpp

#include <hello.hpp>

/**
 *  The init() and apply() methods must have C calling convention so that the blockchain can lookup and
 *  call these methods.
 */
extern "C" {

    /**
     *  This method is called once when the contract is published or updated.
     */
    void init()  {
       eosio::print( "Init World!\n" );
    }

    /// The apply method implements the dispatch of events to this contract
    void apply( uint64_t code, uint64_t action ) {
       eosio::print( "Hello World: ", eosio::name(code), "->", eosio::name(action), "\n" );
    }

} // extern "C"

這個合約實現了兩個入口, initapply。它所做的只是記錄提交的messages而并不作檢查。只要區(qū)塊生產者同意,任何人在任何時間都可以提交任何message。但沒有所需的簽名,合約將因消耗帶寬被收費。

您可以將合約像這樣編譯成文本版本的WASM (.wast) :

$ eoscpp -o hello.wast hello.cpp

部署您的合約

現在您已經編譯了您的應用,我們可以部署了。這需要您先:

  1. 啟動 eosd 并打開錢包插件
  2. 新建錢包,導入至少一個賬戶的密鑰
  3. 解鎖錢包

如果您的錢包里有${account}的密鑰且已經解鎖,您就可以用下面的命令把合約上傳到區(qū)塊鏈上

$ eosc set contract ${account} hello.wast hello.abi
Reading WAST...
Assembling WASM...
Publishing contract...
{
  "transaction_id": "1abb46f1b69feb9a88dbff881ea421fd4f39914df769ae09f66bd684436443d5",
  "processed": {
    "ref_block_num": 144,
    "ref_block_prefix": 2192682225,
    "expiration": "2017-09-14T05:39:15",
    "scope": [
      "eos",
      "${account}"
    ],
    "signatures": [
      "2064610856c773423d239a388d22cd30b7ba98f6a9fbabfa621e42cec5dd03c3b87afdcbd68a3a82df020b78126366227674dfbdd33de7d488f2d010ada914b438"
    ],
    "messages": [{
        "code": "eos",
        "type": "setcode",
        "authorization": [{
            "account": "${account}",
            "permission": "active"
          }
        ],
        "data": "0000000080c758410000f1010061736d0100000001110460017f0060017e0060000060027e7e00021b0203656e76067072696e746e000103656e76067072696e7473000003030202030404017000000503010001071903066d656d6f7279020004696e69740002056170706c7900030a20020600411010010b17004120100120001000413010012001100041c00010010b0b3f050041040b04504000000041100b0d496e697420576f726c64210a000041200b0e48656c6c6f20576f726c643a20000041300b032d3e000041c0000b020a000029046e616d6504067072696e746e0100067072696e7473010004696e697400056170706c790201300131010b4163636f756e744e616d65044e616d6502087472616e7366657200030466726f6d0b4163636f756e744e616d6502746f0b4163636f756e744e616d6506616d6f756e740655496e743634076163636f756e740002076163636f756e74044e616d650762616c616e63650655496e74363401000000b298e982a4087472616e736665720100000080bafac6080369363401076163636f756e7400076163636f756e74"
      }
    ],
    "output": [{
        "notify": [],
        "deferred_transactions": []
      }
    ]
  }
}

如果您查看eosd 進程的輸出您將看到:

...] initt generated block #188249 @ 2017-09-13T22:00:24 with 0 trxs  0 pending
Init World!
Init World!
Init World!

您可以看到"Init World!"被執(zhí)行了三次,這其實并不是個bug。區(qū)塊鏈處理transactions的流程是:

1: eosd收到一個新transaction (正在驗證的transaction)

  • 創(chuàng)建一個新的臨時會話
  • 嘗試應用此transaction
  • 成功并打印出"Init World!"
  • 失敗則回滾所做的變化 (也有可能打印"Init World!"后失敗)

2 : eosd開始產出區(qū)塊

  • 撤銷所有pending狀態(tài)
  • pushes all transactions as it builds the block
  • 第二次打印"Init World!"
  • 完成區(qū)塊
  • 撤銷所有創(chuàng)造區(qū)塊時的臨時變化

3rd : eosd如同從網絡上獲得區(qū)塊一樣將區(qū)塊追加到鏈上。

  • 第三次打印 "Init World!"

此時,您的合約就可以開始接受messages了。因為默認message處理器接受所有messages,我們可以發(fā)送任何我們想發(fā)的東西。我們試一下發(fā)一個空的message:

$ eosc push message ${account} hello '"abcd"' --scope ${account}

此命令將"hello"message及16進制字符串"abcd"所代表的二進制文件傳出。注意,后面我們將展示如何定義ABI來用一個好看易讀的JSON對象替換16進制字符串。以上,我們只是想證明“hello”類型的message是如何發(fā)送到賬戶的。

結果是:

{
  "transaction_id": "69d66204ebeeee68c91efef6f8a7f229c22f47bcccd70459e0be833a303956bb",
  "processed": {
    "ref_block_num": 57477,
    "ref_block_prefix": 1051897037,
    "expiration": "2017-09-13T22:17:04",
    "scope": [
      "${account}"
    ],
    "signatures": [],
    "messages": [{
        "code": "${account}",
        "type": "hello",
        "authorization": [],
        "data": "abcd"
      }
    ],
    "output": [{
        "notify": [],
        "deferred_transactions": []
      }
    ]
  }
}

如果您繼續(xù)查看eosd的輸出,您將在屏幕上看到:

Hello World: ${account}->hello
Hello World: ${account}->hello
Hello World: ${account}->hello

再一次,您的合約在transaction被第三次應用并成為產出的區(qū)塊之前被執(zhí)行和撤銷了兩次。

Message名的限定

Message的類型實際上是base32編碼的64位整數。所以Message名的前12個字符需限制在字母a-z, 1-5, 以及'.' 。第13個以后的字符限制在前16個字符('.' and a-p)。

ABI - Application Binary Interface

Application Binary Interface (ABI)是一個基于JSON的描述文件,是關于轉換JSON和二進制格式的用戶actions的。ABI還描述了如何將數據庫狀態(tài)和JSON的互相轉換。一旦您通過ABI描述了您的合約,開發(fā)者和用戶就能夠用JSON和您的合約無縫交互了。

我們正在開發(fā)使用C++源碼自動生成ABI的工具,但目前為止您還是只能手動生成。

這里是一個合約的骨架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 定義了一個叫transfer的action,它的類型也是transfer。這就告訴EOS.IO當${account}->transfer的message發(fā)生時,它的payload是transfer類型的。 transfer類型是在structs的列表中定義的,其中有個對象,name屬性是transfer

...
  "structs": [{
      "name": "transfer",
      "base": "",
      "fields": {
        "from": "account_name",
        "to": "account_name",
        "quantity": "uint64"
      }
    },{
...

這部分包括from, toquantity等字段。這些字段都有對應的類型:account_nameuint64。account_nametypes 列表中被定義為name的別名,而name是一個內置類型,用于用base32編碼uint64_t (比如賬戶名)。

{
  "types": [{
      "new_type_name": "account_name",
      "type": "name"
    }
  ],
...

在弄清骨架ABI后,我們可以構造一個transfer類型的message:

eosc push message ${account} transfer '{"from":"currency","to":"inita","quantity":50}' --scope initc
2570494ms thread-0   main.cpp:797                  operator()           ] Converting argument to binary...
{
  "transaction_id": "b191eb8bff3002757839f204ffc310f1bfe5ba1872a64dda3fc42bfc2c8ed688",
  "processed": {
    "ref_block_num": 253,
    "ref_block_prefix": 3297765944,
    "expiration": "2017-09-14T00:44:28",
    "scope": [
      "initc"
    ],
    "signatures": [],
    "messages": [{
        "code": "initc",
        "type": "transfer",
        "authorization": [],
        "data": {
          "from": "currency",
          "to": "inita",
          "quantity": 50
        },
        "hex_data": "00000079b822651d000000008040934b3200000000000000"
      }
    ],
    "output": [{
        "notify": [],
        "deferred_transactions": []
      }
    ]
  }
}

如果您繼續(xù)觀察eosd的輸出,您將看到:

Hello World: ${account}->transfer
Hello World: ${account}->transfer
Hello World: ${account}->transfer

處理轉賬Message的參數

根據ABI,transfer message應該是如下格式的:

     "fields": {
         "from": "account_name",
         "to": "account_name",
         "quantity": "uint64"
     }

我們也知道account_name -> uint64表示這個message的二進制表示如同:

struct transfer {
    uint64_t from;
    uint64_t to;
    uint64_t quantity;
};

EOS.IO的C API通過Message API提供獲取message的payload的能力:

uint32_t message_size();
uint32_t read_message( void* msg, uint32_t msglen );

讓我們修改hello.cpp來打印出消息內容:

#include <hello.hpp>

/**
 *  The init() and apply() methods must have C calling convention so that the blockchain can lookup and
 *  call these methods.
 */
extern "C" {

    /**
     *  This method is called once when the contract is published or updated.
     */
    void init()  {
       eosio::print( "Init World!\n" );
    }

    struct transfer {
       uint64_t from;
       uint64_t to;
       uint64_t quantity;
    };

    /// The apply method implements the dispatch of events to this contract
    void apply( uint64_t code, uint64_t action ) {
       eosio::print( "Hello World: ", eosio::name(code), "->", eosio::name(action), "\n" );
       if( action == N(transfer) ) {
          transfer message;
          static_assert( sizeof(message) == 3*sizeof(uint64_t), "unexpected padding" );
          auto read = readMessage( &message, sizeof(message) );
          assert( read == sizeof(message), "message too short" );
          eosio::print( "Transfer ", message.quantity, " from ", eosio::name(message.from), " to ", eosio::name(message.to), "\n" );
       }
    }

} // extern "C"

這樣我們就可以重編譯并部署了:

eoscpp -o hello.wast hello.cpp 
eosc set contract ${account} hello.wast hello.abi

eosd因為重部署將再次調用init()

Init World!
Init World!
Init World!

然后我們執(zhí)行transfer:

$ eosc push message ${account} transfer '{"from":"currency","to":"inita","quantity":50}' --scope ${account}
{
  "transaction_id": "a777539b7d5f752fb40e6f2d019b65b5401be8bf91c8036440661506875ba1c0",
  "processed": {
    "ref_block_num": 20,
    "ref_block_prefix": 463381070,
    "expiration": "2017-09-14T01:05:49",
    "scope": [
      "${account}"
    ],
    "signatures": [],
    "messages": [{
        "code": "${account}",
        "type": "transfer",
        "authorization": [],
        "data": {
          "from": "currency",
          "to": "inita",
          "quantity": 50
        },
        "hex_data": "00000079b822651d000000008040934b3200000000000000"
      }
    ],
    "output": [{
        "notify": [],
        "deferred_transactions": []
      }
    ]
  }
}

后面我們將看到eosd有如下輸出:

Hello World: ${account}->transfer
Transfer 50 from currency to inita
Hello World: ${account}->transfer
Transfer 50 from currency to inita
Hello World: ${account}->transfer
Transfer 50 from currency to inita

使用 C++ API來讀取 Messages

目前我們使用是C API因為這是EOS.IO直接暴露給WASM虛擬機的最底層的API。幸運的是,eoslib提供了一個更高級的API,移除了很多不必要的代碼。

/// eoslib/message.hpp
namespace eosio {
     template<typename T>
     T current_message();
}

我們可以向下面一樣更新 hello.cpp 把它變得更簡潔:

#include <hello.hpp>

/**
 *  The init() and apply() methods must have C calling convention so that the blockchain can lookup and
 *  call these methods.
 */
extern "C" {

    /**
     *  This method is called once when the contract is published or updated.
     */
    void init()  {
       eosio::print( "Init World!\n" );
    }

    struct transfer {
       eosio::name from;
       eosio::name to;
       uint64_t quantity;
    };

    /// The apply method implements the dispatch of events to this contract
    void apply( uint64_t code, uint64_t action ) {
       eosio::print( "Hello World: ", eosio::name(code), "->", eosio::name(action), "\n" );
       if( action == N(transfer) ) {
          auto message = eosio::current_message<transfer>();
          eosio::print( "Transfer ", message.quantity, " from ", message.from, " to ", message.to, "\n" );
       }
    }

} // extern "C"

您可以注意到我們更新了transfer的struct,直接使用eosio::name 類型并將read_message前后的類型檢查壓縮為一個單個的current-Message調用。

在編譯和上傳后,您將看到和C語言版本同樣的結果。

獲取發(fā)送者的Authority來進行轉賬

合約最普遍的需求之一就是定義誰可以進行這樣的操作。比如在貨幣轉賬的例子里,我們就需要定義為from字段的賬戶核準此message。

EOS.IO軟件負責加強和驗證簽名,您需要做的是獲取所需的authority。

    ...
    void apply( uint64_t code, uint64_t action ) {
       eosio::print( "Hello World: ", eosio::name(code), "->", eosio::name(action), "\n" );
       if( action == N(transfer) ) {
          auto message = eosio::current_message<transfer>();
          eosio::require_auth( message.from );
          eosio::print( "Transfer ", message.quantity, " from ", message.from, " to ", message.to, "\n" );
       }
    }
    ...

建立和部署后,我們可以再試一次轉賬:

 eosc push message ${account} transfer '{"from":"initb","to":"inita","quantity":50}' --scope ${account}
 1881603ms thread-0   main.cpp:797                  operator()           ] Converting argument to binary...
 1881630ms thread-0   main.cpp:851                  main                 ] Failed with error: 10 assert_exception: Assert Exception
 status_code == 200: Error
 : 3030001 tx_missing_auth: missing required authority
 Transaction is missing required authorization from initb
     {"acct":"initb"}
         thread-0  message_handling_contexts.cpp:19 require_authorization
...

如果您查看eosd ,您將看到:

Hello World: initc->transfer
1881629ms thread-0   chain_api_plugin.cpp:60       operator()           ] Exception encountered while processing chain.push_transaction:
...

這表示此操作嘗試請求應用您的transaction,打印出了初始的"Hello World",然后當eosio::require_auth沒能成功獲取initb賬戶的authorization后,操作終止了。

我們可以通過讓eosc增加所需的permission來修復這個問題:

 eosc push message ${account} transfer '{"from":"initb","to":"inita","quantity":50}' --scope ${account} --permission initb@active

--permission 命令定義了賬戶和permission等級,此例中我們使用active authority,也就是默認值。

這次轉賬應該就成功了,如同我們之前看到的一樣。

Aborting a Message on Error

絕大多數合約開發(fā)中有非常多的前置條件,比如轉賬的金額要大于0。如果用戶嘗試進行一個非法action,合約必須終止且已做出的任何變動都必須自動回滾。

    ...
    void apply( uint64_t code, uint64_t action ) {
       eosio::print( "Hello World: ", eosio::name(code), "->", eosio::name(action), "\n" );
       if( action == N(transfer) ) {
          auto message = eosio::current_message<transfer>();
          assert( message.quantity > 0, "Must transfer a quantity greater than 0" );
          eosio::require_auth( message.from );
          eosio::print( "Transfer ", message.quantity, " from ", message.from, " to ", message.to, "\n" );
       }
    }
    ...

我們編譯、部署并嘗試進行一次金額為0的轉賬:

 $ eoscpp -o hello.wast hello.cpp
 $ eosc set contract ${account} hello.wast hello.abi
 $ eosc push message ${account} transfer '{"from":"initb","to":"inita","quantity":0}' --scope initc --permission initb@active
 3071182ms thread-0   main.cpp:851                  main                 ] Failed with error: 10 assert_exception: Assert Exception
 status_code == 200: Error
 : 10 assert_exception: Assert Exception
 test: assertion failed: Must transfer a quantity greater than 0

4. Tic-Tac-Toe

目標

下面的教程將引導用戶構建一個樣例的PvP的游戲合約。我們用tic tac toe游戲來舉例。本教程的結果在 這里.

前提

在此游戲中,我們用標準的3x3 tic tac toe板。玩家們有兩種角色hostchallenger。Host 永遠是先手。每個玩家只能同時玩兩局比賽,一局是第一個玩家是host另一局是第二個玩家是host。

游戲板

(0,0) (1,0) (2,0)
(0,0) - o x
(0,1) - x -
(0,2) x o o

不同于傳統(tǒng)的tic tac toe游戲,我們不用ox ,而用1 代表host的一步,2代表challenger的一步,0代表空各自。而且我們使用一維數組來保存游戲數據。因此:

(0,0) (1,0) (2,0)
(0,0) - o x
(0,1) - x -
(0,2) x o o

假設 x ,是host上面的游戲板可表示為[0, 2, 1, 0, 1, 0, 1, 2, 2]。

Action

用戶需要用下列actions來和合約交互:

  • create: 創(chuàng)建一個新游戲
  • restart: 重啟一個現有的游戲, host或challenger都可以這么做
  • close: 關閉一個現有的游戲,釋放存儲游戲的數據,只有host可以這么做
  • move: 走一步

合約賬戶

在下面的教程中,我們將把合約添加到一個叫tic.tac.toe的賬戶中。為防止tic.tac.toe的賬戶名被占用,您可以用其他的賬戶名,只需要在代碼里面用您的賬戶名替換掉tic.tac.toe 。如果您沒有賬戶,請先創(chuàng)建。

$ eosc create account ${creator_name} ${contract_account_name} ${contract_pub_owner_key} ${contract_pub_active_key} --permission ${creator_name}@active
# e.g. $ eosc create account inita tic.tac.toe  EOS4toFS3YXEQCkuuw1aqDLrtHim86Gz9u3hBdcBw5KNPZcursVHq EOS7d9A3uLe6As66jzN8j44TXJUqJSK3bFjjEEqR4oTvNAB3iM9SA --permission inita@active

請先解鎖錢包并導入私鑰,否則上面的命令將失敗。

開始!

我們將創(chuàng)建三個文件:

  • tic_tac_toe.hpp => 定義合約結構的頭文件
  • tic_tac_toe.cpp => 合約的主要邏輯
  • tic_tac_toe.abi => 用戶和合約交互的接口

定義結構

讓我們先從定義合約結構開始。打開tic_tac_toe.hpp 并且從下面的模版代碼開始

// Import necessary library
#include <eoslib/eos.hpp> // Generic eos library, i.e. print, type, math, etc
#include <eoslib/db.hpp> // Database access

using namespace eosio;
namespace tic_tac_toe {
    // Your code here
}

游戲表

對于這個合約我們需要把游戲列表存在表中,我們來定義它:

...
namespace tic_tac_toe {
    ...
    using Games = eosio::table<N(tic.tac.toe),N(tic.tac.toe),N(games),game,uint64_t>;
}

NB: 如果您要把合約上傳到其他賬戶上,請用您的賬戶名替代tic.tac.toe

第一個參數定義表的默認scope,比如當有沒有指定scope的數據存入表中時,它就會使用這個賬戶。

  • 第二個參數定義表的所有者 (比如合約的名字)

  • 第三個參數定義表的名字

  • 第四個參數定義存儲數據結構(將在后面定義)

  • 第五個參數定義表中key的類型

游戲結構

下面我們來定義游戲的結構。注意在代碼中,定義結構需要在定義表之前。

...
namespace tic_tac_toe {
    struct PACKED(game) {
        // 默認 constructor
        game() {};
        // Constructor
        game(account_name challenger, account_name host):challenger(challenger), host(host), turn(host) {
            // 初始化游戲板
            initialize_board();
        };
        // challenger的賬戶名,也是表中的key
        account_name     challenger;
        // host的賬戶名
        account_name     host;
        // 輪到誰走, = 可能是host或challenger的賬戶名
        account_name     turn; 
        // 贏家, = 空或平手或者是host或challenger的賬戶名
        account_name     winner = N(none); 
        // 游戲板列表的長度,需放在游戲板列表的前面一個。有此字段abi序列化工具才能正確的可以打包寫入數據庫或從數據庫拆包數據
        uint8_t          board_len = 9;
        // 游戲板列表
        uint8_t          board[9]; 

        // 用空格初始化游戲板
        void initialize_board() {
            for (uint8_t i = 0; i < board_len ; i++) {
            board[i] = 0;
            }
        }

        // 重置游戲
        void reset_game() {
            initialize_board();
            turn = host;
            winner = N(none);
        }
    };
    ...
}

記住,在前面表定義的時候,我們聲明表的key數據類型是uint64_t。因此,在前面的游戲結構中,結構中前sizeof(uint64_t)字節(jié)長度的數據將被當成表的key。順便一提,account_name只是uint64_t的別名。

Action 結構

Create

要新建游戲,我們需要 host 賬戶名和 challenger 賬戶名。

...
namespace tic_tac_toe {
    ...
    struct create {
        account_name   challenger;
        account_name   host;
    };
    ...
}
Restart

要重啟游戲,我們需要host 賬戶名和 challenger 賬戶名來找到該游戲。而且,我們需要指定是誰重啟了游戲,這樣才能驗證是否有有效的簽名。

...
namespace tic_tac_toe {
    ...
    struct restart {
        account_name   challenger;
        account_name   host;
        account_name   by;
    };
    ...
}
Close

要關閉游戲,我們需要host 賬戶名和 challenger 賬戶名來找到該游戲。

...
namespace tic_tac_toe {
    ...
    struct close {
        account_name   challenger;
        account_name   host;
    };
    ...
}
Move

要移動一步,我們需要host 賬戶名和 challenger 賬戶名來找到該游戲。 而且,我們需要指定是誰走的這一步以及這一步走在哪。

...
namespace tic_tac_toe {
    ...
    struct movement {
        uint32_t    row;
        uint32_t    column;
    };

    struct Move {
        account_name   challenger;
        account_name   host;
        account_name   by; // the account who wants to make the move
        movement       m;
    };
    ...
}

您可以在 這里 找到atic_tac_toe.hpp 的最終代碼。

主程序

打開tic_tac_toe.cpp并配置骨架代碼

#include <tic_tac_toe.hpp>
using namespace eosio;
/**
*  The init() and apply() methods must have C calling convention so that the blockchain can lookup and
*  call these methods.
*/
extern "C" {

  // Only called once
  void init()  {
  }

  /// The apply method implements the dispatch of events to this contract
  void apply( uint64_t code, uint64_t action_name ) {
      // Put your message handler here
  }

} // extern "C"

Message 處理器

我們希望tic_tac_toe合約僅響應發(fā)給tic.tac.toe賬戶的message并且根據不同的action類型來給出不同響應。讓我們在apply函數中加入message過濾器。

  ...
  void apply( uint64_t code, uint64_t action_name ) {
        if (code == N(tic.tac.toe)) {
            if (action_name == N(create)) {
                tic_tac_toe::apply_create(current_message<tic_tac_toe::create>());
            } else if (action_name == N(restart)) {
                tic_tac_toe::apply_restart(current_message<tic_tac_toe::restart>());
            } else if (action_name == N(close)) {
                tic_tac_toe::apply_close(current_message<tic_tac_toe::close>());
            } else if (action_name == N(move)) {
                tic_tac_toe::apply_move(current_message<tic_tac_toe::move>());
            }
        }
  }
  ...

注意我們在把message傳入特定處理器之前使用了current_message<T>(),它是將收到的message 轉為struct T的。

NB: 如果您正部署到另一個賬戶,請用您的賬戶名替換tic.tac.toe。

為了簡潔起見,我們把message處理器包裝在namespace tic_tac_toe中:

namespace tic_tac_toe {

  void apply_create(const create& c) {
    // Put code for create action here
  }

  void apply_restart(const restart& r) {
    // Put code for restart action here
  }

  void apply_close(const close& c) {
    // Put code for close action here
  }

  void apply_move(const move& m) {
    // Put code for move action here
  }
  ...
}

create Message 處理器

對于create message的處理器,我們需要

  1. 確保message有host的簽名
  2. 確保同一個玩家并不在玩這盤游戲
  3. 確保該游戲不存在
  4. 把新建的游戲存入數據庫
namespace tic_tac_toe {
    ...
    void apply_create(const create& c) {
        require_auth(c.host);
        assert(c.challenger != c.host, "challenger shouldn't be the same as host");

        // Check if game already exists
        game existing_game;
        bool game_exists = Games::get(c.challenger, existing_game, c.host);
        assert(game_exists == false, "game already exists");

        game game_to_create(c.challenger, c.host);
        Games::store(game_to_create, c.host);
    }
    ...
}

Restart Message 處理器

對于 restart message 處理器,我們需要:

  1. 確保message有host或challenger的簽名
  2. 確保該游戲存在
  3. 確保重啟的action是host或challenger做出的
  4. 重啟游戲
  5. 將更新過的游戲存入數據庫
namespace tic_tac_toe {
    ...
    void apply_restart(const restart& r) {
        require_auth(r.by);

        // Check if game exists
        game game_to_restart;
        bool game_exists = Games::get(r.challenger, game_to_restart, r.host);
        assert(game_exists == true, "game doesn't exist!");

        // Check if this game belongs to the message sender
        assert(r.by == game_to_restart.host || r.by == game_to_restart.challenger, "this is not your game!");

        // Reset game
        game_to_restart.reset_game();

        Games::update(game_to_restart, game_to_restart.host);
    }
    ...
}

Close Message 處理器

對于close message 處理器,我們需要:

  1. 確保message有host的簽名
  2. 確保該游戲存在
  3. 將該游戲從數據庫移除
namespace tic_tac_toe {
    ...
    void apply_close(const close& c) {
        require_auth(c.host);

        // Check if game exists
        game game_to_close;
        bool game_exists = Games::get(c.challenger, game_to_close, c.host);
        assert(game_exists == true, "game doesn't exist!");

        Games::remove(game_to_close, game_to_close.host);
    }
    ...
}

Move Message處理器

對于move message處理器,我們需要:

  1. 確保message有host或challenger的簽名
  2. 確保該游戲存在
  3. 確保該游戲并未結束
  4. 確保move的action是host或challenger做出的
  5. 確保輪到了正確的玩家行動
  6. 驗證這一步是有效的
  7. 用這一步升級游戲板
  8. 將move_turn分給另一個玩家
  9. 判斷贏家
  10. 把更新過的數據存入數據庫
namespace tic_tac_toe {
    ...
    bool is_valid_movement(const movement& mvt, const game& game_for_movement) {
    // Put code here
    }

    account_name get_winner(const game& current_game) {
        // Put code here
    }

    void apply_move(const move& m) {
        require_auth(m.by);

        // Check if game exists
        game game_to_move;
        bool game_exists = Games::get(m.challenger, game_to_move, m.host);
        assert(game_exists == true, "game doesn't exist!");

        // Check if this game hasn't ended yet
        assert(game_to_move.winner == N(none), "the game has ended!");
        // Check if this game belongs to the message sender
        assert(m.by == game_to_move.host || m.by == game_to_move.challenger, "this is not your game!");
        // Check if this is the  message sender's turn
        assert(m.by == game_to_move.turn, "it's not your turn yet!");

        // Check if user makes a valid movement
        assert(is_valid_movement(m.mvt, game_to_move), "not a valid movement!");

        // Fill the cell, 1 for host, 2 for challenger
        bool is_movement_by_host = m.by == game_to_move.host;
        if (is_movement_by_host) {
        game_to_move.board[m.mvt.row * 3 + m.mvt.column] = 1;
        game_to_move.turn = game_to_move.challenger;
        } else {
        game_to_move.board[m.mvt.row * 3 + m.mvt.column] = 2;
        game_to_move.turn = game_to_move.host;
        }
        // Update winner
        game_to_move.winner = get_winner(game_to_move);
        Games::update(game_to_move, game_to_move.host);
    }
    ...
}

驗證操作

驗證游戲的操作意思是每一步都需要落在游戲板上的一個空格子里:

namespace tic_tac_toe {
    ...
        bool is_empty_cell(const uint8_t& cell) {
            return cell == 0;
        }
        bool is_valid_movement(const movement& mvt, const game& game_for_movement) {
            uint32_t movement_location = mvt.row * 3 + mvt.column;
            bool is_valid = movement_location < game_for_movement.board_len && is_empty_cell(game_for_movement.board[movement_location]);
            return is_valid;
        }
    ...
}

判斷贏家

第一個把自己的三個標記在橫向,縱向或對角線連線的玩家獲勝。

namespace tic_tac_toe {
    ...
    account_name get_winner(const game& current_game) {
        if((current_game.board[0] == current_game.board[4] && current_game.board[4] == current_game.board[8]) ||
        (current_game.board[1] == current_game.board[4] && current_game.board[4] == current_game.board[7]) ||
        (current_game.board[2] == current_game.board[4] && current_game.board[4] == current_game.board[6]) ||
        (current_game.board[3] == current_game.board[4] && current_game.board[4] == current_game.board[5])) {
            //  - | - | x    x | - | -    - | - | -    - | x | -
            //  - | x | -    - | x | -    x | x | x    - | x | -
            //  x | - | -    - | - | x    - | - | -    - | x | -
            if (current_game.board[4] == 1) {
                return current_game.host;
            } else if (current_game.board[4] == 2) {
                return current_game.challenger;
            }
        } else if ((current_game.board[0] == current_game.board[1] && current_game.board[1] == current_game.board[2]) ||
                (current_game.board[0] == current_game.board[3] && current_game.board[3] == current_game.board[6])) {
            //  x | x | x       x | - | -
            //  - | - | -       x | - | -
            //  - | - | -       x | - | -
            if (current_game.board[0] == 1) {
                return current_game.host;
            } else if (current_game.board[0] == 2) {
                return current_game.challenger;
            }
        } else if ((current_game.board[2] == current_game.board[5] && current_game.board[5] == current_game.board[8]) ||
                (current_game.board[6] == current_game.board[7] && current_game.board[7] == current_game.board[8])) {
            //  - | - | -       - | - | x
            //  - | - | -       - | - | x
            //  x | x | x       - | - | x
            if (current_game.board[8] == 1) {
                return current_game.host;
            } else if (current_game.board[8] == 2) {
                return current_game.challenger;
            }
        } else {
            bool is_board_full = true;
            for (uint8_t i = 0; i < current_game.board_len; i++) {
                if (is_empty_cell(current_game.board[i])) {
                    is_board_full = false;
                    break;
                }
            }
            if (is_board_full) {
                return N(draw);
            }
        }
        return N(none);
    }
    ...
}

您可以在 這里 找到tic_tac_toe.cpp的完整代碼

創(chuàng)建 ABI

有了Abi (即 Application Binary Interface),合約才能理解您所發(fā)的二進制信息。打開tic_tac_toe.abi并定義如下框架代碼:

{
  "structs": [{
      "name": "...",
      "base": "...",
      "fields": { ... }
  }, ...],
  "actions": [{
      "action_name": "...",
      "type": "..."
  }, ...],
  "tables": [{
      "table_name": "...",
      "type": "...",
      "key_names" : [...],
      "key_types" : [...]
  }, ...]
  • struct: 合約中action/ table所用到的數據結構列表
  • actions: 合約中可用的actions
  • tables: 合約中可用的表

表 ABI

在tic_tac_toe.hpp中,我們創(chuàng)造了一個叫game的single index i64的表。它保存了game 結構并使用challenger作為key(數據類型是account_name)。因此,abi文件是:

{
    ...
    "structs": [{
      "name": "game",
      "base": "",
      "fields": {
        "challenger": "account_name",
        "host": "account_name",
        "turn": "account_name",
        "winner": "account_name",
        "board": "uint8[]"
      }
    }],
    "tables": [{
            "table_name": "games",
            "type": "game",
            "index_type": "i64",
            "key_names" : ["challenger"],
            "key_types" : ["account_name"]
        }
    ]
    ...
}

Actions ABI

對actions來說,我們在actions里定義actions,在structs定義actions的數據結構。

{
    ...
    "structs": [{
      "name": "create",
      "base": "",
      "fields": {
        "challenger": "account_name",
        "host": "account_name"
      }
    },{
      "name": "restart",
      "base": "",
      "fields": {
        "challenger": "account_name",
        "host": "account_name",
        "by": "account_name"
      }
    },{
      "name": "close",
      "base": "",
      "fields": {
        "challenger": "account_name",
        "host": "account_name"
      }
    },{
      "name": "movement",
      "base": "",
      "fields": {
        "row": "uint32",
        "column": "uint32"
      }
    },{
      "name": "move",
      "base": "",
      "fields": {
        "challenger": "account_name",
        "host": "account_name",
        "by": "account_name",
        "movement": "movement"
      }
    }],
  "actions": [{
      "action_name": "create",
      "type": "create"
    },{
      "action_name": "restart",
      "type": "restart"
    },{
      "action_name": "close",
      "type": "close"
    },{
      "action_name": "move",
      "type": "move"
    }
  ]
    ...
}

部署!

現在所有文件(tic_tac_toe.hpp, tic_tac_toe.cpp, tic_tac_toe.abi)都完成了??梢圆渴鹆?

$ eosc set contract tic.tac.toe tic_tac_toe.wast tic_tac_toe.abi

注意您的錢包需要是解鎖的,而tic.tac.toe密鑰已導入。如果您要把該合約上傳到其他賬戶,請用您的賬戶名替換tic.tac.toe并且確保您的錢包里有改賬戶的密鑰。

開玩!

部署并且 transaction確認后,合約就在您的區(qū)塊鏈上生效了。您現在就可以玩了。

新建

$ eosc push message tic.tac.toe create '{"challenger":"inita", "host":"initb"}' -S initb -S tic.tac.toe -p initb@active 

移動

$ eosc push message tic.tac.toe move '{"challenger":"inita", "host":"initb", "by":"initb", "movement":{"row":0, "column":0} }' -S initb -S tic.tac.toe -p initb@active 
$ eosc push message tic.tac.toe move '{"challenger":"inita", "host":"initb", "by":"inita", "movement":{"row":1, "column":1} }' -S initb -S tic.tac.toe -p inita@active 

重啟

$ eosc push message tic.tac.toe restart '{"challenger":"inita", "host":"initb", "by":"initb"}' -S initb -S tic.tac.toe -p initb@active 

關閉

$ eosc push message tic.tac.toe close '{"challenger":"inita", "host":"initb"}' -S initb -S tic.tac.toe -p initb@active

查看游戲狀態(tài)

$ eosc get table initb tic.tac.toe games
{
  "rows": [{
      "challenger": "inita",
      "host": "initb",
      "turn": "inita",
      "winner": "none",
      "board": [
        1,
        0,
        0,
        0,
        2,
        0,
        0,
        0,
        0
      ]
    }
  ],
  "more": false
}
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • 1 EOS智能合約的介紹1.1 所需背景知識1.2 EOS智能合約基礎知識1.3 技術局限性 2 智能合約文件2....
    cenkai88閱讀 30,944評論 5 28
  • 草案:2017 年 6 月 26 日 (@dayzh (https://steemit.com/@dayzh)) ...
    區(qū)塊鏈生存指南閱讀 2,532評論 0 4
  • 今天偶然間在58看到了剽悍一只貓的文章,里面有提到簡書,突然想起很久以前就想下載這個軟件,但是腦子里被別的東西擠的...
    珊小珊1992閱讀 439評論 3 1
  • 1 我現在養(yǎng)成了與女兒每天交談的習慣。 今天女兒給我講了一個發(fā)生在他的班里的小事情。 她說,她的一個同學中午吃飯的...
    唯川閱讀 623評論 4 6
  • 突然 上方的日期變成十一月了 不習慣的同時 竟也感慨 我撐過了九月 也這樣不知不覺地錯過了十月 上完高數課 去了一...
    林有樸蔌閱讀 267評論 0 0

友情鏈接更多精彩內容