{"content":{"title":"sui move 动态字段练习(1)","body":"## 引言\r\n学习了sui move中的动态字段，table，bag，作为练习，我准备使用它们模拟solidity中的映射类型，在sui move实现一个**类似**erc20的同质化代币作为之前学习的实践与巩固。本文分享了练习过程中的obj设计、create token。  \r\n注：本例实现仅用于学习动态字段，由于访问gas和便捷性不强，无法用于生产。在sui move中使用的同质化代币请使用官方标准库中内置的coin  \r\n## obj设计\r\n\r\n### balance\r\n首先，我是用如下结构体模拟balanceOf映射  \r\n```rust\r\n    struct BalanceData<phantom T> has key, store{\r\n        id: UID,\r\n        balance:Table<address,u64>,\r\n        totalsupply: u64,\r\n    }\r\n\r\n    struct BalanceList has key,store{\r\n        id: UID,\r\n        balance_list: Bag,\r\n    }\r\n```\r\nBalanceList obj中的balance_list预计存储不同类型代币的余额相关信息  它的键值对将会是type(T) -> BalanceData<T<T>>  \r\nBalanceData中的totalsupply字段储存这一种类型代币的总供应  \r\nbalance字段储存某一地址代币余额  \r\n  \r\n### allowance\r\n以下obj用于实现allowance  \r\n```rust\r\n    struct AllowanceData<phantom T> has key, store{\r\n        id: UID,\r\n        allowance:Table<address , AllowanceAmountList>,\r\n    }\r\n\r\n    struct AllowanceAmountList has key, store{\r\n        id: UID,\r\n        allowance_amount: Table<address, u64>,\r\n    }\r\n\r\n    struct AllowanceList has key,store{\r\n        id: UID,\r\n        allowance_list: Bag,\r\n    }\r\n```\r\n### 元数据\r\n```rust\r\n    struct ERC20MetaData<phantom T> has key, store{\r\n        id: UID,\r\n        name: string::String,\r\n        symbol: ascii::String,\r\n        decimal: u8,\r\n    }\r\n```\r\n在代币创建时要输入创建的obj，是代币的元数据，将会是share_obj  \r\n\r\n\r\n### Cap\r\n```rust\r\n    struct TreasuryCap<phantom T> has key,store{\r\n        id:UID,\r\n    }\r\n\r\n    struct TokenCap<phantom T> has key{\r\n        id: UID,\r\n    }\r\n```\r\n定义了两个cap  \r\nTreasuryCap持有者有权利铸造销毁代币  \r\nTokenCap将会是share_obj，在转账等其他操作中传入，用于区分代币类型  \r\n\r\n## 构造函数  \r\n```rust\r\n    fun init(ctx:&mut TxContext){\r\n\r\n        let balance_list = BalanceList{\r\n            id: object::new(ctx),\r\n            balance_list: bag::new(ctx),\r\n        };\r\n\r\n        let allowance_list = AllowanceList{\r\n            id: object::new(ctx),\r\n            allowance_list: bag::new(ctx),\r\n        };\r\n\r\n        transfer::share_object(balance_list);\r\n        transfer::share_object(allowance_list);\r\n    }\r\n```\r\n在构造函数中，将BalanceList和AllowanceList初始化，将他们设置成为share_obj。\r\n\r\n### 创建代币\r\n```rust\r\n    public fun create_token<T: drop>(witness: T, name: vector<u8>, symbol: vector<u8>, decimal:u8, ctx:&mut TxContext):TreasuryCap<T>{\r\n\r\n        assert!(sui::types::is_one_time_witness(&witness), EBadWitness);\r\n\r\n        let erc20_metadata = ERC20MetaData<T>{\r\n            id: object::new(ctx),\r\n            name: string::utf8(name),\r\n            symbol: ascii::string(symbol),\r\n            decimal: decimal,\r\n        };\r\n\r\n        let treasury_cap = TreasuryCap<T>{\r\n            id: object::new(ctx),\r\n        };\r\n\r\n        let token_cap = TokenCap<T>{\r\n            id: object::new(ctx),\r\n        };\r\n\r\n        transfer::share_object(erc20_metadata);\r\n        transfer::share_object(token_cap);\r\n        treasury_cap\r\n    }\r\n\r\n    public fun init_token<T>(_:&TokenCap<T>,balance_list: &mut BalanceList, allowance_list: &mut AllowanceList,ctx:&mut TxContext){\r\n        let type = ascii::into_bytes(type_name::into_string(type_name::get_with_original_ids<T>()));\r\n        assert!(!bag::contains(& balance_list.balance_list, type),0);\r\n        assert!(!bag::contains(& allowance_list.allowance_list, type),0);\r\n        let balance_data = BalanceData<T>{\r\n            id: object::new(ctx),\r\n            balance: table::new(ctx),\r\n            totalsupply: 0,\r\n        };\r\n\r\n        bag::add(&mut balance_list.balance_list, type, balance_data);\r\n        let allowance_data = AllowanceData<T>{\r\n            id: object::new(ctx),\r\n            allowance: table::new(ctx),\r\n        };\r\n\r\n        bag::add(&mut allowance_list.allowance_list, type, allowance_data);\r\n    }\r\n```\r\n创建代币需要先后调用create_token和init_token，它们分别做了什么呢？\r\ncreate_token:  \r\n调用create_token需要传入元数据相关信息和一次性见证（区分代币种类）  \r\n1. 检查一次性见证  \r\n2. 生成元数据相关信息  \r\n3. 生成treasury_cap，token_cap  \r\ninit_token：  \r\n调用init_token是为了初始化对应代币的AllowanceData和BalanceData，它们将只会被初始化一次：  \r\n1. 检查对应type的token是否被初始化  \r\n2. 创建AllowanceData，BalanceData\r\n3. 将其添加到BalanceList和AllowanceList中\r\n\r\n\r\n## test\r\n在进行test之前，我们在erc20.move中添加一个只能在测试中被调用的函数，其目的是模拟erc20进行部署时的初始化  \r\n```rust\r\n    #[test_only]\r\n    public fun test_init(ctx:&mut TxContext){\r\n        init(ctx);\r\n    }\r\n```\r\n接下来我们新建一个文件erc20test.move来测试我们刚刚完成的模块  \r\n\r\n```rust\r\n#[test_only]\r\nmodule erc20::erc20test{\r\n    use erc20::erc20::{Self,TokenCap,BalanceList,TreasuryCap};\r\n    use sui::test_scenario::{Self, Scenario};\r\n    use sui::tx_context::{Self,TxContext};\r\n    use sui::transfer;\r\n\r\n    struct ERC20TEST has drop{}\r\n    fun init(witness: ERC20TEST, ctx: &mut TxContext){\r\n        let treasury_cap = erc20::create_token(witness,b\"ETC20Test\", b\"ERCT\", 18, ctx);\r\n        transfer::public_transfer(treasury_cap, tx_context::sender(ctx));\r\n    }\r\n\r\n    #[test]\r\n    public fun test(){\r\n        let addr1 = @0xA;\r\n        let scenario = test_scenario::begin(addr1);\r\n        //1. create a token\r\n        {\r\n            erc20::test_init(test_scenario::ctx(&mut scenario));\r\n            test_scenario::next_tx(&mut scenario, addr1);\r\n            init(ERC20TEST{}, test_scenario::ctx(&mut scenario));\r\n            test_scenario::next_tx(&mut scenario, addr1);\r\n            let balance_list = test_scenario::take_shared<BalanceList>(&mut scenario);\r\n            let allowance_list= test_scenario::take_shared(&mut scenario);\r\n            let token_cap: TokenCap<ERC20TEST> = test_scenario::take_shared(&mut scenario);\r\n            erc20::init_token(&token_cap,&mut balance_list,&mut allowance_list, test_scenario::ctx(&mut scenario));\r\n            test_scenario::return_shared(balance_list);\r\n            test_scenario::return_shared(allowance_list);\r\n            test_scenario::return_shared(token_cap);\r\n        };\r\n        test_scenario::end(scenario);\r\n    }\r\n}\r\n```\r\n测试中先初始化了erc20 module然后再初始化test的过程中创建了ERC20TEST token，调用init_token为ERC20TEST token初始化BalanceData和AllowanceData  \r\n\r\nresult:\r\n```rust\r\nRunning Move unit tests\r\n[ PASS    ] 0x0::erc20test::test\r\nTest result: OK. Total tests: 1; passed: 1; failed: 0\r\n```"},"author":{"user":"https://learnblockchain.cn/people/18488","address":null},"history":null,"timestamp":1710060726,"version":1}