{"content":{"title":"sui move 动态字段练习(2)","body":"## 引言\r\n学习了sui move中的动态字段，table，bag，作为练习，我准备使用它们模拟solidity中的映射类型，在sui move实现一个**类似**erc20的同质化代币作为之前学习的实践与巩固。本文分享了练习过程中的mint,和burn,balance_of,total_supply.  \r\n  \r\n## mint函数\r\n```rust\r\n    public fun mint<T>(_: &TreasuryCap<T> ,balance_list: &mut BalanceList, to:address, value: u64, ctx:&mut TxContext): bool\r\n```\r\nmint函数用来铸造代币供应。只有持有TreasuryCap的地址才能进行铸币  \r\n需要传入BalanceList，铸币地址to，和铸币数量amount  \r\n1. 首先检查value是否为0，如果为0，直接返回  \r\n```rust\r\n        if(value == 0){\r\n            return true\r\n        };\r\n```\r\n2. 确定代币类型，检查balancelist中是否拥有此代币的余额信息（代币是否初始化），如果有，取出借用。  \r\n```rust\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), 2);\r\n        let balance_table = bag::borrow_mut< vector<u8>, BalanceData<T> >(&mut balance_list.balance_list, type);\r\n```\r\n3. 检查to是否作为键存在于table中，如果没有创建table传入value，如果有，将新铸币的余额加上  \r\n```rust\r\n        if(table::contains(&balance_table.balance, to)){\r\n            let balance_to = table::borrow_mut(&mut balance_table.balance,to);\r\n            assert!(*balance_to + value >= *balance_to, 8);\r\n            *balance_to = *balance_to + value;\r\n        }else{\r\n            table::add(&mut balance_table.balance, to, value);\r\n        };\r\n```\r\n4. 将总供应变更  \r\n```rust\r\n        let totalsupply = &mut balance_table.totalsupply;\r\n        assert!(*totalsupply + value >= *totalsupply, 8);\r\n        *totalsupply = *totalsupply + value;\r\n```\r\n\r\n5. 最后返回true  \r\n\r\nmint函数完整代码  \r\n```rust\r\n    public fun mint<T>(_: &TreasuryCap<T> ,balance_list: &mut BalanceList, to:address, value: u64, ctx:&mut TxContext): bool{\r\n        if(value == 0){\r\n            return true\r\n        };\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), 2);\r\n        let balance_table = bag::borrow_mut< vector<u8>, BalanceData<T> >(&mut balance_list.balance_list, type);\r\n        if(table::contains(&balance_table.balance, to)){\r\n            let balance_to = table::borrow_mut(&mut balance_table.balance,to);\r\n            assert!(*balance_to + value >= *balance_to, 8);\r\n            *balance_to = *balance_to + value;\r\n        }else{\r\n            table::add(&mut balance_table.balance, to, value);\r\n        };\r\n        let totalsupply = &mut balance_table.totalsupply;\r\n        assert!(*totalsupply + value >= *totalsupply, 8);\r\n        *totalsupply = *totalsupply + value;\r\n        return true\r\n    }\r\n```\r\n\r\n## burn函数\r\n```rust\r\n    public fun burn<T>(_: &TreasuryCap<T>, balance_list: &mut BalanceList, from:address, value: u64, ctx:&mut TxContext): bool\r\n```\r\nburn函数用于销毁一个地址的代币，同样是持有TreasuryCap的地址才可以销毁  \r\n传入balancelist,要销毁代币的地址，销毁数量  \r\n\r\n1. 首先检查value是否为0，如果为0，直接返回  \r\n```rust\r\n        if(value == 0){\r\n            return true\r\n        };\r\n```\r\n2. 确定代币类型，检查balancelist中是否拥有此代币的余额信息（代币是否初始化），如果有，取出借用。  \r\n```rust\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), 2);\r\n        let balance_table = bag::borrow_mut< vector<u8>, BalanceData<T> >(&mut balance_list.balance_list, type);\r\n```\r\n3. 检查from是否作为键存在于table中，如果不存在，将会回滚，如果有，余额是否足够销毁，如果不够将会回滚，之后变更余额信息和总供应信息  \r\n```rust\r\n        if(table::contains(&balance_table.balance, from)){\r\n            let balance_to = table::borrow_mut(&mut balance_table.balance,from);\r\n            assert!(*balance_to >= value, 8);\r\n\r\n            if(*balance_to > value){\r\n                *balance_to = *balance_to - value;\r\n            }\r\n            else{\r\n                table::remove(&mut balance_table.balance,from);\r\n            };\r\n\r\n            let totalsupply = &mut balance_table.totalsupply;\r\n            *totalsupply = *totalsupply - value;\r\n\r\n        }else{\r\n            assert!(false, 0)\r\n        };\r\n```\r\n4. 函数返回true  \r\n\r\nburn函数完整代码  \r\n```rust\r\n    public fun burn<T>(_: &TreasuryCap<T>, balance_list: &mut BalanceList, from:address, value: u64, ctx:&mut TxContext): bool{\r\n        if(value == 0){\r\n            return true\r\n        };\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), 2);\r\n        let balance_table = bag::borrow_mut<vector<u8>, BalanceData<T>>(&mut balance_list.balance_list, type);\r\n\r\n        if(table::contains(&balance_table.balance, from)){\r\n            let balance_to = table::borrow_mut(&mut balance_table.balance,from);\r\n            assert!(*balance_to >= value, 8);\r\n            if(*balance_to > value){\r\n                *balance_to = *balance_to - value;\r\n            }\r\n            else{\r\n                table::remove(&mut balance_table.balance,from);\r\n            };\r\n            let totalsupply = &mut balance_table.totalsupply;\r\n            *totalsupply = *totalsupply - value;\r\n        }else{\r\n            assert!(false, 0)\r\n        };\r\n        return true\r\n    }\r\n```\r\n\r\n## balance_of  \r\n设置balance_of函数用于获取余额  \r\n```rust\r\n    public fun balance_of<T>(_token_cap :& TokenCap<T>, balance_list: &mut BalanceList, addr:address, ctx:&mut TxContext):u64{\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), 2);\r\n        let balance_table = bag::borrow_mut<vector<u8>, BalanceData<T>>(&mut balance_list.balance_list, type);\r\n        if(table::contains(&balance_table.balance, addr)){\r\n            let amount = *(table::borrow(&mut balance_table.balance,addr));\r\n            return amount\r\n        };\r\n        return 0\r\n    }\r\n```\r\n1. 获取代币类型，检查代币是否初始化，如果有，取出借用，如没有，抛出异常  \r\n2. 检查addr是否作为键存在于table中，如果有，返回余额，如果没有，返回0  \r\n\r\n## total_supply\r\n设置total_supply函数获取当前总供应  \r\n```rust\r\n    public fun total_supply<T>(_token_cap :& TokenCap<T>, balance_list: &mut BalanceList, ctx:&mut TxContext): u64{\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), 2);\r\n        let balance_table = bag::borrow_mut<vector<u8>, BalanceData<T>>(&mut balance_list.balance_list, type);\r\n        return balance_table.totalsupply\r\n    }\r\n```\r\n\r\n## test  \r\n接下来对我们刚刚完成的模块进行测试  \r\n让我们接着上次的测试代码继续写  \r\n我们将重复的测试代码进行重构  \r\n\r\n###  获取某地址余额\r\n```rust\r\n    #[test_only]\r\n    fun get_balance<T>(scenario: &mut Scenario, sender: address ,token_cap: &TokenCap<T>, to:address): u64{\r\n        test_scenario::next_tx(scenario, sender);\r\n        let balance_list = test_scenario::take_shared<BalanceList>(scenario);\r\n        let balance = erc20::balance_of(token_cap,&mut balance_list, to,test_scenario::ctx(scenario));\r\n        test_scenario::return_shared(balance_list);\r\n        return balance\r\n    }\r\n```\r\n\r\n### 获取总供应  \r\n```rust\r\n    #[test_only]\r\n    fun get_totalsupply<T>(scenario: &mut Scenario, sender: address, token_cap: &TokenCap<T>): u64{\r\n        test_scenario::next_tx(scenario, sender);\r\n        let balance_list = test_scenario::take_shared<BalanceList>(scenario);\r\n        let total_supply = erc20::total_supply(token_cap,&mut balance_list,test_scenario::ctx(scenario));\r\n        test_scenario::return_shared(balance_list);\r\n        return total_supply\r\n    }\r\n\r\n```\r\n\r\n### 铸币测试\r\n```rust\r\n    #[test_only]\r\n    fun test_mint(scenario: &mut Scenario, sender: address, to: address, amount:u64) {\r\n        test_scenario::next_tx(scenario, sender);\r\n        let treasury_cap: TreasuryCap<ERC20TEST> = test_scenario::take_from_sender(scenario);\r\n        let balance_list = test_scenario::take_shared<BalanceList>(scenario);\r\n        erc20::mint(&treasury_cap, &mut balance_list, to, amount, test_scenario::ctx(scenario));\r\n        test_scenario::return_shared(balance_list);\r\n        test_scenario::return_to_sender(scenario, treasury_cap);\r\n    }\r\n```\r\n\r\n### 销毁代币测试  \r\n```rust\r\n    #[test_only]\r\n    fun test_burn(scenario: &mut Scenario, sender: address, to: address, amount:u64){\r\n        test_scenario::next_tx(scenario, sender);\r\n        let treasury_cap: TreasuryCap<ERC20TEST> = test_scenario::take_from_sender(scenario);\r\n        let balance_list = test_scenario::take_shared<BalanceList>(scenario);\r\n        erc20::burn(&treasury_cap, &mut balance_list, to, amount, test_scenario::ctx(scenario));\r\n        test_scenario::return_shared(balance_list);\r\n        test_scenario::return_to_sender(scenario, treasury_cap);\r\n    }\r\n```\r\n\r\n将它们移到测试主函数中\r\n```rust\r\n        //2. mint\r\n        {\r\n            test_scenario::next_tx(&mut scenario, addr1);\r\n            let token_cap: TokenCap<ERC20TEST> = test_scenario::take_shared(&mut scenario);\r\n            assert(get_balance(&mut scenario,addr1, &token_cap, addr1) == 0, 0);\r\n            assert(get_totalsupply(&mut scenario, addr1,&token_cap) == 0, 0);\r\n            test_scenario::return_shared(token_cap);\r\n\r\n            test_mint(&mut scenario, addr1, addr1, 1000);\r\n\r\n            test_scenario::next_tx(&mut scenario, addr1);\r\n            let token_cap: TokenCap<ERC20TEST> = test_scenario::take_shared(&mut scenario);\r\n            assert(get_balance(&mut scenario,addr1, &token_cap, addr1) == 1000, 0);\r\n            assert(get_totalsupply(&mut scenario,addr1, &token_cap) == 1000, 0);\r\n            test_scenario::return_shared(token_cap);\r\n\r\n            test_mint(&mut scenario, addr1, addr1, 1000);\r\n\r\n            test_scenario::next_tx(&mut scenario, addr1);\r\n            let token_cap: TokenCap<ERC20TEST> = test_scenario::take_shared(&mut scenario);\r\n            assert(get_balance(&mut scenario,addr1, &token_cap, addr1) == 2000, 0);\r\n            assert(get_totalsupply(&mut scenario,addr1, &token_cap) == 2000, 0);\r\n            test_scenario::return_shared(token_cap);\r\n        };\r\n\r\n        //3. burn \r\n        {\r\n            test_scenario::next_tx(&mut scenario, addr1);\r\n            let treasury_cap: TreasuryCap<ERC20TEST> = test_scenario::take_from_sender(&mut scenario);\r\n            let balance_list = test_scenario::take_shared<BalanceList>(&mut scenario);\r\n            erc20::burn(&treasury_cap,&mut balance_list,addr1,1000,test_scenario::ctx(&mut scenario));\r\n            test_scenario::return_shared(balance_list);\r\n            test_scenario::return_to_sender(& scenario, treasury_cap);\r\n\r\n            test_scenario::next_tx(&mut scenario, addr1);\r\n            let token_cap: TokenCap<ERC20TEST> = test_scenario::take_shared(&mut scenario);\r\n            assert(get_balance(&mut scenario,addr1, &token_cap, addr1) == 1000, 0);\r\n            assert(get_totalsupply(&mut scenario,addr1, &token_cap) == 1000, 0);\r\n            test_scenario::return_shared(token_cap);\r\n\r\n            test_scenario::next_tx(&mut scenario, addr1);\r\n            let treasury_cap: TreasuryCap<ERC20TEST> = test_scenario::take_from_sender(&mut scenario);\r\n            let balance_list = test_scenario::take_shared<BalanceList>(&mut scenario);\r\n            erc20::burn(&treasury_cap,&mut balance_list,addr1,1000,test_scenario::ctx(&mut scenario));\r\n            test_scenario::return_shared(balance_list);\r\n            test_scenario::return_to_sender(& scenario, treasury_cap);\r\n\r\n            test_scenario::next_tx(&mut scenario, addr1);\r\n            let token_cap: TokenCap<ERC20TEST> = test_scenario::take_shared(&mut scenario);\r\n            assert(get_balance(&mut scenario,addr1, &token_cap, addr1) == 0, 0);\r\n            assert(get_totalsupply(&mut scenario,addr1, &token_cap) == 0, 0);\r\n            test_scenario::return_shared(token_cap);\r\n        };\r\n```\r\n\r\n### 目前完整的测试文件\r\n```rust\r\n\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    #[test_only]\r\n    fun test_mint(scenario: &mut Scenario, sender: address, to: address, amount:u64) {\r\n        test_scenario::next_tx(scenario, sender);\r\n        let treasury_cap: TreasuryCap<ERC20TEST> = test_scenario::take_from_sender(scenario);\r\n        let balance_list = test_scenario::take_shared<BalanceList>(scenario);\r\n        erc20::mint(&treasury_cap, &mut balance_list, to, amount, test_scenario::ctx(scenario));\r\n        test_scenario::return_shared(balance_list);\r\n        test_scenario::return_to_sender(scenario, treasury_cap);\r\n    }\r\n\r\n    #[test_only]\r\n    fun test_burn(scenario: &mut Scenario, sender: address, to: address, amount:u64){\r\n        test_scenario::next_tx(scenario, sender);\r\n        let treasury_cap: TreasuryCap<ERC20TEST> = test_scenario::take_from_sender(scenario);\r\n        let balance_list = test_scenario::take_shared<BalanceList>(scenario);\r\n        erc20::burn(&treasury_cap, &mut balance_list, to, amount, test_scenario::ctx(scenario));\r\n        test_scenario::return_shared(balance_list);\r\n        test_scenario::return_to_sender(scenario, treasury_cap);\r\n    }\r\n\r\n    #[test_only]\r\n    fun get_balance<T>(scenario: &mut Scenario, sender: address ,token_cap: &TokenCap<T>, to:address): u64{\r\n        test_scenario::next_tx(scenario, sender);\r\n        let balance_list = test_scenario::take_shared<BalanceList>(scenario);\r\n        let balance = erc20::balance_of(token_cap,&mut balance_list, to,test_scenario::ctx(scenario));\r\n        test_scenario::return_shared(balance_list);\r\n        return balance\r\n    }\r\n\r\n    #[test_only]\r\n    fun get_totalsupply<T>(scenario: &mut Scenario, sender: address, token_cap: &TokenCap<T>): u64{\r\n        test_scenario::next_tx(scenario, sender);\r\n        let balance_list = test_scenario::take_shared<BalanceList>(scenario);\r\n        let total_supply = erc20::total_supply(token_cap,&mut balance_list,test_scenario::ctx(scenario));\r\n        test_scenario::return_shared(balance_list);\r\n        return total_supply\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\r\n        //2. mint\r\n        {\r\n            test_scenario::next_tx(&mut scenario, addr1);\r\n            let token_cap: TokenCap<ERC20TEST> = test_scenario::take_shared(&mut scenario);\r\n            assert(get_balance(&mut scenario,addr1, &token_cap, addr1) == 0, 0);\r\n            assert(get_totalsupply(&mut scenario, addr1,&token_cap) == 0, 0);\r\n            test_scenario::return_shared(token_cap);\r\n\r\n            test_mint(&mut scenario, addr1, addr1, 1000);\r\n\r\n            test_scenario::next_tx(&mut scenario, addr1);\r\n            let token_cap: TokenCap<ERC20TEST> = test_scenario::take_shared(&mut scenario);\r\n            assert(get_balance(&mut scenario,addr1, &token_cap, addr1) == 1000, 0);\r\n            assert(get_totalsupply(&mut scenario,addr1, &token_cap) == 1000, 0);\r\n            test_scenario::return_shared(token_cap);\r\n\r\n            test_mint(&mut scenario, addr1, addr1, 1000);\r\n\r\n            test_scenario::next_tx(&mut scenario, addr1);\r\n            let token_cap: TokenCap<ERC20TEST> = test_scenario::take_shared(&mut scenario);\r\n            assert(get_balance(&mut scenario,addr1, &token_cap, addr1) == 2000, 0);\r\n            assert(get_totalsupply(&mut scenario,addr1, &token_cap) == 2000, 0);\r\n            test_scenario::return_shared(token_cap);\r\n        };\r\n\r\n        //3. burn \r\n        {\r\n            test_scenario::next_tx(&mut scenario, addr1);\r\n            let treasury_cap: TreasuryCap<ERC20TEST> = test_scenario::take_from_sender(&mut scenario);\r\n            let balance_list = test_scenario::take_shared<BalanceList>(&mut scenario);\r\n            erc20::burn(&treasury_cap,&mut balance_list,addr1,1000,test_scenario::ctx(&mut scenario));\r\n            test_scenario::return_shared(balance_list);\r\n            test_scenario::return_to_sender(& scenario, treasury_cap);\r\n\r\n            test_scenario::next_tx(&mut scenario, addr1);\r\n            let token_cap: TokenCap<ERC20TEST> = test_scenario::take_shared(&mut scenario);\r\n            assert(get_balance(&mut scenario,addr1, &token_cap, addr1) == 1000, 0);\r\n            assert(get_totalsupply(&mut scenario,addr1, &token_cap) == 1000, 0);\r\n            test_scenario::return_shared(token_cap);\r\n\r\n            test_scenario::next_tx(&mut scenario, addr1);\r\n            let treasury_cap: TreasuryCap<ERC20TEST> = test_scenario::take_from_sender(&mut scenario);\r\n            let balance_list = test_scenario::take_shared<BalanceList>(&mut scenario);\r\n            erc20::burn(&treasury_cap,&mut balance_list,addr1,1000,test_scenario::ctx(&mut scenario));\r\n            test_scenario::return_shared(balance_list);\r\n            test_scenario::return_to_sender(& scenario, treasury_cap);\r\n\r\n            test_scenario::next_tx(&mut scenario, addr1);\r\n            let token_cap: TokenCap<ERC20TEST> = test_scenario::take_shared(&mut scenario);\r\n            assert(get_balance(&mut scenario,addr1, &token_cap, addr1) == 0, 0);\r\n            assert(get_totalsupply(&mut scenario,addr1, &token_cap) == 0, 0);\r\n            test_scenario::return_shared(token_cap);\r\n        };\r\n\r\n        test_scenario::end(scenario);\r\n    }\r\n}\r\n```\r\n\r\n### result\r\n运行：  \r\n```rust\r\nsui move test --skip-fetch-latest-git-deps \r\n```\r\n结果：  \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":1710163320,"version":1}