sui move 动态字段练习(3)

  • shaflow01
  • 更新于 2024-03-12 14:49
  • 阅读 619

学习了suimove中的动态字段,table,bag,作为练习,我准备使用它们模拟solidity中的映射类型,在suimove实现一个类似erc20的同质化代币作为之前学习的实践与巩固。本文分享了练习过程中的transfer,和approve,transferFrom。

引言

学习了sui move中的动态字段,table,bag,作为练习,我准备使用它们模拟solidity中的映射类型,在sui move实现一个类似erc20的同质化代币作为之前学习的实践与巩固。本文分享了练习过程中的transfer,和approve,transferFrom。
注:本例实现仅用于学习动态字段,由于访问gas和便捷性不强,无法用于生产。在sui move中使用的同质化代币请使用官方标准库中内置的coin

transfer函数

transfer函数用于转账,需要传入tokencap,要转账的地址,转账数量

 public fun transfer<T>(_:& TokenCap<T>, balance_list: &mut BalanceList, to:address, value: u64, ctx:&mut TxContext):bool
  1. 检查to地址是否为0地址,如果是抛出异常;检查转账余额是否为0,如果为0直接返回。

        assert!(to != AddressZero, 1);
        if(value == 0){
            return true
        };
  2. 检查代币信息,取出对应代币余额列表

        let type = ascii::into_bytes(type_name::into_string(type_name::get_with_original_ids<T>()));
        assert!(bag::contains(&balance_list.balance_list, type), 2);
        let balance_table = bag::borrow_mut<vector<u8>, BalanceData<T>>(&mut balance_list.balance_list, type);
  3. 检查转账人是否有足够的余额,之后将余额扣除

        assert!(table::contains(&balance_table.balance, tx_context::sender(ctx)), 3);
        let balance_from = table::borrow_mut(&mut balance_table.balance,tx_context::sender(ctx));
        assert!(*balance_from >= value , 4);
        if(*balance_from > value){
            *balance_from = *balance_from - value;
        }
        else{
            table::remove(&mut balance_table.balance ,tx_context::sender(ctx));
        };
  4. 对to地址的余额进行变更

        if(table::contains(&balance_table.balance, to)){
            let balance_to = table::borrow_mut(&mut balance_table.balance,to);
            assert!(*balance_to + value >= *balance_to, 8);
            *balance_to = *balance_to + value;
        }else{
            table::add(&mut balance_table.balance, to, value);
        };
  5. 函数返回true

transfer函数完整代码:

    public fun transfer<T>(_:& TokenCap<T>, balance_list: &mut BalanceList, to:address, value: u64, ctx:&mut TxContext):bool{
        assert!(to != AddressZero, 1);
        if(value == 0){
            return true
        };
        let type = ascii::into_bytes(type_name::into_string(type_name::get_with_original_ids<T>()));
        assert!(bag::contains(&balance_list.balance_list, type), 2);
        let balance_table = bag::borrow_mut<vector<u8>, BalanceData<T>>(&mut balance_list.balance_list, type);
        assert!(table::contains(&balance_table.balance, tx_context::sender(ctx)), 3);
        let balance_from = table::borrow_mut(&mut balance_table.balance,tx_context::sender(ctx));
        assert!(*balance_from >= value , 4);
        if(*balance_from > value){
            *balance_from = *balance_from - value;
        }
        else{
            table::remove(&mut balance_table.balance ,tx_context::sender(ctx));
        };
        if(table::contains(&balance_table.balance, to)){
            let balance_to = table::borrow_mut(&mut balance_table.balance,to);
            assert!(*balance_to + value >= *balance_to, 8);
            *balance_to = *balance_to + value;
        }else{
            table::add(&mut balance_table.balance, to, value);
        };
        return true
    }

approve函数

approve函数用于授权他人转账,需要传入代币类型,授权列表,要授权对象的地址,要授权代币的数量

    public fun approve<T>(_:& TokenCap<T>, allowance_list: &mut AllowanceList,spender:address, value:u64, ctx:&mut TxContext):bool 
  1. 检查授权余额是否为0,如果是,直接返回true
        assert!(spender != AddressZero, 1);
        if(value == 0){
            return true
        };
  2. 检查代币类型,取出相应授权列表
    
        let type = ascii::into_bytes(type_name::into_string(type_name::get_with_original_ids<T>()));
        assert!(bag::contains(&allowance_list.allowance_list, type), 2);
        //get AllowanceData
        let allowance_table = bag::borrow_mut<vector<u8>, AllowanceData<T>>(&mut allowance_list.allowance_list, type);
3. 如果授权者已存在授权列表,取出更改数据,如没有,创建并填入初始数据  
```rust
        //if have AllowanceAmountList
        if(table::contains(&allowance_table.allowance, tx_context::sender(ctx))){
            let allowance = table::borrow_mut(&mut allowance_table.allowance, tx_context::sender(ctx));

            //if have allowance_amount
            if(table::contains(&allowance.allowance_amount,spender)){
                let amount = table::borrow_mut(&mut allowance.allowance_amount,spender);
                *amount = value;
            }
            else{
                table::add(&mut allowance.allowance_amount, spender, value);
            }
        } else{
            //add AllowanceAmountList
            let allowance_amount = table::new(ctx);
            table::add(&mut allowance_amount, spender, value);
            let allowance_list = AllowanceAmountList{
                id: object::new(ctx),
                allowance_amount: allowance_amount,
            };
            table::add(&mut allowance_table.allowance,tx_context::sender(ctx),allowance_list);
        };
  1. 函数返回true

approve函数完整代码

    public fun approve<T>(_:& TokenCap<T>, allowance_list: &mut AllowanceList,spender:address, value:u64, ctx:&mut TxContext):bool {
        assert!(spender != AddressZero, 1);
        if(value == 0){
            return true
        };
        let type = ascii::into_bytes(type_name::into_string(type_name::get_with_original_ids<T>()));
        assert!(bag::contains(&allowance_list.allowance_list, type), 2);
        //get AllowanceData
        let allowance_table = bag::borrow_mut<vector<u8>, AllowanceData<T>>(&mut allowance_list.allowance_list, type);

        //if have AllowanceAmountList
        if(table::contains(&allowance_table.allowance, tx_context::sender(ctx))){
            let allowance = table::borrow_mut(&mut allowance_table.allowance, tx_context::sender(ctx));

            //if have allowance_amount
            if(table::contains(&allowance.allowance_amount,spender)){
                let amount = table::borrow_mut(&mut allowance.allowance_amount,spender);
                *amount = value;
            }
            else{
                table::add(&mut allowance.allowance_amount, spender, value);
            }
        } else{
            //add AllowanceAmountList
            let allowance_amount = table::new(ctx);
            table::add(&mut allowance_amount, spender, value);
            let allowance_list = AllowanceAmountList{
                id: object::new(ctx),
                allowance_amount: allowance_amount,
            };
            table::add(&mut allowance_table.allowance,tx_context::sender(ctx),allowance_list);
        };
        return true
    }

transferFrom函数

transferFrom用于已被授权的spender进行转账

public fun transferFrom<T>(_:& TokenCap<T>, allowance_list: &mut AllowanceList,balance_list: &mut BalanceList,from: address, to: address, value: u64, ctx:&mut TxContext):bool
  1. 检查地址及转账金额
        assert!(from != AddressZero, 1);
        assert!(to != AddressZero, 1);
        if(value == 0){
            return true
        };
  2. 检查代币类型
    
        let type = ascii::into_bytes(type_name::into_string(type_name::get_with_original_ids<T>()));
        assert!(bag::contains(&allowance_list.allowance_list, type), 2);
        assert!(bag::contains(&balance_list.balance_list, type), 2);
3. 检查授权额度是否足够交易发起者支付  
```rust
        let allowance_table = bag::borrow_mut<vector<u8>, AllowanceData<T>>(&mut allowance_list.allowance_list, type);
        assert!(table::contains(&allowance_table.allowance, from), 5);

        let allowance = table::borrow_mut(&mut allowance_table.allowance, from);
        assert!(table::contains(&allowance.allowance_amount,tx_context::sender(ctx)),6);
        let amount = table::borrow_mut(&mut allowance.allowance_amount,tx_context::sender(ctx));
        assert!(*amount >= value, 7);
        if(*amount > value){
            *amount = *amount - value;
        }
        else{
            *amount = *amount - value;
            table::remove(&mut allowance.allowance_amount, tx_context::sender(ctx));
        };
  1. 对from和to的余额进行变更

        let balance_table = bag::borrow_mut<vector<u8>, BalanceData<T>>(&mut balance_list.balance_list, type);
        assert!(table::contains(&balance_table.balance, from) , 3);
        let balance_from = table::borrow_mut(&mut balance_table.balance, from);
        assert!(*balance_from >= value , 4);
        *balance_from = *balance_from - value;
    
        if(table::contains(&balance_table.balance, to)){
            let balance_to = table::borrow_mut(&mut balance_table.balance,to);
            *balance_to = *balance_to + value;
        }else{
            table::add(&mut balance_table.balance, to, value);
        };
  2. 函数返回true

transferFrom函数完整代码

    public fun transferFrom<T>(_:& TokenCap<T>, allowance_list: &mut AllowanceList,balance_list: &mut BalanceList,from: address, to: address, value: u64, ctx:&mut TxContext):bool{
        assert!(from != AddressZero, 1);
        assert!(to != AddressZero, 1);
        if(value == 0){
            return true
        };
        let type = ascii::into_bytes(type_name::into_string(type_name::get_with_original_ids<T>()));
        assert!(bag::contains(&allowance_list.allowance_list, type), 2);
        assert!(bag::contains(&balance_list.balance_list, type), 2);

        let allowance_table = bag::borrow_mut<vector<u8>, AllowanceData<T>>(&mut allowance_list.allowance_list, type);
        assert!(table::contains(&allowance_table.allowance, from), 5);

        let allowance = table::borrow_mut(&mut allowance_table.allowance, from);
        assert!(table::contains(&allowance.allowance_amount,tx_context::sender(ctx)),6);
        let amount = table::borrow_mut(&mut allowance.allowance_amount,tx_context::sender(ctx));
        assert!(*amount >= value, 7);
        if(*amount > value){
            *amount = *amount - value;
        }
        else{
            *amount = *amount - value;
            table::remove(&mut allowance.allowance_amount, tx_context::sender(ctx));
        };

        let balance_table = bag::borrow_mut<vector<u8>, BalanceData<T>>(&mut balance_list.balance_list, type);
        assert!(table::contains(&balance_table.balance, from) , 3);
        let balance_from = table::borrow_mut(&mut balance_table.balance, from);
        assert!(*balance_from >= value , 4);
        *balance_from = *balance_from - value;

        if(table::contains(&balance_table.balance, to)){
            let balance_to = table::borrow_mut(&mut balance_table.balance,to);
            *balance_to = *balance_to + value;
        }else{
            table::add(&mut balance_table.balance, to, value);
        };
        return true
    }

get_allowance函数

用来访问授权余额

    public fun get_allowance<T>(_token_cap :& TokenCap<T>,allowance_list: &mut AllowanceList, owner:address, spender:address,ctx:&mut TxContext):u64{
        let type = ascii::into_bytes(type_name::into_string(type_name::get_with_original_ids<T>()));
        assert!(bag::contains(&allowance_list.allowance_list, type), 2);

        let allowance_data_list = bag::borrow<vector<u8>, AllowanceData<T>>(& allowance_list.allowance_list, type);

        if(table::contains(&allowance_data_list.allowance, owner)){
            let allowance = table::borrow<address, AllowanceAmountList>(& allowance_data_list.allowance, owner);
            if(table::contains(&allowance.allowance_amount,spender)){
                let allowance_amount = *(table::borrow<address, u64>(& allowance.allowance_amount,spender));
                return allowance_amount
            }
        };
        return 0
    }

先检查代币类型,再尝试一步步访问对应的值,如果中间有一处对应映射不存在,都会返回0.

test

封装测试使用函数

    #[test_only]
    fun get_allowance(scenario: &mut Scenario,owner: address, spender: address): u64{
        test_scenario::next_tx(scenario, spender);
        let allowance_list = test_scenario::take_shared<AllowanceList>(scenario);
        let token_cap: TokenCap<ERC20TEST> = test_scenario::take_shared(scenario);
        let allowance_amount = erc20::get_allowance(&token_cap, &mut allowance_list, owner, spender,test_scenario::ctx(scenario));
        test_scenario::return_shared(allowance_list);
        test_scenario::return_shared(token_cap);
        return allowance_amount
    }

        #[test_only]
    fun test_transfer(scenario: &mut Scenario, from:address, to: address, amount:u64){
        test_scenario::next_tx(scenario, from);
        let token_cap: TokenCap<ERC20TEST> = test_scenario::take_shared(scenario);
        let balance_list:BalanceList = test_scenario::take_shared(scenario);
        erc20::transfer(&token_cap,&mut balance_list, to, amount, test_scenario::ctx(scenario));
        test_scenario::return_shared(balance_list);
        test_scenario::return_shared(token_cap);
    }

    #[test_only]
    fun test_approve(scenario: &mut Scenario, owner:address, spender: address,amount:u64){
        test_scenario::next_tx(scenario, owner);
        let token_cap: TokenCap<ERC20TEST> = test_scenario::take_shared(scenario);
        let allowance_list = test_scenario::take_shared<AllowanceList>(scenario);
        erc20::approve(&token_cap,&mut allowance_list,spender,amount,test_scenario::ctx(scenario));
        test_scenario::return_shared(allowance_list);
        test_scenario::return_shared(token_cap);
    }

    #[test_only]
    fun test_transfer_from(scenario: &mut Scenario,owner:address, spender:address,amount:u64){
        test_scenario::next_tx(scenario, spender);
        let token_cap: TokenCap<ERC20TEST> = test_scenario::take_shared(scenario);
        let allowance_list = test_scenario::take_shared<AllowanceList>(scenario);
        let balance_list = test_scenario::take_shared<BalanceList>(scenario);
        erc20::transferFrom(&token_cap,&mut allowance_list,&mut balance_list,owner,spender,amount,test_scenario::ctx(scenario));
        test_scenario::return_shared(allowance_list);
        test_scenario::return_shared(token_cap);
        test_scenario::return_shared(balance_list);
    }

在主函数中进行测试

        //3.transfer
        {   
            test_scenario::next_tx(&mut scenario, addr1);
            test_mint(&mut scenario, addr1, addr1, 1000);

            assert!(get_balance(&mut scenario,addr1,  addr1) == 1000, 0);
            assert!(get_balance(&mut scenario,addr2,  addr2) == 0, 0);

            test_transfer(&mut scenario, addr1, addr2, 500);

            assert!(get_balance(&mut scenario,addr1,  addr1) == 500, 0);
            assert!(get_balance(&mut scenario,addr2,  addr2) == 500, 0);

        };

        //4.transferFrom
        {

            assert!(get_allowance(&mut scenario, addr1, addr2) == 0,0);

            test_approve(&mut scenario, addr1, addr2, 500);

            assert!(get_allowance(&mut scenario, addr1, addr2 ) == 500,0);

            test_transfer_from(&mut scenario, addr1, addr2, 300);

            assert!(get_allowance(&mut scenario, addr1, addr2 ) == 200,0);
            assert!(get_balance(&mut scenario,addr2,  addr1) == 200, 0);
            assert!(get_balance(&mut scenario,addr2, addr2) == 800, 0);

        };

目前完整测试代码


#[test_only]
module erc20::erc20test{
    use erc20::erc20::{Self,TokenCap,BalanceList,TreasuryCap,AllowanceList};
    use sui::test_scenario::{Self, Scenario};
    use sui::tx_context::{Self,TxContext};
    use sui::transfer;

    struct ERC20TEST has drop{}
    fun init(witness: ERC20TEST, ctx: &mut TxContext){
        let treasury_cap = erc20::create_token(witness,b"ETC20Test", b"ERCT", 18, ctx);
        transfer::public_transfer(treasury_cap, tx_context::sender(ctx));
    }
    #[test_only]
    fun test_mint(scenario: &mut Scenario, sender: address, to: address, amount:u64) {
        test_scenario::next_tx(scenario, sender);
        let treasury_cap: TreasuryCap<ERC20TEST> = test_scenario::take_from_sender(scenario);
        let balance_list = test_scenario::take_shared<BalanceList>(scenario);
        erc20::mint(&treasury_cap, &mut balance_list, to, amount, test_scenario::ctx(scenario));
        test_scenario::return_shared(balance_list);
        test_scenario::return_to_sender(scenario, treasury_cap);
    }

    #[test_only]
    fun test_burn(scenario: &mut Scenario, sender: address, to: address, amount:u64){
        test_scenario::next_tx(scenario, sender);
        let treasury_cap: TreasuryCap<ERC20TEST> = test_scenario::take_from_sender(scenario);
        let balance_list = test_scenario::take_shared<BalanceList>(scenario);
        erc20::burn(&treasury_cap, &mut balance_list, to, amount, test_scenario::ctx(scenario));
        test_scenario::return_shared(balance_list);
        test_scenario::return_to_sender(scenario, treasury_cap);
    }

    #[test_only]
    fun test_transfer(scenario: &mut Scenario, from:address, to: address, amount:u64){
        test_scenario::next_tx(scenario, from);
        let token_cap: TokenCap<ERC20TEST> = test_scenario::take_shared(scenario);
        let balance_list:BalanceList = test_scenario::take_shared(scenario);
        erc20::transfer(&token_cap,&mut balance_list, to, amount, test_scenario::ctx(scenario));
        test_scenario::return_shared(balance_list);
        test_scenario::return_shared(token_cap);
    }

    #[test_only]
    fun test_approve(scenario: &mut Scenario, owner:address, spender: address,amount:u64){
        test_scenario::next_tx(scenario, owner);
        let token_cap: TokenCap<ERC20TEST> = test_scenario::take_shared(scenario);
        let allowance_list = test_scenario::take_shared<AllowanceList>(scenario);
        erc20::approve(&token_cap,&mut allowance_list,spender,amount,test_scenario::ctx(scenario));
        test_scenario::return_shared(allowance_list);
        test_scenario::return_shared(token_cap);
    }

    #[test_only]
    fun test_transfer_from(scenario: &mut Scenario,owner:address, spender:address,amount:u64){
        test_scenario::next_tx(scenario, spender);
        let token_cap: TokenCap<ERC20TEST> = test_scenario::take_shared(scenario);
        let allowance_list = test_scenario::take_shared<AllowanceList>(scenario);
        let balance_list = test_scenario::take_shared<BalanceList>(scenario);
        erc20::transferFrom(&token_cap,&mut allowance_list,&mut balance_list,owner,spender,amount,test_scenario::ctx(scenario));
        test_scenario::return_shared(allowance_list);
        test_scenario::return_shared(token_cap);
        test_scenario::return_shared(balance_list);
    }

    #[test_only]
    fun get_balance(scenario: &mut Scenario, sender: address, to:address): u64{
        test_scenario::next_tx(scenario, sender);
        let token_cap: TokenCap<ERC20TEST> = test_scenario::take_shared(scenario);
        let balance_list = test_scenario::take_shared<BalanceList>(scenario);
        let balance = erc20::balance_of(&token_cap,&mut balance_list, to,test_scenario::ctx(scenario));
        test_scenario::return_shared(balance_list);
        test_scenario::return_shared(token_cap);
        return balance
    }

    #[test_only]
    fun get_totalsupply(scenario: &mut Scenario, sender: address): u64{
        test_scenario::next_tx(scenario, sender);
        let token_cap: TokenCap<ERC20TEST> = test_scenario::take_shared(scenario);
        let balance_list = test_scenario::take_shared<BalanceList>(scenario);
        let total_supply = erc20::total_supply(&token_cap,&mut balance_list,test_scenario::ctx(scenario));
        test_scenario::return_shared(balance_list);
        test_scenario::return_shared(token_cap);
        return total_supply
    }

    #[test_only]
    fun get_allowance(scenario: &mut Scenario,owner: address, spender: address): u64{
        test_scenario::next_tx(scenario, spender);
        let allowance_list = test_scenario::take_shared<AllowanceList>(scenario);
        let token_cap: TokenCap<ERC20TEST> = test_scenario::take_shared(scenario);
        let allowance_amount = erc20::get_allowance(&token_cap, &mut allowance_list, owner, spender,test_scenario::ctx(scenario));
        test_scenario::return_shared(allowance_list);
        test_scenario::return_shared(token_cap);
        return allowance_amount
    }

    #[test]
    public fun test(){
        let addr1 = @0xA;
        let addr2 = @0xB;

        let scenario = test_scenario::begin(addr1);
        //1. create a token
        {
            erc20::test_init(test_scenario::ctx(&mut scenario));
            test_scenario::next_tx(&mut scenario, addr1);
            init(ERC20TEST{}, test_scenario::ctx(&mut scenario));
            test_scenario::next_tx(&mut scenario, addr1);
            let balance_list = test_scenario::take_shared<BalanceList>(&mut scenario);
            let allowance_list= test_scenario::take_shared(&mut scenario);
            let token_cap: TokenCap<ERC20TEST> = test_scenario::take_shared(&mut scenario);
            erc20::init_token(&token_cap,&mut balance_list,&mut allowance_list, test_scenario::ctx(&mut scenario));
            test_scenario::return_shared(balance_list);
            test_scenario::return_shared(allowance_list);
            test_scenario::return_shared(token_cap);
        };

        //2. mint
        {

            assert!(get_balance(&mut scenario,addr1,  addr1) == 0, 0);
            assert!(get_totalsupply(&mut scenario, addr1) == 0, 0);

            test_mint(&mut scenario, addr1, addr1, 1000);

            assert!(get_balance(&mut scenario,addr1,  addr1) == 1000, 0);
            assert!(get_totalsupply(&mut scenario,addr1) == 1000, 0);

            test_mint(&mut scenario, addr1, addr1, 1000);

            assert!(get_balance(&mut scenario,addr1,  addr1) == 2000, 0);
            assert!(get_totalsupply(&mut scenario,addr1) == 2000, 0);

        };

        //3. burn 
        {

            test_burn(&mut scenario, addr1, addr1, 1000);

            assert!(get_balance(&mut scenario,addr1, addr1) == 1000, 0);
            assert!(get_totalsupply(&mut scenario,addr1) == 1000, 0);

            test_burn(&mut scenario, addr1, addr1, 1000);

            assert!(get_balance(&mut scenario,addr1,  addr1) == 0, 0);
            assert!(get_totalsupply(&mut scenario,addr1) == 0, 0);

        };

        //3.transfer
        {   
            test_scenario::next_tx(&mut scenario, addr1);
            test_mint(&mut scenario, addr1, addr1, 1000);

            assert!(get_balance(&mut scenario,addr1,  addr1) == 1000, 0);
            assert!(get_balance(&mut scenario,addr2,  addr2) == 0, 0);

            test_transfer(&mut scenario, addr1, addr2, 500);

            assert!(get_balance(&mut scenario,addr1,  addr1) == 500, 0);
            assert!(get_balance(&mut scenario,addr2,  addr2) == 500, 0);

        };

        //4.transferFrom
        {

            assert!(get_allowance(&mut scenario, addr1, addr2) == 0,0);

            test_approve(&mut scenario, addr1, addr2, 500);

            assert!(get_allowance(&mut scenario, addr1, addr2 ) == 500,0);

            test_transfer_from(&mut scenario, addr1, addr2, 300);

            assert!(get_allowance(&mut scenario, addr1, addr2 ) == 200,0);
            assert!(get_balance(&mut scenario,addr2,  addr1) == 200, 0);
            assert!(get_balance(&mut scenario,addr2, addr2) == 800, 0);

        };

        test_scenario::end(scenario);
    }
}

result

Running Move unit tests
[ PASS    ] 0x0::erc20test::test
Test result: OK. Total tests: 1; passed: 1; failed: 0
  • 原创
  • 学分: 2
  • 分类: Sui
  • 标签:
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

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