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:
Gavin Wood
2021-01-16 18:47:28 +01:00
committed by GitHub
parent 660cf13e6d
commit f1d36a7103
34 changed files with 814 additions and 447 deletions
+119 -73
View File
@@ -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)
}
}
+178 -20
View File
@@ -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));
+8 -8
View File
@@ -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(),