見出し画像

MOVE言語 チュートリアルStep_6 モジュールの汎用化

どうも!TABEです!

今回もMOVEのチュートリアルやっていきます!

公式のチュートリアルリンク





早速ディレクトリに移動します。

cd <moveフォルダをダウンロードしたディレクトリ>/move/language/documentation/tutorial/step_6/BasicCoin

ここでプログラムを回収して
コードの構文に問題ないことを確認する場合は
以下を実行します。

move build


プログラムをコメント付きで見ていきます。

※コメントを日本語で書いていますがリリースする際はコンパイルが通らないみたいです。

BasicCoin.move (色々な関数が共通化されたモジュールです。)

/// このモジュールでは、最小限の一般的なコインとBalanceを定義しています。
module NamedAddr::BasicCoin {
    use std::signer;

    /// エラーコード
    const ENOT_MODULE_OWNER: u64 = 0;
    const EINSUFFICIENT_BALANCE: u64 = 1;
    const EALREADY_HAS_BALANCE: u64 = 2;

    // phantom型について以下の通りに記載がありました。
    // 構造体を定義するとき、Moveの型チェッカはすべてのファントム型パラメータが構造体定義内で使用されていないか、
    // ファントム型パラメータの引数としてのみ使用されていることを保証する
    // 正直意味がわからなかったので、ここも実際に作っている中で理解できるかもしれないです。。。
    struct Coin<phantom CoinType> has store {
        value: u64
    }
    
    struct Balance<phantom CoinType> has key {
        coin: Coin<CoinType>
    }

    /// 空の残高リソースを `account` のアドレスの下に公開します。
    /// この関数は、そのアカウントに送金する前に呼び出す必要があります。
    public fun publish_balance<CoinType>(account: &signer) {
        let empty_coin = Coin<CoinType> { value: 0 };
        assert!(!exists<Balance<CoinType>>(signer::address_of(account)), EALREADY_HAS_BALANCE);
        move_to(account, Balance<CoinType> { coin:  empty_coin });
    }

    /// Mint `amount` トークンを `mint_addr` に送信します。このメソッドでは、 `CoinType` を持つwitnessが必要
    /// CoinType` を所有するモジュールがミンティングのポリシーを決定できるように、このメソッドには `CoinType` を持つ証人が必要です。
    public fun mint<CoinType: drop>(mint_addr: address, amount: u64, _witness: CoinType) acquires Balance {
        // トークンの総量 `total_value` を mint_addr の残高に預ける。
        deposit(mint_addr, Coin<CoinType> { value: amount });
    }

    // 渡されたアドレスがこのCoinをどれくらい持っているのか確認する関数
    public fun balance_of<CoinType>(owner: address): u64 acquires Balance {
        borrow_global<Balance<CoinType>>(owner).coin.value
    }

    /// トークンの量 `amount` を `from` から `to` へ転送する。
    /// このメソッドは `CoinType` を持つウィットネスを必要とする。
    /// これは所有するモジュールが転送のポリシーを決定できるようにするためである。
    public fun transfer<CoinType: drop>(from: &signer, to: address, amount: u64, _witness: CoinType) acquires Balance {
        // withdraw関数より指定したAmountの数のコインを変数に入れる
        let check = withdraw<CoinType>(signer::address_of(from), amount);
        // withdraw関数で対象のアドレスへCoinを送る
        deposit<CoinType>(to, check);
    }

    // モジュールからコインを引き出す
    fun withdraw<CoinType>(addr: address, amount: u64) : Coin<CoinType> acquires Balance {
        let balance = balance_of<CoinType>(addr);
        assert!(balance >= amount, EINSUFFICIENT_BALANCE);
        let balance_ref = &mut borrow_global_mut<Balance<CoinType>>(addr).coin.value;
        *balance_ref = balance - amount;
        Coin<CoinType> { value: amount }
    }

    // 対象のアドレスへCoinを渡す。
    fun deposit<CoinType>(addr: address, check: Coin<CoinType>) acquires Balance{
        let balance = balance_of<CoinType>(addr);
        let balance_ref = &mut borrow_global_mut<Balance<CoinType>>(addr).coin.value;
        let Coin { value } = check;
        *balance_ref = balance + value;
    }
}

solidityをやっている方だとERC721の元ファイルだと思っていただければいいかと思います。

MyOddCoin.moveBasicCoin.moveを呼び出して実行するモジュール)


/// 奇数枚のコインしか毎回送金できない奇数コインを実装したモジュール。
module NamedAddr::MyOddCoin {
    // signerのモジュールをインポートする
    use std::signer;
    // BasicCoinのモジュールをインポートする
    use NamedAddr::BasicCoin;

    // dropは値が転送されず、Moveプログラムの実行に伴って事実上破壊されることを意味します。
    // ここもイマイチ意味が掴めませんでした。リンクは貼り付けておきます。
    struct MyOddCoin has drop {}

    const ENOT_ODD: u64 = 0;

    // &signerはモジュールオーナーのアドレス
    public fun setup_and_mint(account: &signer, amount: u64) {
        // BasicCoinからpublish_balanceを呼び出して使っている。
        BasicCoin::publish_balance<MyOddCoin>(account);
        // BasicCoinからmintを呼び出して使っている。
        BasicCoin::mint<MyOddCoin>(signer::address_of(account), amount, MyOddCoin {});
    }

    public fun transfer(from: &signer, to: address, amount: u64) {
        // 引数で渡されたAmountが奇数かどうかを確認
        assert!(amount % 2 == 1, ENOT_ODD);
        // BasicCoinからtransferを呼び出して使っている。
        BasicCoin::transfer<MyOddCoin>(from, to, amount, MyOddCoin {});
    }

    /*
        ユニットテスト
    */
    #[test(from = @0x42, to = @0x10)]
    fun test_odd_success(from: signer, to: signer) {
        setup_and_mint(&from, 42);
        setup_and_mint(&to, 10);

        transfer(&from, @0x10, 7);
        // BasicCoinからbalance_ofを呼び出して持っているコインを確かるassertをしている
        assert!(BasicCoin::balance_of<MyOddCoin>(@0x42) == 35, 0);
        assert!(BasicCoin::balance_of<MyOddCoin>(@0x10) == 17, 0);
    }

    #[test(from = @0x42, to = @0x10)]
    // ユニットテストが失敗することでPASSするアノテーション
    #[expected_failure]
    fun test_not_odd_failure(from: signer, to: signer) {
        setup_and_mint(&from, 42);
        setup_and_mint(&to, 10);

        // transfer偶数枚のコインを転送するので、これは失敗する
        transfer(&from, @0x10, 8);
    }
}



dropの構文リンク


段々とコードの量が多くなってきましたが
一つ一つ読み解いていくことで知見が溜まっていきますね!

またよろしくお願いいたします!
コツコツやっていきましょう!

この記事が気に入ったらサポートをしてみませんか?