はじめに
本記事ではEOS Developer Portalで紹介されているeosio.tokenコントラクトを用いて、コントラクトのデプロイや実行をおこない、より実用的なコントラクトの構造や実行方法を学びます。
本記事は、前回の最短でHello World!で開発環境を構築済みであることを前提としています。
本記事は、過去にコンセンサス・ベイスが主宰していたオンラインサロンの記事です。記事は2017年~2018年にかけて執筆されたため、一部は、既に古くなっている可能性があります。あらかじめご了承ください。
関連する記事
EOSIO Developer Portal 解説 最短でHello World!
EOSIO Developer Portal 解説 独自トークンの発行 第1回
EOSIO Developer Portal 解説 独自トークンの発行 第2回
EOSIO Developer Portal 解説 独自トークンの発行 第3回
EOSIO Developer Portal 解説 データの永続化とインラインアクション 第1回
EOSIO Developer Portal 解説 データの永続化とインラインアクション 第2回
EOSIO Developer Portal 解説 データの永続化とインラインアクション 第3回
eosio.tokenコントラクト
eosio.tokenコントラクトは、EOSIO公式のサンプルコードとして提供されている、EOS上でさまざまな種類のトークンの作成や管理をおこなうためのコントラクトです。
さっそく、EOSIO公式サンプルコードをダウンロードし、eosio.tokenの中身を確認してみましょう。
コードのダウンロード
下記コマンドを実行して、EOSIOのサンプルコードをダウンロードし、eosio.tokenのファイル構成を確認します。
CONTRACTS_DIRは、環境構築の際にDockerのボリュームに指定したcontractsフォルダのフルパスを指定します。
コマンド1. eosio.tokenコントラクトのダウンロード
$ cd CONTRACTS_DIR
$ git clone https://github.com/EOSIO/eosio.contracts --branch v1.4.0 --single-branch
$ cd eosio.contracts/eosio.token
# treeコマンドがない場合は sudo apt install tree でインストール
# macOSなら brew install tree でもインストール可
$ tree
.
├── CMakeLists.txt
├── README.md
├── include
│ └── eosio.token
│ └── eosio.token.hpp
└── src
└── eosio.token.cpp
※EOSのリポジトリをcloneする際、”You are in ‘detached HEAD’ state~”というメッセージが出ることがありますが、本記事ではソースの確認と実行だけなのでそのまま続行して大丈夫です。
eosio.tokenコントラクトは、主にeosio.token.hppとeosio.token.cppで構成されています。eosio.token.hppには、クラスやアクション、テーブルの定義などが記載されており、eosio.token.cppには、具体的なロジックの実装が記載されています。
コントラクトの構造
eosio.tokenコントラクトの構造を理解するために、まずはeosio.token.hppの中身を確認してみましょう。コード1に、eosio.token.hppのアクション定義を抜粋したコードを示します。
なお、アクションとは、EOSブロックチェーンの状態を変更するための機能であり、Ethereumコントラクトにおけるファンクションに相当します。
コード1. eosio.token.hppのアクション定義抜粋
class [[eosio::contract("eosio.token")]] token : public contract {
public:
using contract::contract;
[[eosio::action]]
void create( name issuer,
asset maximum_supply);
[[eosio::action]]
void issue( name to, asset quantity, string memo );
[[eosio::action]]
void retire( asset quantity, string memo );
[[eosio::action]]
void transfer( name from,
name to,
asset quantity,
string memo );
[[eosio::action]]
void open( name owner, const symbol& symbol, name ram_payer );
[[eosio::action]]
void close( name owner, const symbol& symbol );
};
eosio.token.hppの記述をみると、コントラクトには、create, issue, retire, transfer, open, closeという6つのアクションが定義されていることが分かります。
これらのアクションの概要をテーブル1にまとめます。
テーブル1. eosio.tokenコントラクトのアクション一覧
アクション | 概要 |
create | issuer(発行者)と最大供給量を指定して、新しいトークンの種類を作成します。 |
issue | 新しいトークンを発行し、トークンの供給量を増加させます。 |
retire | 流通しているトークンの一部を回収(還収)し、トークンの供給量を減少させます。 |
transfer | 指定した量のトークンの所有者を変更します。 |
open | 指定したトークンの種類に対応する残高口座を開設します。 |
close | 指定したトークンの種類に対応する残高口座を閉鎖します。 |
第1回の記事では、createアクションを用いて新しいトークンの種類を作成し、第2回で他のアクションを試します。
eosio.tokenコントラクトは、ひとつのコントラクトで複数の種類のトークンを作成、管理します。そのためのデータ構造として、トークンごとの定義や供給量を管理するstatテーブルと、アカウントごとのトークン残高を管理するaccountsテーブルをもちます。
eosio.token.hppファイルにおけるstatテーブルとaccountsテーブルの定義の抜粋をコード2に示します。
コード2. eosio.token.hppのテーブル定義抜粋
private:
struct [[eosio::table]] account {
asset balance;
uint64_t primary_key()const { return balance.symbol.code().raw(); }
};
struct [[eosio::table]] currency_stats {
asset supply;
asset max_supply;
name issuer;
uint64_t primary_key()const { return supply.symbol.code().raw(); }
};
typedef eosio::multi_index< "accounts"_n, account > accounts;
typedef eosio::multi_index< "stat"_n, currency_stats > stats;
それでは、実際にコントラクトを実行して、動作を確認していきましょう。
コントラクトのデプロイ
コントラクトをデプロイする前に、開発環境が正しく構築されているかを確認します。
まず、コントラクトのコンパイルをおこなうために、eosio.cdt (EOSIO Contract Development Toolkit) が必要です。下記コマンドを実行し、CDTのバージョン1.3.2がインストールされていることを確認します。
コマンド2. CDTのバージョン確認
$ eosio-cpp --version
eosio-cpp version 1.3.2
また、eosioのDockerコンテナが正しく起動しているかを確認します。
コマンド3. eosioコンテナの起動と確認例
$ docker start eosio
eosio
$ docker ps
CONTAINER ID IMAGE COMMAND ...
50f1aabd7a1c eosio/eos:v1.4.2 "/bin/bash -c 'keosd…" ...
最後に、必要なウォレットとアカウントが作成されているかを確認します。ウォレットを作成した際に控えたパスワードを用いて、ウォレットをアンロックし、ウォレットに登録されている公開鍵と秘密鍵のペアを確認してみましょう。
なお、前回の記事で起動したコンテナをすでに停止・削除している場合、dockerコンテナを起動した後、改めてwalletのオープンからアンロック、鍵の設定、テストアカウントの作成までの手順を行ってください。
コマンド4. ウォレットのアンロック
# defaultウォレットを開く
$ cleos wallet open
Opened: default
# ウォレットの一覧を確認
$ cleos wallet list
Wallets:
[
"default"
]
# パスワードを用いてウォレットをアンロック
$ cleos wallet unlock
password:
# もう一度ウォレットの一覧を確認
$ cleos wallet list
Wallets:
[
"default *" # * がついていることで、ウォレットがアンロックされている
]
# ウォレットの公開鍵と秘密鍵を確認
$ cleos wallet private_keys
password:
[[
"YOUR_PUBLIC_KEY",
"YOUR_PRIVATE_KEY"
],[
"EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV",
"5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3"
]
]
YOUR_PUBLIC/PRIVATE_KEYの部分は、環境構築の手順で各々が作成した公開鍵・秘密鍵が表示されているはずですので、各々異なった結果となります。EOS6MRyA…から始まる公開鍵は、eosioのDockerにあらかじめセットされた鍵をインポートしましたので、同じ鍵が表示されるはずです。
YOUR_PUBLIC_KEYに対応するアカウントも確認しておきましょう。eosio.tokenコントラクトの実行でも、aliceとbobのアカウントを用いるため、両者のアカウントが作成されていることを確認してください。
コマンド5. 公開鍵に対応するアカウントの確認
$ cleos get accounts YOUR_PUBLIC_KEY
{
"account_names": [
"alice",
"bob",
"hello"
]
}
上記の結果をみて分かるとおり、EOSではアカウントと公開鍵/秘密鍵が1:1には紐付かず、一つの鍵ペアに複数のアカウントが対応することが多くあります。また、今回はまだ意識していませんが、基本的に一つのアカウントに複数の鍵ペアが紐付き、鍵ペアごとに異なる権限を付与することができます。
また、EOSでは一つのコントラクトに対して一つのアカウントが紐付きます。通常は、コントラクトと同じ名前のアカウントを作成します。
今回も、eosio.tokenコントラクトに紐付く、eosio.tokenアカウントを作成しておきましょう。公開鍵は、Dockerであらかじめ用意されている開発用の鍵を用います。
公開鍵は$ docker exec -it eosio bashでコンテナに入って、直下のconfig.iniをcatコマンドなどで表示すれば見つけることができます。
また、EOS Developer Portalにも掲載されているので、こちらからも確認できます。(下記のstep2)
https://developers.eos.io/eosio-home/docs/token-contract
コマンド6. eosio.tokenアカウントの作成
$ cleos create account eosio eosio.token \
EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV
下記のコマンドで、eosio.tokenアカウントが作成されているのが確認できます。
$ cleos get accounts EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV
{
"account_names": [
"eosio.token"
]
}
アカウントの作成や次のコントラクトのデプロイには、ウォレットがアンロックされている必要があります。ウォレットは一定時間でロックされますので、ロックがかかってしまった場合は適宜アンロックをしてください。
コントラクトのデプロイ
環境構築の確認が終わったところで、コントラクトのデプロイを始めます。まず、CDTのeosio-cppを用いてコードをコンパイルします。
成功すると、eosio.token.abiと、eosio.token.wasmファイルが作成されます。
コマンド7. eosio.tokenコントラクトのコンパイル
$ eosio-cpp -I include -o eosio.token.wasm src/eosio.token.cpp --abigen
$ ls -l
CMakeLists.txt
README.md
eosio.token.abi
eosio.token.wasm
include
src
コンパイルが完了したら、コントラクトをブロックチェーンにデプロイします。
下記コマンド8におけるCONTRACTS_DIRは、コントラクトフォルダのフルパスを指定します。
eosio.tokenコントラクトのデプロイは、さきほど作成したeosio.tokenアカウントを用いて実行していることに注意してください(-p eosio.token@activeの部分)。
コマンド8. eosio.tokenコントラクトのデプロイ
$ cleos set contract eosio.token CONTRACTS_DIR/eosio.contracts/eosio.token --abi eosio.token.abi -p eosio.token@active
Reading WASM from ...
Publishing contract...
executed transaction: 69c68b1bd5d61a0cc146b11e89e11f02527f24e4b240731c4003ad1dc0c87c2c 9696 bytes 6290 us
# eosio <= eosio::setcode {"account":"eosio.token","vmtype":0,"vmversion":0,"code":"0061736d0100000001aa011c60037f7e7f0060047f...
# eosio <= eosio::setabi {"account":"eosio.token","abi":"0e656f73696f3a3a6162692f312e30000605636c6f73650002056f776e6572046e61...
warning: transaction executed locally, but may not be confirmed by the network yet ]
新規トークンの作成
それでは、デプロイしたeosio.tokenコントラクトを用いて、新規トークンを作成してみましょう。
新規トークンの作成には、createアクションを用います。
コード3に、eosio.token.cppから、createアクションの実装を抜粋します。
コード3. eosio.token.cppのcreateアクションの抜粋
void token::create( name issuer,
asset maximum_supply )
{
require_auth( _self );
auto sym = maximum_supply.symbol;
eosio_assert( sym.is_valid(), "invalid symbol name" );
eosio_assert( maximum_supply.is_valid(), "invalid supply");
eosio_assert( maximum_supply.amount > 0, "max-supply must be positive");
stats statstable( _self, sym.code().raw() );
auto existing = statstable.find( sym.code().raw() );
eosio_assert( existing == statstable.end(), "token with symbol already exists" );
statstable.emplace( _self, [&]( auto& s ) {
s.supply.symbol = maximum_supply.symbol;
s.max_supply = maximum_supply;
s.issuer = issuer;
});
}
createアクションは、引数にissuerとmaximum_supplyを取ります。issuerには、新規トークンの発行権限を持つアカウントを指定し、maximum_supplyには、トークンの最大供給量をトークンのシンボルとともに指定します。
シンボルは、1~7文字の大文字で表現された文字列です。
例えば、eosioユーザーをissuerにして、最大供給量1000000000.0000の「SYS」トークンを作成するコマンドは、コマンド9のとおりです。
コマンド9. 新規SYSトークンの作成例
$ cleos push action eosio.token create '[ "eosio", "1000000000.0000 SYS"]' -p eosio.token@active
executed transaction: 0e49a421f6e75f4c5e09dd738a02d3f51bd18a0cf31894f68d335cd70d9c0e12 120 bytes 1000 cycles
# eosio.token <= eosio.token::create {"issuer":"eosio","maximum_supply":"1000000000.0000 SYS"}
トークンの最大供給量には、小数点以下いくつまでを許容するかも定義可能です。小数点による分割を許容しない YEN トークンを作成するには、下記コマンド10のようにします。
コマンド10. 新規YENトークンの作成例
$ cleos push action eosio.token create '[ "eosio", "1000000000 YEN"]' -p eosio.token@active
executed transaction: 487d3838a96f7389029929fa41a8249c37f35216de0110a07a4c6cb129fc31d4 120 bytes 1407 us
# eosio.token <= eosio.token::create {"issuer":"eosio","maximum_supply":"1000000000 YEN"}
作成されたトークンの定義は、eosio.tokenコントラクトのstatテーブルに格納されています。下記のコマンド11に示した手順で、作成したトークンの定義を確認してみましょう。
コマンド11. 作成したトークンの確認
$ cleos get table eosio.token SYS stat
{
"rows": [{
"supply": "0 SYS",
"max_supply": "1000000000.0000 SYS",
"issuer": "eosio"
}
],
"more": false
}
$ cleos get table eosio.token YEN stat
{
"rows": [{
"supply": "0 YEN",
"max_supply": "1000000000 YEN",
"issuer": "eosio"
}
],
"more": false
}
指定したmax_supplyとissuerにより、正しくSYS、YENトークンが作成されていることがわかります。
ただし、両者ともsupplyは0のままです。これは、新規トークンの定義は作成されたものの、実体としてのトークンはまだ発行されていない状態だからです。第2回の記事にて、トークンの新規発行や送金、回収などのアクションを実行してみましょう。
参考文献:
- EOSIO Developer Portal – 2.2 Deploy, Issue and Transfer Tokens
https://developers.eos.io/eosio-home/docs/token-contract - B9lab Academy – Introduction to EOSIO for Developers
https://academy.b9lab.com/courses/course-v1:B9lab+EOSIO-FREE+2018-09/about - Understanding The eosio.token Contract
https://medium.com/coinmonks/understanding-the-eosio-token-contract-87466b9fdca9