{"content":{"title":"基于coin创建的同质化代币(1)","body":"### 引言\r\n本文通过对sui-framework包下的coin相关实现进行部分解读，了解move中如何基于coin创建同质化代币  \r\n\r\n\r\n### 代币创建\r\n阅读Coin合约的实现：  \r\n\r\n首先来看两个基本的obj  \r\n    \r\n\r\n```move\r\n\r\n struct CoinMetadata<phantom T> has key, store {\r\n        id: UID,\r\n        /// Number of decimal places the coin uses.\r\n        /// A coin with `value ` N and `decimals` D should be shown as N / 10^D\r\n        /// E.g., a coin with `value` 7002 and decimals 3 should be displayed as 7.002\r\n        /// This is metadata for display usage only.\r\n        decimals: u8,\r\n        /// Name for the token\r\n        name: string::String,\r\n        /// Symbol for the token\r\n        symbol: ascii::String,\r\n        /// Description of the token\r\n        description: string::String,\r\n        /// URL for the token logo\r\n        icon_url: Option<Url>\r\n    }\r\n\r\n```\r\n\r\n这个obj定义了同质化代币的元数据，包括小数位数，代币名称，代币标志，代币的描述和url（可为空）。  \r\n\r\n```move\r\n\r\n    struct TreasuryCap<phantom T> has key, store {\r\n        id: UID,\r\n        total_supply: Supply<T>\r\n    }\r\n\r\n```\r\n这个obj是同质化代币权限凭证，用有此凭证的人才可以进行铸币和销毁操作。同样地，泛型T也是一次性见证，用来区别代币种类。  \r\n\r\n那么我们要想创建一个同质化代币，应该进行什么操作呢？\r\n\r\n* 生成一次性见证：coin合约创建代币需要通过一次性见证来区分代币类型，并且由于一次性见证是唯一的，只有一个实例，且使用完被消耗。保证相同类型的代币不会被重复创建。\r\n* 调用Coin合约的create_currency函数：\r\n\r\n```move\r\n public fun create_currency<T: drop>(\r\n        witness: T,\r\n        decimals: u8,\r\n        symbol: vector<u8>,\r\n        name: vector<u8>,\r\n        description: vector<u8>,\r\n        icon_url: Option<Url>,\r\n        ctx: &mut TxContext\r\n    ): (TreasuryCap<T>, CoinMetadata<T>) {\r\n        // Make sure there's only one instance of the type T\r\n        assert!(sui::types::is_one_time_witness(&witness), EBadWitness);\r\n\r\n        (\r\n            TreasuryCap {\r\n                id: object::new(ctx),\r\n                total_supply: balance::create_supply(witness)\r\n            },\r\n            CoinMetadata {\r\n                id: object::new(ctx),\r\n                decimals,\r\n                name: string::utf8(name),\r\n                symbol: ascii::string(symbol),\r\n                description: string::utf8(description),\r\n                icon_url\r\n            }\r\n        )\r\n    }\r\n```\r\n调用create_currency后，coin合约检查输入的witness是否为一次性见证，之后会为我们的代币创建TreasuryCap obj和CoinMetadata obj并返回。\r\n* 处理返回的TreasuryCap 和CoinMetadata\r\nCoinMetadata是我们的代币元数据\r\n如果使用transfer::public_freeze_object将其变为不可变对象，使其不能更改、传输或删除，但所有人都可以使用。  \r\n如果后续需要更改代币元数据请勿这样处理。coin合约提供了一些方法允许TreasuryCap所有者对元数据进行更改  \r\nTreasuryCap是权限凭证，因此一般将所有权转移给代币的主人，也就是交易发起者  \r\n  \r\n实例:  \r\n```move\r\nmodule token::token{\r\n\r\n    use std::option;\r\n    use sui::coin::{Self, Coin, TreasuryCap};\r\n    use sui::transfer;\r\n    use sui::tx_context::{Self, TxContext};\r\n\r\n    struct TOKEN has drop{}\r\n\r\n    fun init(witness: TOKEN,ctx: &mut TxContext){\r\n        let (treasury_cap,metadata) = coin::create_currency<TOKEN>(witness,18,b\"shaflow\",b\"shaflow01\",b\"\",option::none(),ctx);\r\n        transfer::public_freeze_object(metadata);\r\n        transfer::public_transfer(treasury_cap,tx_context::sender(ctx));\r\n    }\r\n\r\n}\r\n```\r\n在合约的构造函数中，我调用coin的create_currency创建了一个名称为shaflow的代币，并且将权限凭证转移给了合约创建者。\r\n\r\n### 代币流通  \r\n\r\n先阅读合约实现  \r\nsui-framework/coin：\r\n```move\r\n    struct Coin<phantom T> has key, store {\r\n        id: UID,\r\n        balance: Balance<T>\r\n    }\r\n```\r\n\r\n这是一个obj，实现了key和store能力。其中参数T封装了一次性见证，用于区分代币种类。  \r\n可以将Coin理解一类同质化代币的小钱包，T就区分了钱包中封装了什么类型的代币，balance代表了封装的代币的余额，Coin的所有者就规定了这个钱包是属于谁的。而我们花费代币时，一般需要将我们拥有的Coin传入。  \r\n在交易过程中，可能有Coin封装balance生成，也可能有Coin被销毁，其中的balance被提取出来。  \r\n  \r\n这是balance的类型  \r\nsui-framework/balance：\r\n```move\r\n    struct Balance<phantom T> has store {\r\n        value: u64\r\n    }\r\n```\r\nbalance合约中实现了与balance相关的函数:\r\n\r\nsui-framework/balance：\r\n```move\r\n    public fun value<T>(self: &Balance<T>): u64 {\r\n        self.value\r\n    }\r\n\r\n    public fun zero<T>(): Balance<T> {\r\n        Balance { value: 0 }\r\n    }\r\n\r\n    public fun destroy_zero<T>(balance: Balance<T>) {\r\n        assert!(balance.value == 0, ENonZero);\r\n        let Balance { value: _ } = balance;\r\n    }\r\n\r\n    public fun join<T>(self: &mut Balance<T>, balance: Balance<T>): u64 {\r\n        let Balance { value } = balance;\r\n        self.value = self.value + value;\r\n        self.value\r\n    }\r\n\r\n    public fun split<T>(self: &mut Balance<T>, value: u64): Balance<T> {\r\n        assert!(self.value >= value, ENotEnough);\r\n        self.value = self.value - value;\r\n        Balance { value }\r\n    }\r\n\r\n    public fun withdraw_all<T>(self: &mut Balance<T>): Balance<T> {\r\n        let value = self.value;\r\n        split(self, value)\r\n    }\r\n```\r\n* value: 传入balance的不可变引用，返回balance中的value值  \r\n* zero：创建一个value为0的balance并返回  \r\n* destroy_zero：用于销毁一个value为0的balance。传入balance。\r\n* join：用于将两个balance合并为一个balance。传入第一个balance的可变引用，第二个将直接对象传入。这样第一个balance的value将会被加上第二个balance拥有的value，之后第二个balance被释放销毁。\r\n* split： 用于拆分一个balance。传入带拆分balance的可变引用，然后传入要拆分的value。balance减去value，并新创建一个含有value的balance对象并返回。\r\n* withdraw_all: 将一个balance的value全部拆分，但是保留原balance对象\r\n\r\n还记得supply吗？它被封装在TreasuryCap中，代表我们代币的总供应。让我们也阅读一下它的类型和相关函数的实现\r\n```move\r\n    struct Supply<phantom T> has store {\r\n        value: u64\r\n    }\r\n```\r\n\r\n```move\r\n\r\n    public fun increase_supply<T>(self: &mut Supply<T>, value: u64): Balance<T> {\r\n        assert!(value < (18446744073709551615u64 - self.value), EOverflow);\r\n        self.value = self.value + value;\r\n        Balance { value }\r\n    }\r\n\r\n    public fun decrease_supply<T>(self: &mut Supply<T>, balance: Balance<T>): u64 {\r\n        let Balance { value } = balance;\r\n        assert!(self.value >= value, EOverflow);\r\n        self.value = self.value - value;\r\n        value \r\n    }\r\n\r\n```\r\n* increase_supply： 增加供应，传入supply的可变引用，要增加的供应量。函数最终返回了含有等量value的balance对象  \r\n* decrease_supply： 减少供应，传入supply的可变引用，balance对象，最终supply减少，相应的balance对象被销毁  \r\n* 注：assert!(value < (18446744073709551615u64 - self.value), EOverflow)与assert!(self.value >= value, EOverflow)是为了防止溢出。rust中存在溢出检查，但是在Release模式下发布不会存在溢出检查，所以猜测move的包是在Release模式下发布。  \r\n\r\n#### 铸造代币\r\n了解了这些，我们来看看如何铸造代币  \r\ncoin合约提供了两个方法\r\n\r\n```move\r\n    public fun mint<T>(\r\n        cap: &mut TreasuryCap<T>, value: u64, ctx: &mut TxContext,\r\n    ): Coin<T> {\r\n        Coin {\r\n            id: object::new(ctx),\r\n            balance: balance::increase_supply(&mut cap.total_supply, value)\r\n        }\r\n    }\r\n\r\n    public fun mint_balance<T>(\r\n        cap: &mut TreasuryCap<T>, value: u64\r\n    ): Balance<T> {\r\n        balance::increase_supply(&mut cap.total_supply, value)\r\n    }\r\n```\r\n通常情况下，我们只需要调用coin mint方法，函数会增加总供应并为我们返回一个Coin对象，之后我们把Coin对象转移给我们指定的铸造地址  \r\n如果需要铸造未被Coin封装的Balance，那么可以直接调用mint_balance\r\n\r\n\r\n* 注：需要TreasuryCap权限凭证的拥有者才可调用  \r\n为刚才的实例合约补充mint方法  \r\n```move\r\n    public entry fun mint(treasury_cap: &mut TreasuryCap<TOKEN>,amount: u64,receipt: address,ctx: &mut TxContext){\r\n        let new_coin = coin::mint(treasury_cap,amount,ctx);\r\n        transfer::public_transfer(new_coin,receipt);\r\n    }\r\n```\r\n而继续阅读，会发现coin合约提供了一个更便利的方法，创建Coin后转移\r\n```move\r\n    public entry fun mint_and_transfer<T>(\r\n        c: &mut TreasuryCap<T>, amount: u64, recipient: address, ctx: &mut TxContext\r\n    ) {\r\n        transfer::public_transfer(mint(c, amount, ctx), recipient)\r\n    }\r\n```\r\n因此可以直接调用此函数实现mint  一下方法与上述事例补充函数等效\r\n```move\r\n    public entry fun mint_and_tranfer(treasury_cap: &mut TreasuryCap<TOKEN>,amount: u64,receipt: address,ctx: &mut TxContext){\r\n        coin::mint_and_transfer(treasury_cap,amount,receipt,ctx);\r\n    }\r\n```\r\n\r\n#### 销毁代币\r\n\r\n```move\r\n    public entry fun burn<T>(cap: &mut TreasuryCap<T>, c: Coin<T>): u64 {\r\n        let Coin { id, balance } = c;\r\n        object::delete(id);\r\n        balance::decrease_supply(&mut cap.total_supply, balance)\r\n    }\r\n```\r\n传入待销毁的coin，减少总供应，之后Coin被销毁  \r\n* 注：需要TreasuryCap权限凭证的拥有者才可调用  \r\n为实例合约补充burn功能  \r\n```move\r\n    public entry fun burn(treasury_cap:&mut TreasuryCap<TOKEN>,coin: Coin<TOKEN>){\r\n        coin::burn(treasury_cap,coin);\r\n    }\r\n```\r\n#### 代币转移\r\nCoin的拥有者可以将自己的Coin转移给其他人来实现  \r\n也可以我们在自己的合约中实现方便调用  \r\n接下来让我们继续完善代币合约，可以在自己的合约中定义转移规则方便代币在用户间的相互转移  \r\n\r\n```move\r\n    public entry fun transfer(coin:&mut Coin<TOKEN>,amount: u64,receipt: address,ctx: &mut TxContext){\r\n        let new_coin = coin::split(coin,amount,ctx);\r\n        transfer::public_transfer(new_coin,receipt);\r\n    }\r\n```\r\n这里没有amount做检查是因为检查会发生在balance::split    \r\n\r\n其中与coin有关的有这些函数  \r\n```move\r\n\r\n    public fun take<T>(\r\n        balance: &mut Balance<T>, value: u64, ctx: &mut TxContext,\r\n    ): Coin<T> {\r\n        Coin {\r\n            id: object::new(ctx),\r\n            balance: balance::split(balance, value)\r\n        }\r\n    }\r\n\r\n    public entry fun join<T>(self: &mut Coin<T>, c: Coin<T>) {\r\n        let Coin { id, balance } = c;\r\n        object::delete(id);\r\n        balance::join(&mut self.balance, balance);\r\n    }\r\n\r\n\r\n    public fun split<T>(\r\n        self: &mut Coin<T>, split_amount: u64, ctx: &mut TxContext\r\n    ): Coin<T> {\r\n        take(&mut self.balance, split_amount, ctx)\r\n    }\r\n\r\n```\r\n完整实例代码：\r\n```move\r\nmodule token::token{\r\n\r\n    use std::option;\r\n    use sui::coin::{Self, Coin, TreasuryCap};\r\n    use sui::transfer;\r\n    use sui::tx_context::{Self, TxContext};\r\n    use sui::event;\r\n\r\n\r\n    struct TOKEN has drop{}\r\n\r\n\r\n    fun init(witness: TOKEN,ctx: &mut TxContext){\r\n        let (treasury_cap,metadata) = coin::create_currency<TOKEN>(witness,18,b\"SULC\",b\"SUL COIN\",b\"\",option::none(),ctx);\r\n        transfer::public_freeze_object(metadata);\r\n        transfer::public_transfer(treasury_cap,tx_context::sender(ctx));\r\n    }\r\n\r\n    public entry fun mint_and_transfer(treasury_cap: &mut TreasuryCap<TOKEN>,amount: u64,receipt: address,ctx: &mut TxContext){\r\n        coin::mint_and_transfer(treasury_cap,amount,receipt,ctx);\r\n    }\r\n\r\n    public entry fun burn(treasury_cap:&mut TreasuryCap<TOKEN>,coin: Coin<TOKEN>){\r\n        coin::burn(treasury_cap,coin);\r\n    }\r\n\r\n    public entry fun mint(treasury_cap: &mut TreasuryCap<TOKEN>,amount: u64,receipt: address,ctx: &mut TxContext){\r\n        let new_coin = coin::mint(treasury_cap,amount,ctx);\r\n        transfer::public_transfer(new_coin,receipt);\r\n    }\r\n\r\n    public entry fun transfer(coin:&mut Coin<TOKEN>,amount: u64,receipt: address,ctx: &mut TxContext){\r\n        let new_coin = coin::split(coin,amount,ctx);\r\n        transfer::public_transfer(new_coin,receipt);\r\n    }\r\n\r\n}\r\n```\r\n上述是基于coin合约创建同质化代币的基本内容。coin合约中还有提供的一些函数没有涉及，待继续阅读分析。\r\n（未完待续）\r\n\r\nMove语言学习交流QQ群: 79489587\r\nSui官方中文开发者电报群: https://t.me/sui_dev_cn"},"author":{"user":"https://learnblockchain.cn/people/18488","address":null},"history":null,"timestamp":1706723440,"version":1}