Allow ExistentialDeposit = 0 (#6185)

* Allow ExistentialDeposit = 0

* there are better ways to check is an account exists

* fix StorageMapShim

* test account events and fix some bugs
This commit is contained in:
Xiliang Chen
2020-06-03 03:22:21 +12:00
committed by GitHub
parent 7b9add7a71
commit 6547d7a09a
6 changed files with 186 additions and 42 deletions
+27 -27
View File
@@ -365,9 +365,6 @@ decl_storage! {
/// The balance of an account.
///
/// 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.
///
/// NOTE: This is only used in the case that this module is used to store balances.
pub Account: map hasher(blake2_128_concat) T::AccountId => AccountData<T::Balance>;
@@ -384,10 +381,6 @@ decl_storage! {
config(balances): Vec<(T::AccountId, T::Balance)>;
// ^^ begin, length, amount liquid at genesis
build(|config: &GenesisConfig<T, I>| {
assert!(
<T as Trait<I>>::ExistentialDeposit::get() > Zero::zero(),
"The existential deposit should be greater than zero."
);
for (_, balance) in &config.balances {
assert!(
*balance >= <T as Trait<I>>::ExistentialDeposit::get(),
@@ -609,7 +602,7 @@ impl<T: Trait<I>, I: Instance> Module<T, I> {
who: &T::AccountId,
f: impl FnOnce(&mut AccountData<T::Balance>) -> R
) -> R {
Self::try_mutate_account(who, |a| -> Result<R, Infallible> { Ok(f(a)) })
Self::try_mutate_account(who, |a, _| -> Result<R, Infallible> { Ok(f(a)) })
.expect("Error is infallible; qed")
}
@@ -624,13 +617,13 @@ impl<T: Trait<I>, I: Instance> Module<T, I> {
/// the caller will do this.
fn try_mutate_account<R, E>(
who: &T::AccountId,
f: impl FnOnce(&mut AccountData<T::Balance>) -> Result<R, E>
f: impl FnOnce(&mut AccountData<T::Balance>, bool) -> Result<R, E>
) -> Result<R, E> {
T::AccountStore::try_mutate_exists(who, |maybe_account| {
let is_new = maybe_account.is_none();
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 };
f(&mut account, is_new).map(move |result| {
let maybe_endowed = if is_new { Some(account.free) } else { None };
*maybe_account = Self::post_mutation(who, account);
(maybe_endowed, result)
})
@@ -966,8 +959,8 @@ impl<T: Trait<I>, I: Instance> Currency<T::AccountId> for Module<T, I> where
) -> DispatchResult {
if value.is_zero() || transactor == dest { return Ok(()) }
Self::try_mutate_account(dest, |to_account| -> DispatchResult {
Self::try_mutate_account(transactor, |from_account| -> DispatchResult {
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)?;
@@ -1038,8 +1031,8 @@ impl<T: Trait<I>, I: Instance> Currency<T::AccountId> for Module<T, I> where
) -> Result<Self::PositiveImbalance, DispatchError> {
if value.is_zero() { return Ok(PositiveImbalance::zero()) }
Self::try_mutate_account(who, |account| -> Result<Self::PositiveImbalance, DispatchError> {
ensure!(!account.total().is_zero(), Error::<T, I>::DeadAccount);
Self::try_mutate_account(who, |account, is_new| -> Result<Self::PositiveImbalance, DispatchError> {
ensure!(!is_new, Error::<T, I>::DeadAccount);
account.free = account.free.checked_add(&value).ok_or(Error::<T, I>::Overflow)?;
Ok(PositiveImbalance::new(value))
})
@@ -1057,10 +1050,10 @@ impl<T: Trait<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| -> Result<Self::PositiveImbalance, Self::PositiveImbalance> {
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 ed = T::ExistentialDeposit::get();
ensure!(value >= ed || !account.total().is_zero(), Self::PositiveImbalance::zero());
ensure!(value >= ed || !is_new, Self::PositiveImbalance::zero());
// defensive only: overflow should never happen, however in case it does, then this
// operation is a no-op.
@@ -1081,7 +1074,7 @@ impl<T: Trait<I>, I: Instance> Currency<T::AccountId> for Module<T, I> where
) -> result::Result<Self::NegativeImbalance, DispatchError> {
if value.is_zero() { return Ok(NegativeImbalance::zero()); }
Self::try_mutate_account(who, |account|
Self::try_mutate_account(who, |account, _|
-> Result<Self::NegativeImbalance, DispatchError>
{
let new_free_account = account.free.checked_sub(&value)
@@ -1105,7 +1098,7 @@ impl<T: Trait<I>, I: Instance> Currency<T::AccountId> for Module<T, I> where
fn make_free_balance_be(who: &T::AccountId, value: Self::Balance)
-> SignedImbalance<Self::Balance, Self::PositiveImbalance>
{
Self::try_mutate_account(who, |account|
Self::try_mutate_account(who, |account, is_new|
-> Result<SignedImbalance<Self::Balance, Self::PositiveImbalance>, ()>
{
let ed = T::ExistentialDeposit::get();
@@ -1116,7 +1109,7 @@ 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.
ensure!(value.saturating_add(account.reserved) >= ed || !account.total().is_zero(), ());
ensure!(value.saturating_add(account.reserved) >= ed || !is_new, ());
let imbalance = if account.free <= value {
SignedImbalance::Positive(PositiveImbalance::new(value - account.free))
@@ -1154,7 +1147,7 @@ impl<T: Trait<I>, I: Instance> ReservableCurrency<T::AccountId> for Module<T, I>
fn reserve(who: &T::AccountId, value: Self::Balance) -> DispatchResult {
if value.is_zero() { return Ok(()) }
Self::try_mutate_account(who, |account| -> DispatchResult {
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)
@@ -1215,9 +1208,9 @@ impl<T: Trait<I>, I: Instance> ReservableCurrency<T::AccountId> for Module<T, I>
};
}
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> {
Self::try_mutate_account(beneficiary, |to_account, is_new| -> Result<Self::Balance, DispatchError> {
ensure!(!is_new, 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)?,
@@ -1237,7 +1230,14 @@ impl<T: Trait<I>, I: Instance> ReservableCurrency<T::AccountId> for Module<T, I>
/// 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>::remove(who);
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;
});
}
}
@@ -1311,7 +1311,7 @@ impl<T: Trait<I>, I: Instance> IsDeadAccount<T::AccountId> for Module<T, I> wher
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()`
// this should always be exactly equivalent to `Self::account(who).total().is_zero()` if ExistentialDeposit > 0
!T::AccountStore::is_explicit(who)
}
}
+70
View File
@@ -61,6 +61,14 @@ macro_rules! decl_tests {
DispatchInfo { weight: w, ..Default::default() }
}
fn events() -> Vec<Event> {
let evt = System::events().into_iter().map(|evt| evt.event).collect::<Vec<_>>();
System::reset_events();
evt
}
#[test]
fn basic_locking_should_work() {
<$ext_builder>::default().existential_deposit(1).monied(true).build().execute_with(|| {
@@ -674,5 +682,67 @@ macro_rules! decl_tests {
assert_eq!(Balances::reserved_balance(1), 0);
});
}
#[test]
fn emit_events_with_existential_deposit() {
<$ext_builder>::default()
.existential_deposit(100)
.build()
.execute_with(|| {
assert_ok!(Balances::set_balance(RawOrigin::Root.into(), 1, 100, 0));
assert_eq!(
events(),
[
Event::system(system::RawEvent::NewAccount(1)),
Event::balances(RawEvent::Endowed(1, 100)),
Event::balances(RawEvent::BalanceSet(1, 100, 0)),
]
);
let _ = Balances::slash(&1, 1);
assert_eq!(
events(),
[
Event::balances(RawEvent::DustLost(1, 99)),
Event::system(system::RawEvent::KilledAccount(1))
]
);
});
}
#[test]
fn emit_events_with_no_existential_deposit_suicide() {
<$ext_builder>::default()
.existential_deposit(0)
.build()
.execute_with(|| {
assert_ok!(Balances::set_balance(RawOrigin::Root.into(), 1, 100, 0));
assert_eq!(
events(),
[
Event::system(system::RawEvent::NewAccount(1)),
Event::balances(RawEvent::Endowed(1, 100)),
Event::balances(RawEvent::BalanceSet(1, 100, 0)),
]
);
let _ = Balances::slash(&1, 100);
// no events
assert_eq!(events(), []);
assert_ok!(System::suicide(Origin::signed(1)));
assert_eq!(
events(),
[
Event::system(system::RawEvent::KilledAccount(1))
]
);
});
}
}
}
@@ -26,7 +26,7 @@ use sp_runtime::{
};
use sp_core::H256;
use sp_io;
use frame_support::{impl_outer_origin, parameter_types};
use frame_support::{impl_outer_origin, impl_outer_event, parameter_types};
use frame_support::traits::Get;
use frame_support::weights::{Weight, DispatchInfo, IdentityFee};
use std::cell::RefCell;
@@ -37,6 +37,17 @@ impl_outer_origin!{
pub enum Origin for Test {}
}
mod balances {
pub use crate::Event;
}
impl_outer_event! {
pub enum Event for Test {
system<T>,
balances<T>,
}
}
thread_local! {
static EXISTENTIAL_DEPOSIT: RefCell<u64> = RefCell::new(0);
}
@@ -65,7 +76,7 @@ impl frame_system::Trait for Test {
type AccountId = u64;
type Lookup = IdentityLookup<Self::AccountId>;
type Header = Header;
type Event = ();
type Event = Event;
type BlockHashCount = BlockHashCount;
type MaximumBlockWeight = MaximumBlockWeight;
type DbWeight = ();
@@ -93,7 +104,7 @@ impl pallet_transaction_payment::Trait for Test {
impl Trait for Test {
type Balance = u64;
type DustRemoval = ();
type Event = ();
type Event = Event;
type ExistentialDeposit = ExistentialDeposit;
type AccountStore = system::Module<Test>;
}
@@ -138,7 +149,10 @@ impl ExtBuilder {
vec![]
},
}.assimilate_storage(&mut t).unwrap();
t.into()
let mut ext = sp_io::TestExternalities::new(t);
ext.execute_with(|| System::set_block_number(1));
ext
}
}
+52 -4
View File
@@ -26,7 +26,7 @@ use sp_runtime::{
};
use sp_core::H256;
use sp_io;
use frame_support::{impl_outer_origin, parameter_types};
use frame_support::{impl_outer_origin, impl_outer_event, parameter_types};
use frame_support::traits::{Get, StorageMapShim};
use frame_support::weights::{Weight, DispatchInfo, IdentityFee};
use std::cell::RefCell;
@@ -37,6 +37,17 @@ impl_outer_origin!{
pub enum Origin for Test {}
}
mod balances {
pub use crate::Event;
}
impl_outer_event! {
pub enum Event for Test {
system<T>,
balances<T>,
}
}
thread_local! {
static EXISTENTIAL_DEPOSIT: RefCell<u64> = RefCell::new(0);
}
@@ -65,7 +76,7 @@ impl frame_system::Trait for Test {
type AccountId = u64;
type Lookup = IdentityLookup<Self::AccountId>;
type Header = Header;
type Event = ();
type Event = Event;
type BlockHashCount = BlockHashCount;
type MaximumBlockWeight = MaximumBlockWeight;
type DbWeight = ();
@@ -93,7 +104,7 @@ impl pallet_transaction_payment::Trait for Test {
impl Trait for Test {
type Balance = u64;
type DustRemoval = ();
type Event = ();
type Event = Event;
type ExistentialDeposit = ExistentialDeposit;
type AccountStore = StorageMapShim<
super::Account<Test>,
@@ -146,8 +157,45 @@ impl ExtBuilder {
vec![]
},
}.assimilate_storage(&mut t).unwrap();
t.into()
let mut ext = sp_io::TestExternalities::new(t);
ext.execute_with(|| System::set_block_number(1));
ext
}
}
decl_tests!{ Test, ExtBuilder, EXISTENTIAL_DEPOSIT }
#[test]
fn emit_events_with_no_existential_deposit_suicide_with_dust() {
<ExtBuilder>::default()
.existential_deposit(0)
.build()
.execute_with(|| {
assert_ok!(Balances::set_balance(RawOrigin::Root.into(), 1, 100, 0));
assert_eq!(
events(),
[
Event::system(system::RawEvent::NewAccount(1)),
Event::balances(RawEvent::Endowed(1, 100)),
Event::balances(RawEvent::BalanceSet(1, 100, 0)),
]
);
let _ = Balances::slash(&1, 99);
// no events
assert_eq!(events(), []);
assert_ok!(System::suicide(Origin::signed(1)));
assert_eq!(
events(),
[
Event::balances(RawEvent::DustLost(1, 1)),
Event::system(system::RawEvent::KilledAccount(1))
]
);
});
}
+7 -4
View File
@@ -105,20 +105,23 @@ impl<
fn get(k: &K) -> T { S::get(k) }
fn is_explicit(k: &K) -> bool { S::contains_key(k) }
fn insert(k: &K, t: T) {
let existed = S::contains_key(&k);
S::insert(k, t);
if !S::contains_key(&k) {
if !existed {
Created::happened(k);
}
}
fn remove(k: &K) {
if S::contains_key(&k) {
let existed = S::contains_key(&k);
S::remove(k);
if existed {
Removed::happened(&k);
}
S::remove(k);
}
fn mutate<R>(k: &K, f: impl FnOnce(&mut T) -> R) -> R {
let existed = S::contains_key(&k);
let r = S::mutate(k, f);
if !S::contains_key(&k) {
if !existed {
Created::happened(k);
}
r
+12 -3
View File
@@ -742,12 +742,12 @@ decl_module! {
/// No DB Read or Write operations because caller is already in overlay
/// # </weight>
#[weight = (10_000_000, DispatchClass::Operational)]
fn suicide(origin) {
pub fn suicide(origin) {
let who = ensure_signed(origin)?;
let account = Account::<T>::get(&who);
ensure!(account.refcount == 0, Error::<T>::NonZeroRefCount);
ensure!(account.data == T::AccountData::default(), Error::<T>::NonDefaultComposite);
Account::<T>::remove(who);
Self::kill_account(&who);
}
}
}
@@ -1131,6 +1131,15 @@ impl<T: Trait> Module<T> {
AllExtrinsicsLen::put(len as u32);
}
/// Reset events. Can be used as an alternative to
/// `initialize` for tests that don't need to bother with the other environment entries.
#[cfg(any(feature = "std", feature = "runtime-benchmarks", test))]
pub fn reset_events() {
<Events<T>>::kill();
EventCount::kill();
<EventTopics<T>>::remove_all();
}
/// Return the chain's current runtime version.
pub fn runtime_version() -> RuntimeVersion { T::Version::get() }
@@ -1222,8 +1231,8 @@ impl<T: Trait> Module<T> {
"WARNING: Referenced account deleted. This is probably a bug."
);
}
Module::<T>::on_killed_account(who.clone());
}
Module::<T>::on_killed_account(who.clone());
}
/// Determine whether or not it is possible to update the code.