基于coin创建的同质化代币(1)

  • shaflow01
  • 更新于 2024-02-01 01:50
  • 阅读 967

本文通过对sui-framework包下的coin相关实现进行部分解读,了解move中如何基于coin创建同质化代币代币创建阅读Coin合约的实现

引言

本文通过对sui-framework包下的coin相关实现进行部分解读,了解move中如何基于coin创建同质化代币

代币创建

阅读Coin合约的实现:

首先来看两个基本的obj


 struct CoinMetadata<phantom T> has key, store {
        id: UID,
        /// Number of decimal places the coin uses.
        /// A coin with `value ` N and `decimals` D should be shown as N / 10^D
        /// E.g., a coin with `value` 7002 and decimals 3 should be displayed as 7.002
        /// This is metadata for display usage only.
        decimals: u8,
        /// Name for the token
        name: string::String,
        /// Symbol for the token
        symbol: ascii::String,
        /// Description of the token
        description: string::String,
        /// URL for the token logo
        icon_url: Option<Url>
    }

这个obj定义了同质化代币的元数据,包括小数位数,代币名称,代币标志,代币的描述和url(可为空)。


    struct TreasuryCap<phantom T> has key, store {
        id: UID,
        total_supply: Supply<T>
    }

这个obj是同质化代币权限凭证,用有此凭证的人才可以进行铸币和销毁操作。同样地,泛型T也是一次性见证,用来区别代币种类。

那么我们要想创建一个同质化代币,应该进行什么操作呢?

  • 生成一次性见证:coin合约创建代币需要通过一次性见证来区分代币类型,并且由于一次性见证是唯一的,只有一个实例,且使用完被消耗。保证相同类型的代币不会被重复创建。
  • 调用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>) {
        // Make sure there's only one instance of the type T
        assert!(sui::types::is_one_time_witness(&witness), EBadWitness);

        (
            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后,coin合约检查输入的witness是否为一次性见证,之后会为我们的代币创建TreasuryCap obj和CoinMetadata obj并返回。

  • 处理返回的TreasuryCap 和CoinMetadata CoinMetadata是我们的代币元数据 如果使用transfer::public_freeze_object将其变为不可变对象,使其不能更改、传输或删除,但所有人都可以使用。
    如果后续需要更改代币元数据请勿这样处理。coin合约提供了一些方法允许TreasuryCap所有者对元数据进行更改
    TreasuryCap是权限凭证,因此一般将所有权转移给代币的主人,也就是交易发起者

实例:

module token::token{

    use std::option;
    use sui::coin::{Self, Coin, TreasuryCap};
    use sui::transfer;
    use sui::tx_context::{Self, TxContext};

    struct TOKEN has drop{}

    fun init(witness: TOKEN,ctx: &mut TxContext){
        let (treasury_cap,metadata) = coin::create_currency<TOKEN>(witness,18,b"shaflow",b"shaflow01",b"",option::none(),ctx);
        transfer::public_freeze_object(metadata);
        transfer::public_transfer(treasury_cap,tx_context::sender(ctx));
    }

}

在合约的构造函数中,我调用coin的create_currency创建了一个名称为shaflow的代币,并且将权限凭证转移给了合约创建者。

代币流通

先阅读合约实现
sui-framework/coin:

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

这是一个obj,实现了key和store能力。其中参数T封装了一次性见证,用于区分代币种类。
可以将Coin理解一类同质化代币的小钱包,T就区分了钱包中封装了什么类型的代币,balance代表了封装的代币的余额,Coin的所有者就规定了这个钱包是属于谁的。而我们花费代币时,一般需要将我们拥有的Coin传入。
在交易过程中,可能有Coin封装balance生成,也可能有Coin被销毁,其中的balance被提取出来。

这是balance的类型
sui-framework/balance:

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

balance合约中实现了与balance相关的函数:

sui-framework/balance:

    public fun value<T>(self: &Balance<T>): u64 {
        self.value
    }

    public fun zero<T>(): Balance<T> {
        Balance { value: 0 }
    }

    public fun destroy_zero<T>(balance: Balance<T>) {
        assert!(balance.value == 0, ENonZero);
        let Balance { value: _ } = balance;
    }

    public fun join<T>(self: &mut Balance<T>, balance: Balance<T>): u64 {
        let Balance { value } = balance;
        self.value = self.value + value;
        self.value
    }

    public fun split<T>(self: &mut Balance<T>, value: u64): Balance<T> {
        assert!(self.value >= value, ENotEnough);
        self.value = self.value - value;
        Balance { value }
    }

    public fun withdraw_all<T>(self: &mut Balance<T>): Balance<T> {
        let value = self.value;
        split(self, value)
    }
  • value: 传入balance的不可变引用,返回balance中的value值
  • zero:创建一个value为0的balance并返回
  • destroy_zero:用于销毁一个value为0的balance。传入balance。
  • join:用于将两个balance合并为一个balance。传入第一个balance的可变引用,第二个将直接对象传入。这样第一个balance的value将会被加上第二个balance拥有的value,之后第二个balance被释放销毁。
  • split: 用于拆分一个balance。传入带拆分balance的可变引用,然后传入要拆分的value。balance减去value,并新创建一个含有value的balance对象并返回。
  • withdraw_all: 将一个balance的value全部拆分,但是保留原balance对象

还记得supply吗?它被封装在TreasuryCap中,代表我们代币的总供应。让我们也阅读一下它的类型和相关函数的实现

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

    public fun increase_supply<T>(self: &mut Supply<T>, value: u64): Balance<T> {
        assert!(value < (18446744073709551615u64 - self.value), EOverflow);
        self.value = self.value + value;
        Balance { value }
    }

    public fun decrease_supply<T>(self: &mut Supply<T>, balance: Balance<T>): u64 {
        let Balance { value } = balance;
        assert!(self.value >= value, EOverflow);
        self.value = self.value - value;
        value 
    }
  • increase_supply: 增加供应,传入supply的可变引用,要增加的供应量。函数最终返回了含有等量value的balance对象
  • decrease_supply: 减少供应,传入supply的可变引用,balance对象,最终supply减少,相应的balance对象被销毁
  • 注:assert!(value < (18446744073709551615u64 - self.value), EOverflow)与assert!(self.value >= value, EOverflow)是为了防止溢出。rust中存在溢出检查,但是在Release模式下发布不会存在溢出检查,所以猜测move的包是在Release模式下发布。

铸造代币

了解了这些,我们来看看如何铸造代币
coin合约提供了两个方法

    public fun mint&lt;T>(
        cap: &mut TreasuryCap&lt;T>, value: u64, ctx: &mut TxContext,
    ): Coin&lt;T> {
        Coin {
            id: object::new(ctx),
            balance: balance::increase_supply(&mut cap.total_supply, value)
        }
    }

    public fun mint_balance&lt;T>(
        cap: &mut TreasuryCap&lt;T>, value: u64
    ): Balance&lt;T> {
        balance::increase_supply(&mut cap.total_supply, value)
    }

通常情况下,我们只需要调用coin mint方法,函数会增加总供应并为我们返回一个Coin对象,之后我们把Coin对象转移给我们指定的铸造地址
如果需要铸造未被Coin封装的Balance,那么可以直接调用mint_balance

  • 注:需要TreasuryCap权限凭证的拥有者才可调用
    为刚才的实例合约补充mint方法
    public entry fun mint(treasury_cap: &mut TreasuryCap&lt;TOKEN>,amount: u64,receipt: address,ctx: &mut TxContext){
        let new_coin = coin::mint(treasury_cap,amount,ctx);
        transfer::public_transfer(new_coin,receipt);
    }

    而继续阅读,会发现coin合约提供了一个更便利的方法,创建Coin后转移

    public entry fun mint_and_transfer&lt;T>(
        c: &mut TreasuryCap&lt;T>, amount: u64, recipient: address, ctx: &mut TxContext
    ) {
        transfer::public_transfer(mint(c, amount, ctx), recipient)
    }

    因此可以直接调用此函数实现mint 一下方法与上述事例补充函数等效

    public entry fun mint_and_tranfer(treasury_cap: &mut TreasuryCap&lt;TOKEN>,amount: u64,receipt: address,ctx: &mut TxContext){
        coin::mint_and_transfer(treasury_cap,amount,receipt,ctx);
    }

销毁代币

    public entry fun burn&lt;T>(cap: &mut TreasuryCap&lt;T>, c: Coin&lt;T>): u64 {
        let Coin { id, balance } = c;
        object::delete(id);
        balance::decrease_supply(&mut cap.total_supply, balance)
    }

传入待销毁的coin,减少总供应,之后Coin被销毁

  • 注:需要TreasuryCap权限凭证的拥有者才可调用
    为实例合约补充burn功能
    public entry fun burn(treasury_cap:&mut TreasuryCap&lt;TOKEN>,coin: Coin&lt;TOKEN>){
        coin::burn(treasury_cap,coin);
    }

    代币转移

    Coin的拥有者可以将自己的Coin转移给其他人来实现
    也可以我们在自己的合约中实现方便调用
    接下来让我们继续完善代币合约,可以在自己的合约中定义转移规则方便代币在用户间的相互转移

    public entry fun transfer(coin:&mut Coin&lt;TOKEN>,amount: u64,receipt: address,ctx: &mut TxContext){
        let new_coin = coin::split(coin,amount,ctx);
        transfer::public_transfer(new_coin,receipt);
    }

这里没有amount做检查是因为检查会发生在balance::split

其中与coin有关的有这些函数


    public fun take&lt;T>(
        balance: &mut Balance&lt;T>, value: u64, ctx: &mut TxContext,
    ): Coin&lt;T> {
        Coin {
            id: object::new(ctx),
            balance: balance::split(balance, value)
        }
    }

    public entry fun join&lt;T>(self: &mut Coin&lt;T>, c: Coin&lt;T>) {
        let Coin { id, balance } = c;
        object::delete(id);
        balance::join(&mut self.balance, balance);
    }

    public fun split&lt;T>(
        self: &mut Coin&lt;T>, split_amount: u64, ctx: &mut TxContext
    ): Coin&lt;T> {
        take(&mut self.balance, split_amount, ctx)
    }

完整实例代码:

module token::token{

    use std::option;
    use sui::coin::{Self, Coin, TreasuryCap};
    use sui::transfer;
    use sui::tx_context::{Self, TxContext};
    use sui::event;

    struct TOKEN has drop{}

    fun init(witness: TOKEN,ctx: &mut TxContext){
        let (treasury_cap,metadata) = coin::create_currency&lt;TOKEN>(witness,18,b"SULC",b"SUL COIN",b"",option::none(),ctx);
        transfer::public_freeze_object(metadata);
        transfer::public_transfer(treasury_cap,tx_context::sender(ctx));
    }

    public entry fun mint_and_transfer(treasury_cap: &mut TreasuryCap&lt;TOKEN>,amount: u64,receipt: address,ctx: &mut TxContext){
        coin::mint_and_transfer(treasury_cap,amount,receipt,ctx);
    }

    public entry fun burn(treasury_cap:&mut TreasuryCap&lt;TOKEN>,coin: Coin&lt;TOKEN>){
        coin::burn(treasury_cap,coin);
    }

    public entry fun mint(treasury_cap: &mut TreasuryCap&lt;TOKEN>,amount: u64,receipt: address,ctx: &mut TxContext){
        let new_coin = coin::mint(treasury_cap,amount,ctx);
        transfer::public_transfer(new_coin,receipt);
    }

    public entry fun transfer(coin:&mut Coin&lt;TOKEN>,amount: u64,receipt: address,ctx: &mut TxContext){
        let new_coin = coin::split(coin,amount,ctx);
        transfer::public_transfer(new_coin,receipt);
    }

}

上述是基于coin合约创建同质化代币的基本内容。coin合约中还有提供的一些函数没有涉及,待继续阅读分析。 (未完待续)

Move语言学习交流QQ群: 79489587 Sui官方中文开发者电报群: https://t.me/sui_dev_cn

  • 原创
  • 学分: 12
  • 分类: Sui
  • 标签:
点赞 2
收藏 1
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
shaflow01
shaflow01
0x4937...bA76
江湖只有他的大名,没有他的介绍。