Substrate & Ink Part 2 ~テストとデプロイ~

Yuya Sugano
14 min readJul 13, 2019

ブロックチェーンの開発フレームワークであるSubstrateによってスクラッチから各コンポーネントを作成することなく複数チェーンを接続するブロックチェーンが作成できるようになりました(Polkadotネットワークのパラチェーンとして)。Ink(ink!)はSubstrate上でスマートコントラクトを書くための『eDSL』です。この記事ではInkを使用してNFT(Non-Fungible Token)を実装してみたいと思います。Part 1の続きです。

Substrate & Ink

Ink(ink!)はParityが提供するSubstrateベースのブロックチェーンでスマートコントラクトを記述する『eDSL』です。[1]

Ink (or ink! as the name is commonly termed in documentation) is Parity’s solution to writing smart contracts for a Substrate based blockchain.

または

Ink is an eDSL to write WebAssembly based smart contracts using the Rust programming language.

前回の記事ではInkの導入とInkを使用したNFT(Non-Fungible Token)のスマートコントラクトを記述しました。[2]

今回はInkによるテストの記述とデプロイおよび Polkadot JS App を使用したアプリケーションの動作確認を行ってみたいと思います。

  • Inkを使用したテストの記述
  • Substrateブロックチェーン上でのスマートコントラクトのビルド
  • Substrate devチェーンへのWebAssemblyのデプロイ
  • Polkadot JS App を使用したアプリケーションの動作確認

テスト

Inkによるスマートコントラクトのテストはチェーンにコントラクトをデプロイしてからだけでなく、 test モジュールを利用してオフチェーンに行うこともできます。

テスト自体を src/lib.rs の下部に testsとして記述していきます。ここでは use super::*; を使用して上位のモジュールであるスマートコントラクト自体を呼び出しています。

// test function boilerplate#[cfg(all(test, feature = "test-env"))]
mod tests {

use super::*;
use std::convert::TryFrom;
#[test]
fn it_works() {
// test function...
}
}

TryFrom トレイトを読み込み try_from() メソッドでテストに使用するアカウントを用意します。

The TryFrom trait implements simple and safe type conversions that may fail in a controlled way under some circumstances.

テスト句内ではまずコントラクトをデプロイして、100個のNFTトークンを作成します。 deploy_mock はコンストラクタを呼び出してコンストラクタの引数を渡します。次に try_from をAccountIdに実装して32個の0x0からaliceのアカウントを作成します。同様にbob、charlie、daveのアカウントを作成します。

#[test]
fn it_works() {
// initialise a contract instance to test
let mut _nftoken = NFToken::deploy_mock(100);
// try to obtain alice's account
let alice = AccountId::try_from([0x0; 32]).unwrap();
...
}

assert_eq!をアサーション文として使い、テストケースとして以下をカバーしています。

  • トークン発行量が100である
// initial minted token amount
let total_minted = _nftoken.total_minted();
assert_eq!(total_minted, 100);
  • aliceからbobへの transfer
// transferring token_id from alice to bob
_nftoken.transfer(bob, 1);
let alice_balance = _nftoken.balance_of(alice);
let mut bob_balance = _nftoken.balance_of(bob);
assert_eq!(alice_balance, 99);
assert_eq!(bob_balance, 1);
  • aliceがトークン2をcharlieに対して approval
// approve charlie to send token_id 2 from alice's account
_nftoken.approval(charlie, 2, true);
assert_eq!(_nftoken.is_approved(2, charlie), true);
  • aliceがトークン2をdaveに対して approval (上のケースの上書き)
// overwrite charlie's approval with dave's approval
_nftoken.approval(dave, 2, true);
assert_eq!(_nftoken.is_approved(2, dave), true);
  • daveに対するaliceのトークン2の approval を取り消す
// remove dave from approvals
_nftoken.approval(dave, 2, false);
assert_eq!(_nftoken.is_approved(2, dave), false);
  • aliceから transfer_from を呼び出してトークン3をbobへ送付
// transfer_from function: caller is token owner
_nftoken.approval(charlie, 3, true);
assert_eq!(_nftoken.is_approved(3, charlie), true);
_nftoken.transfer_from(bob, 3);
bob_balance = _nftoken.balance_of(bob);
assert_eq!(bob_balance, 2);

以下のコマンドでテストを実行してください。

$ cargo test --features test-env -- --nocapture

--features test-envCargo.toml で設定されている通りInkの環境のみでテストするように指定します。

[features]
default = []
test-env = [
"ink_core/test-env",
"ink_model/test-env",
"ink_lang/test-env",
]

generate-api-description = [
"ink_lang/generate-api-description"
]

tests::it_works がパスしたという1行のみですがテストが成功したことが分かります。

cargo test — features test-env — — nocapture

コンパイル

コードをWebAssembly(Wasm)にコンパイルします。 build.sh でコンパイルしますが、cargo buildに +nightly キーワードを追加してnightlyリリースでコンパイルするように指定してください。

build.sh を以下のように編集します。

#!/bin/bash
set -e
PROJNAME=nftoken# cargo clean
# rm Cargo.lock
CARGO_INCREMENTAL=0 &&
cargo +nightly build --release --features generate-api-description --target=wasm32-unknown-unknown --verbose
wasm2wat -o target/$PROJNAME.wat target/wasm32-unknown-unknown/release/$PROJNAME.wasm
cat target/$PROJNAME.wat | sed "s/(import \"env\" \"memory\" (memory (;0;) 2))/(import \"env\" \"memory\" (memory (;0;) 2 16))/" > target/$PROJNAME-fixed.wat
wat2wasm -o target/$PROJNAME.wasm target/$PROJNAME-fixed.wat
wasm-prune --exports call,deploy target/$PROJNAME.wasm target/$PROJNAME-pruned.wasm
$ ./build.sh
...
Finished release [optimized] target(s) in 3m 35s

コンパイルするとWebAssemblyランタイムである .wasm ファイルが作成されます。以下のファイルがコンパイルで作成されていることを確認してください。 target ディレクトリ以下を探すと見つかります。

  • nftoken.wasm: .wasm ファイルでSubstrateチェーンへデプロイします( nfttoken-pruned.wasm でも結構です)
  • NFToken.json: JSONで記載されたコントラクトのABI(Application Binary Interface)ファイルです、Ethereumでも同じようなJSONのABIのファイルがあります

Ethereum 2.0ではeWasmというWebAssemblyのサブセットに依存しており、Substrateもこの標準に準拠しています。WebAssemblyはWebに限定されたランタイムではなく、今後数年でさらに多くの機能がリリースされる予定です。

コンパイルが完了したのでローカルでSubstrateノードを起動します。

$ substrate --version
substrate 1.0.0-3ec6247-x86_64-linux-gnu
$ substrate --dev

デプロイ

Substrate上のコントラクトはチェーン上へのデプロイとインスタンス化の2ステップに分かれています。開発者がWebAssemblyのコードをチェーン上へデプロイしておけば、同じ機能のNFTトークンを誰でもインスタンス化して使用することができます。

  • チェーンへのコードのアップロード
  • コントラクトのインスタンス化

上記の作業は Polkadot JS を使用して行います。[5]

https://polkadot.js.org/apps/ へアクセスしてローカルノードと接続することもコードをクローンしてサーバ上で動作させることもどちらも可能です。サーバ上では yarn run dev で実行可能です。

$ yarn
yarn install v1.13.0
[1/4] Resolving packages...
success Already up-to-date.
Done in 2.42s.
$ yarn run dev
yarn run dev

substrate --dev を動かしている環境を動作させている Polkadot JS 上で設定してください。

https://polkadot.js.org/apps/#/settings

次にサイドバーのContractsを選択し、CodeタブからWebAssemblyのコードをアップロードしてください。

WebAssembly code upload !!

Deployをクリックする前に以下の3つの項目を確認してください。

  • compiled contract WASMへnftoken.wasm を配置している( nftoken-pruned.wasm でも結構です)
  • contract ABIへNFToken.json を配置している
  • maximum gas allowedを500,000に設定してください

トランザクションが発生しWASMがデプロイされます。

正しくデプロイできるとInstanceとCallのタブが有効化されます。機能をテストするためにはインスタンス化する必要があります。code for this contractを確認して先ほどデプロイした nftoken.wasm が選択されていることを確認してください( nftoken-pruned.wasm でも結構です)。

またinitValue: u64に発行したいNFTトークンの数を設定します。ここでは100種類としています。

また以下の2点を確認してください。

  • endowment を1000に設定してください [6]
  • maximum gas allowed を500,000に設定してください

Instantiateをクリックしてインスタンス化します。コントラクトがインスタンス化されると各ファンクションを呼べるようになります。

Callのタブへ移動してください。contract to useを選択し、message to sendに呼び出したい関数を選びます。これで対象の関数が動作するかデプロイしたチェーン上でテストすることができます。あとはテストしたい関数を選択して動作を確認してください。

Confirm your functions are callable

以上です。

まとめ

  • Ink(ink!)はSubstrateベースのブロックチェーンでスマートコントラクトを記述するための『eDSL』である
  • EthereumのSolidityによるスマートコントラクトとコードの構造が似いるため実装しやすい
  • テストはチェーンにコントラクトをデプロイするオンチェーンだけでなく、 test モジュールを利用してオフチェーンに実施することも可能である
  • Polkadot JS を利用してWASMのアップロードやコントラクトのインスタンス化を行うことができる

--

--

Yuya Sugano

Cloud Architect and Blockchain Enthusiast, techflare.blog, Vinyl DJ, Backpacker. ブロックチェーン・クラウド(AWS/Azure)関連の記事をパブリッシュ。バックパッカーとしてユーラシア大陸を陸路横断するなど旅が趣味。