Let's Move 是一項學習 Move 語言的激勵計畫,鼓勵更多的人學習 Move 語言
學習日誌 (alva-lin)
任務 2 - 完成兩個 Coin 合約的上鏈部署 (mainnet)#
任務
完成 Coin 相關知識的學習
完成
My Coin
的學習並部署主網完成
Faucet Coin
的學習並部署主網提交
My Coin
和Faucet 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 | 圖片 |
方法的返回值是一個元組,包含兩個值,TreasuryCap
和 CoinMetadata
,
其中 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
方法,獲取 TreasuryCap
和 CoinMetadata
資源,並立即將 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 ID
和 TreasuryCap 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,也可以從區塊瀏覽器中找到。
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 直接在區塊瀏覽器中搜索到,只能從歷史交易中間接找到其記錄