• Home
  • リブラのスマートコントラクトと Move言語

リブラのスマートコントラクトと Move言語

スマートコントラクト

Libra のスマートコントラクトは、あくまでも「通貨としての使いやすさ」を目的としたものとなっており、汎用的な目的で利用可能な Ethereum などとは異なっている。

Ethereum との違い

Ethereum では前述の通り汎用性を重視しており、「とある目的のために CPU やディスクなどのリソースの貸し借りをする」などといった内容でもスマートコントラクトで実現可能。

一方で Libra はあくまでも「金銭のやり取り」にのみ主眼を置いており、「支払いの自動化」などといった内容のみスマートコントラクトでの実現が可能。

Move 言語

Libra のスマートコントラクトは専用のプログラミング言語である Move 言語を使って実装する。なお、Move 言語自体は Rust 言語で実装されていて、テクニカルペーパーも公開されている。

過去のスマートコントラクト関連の問題を分析して、安全性・セキュリティ面を意識して設計された。静的型付けのバイトコード言語であり、Move VM で直接実行できるという点が Ethereum とは異なっている。

Move 言語は libra の GitHublanguage ディレクトリ配下で公開されている。以下は 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)

libra のバージョンは 05c40c9 を使用。

$ 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) > 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<LibraFakeERC20>(LibraFakeERC20{ name: h"66616b65206572633230", total_supply: 0 }); // fake erc20
move_to_sender<WALLET>(WALLET{ balance: 0 });
return;
}

// コインの名前を取得する関数
public get_coin_name(): bytearray {
let fake_erc20_ref: &mut R#Self.LibraFakeERC20;
let name: bytearray;
let sender: address;

sender = get_txn_sender();
fake_erc20_ref = borrow_global<LibraFakeERC20>(move(sender));
name = *(&move(fake_erc20_ref).name);

return move(name);
}

// 合計発行量を取得する関数
public total_supply(): u64 {
let fake_erc20_ref: &mut R#Self.LibraFakeERC20;
let supply: u64;
let sender: address;

sender = get_txn_sender();
fake_erc20_ref = borrow_global<LibraFakeERC20>(move(sender));
supply = *(&move(fake_erc20_ref).total_supply);

return move(supply);
}

// コインを発行する関数
public mint(addr: address, value: u64) {
let fake_erc20_ref: &mut R#Self.LibraFakeERC20;
let total_supply: u64;
let wallet_ref: &mut R#Self.WALLET;
let balance: u64;

fake_erc20_ref = borrow_global<LibraFakeERC20>(copy(addr));
total_supply = *(&copy(fake_erc20_ref).total_supply);
*(&mut move(fake_erc20_ref).total_supply) = move(total_supply) + copy(value);

wallet_ref = borrow_global<WALLET>(move(addr));
balance = *(&copy(wallet_ref).balance);
*(&mut move(wallet_ref).balance) = move(balance) + copy(value);

return;
}

// 残高を取得する関数
public balance_of(addr: address): u64 {
let coin_ref: &mut R#Self.WALLET;
let coin_value: u64;

coin_ref = borrow_global<WALLET>(copy(addr));
coin_value = *(&move(coin_ref).balance);

return move(coin_value);
}

// 送信する関数
public transfer(recipient_address: address, amount: u64) {
let sender: address;
let sender_wallet_ref: &mut R#Self.WALLET;
let sender_balance: u64;
let receiver_wallet_ref: &mut R#Self.WALLET;
let receiver_balance: u64;

sender = get_txn_sender();
sender_wallet_ref = borrow_global<WALLET>(copy(sender));
sender_balance = *(&copy(sender_wallet_ref).balance);
*(&mut move(sender_wallet_ref).balance) = move(sender_balance) - copy(amount);

//receiver_wallet_ref = borrow_global<WALLET>(move(recipient_address));
//receiver_balance = *(&copy(receiver_wallet_ref).balance);
//*(&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 になればテスト成功。

参考記事

ご相談・お見積もり

03-5207-2689