mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-06 04:28:01 +00:00
Introduces account existence providers reference counting (#7363)
* Initial draft * Latest changes * Final bits. * Fixes * Fixes * Test fixes * Fix tests * Fix babe tests * Fix * Fix * Fix * Fix * Fix * fix warnings in assets * Fix UI tests * fix line width * Fix * Update frame/system/src/lib.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * Update frame/system/src/lib.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * Fix * fix unused warnings * Fix * Update frame/system/src/lib.rs Co-authored-by: Guillaume Thiolliere <gui.thiolliere@gmail.com> * Update frame/system/src/lib.rs Co-authored-by: Guillaume Thiolliere <gui.thiolliere@gmail.com> * Fix * fix slash and comprehensive slash test * fix reserved slash and comprehensive tests * check slash on non-existent account * Revert "Fix UI tests" This reverts commit e0002c0f13442f7d0c95a054a6c515536328a4a0. * Fix * Fix utility tests * keep dispatch error backwards compatible * Fix * Fix * fix ui test * Companion checker shouldn't be so anal. * Fix * Fix * Fix * Apply suggestions from code review Co-authored-by: Alexander Popiak <alexander.popiak@parity.io> * Update frame/balances/src/lib.rs Co-authored-by: Alexander Popiak <alexander.popiak@parity.io> * return correct slash info when failing gracefully * fix missing import * Update frame/system/src/lib.rs Co-authored-by: Guillaume Thiolliere <gui.thiolliere@gmail.com> * Fix * Update frame/balances/src/tests_local.rs Co-authored-by: Guillaume Thiolliere <gui.thiolliere@gmail.com> * Fixes Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com> Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Co-authored-by: Guillaume Thiolliere <gui.thiolliere@gmail.com> Co-authored-by: Alexander Popiak <alexander.popiak@parity.io>
This commit is contained in:
@@ -157,22 +157,22 @@ mod benchmarking;
|
||||
pub mod weights;
|
||||
|
||||
use sp_std::prelude::*;
|
||||
use sp_std::{cmp, result, mem, fmt::Debug, ops::BitOr, convert::Infallible};
|
||||
use sp_std::{cmp, result, mem, fmt::Debug, ops::BitOr};
|
||||
use codec::{Codec, Encode, Decode};
|
||||
use frame_support::{
|
||||
StorageValue, Parameter, decl_event, decl_storage, decl_module, decl_error, ensure,
|
||||
traits::{
|
||||
Currency, OnKilledAccount, OnUnbalanced, TryDrop, StoredMap,
|
||||
Currency, OnUnbalanced, TryDrop, StoredMap,
|
||||
WithdrawReasons, LockIdentifier, LockableCurrency, ExistenceRequirement,
|
||||
Imbalance, SignedImbalance, ReservableCurrency, Get, ExistenceRequirement::KeepAlive,
|
||||
ExistenceRequirement::AllowDeath, IsDeadAccount, BalanceStatus as Status,
|
||||
ExistenceRequirement::AllowDeath, BalanceStatus as Status,
|
||||
}
|
||||
};
|
||||
use sp_runtime::{
|
||||
RuntimeDebug, DispatchResult, DispatchError,
|
||||
traits::{
|
||||
Zero, AtLeast32BitUnsigned, StaticLookup, Member, CheckedAdd, CheckedSub,
|
||||
MaybeSerializeDeserialize, Saturating, Bounded,
|
||||
MaybeSerializeDeserialize, Saturating, Bounded, StoredMapError,
|
||||
},
|
||||
};
|
||||
use frame_system::{self as system, ensure_signed, ensure_root};
|
||||
@@ -419,7 +419,7 @@ decl_storage! {
|
||||
assert!(endowed_accounts.len() == config.balances.len(), "duplicate balances in genesis.");
|
||||
|
||||
for &(ref who, free) in config.balances.iter() {
|
||||
T::AccountStore::insert(who, AccountData { free, .. Default::default() });
|
||||
assert!(T::AccountStore::insert(who, AccountData { free, .. Default::default() }).is_ok());
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -524,7 +524,7 @@ decl_module! {
|
||||
account.reserved = new_reserved;
|
||||
|
||||
(account.free, account.reserved)
|
||||
});
|
||||
})?;
|
||||
Self::deposit_event(RawEvent::BalanceSet(who, free, reserved));
|
||||
}
|
||||
|
||||
@@ -634,9 +634,8 @@ impl<T: Config<I>, I: Instance> Module<T, I> {
|
||||
pub fn mutate_account<R>(
|
||||
who: &T::AccountId,
|
||||
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")
|
||||
) -> Result<R, StoredMapError> {
|
||||
Self::try_mutate_account(who, |a, _| -> Result<R, StoredMapError> { Ok(f(a)) })
|
||||
}
|
||||
|
||||
/// Mutate an account to some new value, or delete it entirely with `None`. Will enforce
|
||||
@@ -648,7 +647,7 @@ impl<T: Config<I>, I: Instance> Module<T, I> {
|
||||
///
|
||||
/// 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>(
|
||||
fn try_mutate_account<R, E: From<StoredMapError>>(
|
||||
who: &T::AccountId,
|
||||
f: impl FnOnce(&mut AccountData<T::Balance>, bool) -> Result<R, E>
|
||||
) -> Result<R, E> {
|
||||
@@ -676,7 +675,8 @@ impl<T: Config<I>, I: Instance> Module<T, I> {
|
||||
A runtime configuration adjustment may be needed."
|
||||
);
|
||||
}
|
||||
Self::mutate_account(who, |b| {
|
||||
// No way this can fail since we do not alter the existential balances.
|
||||
let _ = Self::mutate_account(who, |b| {
|
||||
b.misc_frozen = Zero::zero();
|
||||
b.fee_frozen = Zero::zero();
|
||||
for l in locks.iter() {
|
||||
@@ -695,12 +695,20 @@ impl<T: Config<I>, I: Instance> Module<T, I> {
|
||||
if existed {
|
||||
// TODO: use Locks::<T, I>::hashed_key
|
||||
// https://github.com/paritytech/substrate/issues/4969
|
||||
system::Module::<T>::dec_ref(who);
|
||||
system::Module::<T>::dec_consumers(who);
|
||||
}
|
||||
} else {
|
||||
Locks::<T, I>::insert(who, locks);
|
||||
if !existed {
|
||||
system::Module::<T>::inc_ref(who);
|
||||
if system::Module::<T>::inc_consumers(who).is_err() {
|
||||
// No providers for the locks. This is impossible under normal circumstances
|
||||
// since the funds that are under the lock will themselves be stored in the
|
||||
// account and therefore will need a reference.
|
||||
frame_support::debug::warn!(
|
||||
"Warning: Attempt to introduce lock consumer reference, yet no providers. \
|
||||
This is unexpected but should be safe."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -711,13 +719,14 @@ impl<T: Config<I>, I: Instance> Module<T, I> {
|
||||
mod imbalances {
|
||||
use super::{
|
||||
result, DefaultInstance, Imbalance, Config, Zero, Instance, Saturating,
|
||||
StorageValue, TryDrop,
|
||||
StorageValue, TryDrop, RuntimeDebug,
|
||||
};
|
||||
use sp_std::mem;
|
||||
|
||||
/// Opaque, move-only struct with private fields that serves as a token denoting that
|
||||
/// funds have been created without any equal and opposite accounting.
|
||||
#[must_use]
|
||||
#[derive(RuntimeDebug, PartialEq, Eq)]
|
||||
pub struct PositiveImbalance<T: Config<I>, I: Instance=DefaultInstance>(T::Balance);
|
||||
|
||||
impl<T: Config<I>, I: Instance> PositiveImbalance<T, I> {
|
||||
@@ -730,6 +739,7 @@ mod imbalances {
|
||||
/// Opaque, move-only struct with private fields that serves as a token denoting that
|
||||
/// funds have been destroyed without any equal and opposite accounting.
|
||||
#[must_use]
|
||||
#[derive(RuntimeDebug, PartialEq, Eq)]
|
||||
pub struct NegativeImbalance<T: Config<I>, I: Instance=DefaultInstance>(T::Balance);
|
||||
|
||||
impl<T: Config<I>, I: Instance> NegativeImbalance<T, I> {
|
||||
@@ -963,10 +973,12 @@ impl<T: Config<I>, I: Instance> Currency<T::AccountId> for Module<T, I> where
|
||||
value,
|
||||
WithdrawReasons::TRANSFER,
|
||||
from_account.free,
|
||||
)?;
|
||||
).map_err(|_| Error::<T, I>::LiquidityRestrictions)?;
|
||||
|
||||
// TODO: This is over-conservative. There may now be other providers, and this module
|
||||
// may not even be a provider.
|
||||
let allow_death = existence_requirement == ExistenceRequirement::AllowDeath;
|
||||
let allow_death = allow_death && system::Module::<T>::allow_death(transactor);
|
||||
let allow_death = allow_death && !system::Module::<T>::is_provider_required(transactor);
|
||||
ensure!(allow_death || from_account.free >= ed, Error::<T, I>::KeepAlive);
|
||||
|
||||
Ok(())
|
||||
@@ -993,21 +1005,48 @@ impl<T: Config<I>, I: Instance> Currency<T::AccountId> for Module<T, I> where
|
||||
value: Self::Balance
|
||||
) -> (Self::NegativeImbalance, Self::Balance) {
|
||||
if value.is_zero() { return (NegativeImbalance::zero(), Zero::zero()) }
|
||||
if Self::is_dead_account(&who) { return (NegativeImbalance::zero(), value) }
|
||||
if Self::total_balance(&who).is_zero() { return (NegativeImbalance::zero(), value) }
|
||||
|
||||
Self::mutate_account(who, |account| {
|
||||
let free_slash = cmp::min(account.free, value);
|
||||
account.free -= free_slash;
|
||||
for attempt in 0..2 {
|
||||
match Self::try_mutate_account(who,
|
||||
|account, _is_new| -> Result<(Self::NegativeImbalance, Self::Balance), StoredMapError> {
|
||||
// Best value is the most amount we can slash following liveness rules.
|
||||
let best_value = match attempt {
|
||||
// First attempt we try to slash the full amount, and see if liveness issues happen.
|
||||
0 => value,
|
||||
// If acting as a critical provider (i.e. first attempt failed), then slash
|
||||
// as much as possible while leaving at least at ED.
|
||||
_ => value.min((account.free + account.reserved).saturating_sub(T::ExistentialDeposit::get())),
|
||||
};
|
||||
|
||||
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())
|
||||
let free_slash = cmp::min(account.free, best_value);
|
||||
account.free -= free_slash; // Safe because of above check
|
||||
let remaining_slash = best_value - free_slash; // Safe because of above check
|
||||
|
||||
if !remaining_slash.is_zero() {
|
||||
// If we have remaining slash, take it from reserved balance.
|
||||
let reserved_slash = cmp::min(account.reserved, remaining_slash);
|
||||
account.reserved -= reserved_slash; // Safe because of above check
|
||||
Ok((
|
||||
NegativeImbalance::new(free_slash + reserved_slash),
|
||||
value - free_slash - reserved_slash, // Safe because value is gt or eq total slashed
|
||||
))
|
||||
} else {
|
||||
// Else we are done!
|
||||
Ok((
|
||||
NegativeImbalance::new(free_slash),
|
||||
value - free_slash, // Safe because value is gt or eq to total slashed
|
||||
))
|
||||
}
|
||||
}
|
||||
) {
|
||||
Ok(r) => return r,
|
||||
Err(_) => (),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Should never get here. But we'll be defensive anyway.
|
||||
(Self::NegativeImbalance::zero(), value)
|
||||
}
|
||||
|
||||
/// Deposit some `value` into the free balance of an existing target account `who`.
|
||||
@@ -1030,7 +1069,8 @@ impl<T: Config<I>, I: Instance> Currency<T::AccountId> for Module<T, I> where
|
||||
///
|
||||
/// This function is a no-op if:
|
||||
/// - the `value` to be deposited is zero; or
|
||||
/// - if the `value` to be deposited is less than the ED and the account does not yet exist; or
|
||||
/// - the `value` to be deposited is less than the required ED and the account does not yet exist; or
|
||||
/// - the deposit would necessitate the account to exist and there are no provider references; or
|
||||
/// - `value` is so large it would cause the balance of `who` to overflow.
|
||||
fn deposit_creating(
|
||||
who: &T::AccountId,
|
||||
@@ -1038,17 +1078,22 @@ impl<T: Config<I>, I: Instance> Currency<T::AccountId> for Module<T, I> where
|
||||
) -> Self::PositiveImbalance {
|
||||
if value.is_zero() { return Self::PositiveImbalance::zero() }
|
||||
|
||||
Self::try_mutate_account(who, |account, is_new| -> Result<Self::PositiveImbalance, Self::PositiveImbalance> {
|
||||
// bail if not yet created and this operation wouldn't be enough to create it.
|
||||
let r = Self::try_mutate_account(who, |account, is_new| -> Result<Self::PositiveImbalance, DispatchError> {
|
||||
|
||||
let ed = T::ExistentialDeposit::get();
|
||||
ensure!(value >= ed || !is_new, Self::PositiveImbalance::zero());
|
||||
ensure!(value >= ed || !is_new, Error::<T, I>::ExistentialDeposit);
|
||||
|
||||
// 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_else(|| Self::PositiveImbalance::zero())?;
|
||||
account.free = match account.free.checked_add(&value) {
|
||||
Some(x) => x,
|
||||
None => return Ok(Self::PositiveImbalance::zero()),
|
||||
};
|
||||
|
||||
Ok(PositiveImbalance::new(value))
|
||||
}).unwrap_or_else(|x| x)
|
||||
}).unwrap_or_else(|_| Self::PositiveImbalance::zero());
|
||||
|
||||
r
|
||||
}
|
||||
|
||||
/// Withdraw some free balance from an account, respecting existence requirements.
|
||||
@@ -1087,9 +1132,10 @@ impl<T: Config<I>, I: Instance> Currency<T::AccountId> for Module<T, I> where
|
||||
-> SignedImbalance<Self::Balance, Self::PositiveImbalance>
|
||||
{
|
||||
Self::try_mutate_account(who, |account, is_new|
|
||||
-> Result<SignedImbalance<Self::Balance, Self::PositiveImbalance>, ()>
|
||||
-> Result<SignedImbalance<Self::Balance, Self::PositiveImbalance>, DispatchError>
|
||||
{
|
||||
let ed = T::ExistentialDeposit::get();
|
||||
let total = value.saturating_add(account.reserved);
|
||||
// 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
|
||||
@@ -1097,7 +1143,7 @@ impl<T: Config<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.
|
||||
ensure!(value.saturating_add(account.reserved) >= ed || !is_new, ());
|
||||
ensure!(total >= ed || !is_new, Error::<T, I>::ExistentialDeposit);
|
||||
|
||||
let imbalance = if account.free <= value {
|
||||
SignedImbalance::Positive(PositiveImbalance::new(value - account.free))
|
||||
@@ -1150,16 +1196,24 @@ impl<T: Config<I>, I: Instance> ReservableCurrency<T::AccountId> for Module<T, I
|
||||
/// Is a no-op if the value to be unreserved is zero or the account does not exist.
|
||||
fn unreserve(who: &T::AccountId, value: Self::Balance) -> Self::Balance {
|
||||
if value.is_zero() { return Zero::zero() }
|
||||
if Self::is_dead_account(&who) { return value }
|
||||
if Self::total_balance(&who).is_zero() { return value }
|
||||
|
||||
let actual = Self::mutate_account(who, |account| {
|
||||
let actual = match 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 data type.
|
||||
account.free = account.free.saturating_add(actual);
|
||||
actual
|
||||
});
|
||||
}) {
|
||||
Ok(x) => x,
|
||||
Err(_) => {
|
||||
// This should never happen since we don't alter the total amount in the account.
|
||||
// If it ever does, then we should fail gracefully though, indicating that nothing
|
||||
// could be done.
|
||||
return value
|
||||
}
|
||||
};
|
||||
|
||||
Self::deposit_event(RawEvent::Unreserved(who.clone(), actual.clone()));
|
||||
value - actual
|
||||
@@ -1174,14 +1228,33 @@ impl<T: Config<I>, I: Instance> ReservableCurrency<T::AccountId> for Module<T, I
|
||||
value: Self::Balance
|
||||
) -> (Self::NegativeImbalance, Self::Balance) {
|
||||
if value.is_zero() { return (NegativeImbalance::zero(), Zero::zero()) }
|
||||
if Self::is_dead_account(&who) { return (NegativeImbalance::zero(), value) }
|
||||
if Self::total_balance(&who).is_zero() { return (NegativeImbalance::zero(), value) }
|
||||
|
||||
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)
|
||||
})
|
||||
// NOTE: `mutate_account` may fail if it attempts to reduce the balance to the point that an
|
||||
// account is attempted to be illegally destroyed.
|
||||
|
||||
for attempt in 0..2 {
|
||||
match Self::mutate_account(who, |account| {
|
||||
let best_value = match attempt {
|
||||
0 => value,
|
||||
// If acting as a critical provider (i.e. first attempt failed), then ensure
|
||||
// slash leaves at least the ED.
|
||||
_ => value.min((account.free + account.reserved).saturating_sub(T::ExistentialDeposit::get())),
|
||||
};
|
||||
|
||||
let actual = cmp::min(account.reserved, best_value);
|
||||
account.reserved -= actual;
|
||||
|
||||
// underflow should never happen, but it if does, there's nothing to be done here.
|
||||
(NegativeImbalance::new(actual), value - actual)
|
||||
}) {
|
||||
Ok(r) => return r,
|
||||
Err(_) => (),
|
||||
}
|
||||
}
|
||||
// Should never get here as we ensure that ED is left in the second attempt.
|
||||
// In case we do, though, then we fail gracefully.
|
||||
(Self::NegativeImbalance::zero(), value)
|
||||
}
|
||||
|
||||
/// Move the reserved balance of one account into the balance of another, according to `status`.
|
||||
@@ -1222,24 +1295,6 @@ impl<T: Config<I>, I: Instance> ReservableCurrency<T::AccountId> for Module<T, I
|
||||
}
|
||||
}
|
||||
|
||||
/// Implement `OnKilledAccount` to remove the local account, if using local account storage.
|
||||
///
|
||||
/// NOTE: You probably won't need to use this! This only needs to be "wired in" to System module
|
||||
/// if you're using the local balance storage. **If you're using the composite system account
|
||||
/// storage (which is the default in most examples and tests) then there's no need.**
|
||||
impl<T: Config<I>, I: Instance> OnKilledAccount<T::AccountId> for Module<T, I> {
|
||||
fn on_killed_account(who: &T::AccountId) {
|
||||
Account::<T, I>::mutate_exists(who, |account| {
|
||||
let total = account.as_ref().map(|acc| acc.total()).unwrap_or_default();
|
||||
if !total.is_zero() {
|
||||
T::DustRemoval::on_unbalanced(NegativeImbalance::new(total));
|
||||
Self::deposit_event(RawEvent::DustLost(who.clone(), total));
|
||||
}
|
||||
*account = None;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: Instance> LockableCurrency<T::AccountId> for Module<T, I>
|
||||
where
|
||||
T::Balance: MaybeSerializeDeserialize + Debug
|
||||
@@ -1304,12 +1359,3 @@ where
|
||||
Self::update_locks(who, &locks[..]);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<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()` if ExistentialDeposit > 0
|
||||
!T::AccountStore::is_explicit(who)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ macro_rules! decl_tests {
|
||||
assert_noop, assert_storage_noop, assert_ok, assert_err,
|
||||
traits::{
|
||||
LockableCurrency, LockIdentifier, WithdrawReasons,
|
||||
Currency, ReservableCurrency, ExistenceRequirement::AllowDeath, StoredMap
|
||||
Currency, ReservableCurrency, ExistenceRequirement::AllowDeath
|
||||
}
|
||||
};
|
||||
use pallet_transaction_payment::{ChargeTransactionPayment, Multiplier};
|
||||
@@ -91,7 +91,8 @@ macro_rules! decl_tests {
|
||||
<$ext_builder>::default().existential_deposit(1).monied(true).build().execute_with(|| {
|
||||
assert_eq!(Balances::free_balance(1), 10);
|
||||
assert_ok!(<Balances as Currency<_>>::transfer(&1, &2, 10, AllowDeath));
|
||||
assert!(!<<Test as Config>::AccountStore as StoredMap<u64, AccountData<u64>>>::is_explicit(&1));
|
||||
// Check that the account is dead.
|
||||
assert!(!frame_system::Account::<Test>::contains_key(&1));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -262,14 +263,12 @@ macro_rules! decl_tests {
|
||||
.monied(true)
|
||||
.build()
|
||||
.execute_with(|| {
|
||||
assert_eq!(Balances::is_dead_account(&5), true);
|
||||
// account 5 should not exist
|
||||
// ext_deposit is 10, value is 9, not satisfies for ext_deposit
|
||||
assert_noop!(
|
||||
Balances::transfer(Some(1).into(), 5, 9),
|
||||
Error::<$test, _>::ExistentialDeposit,
|
||||
);
|
||||
assert_eq!(Balances::is_dead_account(&5), true); // account 5 should not exist
|
||||
assert_eq!(Balances::free_balance(1), 100);
|
||||
});
|
||||
}
|
||||
@@ -282,31 +281,25 @@ macro_rules! decl_tests {
|
||||
.build()
|
||||
.execute_with(|| {
|
||||
System::inc_account_nonce(&2);
|
||||
assert_eq!(Balances::is_dead_account(&2), false);
|
||||
assert_eq!(Balances::is_dead_account(&5), true);
|
||||
assert_eq!(Balances::total_balance(&2), 256 * 20);
|
||||
|
||||
assert_ok!(Balances::reserve(&2, 256 * 19 + 1)); // account 2 becomes mostly reserved
|
||||
assert_eq!(Balances::free_balance(2), 255); // "free" account deleted."
|
||||
assert_eq!(Balances::total_balance(&2), 256 * 20); // reserve still exists.
|
||||
assert_eq!(Balances::is_dead_account(&2), false);
|
||||
assert_eq!(System::account_nonce(&2), 1);
|
||||
|
||||
// account 4 tries to take index 1 for account 5.
|
||||
assert_ok!(Balances::transfer(Some(4).into(), 5, 256 * 1 + 0x69));
|
||||
assert_eq!(Balances::total_balance(&5), 256 * 1 + 0x69);
|
||||
assert_eq!(Balances::is_dead_account(&5), false);
|
||||
|
||||
assert!(Balances::slash(&2, 256 * 19 + 2).1.is_zero()); // account 2 gets slashed
|
||||
// "reserve" account reduced to 255 (below ED) so account deleted
|
||||
assert_eq!(Balances::total_balance(&2), 0);
|
||||
assert_eq!(System::account_nonce(&2), 0); // nonce zero
|
||||
assert_eq!(Balances::is_dead_account(&2), true);
|
||||
|
||||
// account 4 tries to take index 1 again for account 6.
|
||||
assert_ok!(Balances::transfer(Some(4).into(), 6, 256 * 1 + 0x69));
|
||||
assert_eq!(Balances::total_balance(&6), 256 * 1 + 0x69);
|
||||
assert_eq!(Balances::is_dead_account(&6), false);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -417,7 +410,7 @@ macro_rules! decl_tests {
|
||||
fn refunding_balance_should_work() {
|
||||
<$ext_builder>::default().build().execute_with(|| {
|
||||
let _ = Balances::deposit_creating(&1, 42);
|
||||
Balances::mutate_account(&1, |a| a.reserved = 69);
|
||||
assert!(Balances::mutate_account(&1, |a| a.reserved = 69).is_ok());
|
||||
Balances::unreserve(&1, 69);
|
||||
assert_eq!(Balances::free_balance(1), 111);
|
||||
assert_eq!(Balances::reserved_balance(1), 0);
|
||||
@@ -623,7 +616,6 @@ macro_rules! decl_tests {
|
||||
Balances::transfer_keep_alive(Some(1).into(), 2, 100),
|
||||
Error::<$test, _>::KeepAlive
|
||||
);
|
||||
assert_eq!(Balances::is_dead_account(&1), false);
|
||||
assert_eq!(Balances::total_balance(&1), 100);
|
||||
assert_eq!(Balances::total_balance(&2), 0);
|
||||
});
|
||||
@@ -695,7 +687,6 @@ macro_rules! decl_tests {
|
||||
// Reserve some free balance
|
||||
let _ = Balances::slash(&1, 1);
|
||||
// The account should be dead.
|
||||
assert!(Balances::is_dead_account(&1));
|
||||
assert_eq!(Balances::free_balance(1), 0);
|
||||
assert_eq!(Balances::reserved_balance(1), 0);
|
||||
});
|
||||
@@ -767,7 +758,7 @@ macro_rules! decl_tests {
|
||||
#[test]
|
||||
fn emit_events_with_no_existential_deposit_suicide() {
|
||||
<$ext_builder>::default()
|
||||
.existential_deposit(0)
|
||||
.existential_deposit(1)
|
||||
.build()
|
||||
.execute_with(|| {
|
||||
assert_ok!(Balances::set_balance(RawOrigin::Root.into(), 1, 100, 0));
|
||||
@@ -783,11 +774,6 @@ macro_rules! decl_tests {
|
||||
|
||||
let _ = Balances::slash(&1, 100);
|
||||
|
||||
// no events
|
||||
assert_eq!(events(), []);
|
||||
|
||||
assert_ok!(System::suicide(Origin::signed(1)));
|
||||
|
||||
assert_eq!(
|
||||
events(),
|
||||
[
|
||||
@@ -797,6 +783,178 @@ macro_rules! decl_tests {
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn slash_loop_works() {
|
||||
<$ext_builder>::default()
|
||||
.existential_deposit(100)
|
||||
.build()
|
||||
.execute_with(|| {
|
||||
/* User has no reference counter, so they can die in these scenarios */
|
||||
|
||||
// SCENARIO: Slash would not kill account.
|
||||
assert_ok!(Balances::set_balance(Origin::root(), 1, 1_000, 0));
|
||||
// Slashed completed in full
|
||||
assert_eq!(Balances::slash(&1, 900), (NegativeImbalance::new(900), 0));
|
||||
// Account is still alive
|
||||
assert!(System::account_exists(&1));
|
||||
|
||||
// SCENARIO: Slash will kill account because not enough balance left.
|
||||
assert_ok!(Balances::set_balance(Origin::root(), 1, 1_000, 0));
|
||||
// Slashed completed in full
|
||||
assert_eq!(Balances::slash(&1, 950), (NegativeImbalance::new(950), 0));
|
||||
// Account is killed
|
||||
assert!(!System::account_exists(&1));
|
||||
|
||||
// SCENARIO: Over-slash will kill account, and report missing slash amount.
|
||||
assert_ok!(Balances::set_balance(Origin::root(), 1, 1_000, 0));
|
||||
// Slashed full free_balance, and reports 300 not slashed
|
||||
assert_eq!(Balances::slash(&1, 1_300), (NegativeImbalance::new(1000), 300));
|
||||
// Account is dead
|
||||
assert!(!System::account_exists(&1));
|
||||
|
||||
// SCENARIO: Over-slash can take from reserved, but keep alive.
|
||||
assert_ok!(Balances::set_balance(Origin::root(), 1, 1_000, 400));
|
||||
// Slashed full free_balance and 300 of reserved balance
|
||||
assert_eq!(Balances::slash(&1, 1_300), (NegativeImbalance::new(1300), 0));
|
||||
// Account is still alive
|
||||
assert!(System::account_exists(&1));
|
||||
|
||||
// SCENARIO: Over-slash can take from reserved, and kill.
|
||||
assert_ok!(Balances::set_balance(Origin::root(), 1, 1_000, 350));
|
||||
// Slashed full free_balance and 300 of reserved balance
|
||||
assert_eq!(Balances::slash(&1, 1_300), (NegativeImbalance::new(1300), 0));
|
||||
// Account is dead because 50 reserved balance is not enough to keep alive
|
||||
assert!(!System::account_exists(&1));
|
||||
|
||||
// SCENARIO: Over-slash can take as much as possible from reserved, kill, and report missing amount.
|
||||
assert_ok!(Balances::set_balance(Origin::root(), 1, 1_000, 250));
|
||||
// Slashed full free_balance and 300 of reserved balance
|
||||
assert_eq!(Balances::slash(&1, 1_300), (NegativeImbalance::new(1250), 50));
|
||||
// Account is super dead
|
||||
assert!(!System::account_exists(&1));
|
||||
|
||||
/* User will now have a reference counter on them, keeping them alive in these scenarios */
|
||||
|
||||
// SCENARIO: Slash would not kill account.
|
||||
assert_ok!(Balances::set_balance(Origin::root(), 1, 1_000, 0));
|
||||
assert_ok!(System::inc_consumers(&1)); // <-- Reference counter added here is enough for all tests
|
||||
// Slashed completed in full
|
||||
assert_eq!(Balances::slash(&1, 900), (NegativeImbalance::new(900), 0));
|
||||
// Account is still alive
|
||||
assert!(System::account_exists(&1));
|
||||
|
||||
// SCENARIO: Slash will take as much as possible without killing account.
|
||||
assert_ok!(Balances::set_balance(Origin::root(), 1, 1_000, 0));
|
||||
// Slashed completed in full
|
||||
assert_eq!(Balances::slash(&1, 950), (NegativeImbalance::new(900), 50));
|
||||
// Account is still alive
|
||||
assert!(System::account_exists(&1));
|
||||
|
||||
// SCENARIO: Over-slash will not kill account, and report missing slash amount.
|
||||
assert_ok!(Balances::set_balance(Origin::root(), 1, 1_000, 0));
|
||||
// Slashed full free_balance minus ED, and reports 400 not slashed
|
||||
assert_eq!(Balances::slash(&1, 1_300), (NegativeImbalance::new(900), 400));
|
||||
// Account is still alive
|
||||
assert!(System::account_exists(&1));
|
||||
|
||||
// SCENARIO: Over-slash can take from reserved, but keep alive.
|
||||
assert_ok!(Balances::set_balance(Origin::root(), 1, 1_000, 400));
|
||||
// Slashed full free_balance and 300 of reserved balance
|
||||
assert_eq!(Balances::slash(&1, 1_300), (NegativeImbalance::new(1300), 0));
|
||||
// Account is still alive
|
||||
assert!(System::account_exists(&1));
|
||||
|
||||
// SCENARIO: Over-slash can take from reserved, but keep alive.
|
||||
assert_ok!(Balances::set_balance(Origin::root(), 1, 1_000, 350));
|
||||
// Slashed full free_balance and 250 of reserved balance to leave ED
|
||||
assert_eq!(Balances::slash(&1, 1_300), (NegativeImbalance::new(1250), 50));
|
||||
// Account is still alive
|
||||
assert!(System::account_exists(&1));
|
||||
|
||||
// SCENARIO: Over-slash can take as much as possible from reserved and report missing amount.
|
||||
assert_ok!(Balances::set_balance(Origin::root(), 1, 1_000, 250));
|
||||
// Slashed full free_balance and 300 of reserved balance
|
||||
assert_eq!(Balances::slash(&1, 1_300), (NegativeImbalance::new(1150), 150));
|
||||
// Account is still alive
|
||||
assert!(System::account_exists(&1));
|
||||
|
||||
// Slash on non-existent account is okay.
|
||||
assert_eq!(Balances::slash(&12345, 1_300), (NegativeImbalance::new(0), 1300));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn slash_reserved_loop_works() {
|
||||
<$ext_builder>::default()
|
||||
.existential_deposit(100)
|
||||
.build()
|
||||
.execute_with(|| {
|
||||
/* User has no reference counter, so they can die in these scenarios */
|
||||
|
||||
// SCENARIO: Slash would not kill account.
|
||||
assert_ok!(Balances::set_balance(Origin::root(), 1, 50, 1_000));
|
||||
// Slashed completed in full
|
||||
assert_eq!(Balances::slash_reserved(&1, 900), (NegativeImbalance::new(900), 0));
|
||||
// Account is still alive
|
||||
assert!(System::account_exists(&1));
|
||||
|
||||
// SCENARIO: Slash would kill account.
|
||||
assert_ok!(Balances::set_balance(Origin::root(), 1, 50, 1_000));
|
||||
// Slashed completed in full
|
||||
assert_eq!(Balances::slash_reserved(&1, 1_000), (NegativeImbalance::new(1_000), 0));
|
||||
// Account is dead
|
||||
assert!(!System::account_exists(&1));
|
||||
|
||||
// SCENARIO: Over-slash would kill account, and reports left over slash.
|
||||
assert_ok!(Balances::set_balance(Origin::root(), 1, 50, 1_000));
|
||||
// Slashed completed in full
|
||||
assert_eq!(Balances::slash_reserved(&1, 1_300), (NegativeImbalance::new(1_000), 300));
|
||||
// Account is dead
|
||||
assert!(!System::account_exists(&1));
|
||||
|
||||
// SCENARIO: Over-slash does not take from free balance.
|
||||
assert_ok!(Balances::set_balance(Origin::root(), 1, 300, 1_000));
|
||||
// Slashed completed in full
|
||||
assert_eq!(Balances::slash_reserved(&1, 1_300), (NegativeImbalance::new(1_000), 300));
|
||||
// Account is alive because of free balance
|
||||
assert!(System::account_exists(&1));
|
||||
|
||||
/* User has a reference counter, so they cannot die */
|
||||
|
||||
// SCENARIO: Slash would not kill account.
|
||||
assert_ok!(Balances::set_balance(Origin::root(), 1, 50, 1_000));
|
||||
assert_ok!(System::inc_consumers(&1)); // <-- Reference counter added here is enough for all tests
|
||||
// Slashed completed in full
|
||||
assert_eq!(Balances::slash_reserved(&1, 900), (NegativeImbalance::new(900), 0));
|
||||
// Account is still alive
|
||||
assert!(System::account_exists(&1));
|
||||
|
||||
// SCENARIO: Slash as much as possible without killing.
|
||||
assert_ok!(Balances::set_balance(Origin::root(), 1, 50, 1_000));
|
||||
// Slashed as much as possible
|
||||
assert_eq!(Balances::slash_reserved(&1, 1_000), (NegativeImbalance::new(950), 50));
|
||||
// Account is still alive
|
||||
assert!(System::account_exists(&1));
|
||||
|
||||
// SCENARIO: Over-slash reports correctly, where reserved is needed to keep alive.
|
||||
assert_ok!(Balances::set_balance(Origin::root(), 1, 50, 1_000));
|
||||
// Slashed as much as possible
|
||||
assert_eq!(Balances::slash_reserved(&1, 1_300), (NegativeImbalance::new(950), 350));
|
||||
// Account is still alive
|
||||
assert!(System::account_exists(&1));
|
||||
|
||||
// SCENARIO: Over-slash reports correctly, where full reserved is removed.
|
||||
assert_ok!(Balances::set_balance(Origin::root(), 1, 200, 1_000));
|
||||
// Slashed as much as possible
|
||||
assert_eq!(Balances::slash_reserved(&1, 1_300), (NegativeImbalance::new(1_000), 300));
|
||||
// Account is still alive
|
||||
assert!(System::account_exists(&1));
|
||||
|
||||
// Slash on non-existent account is okay.
|
||||
assert_eq!(Balances::slash_reserved(&12345, 1_300), (NegativeImbalance::new(0), 1300));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn operations_on_dead_account_should_not_change_state() {
|
||||
// These functions all use `mutate_account` which may introduce a storage change when
|
||||
@@ -805,7 +963,7 @@ macro_rules! decl_tests {
|
||||
.existential_deposit(0)
|
||||
.build()
|
||||
.execute_with(|| {
|
||||
assert!(!<Test as Config>::AccountStore::is_explicit(&1337));
|
||||
assert!(!frame_system::Account::<Test>::contains_key(&1337));
|
||||
|
||||
// Unreserve
|
||||
assert_storage_noop!(assert_eq!(Balances::unreserve(&1337, 42), 42));
|
||||
|
||||
@@ -74,9 +74,9 @@ impl frame_system::Config for Test {
|
||||
type BlockHashCount = BlockHashCount;
|
||||
type Version = ();
|
||||
type PalletInfo = ();
|
||||
type AccountData = super::AccountData<u64>;
|
||||
type AccountData = ();
|
||||
type OnNewAccount = ();
|
||||
type OnKilledAccount = Module<Test>;
|
||||
type OnKilledAccount = ();
|
||||
type SystemWeightInfo = ();
|
||||
type SS58Prefix = ();
|
||||
}
|
||||
@@ -99,9 +99,9 @@ impl Config for Test {
|
||||
type ExistentialDeposit = ExistentialDeposit;
|
||||
type AccountStore = StorageMapShim<
|
||||
super::Account<Test>,
|
||||
system::CallOnCreatedAccount<Test>,
|
||||
system::CallKillAccount<Test>,
|
||||
u64, super::AccountData<u64>
|
||||
system::Provider<Test>,
|
||||
u64,
|
||||
super::AccountData<u64>,
|
||||
>;
|
||||
type MaxLocks = MaxLocks;
|
||||
type WeightInfo = ();
|
||||
@@ -162,7 +162,7 @@ decl_tests!{ Test, ExtBuilder, EXISTENTIAL_DEPOSIT }
|
||||
#[test]
|
||||
fn emit_events_with_no_existential_deposit_suicide_with_dust() {
|
||||
<ExtBuilder>::default()
|
||||
.existential_deposit(0)
|
||||
.existential_deposit(2)
|
||||
.build()
|
||||
.execute_with(|| {
|
||||
assert_ok!(Balances::set_balance(RawOrigin::Root.into(), 1, 100, 0));
|
||||
@@ -176,12 +176,12 @@ fn emit_events_with_no_existential_deposit_suicide_with_dust() {
|
||||
]
|
||||
);
|
||||
|
||||
let _ = Balances::slash(&1, 99);
|
||||
let _ = Balances::slash(&1, 98);
|
||||
|
||||
// no events
|
||||
assert_eq!(events(), []);
|
||||
|
||||
assert_ok!(System::suicide(Origin::signed(1)));
|
||||
let _ = Balances::slash(&1, 1);
|
||||
|
||||
assert_eq!(
|
||||
events(),
|
||||
|
||||
Reference in New Issue
Block a user