diff --git a/tokens/src/lib.rs b/tokens/src/lib.rs index 65ff4bd3b..e1d0830c0 100644 --- a/tokens/src/lib.rs +++ b/tokens/src/lib.rs @@ -36,6 +36,7 @@ #![cfg_attr(not(feature = "std"), no_std)] #![allow(clippy::unused_unit)] +#![allow(clippy::comparison_chain)] pub use crate::imbalances::{NegativeImbalance, PositiveImbalance}; @@ -87,7 +88,13 @@ where fn on_dust(who: &T::AccountId, currency_id: T::CurrencyId, amount: T::Balance) { // transfer the dust to treasury account, ignore the result, // if failed will leave some dust which still could be recycled. - let _ = as MultiCurrency>::transfer(currency_id, who, &GetAccountId::get(), amount); + let _ = Pallet::::do_transfer( + currency_id, + who, + &GetAccountId::get(), + amount, + ExistenceRequirement::AllowDeath, + ); } } @@ -96,7 +103,7 @@ impl OnDust for BurnDust fn on_dust(who: &T::AccountId, currency_id: T::CurrencyId, amount: T::Balance) { // burn the dust, ignore the result, // if failed will leave some dust which still could be recycled. - let _ = Pallet::::withdraw(currency_id, who, amount); + let _ = Pallet::::do_withdraw(currency_id, who, amount, ExistenceRequirement::AllowDeath, true); } } @@ -229,6 +236,8 @@ pub mod module { /// Some balance was unreserved (moved from reserved to free). /// \[currency_id, who, value\] Unreserved(T::CurrencyId, T::AccountId, T::Balance), + /// A balance was set by root. \[who, free, reserved\] + BalanceSet(T::CurrencyId, T::AccountId, T::Balance, T::Balance), } /// The total issuance of a token type. @@ -321,10 +330,19 @@ pub mod module { #[pallet::call] impl Pallet { - /// Transfer some balance to another account. + /// Transfer some liquid free balance to another account. + /// + /// `transfer` will set the `FreeBalance` of the sender and receiver. + /// It will decrease the total issuance of the system by the + /// `TransferFee`. If the sender's account is below the existential + /// deposit as a result of the transfer, the account will be reaped. /// /// The dispatch origin for this call must be `Signed` by the /// transactor. + /// + /// - `dest`: The recipient of the transfer. + /// - `currency_id`: currency type. + /// - `amount`: free balance amount to tranfer. #[pallet::weight(T::WeightInfo::transfer())] pub fn transfer( origin: OriginFor, @@ -334,7 +352,7 @@ pub mod module { ) -> DispatchResult { let from = ensure_signed(origin)?; let to = T::Lookup::lookup(dest)?; - >::transfer(currency_id, &from, &to, amount)?; + Self::do_transfer(currency_id, &from, &to, amount, ExistenceRequirement::AllowDeath)?; Self::deposit_event(Event::Transfer(currency_id, from, to, amount)); Ok(()) @@ -342,22 +360,144 @@ pub mod module { /// Transfer all remaining balance to the given account. /// + /// NOTE: This function only attempts to transfer _transferable_ + /// balances. This means that any locked, reserved, or existential + /// deposits (when `keep_alive` is `true`), will not be transferred by + /// this function. To ensure that this function results in a killed + /// account, you might need to prepare the account by removing any + /// reference counters, storage deposits, etc... + /// /// The dispatch origin for this call must be `Signed` by the /// transactor. + /// + /// - `dest`: The recipient of the transfer. + /// - `currency_id`: currency type. + /// - `keep_alive`: A boolean to determine if the `transfer_all` + /// operation should send all of the funds the account has, causing + /// the sender account to be killed (false), or transfer everything + /// except at least the existential deposit, which will guarantee to + /// keep the sender account alive (true). #[pallet::weight(T::WeightInfo::transfer_all())] pub fn transfer_all( origin: OriginFor, dest: ::Source, currency_id: T::CurrencyId, + keep_alive: bool, ) -> DispatchResult { let from = ensure_signed(origin)?; let to = T::Lookup::lookup(dest)?; - let balance = >::free_balance(currency_id, &from); - >::transfer(currency_id, &from, &to, balance)?; + let reducible_balance = + >::reducible_balance(currency_id, &from, keep_alive); + >::transfer(currency_id, &from, &to, reducible_balance, keep_alive)?; - Self::deposit_event(Event::Transfer(currency_id, from, to, balance)); + Self::deposit_event(Event::Transfer(currency_id, from, to, reducible_balance)); Ok(()) } + + /// Same as the [`transfer`] call, but with a check that the transfer + /// will not kill the origin account. + /// + /// 99% of the time you want [`transfer`] instead. + /// + /// The dispatch origin for this call must be `Signed` by the + /// transactor. + /// + /// - `dest`: The recipient of the transfer. + /// - `currency_id`: currency type. + /// - `amount`: free balance amount to tranfer. + #[pallet::weight(T::WeightInfo::transfer_keep_alive())] + pub fn transfer_keep_alive( + origin: OriginFor, + dest: ::Source, + currency_id: T::CurrencyId, + #[pallet::compact] amount: T::Balance, + ) -> DispatchResultWithPostInfo { + let from = ensure_signed(origin)?; + let to = T::Lookup::lookup(dest)?; + Self::do_transfer(currency_id, &from, &to, amount, ExistenceRequirement::KeepAlive)?; + + Self::deposit_event(Event::Transfer(currency_id, from, to, amount)); + Ok(().into()) + } + + /// Exactly as `transfer`, except the origin must be root and the source + /// account may be specified. + /// + /// The dispatch origin for this call must be _Root_. + /// + /// - `source`: The sender of the transfer. + /// - `dest`: The recipient of the transfer. + /// - `currency_id`: currency type. + /// - `amount`: free balance amount to tranfer. + #[pallet::weight(T::WeightInfo::force_transfer())] + pub fn force_transfer( + origin: OriginFor, + source: ::Source, + dest: ::Source, + currency_id: T::CurrencyId, + #[pallet::compact] amount: T::Balance, + ) -> DispatchResult { + ensure_root(origin)?; + let from = T::Lookup::lookup(source)?; + let to = T::Lookup::lookup(dest)?; + Self::do_transfer(currency_id, &from, &to, amount, ExistenceRequirement::AllowDeath)?; + + Self::deposit_event(Event::Transfer(currency_id, from, to, amount)); + Ok(()) + } + + /// Set the balances of a given account. + /// + /// This will alter `FreeBalance` and `ReservedBalance` in storage. it + /// will also decrease the total issuance of the system + /// (`TotalIssuance`). If the new free or reserved balance is below the + /// existential deposit, it will reap the `AccountInfo`. + /// + /// The dispatch origin for this call is `root`. + #[pallet::weight(T::WeightInfo::set_balance())] + pub fn set_balance( + origin: OriginFor, + who: ::Source, + currency_id: T::CurrencyId, + #[pallet::compact] new_free: T::Balance, + #[pallet::compact] new_reserved: T::Balance, + ) -> DispatchResult { + ensure_root(origin)?; + let who = T::Lookup::lookup(who)?; + + Self::try_mutate_account(&who, currency_id, |account, _| -> DispatchResult { + let mut new_total = new_free.checked_add(&new_reserved).ok_or(ArithmeticError::Overflow)?; + let (new_free, new_reserved) = if new_free + new_reserved < T::ExistentialDeposits::get(¤cy_id) { + new_total = Zero::zero(); + (Zero::zero(), Zero::zero()) + } else { + (new_free, new_reserved) + }; + let old_total = account.total(); + + account.free = new_free; + account.reserved = new_reserved; + + if new_total > old_total { + TotalIssuance::::try_mutate(currency_id, |t| -> DispatchResult { + *t = t + .checked_add(&(new_total - old_total)) + .ok_or(ArithmeticError::Overflow)?; + Ok(()) + })?; + } else if new_total < old_total { + TotalIssuance::::try_mutate(currency_id, |t| -> DispatchResult { + *t = t + .checked_sub(&(old_total - new_total)) + .ok_or(ArithmeticError::Underflow)?; + Ok(()) + })?; + } + + Self::deposit_event(Event::BalanceSet(currency_id, who.clone(), new_free, new_reserved)); + Ok(()) + }) + } } } @@ -439,6 +579,28 @@ impl Pallet { success } + // Ensure that an account can withdraw from their free balance given any + // existing withdrawal restrictions like locks and vesting balance. + // Is a no-op if amount to be withdrawn is zero. + pub(crate) fn ensure_can_withdraw( + currency_id: T::CurrencyId, + who: &T::AccountId, + amount: T::Balance, + ) -> DispatchResult { + if amount.is_zero() { + return Ok(()); + } + + let new_balance = Self::free_balance(currency_id, who) + .checked_sub(&amount) + .ok_or(Error::::BalanceTooLow)?; + ensure!( + new_balance >= Self::accounts(who, currency_id).frozen(), + Error::::LiquidityRestrictions + ); + Ok(()) + } + pub(crate) fn try_mutate_account( who: &T::AccountId, currency_id: T::CurrencyId, @@ -537,9 +699,9 @@ impl Pallet { }); // update locks - let existed = >::contains_key(who, currency_id); + let existed = Locks::::contains_key(who, currency_id); if locks.is_empty() { - >::remove(who, currency_id); + Locks::::remove(who, currency_id); if existed { // decrease account ref count when destruct lock frame_system::Pallet::::dec_consumers(who); @@ -547,7 +709,7 @@ impl Pallet { } else { let bounded_locks: BoundedVec, T::MaxLocks> = locks.to_vec().try_into().map_err(|_| Error::::MaxLocksExceeded)?; - >::insert(who, currency_id, bounded_locks); + Locks::::insert(who, currency_id, bounded_locks); if !existed { // increase account ref count when initialize lock if frame_system::Pallet::::inc_consumers(who).is_err() { @@ -565,11 +727,12 @@ impl Pallet { Ok(()) } - /// Transfer some free balance from `from` to `to`. - /// Is a no-op if value to be transferred is zero or the `from` is the - /// same as `to`. - /// Ensure from_account allow death or new balance above existential - /// deposit. Ensure to_account new balance above existential deposit. + /// Transfer some free balance from `from` to `to`. Ensure from_account + /// allow death or new balance above existential deposit. Ensure to_account + /// new balance above existential deposit. + /// + /// Is a no-op if value to be transferred is zero or the `from` is the same + /// as `to`. pub(crate) fn do_transfer( currency_id: T::CurrencyId, from: &T::AccountId, @@ -581,8 +744,8 @@ impl Pallet { return Ok(()); } - Pallet::::try_mutate_account(to, currency_id, |to_account, _existed| -> DispatchResult { - Pallet::::try_mutate_account(from, currency_id, |from_account, _existed| -> DispatchResult { + Self::try_mutate_account(to, currency_id, |to_account, _existed| -> DispatchResult { + Self::try_mutate_account(from, currency_id, |from_account, _existed| -> DispatchResult { from_account.free = from_account .free .checked_sub(&amount) @@ -607,6 +770,81 @@ impl Pallet { Ok(()) }) } + + /// Withdraw some free balance from an account, respecting existence + /// requirements, and if AllowDeath do not require ED. + /// + /// Is a no-op if value to be withdrawn is zero. + pub(crate) fn do_withdraw( + currency_id: T::CurrencyId, + who: &T::AccountId, + amount: T::Balance, + existence_requirement: ExistenceRequirement, + change_total_issuance: bool, + ) -> DispatchResult { + if amount.is_zero() { + return Ok(()); + } + + Self::try_mutate_account(who, currency_id, |account, _existed| -> DispatchResult { + Self::ensure_can_withdraw(currency_id, who, amount)?; + let previous_total = account.total(); + account.free -= amount; + + let ed = T::ExistentialDeposits::get(¤cy_id); + let would_be_dead = account.total() < ed; + let would_kill = would_be_dead && previous_total >= ed; + ensure!( + existence_requirement == ExistenceRequirement::AllowDeath || !would_kill, + Error::::KeepAlive + ); + + if change_total_issuance { + TotalIssuance::::mutate(currency_id, |v| *v -= amount); + } + + Ok(()) + }) + } + + /// Deposit some `value` into the free balance of `who`. + /// + /// `require_existed`: + /// - true, the account must already exist, do not require ED. + /// - false, possibly creating a new account, require ED if the account does + /// not yet exist. + /// + /// Is a no-op if value to be deposit is zero. + pub(crate) fn do_deposit( + currency_id: T::CurrencyId, + who: &T::AccountId, + amount: T::Balance, + require_existed: bool, + change_total_issuance: bool, + ) -> DispatchResult { + if amount.is_zero() { + return Ok(()); + } + + Self::try_mutate_account(who, currency_id, |account, existed| -> DispatchResult { + if require_existed { + ensure!(existed, Error::::DeadAccount); + } else { + let ed = T::ExistentialDeposits::get(¤cy_id); + ensure!(amount >= ed || existed, Error::::ExistentialDeposit); + } + + let new_total_issuance = Self::total_issuance(currency_id) + .checked_add(&amount) + .ok_or(ArithmeticError::Overflow)?; + if change_total_issuance { + TotalIssuance::::mutate(currency_id, |v| *v = new_total_issuance); + } + account.free += amount; + + Ok(()) + }) + } } impl MultiCurrency for Pallet { @@ -618,7 +856,7 @@ impl MultiCurrency for Pallet { } fn total_issuance(currency_id: Self::CurrencyId) -> Self::Balance { - >::get(currency_id) + Self::total_issuance(currency_id) } fn total_balance(currency_id: Self::CurrencyId, who: &T::AccountId) -> Self::Balance { @@ -629,64 +867,28 @@ impl MultiCurrency for Pallet { Self::accounts(who, currency_id).free } - // Ensure that an account can withdraw from their free balance given any - // existing withdrawal restrictions like locks and vesting balance. - // Is a no-op if amount to be withdrawn is zero. fn ensure_can_withdraw(currency_id: Self::CurrencyId, who: &T::AccountId, amount: Self::Balance) -> DispatchResult { - if amount.is_zero() { - return Ok(()); - } - - let new_balance = Self::free_balance(currency_id, who) - .checked_sub(&amount) - .ok_or(Error::::BalanceTooLow)?; - ensure!( - new_balance >= Self::accounts(who, currency_id).frozen(), - Error::::LiquidityRestrictions - ); - Ok(()) + Self::ensure_can_withdraw(currency_id, who, amount) } - /// Transfer some free balance from `from` to `to`. - /// Is a no-op if value to be transferred is zero or the `from` is the - /// same as `to`. fn transfer( currency_id: Self::CurrencyId, from: &T::AccountId, to: &T::AccountId, amount: Self::Balance, ) -> DispatchResult { + // allow death Self::do_transfer(currency_id, from, to, amount, ExistenceRequirement::AllowDeath) } - /// Deposit some `amount` into the free balance of account `who`. - /// - /// Is a no-op if the `amount` to be deposited is zero. fn deposit(currency_id: Self::CurrencyId, who: &T::AccountId, amount: Self::Balance) -> DispatchResult { - if amount.is_zero() { - return Ok(()); - } - - TotalIssuance::::try_mutate(currency_id, |total_issuance| -> DispatchResult { - *total_issuance = total_issuance.checked_add(&amount).ok_or(ArithmeticError::Overflow)?; - - Self::set_free_balance(currency_id, who, Self::free_balance(currency_id, who) + amount); - - Ok(()) - }) + // do not require existing + Self::do_deposit(currency_id, who, amount, false, true) } fn withdraw(currency_id: Self::CurrencyId, who: &T::AccountId, amount: Self::Balance) -> DispatchResult { - if amount.is_zero() { - return Ok(()); - } - Self::ensure_can_withdraw(currency_id, who, amount)?; - - // Cannot underflow because ensure_can_withdraw check - >::mutate(currency_id, |v| *v -= amount); - Self::set_free_balance(currency_id, who, Self::free_balance(currency_id, who) - amount); - - Ok(()) + // allow death + Self::do_withdraw(currency_id, who, amount, ExistenceRequirement::AllowDeath, true) } // Check if `value` amount of free balance can be slashed from `who`. @@ -731,7 +933,7 @@ impl MultiCurrency for Pallet { // Cannot underflow because the slashed value cannot be greater than total // issuance - >::mutate(currency_id, |v| *v -= amount - remaining_slash); + TotalIssuance::::mutate(currency_id, |v| *v -= amount - remaining_slash); remaining_slash } } @@ -855,7 +1057,7 @@ impl MultiReservableCurrency for Pallet { let reserved_balance = Self::reserved_balance(currency_id, who); let actual = reserved_balance.min(value); Self::set_reserved_balance(currency_id, who, reserved_balance - actual); - >::mutate(currency_id, |v| *v -= actual); + TotalIssuance::::mutate(currency_id, |v| *v -= actual); value - actual } @@ -947,16 +1149,19 @@ impl fungibles::Inspect for Pallet { type Balance = T::Balance; fn total_issuance(asset_id: Self::AssetId) -> Self::Balance { - Pallet::::total_issuance(asset_id) + Self::total_issuance(asset_id) } + fn minimum_balance(asset_id: Self::AssetId) -> Self::Balance { - >::minimum_balance(asset_id) + T::ExistentialDeposits::get(&asset_id) } + fn balance(asset_id: Self::AssetId, who: &T::AccountId) -> Self::Balance { - Pallet::::total_balance(asset_id, who) + Self::accounts(who, asset_id).total() } + fn reducible_balance(asset_id: Self::AssetId, who: &T::AccountId, keep_alive: bool) -> Self::Balance { - let a = Pallet::::accounts(who, asset_id); + let a = Self::accounts(who, asset_id); // Liquid balance is what is neither reserved nor locked/frozen. let liquid = a.free.saturating_sub(a.frozen); if frame_system::Pallet::::can_dec_provider(who) && !keep_alive { @@ -968,32 +1173,25 @@ impl fungibles::Inspect for Pallet { liquid.saturating_sub(must_remain_to_exist) } } + fn can_deposit(asset_id: Self::AssetId, who: &T::AccountId, amount: Self::Balance) -> DepositConsequence { - Pallet::::deposit_consequence(who, asset_id, amount, &Pallet::::accounts(who, asset_id)) + Self::deposit_consequence(who, asset_id, amount, &Self::accounts(who, asset_id)) } + fn can_withdraw( asset_id: Self::AssetId, who: &T::AccountId, amount: Self::Balance, ) -> WithdrawConsequence { - Pallet::::withdraw_consequence(who, asset_id, amount, &Pallet::::accounts(who, asset_id)) + Self::withdraw_consequence(who, asset_id, amount, &Self::accounts(who, asset_id)) } } impl fungibles::Mutate for Pallet { fn mint_into(asset_id: Self::AssetId, who: &T::AccountId, amount: Self::Balance) -> DispatchResult { - if amount.is_zero() { - return Ok(()); - } - Pallet::::try_mutate_account(who, asset_id, |account, _existed| -> DispatchResult { - Pallet::::deposit_consequence(who, asset_id, amount, &account).into_result()?; - // deposit_consequence already did overflow checking - account.free += amount; - Ok(()) - })?; - // deposit_consequence already did overflow checking - >::mutate(asset_id, |t| *t += amount); - Ok(()) + Self::deposit_consequence(who, asset_id, amount, &Self::accounts(who, asset_id)).into_result()?; + // do not require existing + Self::do_deposit(asset_id, who, amount, false, true) } fn burn_from( @@ -1001,23 +1199,10 @@ impl fungibles::Mutate for Pallet { who: &T::AccountId, amount: Self::Balance, ) -> Result { - if amount.is_zero() { - return Ok(Self::Balance::zero()); - } - let actual = Pallet::::try_mutate_account( - who, - asset_id, - |account, _existed| -> Result { - let extra = Pallet::::withdraw_consequence(who, asset_id, amount, &account).into_result()?; - // withdraw_consequence already did underflow checking - let actual = amount + extra; - account.free -= actual; - Ok(actual) - }, - )?; - // withdraw_consequence already did underflow checking - >::mutate(asset_id, |t| *t -= actual); - Ok(actual) + let extra = Self::withdraw_consequence(who, asset_id, amount, &Self::accounts(who, asset_id)).into_result()?; + let actual = amount + extra; + // allow death + Self::do_withdraw(asset_id, who, actual, ExistenceRequirement::AllowDeath, true).map(|_| actual) } } @@ -1029,34 +1214,35 @@ impl fungibles::Transfer for Pallet { amount: T::Balance, keep_alive: bool, ) -> Result { - let er = if keep_alive { + let existence_requirement = if keep_alive { ExistenceRequirement::KeepAlive } else { ExistenceRequirement::AllowDeath }; - Self::do_transfer(asset_id, source, dest, amount, er).map(|_| amount) + Self::do_transfer(asset_id, source, dest, amount, existence_requirement).map(|_| amount) } } impl fungibles::Unbalanced for Pallet { fn set_balance(asset_id: Self::AssetId, who: &T::AccountId, amount: Self::Balance) -> DispatchResult { // Balance is the same type and will not overflow - Pallet::::mutate_account(who, asset_id, |account, _| account.free = amount); + Self::mutate_account(who, asset_id, |account, _| account.free = amount); Ok(()) } fn set_total_issuance(asset_id: Self::AssetId, amount: Self::Balance) { // Balance is the same type and will not overflow - >::mutate(asset_id, |t| *t = amount); + TotalIssuance::::mutate(asset_id, |t| *t = amount); } } impl fungibles::InspectHold for Pallet { fn balance_on_hold(asset_id: Self::AssetId, who: &T::AccountId) -> T::Balance { - Pallet::::accounts(who, asset_id).reserved + Self::accounts(who, asset_id).reserved } + fn can_hold(asset_id: Self::AssetId, who: &T::AccountId, amount: T::Balance) -> bool { - let a = Pallet::::accounts(who, asset_id); + let a = Self::accounts(who, asset_id); let min_balance = T::ExistentialDeposits::get(&asset_id).max(a.frozen); if a.reserved.checked_add(&amount).is_none() { return false; @@ -1077,11 +1263,8 @@ impl fungibles::MutateHold for Pallet { if amount.is_zero() { return Ok(()); } - ensure!( - Pallet::::can_reserve(asset_id, who, amount), - Error::::BalanceTooLow - ); - Pallet::::mutate_account(who, asset_id, |a, _| { + ensure!(Self::can_reserve(asset_id, who, amount), Error::::BalanceTooLow); + Self::mutate_account(who, asset_id, |a, _| { // `can_reserve` has did underflow checking a.free -= amount; // Cannot overflow as `amount` is from `a.free` @@ -1089,6 +1272,7 @@ impl fungibles::MutateHold for Pallet { }); Ok(()) } + fn release( asset_id: Self::AssetId, who: &T::AccountId, @@ -1099,7 +1283,7 @@ impl fungibles::MutateHold for Pallet { return Ok(amount); } // Done on a best-effort basis. - Pallet::::try_mutate_account(who, asset_id, |a, _existed| { + Self::try_mutate_account(who, asset_id, |a, _existed| { let new_free = a.free.saturating_add(amount.min(a.reserved)); let actual = new_free - a.free; // Guaranteed to be <= amount and <= a.reserved @@ -1109,6 +1293,7 @@ impl fungibles::MutateHold for Pallet { Ok(actual) }) } + fn transfer_held( asset_id: Self::AssetId, source: &T::AccountId, @@ -1118,7 +1303,7 @@ impl fungibles::MutateHold for Pallet { on_hold: bool, ) -> Result { let status = if on_hold { Status::Reserved } else { Status::Free }; - Pallet::::repatriate_reserved(asset_id, source, dest, amount, status) + Self::repatriate_reserved(asset_id, source, dest, amount, status) } } @@ -1134,26 +1319,26 @@ where type NegativeImbalance = NegativeImbalance; fn total_balance(who: &T::AccountId) -> Self::Balance { - Pallet::::total_balance(GetCurrencyId::get(), who) + as MultiCurrency<_>>::total_balance(GetCurrencyId::get(), who) } fn can_slash(who: &T::AccountId, value: Self::Balance) -> bool { - Pallet::::can_slash(GetCurrencyId::get(), who, value) + as MultiCurrency<_>>::can_slash(GetCurrencyId::get(), who, value) } fn total_issuance() -> Self::Balance { - Pallet::::total_issuance(GetCurrencyId::get()) + as MultiCurrency<_>>::total_issuance(GetCurrencyId::get()) } fn minimum_balance() -> Self::Balance { - Pallet::::minimum_balance(GetCurrencyId::get()) + as MultiCurrency<_>>::minimum_balance(GetCurrencyId::get()) } fn burn(mut amount: Self::Balance) -> Self::PositiveImbalance { if amount.is_zero() { return PositiveImbalance::zero(); } - >::mutate(GetCurrencyId::get(), |issued| { + TotalIssuance::::mutate(GetCurrencyId::get(), |issued| { *issued = issued.checked_sub(&amount).unwrap_or_else(|| { amount = *issued; Zero::zero() @@ -1166,7 +1351,7 @@ where if amount.is_zero() { return NegativeImbalance::zero(); } - >::mutate(GetCurrencyId::get(), |issued| { + TotalIssuance::::mutate(GetCurrencyId::get(), |issued| { *issued = issued.checked_add(&amount).unwrap_or_else(|| { amount = Self::Balance::max_value() - *issued; Self::Balance::max_value() @@ -1176,7 +1361,7 @@ where } fn free_balance(who: &T::AccountId) -> Self::Balance { - Pallet::::free_balance(GetCurrencyId::get(), who) + as MultiCurrency<_>>::free_balance(GetCurrencyId::get(), who) } fn ensure_can_withdraw( @@ -1185,7 +1370,7 @@ where _reasons: WithdrawReasons, _new_balance: Self::Balance, ) -> DispatchResult { - Pallet::::ensure_can_withdraw(GetCurrencyId::get(), who, amount) + as MultiCurrency<_>>::ensure_can_withdraw(GetCurrencyId::get(), who, amount) } fn transfer( @@ -1232,39 +1417,16 @@ where who: &T::AccountId, value: Self::Balance, ) -> sp_std::result::Result { - if value.is_zero() { - return Ok(Self::PositiveImbalance::zero()); - } - let currency_id = GetCurrencyId::get(); - Pallet::::try_mutate_account( - who, - currency_id, - |account, existed| -> Result { - ensure!(existed, Error::::DeadAccount); - account.free = account.free.checked_add(&value).ok_or(ArithmeticError::Overflow)?; - Ok(PositiveImbalance::new(value)) - }, - ) + // do not change total issuance + Pallet::::do_deposit(GetCurrencyId::get(), who, value, true, false).map(|_| PositiveImbalance::new(value)) } /// Deposit some `value` into the free balance of `who`, possibly creating a /// new account. fn deposit_creating(who: &T::AccountId, value: Self::Balance) -> Self::PositiveImbalance { - if value.is_zero() { - return Self::PositiveImbalance::zero(); - } - let currency_id = GetCurrencyId::get(); - Pallet::::try_mutate_account( - who, - currency_id, - |account, existed| -> Result { - let ed = T::ExistentialDeposits::get(¤cy_id); - ensure!(value >= ed || existed, Error::::ExistentialDeposit); - account.free = account.free.checked_add(&value).ok_or(ArithmeticError::Overflow)?; - Ok(PositiveImbalance::new(value)) - }, - ) - .unwrap_or_else(|_| Self::PositiveImbalance::zero()) + // do not change total issuance + Pallet::::do_deposit(GetCurrencyId::get(), who, value, false, false) + .map_or_else(|_| Self::PositiveImbalance::zero(), |_| PositiveImbalance::new(value)) } fn withdraw( @@ -1273,25 +1435,9 @@ where _reasons: WithdrawReasons, liveness: ExistenceRequirement, ) -> sp_std::result::Result { - if value.is_zero() { - return Ok(Self::NegativeImbalance::zero()); - } - - let currency_id = GetCurrencyId::get(); - Pallet::::try_mutate_account(who, currency_id, |account, _existed| -> DispatchResult { - account.free = account.free.checked_sub(&value).ok_or(Error::::BalanceTooLow)?; - - Pallet::::ensure_can_withdraw(currency_id, who, value)?; - - let ed = T::ExistentialDeposits::get(¤cy_id); - let allow_death = liveness == ExistenceRequirement::AllowDeath; - let allow_death = allow_death && frame_system::Pallet::::can_dec_provider(who); - ensure!(allow_death || account.total() >= ed, Error::::KeepAlive); - - Ok(()) - })?; - - Ok(Self::NegativeImbalance::new(value)) + // do not change total issuance + Pallet::::do_withdraw(GetCurrencyId::get(), who, value, liveness, false) + .map(|_| Self::NegativeImbalance::new(value)) } fn make_free_balance_be( @@ -1332,24 +1478,24 @@ where GetCurrencyId: Get, { fn can_reserve(who: &T::AccountId, value: Self::Balance) -> bool { - Pallet::::can_reserve(GetCurrencyId::get(), who, value) + as MultiReservableCurrency<_>>::can_reserve(GetCurrencyId::get(), who, value) } fn slash_reserved(who: &T::AccountId, value: Self::Balance) -> (Self::NegativeImbalance, Self::Balance) { - let actual = Pallet::::slash_reserved(GetCurrencyId::get(), who, value); + let actual = as MultiReservableCurrency<_>>::slash_reserved(GetCurrencyId::get(), who, value); (Self::NegativeImbalance::zero(), actual) } fn reserved_balance(who: &T::AccountId) -> Self::Balance { - Pallet::::reserved_balance(GetCurrencyId::get(), who) + as MultiReservableCurrency<_>>::reserved_balance(GetCurrencyId::get(), who) } fn reserve(who: &T::AccountId, value: Self::Balance) -> DispatchResult { - Pallet::::reserve(GetCurrencyId::get(), who, value) + as MultiReservableCurrency<_>>::reserve(GetCurrencyId::get(), who, value) } fn unreserve(who: &T::AccountId, value: Self::Balance) -> Self::Balance { - Pallet::::unreserve(GetCurrencyId::get(), who, value) + as MultiReservableCurrency<_>>::unreserve(GetCurrencyId::get(), who, value) } fn repatriate_reserved( @@ -1358,7 +1504,13 @@ where value: Self::Balance, status: Status, ) -> sp_std::result::Result { - Pallet::::repatriate_reserved(GetCurrencyId::get(), slashed, beneficiary, value, status) + as MultiReservableCurrency<_>>::repatriate_reserved( + GetCurrencyId::get(), + slashed, + beneficiary, + value, + status, + ) } } @@ -1371,15 +1523,15 @@ where type MaxLocks = (); fn set_lock(id: LockIdentifier, who: &T::AccountId, amount: Self::Balance, _reasons: WithdrawReasons) { - let _ = Pallet::::set_lock(id, GetCurrencyId::get(), who, amount); + let _ = as MultiLockableCurrency<_>>::set_lock(id, GetCurrencyId::get(), who, amount); } fn extend_lock(id: LockIdentifier, who: &T::AccountId, amount: Self::Balance, _reasons: WithdrawReasons) { - let _ = Pallet::::extend_lock(id, GetCurrencyId::get(), who, amount); + let _ = as MultiLockableCurrency<_>>::extend_lock(id, GetCurrencyId::get(), who, amount); } fn remove_lock(id: LockIdentifier, who: &T::AccountId) { - let _ = Pallet::::remove_lock(id, GetCurrencyId::get(), who); + let _ = as MultiLockableCurrency<_>>::remove_lock(id, GetCurrencyId::get(), who); } } @@ -1387,7 +1539,14 @@ impl TransferAll for Pallet { #[transactional] fn transfer_all(source: &T::AccountId, dest: &T::AccountId) -> DispatchResult { Accounts::::iter_prefix(source).try_for_each(|(currency_id, account_data)| -> DispatchResult { - >::transfer(currency_id, source, dest, account_data.free) + // allow death + Self::do_transfer( + currency_id, + source, + dest, + account_data.free, + ExistenceRequirement::AllowDeath, + ) }) } } diff --git a/tokens/src/mock.rs b/tokens/src/mock.rs index d12c8d3c5..f3061466c 100644 --- a/tokens/src/mock.rs +++ b/tokens/src/mock.rs @@ -23,7 +23,6 @@ pub type Balance = u64; pub const DOT: CurrencyId = 1; pub const BTC: CurrencyId = 2; -pub const ETH: CurrencyId = 3; pub const ALICE: AccountId = AccountId32::new([0u8; 32]); pub const BOB: AccountId = AccountId32::new([1u8; 32]); pub const CHARLIE: AccountId = AccountId32::new([2u8; 32]); @@ -253,15 +252,15 @@ pub struct ExtBuilder { impl Default for ExtBuilder { fn default() -> Self { Self { - balances: vec![], + balances: vec![(DustAccount::get(), DOT, ExistentialDeposits::get(&DOT))], treasury_genesis: false, } } } impl ExtBuilder { - pub fn balances(mut self, balances: Vec<(AccountId, CurrencyId, Balance)>) -> Self { - self.balances = balances; + pub fn balances(mut self, mut balances: Vec<(AccountId, CurrencyId, Balance)>) -> Self { + self.balances.append(&mut balances); self } diff --git a/tokens/src/tests.rs b/tokens/src/tests.rs index 16590f187..f257507b7 100644 --- a/tokens/src/tests.rs +++ b/tokens/src/tests.rs @@ -4,70 +4,853 @@ use super::*; use frame_support::{assert_noop, assert_ok}; +use frame_system::RawOrigin; use mock::{Event, *}; +use sp_runtime::{traits::BadOrigin, TokenError}; + +// ************************************************* +// tests for genesis +// ************************************************* + +#[test] +fn genesis_issuance_should_work() { + ExtBuilder::default() + .one_hundred_for_alice_n_bob() + .build() + .execute_with(|| { + assert_eq!(Tokens::free_balance(DOT, &ALICE), 100); + assert_eq!(Tokens::free_balance(DOT, &BOB), 100); + assert_eq!(Tokens::free_balance(DOT, &DustAccount::get()), 2); + assert_eq!(Tokens::total_issuance(DOT), 202); + }); +} + +// ************************************************* +// tests for call +// ************************************************* + +#[test] +fn transfer_should_work() { + ExtBuilder::default() + .one_hundred_for_alice_n_bob() + .build() + .execute_with(|| { + assert_ok!(Tokens::transfer(Some(ALICE).into(), BOB, DOT, 50)); + System::assert_last_event(Event::Tokens(crate::Event::Transfer(DOT, ALICE, BOB, 50))); + assert_eq!(Tokens::free_balance(DOT, &ALICE), 50); + assert_eq!(Tokens::free_balance(DOT, &BOB), 150); + assert_eq!(Tokens::total_issuance(DOT), 202); + + assert_noop!( + Tokens::transfer(Some(ALICE).into(), BOB, DOT, 60), + Error::::BalanceTooLow, + ); + assert_noop!( + Tokens::transfer(Some(ALICE).into(), CHARLIE, DOT, 1), + Error::::ExistentialDeposit, + ); + assert_ok!(Tokens::transfer(Some(ALICE).into(), CHARLIE, DOT, 2)); + + // imply AllowDeath + assert_eq!(Accounts::::contains_key(ALICE, DOT), true); + assert_ok!(Tokens::transfer(Some(ALICE).into(), BOB, DOT, 48)); + assert_eq!(Accounts::::contains_key(ALICE, DOT), false); + assert_eq!(Tokens::free_balance(DOT, &ALICE), 0); + assert_eq!(Tokens::free_balance(DOT, &BOB), 198); + assert_eq!(Tokens::total_issuance(DOT), 202); + }); +} + +#[test] +fn transfer_keep_alive_should_work() { + ExtBuilder::default() + .one_hundred_for_alice_n_bob() + .build() + .execute_with(|| { + assert_eq!(Tokens::free_balance(DOT, &ALICE), 100); + assert_eq!(Tokens::free_balance(DOT, &BOB), 100); + + // imply KeepAlive + assert_noop!( + Tokens::transfer_keep_alive(Some(ALICE).into(), BOB, DOT, 99), + Error::::KeepAlive, + ); + + assert_ok!(Tokens::transfer_keep_alive(Some(ALICE).into(), BOB, DOT, 98)); + System::assert_last_event(Event::Tokens(crate::Event::Transfer(DOT, ALICE, BOB, 98))); + assert_eq!(Tokens::free_balance(DOT, &ALICE), 2); + assert_eq!(Tokens::free_balance(DOT, &BOB), 198); + }); +} + +#[test] +fn transfer_all_keep_alive_should_work() { + ExtBuilder::default() + .one_hundred_for_alice_n_bob() + .build() + .execute_with(|| { + assert_eq!(Tokens::free_balance(DOT, &ALICE), 100); + assert_ok!(Tokens::transfer_all(Some(ALICE).into(), CHARLIE, DOT, true)); + System::assert_has_event(Event::Tokens(crate::Event::Transfer(DOT, ALICE, CHARLIE, 98))); + assert_eq!(Tokens::free_balance(DOT, &ALICE), 2); + + assert_ok!(Tokens::set_lock(ID_1, DOT, &BOB, 50)); + assert_eq!(Tokens::accounts(&BOB, DOT).frozen, 50); + assert_eq!(Tokens::free_balance(DOT, &BOB), 100); + assert_ok!(Tokens::transfer_all(Some(BOB).into(), CHARLIE, DOT, true)); + System::assert_has_event(Event::Tokens(crate::Event::Transfer(DOT, BOB, CHARLIE, 50))); + }); +} + +#[test] +fn transfer_all_allow_death_should_work() { + ExtBuilder::default() + .one_hundred_for_alice_n_bob() + .build() + .execute_with(|| { + assert_eq!(Accounts::::contains_key(ALICE, DOT), true); + assert_eq!(Tokens::free_balance(DOT, &ALICE), 100); + assert_ok!(Tokens::transfer_all(Some(ALICE).into(), CHARLIE, DOT, false)); + System::assert_last_event(Event::Tokens(crate::Event::Transfer(DOT, ALICE, CHARLIE, 100))); + assert_eq!(Accounts::::contains_key(ALICE, DOT), false); + assert_eq!(Tokens::free_balance(DOT, &ALICE), 0); + + assert_ok!(Tokens::set_lock(ID_1, DOT, &BOB, 50)); + assert_eq!(Tokens::accounts(&BOB, DOT).frozen, 50); + assert_eq!(Tokens::free_balance(DOT, &BOB), 100); + assert_ok!(Tokens::transfer_all(Some(BOB).into(), CHARLIE, DOT, false)); + System::assert_last_event(Event::Tokens(crate::Event::Transfer(DOT, BOB, CHARLIE, 50))); + }); +} + +#[test] +fn force_transfer_should_work() { + ExtBuilder::default() + .one_hundred_for_alice_n_bob() + .build() + .execute_with(|| { + assert_eq!(Accounts::::contains_key(ALICE, DOT), true); + assert_eq!(Tokens::free_balance(DOT, &ALICE), 100); + assert_eq!(Tokens::free_balance(DOT, &BOB), 100); + assert_noop!( + Tokens::force_transfer(Some(ALICE).into(), ALICE, BOB, DOT, 100), + BadOrigin + ); + + // imply AllowDeath + assert_ok!(Tokens::force_transfer(RawOrigin::Root.into(), ALICE, BOB, DOT, 100)); + System::assert_last_event(Event::Tokens(crate::Event::Transfer(DOT, ALICE, BOB, 100))); + assert_eq!(Accounts::::contains_key(ALICE, DOT), false); + assert_eq!(Tokens::free_balance(DOT, &ALICE), 0); + assert_eq!(Tokens::free_balance(DOT, &BOB), 200); + }); +} + +#[test] +fn set_balance_should_work() { + ExtBuilder::default() + .one_hundred_for_alice_n_bob() + .build() + .execute_with(|| { + // bad origin + assert_noop!(Tokens::set_balance(Some(ALICE).into(), ALICE, DOT, 200, 100), BadOrigin); + + // total balance overflow + assert_noop!( + Tokens::set_balance(RawOrigin::Root.into(), ALICE, DOT, Balance::max_value(), 1), + ArithmeticError::Overflow + ); + + // total issurance overflow + assert_noop!( + Tokens::set_balance(RawOrigin::Root.into(), ALICE, DOT, Balance::max_value(), 0), + ArithmeticError::Overflow + ); + + // total issurance overflow + assert_noop!( + Tokens::set_balance(RawOrigin::Root.into(), ALICE, DOT, Balance::max_value(), 0), + ArithmeticError::Overflow + ); + + assert_eq!(Accounts::::contains_key(ALICE, DOT), true); + assert_eq!(Tokens::free_balance(DOT, &ALICE), 100); + assert_eq!(Tokens::reserved_balance(DOT, &ALICE), 0); + assert_eq!(Tokens::total_issuance(DOT), 202); + + assert_ok!(Tokens::set_balance(RawOrigin::Root.into(), ALICE, DOT, 200, 100)); + System::assert_has_event(Event::Tokens(crate::Event::BalanceSet(DOT, ALICE, 200, 100))); + assert_eq!(Accounts::::contains_key(ALICE, DOT), true); + assert_eq!(Tokens::free_balance(DOT, &ALICE), 200); + assert_eq!(Tokens::reserved_balance(DOT, &ALICE), 100); + assert_eq!(Tokens::total_issuance(DOT), 402); + + assert_eq!(Accounts::::contains_key(BOB, DOT), true); + assert_eq!(Tokens::free_balance(DOT, &BOB), 100); + assert_eq!(Tokens::reserved_balance(DOT, &BOB), 0); + + assert_ok!(Tokens::set_balance(RawOrigin::Root.into(), BOB, DOT, 0, 0)); + System::assert_has_event(Event::Tokens(crate::Event::BalanceSet(DOT, BOB, 0, 0))); + assert_eq!(Accounts::::contains_key(BOB, DOT), false); + assert_eq!(Tokens::free_balance(DOT, &BOB), 0); + assert_eq!(Tokens::reserved_balance(DOT, &BOB), 0); + assert_eq!(Tokens::total_issuance(DOT), 302); + + assert_eq!(Accounts::::contains_key(CHARLIE, DOT), false); + assert_eq!(Tokens::free_balance(DOT, &CHARLIE), 0); + assert_eq!(Tokens::reserved_balance(DOT, &CHARLIE), 0); + + // below ED, + assert_ok!(Tokens::set_balance(RawOrigin::Root.into(), CHARLIE, DOT, 1, 0)); + System::assert_has_event(Event::Tokens(crate::Event::BalanceSet(DOT, CHARLIE, 0, 0))); + assert_eq!(Accounts::::contains_key(CHARLIE, DOT), false); + assert_eq!(Tokens::free_balance(DOT, &CHARLIE), 0); + assert_eq!(Tokens::reserved_balance(DOT, &CHARLIE), 0); + assert_eq!(Tokens::total_issuance(DOT), 302); + }); +} + +// ************************************************* +// tests for inline impl +// ************************************************* + +#[test] +fn deposit_consequence_should_work() { + ExtBuilder::default().build().execute_with(|| { + assert_eq!( + Tokens::deposit_consequence( + &CHARLIE, + DOT, + 0, + &AccountData { + free: 1, + reserved: 0, + frozen: 0 + } + ) + .into_result(), + Ok(()) + ); + + // total issuance overflow + assert_eq!( + Tokens::deposit_consequence( + &CHARLIE, + DOT, + Balance::max_value(), + &AccountData { + free: 1, + reserved: 0, + frozen: 0 + } + ) + .into_result(), + Err(ArithmeticError::Overflow.into()) + ); + + // total balance overflow + assert_eq!( + Tokens::deposit_consequence( + &CHARLIE, + DOT, + 1, + &AccountData { + free: Balance::max_value(), + reserved: 0, + frozen: 0 + } + ) + .into_result(), + Err(ArithmeticError::Overflow.into()) + ); + + // below ed + assert_eq!( + Tokens::deposit_consequence( + &CHARLIE, + DOT, + 1, + &AccountData { + free: 0, + reserved: 0, + frozen: 0 + } + ) + .into_result(), + Err(TokenError::BelowMinimum.into()) + ); + + assert_eq!( + Tokens::deposit_consequence( + &CHARLIE, + DOT, + 1, + &AccountData { + free: 1, + reserved: 0, + frozen: 0 + } + ) + .into_result(), + Ok(()) + ); + }); +} + +#[test] +fn withdraw_consequence_should_work() { + ExtBuilder::default().build().execute_with(|| { + assert_eq!( + Tokens::withdraw_consequence( + &ALICE, + DOT, + 0, + &AccountData { + free: 1, + reserved: 0, + frozen: 0 + } + ) + .into_result(), + Ok(Zero::zero()) + ); + + // total issuance underflow + assert_eq!(Tokens::total_issuance(DOT), 2); + assert_eq!( + Tokens::withdraw_consequence( + &ALICE, + DOT, + 3, + &AccountData { + free: 1, + reserved: 0, + frozen: 0 + } + ) + .into_result(), + Err(ArithmeticError::Underflow.into()) + ); + + // total issuance is not enough + assert_eq!( + Tokens::withdraw_consequence( + &ALICE, + DOT, + 2, + &AccountData { + free: 1, + reserved: 0, + frozen: 0 + } + ) + .into_result(), + Err(TokenError::NoFunds.into()) + ); + + // below ED and cannot dec provider + assert_ok!(Tokens::update_balance(DOT, &ALICE, 2)); + assert_eq!(System::providers(&ALICE), 1); + assert_ok!(System::inc_consumers(&ALICE)); + assert_eq!(System::can_dec_provider(&ALICE), false); + assert_eq!( + Tokens::withdraw_consequence( + &ALICE, + DOT, + 1, + &AccountData { + free: 2, + reserved: 0, + frozen: 0 + } + ) + .into_result(), + Err(TokenError::WouldDie.into()) + ); + + // below ED and can dec provider + let _ = System::inc_providers(&ALICE); + assert_eq!(System::can_dec_provider(&ALICE), true); + assert_eq!( + Tokens::withdraw_consequence( + &ALICE, + DOT, + 1, + &AccountData { + free: 2, + reserved: 0, + frozen: 0 + } + ) + .into_result(), + Ok(1) + ); + + // free balance is not enough + assert_eq!( + Tokens::withdraw_consequence( + &ALICE, + DOT, + 2, + &AccountData { + free: 1, + reserved: 1, + frozen: 0 + } + ) + .into_result(), + Err(TokenError::NoFunds.into()) + ); + + // less to frozen balance + assert_eq!( + Tokens::withdraw_consequence( + &ALICE, + DOT, + 2, + &AccountData { + free: 2, + reserved: 0, + frozen: 2 + } + ) + .into_result(), + Err(TokenError::Frozen.into()) + ); + }); +} + +#[test] +fn ensure_can_withdraw_should_work() { + ExtBuilder::default() + .one_hundred_for_alice_n_bob() + .build() + .execute_with(|| { + assert_eq!(Tokens::free_balance(DOT, &ALICE), 100); + + assert_noop!( + Tokens::ensure_can_withdraw(DOT, &ALICE, 101), + Error::::BalanceTooLow + ); + + assert_ok!(Tokens::set_lock(ID_1, DOT, &ALICE, 50)); + assert_noop!( + Tokens::ensure_can_withdraw(DOT, &ALICE, 51), + Error::::LiquidityRestrictions + ); + + assert_ok!(Tokens::ensure_can_withdraw(DOT, &ALICE, 50)); + }); +} #[test] -fn minimum_balance_work() { +fn set_free_balance_should_work() { ExtBuilder::default().build().execute_with(|| { - assert_eq!(Tokens::minimum_balance(BTC), 1); - assert_eq!(Tokens::minimum_balance(DOT), 2); - assert_eq!(Tokens::minimum_balance(ETH), 0); + // set_free_balance do not update total inssurance! + assert_eq!(Tokens::free_balance(DOT, &ALICE), 0); + assert_eq!(Tokens::total_issuance(DOT), 2); + Tokens::set_free_balance(DOT, &ALICE, 100); + assert_eq!(Tokens::free_balance(DOT, &ALICE), 100); + assert_eq!(Tokens::total_issuance(DOT), 2); }); } #[test] -fn remove_dust_work() { - ExtBuilder::default().build().execute_with(|| { - System::set_block_number(1); - assert_ok!(Tokens::deposit(DOT, &ALICE, 100)); - assert_eq!(Tokens::total_issuance(DOT), 100); - assert_eq!(Accounts::::contains_key(ALICE, DOT), true); - assert_eq!(Tokens::free_balance(DOT, &ALICE), 100); - assert_eq!(System::providers(&ALICE), 1); - assert_eq!(Accounts::::contains_key(DustAccount::get(), DOT), false); - assert_eq!(Tokens::free_balance(DOT, &DustAccount::get()), 0); - assert_eq!(System::providers(&DustAccount::get()), 0); - - // total is gte ED, will not handle dust - assert_ok!(Tokens::withdraw(DOT, &ALICE, 98)); - assert_eq!(Tokens::total_issuance(DOT), 2); - assert_eq!(Accounts::::contains_key(ALICE, DOT), true); - assert_eq!(Tokens::free_balance(DOT, &ALICE), 2); - assert_eq!(System::providers(&ALICE), 1); - assert_eq!(Accounts::::contains_key(DustAccount::get(), DOT), false); - assert_eq!(Tokens::free_balance(DOT, &DustAccount::get()), 0); - assert_eq!(System::providers(&DustAccount::get()), 0); - - // ensure dust account balance gte ED - assert_ok!(Tokens::deposit(DOT, &DustAccount::get(), Tokens::minimum_balance(DOT))); - - assert_ok!(Tokens::withdraw(DOT, &ALICE, 1)); - - // total is lte ED, will handle dust - assert_eq!(Tokens::total_issuance(DOT), 3); - assert_eq!(Accounts::::contains_key(ALICE, DOT), false); - assert_eq!(Tokens::free_balance(DOT, &ALICE), 0); - assert_eq!(System::providers(&ALICE), 0); +fn set_reserved_balance_should_work() { + ExtBuilder::default().build().execute_with(|| { + // set_reserved_balance do not update total inssurance! + assert_eq!(Tokens::reserved_balance(DOT, &ALICE), 0); + assert_eq!(Tokens::total_issuance(DOT), 2); + Tokens::set_reserved_balance(DOT, &ALICE, 100); + assert_eq!(Tokens::reserved_balance(DOT, &ALICE), 100); + assert_eq!(Tokens::total_issuance(DOT), 2); + }); +} + +#[test] +fn do_transfer_should_work() { + ExtBuilder::default() + .one_hundred_for_alice_n_bob() + .build() + .execute_with(|| { + // always ok when from == to + assert_ok!(Tokens::do_transfer( + DOT, + &ALICE, + &ALICE, + 101, + ExistenceRequirement::KeepAlive + )); + + assert_eq!(Tokens::free_balance(DOT, &ALICE), 100); + assert_eq!(Tokens::free_balance(DOT, &BOB), 100); + assert_eq!(Tokens::free_balance(DOT, &CHARLIE), 0); + + assert_noop!( + Tokens::do_transfer(DOT, &ALICE, &BOB, 101, ExistenceRequirement::KeepAlive), + Error::::BalanceTooLow + ); + assert_noop!( + Tokens::do_transfer(DOT, &ALICE, &CHARLIE, 1, ExistenceRequirement::KeepAlive), + Error::::ExistentialDeposit + ); + + assert_ok!(Tokens::do_transfer( + DOT, + &ALICE, + &BOB, + 100, + ExistenceRequirement::AllowDeath + )); + assert_eq!(Tokens::free_balance(DOT, &ALICE), 0); + assert_eq!(Tokens::free_balance(DOT, &BOB), 200); + }); +} + +#[test] +fn do_transfer_failed_due_to_keep_alive() { + ExtBuilder::default() + .one_hundred_for_alice_n_bob() + .build() + .execute_with(|| { + assert_eq!(Tokens::free_balance(DOT, &ALICE), 100); + assert_noop!( + Tokens::do_transfer(DOT, &ALICE, &BOB, 99, ExistenceRequirement::KeepAlive), + Error::::KeepAlive + ); + }); +} + +#[test] +fn do_transfer_and_dust_remove_when_allow_death() { + ExtBuilder::default() + .one_hundred_for_alice_n_bob() + .build() + .execute_with(|| { + assert_eq!(Tokens::free_balance(DOT, &ALICE), 100); + assert_eq!(Tokens::free_balance(DOT, &BOB), 100); + assert_eq!(Tokens::free_balance(DOT, &DustAccount::get()), 2); + + assert_ok!(Tokens::do_transfer( + DOT, + &ALICE, + &BOB, + 99, + ExistenceRequirement::AllowDeath + )); + assert_eq!(Tokens::free_balance(DOT, &ALICE), 0); + assert_eq!(Tokens::free_balance(DOT, &BOB), 199); + assert_eq!(Tokens::free_balance(DOT, &DustAccount::get()), 3); + }); +} + +#[test] +fn do_transfer_failed_when_allow_death_due_to_cannot_dec_provider() { + ExtBuilder::default() + .one_hundred_for_alice_n_bob() + .build() + .execute_with(|| { + assert_eq!(System::can_dec_provider(&ALICE), true); + assert_ok!(System::inc_consumers(&ALICE)); + assert_eq!(System::can_dec_provider(&ALICE), false); + assert_noop!( + Tokens::do_transfer(DOT, &ALICE, &BOB, 99, ExistenceRequirement::AllowDeath), + Error::::KeepAlive + ); + + assert_ok!(Tokens::deposit(BTC, &ALICE, 100)); + assert_eq!(System::can_dec_provider(&ALICE), true); + assert_ok!(Tokens::do_transfer( + DOT, + &ALICE, + &BOB, + 99, + ExistenceRequirement::AllowDeath + )); + }); +} + +#[test] +fn do_withdraw_when_keep_alive() { + ExtBuilder::default() + .one_hundred_for_alice_n_bob() + .build() + .execute_with(|| { + assert_eq!(Tokens::free_balance(DOT, &ALICE), 100); + assert_eq!(Tokens::total_issuance(DOT), 202); + + assert_ok!(Tokens::do_withdraw( + DOT, + &ALICE, + 50, + ExistenceRequirement::KeepAlive, + true + )); + assert_eq!(Tokens::free_balance(DOT, &ALICE), 50); + assert_eq!(Tokens::total_issuance(DOT), 152); + + assert_noop!( + Tokens::do_withdraw(DOT, &ALICE, 49, ExistenceRequirement::KeepAlive, true), + Error::::KeepAlive + ); + + // do not change issuance + assert_ok!(Tokens::do_withdraw( + DOT, + &ALICE, + 10, + ExistenceRequirement::KeepAlive, + false + )); + assert_eq!(Tokens::free_balance(DOT, &ALICE), 40); + assert_eq!(Tokens::total_issuance(DOT), 152); + + assert_ok!(Tokens::set_lock(ID_1, DOT, &BOB, 50)); + assert_eq!(Tokens::free_balance(DOT, &BOB), 100); + assert_noop!( + Tokens::do_withdraw(DOT, &BOB, 51, ExistenceRequirement::KeepAlive, true), + Error::::LiquidityRestrictions + ); + }); +} + +#[test] +fn do_withdraw_when_allow_death() { + ExtBuilder::default() + .one_hundred_for_alice_n_bob() + .build() + .execute_with(|| { + assert_eq!(Tokens::total_issuance(DOT), 202); + assert_eq!(Tokens::free_balance(DOT, &ALICE), 100); + assert_eq!(Accounts::::contains_key(ALICE, DOT), true); + + assert_ok!(Tokens::do_withdraw( + DOT, + &ALICE, + 99, + ExistenceRequirement::AllowDeath, + true + )); + assert_eq!(Tokens::total_issuance(DOT), 103); + assert_eq!(Tokens::free_balance(DOT, &ALICE), 0); + assert_eq!(Accounts::::contains_key(ALICE, DOT), false); + }); +} + +#[test] +fn do_deposit_should_work() { + ExtBuilder::default() + .one_hundred_for_alice_n_bob() + .build() + .execute_with(|| { + assert_eq!(Accounts::::contains_key(CHARLIE, DOT), false); + assert_eq!(Tokens::free_balance(DOT, &CHARLIE), 0); + assert_eq!(Tokens::total_issuance(DOT), 202); + assert_noop!( + Tokens::do_deposit(DOT, &CHARLIE, 10, true, true), + Error::::DeadAccount + ); + + assert_noop!( + Tokens::do_deposit(DOT, &CHARLIE, 1, false, true), + Error::::ExistentialDeposit + ); + + assert_ok!(Tokens::do_deposit(DOT, &CHARLIE, 10, false, true)); + assert_eq!(Accounts::::contains_key(CHARLIE, DOT), true); + assert_eq!(Tokens::free_balance(DOT, &CHARLIE), 10); + assert_eq!(Tokens::total_issuance(DOT), 212); + + assert_eq!(Accounts::::contains_key(ALICE, DOT), true); + assert_eq!(Tokens::free_balance(DOT, &ALICE), 100); + + assert_ok!(Tokens::do_deposit(DOT, &ALICE, 10, true, true)); + assert_eq!(Tokens::free_balance(DOT, &ALICE), 110); + assert_eq!(Tokens::total_issuance(DOT), 222); + + assert_noop!( + Tokens::do_deposit(DOT, &BOB, Balance::max_value(), false, true), + ArithmeticError::Overflow + ); + + // do not change issuance + assert_ok!(Tokens::do_deposit(DOT, &ALICE, 10, true, false)); + assert_eq!(Tokens::free_balance(DOT, &ALICE), 120); + assert_eq!(Tokens::total_issuance(DOT), 222); + }); +} + +// ************************************************* +// tests for endowed account and remove account +// ************************************************* + +#[test] +fn endowed_account_work() { + ExtBuilder::default().build().execute_with(|| { + assert_eq!(System::providers(&ALICE), 0); + assert_eq!(Accounts::::contains_key(ALICE, DOT), false); + Tokens::set_free_balance(DOT, &ALICE, 100); + System::assert_last_event(Event::Tokens(crate::Event::Endowed(DOT, ALICE, 100))); + assert_eq!(System::providers(&ALICE), 1); + assert_eq!(Accounts::::contains_key(ALICE, DOT), true); + }); +} + +#[test] +fn remove_account_work() { + ExtBuilder::default() + .one_hundred_for_alice_n_bob() + .build() + .execute_with(|| { + assert_eq!(System::providers(&ALICE), 1); + assert_eq!(Accounts::::contains_key(ALICE, DOT), true); + Tokens::set_free_balance(DOT, &ALICE, 0); + assert_eq!(System::providers(&ALICE), 0); + assert_eq!(Accounts::::contains_key(ALICE, DOT), false); + }); +} + +#[test] +fn dust_remove_work() { + ExtBuilder::default() + .one_hundred_for_alice_n_bob() + .build() + .execute_with(|| { + let dust_account = DustAccount::get(); + + assert_eq!(System::providers(&ALICE), 1); + assert_eq!(Accounts::::contains_key(ALICE, DOT), true); + assert_eq!(Tokens::free_balance(DOT, &ALICE), 100); + assert_eq!(Tokens::free_balance(DOT, &dust_account), 2); + Tokens::set_free_balance(DOT, &ALICE, 1); + System::assert_last_event(Event::Tokens(crate::Event::DustLost(DOT, ALICE, 1))); + assert_eq!(System::providers(&ALICE), 0); + assert_eq!(Accounts::::contains_key(ALICE, DOT), false); + assert_eq!(Tokens::free_balance(DOT, &ALICE), 0); + assert_eq!(Tokens::free_balance(DOT, &dust_account), 3); + }); +} + +#[test] +fn account_survive_due_to_dust_transfer_failure() { + ExtBuilder::default().build().execute_with(|| { + let dust_account = DustAccount::get(); + + Tokens::set_free_balance(DOT, &dust_account, 0); + assert_eq!(Tokens::free_balance(DOT, &dust_account), 0); + assert_eq!(Tokens::free_balance(DOT, &ALICE), 0); + assert_eq!(System::providers(&ALICE), 0); + assert_eq!(Accounts::::contains_key(ALICE, DOT), false); + Tokens::set_free_balance(DOT, &ALICE, 1); + System::assert_last_event(Event::Tokens(crate::Event::DustLost(DOT, ALICE, 1))); + assert_eq!(Tokens::free_balance(DOT, &dust_account), 0); + assert_eq!(Tokens::free_balance(DOT, &ALICE), 1); + assert_eq!(System::providers(&ALICE), 1); + assert_eq!(Accounts::::contains_key(ALICE, DOT), true); + }); +} + +// ************************************************* +// tests for MultiCurrency, MultiCurrencyExtended, MultiLockableCurrency, +// MultiReservableCurrency traits ********************************************** +// *** + +#[test] +fn multicurrency_deposit_work() { + ExtBuilder::default().build().execute_with(|| { + assert_eq!(Accounts::::contains_key(CHARLIE, DOT), false); + assert_eq!(Tokens::free_balance(DOT, &CHARLIE), 0); + assert_eq!(Tokens::total_issuance(DOT), 2); + assert_ok!(Tokens::deposit(DOT, &CHARLIE, 10)); + assert_eq!(Accounts::::contains_key(CHARLIE, DOT), true); + assert_eq!(Tokens::free_balance(DOT, &CHARLIE), 10); + assert_eq!(Tokens::total_issuance(DOT), 12); + }); +} + +#[test] +fn multicurrency_withdraw_work() { + ExtBuilder::default() + .one_hundred_for_alice_n_bob() + .build() + .execute_with(|| { + assert_eq!(Accounts::::contains_key(ALICE, DOT), true); + assert_eq!(Tokens::free_balance(DOT, &ALICE), 100); + assert_eq!(Tokens::total_issuance(DOT), 202); + assert_ok!(Tokens::withdraw(DOT, &ALICE, 99)); + assert_eq!(Accounts::::contains_key(ALICE, DOT), false); + assert_eq!(Tokens::free_balance(DOT, &ALICE), 0); + assert_eq!(Tokens::total_issuance(DOT), 103); + }); +} + +#[test] +fn multicurrency_transfer_work() { + ExtBuilder::default() + .one_hundred_for_alice_n_bob() + .build() + .execute_with(|| { + assert_eq!(Accounts::::contains_key(ALICE, DOT), true); + assert_eq!(Tokens::free_balance(DOT, &ALICE), 100); + assert_eq!(Tokens::free_balance(DOT, &BOB), 100); + assert_ok!(>::transfer(DOT, &ALICE, &BOB, 99)); + assert_eq!(Accounts::::contains_key(ALICE, DOT), false); + assert_eq!(Tokens::free_balance(DOT, &ALICE), 0); + assert_eq!(Tokens::free_balance(DOT, &BOB), 199); + }); +} + +#[test] +fn multicurrency_can_slash_work() { + ExtBuilder::default() + .one_hundred_for_alice_n_bob() + .build() + .execute_with(|| { + assert_eq!(Tokens::free_balance(DOT, &ALICE), 100); + assert_eq!(Tokens::can_slash(DOT, &ALICE, 101), false); + assert_eq!(Tokens::can_slash(DOT, &ALICE, 100), true); + }); +} - // will not handle dust for module account - assert_eq!(Accounts::::contains_key(DustAccount::get(), DOT), true); - assert_eq!(Tokens::free_balance(DOT, &DustAccount::get()), 3); - assert_eq!(System::providers(&DustAccount::get()), 1); +#[test] +fn multicurrency_slash_work() { + ExtBuilder::default() + .one_hundred_for_alice_n_bob() + .build() + .execute_with(|| { + // slashed_amount < amount + assert_eq!(Tokens::total_issuance(DOT), 202); + assert_eq!(Tokens::slash(DOT, &ALICE, 50), 0); + assert_eq!(Tokens::free_balance(DOT, &ALICE), 50); + assert_eq!(Tokens::total_issuance(DOT), 152); - System::assert_last_event(Event::Tokens(crate::Event::DustLost(DOT, ALICE, 1))); - }); + // slashed_amount == amount + assert_eq!(Tokens::slash(DOT, &ALICE, 51), 1); + assert_eq!(Tokens::free_balance(DOT, &ALICE), 0); + assert_eq!(Tokens::total_issuance(DOT), 102); + }); } #[test] -fn set_free_balance_should_work() { - ExtBuilder::default().build().execute_with(|| { - Tokens::set_free_balance(DOT, &ALICE, 100); - System::assert_last_event(Event::Tokens(crate::Event::Endowed(DOT, ALICE, 100))); - }); +fn multicurrency_extended_update_balance_work() { + ExtBuilder::default() + .one_hundred_for_alice_n_bob() + .build() + .execute_with(|| { + assert_ok!(Tokens::update_balance(DOT, &ALICE, 50)); + assert_eq!(Tokens::free_balance(DOT, &ALICE), 150); + assert_eq!(Tokens::total_issuance(DOT), 252); + + assert_ok!(Tokens::update_balance(DOT, &BOB, -50)); + assert_eq!(Tokens::free_balance(DOT, &BOB), 50); + assert_eq!(Tokens::total_issuance(DOT), 202); + + assert_noop!(Tokens::update_balance(DOT, &BOB, -60), Error::::BalanceTooLow); + }); } #[test] -fn set_lock_should_work() { +fn multi_lockable_currency_set_lock_work() { ExtBuilder::default() .one_hundred_for_alice_n_bob() .build() @@ -86,7 +869,7 @@ fn set_lock_should_work() { } #[test] -fn extend_lock_should_work() { +fn multi_lockable_currency_extend_lock_work() { ExtBuilder::default() .one_hundred_for_alice_n_bob() .build() @@ -104,7 +887,7 @@ fn extend_lock_should_work() { } #[test] -fn remove_lock_should_work() { +fn multi_lockable_currency_remove_lock_work() { ExtBuilder::default() .one_hundred_for_alice_n_bob() .build() @@ -118,35 +901,40 @@ fn remove_lock_should_work() { } #[test] -fn frozen_can_limit_liquidity() { +fn multi_reservable_currency_can_reserve_work() { ExtBuilder::default() .one_hundred_for_alice_n_bob() .build() .execute_with(|| { - assert_ok!(Tokens::set_lock(ID_1, DOT, &ALICE, 90)); - assert_noop!( - >::transfer(DOT, &ALICE, &BOB, 11), - Error::::LiquidityRestrictions, - ); - assert_ok!(Tokens::set_lock(ID_1, DOT, &ALICE, 10)); - assert_ok!(>::transfer(DOT, &ALICE, &BOB, 11)); + assert_eq!(Tokens::can_reserve(DOT, &ALICE, 0), true); + assert_eq!(Tokens::can_reserve(DOT, &ALICE, 101), false); + assert_eq!(Tokens::can_reserve(DOT, &ALICE, 100), true); }); } #[test] -fn can_reserve_is_correct() { +fn multi_reservable_currency_slash_reserved_work() { ExtBuilder::default() .one_hundred_for_alice_n_bob() .build() .execute_with(|| { - assert_eq!(Tokens::can_reserve(DOT, &ALICE, 0), true); - assert_eq!(Tokens::can_reserve(DOT, &ALICE, 101), false); - assert_eq!(Tokens::can_reserve(DOT, &ALICE, 100), true); + assert_ok!(Tokens::reserve(DOT, &ALICE, 50)); + assert_eq!(Tokens::free_balance(DOT, &ALICE), 50); + assert_eq!(Tokens::reserved_balance(DOT, &ALICE), 50); + assert_eq!(Tokens::total_issuance(DOT), 202); + assert_eq!(Tokens::slash_reserved(DOT, &ALICE, 0), 0); + assert_eq!(Tokens::free_balance(DOT, &ALICE), 50); + assert_eq!(Tokens::reserved_balance(DOT, &ALICE), 50); + assert_eq!(Tokens::total_issuance(DOT), 202); + assert_eq!(Tokens::slash_reserved(DOT, &ALICE, 100), 50); + assert_eq!(Tokens::free_balance(DOT, &ALICE), 50); + assert_eq!(Tokens::reserved_balance(DOT, &ALICE), 0); + assert_eq!(Tokens::total_issuance(DOT), 152); }); } #[test] -fn reserve_should_work() { +fn multi_reservable_currency_reserve_work() { ExtBuilder::default() .one_hundred_for_alice_n_bob() .build() @@ -165,7 +953,7 @@ fn reserve_should_work() { } #[test] -fn unreserve_should_work() { +fn multi_reservable_currency_unreserve_work() { ExtBuilder::default() .one_hundred_for_alice_n_bob() .build() @@ -191,28 +979,7 @@ fn unreserve_should_work() { } #[test] -fn slash_reserved_should_work() { - ExtBuilder::default() - .one_hundred_for_alice_n_bob() - .build() - .execute_with(|| { - assert_ok!(Tokens::reserve(DOT, &ALICE, 50)); - assert_eq!(Tokens::free_balance(DOT, &ALICE), 50); - assert_eq!(Tokens::reserved_balance(DOT, &ALICE), 50); - assert_eq!(Tokens::total_issuance(DOT), 200); - assert_eq!(Tokens::slash_reserved(DOT, &ALICE, 0), 0); - assert_eq!(Tokens::free_balance(DOT, &ALICE), 50); - assert_eq!(Tokens::reserved_balance(DOT, &ALICE), 50); - assert_eq!(Tokens::total_issuance(DOT), 200); - assert_eq!(Tokens::slash_reserved(DOT, &ALICE, 100), 50); - assert_eq!(Tokens::free_balance(DOT, &ALICE), 50); - assert_eq!(Tokens::reserved_balance(DOT, &ALICE), 0); - assert_eq!(Tokens::total_issuance(DOT), 150); - }); -} - -#[test] -fn repatriate_reserved_should_work() { +fn multi_reservable_currency_repatriate_reserved_work() { ExtBuilder::default() .one_hundred_for_alice_n_bob() .build() @@ -271,175 +1038,17 @@ fn slash_draw_reserved_correct() { assert_ok!(Tokens::reserve(DOT, &ALICE, 50)); assert_eq!(Tokens::free_balance(DOT, &ALICE), 50); assert_eq!(Tokens::reserved_balance(DOT, &ALICE), 50); - assert_eq!(Tokens::total_issuance(DOT), 200); + assert_eq!(Tokens::total_issuance(DOT), 202); assert_eq!(Tokens::slash(DOT, &ALICE, 80), 0); assert_eq!(Tokens::free_balance(DOT, &ALICE), 0); assert_eq!(Tokens::reserved_balance(DOT, &ALICE), 20); - assert_eq!(Tokens::total_issuance(DOT), 120); + assert_eq!(Tokens::total_issuance(DOT), 122); assert_eq!(Tokens::slash(DOT, &ALICE, 50), 30); assert_eq!(Tokens::free_balance(DOT, &ALICE), 0); assert_eq!(Tokens::reserved_balance(DOT, &ALICE), 0); - assert_eq!(Tokens::total_issuance(DOT), 100); - }); -} - -#[test] -fn genesis_issuance_should_work() { - ExtBuilder::default() - .one_hundred_for_alice_n_bob() - .build() - .execute_with(|| { - assert_eq!(Tokens::free_balance(DOT, &ALICE), 100); - assert_eq!(Tokens::free_balance(DOT, &BOB), 100); - assert_eq!(Tokens::total_issuance(DOT), 200); - }); -} - -#[test] -fn transfer_should_work() { - ExtBuilder::default() - .one_hundred_for_alice_n_bob() - .build() - .execute_with(|| { - System::set_block_number(1); - - assert_ok!(Tokens::transfer(Some(ALICE).into(), BOB, DOT, 50)); - assert_eq!(Tokens::free_balance(DOT, &ALICE), 50); - assert_eq!(Tokens::free_balance(DOT, &BOB), 150); - assert_eq!(Tokens::total_issuance(DOT), 200); - - System::assert_last_event(Event::Tokens(crate::Event::Transfer(DOT, ALICE, BOB, 50))); - - assert_noop!( - Tokens::transfer(Some(ALICE).into(), BOB, DOT, 60), - Error::::BalanceTooLow, - ); - assert_noop!( - Tokens::transfer(Some(ALICE).into(), CHARLIE, DOT, 1), - Error::::ExistentialDeposit, - ); - assert_ok!(Tokens::transfer(Some(ALICE).into(), CHARLIE, DOT, 2)); - }); -} - -#[test] -fn transfer_all_should_work() { - ExtBuilder::default() - .one_hundred_for_alice_n_bob() - .build() - .execute_with(|| { - System::set_block_number(1); - - assert_ok!(Tokens::transfer_all(Some(ALICE).into(), BOB, DOT)); - assert_eq!(Tokens::free_balance(DOT, &ALICE), 0); - assert_eq!(Tokens::free_balance(DOT, &BOB), 200); - - System::assert_last_event(Event::Tokens(crate::Event::Transfer(DOT, ALICE, BOB, 100))); - }); -} - -#[test] -fn transfer_failed_when_allow_death_but_cannot_dec_provider() { - ExtBuilder::default() - .one_hundred_for_alice_n_bob() - .build() - .execute_with(|| { - assert_eq!(System::can_dec_provider(&ALICE), true); - assert_ok!(System::inc_consumers(&ALICE)); - assert_eq!(System::can_dec_provider(&ALICE), false); - assert_noop!( - Tokens::transfer(Some(ALICE).into(), BOB, DOT, 100), - Error::::KeepAlive - ); - - assert_ok!(Tokens::deposit(BTC, &ALICE, 100)); - assert_eq!(System::can_dec_provider(&ALICE), true); - assert_ok!(Tokens::transfer(Some(ALICE).into(), BOB, DOT, 100)); - }); -} - -#[test] -fn deposit_should_work() { - ExtBuilder::default() - .one_hundred_for_alice_n_bob() - .build() - .execute_with(|| { - assert_ok!(Tokens::deposit(DOT, &ALICE, 100)); - assert_eq!(Tokens::free_balance(DOT, &ALICE), 200); - assert_eq!(Tokens::total_issuance(DOT), 300); - - assert_noop!( - Tokens::deposit(DOT, &ALICE, Balance::max_value()), - ArithmeticError::Overflow, - ); - }); -} - -#[test] -fn withdraw_should_work() { - ExtBuilder::default() - .one_hundred_for_alice_n_bob() - .build() - .execute_with(|| { - assert_ok!(Tokens::withdraw(DOT, &ALICE, 50)); - assert_eq!(Tokens::free_balance(DOT, &ALICE), 50); - assert_eq!(Tokens::total_issuance(DOT), 150); - - assert_noop!(Tokens::withdraw(DOT, &ALICE, 60), Error::::BalanceTooLow); - }); -} - -#[test] -fn slash_should_work() { - ExtBuilder::default() - .one_hundred_for_alice_n_bob() - .build() - .execute_with(|| { - // slashed_amount < amount - assert_eq!(Tokens::slash(DOT, &ALICE, 50), 0); - assert_eq!(Tokens::free_balance(DOT, &ALICE), 50); - assert_eq!(Tokens::total_issuance(DOT), 150); - - // slashed_amount == amount - assert_eq!(Tokens::slash(DOT, &ALICE, 51), 1); - assert_eq!(Tokens::free_balance(DOT, &ALICE), 0); - assert_eq!(Tokens::total_issuance(DOT), 100); - }); -} - -#[test] -fn update_balance_should_work() { - ExtBuilder::default() - .one_hundred_for_alice_n_bob() - .build() - .execute_with(|| { - assert_ok!(Tokens::update_balance(DOT, &ALICE, 50)); - assert_eq!(Tokens::free_balance(DOT, &ALICE), 150); - assert_eq!(Tokens::total_issuance(DOT), 250); - - assert_ok!(Tokens::update_balance(DOT, &BOB, -50)); - assert_eq!(Tokens::free_balance(DOT, &BOB), 50); - assert_eq!(Tokens::total_issuance(DOT), 200); - - assert_noop!(Tokens::update_balance(DOT, &BOB, -60), Error::::BalanceTooLow); - }); -} - -#[test] -fn ensure_can_withdraw_should_work() { - ExtBuilder::default() - .one_hundred_for_alice_n_bob() - .build() - .execute_with(|| { - assert_noop!( - Tokens::ensure_can_withdraw(DOT, &ALICE, 101), - Error::::BalanceTooLow - ); - - assert_ok!(Tokens::ensure_can_withdraw(DOT, &ALICE, 1)); - assert_eq!(Tokens::free_balance(DOT, &ALICE), 100); + assert_eq!(Tokens::total_issuance(DOT), 102); }); } @@ -482,13 +1091,17 @@ fn transfer_all_trait_should_work() { }); } +// ************************************************* +// tests for CurrencyAdapter +// ************************************************* + #[test] fn currency_adapter_ensure_currency_adapter_should_work() { ExtBuilder::default() .one_hundred_for_treasury_account() .build() .execute_with(|| { - assert_eq!(Tokens::total_issuance(DOT), 102); + assert_eq!(Tokens::total_issuance(DOT), 104); assert_eq!(Tokens::total_balance(DOT, &Treasury::account_id()), 2); assert_eq!(Tokens::total_balance(DOT, &TREASURY_ACCOUNT), 100); assert_eq!(Tokens::reserved_balance(DOT, &TREASURY_ACCOUNT), 0); @@ -503,7 +1116,7 @@ fn currency_adapter_ensure_currency_adapter_should_work() { ); assert_eq!( ::Currency::total_issuance(), - 102 + 104 ); assert_eq!( ::Currency::minimum_balance(), @@ -518,24 +1131,24 @@ fn currency_adapter_ensure_currency_adapter_should_work() { let imbalance = ::Currency::burn(10); assert_eq!( ::Currency::total_issuance(), - 92 + 94 ); drop(imbalance); assert_eq!( ::Currency::total_issuance(), - 102 + 104 ); // issue let imbalance = ::Currency::issue(20); assert_eq!( ::Currency::total_issuance(), - 122 + 124 ); drop(imbalance); assert_eq!( ::Currency::total_issuance(), - 102 + 104 ); // transfer @@ -565,7 +1178,7 @@ fn currency_adapter_ensure_currency_adapter_should_work() { // deposit assert_eq!( ::Currency::total_issuance(), - 102 + 104 ); let imbalance = TreasuryCurrencyAdapter::deposit_creating(&TREASURY_ACCOUNT, 11); assert_eq!( @@ -574,7 +1187,7 @@ fn currency_adapter_ensure_currency_adapter_should_work() { ); assert_eq!( ::Currency::total_issuance(), - 102 + 104 ); drop(imbalance); assert_eq!( @@ -583,7 +1196,7 @@ fn currency_adapter_ensure_currency_adapter_should_work() { ); assert_eq!( ::Currency::total_issuance(), - 113 + 115 ); // withdraw @@ -599,7 +1212,7 @@ fn currency_adapter_ensure_currency_adapter_should_work() { ); assert_eq!( ::Currency::total_issuance(), - 113 + 115 ); drop(imbalance); assert_eq!( @@ -608,39 +1221,11 @@ fn currency_adapter_ensure_currency_adapter_should_work() { ); assert_eq!( ::Currency::total_issuance(), - 103 + 105 ); }); } -#[test] -fn currency_adapter_withdraw_failed_when_allow_death_but_cannot_dec_provider() { - ExtBuilder::default() - .one_hundred_for_alice_n_bob() - .build() - .execute_with(|| { - assert_eq!(System::can_dec_provider(&ALICE), true); - assert_ok!(System::inc_consumers(&ALICE)); - assert_eq!(System::can_dec_provider(&ALICE), false); - assert!(TreasuryCurrencyAdapter::withdraw( - &ALICE, - 100, - WithdrawReasons::TRANSFER, - ExistenceRequirement::AllowDeath - ) - .is_err()); - assert_ok!(Tokens::deposit(BTC, &ALICE, 100)); - assert_eq!(System::can_dec_provider(&ALICE), true); - assert!(TreasuryCurrencyAdapter::withdraw( - &ALICE, - 100, - WithdrawReasons::TRANSFER, - ExistenceRequirement::AllowDeath - ) - .is_ok()); - }); -} - #[test] fn currency_adapter_burn_must_work() { ExtBuilder::default() @@ -712,7 +1297,7 @@ fn currency_adapter_slashing_balance_should_work() { assert!(TreasuryCurrencyAdapter::slash(&TREASURY_ACCOUNT, 69).1.is_zero()); assert_eq!(TreasuryCurrencyAdapter::free_balance(&TREASURY_ACCOUNT), 0); assert_eq!(TreasuryCurrencyAdapter::reserved_balance(&TREASURY_ACCOUNT), 42); - assert_eq!(TreasuryCurrencyAdapter::total_issuance(), 42); + assert_eq!(TreasuryCurrencyAdapter::total_issuance(), 44); }); } @@ -720,11 +1305,12 @@ fn currency_adapter_slashing_balance_should_work() { fn currency_adapter_slashing_incomplete_balance_should_work() { ExtBuilder::default().build().execute_with(|| { let _ = TreasuryCurrencyAdapter::deposit_creating(&TREASURY_ACCOUNT, 42); + assert_eq!(TreasuryCurrencyAdapter::total_issuance(), 44); assert_ok!(TreasuryCurrencyAdapter::reserve(&TREASURY_ACCOUNT, 21)); assert_eq!(TreasuryCurrencyAdapter::slash(&TREASURY_ACCOUNT, 69).1, 27); assert_eq!(TreasuryCurrencyAdapter::free_balance(&TREASURY_ACCOUNT), 0); assert_eq!(TreasuryCurrencyAdapter::reserved_balance(&TREASURY_ACCOUNT), 0); - assert_eq!(TreasuryCurrencyAdapter::total_issuance(), 0); + assert_eq!(TreasuryCurrencyAdapter::total_issuance(), 2); }); } @@ -905,20 +1491,15 @@ fn currency_adapter_deposit_creating_should_work() { .one_hundred_for_alice_n_bob() .build() .execute_with(|| { - assert_eq!(TreasuryCurrencyAdapter::total_issuance(), 200); - assert_eq!(TreasuryCurrencyAdapter::total_balance(&TREASURY_ACCOUNT), 0); - let _ = TreasuryCurrencyAdapter::deposit_creating(&TREASURY_ACCOUNT, 1); - assert_eq!(TreasuryCurrencyAdapter::total_issuance(), 200); + assert_eq!(TreasuryCurrencyAdapter::total_issuance(), 202); assert_eq!(TreasuryCurrencyAdapter::total_balance(&TREASURY_ACCOUNT), 0); - let _ = TreasuryCurrencyAdapter::deposit_creating(&TREASURY_ACCOUNT, 2); - assert_eq!(TreasuryCurrencyAdapter::total_issuance(), 202); + assert_eq!(TreasuryCurrencyAdapter::total_issuance(), 204); assert_eq!(TreasuryCurrencyAdapter::total_balance(&TREASURY_ACCOUNT), 2); - assert_eq!(TreasuryCurrencyAdapter::total_issuance(), 202); assert_eq!(TreasuryCurrencyAdapter::total_balance(&ALICE), 100); let _ = TreasuryCurrencyAdapter::deposit_creating(&ALICE, 1); - assert_eq!(TreasuryCurrencyAdapter::total_issuance(), 203); + assert_eq!(TreasuryCurrencyAdapter::total_issuance(), 205); assert_eq!(TreasuryCurrencyAdapter::total_balance(&ALICE), 101); }); } @@ -935,10 +1516,10 @@ fn currency_adapter_deposit_into_existing_should_work() { Error::::DeadAccount, ); - assert_eq!(TreasuryCurrencyAdapter::total_issuance(), 200); + assert_eq!(TreasuryCurrencyAdapter::total_issuance(), 202); assert_eq!(TreasuryCurrencyAdapter::total_balance(&ALICE), 100); assert_ok!(TreasuryCurrencyAdapter::deposit_into_existing(&ALICE, 10).map(drop)); - assert_eq!(TreasuryCurrencyAdapter::total_issuance(), 210); + assert_eq!(TreasuryCurrencyAdapter::total_issuance(), 212); assert_eq!(TreasuryCurrencyAdapter::total_balance(&ALICE), 110); }); } @@ -949,12 +1530,12 @@ fn currency_adapter_reward_should_work() { .one_hundred_for_treasury_account() .build() .execute_with(|| { - assert_eq!(TreasuryCurrencyAdapter::total_issuance(), 102); + assert_eq!(TreasuryCurrencyAdapter::total_issuance(), 104); assert_eq!(TreasuryCurrencyAdapter::total_balance(&TREASURY_ACCOUNT), 100); assert_eq!(TreasuryCurrencyAdapter::total_balance(&Treasury::account_id()), 2); assert_ok!(TreasuryCurrencyAdapter::deposit_into_existing(&TREASURY_ACCOUNT, 10).map(drop)); assert_eq!(TreasuryCurrencyAdapter::total_balance(&TREASURY_ACCOUNT), 110); - assert_eq!(TreasuryCurrencyAdapter::total_issuance(), 112); + assert_eq!(TreasuryCurrencyAdapter::total_issuance(), 114); }); } @@ -962,11 +1543,12 @@ fn currency_adapter_reward_should_work() { fn currency_adapter_slashing_reserved_balance_should_work() { ExtBuilder::default().build().execute_with(|| { let _ = TreasuryCurrencyAdapter::deposit_creating(&TREASURY_ACCOUNT, 111); + assert_eq!(TreasuryCurrencyAdapter::total_issuance(), 113); assert_ok!(TreasuryCurrencyAdapter::reserve(&TREASURY_ACCOUNT, 111)); assert_eq!(TreasuryCurrencyAdapter::slash_reserved(&TREASURY_ACCOUNT, 42).1, 0); assert_eq!(TreasuryCurrencyAdapter::reserved_balance(&TREASURY_ACCOUNT), 69); assert_eq!(TreasuryCurrencyAdapter::free_balance(&TREASURY_ACCOUNT), 0); - assert_eq!(TreasuryCurrencyAdapter::total_issuance(), 69); + assert_eq!(TreasuryCurrencyAdapter::total_issuance(), 71); }); } @@ -974,11 +1556,12 @@ fn currency_adapter_slashing_reserved_balance_should_work() { fn currency_adapter_slashing_incomplete_reserved_balance_should_work() { ExtBuilder::default().build().execute_with(|| { let _ = TreasuryCurrencyAdapter::deposit_creating(&TREASURY_ACCOUNT, 111); + assert_eq!(TreasuryCurrencyAdapter::total_issuance(), 113); assert_ok!(TreasuryCurrencyAdapter::reserve(&TREASURY_ACCOUNT, 42)); assert_eq!(TreasuryCurrencyAdapter::slash_reserved(&TREASURY_ACCOUNT, 69).1, 27); assert_eq!(TreasuryCurrencyAdapter::free_balance(&TREASURY_ACCOUNT), 69); assert_eq!(TreasuryCurrencyAdapter::reserved_balance(&TREASURY_ACCOUNT), 0); - assert_eq!(TreasuryCurrencyAdapter::total_issuance(), 69); + assert_eq!(TreasuryCurrencyAdapter::total_issuance(), 71); }); } @@ -1089,13 +1672,17 @@ fn exceeding_max_locks_should_fail() { }); } +// ************************************************* +// tests for fungibles traits +// ************************************************* + #[test] fn fungibles_inspect_trait_should_work() { ExtBuilder::default() .one_hundred_for_alice_n_bob() .build() .execute_with(|| { - assert_eq!(>::total_issuance(DOT), 200); + assert_eq!(>::total_issuance(DOT), 202); assert_eq!(>::minimum_balance(DOT), 2); assert_eq!(>::balance(DOT, &ALICE), 100); assert_eq!( @@ -1144,7 +1731,7 @@ fn fungibles_unbalanced_trait_should_work() { assert_ok!(>::set_balance(DOT, &ALICE, 10)); assert_eq!(>::balance(DOT, &ALICE), 10); - assert_eq!(>::total_issuance(DOT), 200); + assert_eq!(>::total_issuance(DOT), 202); >::set_total_issuance(DOT, 10); assert_eq!(>::total_issuance(DOT), 10); }); diff --git a/tokens/src/weights.rs b/tokens/src/weights.rs index 5b6ed9474..bde21c178 100644 --- a/tokens/src/weights.rs +++ b/tokens/src/weights.rs @@ -32,6 +32,9 @@ use sp_std::marker::PhantomData; pub trait WeightInfo { fn transfer() -> Weight; fn transfer_all() -> Weight; + fn transfer_keep_alive() -> Weight; + fn force_transfer() -> Weight; + fn set_balance() -> Weight; } /// Default weights. @@ -46,4 +49,19 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } + fn transfer_keep_alive() -> Weight { + (60_000_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(5 as Weight)) + .saturating_add(RocksDbWeight::get().writes(4 as Weight)) + } + fn force_transfer() -> Weight { + (60_000_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(5 as Weight)) + .saturating_add(RocksDbWeight::get().writes(4 as Weight)) + } + fn set_balance() -> Weight { + (60_000_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(5 as Weight)) + .saturating_add(RocksDbWeight::get().writes(4 as Weight)) + } }