第2回 Move言語でHelloWorldプログラムを作成する

第2回 Move言語でHelloWorldプログラムを作成する

Libraに関するセミナーのご依頼、その他ブロックチェーンに関するコンサルティングのお問い合わせはこちらからお願いいたします。(ページ右下の「サポート」をクリック)
連載全5回のインデックスはこちら

言語仕様

Move言語で書かれたプログラムは、Move IRとも呼ばれ、ファイルの拡張子が「.mvir」になります。
Moveの言語仕様は公式ドキュメントのテクニカルペーパーを確認するのが一番早いと思います。以下に主だったものを紹介します。
  • Move IR は、トランザクションスクリプトとモジュールで構成される(1ファイルに両方記載してもよいし、別々にしてimportしてもよい)。
  • トランザクションスクリプトは main関数を記載する。
  • 各関数の最後は return で終了する。
modules:
module M {
    foo() {
        return;
    }
}
 
script:
main() {
    return;
}
  • Moveで扱えるプリミティブ型は、ブール値、64ビットの符号なし整数、256ビットのアカウントアドレス、固定長バイト配列がサポートされている。その他、構造体を使用することができる。
main() {
    let a: bool;
    let b: u64;
    let c: address;
    let d: bytearray;
・・・
  • 変数は関数の先頭に宣言しなければならない。
  • if や while、break、continue等の基本的な制御構文の書き方、演算子はRustと同じ。

付属のサンプルプログラム

LibraのGitHubからクローンしたソースコードの中には、Moveのサンプルプログラムも含まれています。サンプルプログラムは、
libra/language/functional_tests/tests/testsuite/
の配下にある各ディレクトリの中に配置されています。
サンプルプログラムには、主だったものとして以下のようなものがあります。
  • builtins/
    • ビルトイン関数として最初から用意されている関数の使い方のサンプル
  • commands/
    • if や while 等の基本的な制御構文の使い方のサンプル
  • comments/
    • コメント文の使い方のサンプル
  • examples/
    • シンプルなMoveの構文のサンプル
  • global_ref_count/
    • リソースの取得と格納方法のサンプル
  • modules/
    • モジュールに関する様々な使い方のサンプル
  • move_getting_started_examples/
    • ドキュメント「Getting Started With Move」に出てくるサンプルプログラム
  • operators/
    • 加減乗除や比較の演算子の使い方のサンプル
  • payments/
    • 送金プログラムのサンプル
上記以外にも様々なサンプルがあります。最初は、「commands/」や「operators/」など基本的なMoveのプログラムから見ていただくのがよいと思います。

サンプルプログラムの中には、「// check: VerificationError」や「// check: CopyLocUnavailableError」などが記載されているファイルがあります。これは、記載されているプログラムを実行すると、「VerificationError」が発生するというエラーケースのサンプルになります。コメントを削除して実行すると、テストが失敗し、「VerificationError」が発生することが確認できます。

HelloWorldプログラムの作成

サンプルプログラムでMove言語の書き方を理解したら、独自のプログラムを作ってみます。
HelloWorldといえば、「HelloWorld」という文字列を標準出力に表示させるプログラムが一般的ですが、Move言語では文字列を簡単に扱うことができず、標準出力に表示させることもできないので、足し算・引き算を行うプログラムを作ってみました。

以下が作成したソースコードです。
modules:
module HelloWorld {
 
    resource ACC {
        total: u64,
    }
 
    // 初期化する関数
    public publish() {
        move_to_sender(ACC{ total: 0 });
        return;
    }
 
    // 足し算する関数
    public add(value: u64) {
        let acc: &mut R#Self.ACC;
        let total: u64;
        let my_address: address;
 
        // 自分(トランザクション実行者)のアドレスを取得
        my_address = get_txn_sender();
 
        // 自分が持つACCリソースを取得
        acc = borrow_global(move(my_address));
 
        // ACCリソースのtotal値を取得
        total = *(©(acc).total);
 
        // これまでのtotalに引数のvalueを加算してリソースに保存する
        *(&mut move(acc).total) = move(total) + move(value);
 
        return;
    }
 
    // 引き算する関数
    public sub(value: u64) {
        let acc: &mut R#Self.ACC;
        let total: u64;
        let my_address: address;
 
        my_address = get_txn_sender();
        acc = borrow_global(move(my_address));
        total = *(©(acc).total);
        *(&mut move(acc).total) = move(total) - move(value);
        return;
    }
 
    // クリアする関数
    public clear() {
        let acc: &mut R#Self.ACC;
        let total: u64;
        let my_address: address;
 
        my_address = get_txn_sender();
        acc = borrow_global(move(my_address));
        *(&mut move(acc).total) = 0;
        return;
    }
 
    // リソースの合計値を取得する関数
    public get_total(): u64 {
        let acc: &mut R#Self.ACC;
        let total: u64;
        let my_address: address;
 
        my_address = get_txn_sender();
        acc = borrow_global(move(my_address));
        total = *(&move(acc).total);
        return move(total);
    }
 
}
 
script:
import Transaction.HelloWorld;
main() {
    let total: u64;
 
    // 初期化して合計値が0であることを確認
    HelloWorld.publish();
    total = HelloWorld.get_total();
    assert(move(total) == 0, 99);
 
    // 10を加算して合計値が10であることを確認
    HelloWorld.add(10);
    total = HelloWorld.get_total();
    assert(move(total) == 10, 99);
 
    // 7を減算して合計値を3であることを確認
    HelloWorld.sub(7);
    total = HelloWorld.get_total();
    assert(move(total) == 3, 99);
 
    // 合計値をクリアして0であることを確認
    HelloWorld.clear();
    total = HelloWorld.get_total();
    assert(move(total) == 0, 99);
 
    return;
}
HelloWorldにしてはソースコードが長くなってしまいました。
ソースコード中のコメントにも記述していますが、足し算、引き算、クリアの処理を行い、それぞれの結果が正しいことをassertで検証しています。

各処理の保存領域としてACCというリソースを定義し、各関数内で、「borrow_global」関数を使って、取得・保存を行っています。
このようにすることで、例えば送金を行った結果の値を保存したり、計算結果を保存したり、といった使い方が可能になります。

このリソースの大きな特徴は、アカウント(アドレス)毎にリソースの領域が作成されるという点で、必要に応じて各アカウントのリソースの取得、操作が必要になります。

HelloWorldプログラムのテスト実行

作成したプログラムのテストを実行するためには、テスト対象となるMove言語のファイル(拡張子 .mvir)を
libra/language/functional_tests/tests/testsuite/
の配下にディレクトリを作り、その下に配置します。
今回は、helloworldというディレクトリを作成し、その下にhelloworld.mvirというファイルで保存しました。

テストの実行は、テスト対象のファイルを指定する場合は、
$ cargo test -p functional_tests helloworld.mvir
とすることで、1ファイルだけをテストすることができます。

helloworld.mvirのテストを実行すると、
test functional_tests::helloworld.mvir ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 245 filtered out
と結果が出力され、無事にテストが成功していることが確認できました。

ちなみに、テストに失敗するとエラー情報が出力されますが、環境変数に「RUST_BACKTRACE=1」を設定すると、詳細な情報が出力されるようになります。

まとめ

Move言語は、Rust言語と似ていることもあり、Rustの文法や所有権の考え方に慣れているとスムースにプログラムを作成できる言語であると思います。筆者はmove関数やcopy関数の扱いに苦労しました。
また、現時点ではコンパイルでエラーが発生した際に、どこに原因があるのかが出力されるメッセージからでは特定しづらく、コメントアウトなどをしながらコンパイルして結果を確かめるといった作業を繰返し行っていました。このあたりは今後改善されていくことを期待したいです。

次回は、より実践的なプログラムの作成に挑戦してみます。

ブロックチェーンの専門企業で働いてみませんか?

当サイトを運営するコンセンサス・ベイス株式会社では、エンジニア、プロジェクトマネージャー、ライターなど、様々なポジションで一緒に働いてくださる仲間を募集しています。

ブロックチェーン業界にチャレンジしてみたいあなたのご応募をお待ちしております!

リブラ(Libra)カテゴリの最新記事