Libraの公式サイトである Getting Started With Move で記述されている内容に、かみ砕いた解説を付け加える形で、Move言語によるスマートコントラクト構築の基本を説明していきます。
まずは環境構築から。Libra Coreのプロジェクトをクローンします。
1 |
$ git clone https://github.com/libra/libra.git && cd libra |
Libra Coreをセットアップします。依存ファイル等がインストールされます。
1 |
$ ./scripts/dev_setup.sh |
関連ページ
Libra Testnetを試す[発行][送金][mnemonic][recover]
Move言語によるコントラクト概要
Move言語は静的型付けの言語であり、文法もRustやC++を踏襲しています。ファイルの拡張子は .mvir(Move Intermediate Representation)です。
コントラクトはモジュール(module)として作成していきます。つまりこのモジュールとしての名前空間の中にプロシージャ(関数のこと)や型を定義していきます。
1 2 3 |
module Hoge{ … } |
本記事では、実際に Getting Started With Move で紹介されているモジュール例を見ながら説明していきます。
上記リンク先のEarmarkedLibraCoinというコントラクトは、下記イラストのような取引を想定した機能が実装されています。アリスはボブが引き落とすためのLibraコインとしてのリソース(後述)を作成します。「earmake for 〜」 は、〜のためにとっておくという意味を持ち、アリスはボブが利用するためのLibraコインをリソースとして用意します。
モジュールについて
EarmarkedLibraCoinという名前のモジュールを作成しています。
1 2 3 4 |
module EarmarkedLibraCoin { import 0x0.LibraCoin; … } |
EarmarkedLibraCoinという名前空間になり、このモジュール内のプロシージャは、EarmarkedLibraCoin.hoge()のようにして外部モジュールから実行することができます。
2行目 LibraCoinというLibraコインを扱う上で必須のモジュールをインポートしています。0x0はアドレスを表しています。後述しますがMoveではアカウントを扱うLibraAccountというモジュールとセットでよく利用します。LibraCoinモジュールは下記のディレクトリ内のlibra_coin.mvirファイルに記述されています。
/libra/language/stdlib/modules/
リソース(resource)とmove_to_sender()
リソースTを定義しています。リソースがコントラクトにおけるアセット(資産)となります。
1 2 3 4 |
resource T { coin: LibraCoin.T, recipient: address } |
リソース(型)は構造体に似た、Move言語独自のデータ型です。のちに説明するようにこのリソースとしてのデータは、組み込み関数である move_to_sender() を利用するとブロックチェーンへと取り込まれます。より正確な表現としては、この move_to_sender() を利用すると作成したリソースが、move_to_sender() を呼出したアカウントのアドレスと紐付き発行されます(Publish the earmarked coin under the transaction sender’s account address.)。またその逆にそのリソースへは同じく組み込み関数である move_from() にアドレスを指定することでアクセスできるようになります。
上記リソースは、まずcoin変数の型としてLibraコインが指定されています。LibraCoin.Tは上述したLibraCoinモジュール内で定義されているリソースTを指し、その型は符号なし整数としてのu64(0から2^64-1)が指定されています。つまりLibraコインの量そのものです。recipient変数はこのリソースのLibraコインを受け取ることのできるアドレス(つまりボブのアドレスを想定)を設定するために用意されています。
これまで説明してきたモジュール/リソース/プロシージャの関係は、クラス/オブジェクト/メソッドと捉えることができます。リソースの値は、同じモジュール内のプロシージャによってのみ更新することができます。
move()による所有権(ownership)の移動
リソースT(EarmarkedLibraCoinモジュール内の)を作成するためのプロシージャです。
1 2 3 4 5 6 7 8 9 10 11 |
public create(coin: LibraCoin.T, recipient: address) { let t: Self.T; t = T { coin: move(coin), recipient: move(recipient), }; move_to_sender<T>(move(t)); return; } |
引数にはLibraコインの量とこのコインを引き落とすことのできるアドレスを指定します。
5/6行目 Move言語はRust言語の「所有権」という機能に基づいており、一度代入された変数へはアクセスすることができません。その値を利用するためには、move()を利用して所有権を移動させる必要があります。下記コード例にあるように、 = では所有権を移動させることはできません。リソースの値は、このmove()を利用して操作すると考えて良いでしょう。Move言語によるアセット(資産)の安全性は、この所有権の仕組みが土台となっているようです。また本記事のコード内では利用されていませんが、copy()もよく利用されます。これは所有権を移動させることなく変数の値を別の変数へと移動させることができます(複製している)。詳しくは「ムーブセマンティクス(move semantics)」で調べてみても良いでしょう。
1 2 3 4 |
let x: u64; let y: u64; x = 1; y = move(x); //y = x だとエラーとなる |
9行目 move_to_sender()でリソースをブロックチェーンへと取り込みます。ここでは作成したリソースtを引数に指定しています。<T>はジェネリックとしての機能を明示しています。先に述べたように、move_to_sender()によって、リソースとmove_to_sender()を呼出したアカウントのアドレスとが紐付きブロックチェーンに取り込まれます。その紐づきにより再度アドレスからリソースを特定することができます。
リソース(Libraコイン)をボブが受け取るためのプロシージャです(ボブが実行することを想定)。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public claim_for_recipient(earmarked_coin_address: address): Self.T acquires T { let t: Self.T; let t_ref: &Self.T; let sender: address; t = move_from<T>(move(earmarked_coin_address)); t_ref = &t; sender = get_txn_sender(); assert(*(&move(t_ref).recipient) == move(sender), 99); return move(t); } |
3行目 & はアドレス演算子で、t_ref へは、Self.T のアドレス(Self.Tへの参照)を格納します。
6行目 組み込み関数である move_from() で、指定したアドレス(earmarked_coin_address)に紐付いたリソースを特定すると同時に、そのアドレスからリソースを破棄します(removing a resource from an account via the MoveFrom instruction)。
9/10行目 get_txn_sender() はこの claim_for_recipient() を呼出したアカウントのアドレスを取得します。Ethereumのコントラクトを構築するSolidty言語の msg.sender と同様の機能を持ちます。assert() は第1引数の条件が真(true)の場合はそのままスルーされ return へと続き、偽(false)の場合はそこで終了となります。第2引数の数値は、偽となった場合のエラー箇所の目印として任意に付けられる番号で、番号自体に意味はありません。アリスが作成したリソース内のアドレス(recipient)とこの claim_for_recipient() を呼出すアカウントのアドレス(つまりボブのアドレスを想定)が一致した場合に真となります。
このclaim_for_creator()を呼出したアカウントのアドレスに紐付いたリソースを返します。
1 2 3 4 5 6 7 8 9 |
public claim_for_creator(): Self.T acquires T { let t: Self.T; let sender: address; sender = get_txn_sender(); t = move_from<T>(move(sender)); return move(t); } |
リソースを作成したアカウント(つまりアリスを想定)が、自分のリソースを取得するためのプロシージャです。テスト項目で説明しますが、たんにアリスが自分のコインを取り戻すために作られたプロシージャです。
リソースからLibraコインを取り出すプロシージャです。
1 2 3 4 5 6 7 |
public unwrap(t: Self.T): LibraCoin.T { let coin: LibraCoin.T; let recipient: address; T { coin, recipient } = move(t); return move(coin); } |
引数にリソースを指定すると、そのリソースがもつLibraコインの量が返ってきます。
テスト
実際にEarmarkedLibraCoinモジュールをテストしてみます。といってもそのコードもすでにサンプルが用意されているので、それを実行してみます。ファイルは下記ディレクトリ内にある earmarked_libra.mvir という名前のファイルです。
/libra/language/functional_tests/tests/testsuite/move_getting_started_examples/
下記部分が実際にトランザクションを生成するコードとなります。このmain()自体はアリスが実行することを想定しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
//! new-transaction import 0x0.LibraAccount; import 0x0.LibraCoin; import {{default}}.EarmarkedLibraCoin; main() { let recipient_address: address; let coin: LibraCoin.T; let earmarked_coin: EarmarkedLibraCoin.T; let sender: address; recipient_address = 0xb0b; coin = LibraAccount.withdraw_from_sender(1000); EarmarkedLibraCoin.create(move(coin), move(recipient_address)); earmarked_coin = EarmarkedLibraCoin.claim_for_creator(); coin = EarmarkedLibraCoin.unwrap(move(earmarked_coin)); sender = get_txn_sender(); LibraAccount.deposit(move(sender), move(coin)); return; } |
3行目 アカウントを扱う上で必須のLibraAccountモジュールをインポートしています(このモジュールのファイル場所は上述)。
13〜15行目 ボブが受け取ることのできるリソースを作成しています。
14行目 LibraAccountモジュールで用意されているwithdraw_from_sender()を実行しています。このトランザクションを生成するアカウントから引数に指定されたLibraコインの量を引き落とします。
17〜20行目 リソースを作成したアカウント(つまりアリス)が、再度リソースからLibraコインを引き戻しています。LibraAccount.deposit()は、第1引数に受取人(ここでは自分自身を指定している)を指定し、第2引数に入金するLibraコイン量を指定します。
以下コマンドでテストします。functional_testsディレクトリ以下に配置されたファイルが自動的に探し出されて実行されます。はじめて実行する場合は依存ファイルのコンパイルよってしばらく時間がかかります。ファイル名をearmarkedとだけ記述しても、その文字列を含むファイルがテストされます。
1 |
cargo test -p functional_tests earmarked_libra.mvir |
参照ページ・書籍
※move_to_sender()およびmove_from()については、下記PDF内のMoveToSender、MoveFromで検索してみて下さい。
Move: A Language With Programmable Resources PDF