引言
本文通过对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<T>(
cap: &mut TreasuryCap<T>, value: u64, ctx: &mut TxContext,
): Coin<T> {
Coin {
id: object::new(ctx),
balance: balance::increase_supply(&mut cap.total_supply, value)
}
}
public fun mint_balance<T>(
cap: &mut TreasuryCap<T>, value: u64
): Balance<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<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<T>(
c: &mut TreasuryCap<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<TOKEN>,amount: u64,receipt: address,ctx: &mut TxContext){
coin::mint_and_transfer(treasury_cap,amount,receipt,ctx);
}
销毁代币
public entry fun burn<T>(cap: &mut TreasuryCap<T>, c: Coin<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<TOKEN>,coin: Coin<TOKEN>){
coin::burn(treasury_cap,coin);
}
代币转移
Coin的拥有者可以将自己的Coin转移给其他人来实现
也可以我们在自己的合约中实现方便调用
接下来让我们继续完善代币合约,可以在自己的合约中定义转移规则方便代币在用户间的相互转移
public entry fun transfer(coin:&mut Coin<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<T>(
balance: &mut Balance<T>, value: u64, ctx: &mut TxContext,
): Coin<T> {
Coin {
id: object::new(ctx),
balance: balance::split(balance, value)
}
}
public entry fun join<T>(self: &mut Coin<T>, c: Coin<T>) {
let Coin { id, balance } = c;
object::delete(id);
balance::join(&mut self.balance, balance);
}
public fun split<T>(
self: &mut Coin<T>, split_amount: u64, ctx: &mut TxContext
): Coin<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<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<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<TOKEN>,coin: Coin<TOKEN>){
coin::burn(treasury_cap,coin);
}
public entry fun mint(treasury_cap: &mut TreasuryCap<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<TOKEN>,amount: u64,receipt: address,ctx: &mut TxContext){
let new_coin = coin::split(coin,amount,ctx);
transfer::public_transfer(new_coin,receipt);
}
}
上述是基于coin合约创建同质化代币的基本内容。coin合约中还有提供的一些函数没有涉及,待继续阅读分析。
(未完待续)文章来源:https://www.toymoban.com/news/detail-853918.html
Move语言学习交流QQ群: 79489587
Sui官方中文开发者电报群: https://t.me/sui_dev_cn文章来源地址https://www.toymoban.com/news/detail-853918.html
到了这里,关于基于coin创建的同质化代币的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!