Alva

Alva

programmer
github
telegram
email

讓我們移動 - 學 Move 得 Sui(二):完成 Coin 合約的上鏈部署

letsmove

Let's Move 是一項學習 Move 語言的激勵計畫,鼓勵更多的人學習 Move 語言

學習日誌 (alva-lin)

任務 2 - 完成兩個 Coin 合約的上鏈部署 (mainnet)#

任務

  • 完成 Coin 相關知識的學習

  • 完成 My Coin 的學習並部署主網

  • 完成 Faucet Coin 的學習並部署主網

  • 提交 My CoinFaucet Coin 合約發布 package id

  • 發送 My Coin 給地址 0x7b8e0864967427679b4e129f79dc332a885c6087ec9e187b53451a9006ee15f2

主要參考 Managed Coin 案例 - Sui Move 導論 一文

sui::coin 庫#

Coin#

Coin 合約的編寫,主要使用了 sui::coin 庫中提供的結構體和方法。

struct Coin<phantom T> has key, store {
    id: UID,
    balance: Balance<T>
}

struct Balance<phantom T> has store {
    value: u64
}

Coin 擁有 key 和 store 能力,被視為資產,可以在不同地址間轉移,後續在區塊瀏覽器中可以查閱到自己擁有的所有 Coin。

create_currency#

public fun create_currency<T: drop>(
    witness: T,
    decimals: u8,
    symbol: vector<u8>,
    name: vector<u8>,
    description: vector<u8>,
    icon_url: Option<Url>,
    ctx: &mut TxContext
): (TreasuryCap<T>, CoinMetadata<T>) {
    // 確保只有一個類型 T 的實例
    assert!(sui::types::is_one_time_witness(&witness), EBadWitness);

    // 發出貨幣元數據作為事件。
    event::emit(CurrencyCreated<T> {
        decimals
    });

    (
        TreasuryCap {
            id: object::new(ctx),
            total_supply: balance::create_supply(witness)
        },
        CoinMetadata {
            id: object::new(ctx),
            decimals,
            name: string::utf8(name),
            symbol: ascii::string(symbol),
            description: string::utf8(description),
            icon_url
        }
    )
}

create_currency 方法參數中

參數描述
decimals精度,被分割的最小單位。 10^(-1*n)
symbol符號
name名稱
description描述
icon_url圖片

方法的返回值是一個元組,包含兩個值,TreasuryCapCoinMetadata

其中 TreasuryCap 是一種資產,通過一次性見證模式保證是單體對象,其類型聲明如下

/// 允許持有者鑄造和銷毀
/// 類型為 `T` 的硬幣的能力。可轉移
struct TreasuryCap<phantom T> has key, store {
        id: UID,
        total_supply: Supply<T>
}
/// T 的供應。用於鑄造和銷燬。
/// 包裝在 `Coin` 模塊中的 `TreasuryCap` 中。
struct Supply<phantom T> has store {
    value: u64
}

這個 total_supply 值跟蹤了當前貨幣 T 的發行總量,所以 TreasuryCap 只需要一個即可。而 CoinMetadata 可以簡單看出,其保存了當前貨幣的元數據。

coin 合約代碼#

得益於 sui 框架庫的完善,想要創建一個擁有 mint (鑄造) 和 burn (銷毀) 功能的代幣合約,所需代碼很少。

// file: my_coin.move
module new_coin::my_coin {
    use std::option;
    use sui::coin::{Self, Coin, TreasuryCap};
    use sui::transfer;
    use sui::tx_context::{Self, TxContext};

    struct MY_COIN has drop {}

    fun init(witness: MY_COIN, ctx: &mut TxContext) {
        let (treasury_cap, metadata) = coin::create_currency<MY_COIN>(
            witness,
            2,
            b"MY_COIN",
            b"MC",
            b"learning for letsmove, power by alva-lin",
            option::none(),
            ctx
        );

        transfer::public_freeze_object(metadata);
        transfer::public_transfer(treasury_cap, tx_context::sender(ctx));
    }

    public fun mint(
        treasury_cap: &mut TreasuryCap<MY_COIN>,
        amount: u64,
        recipient: address,
        ctx: &mut TxContext
    ) {
        coin::mint_and_transfer(treasury_cap, amount, recipient, ctx);
    }

    public fun burn(treasury_cap: &mut TreasuryCap<MY_COIN>, coin: Coin<MY_COIN>) {
        coin::burn(treasury_cap, coin);
    }

    #[test_only]
    public fun test_init(ctx: &mut TxContext) {
        init(MY_COIN {}, ctx);
    }
}

my_coin 模塊先根據 Witness 模式 定義了名為 MY_COIN 的結構體。隨後在 init 方法參數中,添加一個 MY_COIN 類型的 witness 資源,由模塊推送後自動創建。

init 方法中,調用了 coin::create_currency 方法,獲取 TreasuryCapCoinMetadata 資源,並立即將 CoinMetadata 冷凍。

TreasuryCap 作為控制調用 mint 方法和 burn 方法的憑證,被發送到了發布者的地址上。

測試#

測試代碼如下,模擬了一個多重交易時間,來進行 mint 操作和 burn 操作

#[test_only]
module new_coin::my_coin_tests {
    use new_coin::my_coin::{Self, MY_COIN};
    use sui::coin::{Coin, TreasuryCap};
    use sui::test_scenario::{Self, next_tx, ctx};

    #[test]
    fun mint_burn() {
        // 初始化一個模擬發送者地址
        let addr1 = @0xA;

        // 以 addr1 作為發送者開始多交易場景
        let scenario = test_scenario::begin(addr1);

        // 運行貨幣模塊初始化函數
        {
            my_coin::test_init(ctx(&mut scenario))
        };

        // 鑄造一個 `Coin<MY_COIN>` 對象
        next_tx(&mut scenario, addr1);
        {
            let treasurycap = test_scenario::take_from_sender<TreasuryCap<MY_COIN>>(&scenario);
            my_coin::mint(&mut treasurycap, 100, addr1, test_scenario::ctx(&mut scenario));
            test_scenario::return_to_address<TreasuryCap<MY_COIN>>(addr1, treasurycap);
        };

        // 銷毀一個 `Coin<MY_COIN>` 對象
        next_tx(&mut scenario, addr1);
        {
            let coin = test_scenario::take_from_sender<Coin<MY_COIN>>(&scenario);
            let treasurycap = test_scenario::take_from_sender<TreasuryCap<MY_COIN>>(&scenario);
            my_coin::burn(&mut treasurycap, coin);
            test_scenario::return_to_address<TreasuryCap<MY_COIN>>(addr1, treasurycap);
        };

        // 清理場景對象
        test_scenario::end(scenario);
    }
}

發布#

先切換網絡到所需的環境

sui client switch --env mainnet

在項目文件夾下,執行以下命令,來發布包或單獨發布模塊

# 發布包
sui client publish --gas-budget 30000

# 或者按需發布模塊
# 發布模塊
# sui client publish sources/my_coin.move --gas-budget 30000

發布後的輸出結果中,包含了

  • Package: 位於 Object Changes > Published Objects 塊中

  • CoinMetadata: 位於 Object Changes > Created Objects 塊中

    其 ObjectType 為 0x2::coin::CoinMetadata<<Package ID>::<Module>::<Witness Type>>

  • TreasuryCap: 位於 Object Changes > Created Objects

    其 ObjectType 為 0x2::coin::TreasuryCap<<Package ID>::<Module>::<Witness Type>>

Package IDTreasuryCap ID 記錄:

export PACKAGE_ID=0xf9623587d620d26024868578a3539642993e2da44598c3fcaa1f384f5327f6a5
export TREASURYCAP_ID=0x97233be4acd1688c93f2c60ce81350b993a810c6b4851e8cdf214402759fad88

鑄造和銷毀#

使用 sui cli 來調用模塊相應的模塊函數即可。

需要注意,想要的代幣數量 = 傳入值 * 10^(-1*n),其中 n 為前面發布的合約代碼中,decimals 的值

在本文中,decimals 的值為 2。如果想要鑄造 100 枚代幣,則需要傳值 10000

# 轉入的錢包地址
export RECIPIENT_ADDRESS=<YOUR_WALLET_ADDRESS>
# 鑄造
sui client call --gas-budget 3000 --package $PACKAGE_ID --module my_coin --function mint --args $TREASURYCAP_ID \"10000\" $RECIPIENT_ADDRESS

在輸出內容中,可以看到交易詳情。

又或者是拿到交易 hash,可以去 SuiScan 或其他 sui 區塊瀏覽器上查看交易詳情。

# 交易 hash 的字樣類似以下形式,一般在輸出內容的最前面
Transaction Digest: <hash>

在鑄造時的輸出內容中,找到 Object Changes > Created Objects 塊中,ObjectType 類型為 0x2::coin::Coin<<package>, <module>, <type>> 的對象,獲取 ObjectID

這個 ObjectID 即為 CoinID,將其存為變量

如果找不到命令行輸出的 coin id,也可以從區塊瀏覽器中找到。

how to find coin id

export COIN_ID=0x3460204ae7f9385df79dc963a17d8b11eb0fa7a699f7196fac80405e1777d36c

調用 burn 方法,銷毀貨幣

# 注意 gas 預算需要提高
# 銷毀
sui client call --gas-budget 7500000 --package $PACKAGE_ID --module joker --function burn --args $TREASURYCAP_ID $COIN_ID

命令執行成功後,可以從錢包中看到,之前鑄造的這筆金額被刪除了

代幣銷毀後,不能使用 COIN ID 直接在區塊瀏覽器中搜索到,只能從歷史交易中間接找到其記錄

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。