EOSをDockerで動かしてトークンで遊んでみる

[一年以上前にEOSを試した時に簡単にコントラクトの実行についてメモした](https://diary.shuichi.tech/entry/2018/03/31/000555)がガラリと変わっているのでEOSのノードをDockerで動かしつつトークンをデプロイするところまでサクッと見ていく。

[EOSの公式ドキュメント](https://developers.eos.io/eosio-home/docs/getting-the-software)を参照するとMacOSやUbuntu、CentOSなどいくつかのOSにおけるインストール方法が出てくるが、Dockerを使った方がPCも汚れないし簡単に試すことができるので、Dockerを使ったEOSの立ち上げ方について説明する。Dockerを使っているのはあくまでノードの部分（nodeos）のみ。

＊ここでは`eosstudio`が公開しているDockerイメージを利用する。執筆時点での最新Verが1.8.3なのでそれを利用しているが、その時の最新版で書き換えて良い。

## EOSのノードのセットアップ

```bash
# dockerイメージを落とす
$ docker pull eostudio/eos:v1.8.3
v1.8.3: Pulling from eostudio/eos
5b7339215d1d: Already exists
14ca88e9f672: Already exists
a31c3b1caad4: Already exists
b054a26005b7: Already exists
a677768c3204: Pull complete
a743177c79c8: Pull complete
4dd68c5b6b2c: Pull complete
2a9ecc7eee10: Pull complete
Digest: sha256:725154d672654d09c86dcde6b4d2cbbec48c944b31ad4e765ec0903d63db2533
Status: Downloaded newer image for eostudio/eos:v1.8.3
docker.io/eostudio/eos:v1.8.3

# portは8888を解放。スマートコントラクトを配置するディレクトリは/path/to/contractsとしている。任意で良い。
$ docker run --name nodeos -dit -p 8888:8888 -v /path/to/contracts:/contracts eostudio/eos:v1.8.3
2b62951d170dacfc7d6e106bbfdd68cff419641b6a47ce63d6cf563b648a60b13

# dockerコンテナが動いてるのが確認できる。
$ docker ps -a
CONTAINER ID        IMAGE                 COMMAND             CREATED             STATUS              PORTS                    NAMES
2b62951d170d        eostudio/eos:v1.8.3   "/bin/bash"         20 seconds ago      Up 19 seconds       0.0.0.0:8888->8888/tcp   nodeos

# コンテナの中に入る。
$ docker exec -it nodeos bash
root@2b62951d170d:/#

# コンテナ内でnodeosのコマンドを実行する。
root@2b62951d170d:/# nodeos -e -p eosio --plugin eosio::producer_plugin --plugin eosio::history_plugin --plugin eosio::chain_api_plugin --plugin eosio::history_api_plugin --plugin eosio::http_plugin -d /mnt/dev/data --config-dir /mnt/dev/config --http-server-address=0.0.0.0:8888 --access-control-allow-origin=* --contracts-console --http-validate-host=false --max-transaction-time=1000
```

以下の画像のようにnodeosが動き出す。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/122365/59c0c8cc-224e-6aca-1f28-4b4d7739912b.png)

EOSのブロック情報が以下のように取得できれば成功。

```bash
$ curl http://localhost:8888/v1/chain/get_info
{"server_version":"7116e887","chain_id":"cf057bbfb72640471fd910bcb67639c22df9f92470936cddc1ade0e2f2e7dc4f","head_block_num":289,"last_irreversible_block_num":288,"last_irreversible_block_id":"0000012068f42c0c021775b7ea90535f3a5a0458d0e7ce345fc11b18ca6727b3","head_block_id":"0000012134461c28c52f92aff0dc20370d4bc5d21ddc9869538a6617267e5fca","head_block_time":"2019-09-20T08:54:33.000","head_block_producer":"eosio","virtual_block_cpu_limit":266624,"virtual_block_net_limit":1398582,"block_cpu_limit":199900,"block_net_limit":1048576,"server_version_string":"v1.8.3","fork_db_head_block_num":289,"fork_db_head_block_id":"0000012134461c28c52f92aff0dc20370d4bc5d21ddc9869538a6617267e5fca"}
```

## EOSのコマンドラインのセットアップ

[公式ページ](https://developers.eos.io/eosio-home/docs/installing-the-contract-development-toolkit)に詳しいインストール方法が書いてある。このツールはコントラクトのコンパイル等で利用される。

MacOSだとこんな感じ。

```bash
$ brew tap eosio/eosio.cdt
$ brew install eosio.cdt
```

Ubuntuだとこんな感じ。

```bash
$ wget https://github.com/EOSIO/eosio.cdt/releases/download/v1.6.1/eosio.cdt_1.6.1-1_amd64.deb
$ sudo apt install ./eosio.cdt_1.6.1-1_amd64.deb
```

## Walletのセットアップ

```bash
# コンテナの中に入った状態で始める。
root@2b62951d170d:/# cleos wallet create --to-console
Creating wallet: default
Save password to use in the future to unlock this wallet.
Without password imported keys will not be retrievable.
"PW5J1GMa6QzUGVAHeiJcBv6ct7nkmAj312xGaYUg7auybF2NvTwcp"

root@2b62951d170d:/# cleos wallet open
Opened: default

root@2b62951d170d:/# cleos wallet list
Wallets:
[
  "default"
]

# 上で発行したパスワード（PW5J1GMa6QzUGVAHeiJcBv6ct7nkmAj312xGaYUg7auybF2NvTwcp）を入力
root@2b62951d170d:/# cleos wallet unlock
password: Unlocked: default

# *印がunlock状態を意味する。
root@2b62951d170d:/# cleos wallet list
Wallets:
[
  "default *"
]

# 新しいキーペアを作成。
root@2b62951d170d:/# cleos wallet create_key
Created new private key with a public key of: "EOS7vURhtJepaqsRSAJVSpPA9bv7U3uyv8RotpRwxLvqL7s2JDnPW"

# eosioの秘密鍵（5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3）を入力する。
root@2b62951d170d:/# cleos wallet import
private key: imported private key for: EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV

# create_keyで作成したキーペアを元にbobというアカウントを作成する。
root@2b62951d170d:/# cleos create account eosio bob EOS7vURhtJepaqsRSAJVSpPA9bv7U3uyv8RotpRwxLvqL7s2JDnPW
executed transaction: d145f646aad4f50baa2c94a1ccabe996e8901d564dd5b66cff5339ca95d67103  200 bytes  231 us
#         eosio <= eosio::newaccount            {"creator":"eosio","name":"bob","owner":{"threshold":1,"keys":[{"key":"EOS7vURhtJepaqsRSAJVSpPA9bv7U...
warning: transaction executed locally, but may not be confirmed by the network yet         ]

# create_keyで作成したキーペアを元にaliceというアカウントを作成する。
root@2b62951d170d:/# cleos create account eosio alice EOS7vURhtJepaqsRSAJVSpPA9bv7U3uyv8RotpRwxLvqL7s2JDnPW
executed transaction: 710cfb8ca86df0e0aebb349378141d1f46c81333d1f228a5329f71fe01641306  200 bytes  219 us
#         eosio <= eosio::newaccount            {"creator":"eosio","name":"alice","owner":{"threshold":1,"keys":[{"key":"EOS7vURhtJepaqsRSAJVSpPA9bv...
warning: transaction executed locally, but may not be confirmed by the network yet         ]

# aliceのaccount情報はこんな感じ。
root@2b62951d170d:/# cleos get account alice
created: 2019-09-20T10:53:06.000
permissions:
     owner     1:    1 EOS7vURhtJepaqsRSAJVSpPA9bv7U3uyv8RotpRwxLvqL7s2JDnPW
        active     1:    1 EOS7vURhtJepaqsRSAJVSpPA9bv7U3uyv8RotpRwxLvqL7s2JDnPW
memory:
     quota:       unlimited  used:      2.66 KiB

net bandwidth:
     used:               unlimited
     available:          unlimited
     limit:              unlimited

cpu bandwidth:
     used:               unlimited
     available:          unlimited
     limit:              unlimited
```


## スマートコントラクトを試す

```bash
# /path/to/contractsの階層でhelloディレクトリを作る。
$ mkdir hello
$ cd hello
$ vim hello.cpp
```

hello.cppを以下のように編集する。実行すると、 `Hello, 名前`が返ってくるだけのいたってシンプルなEOSスマートコントラクトである。

```cpp

#include <eosio/eosio.hpp>

using namespace eosio;

class [[eosio::contract]] hello : public contract {
  public:
      using contract::contract;

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

このhello.cppをコンパイルする。`eosio-cpp`は先ほどインストールした`eosio.cdt`に含まれている。

```bash
$ eosio-cpp hello.cpp -o hello.wasm
```

再びdockerコンテナに戻る。もしwalletがロックされていたら、`cleos wallet unlock`で最初に発行されたパスワード（本稿ではPW5J1GMa6QzUGVAHeiJcBv6ct7nkmAj312xGaYUg7auybF2NvTwcp）を入力することで再びアンロックできる。

```bash
# コントラクト用にhelloというアカウントを作成する。
root@2b62951d170d:/# cleos create account eosio hello EOS7vURhtJepaqsRSAJVSpPA9bv7U3uyv8RotpRwxLvqL7s2JDnPW -p eosio@active
executed transaction: 93f33e70d3108b723bcdcf850e3f12e9f5944f230cadbeee8804d6a397086a1b  200 bytes  244 us
#         eosio <= eosio::newaccount            {"creator":"eosio","name":"hello","owner":{"threshold":1,"keys":[{"key":"EOS7vURhtJepaqsRSAJVSpPA9bv...

# helloアカウントにコントラクトをセットする。
root@2b62951d170d:/# cleos set contract hello contracts/hello -p hello@active
Reading WASM from /contracts/hello/hello.wasm...
Publishing contract...
executed transaction: 07acebc8a0492277db4581eddf7cd09b53cff6725e47a16624a18825488372ac  688 bytes  422 us
#         eosio <= eosio::setcode               {"account":"hello","vmtype":0,"vmversion":0,"code":"0061736d0100000001370b6000017f60027f7f0060037f7f...
#         eosio <= eosio::setabi                {"account":"hello","abi":"0e656f73696f3a3a6162692f312e31000102686900010475736572046e616d650100000000...

# bobアカウントから、コントラクトのメソッドhelloを実行してみる。
root@2b62951d170d:/# cleos push action hello hi '["bob"]' -p bob@active
executed transaction: f8df222edc812552dfaf6f4e72d39c9ed3300cb5ea72bec3988b077e346fa525  104 bytes  460 us
#         hello <= hello::hi                    {"user":"bob"}
>> Hello, bob

# aliceアカウントから、コントラクトのメソッドhelloを実行してみる。
root@2b62951d170d:/# cleos push action hello hi '["bob"]' -p alice@active
executed transaction: fb6ee5f4d6ddcea43ab825867213f743da0637a39946e7e9e82cf9f7072dd872  104 bytes  432 us
#         hello <= hello::hi                    {"user":"bob"}
>> Hello, bob
```

helloアクションが正常に実行されたのが確認できた。

## トークンをデプロイして送金してみる

helloが表示されるだけでは面白くないので、次は、公式で出ているサンプルトークンを試してみる。

```bash
# 最初のディレクトリに戻る。
$ cd /path/to/contracts
# cloneする。
$ git clone https://github.com/EOSIO/eosio.contracts --branch v1.7.0 --single-branch
# eosio.tokenディレクトリへ（eosio.tokenとはEthereumでいうところのERC20トークン）。
$ cd eosio.contracts/contracts/eosio.token
# コンパイルする。
$ eosio-cpp -I include -o eosio.token.wasm src/eosio.token.cpp --abigen
```

再びdockerコンテナに戻る。

```bash
# eosのアカウントからトークン用(eosio.token)のアカウントを作成する。
root@2b62951d170d:/# cleos create account eosio eosio.token EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV
executed transaction: 0dd3ce0bb9f9cd47869c0d123dc99c205ff318698b2cd4db58a0e80ba7dd1950  200 bytes  183 us
#         eosio <= eosio::newaccount            {"creator":"eosio","name":"eosio.token","owner":{"threshold":1,"keys":[{"key":"EOS6MRyAjQq8ud7hVNYcf...

# eos.tokenアカウントにデプロイする。
root@2b62951d170d:/# cleos set contract eosio.token contracts/eosio.contracts/contracts/eosio.token --abi eosio.token.abi -p eosio.token@active
Reading WASM from /contracts/eosio.contracts/contracts/eosio.token/eosio.token.wasm...
Publishing contract...
executed transaction: 32f940123f30c131f17d828a6e018ac3278725e1a3601e323e05aed8dee55764  6984 bytes  1198 us
#         eosio <= eosio::setcode               {"account":"eosio.token","vmtype":0,"vmversion":0,"code":"0061736d0100000001a0011b60000060017e006002...
#         eosio <= eosio::setabi                {"account":"eosio.token","abi":"0e656f73696f3a3a6162692f312e310008076163636f756e7400010762616c616e63...

# eosio.tokenのcreateアクションを実行する。aliceは 1000000000 SYS分の発行権を獲得する。'SYS'という決まりはなく'JPY'でも問題ない。
root@2b62951d170d:/# cleos push action eosio.token create '[ "alice", "1000000000.0000 SYS"]' -p eosio.token@active
executed transaction: 8c2fdcf0d38ce0220a324a1f92beb92ea416503316f0ccded26f7ad564a07553  120 bytes  1210 us
#   eosio.token <= eosio.token::create          {"issuer":"alice","maximum_supply":"1000000000.0000 SYS"}

# aliceは自分に対して 100 SYS 発行する。createアクションの時点ではaliceの残高は0のまま。
root@2b62951d170d:/# cleos push action eosio.token issue '[ "alice", "100.0000 SYS", "memo" ]' -p alice@active
executed transaction: c2e2717923df4bb07fe2b42e286a2a513342634380684e65a6f89a4684d19c71  128 bytes  2333 us
#   eosio.token <= eosio.token::issue           {"to":"alice","quantity":"100.0000 SYS","memo":"memo"}

# aliceは所有している 100 SYS のうち、 25 SYS をbobに送金する。
root@2b62951d170d:/# cleos push action eosio.token transfer '[ "alice", "bob", "25.0000 SYS", "m" ]' -p alice@active
executed transaction: d5d5c59ad6612aba2ddf52a9ef0b170d1bc8cf32f4e02caeb917f9081dd611b2  128 bytes  2751 us
#   eosio.token <= eosio.token::transfer        {"from":"alice","to":"bob","quantity":"25.0000 SYS","memo":"m"}
#         alice <= eosio.token::transfer        {"from":"alice","to":"bob","quantity":"25.0000 SYS","memo":"m"}
#           bob <= eosio.token::transfer        {"from":"alice","to":"bob","quantity":"25.0000 SYS","memo":"m"}

# bobの所持金を確認すると、 25 SYS になっている。
root@2b62951d170d:/# cleos get currency balance eosio.token bob SYS
25.0000 SYS

# aliceの所持金を確認すると、 75 SYS になっている。
root@2b62951d170d:/# cleos get currency balance eosio.token alice SYS
75.0000 SYS
```

これでEOSで標準のトークンを発行して送金してみるところまで確認できた。

dockerを閉じる時は、以下のようにすれば良い。

```bash
$ docker stop nodeos
$ docker rm nodeos
```

## 所感

- 2018年の2月頭にEOSを初めて触った時は、READMEも嘘ばっかりで地獄の様相を呈していたが、今はドキュメントもかなりまともになっていた。
- account名やaction名、eosio.tokenのシンボル名等の命名規則が厳しい。
- EOSはDPoSを採用しており、「DPoSは中央集権的なのでは」という議論もあるが、TPSを考慮しても現時点では現実的な技術なのではないかという感想。

## 参照

- [公式ページ](https://developers.eos.io/eosio-home/docs/token-contract)