- Home
- リブラのスマートコントラクトと Move言語
リブラのスマートコントラクトと Move言語
スマートコントラクト
Libra のスマートコントラクトは、あくまでも「通貨としての使いやすさ」を目的としたものとなっており、汎用的な目的で利用可能な Ethereum などとは異なっている。
Ethereum との違い
Ethereum では前述の通り汎用性を重視しており、「とある目的のために CPU やディスクなどのリソースの貸し借りをする」などといった内容でもスマートコントラクトで実現可能。
一方で Libra はあくまでも「金銭のやり取り」にのみ主眼を置いており、「支払いの自動化」などといった内容のみスマートコントラクトでの実現が可能。
Move 言語
Libra のスマートコントラクトは専用のプログラミング言語である Move 言語を使って実装する。なお、Move 言語自体は Rust 言語で実装されていて、テクニカルペーパーも公開されている。
過去のスマートコントラクト関連の問題を分析して、安全性・セキュリティ面を意識して設計された。静的型付けのバイトコード言語であり、Move VM で直接実行できるという点が Ethereum とは異なっている。
Move 言語は libra の GitHub の language ディレクトリ配下で公開されている。以下は README.md よりディレクトリ構成についてを引用。
├── README.md # This README ├── benchmarks # Benchmarks for the Move language VM and surrounding code ├── bytecode_verifier # The bytecode verifier ├── e2e_tests # Infrastructure and tests for the end-to-end flow ├── functional_tests # Testing framework for the Move language ├── compiler # The IR to Move bytecode compiler ├── stdlib # Core Move modules and transaction scripts ├── test.sh # Script for running all the language tests └── vm ├── cost_synthesis # Cost synthesis for bytecode instructions ├── src # Bytecode language definitions, serializer, and deserializer ├── tests # VM tests ├── vm_genesis # The genesis state creation, and blockchain genesis writeset └── vm_runtime # The bytecode interpreter
functional_tests にサンプルコードが配置されているため、Move 言語を使った実装例はここを見ると良い。
スマートコントラクトを Move 言語で動かす
実際に Move 言語でスマートコントラクトを動かした例を記載。
検証環境
$ cat /etc/os-release NAME="Ubuntu" VERSION="18.04.2 LTS (Bionic Beaver)" $ rustup show active toolchain ---------------- nightly-2019-07-08-x86_64-unknown-linux-gnu (overridden by '/home/ubuntu/libra/libra/rust-toolchain') rustc 1.38.0-nightly (6e310f2ab 2019-07-07) $ cargo --version cargo 1.37.0-nightly (4c1fa54d1 2019-06-24)
$ git log --oneline -n1 05c40c9 (HEAD -> testnet, origin/testnet, origin/pretestnet) [mempool] Check only gas price is updated when we allow increase in gas price to speed up a transaction
※最新バージョンを使うと、後述のコードがパスしないため注意。
セットアップ
My First Transaction の手順に従いセットアップを行う。
リポジトリをクローンして移動。
$ git clone <a style="cursor: help; display: inline !important;" href="https://github.com/libra/libra.git" target="_blank" rel="noopener noreferrer">https://github.com/libra/libra.git</a> $ cd libra
ブランチを testnet に変更。
$ git checkout testnet # 検証環境と合わせる場合は git checkout 05c40c9
scripts/dev_setup.sh
を実行して、開発環境を構築。
$ ./scripts/dev_setup.sh Welcome to Libra! This script will download and install the necessary dependencies needed to build Libra Core. This includes: * Rust (and the necessary components, e.g. rust-fmt, clippy) * CMake, protobuf, go (for building protobuf) If you'd prefer to install these dependencies yourself, please exit this script now with Ctrl-C. Proceed with installing necessary dependencies? (y/N) &gt; y Installing Rust...... ...
検証
第4回 Move言語でEthereumのERC20のような独自コインを作れるか試す をほぼそのまま使用、module
などの名称は変更している。元記事にもある通り、ERC20 ライクなコインを作成するサンプルとなっている。
- コインに名前を付けることができる
- 総発行量を取得できる
- 残高を取得できる
- 送信できる
なお、そのまま動かすとエラーになるため、以下を変更。
name: bytearray
の値設定を PR #339 での変更に従いb"xx"
からh"xx"
に変更h"66616b65206572633230"
は ASCII でface erc20
modules: module LibraFakeERC20 { // コイン全体を表すリソース resource LibraFakeERC20 { name: bytearray, total_supply: u64, } // アカウントごとの残高を表すリソース resource WALLET { balance: u64, } // 初期化関数 public publish() { move_to_sender&lt;LibraFakeERC20&gt;(LibraFakeERC20{ name: h"66616b65206572633230", total_supply: 0 }); // fake erc20 move_to_sender&lt;WALLET&gt;(WALLET{ balance: 0 }); return; } // コインの名前を取得する関数 public get_coin_name(): bytearray { let fake_erc20_ref: &amp;mut R#Self.LibraFakeERC20; let name: bytearray; let sender: address; sender = get_txn_sender(); fake_erc20_ref = borrow_global&lt;LibraFakeERC20&gt;(move(sender)); name = *(&amp;move(fake_erc20_ref).name); return move(name); } // 合計発行量を取得する関数 public total_supply(): u64 { let fake_erc20_ref: &amp;mut R#Self.LibraFakeERC20; let supply: u64; let sender: address; sender = get_txn_sender(); fake_erc20_ref = borrow_global&lt;LibraFakeERC20&gt;(move(sender)); supply = *(&amp;move(fake_erc20_ref).total_supply); return move(supply); } // コインを発行する関数 public mint(addr: address, value: u64) { let fake_erc20_ref: &amp;mut R#Self.LibraFakeERC20; let total_supply: u64; let wallet_ref: &amp;mut R#Self.WALLET; let balance: u64; fake_erc20_ref = borrow_global&lt;LibraFakeERC20&gt;(copy(addr)); total_supply = *(&amp;copy(fake_erc20_ref).total_supply); *(&amp;mut move(fake_erc20_ref).total_supply) = move(total_supply) + copy(value); wallet_ref = borrow_global&lt;WALLET&gt;(move(addr)); balance = *(&amp;copy(wallet_ref).balance); *(&amp;mut move(wallet_ref).balance) = move(balance) + copy(value); return; } // 残高を取得する関数 public balance_of(addr: address): u64 { let coin_ref: &amp;mut R#Self.WALLET; let coin_value: u64; coin_ref = borrow_global&lt;WALLET&gt;(copy(addr)); coin_value = *(&amp;move(coin_ref).balance); return move(coin_value); } // 送信する関数 public transfer(recipient_address: address, amount: u64) { let sender: address; let sender_wallet_ref: &amp;mut R#Self.WALLET; let sender_balance: u64; let receiver_wallet_ref: &amp;mut R#Self.WALLET; let receiver_balance: u64; sender = get_txn_sender(); sender_wallet_ref = borrow_global&lt;WALLET&gt;(copy(sender)); sender_balance = *(&amp;copy(sender_wallet_ref).balance); *(&amp;mut move(sender_wallet_ref).balance) = move(sender_balance) - copy(amount); //receiver_wallet_ref = borrow_global&lt;WALLET&gt;(move(recipient_address)); //receiver_balance = *(&amp;copy(receiver_wallet_ref).balance); //*(&amp;mut move(receiver_wallet_ref).balance) = move(receiver_balance) + copy(amount); return; } } script: import Transaction.LibraFakeERC20; import 0x0.LibraAccount; main() { let sender: address; let name: bytearray; let total_supply: u64; let balance: u64; let recipient_address: address; let sender_balance: u64; let recipient_balance: u64; sender = get_txn_sender(); recipient_address = 0xb0b; LibraFakeERC20.publish(); // コインのnameを取得して検証 name = LibraFakeERC20.get_coin_name(); assert(copy(name) == h"66616b65206572633230", 99); // fake erc20 // コインの合計発行量が0であることを検証 total_supply = LibraFakeERC20.total_supply(); assert(copy(total_supply) == 0, 99); // コインを発行し、発行先のアドレスの残高を検証 LibraFakeERC20.mint(copy(sender), 100); balance = LibraFakeERC20.balance_of(copy(sender)); assert(move(balance) == 100, 99); LibraAccount.create_new_account(copy(recipient_address), 0); // コインを送信 LibraFakeERC20.transfer(copy(recipient_address), 70); // 送信元の残高を取得して検証 sender_balance = LibraFakeERC20.balance_of(copy(sender)); assert(move(sender_balance) == 30, 99); // 送信先の残高を取得して検証 //recipient_balance = LibraFakeERC20.balance_of(copy(recipient_address)); //assert(move(recipient_balance) == 70, 99); // コインの合計発行量を検証 total_supply = LibraFakeERC20.total_supply(); assert(move(total_supply) == 100, 99); return; }
上記コードを libra/language/functional_tests/tests/testsuite
配下にディレクトリを作成して保存(例: libra/language/functional_tests/tests/testsuite/fake_erc20/fake_erc20.mvir
)
libra/language
に移動。
$ cd language
cargo
でテスト実行。コインの名前や総発行量のチェック、発行、送信などの検証が行われる。
$ cargo test -p functional_tests fake_erc20.mvir Finished dev [unoptimized + debuginfo] target(s) in 0.26s Running /home/ubuntu/libra/target/debug/deps/functional_tests-254508ec68a98a4c running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 23 filtered out Running /home/ubuntu/libra/target/debug/deps/testsuite-3002a31ac7b9d834 running 1 test test functional_tests::fake_erc20/fake_erc20.mvir ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 293 filtered out
test result: ok. 1 passed
になればテスト成功。