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-env
は Cargo.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行のみですがテストが成功したことが分かります。
コンパイル
コードをWebAssembly(Wasm)にコンパイルします。 build.sh
でコンパイルしますが、cargo buildに +nightly
キーワードを追加してnightlyリリースでコンパイルするように指定してください。
build.sh
を以下のように編集します。
#!/bin/bash
set -ePROJNAME=nftoken# cargo clean
# rm Cargo.lockCARGO_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
substrate --dev
を動かしている環境を動作させている Polkadot JS 上で設定してください。
次にサイドバーのContractsを選択し、CodeタブからWebAssemblyのコードをアップロードしてください。
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に呼び出したい関数を選びます。これで対象の関数が動作するかデプロイしたチェーン上でテストすることができます。あとはテストしたい関数を選択して動作を確認してください。
以上です。
まとめ
- Ink(ink!)はSubstrateベースのブロックチェーンでスマートコントラクトを記述するための『eDSL』である
- EthereumのSolidityによるスマートコントラクトとコードの構造が似いるため実装しやすい
- テストはチェーンにコントラクトをデプロイするオンチェーンだけでなく、
test
モジュールを利用してオフチェーンに実施することも可能である - Polkadot JS を利用してWASMのアップロードやコントラクトのインスタンス化を行うことができる