Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Introduces account existence providers reference counting #7363

Merged
merged 64 commits into from
Jan 16, 2021
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
ebcc2fe
Initial draft
gavofyork Oct 20, 2020
16724ae
Latest changes
gavofyork Nov 9, 2020
8a15927
Final bits.
gavofyork Nov 11, 2020
866e78b
Fixes
gavofyork Nov 11, 2020
51aec12
Fixes
gavofyork Nov 12, 2020
3609f9e
Merge remote-tracking branch 'origin/master' into gav-account-provide…
gavofyork Nov 12, 2020
934fea6
Merge branch 'gav-account-provider-refs' of github.com:paritytech/sub…
gavofyork Nov 12, 2020
cbe14af
Test fixes
gavofyork Nov 18, 2020
82bb7f1
Merge remote-tracking branch 'origin/master' into gav-account-provide…
gavofyork Nov 18, 2020
be9eb12
Merge remote-tracking branch 'origin/master' into gav-account-provide…
gavofyork Nov 27, 2020
5823e35
Fix tests
gavofyork Dec 8, 2020
4b49fd4
Fix babe tests
gavofyork Dec 9, 2020
4134218
Merge remote-tracking branch 'origin/master' into gav-account-provide…
gavofyork Dec 9, 2020
dc5a96e
Fix
gavofyork Dec 9, 2020
24e41a3
Fix
gavofyork Dec 9, 2020
44fb86e
Fix
gavofyork Dec 9, 2020
4f214a6
Fix
gavofyork Dec 9, 2020
a20c16e
Fix
gavofyork Dec 9, 2020
0f02f26
fix warnings in assets
shawntabrizi Dec 14, 2020
84d94f4
Merge branch 'master' into gav-account-provider-refs
shawntabrizi Dec 14, 2020
e818dc7
Fix UI tests
shawntabrizi Dec 14, 2020
567e1b5
fix line width
shawntabrizi Dec 14, 2020
dadb7f9
Fix
gavofyork Dec 16, 2020
93d07b4
Merge branch 'gav-account-provider-refs' of github.com:paritytech/sub…
gavofyork Dec 16, 2020
928381f
Merge remote-tracking branch 'origin/master' into gav-account-provide…
gavofyork Dec 16, 2020
514bc5d
Update frame/system/src/lib.rs
gavofyork Dec 16, 2020
b6a7378
Update frame/system/src/lib.rs
gavofyork Dec 16, 2020
237bdb0
Fix
gavofyork Dec 16, 2020
3149164
Merge branch 'gav-account-provider-refs' of github.com:paritytech/sub…
gavofyork Dec 16, 2020
6bb2813
fix unused warnings
shawntabrizi Dec 16, 2020
968ca3f
Fix
gavofyork Dec 17, 2020
160838c
Merge branch 'gav-account-provider-refs' of github.com:paritytech/sub…
gavofyork Dec 17, 2020
f15cfeb
Update frame/system/src/lib.rs
gavofyork Dec 17, 2020
702ca55
Update frame/system/src/lib.rs
gavofyork Dec 17, 2020
d34d7f2
Fix
gavofyork Dec 17, 2020
1e23f19
fix slash and comprehensive slash test
shawntabrizi Dec 19, 2020
58643c5
fix reserved slash and comprehensive tests
shawntabrizi Dec 19, 2020
47c1cbe
check slash on non-existent account
shawntabrizi Dec 19, 2020
c856136
Revert "Fix UI tests"
shawntabrizi Dec 19, 2020
66e9be1
Fix
gavofyork Dec 19, 2020
269f36c
Merge branch 'master' into gav-account-provider-refs
shawntabrizi Dec 20, 2020
a075a5e
Fix utility tests
shawntabrizi Dec 20, 2020
e5abf6b
keep dispatch error backwards compatible
shawntabrizi Dec 20, 2020
dfc9bfe
Fix
gavofyork Dec 20, 2020
fbcde4a
Merge branch 'gav-account-provider-refs' of github.com:paritytech/sub…
gavofyork Dec 20, 2020
a3596de
Fix
gavofyork Dec 20, 2020
2df412e
fix ui test
shawntabrizi Dec 21, 2020
f0e1c6b
Companion checker shouldn't be so anal.
gavofyork Dec 21, 2020
d76a08b
Fix
gavofyork Dec 21, 2020
7d9d63a
Fix
gavofyork Dec 21, 2020
9c03134
Fix
gavofyork Dec 21, 2020
fd925e9
Apply suggestions from code review
shawntabrizi Dec 23, 2020
95a2255
Update frame/balances/src/lib.rs
shawntabrizi Dec 23, 2020
f55da2f
return correct slash info when failing gracefully
shawntabrizi Dec 23, 2020
16238a3
Merge branch 'master' into gav-account-provider-refs
shawntabrizi Dec 23, 2020
054516b
fix missing import
shawntabrizi Dec 23, 2020
4aa1bb4
Merge branch 'master' into gav-account-provider-refs
shawntabrizi Dec 28, 2020
2f4a6db
Merge branch 'master' into gav-account-provider-refs
shawntabrizi Dec 28, 2020
17438be
Update frame/system/src/lib.rs
gavofyork Jan 9, 2021
282a391
Merge branch 'master' into gav-account-provider-refs
gavofyork Jan 9, 2021
d5248d1
Fix
gavofyork Jan 10, 2021
9f1df71
Update frame/balances/src/tests_local.rs
gavofyork Jan 16, 2021
4491e73
Fixes
gavofyork Jan 16, 2021
859abab
Merge remote-tracking branch 'origin/master' into gav-account-provide…
gavofyork Jan 16, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion bin/node/executor/tests/submit_transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ fn submitted_transaction_should_be_valid() {
let author = extrinsic.signature.clone().unwrap().0;
let address = Indices::lookup(author).unwrap();
let data = pallet_balances::AccountData { free: 5_000_000_000_000, ..Default::default() };
let account = frame_system::AccountInfo { nonce: 0, refcount: 0, data };
let account = frame_system::AccountInfo { nonce: 0, consumers: 0, providers: 0, data };
<frame_system::Account<Runtime>>::insert(&address, account);

// check validity
Expand Down
172 changes: 102 additions & 70 deletions frame/balances/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -413,7 +413,7 @@ decl_storage! {
)
}
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());
}
});
}
Expand Down Expand Up @@ -518,7 +518,7 @@ decl_module! {
account.reserved = new_reserved;

(account.free, account.reserved)
});
})?;
Self::deposit_event(RawEvent::BalanceSet(who, free, reserved));
}

Expand Down Expand Up @@ -628,9 +628,8 @@ impl<T: Trait<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
Expand All @@ -642,7 +641,7 @@ impl<T: Trait<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> {
Expand Down Expand Up @@ -670,7 +669,8 @@ impl<T: Trait<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() {
Expand All @@ -689,12 +689,20 @@ impl<T: Trait<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."
);
}
}
}
}
Expand Down Expand Up @@ -957,10 +965,12 @@ impl<T: Trait<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(())
Expand Down Expand Up @@ -988,19 +998,34 @@ 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()) }

Self::mutate_account(who, |account| {
let free_slash = cmp::min(account.free, value);
account.free -= free_slash;

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())
for attempt in 0..2 {
match Self::try_mutate_account(who, |account, _is_new| -> Result<(Self::NegativeImbalance, Self::Balance), StoredMapError> {
let free_slash = cmp::min(account.free, value);
account.free -= free_slash;

let 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 - 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;
Ok((NegativeImbalance::new(free_slash + reserved_slash), remaining_slash - reserved_slash))
} else {
Ok((NegativeImbalance::new(value), Zero::zero()))
}
}) {
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`.
Expand All @@ -1023,25 +1048,31 @@ impl<T: Trait<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,
value: Self::Balance,
) -> 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.
Expand Down Expand Up @@ -1080,17 +1111,18 @@ impl<T: Trait<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
// (in the dust cleaner of set_account) before we account for its actual
// 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))
Expand Down Expand Up @@ -1144,14 +1176,22 @@ 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 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
Expand All @@ -1167,12 +1207,31 @@ 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()) }

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 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 - T::ExistentialDeposit::get()),
apopiak marked this conversation as resolved.
Show resolved Hide resolved
};

let actual = cmp::min(account.reserved, 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.
(NegativeImbalance::zero(), Zero::zero())
apopiak marked this conversation as resolved.
Show resolved Hide resolved
}

/// Move the reserved balance of one account into the balance of another, according to `status`.
Expand Down Expand Up @@ -1213,24 +1272,6 @@ impl<T: Trait<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: Trait<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: Trait<I>, I: Instance> LockableCurrency<T::AccountId> for Module<T, I>
where
T::Balance: MaybeSerializeDeserialize + Debug
Expand Down Expand Up @@ -1295,12 +1336,3 @@ where
Self::update_locks(who, &locks[..]);
}
}

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()` if ExistentialDeposit > 0
!T::AccountStore::is_explicit(who)
}
}
Loading