Composite accounts (#4820)

* Basic account composition.

* Add try_mutate_exists

* De-duplicate

* Refactor away the UpdateBalanceOutcome

* Expunge final UpdateBalanceOutcome refs

* Refactor transfer

* Refactor reservable currency stuff.

* Test with the alternative setup.

* Fixes

* Test with both setups.

* Fixes

* Fix

* Fix macros

* Make indices opt-in

* Remove CreationFee, and make indices opt-in.

* Fix construct_runtime

* Fix last few bits

* Fix tests

* Update trait impls

* Don't hardcode the system event

* Make tests build and fix some stuff.

* Pointlessly bump runtime version

* Fix benchmark

* Another fix

* Whitespace

* Make indices module economically safe

* Migrations for indices.

* Fix

* Whilespace

* Trim defunct migrations

* Remove unused storage item

* More contains_key fixes

* Docs.

* Bump runtime

* Remove unneeded code

* Fix test

* Fix test

* Update frame/balances/src/lib.rs

Co-Authored-By: Shawn Tabrizi <shawntabrizi@gmail.com>

* Fix ED logic

* Repatriate reserved logic

* Typo

* Fix typo

* Update frame/system/src/lib.rs

Co-Authored-By: Shawn Tabrizi <shawntabrizi@gmail.com>

* Update frame/system/src/lib.rs

Co-Authored-By: Shawn Tabrizi <shawntabrizi@gmail.com>

* Last few fixes

* Another fix

* Build fix

Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>
Co-authored-by: Jaco Greeff <jacogr@gmail.com>
Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>
This commit is contained in:
Gavin Wood
2020-02-14 00:47:51 +00:00
committed by GitHub
parent d3fa8c91af
commit 5b7512e2e4
79 changed files with 2459 additions and 2100 deletions
+7 -8
View File
@@ -50,7 +50,7 @@ impl<T: Trait> BenchmarkingSetup<T, crate::Call<T>, RawOrigin<T::AccountId>> for
{
// Constants
let ed = T::ExistentialDeposit::get();
// Select an account
let u = components.iter().find(|&c| c.0 == BenchmarkParameter::U).unwrap().1;
let user = account::<T>("user", u);
@@ -58,8 +58,7 @@ impl<T: Trait> BenchmarkingSetup<T, crate::Call<T>, RawOrigin<T::AccountId>> for
// Give some multiple of the existential deposit + creation fee + transfer fee
let e = components.iter().find(|&c| c.0 == BenchmarkParameter::E).unwrap().1;
let mut balance = ed.saturating_mul(e.into());
balance += T::CreationFee::get();
let balance = ed.saturating_mul(e.into());
let _ = <Balances<T> as Currency<_>>::make_free_balance_be(&user, balance);
// Transfer `e - 1` existential deposits + 1 unit, which guarantees to create one account, and reap this user.
@@ -90,7 +89,7 @@ impl<T: Trait> BenchmarkingSetup<T, crate::Call<T>, RawOrigin<T::AccountId>> for
{
// Constants
let ed = T::ExistentialDeposit::get();
// Select a sender
let u = components.iter().find(|&c| c.0 == BenchmarkParameter::U).unwrap().1;
let user = account::<T>("user", u);
@@ -135,7 +134,7 @@ impl<T: Trait> BenchmarkingSetup<T, crate::Call<T>, RawOrigin<T::AccountId>> for
{
// Constants
let ed = T::ExistentialDeposit::get();
// Select a sender
let u = components.iter().find(|&c| c.0 == BenchmarkParameter::U).unwrap().1;
let user = account::<T>("user", u);
@@ -176,7 +175,7 @@ impl<T: Trait> BenchmarkingSetup<T, crate::Call<T>, RawOrigin<T::AccountId>> for
{
// Constants
let ed = T::ExistentialDeposit::get();
// Select a sender
let u = components.iter().find(|&c| c.0 == BenchmarkParameter::U).unwrap().1;
let user = account::<T>("user", u);
@@ -208,7 +207,7 @@ impl<T: Trait> BenchmarkingSetup<T, crate::Call<T>, RawOrigin<T::AccountId>> for
{
// Constants
let ed = T::ExistentialDeposit::get();
// Select a sender
let u = components.iter().find(|&c| c.0 == BenchmarkParameter::U).unwrap().1;
let user = account::<T>("user", u);
@@ -275,7 +274,7 @@ impl<T: Trait> Benchmarking<BenchmarkResults> for Module<T> {
sp_io::benchmarking::commit_db();
sp_io::benchmarking::wipe_db();
let components = <SelectedBenchmark as BenchmarkingSetup<T, crate::Call<T>, RawOrigin<T::AccountId>>>::components(&selected_benchmark);
let components = <SelectedBenchmark as BenchmarkingSetup<T, crate::Call<T>, RawOrigin<T::AccountId>>>::components(&selected_benchmark);
// results go here
let mut results: Vec<BenchmarkResults> = Vec::new();
// Select the component we will be benchmarking. Each component will be benchmarked.
+271 -303
View File
@@ -149,21 +149,24 @@
#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(test)]
mod mock;
mod tests_local;
#[cfg(test)]
mod tests_composite;
#[cfg(test)]
#[macro_use]
mod tests;
mod migration;
mod benchmarking;
use sp_std::prelude::*;
use sp_std::{cmp, result, mem, fmt::Debug, ops::BitOr};
use sp_std::{cmp, result, mem, fmt::Debug, ops::BitOr, convert::Infallible};
use codec::{Codec, Encode, Decode};
use frame_support::{
StorageValue, Parameter, decl_event, decl_storage, decl_module, decl_error, ensure,
weights::SimpleDispatchInfo, traits::{
UpdateBalanceOutcome, Currency, OnReapAccount, OnUnbalanced, TryDrop,
weights::SimpleDispatchInfo, traits::{
Currency, OnReapAccount, OnUnbalanced, TryDrop, StoredMap,
WithdrawReason, WithdrawReasons, LockIdentifier, LockableCurrency, ExistenceRequirement,
Imbalance, SignedImbalance, ReservableCurrency, Get, ExistenceRequirement::KeepAlive
Imbalance, SignedImbalance, ReservableCurrency, Get, ExistenceRequirement::KeepAlive,
ExistenceRequirement::AllowDeath, IsDeadAccount, BalanceStatus as Status
}
};
use sp_runtime::{
@@ -173,8 +176,10 @@ use sp_runtime::{
MaybeSerializeDeserialize, Saturating, Bounded,
},
};
use frame_system::{self as system, IsDeadAccount, OnNewAccount, ensure_signed, ensure_root};
use migration::{get_storage_value, put_storage_value, StorageIterator};
use frame_system::{self as system, ensure_signed, ensure_root};
use frame_support::storage::migration::{
get_storage_value, take_storage_value, put_storage_value, StorageIterator
};
pub use self::imbalances::{PositiveImbalance, NegativeImbalance};
@@ -183,21 +188,11 @@ pub trait Subtrait<I: Instance = DefaultInstance>: frame_system::Trait {
type Balance: Parameter + Member + AtLeast32Bit + Codec + Default + Copy +
MaybeSerializeDeserialize + Debug;
/// A function that is invoked when the free-balance and the reserved-balance has fallen below
/// the existential deposit and both have been reduced to zero.
///
/// All resources should be cleaned up all resources associated with the given account.
type OnReapAccount: OnReapAccount<Self::AccountId>;
/// Handler for when a new account is created.
type OnNewAccount: OnNewAccount<Self::AccountId>;
/// The minimum amount required to keep an account open.
type ExistentialDeposit: Get<Self::Balance>;
/// The fee required to create an account. If you're doing significant stuff with `OnNewAccount`
/// then you'll probably want to make this non-zero.
type CreationFee: Get<Self::Balance>;
/// The means of storing the balances of an account.
type AccountStore: StoredMap<Self::AccountId, AccountData<Self::Balance>>;
}
pub trait Trait<I: Instance = DefaultInstance>: frame_system::Trait {
@@ -205,19 +200,6 @@ pub trait Trait<I: Instance = DefaultInstance>: frame_system::Trait {
type Balance: Parameter + Member + AtLeast32Bit + Codec + Default + Copy +
MaybeSerializeDeserialize + Debug;
/// A function that is invoked when the free-balance and the reserved-balance has fallen below
/// the existential deposit and both have been reduced to zero.
///
/// All resources should be cleaned up all resources associated with the given account.
type OnReapAccount: OnReapAccount<Self::AccountId>;
/// Handler for when a new account is created.
type OnNewAccount: OnNewAccount<Self::AccountId>;
/// Handler for the unbalanced reduction when taking fees associated with balance
/// transfer (which may also include account creation).
type TransferPayment: OnUnbalanced<NegativeImbalance<Self, I>>;
/// Handler for the unbalanced reduction when removing a dust account.
type DustRemoval: OnUnbalanced<NegativeImbalance<Self, I>>;
@@ -227,16 +209,14 @@ pub trait Trait<I: Instance = DefaultInstance>: frame_system::Trait {
/// The minimum amount required to keep an account open.
type ExistentialDeposit: Get<Self::Balance>;
/// The fee required to create an account.
type CreationFee: Get<Self::Balance>;
/// The means of storing the balances of an account.
type AccountStore: StoredMap<Self::AccountId, AccountData<Self::Balance>>;
}
impl<T: Trait<I>, I: Instance> Subtrait<I> for T {
type Balance = T::Balance;
type OnReapAccount = T::OnReapAccount;
type OnNewAccount = T::OnNewAccount;
type ExistentialDeposit = T::ExistentialDeposit;
type CreationFee = T::CreationFee;
type AccountStore = T::AccountStore;
}
decl_event!(
@@ -244,12 +224,13 @@ decl_event!(
<T as frame_system::Trait>::AccountId,
<T as Trait<I>>::Balance
{
/// A new account was created.
NewAccount(AccountId, Balance),
/// An account was reaped.
ReapedAccount(AccountId, Balance),
/// Transfer succeeded (from, to, value, fees).
Transfer(AccountId, AccountId, Balance, Balance),
/// An account was created with some free balance.
Endowed(AccountId, Balance),
/// An account was removed whose balance was non-zero but below ExistentialDeposit,
/// resulting in an outright loss.
DustLost(AccountId, Balance),
/// Transfer succeeded (from, to, value).
Transfer(AccountId, AccountId, Balance),
/// A balance was set by root (who, free, reserved).
BalanceSet(AccountId, Balance, Balance),
/// Some amount was deposited (e.g. for transaction fees).
@@ -376,11 +357,9 @@ decl_storage! {
///
/// NOTE: THIS MAY NEVER BE IN EXISTENCE AND YET HAVE A `total().is_zero()`. If the total
/// is ever zero, then the entry *MUST* be removed.
pub Account get(fn account)
build(|config: &GenesisConfig<T, I>| config.balances.iter()
.map(|&(ref who, free)| (who.clone(), AccountData { free, .. Default::default() }))
.collect::<Vec<_>>()
): map hasher(blake2_256) T::AccountId => AccountData<T::Balance>;
///
/// NOTE: This is only used in the case that this module is used to store balances.
pub Account: map hasher(blake2_256) T::AccountId => AccountData<T::Balance>;
/// Any liquidity locks on some account balances.
/// NOTE: Should only be accessed when setting, changing and freeing a lock.
@@ -405,6 +384,9 @@ decl_storage! {
"the balance of any account should always be more than existential deposit.",
)
}
for &(ref who, free) in config.balances.iter() {
T::AccountStore::insert(who, AccountData { free, .. Default::default() });
}
});
}
}
@@ -416,9 +398,6 @@ decl_module! {
/// The minimum amount required to keep an account open.
const ExistentialDeposit: T::Balance = T::ExistentialDeposit::get();
/// The fee required to create an account.
const CreationFee: T::Balance = T::CreationFee::get();
fn deposit_event() = default;
/// Transfer some liquid free balance to another account.
@@ -484,24 +463,25 @@ decl_module! {
let new_free = if wipeout { Zero::zero() } else { new_free };
let new_reserved = if wipeout { Zero::zero() } else { new_reserved };
let old_account = Account::<T, I>::get(&who);
let (free, reserved) = Self::mutate_account(&who, |account| {
if new_free > account.free {
mem::drop(PositiveImbalance::<T, I>::new(new_free - account.free));
} else if new_free < account.free {
mem::drop(NegativeImbalance::<T, I>::new(account.free - new_free));
}
if new_free > old_account.free {
mem::drop(PositiveImbalance::<T, I>::new(new_free - old_account.free));
} else if new_free < old_account.free {
mem::drop(NegativeImbalance::<T, I>::new(old_account.free - new_free));
}
if new_reserved > account.reserved {
mem::drop(PositiveImbalance::<T, I>::new(new_reserved - account.reserved));
} else if new_reserved < account.reserved {
mem::drop(NegativeImbalance::<T, I>::new(account.reserved - new_reserved));
}
if new_reserved > old_account.reserved {
mem::drop(PositiveImbalance::<T, I>::new(new_reserved - old_account.reserved));
} else if new_reserved < old_account.reserved {
mem::drop(NegativeImbalance::<T, I>::new(old_account.reserved - new_reserved));
}
account.free = new_free;
account.reserved = new_reserved;
let account = AccountData { free: new_free, reserved: new_reserved, ..old_account };
Self::set_account(&who, &account, &old_account);
Self::deposit_event(RawEvent::BalanceSet(who, account.free, account.reserved));
(account.free, account.reserved)
});
Self::deposit_event(RawEvent::BalanceSet(who, free, reserved));
}
/// Exactly as `transfer`, except the origin must be root and the source account may be
@@ -626,87 +606,110 @@ impl<T: Trait<I>, I: Instance> Module<T, I> {
put_storage_value(b"Balances", b"Locks", &hash, locks);
put_storage_value(b"Balances", b"Account", &hash, account);
}
for (hash, balances) in StorageIterator::<AccountData<T::Balance>>::new(b"Balances", b"Account").drain() {
let nonce = take_storage_value::<T::Index>(b"System", b"AccountNonce", &hash).unwrap_or_default();
put_storage_value(b"System", b"Account", &hash, (nonce, balances));
}
}
/// Get the free balance of an account.
pub fn free_balance(who: impl sp_std::borrow::Borrow<T::AccountId>) -> T::Balance {
Account::<T, I>::get(who.borrow()).free
Self::account(who.borrow()).free
}
/// Get the balance of an account that can be used for transfers, reservations, or any other
/// non-locking, non-transaction-fee activity. Will be at most `free_balance`.
pub fn usable_balance(who: impl sp_std::borrow::Borrow<T::AccountId>) -> T::Balance {
Account::<T, I>::get(who.borrow()).usable(Reasons::Misc)
Self::account(who.borrow()).usable(Reasons::Misc)
}
/// Get the balance of an account that can be used for paying transaction fees (not tipping,
/// or any other kind of fees, though). Will be at most `free_balance`.
pub fn usable_balance_for_fees(who: impl sp_std::borrow::Borrow<T::AccountId>) -> T::Balance {
Account::<T, I>::get(who.borrow()).usable(Reasons::Fee)
Self::account(who.borrow()).usable(Reasons::Fee)
}
/// Get the reserved balance of an account.
pub fn reserved_balance(who: impl sp_std::borrow::Borrow<T::AccountId>) -> T::Balance {
Account::<T, I>::get(who.borrow()).reserved
Self::account(who.borrow()).reserved
}
/// Set both the free and reserved balance of an account to some new value. Will enforce
/// Get both the free and reserved balances of an account.
fn account(who: &T::AccountId) -> AccountData<T::Balance> {
T::AccountStore::get(&who)
}
/// Places the `free` and `reserved` parts of `new` into `account`. Also does any steps needed
/// after mutating an account. This includes DustRemoval unbalancing, in the case than the `new`
/// account's total balance is non-zero but below ED.
///
/// Returns the final free balance, iff the account was previously of total balance zero, known
/// as its "endowment".
fn post_mutation(
who: &T::AccountId,
new: AccountData<T::Balance>,
) -> Option<AccountData<T::Balance>> {
let total = new.total();
if total < T::ExistentialDeposit::get() {
if !total.is_zero() {
T::DustRemoval::on_unbalanced(NegativeImbalance::new(total));
Self::deposit_event(RawEvent::DustLost(who.clone(), total));
}
None
} else {
Some(new)
}
}
/// Mutate an account to some new value, or delete it entirely with `None`. Will enforce
/// `ExistentialDeposit` law, annulling the account as needed.
///
/// Will return `AccountKilled` if either reserved or free are too low.
///
/// NOTE: This assumes that `account` is the same as `Self::account(who)` except for altered
/// values of `free` and `balance`.
///
/// NOTE: Doesn't do any preparatory work for creating a new account, so should only be used
/// when it is known that the account already exists.
///
/// NOTE: LOW-LEVEL: This will not attempt to maintain total issuance. It is expected that
/// the caller will do this.
fn set_account(
fn mutate_account<R>(
who: &T::AccountId,
account: &AccountData<T::Balance>,
old: &AccountData<T::Balance>,
) -> UpdateBalanceOutcome {
let total = account.free + account.reserved;
if total < T::ExistentialDeposit::get() {
T::DustRemoval::on_unbalanced(NegativeImbalance::new(total));
if !old.total().is_zero() {
Self::reap_account(who, total);
UpdateBalanceOutcome::AccountKilled
} else {
UpdateBalanceOutcome::StillDead
}
} else {
if old.total().is_zero() {
Self::about_to_create_account(who, account.free);
}
Account::<T, I>::insert(who, account);
UpdateBalanceOutcome::Updated
}
f: impl FnOnce(&mut AccountData<T::Balance>) -> R
) -> R {
Self::try_mutate_account(who, |a| -> Result<R, Infallible> { Ok(f(a)) })
.expect("Error is infallible; qed")
}
/// Register a new account (with existential balance).
/// Mutate an account to some new value, or delete it entirely with `None`. Will enforce
/// `ExistentialDeposit` law, annulling the account as needed. This will do nothing if the
/// result of `f` is an `Err`.
///
/// This just calls appropriate hooks. It doesn't (necessarily) make any state changes.
fn about_to_create_account(who: &T::AccountId, balance: T::Balance) {
T::OnNewAccount::on_new_account(&who);
Self::deposit_event(RawEvent::NewAccount(who.clone(), balance.clone()));
}
/// Unregister an account.
/// NOTE: Doesn't do any preparatory work for creating a new account, so should only be used
/// when it is known that the account already exists.
///
/// This just removes the nonce and leaves an event.
fn reap_account(who: &T::AccountId, dust: T::Balance) {
Locks::<T, I>::remove(who);
Account::<T, I>::remove(who);
T::OnReapAccount::on_reap_account(who);
Self::deposit_event(RawEvent::ReapedAccount(who.clone(), dust));
/// NOTE: LOW-LEVEL: This will not attempt to maintain total issuance. It is expected that
/// the caller will do this.
fn try_mutate_account<R, E>(
who: &T::AccountId,
f: impl FnOnce(&mut AccountData<T::Balance>) -> Result<R, E>
) -> Result<R, E> {
T::AccountStore::try_mutate_exists(who, |maybe_account| {
let mut account = maybe_account.take().unwrap_or_default();
let was_zero = account.total().is_zero();
f(&mut account).map(move |result| {
let maybe_endowed = if was_zero { Some(account.free) } else { None };
*maybe_account = Self::post_mutation(who, account);
(maybe_endowed, result)
})
}).map(|(maybe_endowed, result)| {
if let Some(endowed) = maybe_endowed {
Self::deposit_event(RawEvent::Endowed(who.clone(), endowed));
}
result
})
}
/// Update the account entry for `who`, given the locks.
fn update_locks(who: &T::AccountId, locks: &[BalanceLock<T::Balance>]) {
Account::<T, I>::mutate(who, |b| {
Self::mutate_account(who, |b| {
b.misc_frozen = Zero::zero();
b.fee_frozen = Zero::zero();
for l in locks.iter() {
@@ -722,6 +725,13 @@ impl<T: Trait<I>, I: Instance> Module<T, I> {
}
}
impl<T: Trait<I>, I: Instance> OnReapAccount<T::AccountId> for Module<T, I> {
fn on_reap_account(who: &T::AccountId) {
Locks::<T, I>::remove(who);
Account::<T, I>::remove(who);
}
}
// wrapping these imbalances in a private module is necessary to ensure absolute privacy
// of the inner member.
mod imbalances {
@@ -885,9 +895,8 @@ mod imbalances {
// its type declaration).
// This works as long as `increase_total_issuance_by` doesn't use the Imbalance
// types (basically for charging fees).
// This should eventually be refactored so that the three type items that do
// depend on the Imbalance type (TransferPayment, DustRemoval)
// are placed in their own SRML module.
// This should eventually be refactored so that the type item that
// depends on the Imbalance type (DustRemoval) is placed in its own SRML module.
struct ElevatedTrait<T: Subtrait<I>, I: Instance>(T, I);
impl<T: Subtrait<I>, I: Instance> Clone for ElevatedTrait<T, I> {
fn clone(&self) -> Self { unimplemented!() }
@@ -913,16 +922,16 @@ impl<T: Subtrait<I>, I: Instance> frame_system::Trait for ElevatedTrait<T, I> {
type AvailableBlockRatio = T::AvailableBlockRatio;
type Version = T::Version;
type ModuleToIndex = T::ModuleToIndex;
type OnNewAccount = T::OnNewAccount;
type OnReapAccount = T::OnReapAccount;
type AccountData = T::AccountData;
}
impl<T: Subtrait<I>, I: Instance> Trait<I> for ElevatedTrait<T, I> {
type Balance = T::Balance;
type OnReapAccount = T::OnReapAccount;
type OnNewAccount = T::OnNewAccount;
type Event = ();
type TransferPayment = ();
type DustRemoval = ();
type ExistentialDeposit = T::ExistentialDeposit;
type CreationFee = T::CreationFee;
type AccountStore = T::AccountStore;
}
impl<T: Trait<I>, I: Instance> Currency<T::AccountId> for Module<T, I> where
@@ -950,10 +959,6 @@ impl<T: Trait<I>, I: Instance> Currency<T::AccountId> for Module<T, I> where
T::ExistentialDeposit::get()
}
fn free_balance(who: &T::AccountId) -> Self::Balance {
Account::<T, I>::get(who).free
}
// Burn funds from the total issuance, returning a positive imbalance for the amount burned.
// Is a no-op if amount to be burned is zero.
fn burn(mut amount: Self::Balance) -> Self::PositiveImbalance {
@@ -981,6 +986,10 @@ impl<T: Trait<I>, I: Instance> Currency<T::AccountId> for Module<T, I> where
NegativeImbalance::new(amount)
}
fn free_balance(who: &T::AccountId) -> Self::Balance {
Self::account(who).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.
@@ -996,7 +1005,7 @@ impl<T: Trait<I>, I: Instance> Currency<T::AccountId> for Module<T, I> where
new_balance: T::Balance,
) -> DispatchResult {
if amount.is_zero() { return Ok(()) }
let min_balance = Account::<T, I>::get(who).frozen(reasons.into());
let min_balance = Self::account(who).frozen(reasons.into());
ensure!(new_balance >= min_balance, Error::<T, I>::LiquidityRestrictions);
Ok(())
}
@@ -1011,80 +1020,38 @@ impl<T: Trait<I>, I: Instance> Currency<T::AccountId> for Module<T, I> where
) -> DispatchResult {
if value.is_zero() || transactor == dest { return Ok(()) }
let old_from_account = Self::account(transactor);
let mut from_account = old_from_account.clone();
let old_to_account = Self::account(dest);
let mut to_account = old_to_account.clone();
Self::try_mutate_account(dest, |to_account| -> DispatchResult {
Self::try_mutate_account(transactor, |from_account| -> DispatchResult {
from_account.free = from_account.free.checked_sub(&value)
.ok_or(Error::<T, I>::InsufficientBalance)?;
let would_create = to_account.total().is_zero();
let fee = if would_create { T::CreationFee::get() } else { Zero::zero() };
let liability = value.checked_add(&fee).ok_or(Error::<T, I>::Overflow)?;
// NOTE: total stake being stored in the same type means that this could never overflow
// but better to be safe than sorry.
to_account.free = to_account.free.checked_add(&value).ok_or(Error::<T, I>::Overflow)?;
from_account.free = from_account.free.checked_sub(&liability)
.ok_or(Error::<T, I>::InsufficientBalance)?;
let ed = T::ExistentialDeposit::get();
ensure!(to_account.total() >= ed, Error::<T, I>::ExistentialDeposit);
// NOTE: total stake being stored in the same type means that this could never overflow
// but better to be safe than sorry.
to_account.free = to_account.free.checked_add(&value).ok_or(Error::<T, I>::Overflow)?;
Self::ensure_can_withdraw(
transactor,
value,
WithdrawReason::Transfer.into(),
from_account.free,
)?;
let ed = T::ExistentialDeposit::get();
ensure!(to_account.free >= ed, Error::<T, I>::ExistentialDeposit);
let allow_death = existence_requirement == ExistenceRequirement::AllowDeath;
ensure!(allow_death || from_account.free >= ed, Error::<T, I>::KeepAlive);
Self::ensure_can_withdraw(
transactor,
value,
WithdrawReason::Transfer.into(),
from_account.free,
)?;
let allow_death = existence_requirement == ExistenceRequirement::AllowDeath;
ensure!(allow_death || from_account.free >= ed, Error::<T, I>::KeepAlive);
Self::set_account(transactor, &from_account, &old_from_account);
// Take action on the set_account call.
// This will emit events that _resulted_ from the transfer.
Self::set_account(dest, &to_account, &old_to_account);
Ok(())
})
})?;
// Emit transfer event.
Self::deposit_event(RawEvent::Transfer(transactor.clone(), dest.clone(), value, fee));
T::TransferPayment::on_unbalanced(NegativeImbalance::new(fee));
Self::deposit_event(RawEvent::Transfer(transactor.clone(), dest.clone(), value));
Ok(())
}
// Withdraw some free balance from an account, respecting existence requirements.
// Is a no-op if value to be withdrawn is zero.
fn withdraw(
who: &T::AccountId,
value: Self::Balance,
reasons: WithdrawReasons,
liveness: ExistenceRequirement,
) -> result::Result<Self::NegativeImbalance, DispatchError> {
if value.is_zero() { return Ok(NegativeImbalance::zero()); }
let old_account = Self::account(who);
let mut account = old_account.clone();
if let Some(new_free_account) = account.free.checked_sub(&value) {
// if we need to keep the account alive...
if liveness == ExistenceRequirement::KeepAlive
// ...and it would be dead afterwards...
&& new_free_account < T::ExistentialDeposit::get()
// ...yet is was alive before
&& account.free >= T::ExistentialDeposit::get()
{
Err(Error::<T, I>::KeepAlive)?
}
Self::ensure_can_withdraw(who, value, reasons, new_free_account)?;
account.free = new_free_account;
Self::set_account(who, &account, &old_account);
Ok(NegativeImbalance::new(value))
} else {
Err(Error::<T, I>::InsufficientBalance)?
}
}
/// Slash a target account `who`, returning the negative imbalance created and any left over
/// amount that could not be slashed.
///
@@ -1100,22 +1067,19 @@ impl<T: Trait<I>, I: Instance> Currency<T::AccountId> for Module<T, I> where
) -> (Self::NegativeImbalance, Self::Balance) {
if value.is_zero() { return (NegativeImbalance::zero(), Zero::zero()) }
let old_account = Self::account(who);
let mut account = old_account.clone();
Self::mutate_account(who, |account| {
let free_slash = cmp::min(account.free, value);
account.free -= free_slash;
let free_slash = cmp::min(account.free, value);
account.free -= free_slash;
let remaining_slash = value - free_slash;
let result = if !remaining_slash.is_zero() {
let reserved_slash = cmp::min(account.reserved, remaining_slash);
account.reserved -= reserved_slash;
(NegativeImbalance::new(free_slash + reserved_slash), remaining_slash - reserved_slash)
} else {
(NegativeImbalance::new(value), Zero::zero())
};
Self::set_account(who, &account, &old_account);
result
let remaining_slash = value - free_slash;
if !remaining_slash.is_zero() {
let reserved_slash = cmp::min(account.reserved, remaining_slash);
account.reserved -= reserved_slash;
(NegativeImbalance::new(free_slash + reserved_slash), remaining_slash - reserved_slash)
} else {
(NegativeImbalance::new(value), Zero::zero())
}
})
}
/// Deposit some `value` into the free balance of an existing target account `who`.
@@ -1124,16 +1088,14 @@ impl<T: Trait<I>, I: Instance> Currency<T::AccountId> for Module<T, I> where
fn deposit_into_existing(
who: &T::AccountId,
value: Self::Balance
) -> result::Result<Self::PositiveImbalance, DispatchError> {
) -> Result<Self::PositiveImbalance, DispatchError> {
if value.is_zero() { return Ok(PositiveImbalance::zero()) }
let old_account = Self::account(who);
let mut account = old_account.clone();
ensure!(!account.total().is_zero(), Error::<T, I>::DeadAccount);
account.free = account.free.checked_add(&value).ok_or(Error::<T, I>::Overflow)?;
Self::set_account(who, &account, &old_account);
Ok(PositiveImbalance::new(value))
Self::try_mutate_account(who, |account| -> Result<Self::PositiveImbalance, DispatchError> {
ensure!(!account.total().is_zero(), Error::<T, I>::DeadAccount);
account.free = account.free.checked_add(&value).ok_or(Error::<T, I>::Overflow)?;
Ok(PositiveImbalance::new(value))
})
}
/// Deposit some `value` into the free balance of `who`, possibly creating a new account.
@@ -1148,34 +1110,58 @@ impl<T: Trait<I>, I: Instance> Currency<T::AccountId> for Module<T, I> where
) -> Self::PositiveImbalance {
if value.is_zero() { return Self::PositiveImbalance::zero() }
let old_account = Self::account(who);
let mut account = old_account.clone();
let ed = T::ExistentialDeposit::get();
Self::try_mutate_account(who, |account| -> Result<Self::PositiveImbalance, Self::PositiveImbalance> {
// bail if not yet created and this operation wouldn't be enough to create it.
let ed = T::ExistentialDeposit::get();
ensure!(value >= ed || !account.total().is_zero(), Self::PositiveImbalance::zero());
// bail if not yet created and this operation wouldn't be enough to create it.
if value < ed && account.total().is_zero() { return Self::PositiveImbalance::zero() }
// defensive only: overflow should never happen, however in case it does, then this
// operation is a no-op.
account.free = account.free.checked_add(&value).ok_or(Self::PositiveImbalance::zero())?;
// defensive only: overflow should never happen, however in case it does, then this
// operation is a no-op.
account.free = match account.free.checked_add(&value) {
Some(f) => f,
None => return Self::PositiveImbalance::zero(),
};
Ok(PositiveImbalance::new(value))
}).unwrap_or_else(|x| x)
}
Self::set_account(who, &account, &old_account);
/// Withdraw some free balance from an account, respecting existence requirements.
///
/// Is a no-op if value to be withdrawn is zero.
fn withdraw(
who: &T::AccountId,
value: Self::Balance,
reasons: WithdrawReasons,
liveness: ExistenceRequirement,
) -> result::Result<Self::NegativeImbalance, DispatchError> {
if value.is_zero() { return Ok(NegativeImbalance::zero()); }
PositiveImbalance::new(value)
Self::try_mutate_account(who, |account|
-> Result<Self::NegativeImbalance, DispatchError>
{
let new_free_account = account.free.checked_sub(&value)
.ok_or(Error::<T, I>::InsufficientBalance)?;
// bail if we need to keep the account alive and this would kill it.
let ed = T::ExistentialDeposit::get();
let would_be_dead = new_free_account + account.reserved < ed;
let would_kill = would_be_dead && account.free + account.reserved >= ed;
ensure!(liveness == AllowDeath || !would_kill, Error::<T, I>::KeepAlive);
Self::ensure_can_withdraw(who, value, reasons, new_free_account)?;
account.free = new_free_account;
Ok(NegativeImbalance::new(value))
})
}
/// Force the new free balance of a target account `who` to some new value `balance`.
fn make_free_balance_be(who: &T::AccountId, value: Self::Balance) -> (
SignedImbalance<Self::Balance, Self::PositiveImbalance>,
UpdateBalanceOutcome
) {
let old_account = Self::account(who);
let mut account = old_account.clone();
if value < T::ExistentialDeposit::get() && account.free.is_zero() {
fn make_free_balance_be(who: &T::AccountId, value: Self::Balance)
-> SignedImbalance<Self::Balance, Self::PositiveImbalance>
{
Self::try_mutate_account(who, |account|
-> Result<SignedImbalance<Self::Balance, Self::PositiveImbalance>, ()>
{
let ed = T::ExistentialDeposit::get();
// If we're attempting to set an existing account to less than ED, then
// bypass the entire operation. It's a no-op if you follow it through, but
// since this is an instance where we might account for a negative imbalance
@@ -1183,24 +1169,16 @@ impl<T: Trait<I>, I: Instance> Currency<T::AccountId> for Module<T, I> where
// equal and opposite cause (returned as an Imbalance), then in the
// instance that there's no other accounts on the system at all, we might
// underflow the issuance and our arithmetic will be off.
return (
SignedImbalance::Positive(Self::PositiveImbalance::zero()),
UpdateBalanceOutcome::AccountKilled,
)
}
let imbalance = if account.free <= value {
SignedImbalance::Positive(PositiveImbalance::new(value - account.free))
} else {
SignedImbalance::Negative(NegativeImbalance::new(account.free - value))
};
account.free = value;
ensure!(value + account.reserved >= ed || !account.total().is_zero(), ());
// If the balance is too low, then the account is reaped.
// Free balance can never be less than ED. If that happens, it gets reduced to zero
// and the account information relevant to this subsystem is deleted (i.e. the
// account is reaped).
let outcome = Self::set_account(who, &account, &old_account);
(imbalance, outcome)
let imbalance = if account.free <= value {
SignedImbalance::Positive(PositiveImbalance::new(value - account.free))
} else {
SignedImbalance::Negative(NegativeImbalance::new(account.free - value))
};
account.free = value;
Ok(imbalance)
}).unwrap_or(SignedImbalance::Positive(Self::PositiveImbalance::zero()))
}
}
@@ -1226,18 +1204,14 @@ impl<T: Trait<I>, I: Instance> ReservableCurrency<T::AccountId> for Module<T, I>
/// Move `value` from the free balance from `who` to their reserved balance.
///
/// Is a no-op if value to be reserved is zero.
fn reserve(who: &T::AccountId, value: Self::Balance) -> result::Result<(), DispatchError> {
fn reserve(who: &T::AccountId, value: Self::Balance) -> DispatchResult {
if value.is_zero() { return Ok(()) }
let old_account = Self::account(who);
let mut account = old_account.clone();
account.free = account.free.checked_sub(&value).ok_or(Error::<T, I>::InsufficientBalance)?;
account.reserved = account.reserved.checked_add(&value).ok_or(Error::<T, I>::Overflow)?;
Self::ensure_can_withdraw(who, value, WithdrawReason::Reserve.into(), account.free)?;
Self::set_account(who, &account, &old_account);
Ok(())
Self::try_mutate_account(who, |account| -> DispatchResult {
account.free = account.free.checked_sub(&value).ok_or(Error::<T, I>::InsufficientBalance)?;
account.reserved = account.reserved.checked_add(&value).ok_or(Error::<T, I>::Overflow)?;
Self::ensure_can_withdraw(who, value, WithdrawReason::Reserve.into(), account.free)
})
}
/// Unreserve some funds, returning any amount that was unable to be unreserved.
@@ -1246,18 +1220,14 @@ impl<T: Trait<I>, I: Instance> ReservableCurrency<T::AccountId> for Module<T, I>
fn unreserve(who: &T::AccountId, value: Self::Balance) -> Self::Balance {
if value.is_zero() { return Zero::zero() }
let old_account = Self::account(who);
let mut account = old_account.clone();
let actual = cmp::min(account.reserved, value);
account.reserved -= actual;
// defensive only: this can never fail since total issuance which is at least free+reserved
// fits into the same datatype.
account.free = account.free.saturating_add(actual);
Self::set_account(who, &account, &old_account);
value - actual
Self::mutate_account(who, |account| {
let actual = cmp::min(account.reserved, value);
account.reserved -= actual;
// defensive only: this can never fail since total issuance which is at least free+reserved
// fits into the same datatype.
account.free = account.free.saturating_add(actual);
value - actual
})
}
/// Slash from reserved balance, returning the negative imbalance created,
@@ -1270,47 +1240,46 @@ impl<T: Trait<I>, I: Instance> ReservableCurrency<T::AccountId> for Module<T, I>
) -> (Self::NegativeImbalance, Self::Balance) {
if value.is_zero() { return (NegativeImbalance::zero(), Zero::zero()) }
let old_account = Self::account(who);
let mut account = old_account.clone();
// underflow should never happen, but it if does, there's nothing to be done here.
let actual = cmp::min(account.reserved, value);
account.reserved -= actual;
Self::set_account(who, &account, &old_account);
(NegativeImbalance::new(actual), value - actual)
Self::mutate_account(who, |account| {
// underflow should never happen, but it if does, there's nothing to be done here.
let actual = cmp::min(account.reserved, value);
account.reserved -= actual;
(NegativeImbalance::new(actual), value - actual)
})
}
/// Move the reserved balance of one account into the free balance of another.
/// Move the reserved balance of one account into the balance of another, according to `status`.
///
/// Is a no-op if the value to be moved is zero.
/// Is a no-op if:
/// - the value to be moved is zero; or
/// - the `slashed` id equal to `beneficiary` and the `status` is `Reserved`.
fn repatriate_reserved(
slashed: &T::AccountId,
beneficiary: &T::AccountId,
value: Self::Balance,
) -> result::Result<Self::Balance, DispatchError> {
if value.is_zero() { return Ok (Zero::zero()) }
status: Status,
) -> Result<Self::Balance, DispatchError> {
if value.is_zero() { return Ok(Zero::zero()) }
if slashed == beneficiary {
return Ok(Self::unreserve(slashed, value));
return match status {
Status::Free => Ok(Self::unreserve(slashed, value)),
Status::Reserved => Ok(value.saturating_sub(Self::reserved_balance(slashed))),
};
}
let old_to_account = Self::account(beneficiary);
let mut to_account = old_to_account.clone();
ensure!(!to_account.total().is_zero(), Error::<T, I>::DeadAccount);
let old_from_account = Self::account(slashed);
let mut from_account = old_from_account.clone();
let actual = cmp::min(from_account.reserved, value);
to_account.free = to_account.free.checked_add(&actual).ok_or(Error::<T, I>::Overflow)?;
from_account.reserved -= actual;
Self::set_account(slashed, &from_account, &old_from_account);
Self::set_account(beneficiary, &to_account, &old_to_account);
Ok(value - actual)
Self::try_mutate_account(beneficiary, |to_account| -> Result<Self::Balance, DispatchError> {
ensure!(!to_account.total().is_zero(), Error::<T, I>::DeadAccount);
Self::try_mutate_account(slashed, |from_account| -> Result<Self::Balance, DispatchError> {
let actual = cmp::min(from_account.reserved, value);
match status {
Status::Free => to_account.free = to_account.free.checked_add(&actual).ok_or(Error::<T, I>::Overflow)?,
Status::Reserved => to_account.reserved = to_account.reserved.checked_add(&actual).ok_or(Error::<T, I>::Overflow)?,
}
from_account.reserved -= actual;
Ok(value - actual)
})
})
}
}
@@ -1380,12 +1349,11 @@ where
}
}
impl<T: Trait<I>, I: Instance> IsDeadAccount<T::AccountId> for Module<T, I>
where
impl<T: Trait<I>, I: Instance> IsDeadAccount<T::AccountId> for Module<T, I> where
T::Balance: MaybeSerializeDeserialize + Debug
{
fn is_dead_account(who: &T::AccountId) -> bool {
// this should always be exactly equivalent to `Self::account(who).total().is_zero()`
!Account::<T, I>::contains_key(who)
!T::AccountStore::is_explicit(who)
}
}
-84
View File
@@ -1,84 +0,0 @@
// Copyright 2017-2020 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Substrate is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
//! Some utilities for helping access storage with arbitrary key types.
use sp_std::prelude::*;
use codec::{Encode, Decode};
use frame_support::{StorageHasher, Twox128};
pub struct StorageIterator<T> {
prefix: [u8; 32],
previous_key: Vec<u8>,
drain: bool,
_phantom: ::sp_std::marker::PhantomData<T>,
}
impl<T> StorageIterator<T> {
pub fn new(module: &[u8], item: &[u8]) -> Self {
let mut prefix = [0u8; 32];
prefix[0..16].copy_from_slice(&Twox128::hash(module));
prefix[16..32].copy_from_slice(&Twox128::hash(item));
Self { prefix, previous_key: prefix[..].to_vec(), drain: false, _phantom: Default::default() }
}
pub fn drain(mut self) -> Self {
self.drain = true;
self
}
}
impl<T: Decode + Sized> Iterator for StorageIterator<T> {
type Item = (Vec<u8>, T);
fn next(&mut self) -> Option<(Vec<u8>, T)> {
loop {
let maybe_next = sp_io::storage::next_key(&self.previous_key)
.filter(|n| n.starts_with(&self.prefix));
break match maybe_next {
Some(next) => {
self.previous_key = next.clone();
let maybe_value = frame_support::storage::unhashed::get::<T>(&next);
match maybe_value {
Some(value) => {
if self.drain {
frame_support::storage::unhashed::kill(&next);
}
Some((self.previous_key[32..].to_vec(), value))
}
None => continue,
}
}
None => None,
}
}
}
}
pub fn get_storage_value<T: Decode + Sized>(module: &[u8], item: &[u8], hash: &[u8]) -> Option<T> {
let mut key = vec![0u8; 32 + hash.len()];
key[0..16].copy_from_slice(&Twox128::hash(module));
key[16..32].copy_from_slice(&Twox128::hash(item));
key[32..].copy_from_slice(hash);
frame_support::storage::unhashed::get::<T>(&key)
}
pub fn put_storage_value<T: Encode>(module: &[u8], item: &[u8], hash: &[u8], value: T) {
let mut key = vec![0u8; 32 + hash.len()];
key[0..16].copy_from_slice(&Twox128::hash(module));
key[16..32].copy_from_slice(&Twox128::hash(item));
key[32..].copy_from_slice(hash);
frame_support::storage::unhashed::put(&key, &value);
}
File diff suppressed because it is too large Load Diff
@@ -23,7 +23,7 @@ use frame_support::{impl_outer_origin, parameter_types};
use frame_support::traits::Get;
use frame_support::weights::{Weight, DispatchInfo};
use std::cell::RefCell;
use crate::{GenesisConfig, Module, Trait};
use crate::{GenesisConfig, Module, Trait, decl_tests};
use frame_system as system;
impl_outer_origin!{
@@ -31,8 +31,7 @@ impl_outer_origin!{
}
thread_local! {
pub(crate) static EXISTENTIAL_DEPOSIT: RefCell<u64> = RefCell::new(0);
static CREATION_FEE: RefCell<u64> = RefCell::new(0);
static EXISTENTIAL_DEPOSIT: RefCell<u64> = RefCell::new(0);
}
pub struct ExistentialDeposit;
@@ -40,11 +39,6 @@ impl Get<u64> for ExistentialDeposit {
fn get() -> u64 { EXISTENTIAL_DEPOSIT.with(|v| *v.borrow()) }
}
pub struct CreationFee;
impl Get<u64> for CreationFee {
fn get() -> u64 { CREATION_FEE.with(|v| *v.borrow()) }
}
// Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted.
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Test;
@@ -71,6 +65,9 @@ impl frame_system::Trait for Test {
type AvailableBlockRatio = AvailableBlockRatio;
type Version = ();
type ModuleToIndex = ();
type AccountData = super::AccountData<u64>;
type OnNewAccount = ();
type OnReapAccount = Module<Test>;
}
parameter_types! {
pub const TransactionBaseFee: u64 = 0;
@@ -86,25 +83,20 @@ impl pallet_transaction_payment::Trait for Test {
}
impl Trait for Test {
type Balance = u64;
type OnReapAccount = System;
type OnNewAccount = ();
type Event = ();
type DustRemoval = ();
type TransferPayment = ();
type Event = ();
type ExistentialDeposit = ExistentialDeposit;
type CreationFee = CreationFee;
type AccountStore = system::Module<Test>;
}
pub struct ExtBuilder {
existential_deposit: u64,
creation_fee: u64,
monied: bool,
}
impl Default for ExtBuilder {
fn default() -> Self {
Self {
existential_deposit: 1,
creation_fee: 0,
monied: false,
}
}
@@ -114,17 +106,12 @@ impl ExtBuilder {
self.existential_deposit = existential_deposit;
self
}
pub fn creation_fee(mut self, creation_fee: u64) -> Self {
self.creation_fee = creation_fee;
self
}
pub fn monied(mut self, monied: bool) -> Self {
self.monied = monied;
self
}
pub fn set_associated_consts(&self) {
EXISTENTIAL_DEPOSIT.with(|v| *v.borrow_mut() = self.existential_deposit);
CREATION_FEE.with(|v| *v.borrow_mut() = self.creation_fee);
}
pub fn build(self) -> sp_io::TestExternalities {
self.set_associated_consts();
@@ -146,12 +133,4 @@ impl ExtBuilder {
}
}
pub type System = frame_system::Module<Test>;
pub type Balances = Module<Test>;
pub const CALL: &<Test as frame_system::Trait>::Call = &();
/// create a transaction info struct from weight. Handy to avoid building the whole struct.
pub fn info_from_weight(w: Weight) -> DispatchInfo {
DispatchInfo { weight: w, pays_fee: true, ..Default::default() }
}
decl_tests!{ Test, ExtBuilder, EXISTENTIAL_DEPOSIT }
+144
View File
@@ -0,0 +1,144 @@
// Copyright 2018-2020 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Substrate is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
//! Test utilities
use sp_runtime::{Perbill, traits::{ConvertInto, IdentityLookup}, testing::Header};
use sp_core::H256;
use sp_io;
use frame_support::{impl_outer_origin, parameter_types};
use frame_support::traits::{Get, StorageMapShim};
use frame_support::weights::{Weight, DispatchInfo};
use std::cell::RefCell;
use crate::{GenesisConfig, Module, Trait, decl_tests};
use frame_system as system;
impl_outer_origin!{
pub enum Origin for Test {}
}
thread_local! {
static EXISTENTIAL_DEPOSIT: RefCell<u64> = RefCell::new(0);
}
pub struct ExistentialDeposit;
impl Get<u64> for ExistentialDeposit {
fn get() -> u64 { EXISTENTIAL_DEPOSIT.with(|v| *v.borrow()) }
}
// Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted.
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Test;
parameter_types! {
pub const BlockHashCount: u64 = 250;
pub const MaximumBlockWeight: Weight = 1024;
pub const MaximumBlockLength: u32 = 2 * 1024;
pub const AvailableBlockRatio: Perbill = Perbill::one();
}
impl frame_system::Trait for Test {
type Origin = Origin;
type Index = u64;
type BlockNumber = u64;
type Call = ();
type Hash = H256;
type Hashing = ::sp_runtime::traits::BlakeTwo256;
type AccountId = u64;
type Lookup = IdentityLookup<Self::AccountId>;
type Header = Header;
type Event = ();
type BlockHashCount = BlockHashCount;
type MaximumBlockWeight = MaximumBlockWeight;
type MaximumBlockLength = MaximumBlockLength;
type AvailableBlockRatio = AvailableBlockRatio;
type Version = ();
type ModuleToIndex = ();
type AccountData = super::AccountData<u64>;
type OnNewAccount = ();
type OnReapAccount = Module<Test>;
}
parameter_types! {
pub const TransactionBaseFee: u64 = 0;
pub const TransactionByteFee: u64 = 1;
}
impl pallet_transaction_payment::Trait for Test {
type Currency = Module<Test>;
type OnTransactionPayment = ();
type TransactionBaseFee = TransactionBaseFee;
type TransactionByteFee = TransactionByteFee;
type WeightToFee = ConvertInto;
type FeeMultiplierUpdate = ();
}
impl Trait for Test {
type Balance = u64;
type DustRemoval = ();
type Event = ();
type ExistentialDeposit = ExistentialDeposit;
type AccountStore = StorageMapShim<
super::Account<Test>,
system::CallOnCreatedAccount<Test>,
system::CallKillAccount<Test>,
u64, super::AccountData<u64>
>;
}
pub struct ExtBuilder {
existential_deposit: u64,
monied: bool,
}
impl Default for ExtBuilder {
fn default() -> Self {
Self {
existential_deposit: 1,
monied: false,
}
}
}
impl ExtBuilder {
pub fn existential_deposit(mut self, existential_deposit: u64) -> Self {
self.existential_deposit = existential_deposit;
self
}
pub fn monied(mut self, monied: bool) -> Self {
self.monied = monied;
if self.existential_deposit == 0 {
self.existential_deposit = 1;
}
self
}
pub fn set_associated_consts(&self) {
EXISTENTIAL_DEPOSIT.with(|v| *v.borrow_mut() = self.existential_deposit);
}
pub fn build(self) -> sp_io::TestExternalities {
self.set_associated_consts();
let mut t = frame_system::GenesisConfig::default().build_storage::<Test>().unwrap();
GenesisConfig::<Test> {
balances: if self.monied {
vec![
(1, 10 * self.existential_deposit),
(2, 20 * self.existential_deposit),
(3, 30 * self.existential_deposit),
(4, 40 * self.existential_deposit),
(12, 10 * self.existential_deposit)
]
} else {
vec![]
},
}.assimilate_storage(&mut t).unwrap();
t.into()
}
}
decl_tests!{ Test, ExtBuilder, EXISTENTIAL_DEPOSIT }