学习了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函数用于转账,需要传入tokencap,要转账的地址,转账数量
 public fun transfer<T>(_:& TokenCap<T>, balance_list: &mut BalanceList, to:address, value: u64, ctx:&mut TxContext):bool检查to地址是否为0地址,如果是抛出异常;检查转账余额是否为0,如果为0直接返回。
    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));
    };对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);
    };函数返回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函数用于授权他人转账,需要传入代币类型,授权列表,要授权对象的地址,要授权代币的数量
    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);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);
        };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用于已被授权的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    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);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));
        };对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);
    };函数返回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
    }用来访问授权余额
    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_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);
    }
}
Running Move unit tests
[ PASS    ] 0x0::erc20test::test
Test result: OK. Total tests: 1; passed: 1; failed: 0 
                如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!