仮想通貨
ブロックチェーン
EOSIO

EOSIOでスマートコントラクトを実装してみる

EOSIOの概要と準備編はこちら:https://qiita.com/YukiSekiguchi/items/623359857269aff74586

スマートコントラクトの実装

ようやくスマートコントラクトの実装に移る。

2.1 Hello World!

まずはhelloディレクトリを作成。

cd <ローカルの作業用ディレクトリの絶対パス>
mkdir hello
cd hello

続いて、hello.cppを実装する。

#include <eosiolib/eosio.hpp>
#include <eosiolib/print.hpp>

using namespace eosio;

class hello : public contract {
  public:
      using contract::contract;

      [[eosio::action]]
      void hi( account_name user ) {
         print( "Hello, ", name{user} );
      }
};

EOSIO_ABI( hello, (hi) )
  • 1-2行目:必要なライブラリをインクルード
  • 4行目:eosio名前空間のものを修飾せずに使えるようにする
  • 6行目:contractクラスを継承したhelloクラスの宣言
  • 7行目:アクセス修飾子public
  • 8行目:contract::contract名前空間のものを修飾せずに使えるように
  • 10行目:コンパイラがABIを作成するための属性
  • 11行目:account_name型の変数userを引数とするメソッドhiの定義
  • 12行目:アカウント名をname{user}で取得してprintする
  • 16行目:helloというコントラクトを実行するためのマクロ(?)

これをコンパイルする。

eosio-cpp -o hello.wasm hello.cpp --abigen

これでwasm形式のバイナリファイルと、hello.abiというjson形式のファイルが出来上がる(この辺はイーサリアム同様)。
hello.abiの中身はこんな感じ。

$ cat hello.abi
{
    "____comment": "This file was generated with eosio-abigen. DO NOT EDIT Mon Oct  1 00:06:48 2018",
    "version": "eosio::abi/1.0",
    "structs": [
        {
            "name": "hi",
            "base": "",
            "fields": [
                {
                    "name": "user",
                    "type": "name"
                }
            ]
        }
    ],
    "types": [],
    "actions": [
        {
            "name": "hi",
            "type": "hi",
            "ricardian_contract": ""
        }
    ],
    "tables": [],
    "ricardian_clauses": [],
    "abi_extensions": []
}

--volumeオプションをつけてdockerコマンドを実行したため、うまくいけばコンパイルしたファイル群はコンテナ側に入っているはず。ちゃんと入っていない場合はdocker cpで運ぶ。
ブロックチェーン上で他のユーザーがアクセスできるようにするため、コントラクト用にアカウントを作成する。

cleos create account eosio hello YOUR_PUBLIC_KEY -p eosio@active

あとはこのアカウントでブロックチェーン上にコントラクトをbroadcastする

cleos set contract hello <コンテナの、コンパイルしたファイルが入っているパス> -p hello@active

これでブロックチェーン上の誰でもこのコントラクトを利用できるようになる。

Reading WASM from /home/hello/hello.wasm...
Publishing contract...
executed transaction: 6ca2cb39d7a23c95cb678b30ae9ffe9cc76abf8d0e32bca2f68a258fc0ced56f  1520 bytes  449 us
#         eosio <= eosio::setcode               {"account":"hello","vmtype":0,"vmversion":0,"code":"0061736d0100000001320a60027f7e0060027f7f00600001...
#         eosio <= eosio::setabi                {"account":"hello","abi":"0e656f73696f3a3a6162692f312e30000102686900010475736572046e616d650100000000...
warning: transaction executed locally, but may not be confirmed by the network yet    ]

早速このコントラクトを使ってみる。

cleos push action hello hi '["bob"]' -p bob@active

これはhelloというアカウントのhiコントラクトを '["bob"]'というjson形式の引数で、bobというアカウントがactiveというパーミッションレベルで実行するものである。
これがうまくいくと、

executed transaction: 32913c03fe13777654f33f4a6e622dd4d1119a8122d509f01204d179e7e3a33d  104 bytes  315 us
#         hello <= hello::hi                    {"user":"bob"}
>> Hello, bob
warning: transaction executed locally, but may not be confirmed by the network yet    ]

となる。docker logs --tail 100 eosioでコンテナのログを表示すると、

2018-09-30T15:47:09.002 thread-0   producer_plugin.cpp:1419      produce_block        ] Produced block 0001729f34805d46... #94879 @ 2018-09-30T15:47:09.000 signed by eosio [trxs: 0, lib: 94878, confirmed: 0]
2018-09-30T15:47:09.099 thread-0   apply_context.cpp:28          print_debug          ]
[(hello,hi)->hello]: CONSOLE OUTPUT BEGIN =====================
Hello, bob
[(hello,hi)->hello]: CONSOLE OUTPUT END   =====================

となっていてコントラクトを実行したブロックが追加されたことがわかる。
コードからして当然だが、alicebobhiすることができる。

$cleos push action hello hi '["bob"]' -p alice@active
executed transaction: f62c114ea4e2eabe0db9924b5146a2486a7fb9db0733e2845dcdb7cfa4f5a0f4  104 bytes  294 us
#         hello <= hello::hi                    {"user":"bob"}
>> Hello, bob
warning: transaction executed locally, but may not be confirmed by the network yet    ]

コードを変えることで、bobしかbobhiできないようにする。
具体的には

void hi( account_name user ) {
   require_auth( user );
   print( "Hello, ", name{user} );
}

とする。require_authは、コントラクトを実行したユーザーが引数であるuserであるかどうかを確かめるメソッドである。
これを再度コンパイル・broadcastし直す。

eosio-cpp -o hello.wasm hello.cpp --abigen
cleos set contract hello <コンテナの、コンパイルしたファイルが入っているパス> -p hello@active

ちなみにwasmが更新されていない状態でcleos set contract...し直すと以下のようなエラーが出る

$ cleos set contract hello /home/hello -p hello@owner
Reading WASM from /home/hello/hello.wasm...
Publishing contract...
Error 3160008: Contract is already running this version of code

今度はalicebobhiしようとすると

$cleos push action hello hi '["bob"]' -p alice@active
Error 3090004: Missing required authority
Ensure that you have the related authority inside your transaction!;
If you are currently using 'cleos push action' command, try to add the relevant authority using -p option.

というエラーが出る。一方でalicealicehiしてみると

$ cleos push action hello hi '["alice"]' -p alice@active
executed transaction: 887d64024e1d9254560df4243ff127308f31fd86f8c6838728bf195d43167e07  104 bytes  481 us
#         hello <= hello::hi                    {"user":"alice"}
>> Hello, alice
warning: transaction executed locally, but may not be confirmed by the network yet    ]

となる。
超簡単なスマートコントラクトが実装できた。次回は独自の仮想通貨トークンを発行してやりとりしてみる。