Casperスマートコントラクトのベストプラクティス

本質的には、Casperプラットフォームはソフトウェアであり、一般的なソフトウェア開発のベストプラクティスが適用されます。ただ、特定の値や状況など、Casper Networkの開発を行っている際に考慮が必要です。例えば、グローバルステートにインストールされたスマートコントラクトは、ファイルシステムへのアクセスや外部のリソースへオープンな状態で繋ぐことはできません。

データの効率性

Casper上での開発時、効率のよいデータの使用方法におけるポリシーはチェーン上の計算コストが可能な限り低くなっていることを確認します。最終的には、必要となるTransaction数を最小にすることで、最終的なコストを劇的に減らすことができます。

スマートコントラクトを作成する際、明示的なエントリーポイントを含むことで、コントラクトがセッションコードの後続のTransactionがなくともコントラクト自体の初期化(self-initializing)を行えるようになります。このエントリーポイントは、コントラクトの内部構造を作成しTransactionの初期化後は呼び出されることはありません。下記が、call関数内で使われるself-initializingのエントリーポイントの一例です。

  • Self-initialization エントリーポイントの例
    
    // This entry point initializes the donation system, setting up the fundraising purse
    // and creating a dictionary to track the account hashes and the number of donations
    // made.
    #[no_mangle]
    pub extern "C" fn init() {
        let fundraising_purse = system::create_purse();
        runtime::put_key(FUNDRAISING_PURSE, fundraising_purse.into());
        // Create a dictionary to track the mapping of account hashes to number of donations made.
        storage::new_dictionary(LEDGER).unwrap_or_revert();
    }
    
    pub extern "C" fn call() {
        let init_entry_point = EntryPoint::new(
            ENTRY_POINT_INIT,
            vec![],
            CLType::Unit,
            EntryPointAccess::Public,
            EntryPointType::Contract,
        );

ホストノードがこれを強制することはない、ということは覚えておいてください。スマートコントラクトの作成者は、エントリーポイントを作成し最初のTransaction後に呼び出されることがないことを確認しなくてはなりません。

コスト

チェーン上での計算は、ガスコストに紐づいています。効率的なコーディングは、グローバルステートへ送信されたWasm全体の削減によってガスコストを最小にしてくれます。1.5に始まり、無効なWasmでさえ、グローバルステートへ送信された際にガスコストは発生します。その様に、Transactionを送信する前の適切なテストは重要です。

更には、新しいお財布(パース)を作成する為のセットコストとして2.5 CSPRが必要となります。可能であれば、パースの再利用によってこのコスト削減を考慮するべきです。パースを再利用する場合、セキュリティ面での失敗を防ぐ為に適切なアクセス管理を行わなくてはなりません。結局のところ、セキュリティ方法やコントラクトの安全性は、スマートコントラクトの作成者に依存しています。

WASMのサイズを小さくする方法

Transactionには、各ネットワークのchainspecに最大のサイズがmax_transaction_size = 1_048_576として指定されています。例えば、バージョン2.0のノードが動いているネットワークの場合は、以下の最大Transactionサイズがバイト形式にて指定されています。

max_transaction_size = 1_048_576

Transactionが持っているWasmのサイズを小さくするやり方のヒントをいくつかここでお伝えします。

1. リリースモードにてスマートコントラクトをビルドします。 サンプルは、こちらです。

cargo build --release --target wasm32-unknown-unknown

2. wasm-stripをコンパイルしたコード上で実行します (参照:WABT)。サンプルは、こちらです。

wasm-strip target/wasm32-unknown-unknown/release/contract.wasm

3. 標準のライブラリをインポートしないようにプログラムに伝える為の #![no_std]属性を使って casper-contractもしくは casper-types のクレートにリンクさせようとしている際には、std 機能は有効にしないでください。サンプルは、こちらです。詳細については、こちらをご確認ください。

#![no_std]

4. [profile.release]のCargo.tomlに codegen-units = 1を追加することで、codegen-unitsを1に設定したコントラクトをビルドします。サンプルは、こちらです。

5. [profile.release]のCargo.tomlにlto = true を追加し、link-time の最適化が有効になったコントラクトをビルドします。サンプルは、こちらです。

Inlining(インライン)

実際によく、開発者はcall関数へのcall_indirectを行うよりも、コード内に関数のボディを含むことでインライン関数を展開するべきです。Casperブロックチェーンのコーディングコンテキストには、Wasm実行時のオーバーヘッドの削減やリソースの限界値超えによる予期せぬエラーを防ぐことが目的にあります。

テスト

MainnetへTransactionをコミットする前にテストを行うことで、バグやガス手数料が発生する前に非効率性を検知できるようになります。Casperは、ユニットテストやNCTLを用いたテストそしてTestnetへのTransaction送信を含むいくつかのテスト方法を提供しています。

これらのプロセスについての情報は、下記リンク先にて確認いただけます。

下記2つのチュートリアルには、NCTLとTestnetの両方を使ったサンプルコントラクトの送信についての概要が書かれてあります。