第4回 Move言語でEthereumのERC20のような独自コインを作れるか試す

第4回 Move言語でEthereumのERC20のような独自コインを作れるか試す

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

実践的なプログラムの作成②

今回は実践的なプログラムの作成として、EthereumのERC20を使った独自コインのようなものがMove言語で作れるかどうかを試してみます。

ERC20の詳細は割愛しますが、OpenZeppelinのERC20を扱うためのライブラリでは、以下のような機能が提供されており、これらをMove言語で実装できるか検証してみます。
  • コインに名前を付けて取得ができる
  • コインの合計発行量を取得できる
  • コインの発行ができる
  • コインの送金ができる
  • 指定したアドレスの残高を取得できる

プログラム

検証のために作成したプログラムは以下の通りです。
modules:
 
module CBCoin {
 
  // コイン全体を表すリソース
  resource CB_COIN {
    name: bytearray,
    total_supply: u64,
  }
 
  // アカウントごとの残高を表すリソース
  resource WALLET {
    balance: u64,
  }
 
  // 初期化関数
  public publish() {
    move_to_sender(CB_COIN{ name: b"cb", total_supply: 0 });
    move_to_sender(WALLET{ balance: 0 });
    return;
  }
 
  // コインの名前を取得する関数
  public get_coin_name(): bytearray {
    let cbcoin_ref: &mut R#Self.CB_COIN;
    let name: bytearray;
    let sender: address;
 
    sender = get_txn_sender();
    cbcoin_ref = borrow_global(move(sender));
    name = *(&move(cbcoin_ref).name);
 
    return move(name);
  }
 
  // 合計発行量を取得する関数
  public total_supply(): u64 {
    let cbcoin_ref: &mut R#Self.CB_COIN;
    let supply: u64;
    let sender: address;
 
    sender = get_txn_sender();
    cbcoin_ref = borrow_global(move(sender));
    supply = *(&move(cbcoin_ref).total_supply);
 
    return move(supply);
  }
 
  // コインを発行する関数
  public mint(addr: address, value: u64) {
    let cbcoin_ref: &mut R#Self.CB_COIN;
    let total_supply: u64;
    let wallet_ref: &mut R#Self.WALLET;
    let balance: u64;
 
    cbcoin_ref = borrow_global(copy(addr));
    total_supply = *(©(cbcoin_ref).total_supply);
    *(&mut move(cbcoin_ref).total_supply) = move(total_supply) + copy(value);
 
    wallet_ref = borrow_global(move(addr));
    balance = *(©(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(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(copy(sender));
    sender_balance = *(©(sender_wallet_ref).balance);
    *(&mut move(sender_wallet_ref).balance) = move(sender_balance) - copy(amount);
 
    //receiver_wallet_ref = borrow_global(move(recipient_address));
    //receiver_balance = *(©(receiver_wallet_ref).balance);
    //*(&mut move(receiver_wallet_ref).balance) = move(receiver_balance) + copy(amount);
 
    return;
  }
 
}
 
 
script:
import Transaction.CBCoin;
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;
 
  CBCoin.publish();
 
  // コインのnameを取得して検証
  name = CBCoin.get_coin_name();
  assert(copy(name) == b"cb", 99);
 
  // コインの合計発行量が0であることを検証
  total_supply = CBCoin.total_supply();
  assert(copy(total_supply) == 0, 99);
 
  // コインを発行し、発行先のアドレスの残高を検証
  CBCoin.mint(copy(sender), 100);
  balance = CBCoin.balance_of(copy(sender));
  assert(move(balance) == 100, 99);
 
  LibraAccount.create_new_account(copy(recipient_address), 0);
 
  // コインを送金
  CBCoin.transfer(copy(recipient_address), 70);
 
  // 送金元の残高を取得して検証
  sender_balance = CBCoin.balance_of(copy(sender));
  assert(move(sender_balance) == 30, 99);
 
  // 送金先の残高を取得して検証
  //recipient_balance = CBCoin.balance_of(copy(recipient_address));
  //assert(move(recipient_balance) == 70, 99);
 
  // コインの合計発行量を検証
  total_supply = CBCoin.total_supply();
  assert(move(total_supply) == 100, 99);
 
  return;
 
}
上記のプログラムをテスト実行すると、
test functional_tests::cb_coin/CBCoin.mvir ... ok
とOKなりますが、一部コメントアウトしている部分があります。これらを外して実行すると、「Execution(MissingData)」というエラーが発生します。その内容については後述の検証結果にて説明します。

検証結果

検証項目に対する結果は以下のとおりです。
  • コインに名前を付けて取得ができる
    • 可能。ただし、Moveは文字列を扱うことができないので、bytearrayでの格納、取得になる。
  • コインの合計発行量を取得できる
    • 可能。ただし、適切な管理方法が不明。(後述)
  • コインの発行ができる
    • 一部可能。トランザクション実行者であれば、発行した数に応じて合計発行量や残高を増やすこともできたが、それ以外のアカウントではエラーとなった。
  • コインの送金ができる
    • 不可能。詳細は後述。
  • 指定したアドレスの残高を取得できる
    • 一部可能。トランザクション実行者であれば、残高を確認することができたが、それ以外のアカウントではエラーとなった。

Libraにおけるリソースの扱いについて

Libraではリソースの保存領域はアカウントごとになります。そのため、送金を行った際には、送金元のリソースと送金先のリソースをMoveで扱うことになりますが、トランザクション実行者以外のリソースを初期化する手段がなく、初期化しないまま取得しようとすると、「Execution(MissingData)」というエラーが発生して処理を続行することができませんでした。

また、合計発行量のようなシステムで一意となる情報をどこに保存すべきかという点にも悩みました。今回はトランザクション実行者のリソースに保存していますが、実際にメインネット等にデプロイした際には、コントラクトアドレスのリソースで管理する等の方法が適切であると思いますが、デプロイ時の挙動が不明瞭なため、この点は今後の課題となります。

Moveでは連想配列のような情報の持ち方が現状サポートされていないため、各アカウントの情報を配列で保持するようなこともできません。それぞれのアカウントのリソースにアクセスできないと、アカウント間での情報のやり取りを行うようなプログラムが作れない状況になってしまいます。

第3回で行った標準のLibraコインの送金では、送金先の残高も取得できたため、何らかの手段はあると考えていますが、現段階では解決ができませんでした。

まとめ

今回は、EthererumのERC20のような使い方がLibraで可能であるか検証しました。実装可能である部分とうまくいかない部分とがありましたが、惜しいところまではいっていると思うので、引き続き調査、検証を実施していきたいと考えています。

次回は、Ethereumの別の規格ERC721が実装可能であるか検証してみます。

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

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

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

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