学习了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
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!