mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-13 09:21:05 +00:00
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:
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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))
|
||||
]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user