Composite accounts (#4820)

* Basic account composition.

* Add try_mutate_exists

* De-duplicate

* Refactor away the UpdateBalanceOutcome

* Expunge final UpdateBalanceOutcome refs

* Refactor transfer

* Refactor reservable currency stuff.

* Test with the alternative setup.

* Fixes

* Test with both setups.

* Fixes

* Fix

* Fix macros

* Make indices opt-in

* Remove CreationFee, and make indices opt-in.

* Fix construct_runtime

* Fix last few bits

* Fix tests

* Update trait impls

* Don't hardcode the system event

* Make tests build and fix some stuff.

* Pointlessly bump runtime version

* Fix benchmark

* Another fix

* Whitespace

* Make indices module economically safe

* Migrations for indices.

* Fix

* Whilespace

* Trim defunct migrations

* Remove unused storage item

* More contains_key fixes

* Docs.

* Bump runtime

* Remove unneeded code

* Fix test

* Fix test

* Update frame/balances/src/lib.rs

Co-Authored-By: Shawn Tabrizi <shawntabrizi@gmail.com>

* Fix ED logic

* Repatriate reserved logic

* Typo

* Fix typo

* Update frame/system/src/lib.rs

Co-Authored-By: Shawn Tabrizi <shawntabrizi@gmail.com>

* Update frame/system/src/lib.rs

Co-Authored-By: Shawn Tabrizi <shawntabrizi@gmail.com>

* Last few fixes

* Another fix

* Build fix

Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>
Co-authored-by: Jaco Greeff <jacogr@gmail.com>
Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>
This commit is contained in:
Gavin Wood
2020-02-14 00:47:51 +00:00
committed by GitHub
parent d3fa8c91af
commit 5b7512e2e4
79 changed files with 2459 additions and 2100 deletions
+3
View File
@@ -293,6 +293,9 @@ mod tests {
type MaximumBlockLength = MaximumBlockLength;
type Version = ();
type ModuleToIndex = ();
type AccountData = ();
type OnNewAccount = ();
type OnReapAccount = ();
}
impl Trait for Test {
type Event = ();
+3
View File
@@ -61,6 +61,9 @@ impl frame_system::Trait for Test {
type MaximumBlockLength = MaximumBlockLength;
type Version = ();
type ModuleToIndex = ();
type AccountData = ();
type OnNewAccount = ();
type OnReapAccount = ();
}
impl pallet_timestamp::Trait for Test {
@@ -157,6 +157,9 @@ mod tests {
type MaximumBlockLength = MaximumBlockLength;
type Version = ();
type ModuleToIndex = ();
type AccountData = ();
type OnNewAccount = ();
type OnReapAccount = ();
}
impl_outer_origin! {
+3
View File
@@ -431,6 +431,9 @@ mod tests {
type MaximumBlockLength = MaximumBlockLength;
type Version = ();
type ModuleToIndex = ();
type AccountData = ();
type OnNewAccount = ();
type OnReapAccount = ();
}
parameter_types! {
+3
View File
@@ -66,6 +66,9 @@ impl frame_system::Trait for Test {
type AvailableBlockRatio = AvailableBlockRatio;
type MaximumBlockLength = MaximumBlockLength;
type ModuleToIndex = ();
type AccountData = ();
type OnNewAccount = ();
type OnReapAccount = ();
}
impl_opaque_keys! {
+7 -8
View File
@@ -50,7 +50,7 @@ impl<T: Trait> BenchmarkingSetup<T, crate::Call<T>, RawOrigin<T::AccountId>> for
{
// Constants
let ed = T::ExistentialDeposit::get();
// Select an account
let u = components.iter().find(|&c| c.0 == BenchmarkParameter::U).unwrap().1;
let user = account::<T>("user", u);
@@ -58,8 +58,7 @@ impl<T: Trait> BenchmarkingSetup<T, crate::Call<T>, RawOrigin<T::AccountId>> for
// Give some multiple of the existential deposit + creation fee + transfer fee
let e = components.iter().find(|&c| c.0 == BenchmarkParameter::E).unwrap().1;
let mut balance = ed.saturating_mul(e.into());
balance += T::CreationFee::get();
let balance = ed.saturating_mul(e.into());
let _ = <Balances<T> as Currency<_>>::make_free_balance_be(&user, balance);
// Transfer `e - 1` existential deposits + 1 unit, which guarantees to create one account, and reap this user.
@@ -90,7 +89,7 @@ impl<T: Trait> BenchmarkingSetup<T, crate::Call<T>, RawOrigin<T::AccountId>> for
{
// Constants
let ed = T::ExistentialDeposit::get();
// Select a sender
let u = components.iter().find(|&c| c.0 == BenchmarkParameter::U).unwrap().1;
let user = account::<T>("user", u);
@@ -135,7 +134,7 @@ impl<T: Trait> BenchmarkingSetup<T, crate::Call<T>, RawOrigin<T::AccountId>> for
{
// Constants
let ed = T::ExistentialDeposit::get();
// Select a sender
let u = components.iter().find(|&c| c.0 == BenchmarkParameter::U).unwrap().1;
let user = account::<T>("user", u);
@@ -176,7 +175,7 @@ impl<T: Trait> BenchmarkingSetup<T, crate::Call<T>, RawOrigin<T::AccountId>> for
{
// Constants
let ed = T::ExistentialDeposit::get();
// Select a sender
let u = components.iter().find(|&c| c.0 == BenchmarkParameter::U).unwrap().1;
let user = account::<T>("user", u);
@@ -208,7 +207,7 @@ impl<T: Trait> BenchmarkingSetup<T, crate::Call<T>, RawOrigin<T::AccountId>> for
{
// Constants
let ed = T::ExistentialDeposit::get();
// Select a sender
let u = components.iter().find(|&c| c.0 == BenchmarkParameter::U).unwrap().1;
let user = account::<T>("user", u);
@@ -275,7 +274,7 @@ impl<T: Trait> Benchmarking<BenchmarkResults> for Module<T> {
sp_io::benchmarking::commit_db();
sp_io::benchmarking::wipe_db();
let components = <SelectedBenchmark as BenchmarkingSetup<T, crate::Call<T>, RawOrigin<T::AccountId>>>::components(&selected_benchmark);
let components = <SelectedBenchmark as BenchmarkingSetup<T, crate::Call<T>, RawOrigin<T::AccountId>>>::components(&selected_benchmark);
// results go here
let mut results: Vec<BenchmarkResults> = Vec::new();
// Select the component we will be benchmarking. Each component will be benchmarked.
+271 -303
View File
@@ -149,21 +149,24 @@
#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(test)]
mod mock;
mod tests_local;
#[cfg(test)]
mod tests_composite;
#[cfg(test)]
#[macro_use]
mod tests;
mod migration;
mod benchmarking;
use sp_std::prelude::*;
use sp_std::{cmp, result, mem, fmt::Debug, ops::BitOr};
use sp_std::{cmp, result, mem, fmt::Debug, ops::BitOr, convert::Infallible};
use codec::{Codec, Encode, Decode};
use frame_support::{
StorageValue, Parameter, decl_event, decl_storage, decl_module, decl_error, ensure,
weights::SimpleDispatchInfo, traits::{
UpdateBalanceOutcome, Currency, OnReapAccount, OnUnbalanced, TryDrop,
weights::SimpleDispatchInfo, traits::{
Currency, OnReapAccount, OnUnbalanced, TryDrop, StoredMap,
WithdrawReason, WithdrawReasons, LockIdentifier, LockableCurrency, ExistenceRequirement,
Imbalance, SignedImbalance, ReservableCurrency, Get, ExistenceRequirement::KeepAlive
Imbalance, SignedImbalance, ReservableCurrency, Get, ExistenceRequirement::KeepAlive,
ExistenceRequirement::AllowDeath, IsDeadAccount, BalanceStatus as Status
}
};
use sp_runtime::{
@@ -173,8 +176,10 @@ use sp_runtime::{
MaybeSerializeDeserialize, Saturating, Bounded,
},
};
use frame_system::{self as system, IsDeadAccount, OnNewAccount, ensure_signed, ensure_root};
use migration::{get_storage_value, put_storage_value, StorageIterator};
use frame_system::{self as system, ensure_signed, ensure_root};
use frame_support::storage::migration::{
get_storage_value, take_storage_value, put_storage_value, StorageIterator
};
pub use self::imbalances::{PositiveImbalance, NegativeImbalance};
@@ -183,21 +188,11 @@ pub trait Subtrait<I: Instance = DefaultInstance>: frame_system::Trait {
type Balance: Parameter + Member + AtLeast32Bit + Codec + Default + Copy +
MaybeSerializeDeserialize + Debug;
/// A function that is invoked when the free-balance and the reserved-balance has fallen below
/// the existential deposit and both have been reduced to zero.
///
/// All resources should be cleaned up all resources associated with the given account.
type OnReapAccount: OnReapAccount<Self::AccountId>;
/// Handler for when a new account is created.
type OnNewAccount: OnNewAccount<Self::AccountId>;
/// The minimum amount required to keep an account open.
type ExistentialDeposit: Get<Self::Balance>;
/// The fee required to create an account. If you're doing significant stuff with `OnNewAccount`
/// then you'll probably want to make this non-zero.
type CreationFee: Get<Self::Balance>;
/// The means of storing the balances of an account.
type AccountStore: StoredMap<Self::AccountId, AccountData<Self::Balance>>;
}
pub trait Trait<I: Instance = DefaultInstance>: frame_system::Trait {
@@ -205,19 +200,6 @@ pub trait Trait<I: Instance = DefaultInstance>: frame_system::Trait {
type Balance: Parameter + Member + AtLeast32Bit + Codec + Default + Copy +
MaybeSerializeDeserialize + Debug;
/// A function that is invoked when the free-balance and the reserved-balance has fallen below
/// the existential deposit and both have been reduced to zero.
///
/// All resources should be cleaned up all resources associated with the given account.
type OnReapAccount: OnReapAccount<Self::AccountId>;
/// Handler for when a new account is created.
type OnNewAccount: OnNewAccount<Self::AccountId>;
/// Handler for the unbalanced reduction when taking fees associated with balance
/// transfer (which may also include account creation).
type TransferPayment: OnUnbalanced<NegativeImbalance<Self, I>>;
/// Handler for the unbalanced reduction when removing a dust account.
type DustRemoval: OnUnbalanced<NegativeImbalance<Self, I>>;
@@ -227,16 +209,14 @@ pub trait Trait<I: Instance = DefaultInstance>: frame_system::Trait {
/// The minimum amount required to keep an account open.
type ExistentialDeposit: Get<Self::Balance>;
/// The fee required to create an account.
type CreationFee: Get<Self::Balance>;
/// The means of storing the balances of an account.
type AccountStore: StoredMap<Self::AccountId, AccountData<Self::Balance>>;
}
impl<T: Trait<I>, I: Instance> Subtrait<I> for T {
type Balance = T::Balance;
type OnReapAccount = T::OnReapAccount;
type OnNewAccount = T::OnNewAccount;
type ExistentialDeposit = T::ExistentialDeposit;
type CreationFee = T::CreationFee;
type AccountStore = T::AccountStore;
}
decl_event!(
@@ -244,12 +224,13 @@ decl_event!(
<T as frame_system::Trait>::AccountId,
<T as Trait<I>>::Balance
{
/// A new account was created.
NewAccount(AccountId, Balance),
/// An account was reaped.
ReapedAccount(AccountId, Balance),
/// Transfer succeeded (from, to, value, fees).
Transfer(AccountId, AccountId, Balance, Balance),
/// An account was created with some free balance.
Endowed(AccountId, Balance),
/// An account was removed whose balance was non-zero but below ExistentialDeposit,
/// resulting in an outright loss.
DustLost(AccountId, Balance),
/// Transfer succeeded (from, to, value).
Transfer(AccountId, AccountId, Balance),
/// A balance was set by root (who, free, reserved).
BalanceSet(AccountId, Balance, Balance),
/// Some amount was deposited (e.g. for transaction fees).
@@ -376,11 +357,9 @@ decl_storage! {
///
/// 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.
pub Account get(fn account)
build(|config: &GenesisConfig<T, I>| config.balances.iter()
.map(|&(ref who, free)| (who.clone(), AccountData { free, .. Default::default() }))
.collect::<Vec<_>>()
): map hasher(blake2_256) T::AccountId => AccountData<T::Balance>;
///
/// NOTE: This is only used in the case that this module is used to store balances.
pub Account: map hasher(blake2_256) T::AccountId => AccountData<T::Balance>;
/// Any liquidity locks on some account balances.
/// NOTE: Should only be accessed when setting, changing and freeing a lock.
@@ -405,6 +384,9 @@ decl_storage! {
"the balance of any account should always be more than existential deposit.",
)
}
for &(ref who, free) in config.balances.iter() {
T::AccountStore::insert(who, AccountData { free, .. Default::default() });
}
});
}
}
@@ -416,9 +398,6 @@ decl_module! {
/// The minimum amount required to keep an account open.
const ExistentialDeposit: T::Balance = T::ExistentialDeposit::get();
/// The fee required to create an account.
const CreationFee: T::Balance = T::CreationFee::get();
fn deposit_event() = default;
/// Transfer some liquid free balance to another account.
@@ -484,24 +463,25 @@ decl_module! {
let new_free = if wipeout { Zero::zero() } else { new_free };
let new_reserved = if wipeout { Zero::zero() } else { new_reserved };
let old_account = Account::<T, I>::get(&who);
let (free, reserved) = Self::mutate_account(&who, |account| {
if new_free > account.free {
mem::drop(PositiveImbalance::<T, I>::new(new_free - account.free));
} else if new_free < account.free {
mem::drop(NegativeImbalance::<T, I>::new(account.free - new_free));
}
if new_free > old_account.free {
mem::drop(PositiveImbalance::<T, I>::new(new_free - old_account.free));
} else if new_free < old_account.free {
mem::drop(NegativeImbalance::<T, I>::new(old_account.free - new_free));
}
if new_reserved > account.reserved {
mem::drop(PositiveImbalance::<T, I>::new(new_reserved - account.reserved));
} else if new_reserved < account.reserved {
mem::drop(NegativeImbalance::<T, I>::new(account.reserved - new_reserved));
}
if new_reserved > old_account.reserved {
mem::drop(PositiveImbalance::<T, I>::new(new_reserved - old_account.reserved));
} else if new_reserved < old_account.reserved {
mem::drop(NegativeImbalance::<T, I>::new(old_account.reserved - new_reserved));
}
account.free = new_free;
account.reserved = new_reserved;
let account = AccountData { free: new_free, reserved: new_reserved, ..old_account };
Self::set_account(&who, &account, &old_account);
Self::deposit_event(RawEvent::BalanceSet(who, account.free, account.reserved));
(account.free, account.reserved)
});
Self::deposit_event(RawEvent::BalanceSet(who, free, reserved));
}
/// Exactly as `transfer`, except the origin must be root and the source account may be
@@ -626,87 +606,110 @@ impl<T: Trait<I>, I: Instance> Module<T, I> {
put_storage_value(b"Balances", b"Locks", &hash, locks);
put_storage_value(b"Balances", b"Account", &hash, account);
}
for (hash, balances) in StorageIterator::<AccountData<T::Balance>>::new(b"Balances", b"Account").drain() {
let nonce = take_storage_value::<T::Index>(b"System", b"AccountNonce", &hash).unwrap_or_default();
put_storage_value(b"System", b"Account", &hash, (nonce, balances));
}
}
/// Get the free balance of an account.
pub fn free_balance(who: impl sp_std::borrow::Borrow<T::AccountId>) -> T::Balance {
Account::<T, I>::get(who.borrow()).free
Self::account(who.borrow()).free
}
/// Get the balance of an account that can be used for transfers, reservations, or any other
/// non-locking, non-transaction-fee activity. Will be at most `free_balance`.
pub fn usable_balance(who: impl sp_std::borrow::Borrow<T::AccountId>) -> T::Balance {
Account::<T, I>::get(who.borrow()).usable(Reasons::Misc)
Self::account(who.borrow()).usable(Reasons::Misc)
}
/// Get the balance of an account that can be used for paying transaction fees (not tipping,
/// or any other kind of fees, though). Will be at most `free_balance`.
pub fn usable_balance_for_fees(who: impl sp_std::borrow::Borrow<T::AccountId>) -> T::Balance {
Account::<T, I>::get(who.borrow()).usable(Reasons::Fee)
Self::account(who.borrow()).usable(Reasons::Fee)
}
/// Get the reserved balance of an account.
pub fn reserved_balance(who: impl sp_std::borrow::Borrow<T::AccountId>) -> T::Balance {
Account::<T, I>::get(who.borrow()).reserved
Self::account(who.borrow()).reserved
}
/// Set both the free and reserved balance of an account to some new value. Will enforce
/// Get both the free and reserved balances of an account.
fn account(who: &T::AccountId) -> AccountData<T::Balance> {
T::AccountStore::get(&who)
}
/// Places the `free` and `reserved` parts of `new` into `account`. Also does any steps needed
/// after mutating an account. This includes DustRemoval unbalancing, in the case than the `new`
/// account's total balance is non-zero but below ED.
///
/// Returns the final free balance, iff the account was previously of total balance zero, known
/// as its "endowment".
fn post_mutation(
who: &T::AccountId,
new: AccountData<T::Balance>,
) -> Option<AccountData<T::Balance>> {
let total = new.total();
if total < T::ExistentialDeposit::get() {
if !total.is_zero() {
T::DustRemoval::on_unbalanced(NegativeImbalance::new(total));
Self::deposit_event(RawEvent::DustLost(who.clone(), total));
}
None
} else {
Some(new)
}
}
/// Mutate an account to some new value, or delete it entirely with `None`. Will enforce
/// `ExistentialDeposit` law, annulling the account as needed.
///
/// Will return `AccountKilled` if either reserved or free are too low.
///
/// NOTE: This assumes that `account` is the same as `Self::account(who)` except for altered
/// values of `free` and `balance`.
///
/// NOTE: Doesn't do any preparatory work for creating a new account, so should only be used
/// when it is known that the account already exists.
///
/// NOTE: LOW-LEVEL: This will not attempt to maintain total issuance. It is expected that
/// the caller will do this.
fn set_account(
fn mutate_account<R>(
who: &T::AccountId,
account: &AccountData<T::Balance>,
old: &AccountData<T::Balance>,
) -> UpdateBalanceOutcome {
let total = account.free + account.reserved;
if total < T::ExistentialDeposit::get() {
T::DustRemoval::on_unbalanced(NegativeImbalance::new(total));
if !old.total().is_zero() {
Self::reap_account(who, total);
UpdateBalanceOutcome::AccountKilled
} else {
UpdateBalanceOutcome::StillDead
}
} else {
if old.total().is_zero() {
Self::about_to_create_account(who, account.free);
}
Account::<T, I>::insert(who, account);
UpdateBalanceOutcome::Updated
}
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")
}
/// Register a new account (with existential balance).
/// Mutate an account to some new value, or delete it entirely with `None`. Will enforce
/// `ExistentialDeposit` law, annulling the account as needed. This will do nothing if the
/// result of `f` is an `Err`.
///
/// This just calls appropriate hooks. It doesn't (necessarily) make any state changes.
fn about_to_create_account(who: &T::AccountId, balance: T::Balance) {
T::OnNewAccount::on_new_account(&who);
Self::deposit_event(RawEvent::NewAccount(who.clone(), balance.clone()));
}
/// Unregister an account.
/// NOTE: Doesn't do any preparatory work for creating a new account, so should only be used
/// when it is known that the account already exists.
///
/// This just removes the nonce and leaves an event.
fn reap_account(who: &T::AccountId, dust: T::Balance) {
Locks::<T, I>::remove(who);
Account::<T, I>::remove(who);
T::OnReapAccount::on_reap_account(who);
Self::deposit_event(RawEvent::ReapedAccount(who.clone(), dust));
/// 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>(
who: &T::AccountId,
f: impl FnOnce(&mut AccountData<T::Balance>) -> Result<R, E>
) -> Result<R, E> {
T::AccountStore::try_mutate_exists(who, |maybe_account| {
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 };
*maybe_account = Self::post_mutation(who, account);
(maybe_endowed, result)
})
}).map(|(maybe_endowed, result)| {
if let Some(endowed) = maybe_endowed {
Self::deposit_event(RawEvent::Endowed(who.clone(), endowed));
}
result
})
}
/// Update the account entry for `who`, given the locks.
fn update_locks(who: &T::AccountId, locks: &[BalanceLock<T::Balance>]) {
Account::<T, I>::mutate(who, |b| {
Self::mutate_account(who, |b| {
b.misc_frozen = Zero::zero();
b.fee_frozen = Zero::zero();
for l in locks.iter() {
@@ -722,6 +725,13 @@ impl<T: Trait<I>, I: Instance> Module<T, I> {
}
}
impl<T: Trait<I>, I: Instance> OnReapAccount<T::AccountId> for Module<T, I> {
fn on_reap_account(who: &T::AccountId) {
Locks::<T, I>::remove(who);
Account::<T, I>::remove(who);
}
}
// wrapping these imbalances in a private module is necessary to ensure absolute privacy
// of the inner member.
mod imbalances {
@@ -885,9 +895,8 @@ mod imbalances {
// its type declaration).
// This works as long as `increase_total_issuance_by` doesn't use the Imbalance
// types (basically for charging fees).
// This should eventually be refactored so that the three type items that do
// depend on the Imbalance type (TransferPayment, DustRemoval)
// are placed in their own SRML module.
// This should eventually be refactored so that the type item that
// depends on the Imbalance type (DustRemoval) is placed in its own SRML module.
struct ElevatedTrait<T: Subtrait<I>, I: Instance>(T, I);
impl<T: Subtrait<I>, I: Instance> Clone for ElevatedTrait<T, I> {
fn clone(&self) -> Self { unimplemented!() }
@@ -913,16 +922,16 @@ impl<T: Subtrait<I>, I: Instance> frame_system::Trait for ElevatedTrait<T, I> {
type AvailableBlockRatio = T::AvailableBlockRatio;
type Version = T::Version;
type ModuleToIndex = T::ModuleToIndex;
type OnNewAccount = T::OnNewAccount;
type OnReapAccount = T::OnReapAccount;
type AccountData = T::AccountData;
}
impl<T: Subtrait<I>, I: Instance> Trait<I> for ElevatedTrait<T, I> {
type Balance = T::Balance;
type OnReapAccount = T::OnReapAccount;
type OnNewAccount = T::OnNewAccount;
type Event = ();
type TransferPayment = ();
type DustRemoval = ();
type ExistentialDeposit = T::ExistentialDeposit;
type CreationFee = T::CreationFee;
type AccountStore = T::AccountStore;
}
impl<T: Trait<I>, I: Instance> Currency<T::AccountId> for Module<T, I> where
@@ -950,10 +959,6 @@ impl<T: Trait<I>, I: Instance> Currency<T::AccountId> for Module<T, I> where
T::ExistentialDeposit::get()
}
fn free_balance(who: &T::AccountId) -> Self::Balance {
Account::<T, I>::get(who).free
}
// Burn funds from the total issuance, returning a positive imbalance for the amount burned.
// Is a no-op if amount to be burned is zero.
fn burn(mut amount: Self::Balance) -> Self::PositiveImbalance {
@@ -981,6 +986,10 @@ impl<T: Trait<I>, I: Instance> Currency<T::AccountId> for Module<T, I> where
NegativeImbalance::new(amount)
}
fn free_balance(who: &T::AccountId) -> Self::Balance {
Self::account(who).free
}
// Ensure that an account can withdraw from their free balance given any existing withdrawal
// restrictions like locks and vesting balance.
// Is a no-op if amount to be withdrawn is zero.
@@ -996,7 +1005,7 @@ impl<T: Trait<I>, I: Instance> Currency<T::AccountId> for Module<T, I> where
new_balance: T::Balance,
) -> DispatchResult {
if amount.is_zero() { return Ok(()) }
let min_balance = Account::<T, I>::get(who).frozen(reasons.into());
let min_balance = Self::account(who).frozen(reasons.into());
ensure!(new_balance >= min_balance, Error::<T, I>::LiquidityRestrictions);
Ok(())
}
@@ -1011,80 +1020,38 @@ impl<T: Trait<I>, I: Instance> Currency<T::AccountId> for Module<T, I> where
) -> DispatchResult {
if value.is_zero() || transactor == dest { return Ok(()) }
let old_from_account = Self::account(transactor);
let mut from_account = old_from_account.clone();
let old_to_account = Self::account(dest);
let mut to_account = old_to_account.clone();
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)?;
let would_create = to_account.total().is_zero();
let fee = if would_create { T::CreationFee::get() } else { Zero::zero() };
let liability = value.checked_add(&fee).ok_or(Error::<T, I>::Overflow)?;
// NOTE: total stake being stored in the same type means that this could never overflow
// but better to be safe than sorry.
to_account.free = to_account.free.checked_add(&value).ok_or(Error::<T, I>::Overflow)?;
from_account.free = from_account.free.checked_sub(&liability)
.ok_or(Error::<T, I>::InsufficientBalance)?;
let ed = T::ExistentialDeposit::get();
ensure!(to_account.total() >= ed, Error::<T, I>::ExistentialDeposit);
// NOTE: total stake being stored in the same type means that this could never overflow
// but better to be safe than sorry.
to_account.free = to_account.free.checked_add(&value).ok_or(Error::<T, I>::Overflow)?;
Self::ensure_can_withdraw(
transactor,
value,
WithdrawReason::Transfer.into(),
from_account.free,
)?;
let ed = T::ExistentialDeposit::get();
ensure!(to_account.free >= ed, Error::<T, I>::ExistentialDeposit);
let allow_death = existence_requirement == ExistenceRequirement::AllowDeath;
ensure!(allow_death || from_account.free >= ed, Error::<T, I>::KeepAlive);
Self::ensure_can_withdraw(
transactor,
value,
WithdrawReason::Transfer.into(),
from_account.free,
)?;
let allow_death = existence_requirement == ExistenceRequirement::AllowDeath;
ensure!(allow_death || from_account.free >= ed, Error::<T, I>::KeepAlive);
Self::set_account(transactor, &from_account, &old_from_account);
// Take action on the set_account call.
// This will emit events that _resulted_ from the transfer.
Self::set_account(dest, &to_account, &old_to_account);
Ok(())
})
})?;
// Emit transfer event.
Self::deposit_event(RawEvent::Transfer(transactor.clone(), dest.clone(), value, fee));
T::TransferPayment::on_unbalanced(NegativeImbalance::new(fee));
Self::deposit_event(RawEvent::Transfer(transactor.clone(), dest.clone(), value));
Ok(())
}
// Withdraw some free balance from an account, respecting existence requirements.
// Is a no-op if value to be withdrawn is zero.
fn withdraw(
who: &T::AccountId,
value: Self::Balance,
reasons: WithdrawReasons,
liveness: ExistenceRequirement,
) -> result::Result<Self::NegativeImbalance, DispatchError> {
if value.is_zero() { return Ok(NegativeImbalance::zero()); }
let old_account = Self::account(who);
let mut account = old_account.clone();
if let Some(new_free_account) = account.free.checked_sub(&value) {
// if we need to keep the account alive...
if liveness == ExistenceRequirement::KeepAlive
// ...and it would be dead afterwards...
&& new_free_account < T::ExistentialDeposit::get()
// ...yet is was alive before
&& account.free >= T::ExistentialDeposit::get()
{
Err(Error::<T, I>::KeepAlive)?
}
Self::ensure_can_withdraw(who, value, reasons, new_free_account)?;
account.free = new_free_account;
Self::set_account(who, &account, &old_account);
Ok(NegativeImbalance::new(value))
} else {
Err(Error::<T, I>::InsufficientBalance)?
}
}
/// Slash a target account `who`, returning the negative imbalance created and any left over
/// amount that could not be slashed.
///
@@ -1100,22 +1067,19 @@ 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()) }
let old_account = Self::account(who);
let mut account = old_account.clone();
Self::mutate_account(who, |account| {
let free_slash = cmp::min(account.free, value);
account.free -= free_slash;
let free_slash = cmp::min(account.free, value);
account.free -= free_slash;
let remaining_slash = value - free_slash;
let result = 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())
};
Self::set_account(who, &account, &old_account);
result
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())
}
})
}
/// Deposit some `value` into the free balance of an existing target account `who`.
@@ -1124,16 +1088,14 @@ impl<T: Trait<I>, I: Instance> Currency<T::AccountId> for Module<T, I> where
fn deposit_into_existing(
who: &T::AccountId,
value: Self::Balance
) -> result::Result<Self::PositiveImbalance, DispatchError> {
) -> Result<Self::PositiveImbalance, DispatchError> {
if value.is_zero() { return Ok(PositiveImbalance::zero()) }
let old_account = Self::account(who);
let mut account = old_account.clone();
ensure!(!account.total().is_zero(), Error::<T, I>::DeadAccount);
account.free = account.free.checked_add(&value).ok_or(Error::<T, I>::Overflow)?;
Self::set_account(who, &account, &old_account);
Ok(PositiveImbalance::new(value))
Self::try_mutate_account(who, |account| -> Result<Self::PositiveImbalance, DispatchError> {
ensure!(!account.total().is_zero(), Error::<T, I>::DeadAccount);
account.free = account.free.checked_add(&value).ok_or(Error::<T, I>::Overflow)?;
Ok(PositiveImbalance::new(value))
})
}
/// Deposit some `value` into the free balance of `who`, possibly creating a new account.
@@ -1148,34 +1110,58 @@ impl<T: Trait<I>, I: Instance> Currency<T::AccountId> for Module<T, I> where
) -> Self::PositiveImbalance {
if value.is_zero() { return Self::PositiveImbalance::zero() }
let old_account = Self::account(who);
let mut account = old_account.clone();
let ed = T::ExistentialDeposit::get();
Self::try_mutate_account(who, |account| -> 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());
// bail if not yet created and this operation wouldn't be enough to create it.
if value < ed && account.total().is_zero() { return Self::PositiveImbalance::zero() }
// 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(Self::PositiveImbalance::zero())?;
// defensive only: overflow should never happen, however in case it does, then this
// operation is a no-op.
account.free = match account.free.checked_add(&value) {
Some(f) => f,
None => return Self::PositiveImbalance::zero(),
};
Ok(PositiveImbalance::new(value))
}).unwrap_or_else(|x| x)
}
Self::set_account(who, &account, &old_account);
/// Withdraw some free balance from an account, respecting existence requirements.
///
/// Is a no-op if value to be withdrawn is zero.
fn withdraw(
who: &T::AccountId,
value: Self::Balance,
reasons: WithdrawReasons,
liveness: ExistenceRequirement,
) -> result::Result<Self::NegativeImbalance, DispatchError> {
if value.is_zero() { return Ok(NegativeImbalance::zero()); }
PositiveImbalance::new(value)
Self::try_mutate_account(who, |account|
-> Result<Self::NegativeImbalance, DispatchError>
{
let new_free_account = account.free.checked_sub(&value)
.ok_or(Error::<T, I>::InsufficientBalance)?;
// bail if we need to keep the account alive and this would kill it.
let ed = T::ExistentialDeposit::get();
let would_be_dead = new_free_account + account.reserved < ed;
let would_kill = would_be_dead && account.free + account.reserved >= ed;
ensure!(liveness == AllowDeath || !would_kill, Error::<T, I>::KeepAlive);
Self::ensure_can_withdraw(who, value, reasons, new_free_account)?;
account.free = new_free_account;
Ok(NegativeImbalance::new(value))
})
}
/// Force the new free balance of a target account `who` to some new value `balance`.
fn make_free_balance_be(who: &T::AccountId, value: Self::Balance) -> (
SignedImbalance<Self::Balance, Self::PositiveImbalance>,
UpdateBalanceOutcome
) {
let old_account = Self::account(who);
let mut account = old_account.clone();
if value < T::ExistentialDeposit::get() && account.free.is_zero() {
fn make_free_balance_be(who: &T::AccountId, value: Self::Balance)
-> SignedImbalance<Self::Balance, Self::PositiveImbalance>
{
Self::try_mutate_account(who, |account|
-> Result<SignedImbalance<Self::Balance, Self::PositiveImbalance>, ()>
{
let ed = T::ExistentialDeposit::get();
// 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
@@ -1183,24 +1169,16 @@ 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.
return (
SignedImbalance::Positive(Self::PositiveImbalance::zero()),
UpdateBalanceOutcome::AccountKilled,
)
}
let imbalance = if account.free <= value {
SignedImbalance::Positive(PositiveImbalance::new(value - account.free))
} else {
SignedImbalance::Negative(NegativeImbalance::new(account.free - value))
};
account.free = value;
ensure!(value + account.reserved >= ed || !account.total().is_zero(), ());
// If the balance is too low, then the account is reaped.
// Free balance can never be less than ED. If that happens, it gets reduced to zero
// and the account information relevant to this subsystem is deleted (i.e. the
// account is reaped).
let outcome = Self::set_account(who, &account, &old_account);
(imbalance, outcome)
let imbalance = if account.free <= value {
SignedImbalance::Positive(PositiveImbalance::new(value - account.free))
} else {
SignedImbalance::Negative(NegativeImbalance::new(account.free - value))
};
account.free = value;
Ok(imbalance)
}).unwrap_or(SignedImbalance::Positive(Self::PositiveImbalance::zero()))
}
}
@@ -1226,18 +1204,14 @@ impl<T: Trait<I>, I: Instance> ReservableCurrency<T::AccountId> for Module<T, I>
/// Move `value` from the free balance from `who` to their reserved balance.
///
/// Is a no-op if value to be reserved is zero.
fn reserve(who: &T::AccountId, value: Self::Balance) -> result::Result<(), DispatchError> {
fn reserve(who: &T::AccountId, value: Self::Balance) -> DispatchResult {
if value.is_zero() { return Ok(()) }
let old_account = Self::account(who);
let mut account = old_account.clone();
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)?;
Self::set_account(who, &account, &old_account);
Ok(())
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)
})
}
/// Unreserve some funds, returning any amount that was unable to be unreserved.
@@ -1246,18 +1220,14 @@ 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 old_account = Self::account(who);
let mut account = old_account.clone();
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 datatype.
account.free = account.free.saturating_add(actual);
Self::set_account(who, &account, &old_account);
value - actual
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 datatype.
account.free = account.free.saturating_add(actual);
value - actual
})
}
/// Slash from reserved balance, returning the negative imbalance created,
@@ -1270,47 +1240,46 @@ 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()) }
let old_account = Self::account(who);
let mut account = old_account.clone();
// 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;
Self::set_account(who, &account, &old_account);
(NegativeImbalance::new(actual), value - actual)
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)
})
}
/// Move the reserved balance of one account into the free balance of another.
/// Move the reserved balance of one account into the balance of another, according to `status`.
///
/// Is a no-op if the value to be moved is zero.
/// Is a no-op if:
/// - the value to be moved is zero; or
/// - the `slashed` id equal to `beneficiary` and the `status` is `Reserved`.
fn repatriate_reserved(
slashed: &T::AccountId,
beneficiary: &T::AccountId,
value: Self::Balance,
) -> result::Result<Self::Balance, DispatchError> {
if value.is_zero() { return Ok (Zero::zero()) }
status: Status,
) -> Result<Self::Balance, DispatchError> {
if value.is_zero() { return Ok(Zero::zero()) }
if slashed == beneficiary {
return Ok(Self::unreserve(slashed, value));
return match status {
Status::Free => Ok(Self::unreserve(slashed, value)),
Status::Reserved => Ok(value.saturating_sub(Self::reserved_balance(slashed))),
};
}
let old_to_account = Self::account(beneficiary);
let mut to_account = old_to_account.clone();
ensure!(!to_account.total().is_zero(), Error::<T, I>::DeadAccount);
let old_from_account = Self::account(slashed);
let mut from_account = old_from_account.clone();
let actual = cmp::min(from_account.reserved, value);
to_account.free = to_account.free.checked_add(&actual).ok_or(Error::<T, I>::Overflow)?;
from_account.reserved -= actual;
Self::set_account(slashed, &from_account, &old_from_account);
Self::set_account(beneficiary, &to_account, &old_to_account);
Ok(value - actual)
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> {
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)?,
Status::Reserved => to_account.reserved = to_account.reserved.checked_add(&actual).ok_or(Error::<T, I>::Overflow)?,
}
from_account.reserved -= actual;
Ok(value - actual)
})
})
}
}
@@ -1380,12 +1349,11 @@ where
}
}
impl<T: Trait<I>, I: Instance> IsDeadAccount<T::AccountId> for Module<T, I>
where
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()`
!Account::<T, I>::contains_key(who)
!T::AccountStore::is_explicit(who)
}
}
File diff suppressed because it is too large Load Diff
@@ -23,7 +23,7 @@ use frame_support::{impl_outer_origin, parameter_types};
use frame_support::traits::Get;
use frame_support::weights::{Weight, DispatchInfo};
use std::cell::RefCell;
use crate::{GenesisConfig, Module, Trait};
use crate::{GenesisConfig, Module, Trait, decl_tests};
use frame_system as system;
impl_outer_origin!{
@@ -31,8 +31,7 @@ impl_outer_origin!{
}
thread_local! {
pub(crate) static EXISTENTIAL_DEPOSIT: RefCell<u64> = RefCell::new(0);
static CREATION_FEE: RefCell<u64> = RefCell::new(0);
static EXISTENTIAL_DEPOSIT: RefCell<u64> = RefCell::new(0);
}
pub struct ExistentialDeposit;
@@ -40,11 +39,6 @@ impl Get<u64> for ExistentialDeposit {
fn get() -> u64 { EXISTENTIAL_DEPOSIT.with(|v| *v.borrow()) }
}
pub struct CreationFee;
impl Get<u64> for CreationFee {
fn get() -> u64 { CREATION_FEE.with(|v| *v.borrow()) }
}
// Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted.
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Test;
@@ -71,6 +65,9 @@ impl frame_system::Trait for Test {
type AvailableBlockRatio = AvailableBlockRatio;
type Version = ();
type ModuleToIndex = ();
type AccountData = super::AccountData<u64>;
type OnNewAccount = ();
type OnReapAccount = Module<Test>;
}
parameter_types! {
pub const TransactionBaseFee: u64 = 0;
@@ -86,25 +83,20 @@ impl pallet_transaction_payment::Trait for Test {
}
impl Trait for Test {
type Balance = u64;
type OnReapAccount = System;
type OnNewAccount = ();
type Event = ();
type DustRemoval = ();
type TransferPayment = ();
type Event = ();
type ExistentialDeposit = ExistentialDeposit;
type CreationFee = CreationFee;
type AccountStore = system::Module<Test>;
}
pub struct ExtBuilder {
existential_deposit: u64,
creation_fee: u64,
monied: bool,
}
impl Default for ExtBuilder {
fn default() -> Self {
Self {
existential_deposit: 1,
creation_fee: 0,
monied: false,
}
}
@@ -114,17 +106,12 @@ impl ExtBuilder {
self.existential_deposit = existential_deposit;
self
}
pub fn creation_fee(mut self, creation_fee: u64) -> Self {
self.creation_fee = creation_fee;
self
}
pub fn monied(mut self, monied: bool) -> Self {
self.monied = monied;
self
}
pub fn set_associated_consts(&self) {
EXISTENTIAL_DEPOSIT.with(|v| *v.borrow_mut() = self.existential_deposit);
CREATION_FEE.with(|v| *v.borrow_mut() = self.creation_fee);
}
pub fn build(self) -> sp_io::TestExternalities {
self.set_associated_consts();
@@ -146,12 +133,4 @@ impl ExtBuilder {
}
}
pub type System = frame_system::Module<Test>;
pub type Balances = Module<Test>;
pub const CALL: &<Test as frame_system::Trait>::Call = &();
/// create a transaction info struct from weight. Handy to avoid building the whole struct.
pub fn info_from_weight(w: Weight) -> DispatchInfo {
DispatchInfo { weight: w, pays_fee: true, ..Default::default() }
}
decl_tests!{ Test, ExtBuilder, EXISTENTIAL_DEPOSIT }
+144
View File
@@ -0,0 +1,144 @@
// Copyright 2018-2020 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Substrate is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
//! Test utilities
use sp_runtime::{Perbill, traits::{ConvertInto, IdentityLookup}, testing::Header};
use sp_core::H256;
use sp_io;
use frame_support::{impl_outer_origin, parameter_types};
use frame_support::traits::{Get, StorageMapShim};
use frame_support::weights::{Weight, DispatchInfo};
use std::cell::RefCell;
use crate::{GenesisConfig, Module, Trait, decl_tests};
use frame_system as system;
impl_outer_origin!{
pub enum Origin for Test {}
}
thread_local! {
static EXISTENTIAL_DEPOSIT: RefCell<u64> = RefCell::new(0);
}
pub struct ExistentialDeposit;
impl Get<u64> for ExistentialDeposit {
fn get() -> u64 { EXISTENTIAL_DEPOSIT.with(|v| *v.borrow()) }
}
// Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted.
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Test;
parameter_types! {
pub const BlockHashCount: u64 = 250;
pub const MaximumBlockWeight: Weight = 1024;
pub const MaximumBlockLength: u32 = 2 * 1024;
pub const AvailableBlockRatio: Perbill = Perbill::one();
}
impl frame_system::Trait for Test {
type Origin = Origin;
type Index = u64;
type BlockNumber = u64;
type Call = ();
type Hash = H256;
type Hashing = ::sp_runtime::traits::BlakeTwo256;
type AccountId = u64;
type Lookup = IdentityLookup<Self::AccountId>;
type Header = Header;
type Event = ();
type BlockHashCount = BlockHashCount;
type MaximumBlockWeight = MaximumBlockWeight;
type MaximumBlockLength = MaximumBlockLength;
type AvailableBlockRatio = AvailableBlockRatio;
type Version = ();
type ModuleToIndex = ();
type AccountData = super::AccountData<u64>;
type OnNewAccount = ();
type OnReapAccount = Module<Test>;
}
parameter_types! {
pub const TransactionBaseFee: u64 = 0;
pub const TransactionByteFee: u64 = 1;
}
impl pallet_transaction_payment::Trait for Test {
type Currency = Module<Test>;
type OnTransactionPayment = ();
type TransactionBaseFee = TransactionBaseFee;
type TransactionByteFee = TransactionByteFee;
type WeightToFee = ConvertInto;
type FeeMultiplierUpdate = ();
}
impl Trait for Test {
type Balance = u64;
type DustRemoval = ();
type Event = ();
type ExistentialDeposit = ExistentialDeposit;
type AccountStore = StorageMapShim<
super::Account<Test>,
system::CallOnCreatedAccount<Test>,
system::CallKillAccount<Test>,
u64, super::AccountData<u64>
>;
}
pub struct ExtBuilder {
existential_deposit: u64,
monied: bool,
}
impl Default for ExtBuilder {
fn default() -> Self {
Self {
existential_deposit: 1,
monied: false,
}
}
}
impl ExtBuilder {
pub fn existential_deposit(mut self, existential_deposit: u64) -> Self {
self.existential_deposit = existential_deposit;
self
}
pub fn monied(mut self, monied: bool) -> Self {
self.monied = monied;
if self.existential_deposit == 0 {
self.existential_deposit = 1;
}
self
}
pub fn set_associated_consts(&self) {
EXISTENTIAL_DEPOSIT.with(|v| *v.borrow_mut() = self.existential_deposit);
}
pub fn build(self) -> sp_io::TestExternalities {
self.set_associated_consts();
let mut t = frame_system::GenesisConfig::default().build_storage::<Test>().unwrap();
GenesisConfig::<Test> {
balances: if self.monied {
vec![
(1, 10 * self.existential_deposit),
(2, 20 * self.existential_deposit),
(3, 30 * self.existential_deposit),
(4, 40 * self.existential_deposit),
(12, 10 * self.existential_deposit)
]
} else {
vec![]
},
}.assimilate_storage(&mut t).unwrap();
t.into()
}
}
decl_tests!{ Test, ExtBuilder, EXISTENTIAL_DEPOSIT }
+4 -1
View File
@@ -433,6 +433,9 @@ mod tests {
type AvailableBlockRatio = AvailableBlockRatio;
type Version = ();
type ModuleToIndex = ();
type AccountData = ();
type OnNewAccount = ();
type OnReapAccount = ();
}
impl Trait<Instance1> for Test {
type Origin = Origin;
@@ -454,7 +457,7 @@ mod tests {
NodeBlock = Block,
UncheckedExtrinsic = UncheckedExtrinsic
{
System: system::{Module, Call, Event},
System: system::{Module, Call, Event<T>},
Collective: collective::<Instance1>::{Module, Call, Event<T>, Origin<T>, Config<T>},
DefaultCollective: collective::{Module, Call, Event<T>, Origin<T>, Config<T>},
}
+5 -3
View File
@@ -26,7 +26,7 @@ use sp_std::collections::btree_map::{BTreeMap, Entry};
use sp_std::prelude::*;
use sp_io::hashing::blake2_256;
use sp_runtime::traits::{Bounded, Zero};
use frame_support::traits::{Currency, Get, Imbalance, SignedImbalance, UpdateBalanceOutcome};
use frame_support::traits::{Currency, Get, Imbalance, SignedImbalance};
use frame_support::{storage::child, StorageMap};
use frame_system;
@@ -146,9 +146,11 @@ impl<T: Trait> AccountDb<T> for DirectAccountDb {
let mut total_imbalance = SignedImbalance::zero();
for (address, changed) in s.into_iter() {
if let Some(balance) = changed.balance() {
let (imbalance, outcome) = T::Currency::make_free_balance_be(&address, balance);
let existed = !T::Currency::total_balance(&address).is_zero();
let imbalance = T::Currency::make_free_balance_be(&address, balance);
let exists = !T::Currency::total_balance(&address).is_zero();
total_imbalance = total_imbalance.merge(imbalance);
if let UpdateBalanceOutcome::AccountKilled = outcome {
if existed && !exists {
// Account killed. This will ultimately lead to calling `OnReapAccount` callback
// which will make removal of CodeHashOf and AccountStorage for this account.
// In order to avoid writing over the deleted properties we `continue` here.
+5 -20
View File
@@ -554,7 +554,6 @@ where
#[derive(Copy, Clone)]
pub enum TransferFeeKind {
ContractInstantiate,
AccountCreate,
Transfer,
}
@@ -572,7 +571,6 @@ impl<T: Trait> Token<T> for TransferFeeToken<BalanceOf<T>> {
fn calculate_amount(&self, metadata: &Config<T>) -> Gas {
let balance_fee = match self.kind {
TransferFeeKind::ContractInstantiate => metadata.contract_account_instantiate_fee,
TransferFeeKind::AccountCreate => metadata.account_create_fee,
TransferFeeKind::Transfer => return metadata.schedule.transfer_cost,
};
approx_gas_for_balance(self.gas_price, balance_fee)
@@ -612,28 +610,14 @@ fn transfer<'a, T: Trait, V: Vm<T>, L: Loader<T>>(
use self::TransferCause::*;
use self::TransferFeeKind::*;
let to_balance = ctx.overlay.get_balance(dest);
// `would_create` indicates whether the account will be created if this transfer gets executed.
// This flag is orthogonal to `cause.
// For example, we can instantiate a contract at the address which already has some funds. In this
// `would_create` will be `false`. Another example would be when this function is called from `call`,
// and account with the address `dest` doesn't exist yet `would_create` will be `true`.
let would_create = to_balance.is_zero();
let token = {
let kind: TransferFeeKind = match cause {
// If this function is called from `Instantiate` routine, then we always
// charge contract account creation fee.
Instantiate => ContractInstantiate,
// Otherwise the fee depends on whether we create a new account or transfer
// to an existing one.
Call => if would_create {
TransferFeeKind::AccountCreate
} else {
TransferFeeKind::Transfer
},
// Otherwise the fee is to transfer to an account.
Call => TransferFeeKind::Transfer,
};
TransferFeeToken {
kind,
@@ -651,7 +635,8 @@ fn transfer<'a, T: Trait, V: Vm<T>, L: Loader<T>>(
Some(b) => b,
None => Err("balance too low to send value")?,
};
if would_create && value < ctx.config.existential_deposit {
let to_balance = ctx.overlay.get_balance(dest);
if to_balance.is_zero() && value < ctx.config.existential_deposit {
Err("value too low to create account")?
}
T::Currency::ensure_can_withdraw(
@@ -1105,7 +1090,7 @@ mod tests {
toks,
ExecFeeToken::Call,
TransferFeeToken {
kind: TransferFeeKind::AccountCreate,
kind: TransferFeeKind::Transfer,
gas_price: 1u64
},
);
-8
View File
@@ -401,9 +401,6 @@ pub trait Trait: frame_system::Trait {
/// to removal of a contract.
type SurchargeReward: Get<BalanceOf<Self>>;
/// The fee required to create an account.
type CreationFee: Get<BalanceOf<Self>>;
/// The fee to be paid for making a transaction; the base.
type TransactionBaseFee: Get<BalanceOf<Self>>;
@@ -517,9 +514,6 @@ decl_module! {
/// to removal of a contract.
const SurchargeReward: BalanceOf<T> = T::SurchargeReward::get();
/// The fee required to create an account.
const CreationFee: BalanceOf<T> = T::CreationFee::get();
/// The fee to be paid for making a transaction; the base.
const TransactionBaseFee: BalanceOf<T> = T::TransactionBaseFee::get();
@@ -966,7 +960,6 @@ pub struct Config<T: Trait> {
pub max_depth: u32,
pub max_value_size: u32,
pub contract_account_instantiate_fee: BalanceOf<T>,
pub account_create_fee: BalanceOf<T>,
}
impl<T: Trait> Config<T> {
@@ -978,7 +971,6 @@ impl<T: Trait> Config<T> {
max_depth: T::MaxDepth::get(),
max_value_size: T::MaxValueSize::get(),
contract_account_instantiate_fee: T::ContractFee::get(),
account_create_fee: T::CreationFee::get(),
}
}
}
+191 -130
View File
@@ -41,7 +41,7 @@ use std::{cell::RefCell, sync::atomic::{AtomicUsize, Ordering}};
use sp_core::storage::well_known_keys;
use frame_system::{self as system, EventRecord, Phase};
mod contract {
mod contracts {
// Re-export contents of the root. This basically
// needs to give a name for the current crate.
// This hack is required for `impl_outer_event!`.
@@ -53,7 +53,9 @@ use pallet_balances as balances;
impl_outer_event! {
pub enum MetaEvent for Test {
balances<T>, contract<T>,
system<T>,
balances<T>,
contracts<T>,
}
}
impl_outer_origin! {
@@ -62,7 +64,7 @@ impl_outer_origin! {
impl_outer_dispatch! {
pub enum Call for Test where origin: Origin {
balances::Balances,
contract::Contract,
contracts::Contracts,
}
}
@@ -83,11 +85,6 @@ impl Get<u64> for TransferFee {
fn get() -> u64 { TRANSFER_FEE.with(|v| *v.borrow()) }
}
pub struct CreationFee;
impl Get<u64> for CreationFee {
fn get() -> u64 { INSTANTIATION_FEE.with(|v| *v.borrow()) }
}
pub struct BlockGasLimit;
impl Get<u64> for BlockGasLimit {
fn get() -> u64 { BLOCK_GAS_LIMIT.with(|v| *v.borrow()) }
@@ -118,16 +115,16 @@ impl frame_system::Trait for Test {
type MaximumBlockLength = MaximumBlockLength;
type Version = ();
type ModuleToIndex = ();
type AccountData = pallet_balances::AccountData<u64>;
type OnNewAccount = ();
type OnReapAccount = (Balances, Contracts);
}
impl pallet_balances::Trait for Test {
type Balance = u64;
type OnReapAccount = (System, Contract);
type OnNewAccount = ();
type Event = MetaEvent;
type DustRemoval = ();
type TransferPayment = ();
type ExistentialDeposit = ExistentialDeposit;
type CreationFee = CreationFee;
type AccountStore = System;
}
parameter_types! {
pub const MinimumPeriod: u64 = 1;
@@ -169,7 +166,6 @@ impl Trait for Test {
type RentByteFee = RentByteFee;
type RentDepositOffset = RentDepositOffset;
type SurchargeReward = SurchargeReward;
type CreationFee = CreationFee;
type TransactionBaseFee = TransactionBaseFee;
type TransactionByteFee = TransactionByteFee;
type ContractFee = ContractFee;
@@ -182,7 +178,7 @@ impl Trait for Test {
type Balances = pallet_balances::Module<Test>;
type Timestamp = pallet_timestamp::Module<Test>;
type Contract = Module<Test>;
type Contracts = Module<Test>;
type System = frame_system::Module<Test>;
type Randomness = pallet_randomness_collective_flip::Module<Test>;
@@ -304,7 +300,7 @@ fn refunds_unused_gas() {
ExtBuilder::default().gas_price(2).build().execute_with(|| {
Balances::deposit_creating(&ALICE, 100_000_000);
assert_ok!(Contract::call(Origin::signed(ALICE), BOB, 0, 100_000, Vec::new()));
assert_ok!(Contracts::call(Origin::signed(ALICE), BOB, 0, 100_000, Vec::new()));
// 2 * 135 - gas price multiplied by the call base fee.
assert_eq!(Balances::free_balance(ALICE), 100_000_000 - (2 * 135));
@@ -413,10 +409,10 @@ fn instantiate_and_call_and_deposit_event() {
ExtBuilder::default().existential_deposit(100).build().execute_with(|| {
Balances::deposit_creating(&ALICE, 1_000_000);
assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, wasm));
assert_ok!(Contracts::put_code(Origin::signed(ALICE), 100_000, wasm));
// Check at the end to get hash on error easily
let creation = Contract::instantiate(
let creation = Contracts::instantiate(
Origin::signed(ALICE),
100,
100_000,
@@ -427,34 +423,44 @@ fn instantiate_and_call_and_deposit_event() {
assert_eq!(System::events(), vec![
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::balances(pallet_balances::RawEvent::NewAccount(1, 1_000_000)),
event: MetaEvent::system(frame_system::RawEvent::NewAccount(1)),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::contract(RawEvent::CodeStored(code_hash.into())),
event: MetaEvent::balances(pallet_balances::RawEvent::Endowed(1, 1_000_000)),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::contracts(RawEvent::CodeStored(code_hash.into())),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::system(frame_system::RawEvent::NewAccount(BOB)),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::balances(
pallet_balances::RawEvent::NewAccount(BOB, 100)
pallet_balances::RawEvent::Endowed(BOB, 100)
),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::contract(RawEvent::Transfer(ALICE, BOB, 100)),
event: MetaEvent::contracts(RawEvent::Transfer(ALICE, BOB, 100)),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::contract(RawEvent::ContractExecution(BOB, vec![1, 2, 3, 4])),
event: MetaEvent::contracts(RawEvent::ContractExecution(BOB, vec![1, 2, 3, 4])),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::contract(RawEvent::Instantiated(ALICE, BOB)),
event: MetaEvent::contracts(RawEvent::Instantiated(ALICE, BOB)),
topics: vec![],
}
]);
@@ -493,24 +499,29 @@ fn dispatch_call() {
ExtBuilder::default().existential_deposit(50).build().execute_with(|| {
Balances::deposit_creating(&ALICE, 1_000_000);
assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, wasm));
assert_ok!(Contracts::put_code(Origin::signed(ALICE), 100_000, wasm));
// Let's keep this assert even though it's redundant. If you ever need to update the
// wasm source this test will fail and will show you the actual hash.
assert_eq!(System::events(), vec![
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::balances(pallet_balances::RawEvent::NewAccount(1, 1_000_000)),
event: MetaEvent::system(frame_system::RawEvent::NewAccount(1)),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::contract(RawEvent::CodeStored(code_hash.into())),
event: MetaEvent::balances(pallet_balances::RawEvent::Endowed(1, 1_000_000)),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::contracts(RawEvent::CodeStored(code_hash.into())),
topics: vec![],
},
]);
assert_ok!(Contract::instantiate(
assert_ok!(Contracts::instantiate(
Origin::signed(ALICE),
100,
100_000,
@@ -518,7 +529,7 @@ fn dispatch_call() {
vec![],
));
assert_ok!(Contract::call(
assert_ok!(Contracts::call(
Origin::signed(ALICE),
BOB, // newly created account
0,
@@ -529,44 +540,59 @@ fn dispatch_call() {
assert_eq!(System::events(), vec![
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::balances(pallet_balances::RawEvent::NewAccount(1, 1_000_000)),
event: MetaEvent::system(frame_system::RawEvent::NewAccount(1)),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::contract(RawEvent::CodeStored(code_hash.into())),
event: MetaEvent::balances(pallet_balances::RawEvent::Endowed(1, 1_000_000)),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::contracts(RawEvent::CodeStored(code_hash.into())),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::system(frame_system::RawEvent::NewAccount(BOB)),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::balances(
pallet_balances::RawEvent::NewAccount(BOB, 100)
pallet_balances::RawEvent::Endowed(BOB, 100)
),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::contract(RawEvent::Transfer(ALICE, BOB, 100)),
event: MetaEvent::contracts(RawEvent::Transfer(ALICE, BOB, 100)),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::contract(RawEvent::Instantiated(ALICE, BOB)),
event: MetaEvent::contracts(RawEvent::Instantiated(ALICE, BOB)),
topics: vec![],
},
// Dispatching the call.
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::system(frame_system::RawEvent::NewAccount(CHARLIE)),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::balances(
pallet_balances::RawEvent::NewAccount(CHARLIE, 50)
pallet_balances::RawEvent::Endowed(CHARLIE, 50)
),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::balances(
pallet_balances::RawEvent::Transfer(BOB, CHARLIE, 50, 0)
pallet_balances::RawEvent::Transfer(BOB, CHARLIE, 50)
),
topics: vec![],
},
@@ -574,7 +600,7 @@ fn dispatch_call() {
// Event emited as a result of dispatch.
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::contract(RawEvent::Dispatched(BOB, true)),
event: MetaEvent::contracts(RawEvent::Dispatched(BOB, true)),
topics: vec![],
}
]);
@@ -611,24 +637,29 @@ fn dispatch_call_not_dispatched_after_top_level_transaction_failure() {
ExtBuilder::default().existential_deposit(50).build().execute_with(|| {
Balances::deposit_creating(&ALICE, 1_000_000);
assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, wasm));
assert_ok!(Contracts::put_code(Origin::signed(ALICE), 100_000, wasm));
// Let's keep this assert even though it's redundant. If you ever need to update the
// wasm source this test will fail and will show you the actual hash.
assert_eq!(System::events(), vec![
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::balances(pallet_balances::RawEvent::NewAccount(1, 1_000_000)),
event: MetaEvent::system(frame_system::RawEvent::NewAccount(1)),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::contract(RawEvent::CodeStored(code_hash.into())),
event: MetaEvent::balances(pallet_balances::RawEvent::Endowed(1, 1_000_000)),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::contracts(RawEvent::CodeStored(code_hash.into())),
topics: vec![],
},
]);
assert_ok!(Contract::instantiate(
assert_ok!(Contracts::instantiate(
Origin::signed(ALICE),
100,
100_000,
@@ -639,7 +670,7 @@ fn dispatch_call_not_dispatched_after_top_level_transaction_failure() {
// Call the newly instantiated contract. The contract is expected to dispatch a call
// and then trap.
assert_err!(
Contract::call(
Contracts::call(
Origin::signed(ALICE),
BOB, // newly created account
0,
@@ -651,29 +682,39 @@ fn dispatch_call_not_dispatched_after_top_level_transaction_failure() {
assert_eq!(System::events(), vec![
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::balances(pallet_balances::RawEvent::NewAccount(1, 1_000_000)),
event: MetaEvent::system(frame_system::RawEvent::NewAccount(1)),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::contract(RawEvent::CodeStored(code_hash.into())),
event: MetaEvent::balances(pallet_balances::RawEvent::Endowed(1, 1_000_000)),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::contracts(RawEvent::CodeStored(code_hash.into())),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::system(frame_system::RawEvent::NewAccount(BOB)),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::balances(
pallet_balances::RawEvent::NewAccount(BOB, 100)
pallet_balances::RawEvent::Endowed(BOB, 100)
),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::contract(RawEvent::Transfer(ALICE, BOB, 100)),
event: MetaEvent::contracts(RawEvent::Transfer(ALICE, BOB, 100)),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::contract(RawEvent::Instantiated(ALICE, BOB)),
event: MetaEvent::contracts(RawEvent::Instantiated(ALICE, BOB)),
topics: vec![],
},
// ABSENCE of events which would be caused by dispatched Balances::transfer call
@@ -810,19 +851,24 @@ fn test_set_rent_code_and_hash() {
ExtBuilder::default().existential_deposit(50).build().execute_with(|| {
Balances::deposit_creating(&ALICE, 1_000_000);
assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, wasm));
assert_ok!(Contracts::put_code(Origin::signed(ALICE), 100_000, wasm));
// If you ever need to update the wasm source this test will fail
// and will show you the actual hash.
assert_eq!(System::events(), vec![
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::balances(pallet_balances::RawEvent::NewAccount(1, 1_000_000)),
event: MetaEvent::system(frame_system::RawEvent::NewAccount(1)),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::contract(RawEvent::CodeStored(code_hash.into())),
event: MetaEvent::balances(pallet_balances::RawEvent::Endowed(1, 1_000_000)),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::contracts(RawEvent::CodeStored(code_hash.into())),
topics: vec![],
},
]);
@@ -837,8 +883,8 @@ fn storage_size() {
ExtBuilder::default().existential_deposit(50).build().execute_with(|| {
// Create
Balances::deposit_creating(&ALICE, 1_000_000);
assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, wasm));
assert_ok!(Contract::instantiate(
assert_ok!(Contracts::put_code(Origin::signed(ALICE), 100_000, wasm));
assert_ok!(Contracts::instantiate(
Origin::signed(ALICE),
30_000,
100_000, code_hash.into(),
@@ -847,11 +893,11 @@ fn storage_size() {
let bob_contract = ContractInfoOf::<Test>::get(BOB).unwrap().get_alive().unwrap();
assert_eq!(bob_contract.storage_size, <Test as Trait>::StorageSizeOffset::get() + 4);
assert_ok!(Contract::call(Origin::signed(ALICE), BOB, 0, 100_000, call::set_storage_4_byte()));
assert_ok!(Contracts::call(Origin::signed(ALICE), BOB, 0, 100_000, call::set_storage_4_byte()));
let bob_contract = ContractInfoOf::<Test>::get(BOB).unwrap().get_alive().unwrap();
assert_eq!(bob_contract.storage_size, <Test as Trait>::StorageSizeOffset::get() + 4 + 4);
assert_ok!(Contract::call(Origin::signed(ALICE), BOB, 0, 100_000, call::remove_storage_4_byte()));
assert_ok!(Contracts::call(Origin::signed(ALICE), BOB, 0, 100_000, call::remove_storage_4_byte()));
let bob_contract = ContractInfoOf::<Test>::get(BOB).unwrap().get_alive().unwrap();
assert_eq!(bob_contract.storage_size, <Test as Trait>::StorageSizeOffset::get() + 4);
});
@@ -874,8 +920,8 @@ fn deduct_blocks() {
ExtBuilder::default().existential_deposit(50).build().execute_with(|| {
// Create
Balances::deposit_creating(&ALICE, 1_000_000);
assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, wasm));
assert_ok!(Contract::instantiate(
assert_ok!(Contracts::put_code(Origin::signed(ALICE), 100_000, wasm));
assert_ok!(Contracts::instantiate(
Origin::signed(ALICE),
30_000,
100_000, code_hash.into(),
@@ -890,7 +936,7 @@ fn deduct_blocks() {
initialize_block(5);
// Trigger rent through call
assert_ok!(Contract::call(Origin::signed(ALICE), BOB, 0, 100_000, call::null()));
assert_ok!(Contracts::call(Origin::signed(ALICE), BOB, 0, 100_000, call::null()));
// Check result
let rent = (8 + 4 - 3) // storage size = size_offset + deploy_set_storage - deposit_offset
@@ -905,7 +951,7 @@ fn deduct_blocks() {
initialize_block(12);
// Trigger rent through call
assert_ok!(Contract::call(Origin::signed(ALICE), BOB, 0, 100_000, call::null()));
assert_ok!(Contracts::call(Origin::signed(ALICE), BOB, 0, 100_000, call::null()));
// Check result
let rent_2 = (8 + 4 - 2) // storage size = size_offset + deploy_set_storage - deposit_offset
@@ -917,7 +963,7 @@ fn deduct_blocks() {
assert_eq!(Balances::free_balance(BOB), 30_000 - rent - rent_2);
// Second call on same block should have no effect on rent
assert_ok!(Contract::call(Origin::signed(ALICE), BOB, 0, 100_000, call::null()));
assert_ok!(Contracts::call(Origin::signed(ALICE), BOB, 0, 100_000, call::null()));
let bob_contract = ContractInfoOf::<Test>::get(BOB).unwrap().get_alive().unwrap();
assert_eq!(bob_contract.rent_allowance, 1_000 - rent - rent_2);
@@ -930,34 +976,34 @@ fn deduct_blocks() {
fn call_contract_removals() {
removals(|| {
// Call on already-removed account might fail, and this is fine.
Contract::call(Origin::signed(ALICE), BOB, 0, 100_000, call::null());
Contracts::call(Origin::signed(ALICE), BOB, 0, 100_000, call::null());
true
});
}
#[test]
fn inherent_claim_surcharge_contract_removals() {
removals(|| Contract::claim_surcharge(Origin::NONE, BOB, Some(ALICE)).is_ok());
removals(|| Contracts::claim_surcharge(Origin::NONE, BOB, Some(ALICE)).is_ok());
}
#[test]
fn signed_claim_surcharge_contract_removals() {
removals(|| Contract::claim_surcharge(Origin::signed(ALICE), BOB, None).is_ok());
removals(|| Contracts::claim_surcharge(Origin::signed(ALICE), BOB, None).is_ok());
}
#[test]
fn claim_surcharge_malus() {
// Test surcharge malus for inherent
claim_surcharge(4, || Contract::claim_surcharge(Origin::NONE, BOB, Some(ALICE)).is_ok(), true);
claim_surcharge(3, || Contract::claim_surcharge(Origin::NONE, BOB, Some(ALICE)).is_ok(), true);
claim_surcharge(2, || Contract::claim_surcharge(Origin::NONE, BOB, Some(ALICE)).is_ok(), true);
claim_surcharge(1, || Contract::claim_surcharge(Origin::NONE, BOB, Some(ALICE)).is_ok(), false);
claim_surcharge(4, || Contracts::claim_surcharge(Origin::NONE, BOB, Some(ALICE)).is_ok(), true);
claim_surcharge(3, || Contracts::claim_surcharge(Origin::NONE, BOB, Some(ALICE)).is_ok(), true);
claim_surcharge(2, || Contracts::claim_surcharge(Origin::NONE, BOB, Some(ALICE)).is_ok(), true);
claim_surcharge(1, || Contracts::claim_surcharge(Origin::NONE, BOB, Some(ALICE)).is_ok(), false);
// Test surcharge malus for signed
claim_surcharge(4, || Contract::claim_surcharge(Origin::signed(ALICE), BOB, None).is_ok(), true);
claim_surcharge(3, || Contract::claim_surcharge(Origin::signed(ALICE), BOB, None).is_ok(), false);
claim_surcharge(2, || Contract::claim_surcharge(Origin::signed(ALICE), BOB, None).is_ok(), false);
claim_surcharge(1, || Contract::claim_surcharge(Origin::signed(ALICE), BOB, None).is_ok(), false);
claim_surcharge(4, || Contracts::claim_surcharge(Origin::signed(ALICE), BOB, None).is_ok(), true);
claim_surcharge(3, || Contracts::claim_surcharge(Origin::signed(ALICE), BOB, None).is_ok(), false);
claim_surcharge(2, || Contracts::claim_surcharge(Origin::signed(ALICE), BOB, None).is_ok(), false);
claim_surcharge(1, || Contracts::claim_surcharge(Origin::signed(ALICE), BOB, None).is_ok(), false);
}
/// Claim surcharge with the given trigger_call at the given blocks.
@@ -968,8 +1014,8 @@ fn claim_surcharge(blocks: u64, trigger_call: impl Fn() -> bool, removes: bool)
ExtBuilder::default().existential_deposit(50).build().execute_with(|| {
// Create
Balances::deposit_creating(&ALICE, 1_000_000);
assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, wasm));
assert_ok!(Contract::instantiate(
assert_ok!(Contracts::put_code(Origin::signed(ALICE), 100_000, wasm));
assert_ok!(Contracts::instantiate(
Origin::signed(ALICE),
100,
100_000, code_hash.into(),
@@ -1001,8 +1047,8 @@ fn removals(trigger_call: impl Fn() -> bool) {
ExtBuilder::default().existential_deposit(50).build().execute_with(|| {
// Create
Balances::deposit_creating(&ALICE, 1_000_000);
assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, wasm.clone()));
assert_ok!(Contract::instantiate(
assert_ok!(Contracts::put_code(Origin::signed(ALICE), 100_000, wasm.clone()));
assert_ok!(Contracts::instantiate(
Origin::signed(ALICE),
100,
100_000, code_hash.into(),
@@ -1037,8 +1083,8 @@ fn removals(trigger_call: impl Fn() -> bool) {
ExtBuilder::default().existential_deposit(50).build().execute_with(|| {
// Create
Balances::deposit_creating(&ALICE, 1_000_000);
assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, wasm.clone()));
assert_ok!(Contract::instantiate(
assert_ok!(Contracts::put_code(Origin::signed(ALICE), 100_000, wasm.clone()));
assert_ok!(Contracts::instantiate(
Origin::signed(ALICE),
1_000,
100_000, code_hash.into(),
@@ -1072,8 +1118,8 @@ fn removals(trigger_call: impl Fn() -> bool) {
ExtBuilder::default().existential_deposit(50).build().execute_with(|| {
// Create
Balances::deposit_creating(&ALICE, 1_000_000);
assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, wasm.clone()));
assert_ok!(Contract::instantiate(
assert_ok!(Contracts::put_code(Origin::signed(ALICE), 100_000, wasm.clone()));
assert_ok!(Contracts::instantiate(
Origin::signed(ALICE),
50+Balances::minimum_balance(),
100_000, code_hash.into(),
@@ -1086,7 +1132,7 @@ fn removals(trigger_call: impl Fn() -> bool) {
assert_eq!(Balances::free_balance(BOB), 50 + Balances::minimum_balance());
// Transfer funds
assert_ok!(Contract::call(Origin::signed(ALICE), BOB, 0, 100_000, call::transfer()));
assert_ok!(Contracts::call(Origin::signed(ALICE), BOB, 0, 100_000, call::transfer()));
assert_eq!(ContractInfoOf::<Test>::get(BOB).unwrap().get_alive().unwrap().rent_allowance, 1_000);
assert_eq!(Balances::free_balance(BOB), Balances::minimum_balance());
@@ -1116,8 +1162,8 @@ fn call_removed_contract() {
ExtBuilder::default().existential_deposit(50).build().execute_with(|| {
// Create
Balances::deposit_creating(&ALICE, 1_000_000);
assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, wasm.clone()));
assert_ok!(Contract::instantiate(
assert_ok!(Contracts::put_code(Origin::signed(ALICE), 100_000, wasm.clone()));
assert_ok!(Contracts::instantiate(
Origin::signed(ALICE),
100,
100_000, code_hash.into(),
@@ -1125,28 +1171,28 @@ fn call_removed_contract() {
));
// Calling contract should succeed.
assert_ok!(Contract::call(Origin::signed(ALICE), BOB, 0, 100_000, call::null()));
assert_ok!(Contracts::call(Origin::signed(ALICE), BOB, 0, 100_000, call::null()));
// Advance blocks
initialize_block(10);
// Calling contract should remove contract and fail.
assert_err!(
Contract::call(Origin::signed(ALICE), BOB, 0, 100_000, call::null()),
Contracts::call(Origin::signed(ALICE), BOB, 0, 100_000, call::null()),
"contract has been evicted"
);
// Calling a contract that is about to evict shall emit an event.
assert_eq!(System::events(), vec![
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::contract(RawEvent::Evicted(BOB, true)),
event: MetaEvent::contracts(RawEvent::Evicted(BOB, true)),
topics: vec![],
},
]);
// Subsequent contract calls should also fail.
assert_err!(
Contract::call(Origin::signed(ALICE), BOB, 0, 100_000, call::null()),
Contracts::call(Origin::signed(ALICE), BOB, 0, 100_000, call::null()),
"contract has been evicted"
);
})
@@ -1209,8 +1255,8 @@ fn default_rent_allowance_on_instantiate() {
ExtBuilder::default().existential_deposit(50).build().execute_with(|| {
// Create
Balances::deposit_creating(&ALICE, 1_000_000);
assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, wasm));
assert_ok!(Contract::instantiate(
assert_ok!(Contracts::put_code(Origin::signed(ALICE), 100_000, wasm));
assert_ok!(Contracts::instantiate(
Origin::signed(ALICE),
30_000,
100_000,
@@ -1226,7 +1272,7 @@ fn default_rent_allowance_on_instantiate() {
initialize_block(5);
// Trigger rent through call
assert_ok!(Contract::call(Origin::signed(ALICE), BOB, 0, 100_000, call::null()));
assert_ok!(Contracts::call(Origin::signed(ALICE), BOB, 0, 100_000, call::null()));
// Check contract is still alive
let bob_contract = ContractInfoOf::<Test>::get(BOB).unwrap().get_alive();
@@ -1322,32 +1368,37 @@ fn restoration(test_different_storage: bool, test_restore_to_with_dirty_storage:
ExtBuilder::default().existential_deposit(50).build().execute_with(|| {
Balances::deposit_creating(&ALICE, 1_000_000);
assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, restoration_wasm));
assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, set_rent_wasm));
assert_ok!(Contracts::put_code(Origin::signed(ALICE), 100_000, restoration_wasm));
assert_ok!(Contracts::put_code(Origin::signed(ALICE), 100_000, set_rent_wasm));
// If you ever need to update the wasm source this test will fail
// and will show you the actual hash.
assert_eq!(System::events(), vec![
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::balances(pallet_balances::RawEvent::NewAccount(1, 1_000_000)),
event: MetaEvent::system(frame_system::RawEvent::NewAccount(1)),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::contract(RawEvent::CodeStored(restoration_code_hash.into())),
event: MetaEvent::balances(pallet_balances::RawEvent::Endowed(1, 1_000_000)),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::contract(RawEvent::CodeStored(set_rent_code_hash.into())),
event: MetaEvent::contracts(RawEvent::CodeStored(restoration_code_hash.into())),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::contracts(RawEvent::CodeStored(set_rent_code_hash.into())),
topics: vec![],
},
]);
// Create an account with address `BOB` with code `CODE_SET_RENT`.
// The input parameter sets the rent allowance to 0.
assert_ok!(Contract::instantiate(
assert_ok!(Contracts::instantiate(
Origin::signed(ALICE),
30_000,
100_000,
@@ -1361,7 +1412,7 @@ fn restoration(test_different_storage: bool, test_restore_to_with_dirty_storage:
assert_eq!(bob_contract.rent_allowance, 0);
if test_different_storage {
assert_ok!(Contract::call(
assert_ok!(Contracts::call(
Origin::signed(ALICE),
BOB, 0, 100_000,
call::set_storage_4_byte())
@@ -1377,14 +1428,14 @@ fn restoration(test_different_storage: bool, test_restore_to_with_dirty_storage:
// Call `BOB`, which makes it pay rent. Since the rent allowance is set to 0
// we expect that it will get removed leaving tombstone.
assert_err!(
Contract::call(Origin::signed(ALICE), BOB, 0, 100_000, call::null()),
Contracts::call(Origin::signed(ALICE), BOB, 0, 100_000, call::null()),
"contract has been evicted"
);
assert!(ContractInfoOf::<Test>::get(BOB).unwrap().get_tombstone().is_some());
assert_eq!(System::events(), vec![
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::contract(
event: MetaEvent::contracts(
RawEvent::Evicted(BOB.clone(), true)
),
topics: vec![],
@@ -1396,7 +1447,7 @@ fn restoration(test_different_storage: bool, test_restore_to_with_dirty_storage:
/// Note that we can't use `ALICE` for creating `DJANGO` so we create yet another
/// account `CHARLIE` and create `DJANGO` with it.
Balances::deposit_creating(&CHARLIE, 1_000_000);
assert_ok!(Contract::instantiate(
assert_ok!(Contracts::instantiate(
Origin::signed(CHARLIE),
30_000,
100_000,
@@ -1415,7 +1466,7 @@ fn restoration(test_different_storage: bool, test_restore_to_with_dirty_storage:
// Perform a call to `DJANGO`. This should either perform restoration successfully or
// fail depending on the test parameters.
assert_ok!(Contract::call(
assert_ok!(Contracts::call(
Origin::signed(ALICE),
DJANGO,
0,
@@ -1437,7 +1488,7 @@ fn restoration(test_different_storage: bool, test_restore_to_with_dirty_storage:
assert_eq!(System::events(), vec![
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::contract(
event: MetaEvent::contracts(
RawEvent::Restored(DJANGO, BOB, bob_code_hash, 50, false)
),
topics: vec![],
@@ -1448,32 +1499,42 @@ fn restoration(test_different_storage: bool, test_restore_to_with_dirty_storage:
assert_eq!(System::events(), vec![
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::contract(RawEvent::Evicted(BOB, true)),
event: MetaEvent::contracts(RawEvent::Evicted(BOB, true)),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::balances(pallet_balances::RawEvent::NewAccount(CHARLIE, 1_000_000)),
event: MetaEvent::system(frame_system::RawEvent::NewAccount(CHARLIE)),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::balances(pallet_balances::RawEvent::NewAccount(DJANGO, 30_000)),
event: MetaEvent::balances(pallet_balances::RawEvent::Endowed(CHARLIE, 1_000_000)),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::contract(RawEvent::Transfer(CHARLIE, DJANGO, 30_000)),
event: MetaEvent::system(frame_system::RawEvent::NewAccount(DJANGO)),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::contract(RawEvent::Instantiated(CHARLIE, DJANGO)),
event: MetaEvent::balances(pallet_balances::RawEvent::Endowed(DJANGO, 30_000)),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::contract(RawEvent::Restored(
event: MetaEvent::contracts(RawEvent::Transfer(CHARLIE, DJANGO, 30_000)),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::contracts(RawEvent::Instantiated(CHARLIE, DJANGO)),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::contracts(RawEvent::Restored(
DJANGO,
BOB,
bob_code_hash,
@@ -1500,12 +1561,12 @@ fn restoration(test_different_storage: bool, test_restore_to_with_dirty_storage:
assert_eq!(System::events(), vec![
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::balances(balances::RawEvent::ReapedAccount(DJANGO, 0)),
event: MetaEvent::system(system::RawEvent::ReapedAccount(DJANGO)),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: MetaEvent::contract(
event: MetaEvent::contracts(
RawEvent::Restored(DJANGO, BOB, bob_contract.code_hash, 50, true)
),
topics: vec![],
@@ -1586,8 +1647,8 @@ fn storage_max_value_limit() {
ExtBuilder::default().existential_deposit(50).build().execute_with(|| {
// Create
Balances::deposit_creating(&ALICE, 1_000_000);
assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, wasm));
assert_ok!(Contract::instantiate(
assert_ok!(Contracts::put_code(Origin::signed(ALICE), 100_000, wasm));
assert_ok!(Contracts::instantiate(
Origin::signed(ALICE),
30_000,
100_000,
@@ -1600,7 +1661,7 @@ fn storage_max_value_limit() {
assert_eq!(bob_contract.rent_allowance, <BalanceOf<Test>>::max_value());
// Call contract with allowed storage value.
assert_ok!(Contract::call(
assert_ok!(Contracts::call(
Origin::signed(ALICE),
BOB,
0,
@@ -1610,7 +1671,7 @@ fn storage_max_value_limit() {
// Call contract with too large a storage value.
assert_err!(
Contract::call(
Contracts::call(
Origin::signed(ALICE),
BOB,
0,
@@ -1950,10 +2011,10 @@ fn deploy_and_call_other_contract() {
ExtBuilder::default().existential_deposit(50).build().execute_with(|| {
// Create
Balances::deposit_creating(&ALICE, 1_000_000);
assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, callee_wasm));
assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, caller_wasm));
assert_ok!(Contracts::put_code(Origin::signed(ALICE), 100_000, callee_wasm));
assert_ok!(Contracts::put_code(Origin::signed(ALICE), 100_000, caller_wasm));
assert_ok!(Contract::instantiate(
assert_ok!(Contracts::instantiate(
Origin::signed(ALICE),
100_000,
100_000,
@@ -1963,7 +2024,7 @@ fn deploy_and_call_other_contract() {
// Call BOB contract, which attempts to instantiate and call the callee contract and
// makes various assertions on the results from those calls.
assert_ok!(Contract::call(
assert_ok!(Contracts::call(
Origin::signed(ALICE),
BOB,
0,
@@ -2077,10 +2138,10 @@ fn self_destruct_by_draining_balance() {
let (wasm, code_hash) = compile_module::<Test>(CODE_SELF_DESTRUCT).unwrap();
ExtBuilder::default().existential_deposit(50).build().execute_with(|| {
Balances::deposit_creating(&ALICE, 1_000_000);
assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, wasm));
assert_ok!(Contracts::put_code(Origin::signed(ALICE), 100_000, wasm));
// Instantiate the BOB contract.
assert_ok!(Contract::instantiate(
assert_ok!(Contracts::instantiate(
Origin::signed(ALICE),
100_000,
100_000,
@@ -2095,7 +2156,7 @@ fn self_destruct_by_draining_balance() {
);
// Call BOB with no input data, forcing it to self-destruct.
assert_ok!(Contract::call(
assert_ok!(Contracts::call(
Origin::signed(ALICE),
BOB,
0,
@@ -2113,10 +2174,10 @@ fn cannot_self_destruct_while_live() {
let (wasm, code_hash) = compile_module::<Test>(CODE_SELF_DESTRUCT).unwrap();
ExtBuilder::default().existential_deposit(50).build().execute_with(|| {
Balances::deposit_creating(&ALICE, 1_000_000);
assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, wasm));
assert_ok!(Contracts::put_code(Origin::signed(ALICE), 100_000, wasm));
// Instantiate the BOB contract.
assert_ok!(Contract::instantiate(
assert_ok!(Contracts::instantiate(
Origin::signed(ALICE),
100_000,
100_000,
@@ -2133,7 +2194,7 @@ fn cannot_self_destruct_while_live() {
// Call BOB with input data, forcing it make a recursive call to itself to
// self-destruct, resulting in a trap.
assert_err!(
Contract::call(
Contracts::call(
Origin::signed(ALICE),
BOB,
0,
@@ -2313,12 +2374,12 @@ fn destroy_contract_and_transfer_funds() {
ExtBuilder::default().existential_deposit(50).build().execute_with(|| {
// Create
Balances::deposit_creating(&ALICE, 1_000_000);
assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, callee_wasm));
assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, caller_wasm));
assert_ok!(Contracts::put_code(Origin::signed(ALICE), 100_000, callee_wasm));
assert_ok!(Contracts::put_code(Origin::signed(ALICE), 100_000, caller_wasm));
// This deploys the BOB contract, which in turn deploys the CHARLIE contract during
// construction.
assert_ok!(Contract::instantiate(
assert_ok!(Contracts::instantiate(
Origin::signed(ALICE),
200_000,
100_000,
@@ -2333,7 +2394,7 @@ fn destroy_contract_and_transfer_funds() {
);
// Call BOB, which calls CHARLIE, forcing CHARLIE to self-destruct.
assert_ok!(Contract::call(
assert_ok!(Contracts::call(
Origin::signed(ALICE),
BOB,
0,
@@ -2408,12 +2469,12 @@ fn cannot_self_destruct_in_constructor() {
let (wasm, code_hash) = compile_module::<Test>(CODE_SELF_DESTRUCTING_CONSTRUCTOR).unwrap();
ExtBuilder::default().existential_deposit(50).build().execute_with(|| {
Balances::deposit_creating(&ALICE, 1_000_000);
assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, wasm));
assert_ok!(Contracts::put_code(Origin::signed(ALICE), 100_000, wasm));
// Fail to instantiate the BOB contract since its final balance is below existential
// deposit.
assert_err!(
Contract::instantiate(
Contracts::instantiate(
Origin::signed(ALICE),
100_000,
100_000,
@@ -2529,15 +2590,15 @@ fn get_runtime_storage() {
0x14144020u32.to_le_bytes().to_vec().as_ref()
);
assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, wasm));
assert_ok!(Contract::instantiate(
assert_ok!(Contracts::put_code(Origin::signed(ALICE), 100_000, wasm));
assert_ok!(Contracts::instantiate(
Origin::signed(ALICE),
100,
100_000,
code_hash.into(),
vec![],
));
assert_ok!(Contract::call(
assert_ok!(Contracts::call(
Origin::signed(ALICE),
BOB,
0,
+6 -7
View File
@@ -30,7 +30,7 @@ use frame_support::{
weights::SimpleDispatchInfo,
traits::{
Currency, ReservableCurrency, LockableCurrency, WithdrawReason, LockIdentifier, Get,
OnReapAccount, OnUnbalanced
OnReapAccount, OnUnbalanced, BalanceStatus
}
};
use frame_system::{self as system, ensure_signed, ensure_root};
@@ -802,7 +802,7 @@ decl_module! {
let queue = <DispatchQueue<T>>::get();
ensure!(!queue.iter().any(|item| &item.1 == &proposal_hash), Error::<T>::Imminent);
let _ = T::Currency::repatriate_reserved(&old, &who, deposit);
let _ = T::Currency::repatriate_reserved(&old, &who, deposit, BalanceStatus::Free);
<Preimages<T>>::remove(&proposal_hash);
Self::deposit_event(RawEvent::PreimageReaped(proposal_hash, old, deposit, who));
}
@@ -1227,20 +1227,19 @@ mod tests {
type AvailableBlockRatio = AvailableBlockRatio;
type Version = ();
type ModuleToIndex = ();
type AccountData = pallet_balances::AccountData<u64>;
type OnNewAccount = ();
type OnReapAccount = Balances;
}
parameter_types! {
pub const ExistentialDeposit: u64 = 1;
pub const CreationFee: u64 = 0;
}
impl pallet_balances::Trait for Test {
type Balance = u64;
type OnReapAccount = System;
type OnNewAccount = ();
type Event = ();
type TransferPayment = ();
type DustRemoval = ();
type ExistentialDeposit = ExistentialDeposit;
type CreationFee = CreationFee;
type AccountStore = System;
}
parameter_types! {
pub const LaunchPeriod: u64 = 2;
+12 -13
View File
@@ -91,7 +91,7 @@ use frame_support::{
decl_storage, decl_event, ensure, decl_module, decl_error, weights::SimpleDispatchInfo,
traits::{
Currency, Get, LockableCurrency, LockIdentifier, ReservableCurrency, WithdrawReasons,
ChangeMembers, OnUnbalanced, WithdrawReason, Contains
ChangeMembers, OnUnbalanced, WithdrawReason, Contains, BalanceStatus
}
};
use sp_phragmen::ExtendedBalance;
@@ -314,7 +314,7 @@ decl_module! {
let valid = Self::is_defunct_voter(&target);
if valid {
// reporter will get the voting bond of the target
T::Currency::repatriate_reserved(&target, &reporter, T::VotingBond::get())?;
T::Currency::repatriate_reserved(&target, &reporter, T::VotingBond::get(), BalanceStatus::Free)?;
// remove the target. They are defunct.
Self::do_remove_voter(&target, false);
} else {
@@ -814,23 +814,22 @@ mod tests {
type AvailableBlockRatio = AvailableBlockRatio;
type Version = ();
type ModuleToIndex = ();
type AccountData = pallet_balances::AccountData<u64>;
type OnNewAccount = ();
type OnReapAccount = Balances;
}
parameter_types! {
pub const ExistentialDeposit: u64 = 1;
pub const CreationFee: u64 = 0;
}
}
impl pallet_balances::Trait for Test {
type Balance = u64;
type OnNewAccount = ();
type OnReapAccount = System;
type Event = Event;
type TransferPayment = ();
type DustRemoval = ();
type ExistentialDeposit = ExistentialDeposit;
type CreationFee = CreationFee;
}
type AccountStore = frame_system::Module<Test>;
}
parameter_types! {
pub const CandidacyBond: u64 = 3;
@@ -937,7 +936,7 @@ mod tests {
NodeBlock = Block,
UncheckedExtrinsic = UncheckedExtrinsic
{
System: system::{Module, Call, Event},
System: system::{Module, Call, Event<T>},
Balances: pallet_balances::{Module, Call, Event<T>, Config<T>},
Elections: elections::{Module, Call, Event<T>},
}
@@ -1427,7 +1426,7 @@ mod tests {
assert_ok!(Elections::report_defunct_voter(Origin::signed(5), 3));
assert_eq!(
System::events()[1].event,
System::events()[7].event,
Event::elections(RawEvent::VoterReported(3, 5, true))
);
@@ -1456,7 +1455,7 @@ mod tests {
assert_ok!(Elections::report_defunct_voter(Origin::signed(5), 4));
assert_eq!(
System::events()[1].event,
System::events()[7].event,
Event::elections(RawEvent::VoterReported(4, 5, false))
);
@@ -1867,7 +1866,7 @@ mod tests {
assert_eq!(balances(&5), (45, 2));
assert_eq!(
System::events()[0].event,
System::events()[6].event,
Event::elections(RawEvent::NewTerm(vec![(4, 40), (5, 50)])),
);
})
+2 -2
View File
@@ -32,7 +32,7 @@ use frame_support::{
decl_storage, decl_event, ensure, decl_module, decl_error,
weights::SimpleDispatchInfo,
traits::{
Currency, ExistenceRequirement, Get, LockableCurrency, LockIdentifier,
Currency, ExistenceRequirement, Get, LockableCurrency, LockIdentifier, BalanceStatus,
OnUnbalanced, ReservableCurrency, WithdrawReason, WithdrawReasons, ChangeMembers
}
};
@@ -501,7 +501,7 @@ decl_module! {
if valid {
// This only fails if `reporter` doesn't exist, which it clearly must do since its
// the origin. Still, it's no more harmful to propagate any error at this point.
T::Currency::repatriate_reserved(&who, &reporter, T::VotingBond::get())?;
T::Currency::repatriate_reserved(&who, &reporter, T::VotingBond::get(), BalanceStatus::Free)?;
Self::deposit_event(RawEvent::VoterReaped(who, reporter));
} else {
let imbalance = T::Currency::slash_reserved(&reporter, T::VotingBond::get()).0;
+8 -10
View File
@@ -39,9 +39,9 @@ parameter_types! {
}
impl frame_system::Trait for Test {
type Origin = Origin;
type Call = ();
type Index = u64;
type BlockNumber = u64;
type Call = ();
type Hash = H256;
type Hashing = BlakeTwo256;
type AccountId = u64;
@@ -54,21 +54,20 @@ impl frame_system::Trait for Test {
type AvailableBlockRatio = AvailableBlockRatio;
type Version = ();
type ModuleToIndex = ();
type AccountData = pallet_balances::AccountData<u64>;
type OnNewAccount = ();
type OnReapAccount = Balances;
}
parameter_types! {
pub const ExistentialDeposit: u64 = 1;
pub const CreationFee: u64 = 0;
}
impl pallet_balances::Trait for Test {
type Balance = u64;
type OnNewAccount = ();
type OnReapAccount = System;
type Event = Event;
type TransferPayment = ();
type DustRemoval = ();
type Event = Event;
type ExistentialDeposit = ExistentialDeposit;
type CreationFee = CreationFee;
type AccountStore = System;
}
parameter_types! {
@@ -151,7 +150,7 @@ frame_support::construct_runtime!(
NodeBlock = Block,
UncheckedExtrinsic = UncheckedExtrinsic
{
System: system::{Module, Call, Event},
System: system::{Module, Call, Event<T>},
Balances: pallet_balances::{Module, Call, Event<T>, Config<T>},
Elections: elections::{Module, Call, Event<T>, Config<T>},
}
@@ -266,8 +265,7 @@ pub(crate) fn create_candidate(i: u64, index: u32) {
}
pub(crate) fn balances(who: &u64) -> (u64, u64) {
let a = Balances::account(who);
(a.free, a.reserved)
(Balances::free_balance(who), Balances::reserved_balance(who))
}
pub(crate) fn locks(who: &u64) -> Vec<u64> {
+5 -6
View File
@@ -688,20 +688,19 @@ mod tests {
type AvailableBlockRatio = AvailableBlockRatio;
type Version = ();
type ModuleToIndex = ();
type AccountData = pallet_balances::AccountData<u64>;
type OnNewAccount = ();
type OnReapAccount = pallet_balances::Module<Test>;
}
parameter_types! {
pub const ExistentialDeposit: u64 = 1;
pub const CreationFee: u64 = 0;
}
impl pallet_balances::Trait for Test {
type Balance = u64;
type OnReapAccount = System;
type OnNewAccount = ();
type Event = ();
type TransferPayment = ();
type DustRemoval = ();
type Event = ();
type ExistentialDeposit = ExistentialDeposit;
type CreationFee = CreationFee;
type AccountStore = System;
}
impl Trait for Test {
type Event = ();
+6 -6
View File
@@ -416,6 +416,7 @@ mod tests {
impl_outer_event!{
pub enum MetaEvent for Runtime {
system<T>,
balances<T>,
}
}
@@ -451,20 +452,19 @@ mod tests {
type MaximumBlockLength = MaximumBlockLength;
type Version = ();
type ModuleToIndex = ();
type AccountData = pallet_balances::AccountData<u64>;
type OnNewAccount = ();
type OnReapAccount = Balances;
}
parameter_types! {
pub const ExistentialDeposit: u64 = 1;
pub const CreationFee: u64 = 0;
}
impl pallet_balances::Trait for Runtime {
type Balance = u64;
type OnReapAccount = System;
type OnNewAccount = ();
type Event = MetaEvent;
type DustRemoval = ();
type TransferPayment = ();
type ExistentialDeposit = ExistentialDeposit;
type CreationFee = CreationFee;
type AccountStore = System;
}
parameter_types! {
@@ -559,7 +559,7 @@ mod tests {
header: Header {
parent_hash: [69u8; 32].into(),
number: 1,
state_root: hex!("a0b84fec49718caf59350dab6ec2993f12db399a7cccdb80f3cf79618ed93bd8").into(),
state_root: hex!("96797237079b6d6ffab7a47f90ee257a439a0e8268bdab3fe2f1e52572b101de").into(),
extrinsics_root: hex!("03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314").into(),
digest: Digest { logs: vec![], },
},
@@ -261,6 +261,9 @@ mod tests {
type MaximumBlockLength = MaximumBlockLength;
type Version = ();
type ModuleToIndex = ();
type AccountData = ();
type OnNewAccount = ();
type OnReapAccount = ();
}
parameter_types! {
pub const WindowSize: u64 = 11;
+27 -16
View File
@@ -166,7 +166,7 @@ use frame_support::{
decl_event, decl_module, decl_storage, ensure, decl_error,
traits::{
Currency, ExistenceRequirement, Imbalance, LockIdentifier, LockableCurrency, ReservableCurrency,
SignedImbalance, UpdateBalanceOutcome, WithdrawReason, WithdrawReasons, TryDrop,
SignedImbalance, WithdrawReason, WithdrawReasons, TryDrop, BalanceStatus,
},
Parameter, StorageMap,
};
@@ -714,8 +714,8 @@ impl<T: Trait> Module<T> {
}
}
/// Move up to `amount` from reserved balance of account `who` to free balance of account
/// `beneficiary`.
/// Move up to `amount` from reserved balance of account `who` to balance of account
/// `beneficiary`, either free or reserved depending on `status`.
///
/// As much funds up to `amount` will be moved as possible. If this is less than `amount`, then
/// the `remaining` would be returned, else `Zero::zero()`.
@@ -726,13 +726,23 @@ impl<T: Trait> Module<T> {
who: &T::AccountId,
beneficiary: &T::AccountId,
amount: T::Balance,
status: BalanceStatus,
) -> T::Balance {
let b = Self::reserved_balance(asset_id, who);
let slash = sp_std::cmp::min(b, amount);
let original_free_balance = Self::free_balance(asset_id, beneficiary);
let new_free_balance = original_free_balance + slash;
Self::set_free_balance(asset_id, beneficiary, new_free_balance);
match status {
BalanceStatus::Free => {
let original_free_balance = Self::free_balance(asset_id, beneficiary);
let new_free_balance = original_free_balance + slash;
Self::set_free_balance(asset_id, beneficiary, new_free_balance);
}
BalanceStatus::Reserved => {
let original_reserved_balance = Self::reserved_balance(asset_id, beneficiary);
let new_reserved_balance = original_reserved_balance + slash;
Self::set_reserved_balance(asset_id, beneficiary, new_reserved_balance);
}
}
let new_reserve_balance = b - slash;
Self::set_reserved_balance(asset_id, who, new_reserve_balance);
@@ -1080,8 +1090,8 @@ mod imbalances {
// its type declaration).
// This works as long as `increase_total_issuance_by` doesn't use the Imbalance
// types (basically for charging fees).
// This should eventually be refactored so that the three type items that do
// depend on the Imbalance type (TransactionPayment, TransferPayment, DustRemoval)
// This should eventually be refactored so that the two type items that do
// depend on the Imbalance type (TransactionPayment, DustRemoval)
// are placed in their own SRML module.
struct ElevatedTrait<T: Subtrait>(T);
impl<T: Subtrait> Clone for ElevatedTrait<T> {
@@ -1106,12 +1116,15 @@ impl<T: Subtrait> frame_system::Trait for ElevatedTrait<T> {
type Lookup = T::Lookup;
type Header = T::Header;
type Event = ();
type BlockHashCount = T::BlockHashCount;
type MaximumBlockWeight = T::MaximumBlockWeight;
type MaximumBlockLength = T::MaximumBlockLength;
type AvailableBlockRatio = T::AvailableBlockRatio;
type BlockHashCount = T::BlockHashCount;
type Version = T::Version;
type ModuleToIndex = ();
type AccountData = ();
type OnNewAccount = ();
type OnReapAccount = ();
}
impl<T: Subtrait> Trait for ElevatedTrait<T> {
type Balance = T::Balance;
@@ -1189,7 +1202,7 @@ where
}
fn deposit_creating(who: &T::AccountId, value: Self::Balance) -> Self::PositiveImbalance {
let (imbalance, _) = Self::make_free_balance_be(who, Self::free_balance(who) + value);
let imbalance = Self::make_free_balance_be(who, Self::free_balance(who) + value);
if let SignedImbalance::Positive(p) = imbalance {
p
} else {
@@ -1201,10 +1214,7 @@ where
fn make_free_balance_be(
who: &T::AccountId,
balance: Self::Balance,
) -> (
SignedImbalance<Self::Balance, Self::PositiveImbalance>,
UpdateBalanceOutcome,
) {
) -> SignedImbalance<Self::Balance, Self::PositiveImbalance> {
let original = <Module<T>>::free_balance(&U::asset_id(), who);
let imbalance = if original <= balance {
SignedImbalance::Positive(PositiveImbalance::new(balance - original))
@@ -1212,7 +1222,7 @@ where
SignedImbalance::Negative(NegativeImbalance::new(original - balance))
};
<Module<T>>::set_free_balance(&U::asset_id(), who, balance);
(imbalance, UpdateBalanceOutcome::Updated)
imbalance
}
fn can_slash(who: &T::AccountId, value: Self::Balance) -> bool {
@@ -1288,8 +1298,9 @@ where
slashed: &T::AccountId,
beneficiary: &T::AccountId,
value: Self::Balance,
status: BalanceStatus,
) -> result::Result<Self::Balance, DispatchError> {
Ok(<Module<T>>::repatriate_reserved(&U::asset_id(), slashed, beneficiary, value))
Ok(<Module<T>>::repatriate_reserved(&U::asset_id(), slashed, beneficiary, value, status))
}
}
+5 -1
View File
@@ -31,7 +31,7 @@ use frame_support::{parameter_types, impl_outer_event, impl_outer_origin, weight
use super::*;
impl_outer_origin! {
pub enum Origin for Test where system = frame_system {}
pub enum Origin for Test where system = frame_system {}
}
// For testing the module, we construct most of a mock runtime. This means
@@ -62,6 +62,9 @@ impl frame_system::Trait for Test {
type BlockHashCount = BlockHashCount;
type Version = ();
type ModuleToIndex = ();
type AccountData = ();
type OnNewAccount = ();
type OnReapAccount = ();
}
impl Trait for Test {
@@ -77,6 +80,7 @@ mod generic_asset {
use frame_system as system;
impl_outer_event! {
pub enum TestEvent for Test {
system<T>,
generic_asset<T>,
}
}
+2 -2
View File
@@ -556,7 +556,7 @@ fn slash_reserved_should_return_none() {
fn repatriate_reserved_return_amount_substracted_by_slash_amount() {
ExtBuilder::default().build().execute_with(|| {
GenericAsset::set_reserved_balance(&1, &0, 100);
assert_eq!(GenericAsset::repatriate_reserved(&1, &0, &1, 130), 30);
assert_eq!(GenericAsset::repatriate_reserved(&1, &0, &1, 130, BalanceStatus::Free), 30);
});
}
@@ -571,7 +571,7 @@ fn repatriate_reserved_return_amount_substracted_by_slash_amount() {
fn repatriate_reserved_return_none() {
ExtBuilder::default().build().execute_with(|| {
GenericAsset::set_reserved_balance(&1, &0, 100);
assert_eq!(GenericAsset::repatriate_reserved(&1, &0, &1, 90), 0);
assert_eq!(GenericAsset::repatriate_reserved(&1, &0, &1, 90, BalanceStatus::Free), 0);
});
}
+4
View File
@@ -65,6 +65,9 @@ impl frame_system::Trait for Test {
type AvailableBlockRatio = AvailableBlockRatio;
type Version = ();
type ModuleToIndex = ();
type AccountData = ();
type OnNewAccount = ();
type OnReapAccount = ();
}
mod grandpa {
@@ -73,6 +76,7 @@ mod grandpa {
impl_outer_event!{
pub enum TestEvent for Test {
system<T>,
grandpa,
}
}
+13 -13
View File
@@ -148,7 +148,7 @@ impl<T: Trait> BenchmarkingSetup<T, crate::Call<T>, RawOrigin<T::AccountId>> for
Judgement::Reasonable
)?;
}
// Create identity info with x additional fields
let x = components.iter().find(|&c| c.0 == BenchmarkParameter::X).unwrap().1;
// 32 byte data that we reuse below
@@ -171,7 +171,7 @@ impl<T: Trait> BenchmarkingSetup<T, crate::Call<T>, RawOrigin<T::AccountId>> for
fn instance(&self, components: &[(BenchmarkParameter, u32)])
-> Result<(crate::Call<T>, RawOrigin<T::AccountId>), &'static str>
{
{
// Generic data to be used.
let data = Data::Raw(vec![0; 32]);
@@ -212,7 +212,7 @@ impl<T: Trait> BenchmarkingSetup<T, crate::Call<T>, RawOrigin<T::AccountId>> for
fn instance(&self, components: &[(BenchmarkParameter, u32)])
-> Result<(crate::Call<T>, RawOrigin<T::AccountId>), &'static str>
{
{
// The target user
let caller = account::<T>("caller", 0);
let caller_origin: <T as frame_system::Trait>::Origin = RawOrigin::Signed(caller.clone()).into();
@@ -262,7 +262,7 @@ impl<T: Trait> BenchmarkingSetup<T, crate::Call<T>, RawOrigin<T::AccountId>> for
fn instance(&self, components: &[(BenchmarkParameter, u32)])
-> Result<(crate::Call<T>, RawOrigin<T::AccountId>), &'static str>
{
{
// The target user
let caller = account::<T>("caller", 0);
let caller_origin: <T as frame_system::Trait>::Origin = RawOrigin::Signed(caller.clone()).into();
@@ -296,7 +296,7 @@ impl<T: Trait> BenchmarkingSetup<T, crate::Call<T>, RawOrigin<T::AccountId>> for
fn instance(&self, components: &[(BenchmarkParameter, u32)])
-> Result<(crate::Call<T>, RawOrigin<T::AccountId>), &'static str>
{
{
// The target user
let caller = account::<T>("caller", 0);
let caller_origin: <T as frame_system::Trait>::Origin = RawOrigin::Signed(caller.clone()).into();
@@ -330,7 +330,7 @@ impl<T: Trait> BenchmarkingSetup<T, crate::Call<T>, RawOrigin<T::AccountId>> for
fn instance(&self, components: &[(BenchmarkParameter, u32)])
-> Result<(crate::Call<T>, RawOrigin<T::AccountId>), &'static str>
{
{
// The target user
let caller = account::<T>("caller", 0);
let _ = T::Currency::make_free_balance_be(&caller, BalanceOf::<T>::max_value());
@@ -359,7 +359,7 @@ impl<T: Trait> BenchmarkingSetup<T, crate::Call<T>, RawOrigin<T::AccountId>> for
fn instance(&self, components: &[(BenchmarkParameter, u32)])
-> Result<(crate::Call<T>, RawOrigin<T::AccountId>), &'static str>
{
{
// The target user
let caller = account::<T>("caller", 0);
let _ = T::Currency::make_free_balance_be(&caller, BalanceOf::<T>::max_value());
@@ -388,7 +388,7 @@ impl<T: Trait> BenchmarkingSetup<T, crate::Call<T>, RawOrigin<T::AccountId>> for
fn instance(&self, components: &[(BenchmarkParameter, u32)])
-> Result<(crate::Call<T>, RawOrigin<T::AccountId>), &'static str>
{
{
// The target user
let caller = account::<T>("caller", 0);
let _ = T::Currency::make_free_balance_be(&caller, BalanceOf::<T>::max_value());
@@ -410,7 +410,7 @@ impl<T: Trait> BenchmarkingSetup<T, crate::Call<T>, RawOrigin<T::AccountId>> for
}
}
// Benchmark `provide_judgement` extrinsic.
// Benchmark `provide_judgement` extrinsic.g
struct ProvideJudgement;
impl<T: Trait> BenchmarkingSetup<T, crate::Call<T>, RawOrigin<T::AccountId>> for ProvideJudgement {
@@ -425,7 +425,7 @@ impl<T: Trait> BenchmarkingSetup<T, crate::Call<T>, RawOrigin<T::AccountId>> for
fn instance(&self, components: &[(BenchmarkParameter, u32)])
-> Result<(crate::Call<T>, RawOrigin<T::AccountId>), &'static str>
{
{
// Add r registrars
let r = components.iter().find(|&c| c.0 == BenchmarkParameter::R).unwrap().1;
benchmarking::add_registrars::<T>(r)?;
@@ -477,7 +477,7 @@ impl<T: Trait> BenchmarkingSetup<T, crate::Call<T>, RawOrigin<T::AccountId>> for
fn instance(&self, components: &[(BenchmarkParameter, u32)])
-> Result<(crate::Call<T>, RawOrigin<T::AccountId>), &'static str>
{
{
// The target user
let caller = account::<T>("caller", 0);
let caller_origin: <T as frame_system::Trait>::Origin = RawOrigin::Signed(caller.clone()).into();
@@ -550,8 +550,8 @@ impl<T: Trait> Benchmarking<BenchmarkResults> for Module<T> {
sp_io::benchmarking::commit_db();
sp_io::benchmarking::wipe_db();
// first one is set_identity.
let components = <SelectedBenchmark as BenchmarkingSetup<T, crate::Call<T>, RawOrigin<T::AccountId>>>::components(&selected_benchmark);
// first one is set_identity.
let components = <SelectedBenchmark as BenchmarkingSetup<T, crate::Call<T>, RawOrigin<T::AccountId>>>::components(&selected_benchmark);
// results go here
let mut results: Vec<BenchmarkResults> = Vec::new();
// Select the component we will be benchmarking. Each component will be benchmarked.
+6 -7
View File
@@ -73,7 +73,7 @@ use sp_runtime::{DispatchResult, RuntimeDebug};
use sp_runtime::traits::{StaticLookup, EnsureOrigin, Zero, AppendZerosInput};
use frame_support::{
decl_module, decl_event, decl_storage, ensure, decl_error,
traits::{Currency, ReservableCurrency, OnUnbalanced, Get},
traits::{Currency, ReservableCurrency, OnUnbalanced, Get, BalanceStatus},
weights::SimpleDispatchInfo,
};
use frame_system::{self as system, ensure_signed, ensure_root};
@@ -823,7 +823,7 @@ decl_module! {
match id.judgements.binary_search_by_key(&reg_index, |x| x.0) {
Ok(position) => {
if let Judgement::FeePaid(fee) = id.judgements[position].1 {
let _ = T::Currency::repatriate_reserved(&target, &sender, fee);
let _ = T::Currency::repatriate_reserved(&target, &sender, fee, BalanceStatus::Free);
}
id.judgements[position] = item
}
@@ -924,20 +924,19 @@ mod tests {
type AvailableBlockRatio = AvailableBlockRatio;
type Version = ();
type ModuleToIndex = ();
type AccountData = pallet_balances::AccountData<u64>;
type OnNewAccount = ();
type OnReapAccount = Balances;
}
parameter_types! {
pub const ExistentialDeposit: u64 = 1;
pub const CreationFee: u64 = 0;
}
impl pallet_balances::Trait for Test {
type Balance = u64;
type OnReapAccount = System;
type OnNewAccount = ();
type Event = ();
type TransferPayment = ();
type DustRemoval = ();
type ExistentialDeposit = ExistentialDeposit;
type CreationFee = CreationFee;
type AccountStore = System;
}
parameter_types! {
pub const BasicDeposit: u64 = 10;
+3
View File
@@ -115,6 +115,9 @@ impl frame_system::Trait for Runtime {
type AvailableBlockRatio = AvailableBlockRatio;
type Version = ();
type ModuleToIndex = ();
type AccountData = ();
type OnNewAccount = ();
type OnReapAccount = ();
}
parameter_types! {
+3
View File
@@ -16,6 +16,9 @@ sp-core = { version = "2.0.0", default-features = false, path = "../../primitive
frame-support = { version = "2.0.0", default-features = false, path = "../support" }
frame-system = { version = "2.0.0", default-features = false, path = "../system" }
[dev-dependencies]
pallet-balances = { version = "2.0.0", path = "../balances" }
[features]
default = ["std"]
std = [
+181 -142
View File
@@ -19,41 +19,24 @@
#![cfg_attr(not(feature = "std"), no_std)]
use sp_std::{prelude::*, marker::PhantomData, convert::TryInto};
use codec::{Encode, Codec};
use frame_support::{Parameter, decl_module, decl_event, decl_storage};
use sp_runtime::traits::{One, AtLeast32Bit, StaticLookup, Member, LookupError};
use frame_system::{IsDeadAccount, OnNewAccount};
use sp_std::prelude::*;
use codec::Codec;
use sp_runtime::traits::{
StaticLookup, Member, LookupError, Zero, One, BlakeTwo256, Hash, Saturating, AtLeast32Bit
};
use frame_support::{Parameter, decl_module, decl_error, decl_event, decl_storage, ensure};
use frame_support::dispatch::DispatchResult;
use frame_support::traits::{Currency, ReservableCurrency, Get, BalanceStatus::Reserved};
use frame_support::storage::migration::take_storage_value;
use frame_system::{ensure_signed, ensure_root};
use self::address::Address as RawAddress;
mod mock;
pub mod address;
mod tests;
/// Number of account IDs stored per enum set.
const ENUM_SET_SIZE: u32 = 64;
pub type Address<T> = RawAddress<<T as frame_system::Trait>::AccountId, <T as Trait>::AccountIndex>;
/// Turn an Id into an Index, or None for the purpose of getting
/// a hint at a possibly desired index.
pub trait ResolveHint<AccountId, AccountIndex> {
/// Turn an Id into an Index, or None for the purpose of getting
/// a hint at a possibly desired index.
fn resolve_hint(who: &AccountId) -> Option<AccountIndex>;
}
/// Simple encode-based resolve hint implementation.
pub struct SimpleResolveHint<AccountId, AccountIndex>(PhantomData<(AccountId, AccountIndex)>);
impl<AccountId: Encode, AccountIndex: From<u32>>
ResolveHint<AccountId, AccountIndex> for SimpleResolveHint<AccountId, AccountIndex>
{
fn resolve_hint(who: &AccountId) -> Option<AccountIndex> {
Some(AccountIndex::from(who.using_encoded(|e| e[0] as u32 + e[1] as u32 * 256)))
}
}
type BalanceOf<T> = <<T as Trait>::Currency as Currency<<T as frame_system::Trait>::AccountId>>::Balance;
/// The module's config trait.
pub trait Trait: frame_system::Trait {
@@ -61,19 +44,28 @@ pub trait Trait: frame_system::Trait {
/// can hold.
type AccountIndex: Parameter + Member + Codec + Default + AtLeast32Bit + Copy;
/// Whether an account is dead or not.
type IsDeadAccount: IsDeadAccount<Self::AccountId>;
/// The currency trait.
type Currency: ReservableCurrency<Self::AccountId>;
/// How to turn an id into an index.
type ResolveHint: ResolveHint<Self::AccountId, Self::AccountIndex>;
/// The deposit needed for reserving an index.
type Deposit: Get<BalanceOf<Self>>;
/// The overarching event type.
type Event: From<Event<Self>> + Into<<Self as frame_system::Trait>::Event>;
}
decl_module! {
pub struct Module<T: Trait> for enum Call where origin: T::Origin, system = frame_system {
fn deposit_event() = default;
decl_storage! {
trait Store for Module<T: Trait> as Indices {
/// The lookup from index to account.
pub Accounts build(|config: &GenesisConfig<T>|
config.indices.iter()
.cloned()
.map(|(a, b)| (a, (b, Zero::zero())))
.collect::<Vec<_>>()
): map hasher(blake2_128_concat) T::AccountIndex => Option<(T::AccountId, BalanceOf<T>)>;
}
add_extra_genesis {
config(indices): Vec<(T::AccountIndex, T::AccountId)>;
}
}
@@ -82,36 +74,146 @@ decl_event!(
<T as frame_system::Trait>::AccountId,
<T as Trait>::AccountIndex
{
/// A new account index was assigned.
///
/// This event is not triggered when an existing index is reassigned
/// to another `AccountId`.
NewAccountIndex(AccountId, AccountIndex),
/// A account index was assigned.
IndexAssigned(AccountId, AccountIndex),
/// A account index has been freed up (unassigned).
IndexFreed(AccountIndex),
}
);
decl_storage! {
trait Store for Module<T: Trait> as Indices {
/// The next free enumeration set.
pub NextEnumSet get(fn next_enum_set) build(|config: &GenesisConfig<T>| {
(config.ids.len() as u32 / ENUM_SET_SIZE).into()
}): T::AccountIndex;
/// The enumeration sets.
pub EnumSet get(fn enum_set) build(|config: &GenesisConfig<T>| {
(0..((config.ids.len() as u32) + ENUM_SET_SIZE - 1) / ENUM_SET_SIZE)
.map(|i| (
i.into(),
config.ids[
(i * ENUM_SET_SIZE) as usize..
config.ids.len().min(((i + 1) * ENUM_SET_SIZE) as usize)
].to_owned(),
))
.collect::<Vec<_>>()
}): map hasher(blake2_256) T::AccountIndex => Vec<T::AccountId>;
decl_error! {
pub enum Error for Module<T: Trait> {
/// The index was not already assigned.
NotAssigned,
/// The index is assigned to another account.
NotOwner,
/// The index was not available.
InUse,
/// The source and destination accounts are identical.
NotTransfer,
}
add_extra_genesis {
config(ids): Vec<T::AccountId>;
}
decl_module! {
pub struct Module<T: Trait> for enum Call where origin: T::Origin, system = frame_system {
fn deposit_event() = default;
fn on_initialize() {
Self::migrations();
}
/// Assign an previously unassigned index.
///
/// Payment: `Deposit` is reserved from the sender account.
///
/// The dispatch origin for this call must be _Signed_.
///
/// - `index`: the index to be claimed. This must not be in use.
///
/// Emits `IndexAssigned` if successful.
///
/// # <weight>
/// - `O(1)`.
/// - One storage mutation (codec `O(1)`).
/// - One reserve operation.
/// - One event.
/// # </weight>
fn claim(origin, index: T::AccountIndex) {
let who = ensure_signed(origin)?;
Accounts::<T>::try_mutate(index, |maybe_value| {
ensure!(maybe_value.is_none(), Error::<T>::InUse);
*maybe_value = Some((who.clone(), T::Deposit::get()));
T::Currency::reserve(&who, T::Deposit::get())
})?;
Self::deposit_event(RawEvent::IndexAssigned(who, index));
}
/// Assign an index already owned by the sender to another account. The balance reservation
/// is effectively transfered to the new account.
///
/// The dispatch origin for this call must be _Signed_.
///
/// - `index`: the index to be re-assigned. This must be owned by the sender.
/// - `new`: the new owner of the index. This function is a no-op if it is equal to sender.
///
/// Emits `IndexAssigned` if successful.
///
/// # <weight>
/// - `O(1)`.
/// - One storage mutation (codec `O(1)`).
/// - One transfer operation.
/// - One event.
/// # </weight>
fn transfer(origin, new: T::AccountId, index: T::AccountIndex) {
let who = ensure_signed(origin)?;
ensure!(who != new, Error::<T>::NotTransfer);
Accounts::<T>::try_mutate(index, |maybe_value| -> DispatchResult {
let (account, amount) = maybe_value.take().ok_or(Error::<T>::NotAssigned)?;
ensure!(&account == &who, Error::<T>::NotOwner);
let lost = T::Currency::repatriate_reserved(&who, &new, amount, Reserved)?;
*maybe_value = Some((new.clone(), amount.saturating_sub(lost)));
Ok(())
})?;
Self::deposit_event(RawEvent::IndexAssigned(new, index));
}
/// Free up an index owned by the sender.
///
/// Payment: Any previous deposit placed for the index is unreserved in the sender account.
///
/// The dispatch origin for this call must be _Signed_ and the sender must own the index.
///
/// - `index`: the index to be freed. This must be owned by the sender.
///
/// Emits `IndexFreed` if successful.
///
/// # <weight>
/// - `O(1)`.
/// - One storage mutation (codec `O(1)`).
/// - One reserve operation.
/// - One event.
/// # </weight>
fn free(origin, index: T::AccountIndex) {
let who = ensure_signed(origin)?;
Accounts::<T>::try_mutate(index, |maybe_value| -> DispatchResult {
let (account, amount) = maybe_value.take().ok_or(Error::<T>::NotAssigned)?;
ensure!(&account == &who, Error::<T>::NotOwner);
T::Currency::unreserve(&who, amount);
Ok(())
})?;
Self::deposit_event(RawEvent::IndexFreed(index));
}
/// Force an index to an account. This doesn't require a deposit. If the index is already
/// held, then any deposit is reimbursed to its current owner.
///
/// The dispatch origin for this call must be _Root_.
///
/// - `index`: the index to be (re-)assigned.
/// - `new`: the new owner of the index. This function is a no-op if it is equal to sender.
///
/// Emits `IndexAssigned` if successful.
///
/// # <weight>
/// - `O(1)`.
/// - One storage mutation (codec `O(1)`).
/// - Up to one reserve operation.
/// - One event.
/// # </weight>
fn force_transfer(origin, new: T::AccountId, index: T::AccountIndex) {
ensure_root(origin)?;
Accounts::<T>::mutate(index, |maybe_value| {
if let Some((account, amount)) = maybe_value.take() {
T::Currency::unreserve(&account, amount);
}
*maybe_value = Some((new.clone(), Zero::zero()));
});
Self::deposit_event(RawEvent::IndexAssigned(new, index));
}
}
}
@@ -120,22 +222,7 @@ impl<T: Trait> Module<T> {
/// Lookup an T::AccountIndex to get an Id, if there's one there.
pub fn lookup_index(index: T::AccountIndex) -> Option<T::AccountId> {
let enum_set_size = Self::enum_set_size();
let set = Self::enum_set(index / enum_set_size);
let i: usize = (index % enum_set_size).try_into().ok()?;
set.get(i).cloned()
}
/// `true` if the account `index` is ready for reclaim.
pub fn can_reclaim(try_index: T::AccountIndex) -> bool {
let enum_set_size = Self::enum_set_size();
let try_set = Self::enum_set(try_index / enum_set_size);
let maybe_usize: Result<usize, _> = (try_index % enum_set_size).try_into();
if let Ok(i) = maybe_usize {
i < try_set.len() && T::IsDeadAccount::is_dead_account(&try_set[i])
} else {
false
}
Accounts::<T>::get(index).map(|x| x.0)
}
/// Lookup an address to get an Id, if there's one there.
@@ -148,76 +235,28 @@ impl<T: Trait> Module<T> {
}
}
// PUBLIC MUTABLES (DANGEROUS)
/// Do any migrations.
fn migrations() {
if let Some(set_count) = take_storage_value::<T::AccountIndex>(b"Indices", b"NextEnumSet", b"") {
// migrations need doing.
let set_size: T::AccountIndex = 64.into();
fn enum_set_size() -> T::AccountIndex {
ENUM_SET_SIZE.into()
}
}
impl<T: Trait> OnNewAccount<T::AccountId> for Module<T> {
// Implementation of the config type managing the creation of new accounts.
// See Balances module for a concrete example.
//
// # <weight>
// - Independent of the arguments.
// - Given the correct value of `Self::next_enum_set`, it always has a limited
// number of reads and writes and no complex computation.
//
// As for storage, calling this function with _non-dead-indices_ will linearly grow the length of
// of `Self::enum_set`. Appropriate economic incentives should exist to make callers of this
// function provide a `who` argument that reclaims a dead account.
//
// At the time of this writing, only the Balances module calls this function upon creation
// of new accounts.
// # </weight>
fn on_new_account(who: &T::AccountId) {
let enum_set_size = Self::enum_set_size();
let next_set_index = Self::next_enum_set();
if let Some(try_index) = T::ResolveHint::resolve_hint(who) {
// then check to see if this account id identifies a dead account index.
let set_index = try_index / enum_set_size;
let mut try_set = Self::enum_set(set_index);
if let Ok(item_index) = (try_index % enum_set_size).try_into() {
if item_index < try_set.len() {
if T::IsDeadAccount::is_dead_account(&try_set[item_index]) {
// yup - this index refers to a dead account. can be reused.
try_set[item_index] = who.clone();
<EnumSet<T>>::insert(set_index, try_set);
return
let mut set_index: T::AccountIndex = Zero::zero();
while set_index < set_count {
let maybe_accounts = take_storage_value::<Vec<T::AccountId>>(b"Indices", b"EnumSet", BlakeTwo256::hash_of(&set_index).as_ref());
if let Some(accounts) = maybe_accounts {
for (item_index, target) in accounts.into_iter().enumerate() {
if target != T::AccountId::default() && !T::Currency::total_balance(&target).is_zero() {
let index = set_index * set_size + T::AccountIndex::from(item_index as u32);
Accounts::<T>::insert(index, (target, BalanceOf::<T>::zero()));
}
}
} else {
break;
}
set_index += One::one();
}
}
// insert normally as a back up
let mut set_index = next_set_index;
// defensive only: this loop should never iterate since we keep NextEnumSet up to date
// later.
let mut set = loop {
let set = Self::enum_set(set_index);
if set.len() < ENUM_SET_SIZE as usize {
break set;
}
set_index += One::one();
};
let index = set_index * enum_set_size + T::AccountIndex::from(set.len() as u32);
// update set.
set.push(who.clone());
// keep NextEnumSet up to date
if set.len() == ENUM_SET_SIZE as usize {
<NextEnumSet<T>>::put(set_index + One::one());
}
// write set.
<EnumSet<T>>::insert(set_index, set);
Self::deposit_event(RawEvent::NewAccountIndex(who.clone(), index));
}
}
+44 -53
View File
@@ -18,51 +18,29 @@
#![cfg(test)]
use std::{cell::RefCell, collections::HashSet};
use sp_runtime::testing::Header;
use sp_runtime::Perbill;
use sp_core::H256;
use frame_support::{impl_outer_origin, parameter_types, weights::Weight};
use crate::{GenesisConfig, Module, Trait, IsDeadAccount, OnNewAccount, ResolveHint};
use frame_support::{impl_outer_origin, impl_outer_event, parameter_types, weights::Weight};
use crate::{self as indices, Module, Trait};
use frame_system as system;
use pallet_balances as balances;
impl_outer_origin!{
pub enum Origin for Runtime where system = frame_system {}
pub enum Origin for Test where system = frame_system {}
}
thread_local! {
static ALIVE: RefCell<HashSet<u64>> = Default::default();
}
pub fn make_account(who: u64) {
ALIVE.with(|a| a.borrow_mut().insert(who));
Indices::on_new_account(&who);
}
pub fn kill_account(who: u64) {
ALIVE.with(|a| a.borrow_mut().remove(&who));
}
pub struct TestIsDeadAccount {}
impl IsDeadAccount<u64> for TestIsDeadAccount {
fn is_dead_account(who: &u64) -> bool {
!ALIVE.with(|a| a.borrow_mut().contains(who))
}
}
pub struct TestResolveHint;
impl ResolveHint<u64, u64> for TestResolveHint {
fn resolve_hint(who: &u64) -> Option<u64> {
if *who < 256 {
None
} else {
Some(*who - 256)
}
impl_outer_event!{
pub enum MetaEvent for Test {
system<T>,
balances<T>,
indices<T>,
}
}
// Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted.
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Runtime;
pub struct Test;
parameter_types! {
pub const BlockHashCount: u64 = 250;
pub const MaximumBlockWeight: Weight = 1024;
@@ -70,46 +48,59 @@ parameter_types! {
pub const AvailableBlockRatio: Perbill = Perbill::one();
}
impl frame_system::Trait for Runtime {
impl frame_system::Trait for Test {
type Origin = Origin;
type Call = ();
type Index = u64;
type BlockNumber = u64;
type Call = ();
type Hash = H256;
type Hashing = ::sp_runtime::traits::BlakeTwo256;
type AccountId = u64;
type Lookup = Indices;
type Header = Header;
type Event = ();
type Event = MetaEvent;
type BlockHashCount = BlockHashCount;
type MaximumBlockWeight = MaximumBlockWeight;
type MaximumBlockLength = MaximumBlockLength;
type AvailableBlockRatio = AvailableBlockRatio;
type Version = ();
type ModuleToIndex = ();
type AccountData = pallet_balances::AccountData<u64>;
type OnNewAccount = ();
type OnReapAccount = Balances;
}
impl Trait for Runtime {
parameter_types! {
pub const ExistentialDeposit: u64 = 1;
}
impl pallet_balances::Trait for Test {
type Balance = u64;
type DustRemoval = ();
type Event = MetaEvent;
type ExistentialDeposit = ExistentialDeposit;
type AccountStore = System;
}
parameter_types! {
pub const Deposit: u64 = 1;
}
impl Trait for Test {
type AccountIndex = u64;
type IsDeadAccount = TestIsDeadAccount;
type ResolveHint = TestResolveHint;
type Event = ();
type Currency = Balances;
type Deposit = Deposit;
type Event = MetaEvent;
}
pub fn new_test_ext() -> sp_io::TestExternalities {
{
ALIVE.with(|a| {
let mut h = a.borrow_mut();
h.clear();
for i in 1..5 { h.insert(i); }
});
}
let mut t = frame_system::GenesisConfig::default().build_storage::<Runtime>().unwrap();
GenesisConfig::<Runtime> {
ids: vec![1, 2, 3, 4]
let mut t = frame_system::GenesisConfig::default().build_storage::<Test>().unwrap();
pallet_balances::GenesisConfig::<Test>{
balances: vec![(1, 10), (2, 20), (3, 30), (4, 40), (5, 50), (6, 60)],
}.assimilate_storage(&mut t).unwrap();
t.into()
}
pub type Indices = Module<Runtime>;
pub type System = frame_system::Module<Test>;
pub type Balances = pallet_balances::Module<Test>;
pub type Indices = Module<Test>;
+59 -23
View File
@@ -19,49 +19,85 @@
#![cfg(test)]
use super::*;
use crate::mock::{Indices, new_test_ext, make_account, kill_account, TestIsDeadAccount};
use super::mock::*;
use frame_support::{assert_ok, assert_noop};
use pallet_balances::Error as BalancesError;
#[test]
fn claiming_should_work() {
new_test_ext().execute_with(|| {
assert_noop!(Indices::claim(Some(0).into(), 0), BalancesError::<Test, _>::InsufficientBalance);
assert_ok!(Indices::claim(Some(1).into(), 0));
assert_noop!(Indices::claim(Some(2).into(), 0), Error::<Test>::InUse);
assert_eq!(Balances::reserved_balance(1), 1);
});
}
#[test]
fn freeing_should_work() {
new_test_ext().execute_with(|| {
assert_ok!(Indices::claim(Some(1).into(), 0));
assert_ok!(Indices::claim(Some(2).into(), 1));
assert_noop!(Indices::free(Some(0).into(), 0), Error::<Test>::NotOwner);
assert_noop!(Indices::free(Some(1).into(), 1), Error::<Test>::NotOwner);
assert_noop!(Indices::free(Some(1).into(), 2), Error::<Test>::NotAssigned);
assert_ok!(Indices::free(Some(1).into(), 0));
assert_eq!(Balances::reserved_balance(1), 0);
assert_noop!(Indices::free(Some(1).into(), 0), Error::<Test>::NotAssigned);
});
}
#[test]
fn indexing_lookup_should_work() {
new_test_ext().execute_with(|| {
assert_ok!(Indices::claim(Some(1).into(), 0));
assert_ok!(Indices::claim(Some(2).into(), 1));
assert_eq!(Indices::lookup_index(0), Some(1));
assert_eq!(Indices::lookup_index(1), Some(2));
assert_eq!(Indices::lookup_index(2), Some(3));
assert_eq!(Indices::lookup_index(3), Some(4));
assert_eq!(Indices::lookup_index(4), None);
assert_eq!(Indices::lookup_index(2), None);
});
}
#[test]
fn default_indexing_on_new_accounts_should_work() {
fn reclaim_index_on_accounts_should_work() {
new_test_ext().execute_with(|| {
assert_eq!(Indices::lookup_index(4), None);
make_account(5);
assert_eq!(Indices::lookup_index(4), Some(5));
assert_ok!(Indices::claim(Some(1).into(), 0));
assert_ok!(Indices::free(Some(1).into(), 0));
assert_ok!(Indices::claim(Some(2).into(), 0));
assert_eq!(Indices::lookup_index(0), Some(2));
assert_eq!(Balances::reserved_balance(2), 1);
});
}
#[test]
fn reclaim_indexing_on_new_accounts_should_work() {
fn transfer_index_on_accounts_should_work() {
new_test_ext().execute_with(|| {
assert_eq!(Indices::lookup_index(1), Some(2));
assert_eq!(Indices::lookup_index(4), None);
kill_account(2); // index 1 no longer locked to id 2
make_account(1 + 256); // id 257 takes index 1.
assert_eq!(Indices::lookup_index(1), Some(257));
assert_ok!(Indices::claim(Some(1).into(), 0));
assert_noop!(Indices::transfer(Some(1).into(), 2, 1), Error::<Test>::NotAssigned);
assert_noop!(Indices::transfer(Some(2).into(), 3, 0), Error::<Test>::NotOwner);
assert_ok!(Indices::transfer(Some(1).into(), 3, 0));
assert_eq!(Balances::reserved_balance(1), 0);
assert_eq!(Balances::reserved_balance(3), 1);
assert_eq!(Indices::lookup_index(0), Some(3));
});
}
#[test]
fn alive_account_should_prevent_reclaim() {
fn force_transfer_index_on_preowned_should_work() {
new_test_ext().execute_with(|| {
assert!(!TestIsDeadAccount::is_dead_account(&2));
assert_eq!(Indices::lookup_index(1), Some(2));
assert_eq!(Indices::lookup_index(4), None);
make_account(1 + 256); // id 257 takes index 1.
assert_eq!(Indices::lookup_index(4), Some(257));
assert_ok!(Indices::claim(Some(1).into(), 0));
assert_ok!(Indices::force_transfer(Origin::ROOT, 3, 0));
assert_eq!(Balances::reserved_balance(1), 0);
assert_eq!(Balances::reserved_balance(3), 0);
assert_eq!(Indices::lookup_index(0), Some(3));
});
}
#[test]
fn force_transfer_index_on_free_should_work() {
new_test_ext().execute_with(|| {
assert_ok!(Indices::force_transfer(Origin::ROOT, 3, 0));
assert_eq!(Balances::reserved_balance(3), 0);
assert_eq!(Indices::lookup_index(0), Some(3));
});
}
+3
View File
@@ -269,6 +269,9 @@ mod tests {
type AvailableBlockRatio = AvailableBlockRatio;
type Version = ();
type ModuleToIndex = ();
type AccountData = ();
type OnNewAccount = ();
type OnReapAccount = ();
}
ord_parameter_types! {
pub const One: u64 = 1;
+4 -5
View File
@@ -285,20 +285,19 @@ mod tests {
type AvailableBlockRatio = AvailableBlockRatio;
type Version = ();
type ModuleToIndex = ();
type AccountData = pallet_balances::AccountData<u64>;
type OnNewAccount = ();
type OnReapAccount = Balances;
}
parameter_types! {
pub const ExistentialDeposit: u64 = 1;
pub const CreationFee: u64 = 0;
}
impl pallet_balances::Trait for Test {
type Balance = u64;
type OnReapAccount = System;
type OnNewAccount = ();
type Event = ();
type TransferPayment = ();
type DustRemoval = ();
type ExistentialDeposit = ExistentialDeposit;
type CreationFee = CreationFee;
type AccountStore = System;
}
parameter_types! {
pub const ReservationFee: u64 = 2;
+4
View File
@@ -89,6 +89,9 @@ impl frame_system::Trait for Runtime {
type AvailableBlockRatio = AvailableBlockRatio;
type Version = ();
type ModuleToIndex = ();
type AccountData = ();
type OnNewAccount = ();
type OnReapAccount = ();
}
impl Trait for Runtime {
@@ -103,6 +106,7 @@ mod offences {
impl_outer_event! {
pub enum TestEvent for Runtime {
system<T>,
offences,
}
}
@@ -191,6 +191,9 @@ mod tests {
type MaximumBlockLength = MaximumBlockLength;
type Version = ();
type ModuleToIndex = ();
type AccountData = ();
type OnNewAccount = ();
type OnReapAccount = ();
}
type System = frame_system::Module<Test>;
+2 -2
View File
@@ -164,7 +164,7 @@ use frame_support::{
GetDispatchInfo, PaysFee, DispatchClass, ClassifyDispatch, Weight, WeighData,
SimpleDispatchInfo,
},
traits::{Currency, ReservableCurrency, Get, OnReapAccount},
traits::{Currency, ReservableCurrency, Get, OnReapAccount, BalanceStatus},
};
use frame_system::{self as system, ensure_signed, ensure_root};
@@ -587,7 +587,7 @@ decl_module! {
let active_recovery = <ActiveRecoveries<T>>::take(&who, &rescuer).ok_or(Error::<T>::NotStarted)?;
// Move the reserved funds from the rescuer to the rescued account.
// Acts like a slashing mechanism for those who try to maliciously recover accounts.
let _ = T::Currency::repatriate_reserved(&rescuer, &who, active_recovery.deposit);
let _ = T::Currency::repatriate_reserved(&rescuer, &who, active_recovery.deposit, BalanceStatus::Free);
Self::deposit_event(RawEvent::RecoveryClosed(who, rescuer));
}
+7 -7
View File
@@ -36,6 +36,7 @@ impl_outer_origin! {
impl_outer_event! {
pub enum TestEvent for Test {
system<T>,
pallet_balances<T>,
recovery<T>,
}
@@ -62,10 +63,10 @@ parameter_types! {
impl frame_system::Trait for Test {
type Origin = Origin;
type Call = Call;
type Index = u64;
type BlockNumber = u64;
type Hash = H256;
type Call = Call;
type Hashing = BlakeTwo256;
type AccountId = u64;
type Lookup = IdentityLookup<Self::AccountId>;
@@ -77,22 +78,21 @@ impl frame_system::Trait for Test {
type AvailableBlockRatio = AvailableBlockRatio;
type Version = ();
type ModuleToIndex = ();
type AccountData = pallet_balances::AccountData<u128>;
type OnNewAccount = ();
type OnReapAccount = (Balances, Recovery);
}
parameter_types! {
pub const ExistentialDeposit: u64 = 1;
pub const CreationFee: u64 = 0;
}
impl pallet_balances::Trait for Test {
type Balance = u128;
type OnReapAccount = (System, Recovery);
type OnNewAccount = ();
type Event = TestEvent;
type TransferPayment = ();
type DustRemoval = ();
type Event = TestEvent;
type ExistentialDeposit = ExistentialDeposit;
type CreationFee = CreationFee;
type AccountStore = System;
}
parameter_types! {
+4 -5
View File
@@ -47,7 +47,6 @@ parameter_types! {
pub const AvailableBlockRatio: Perbill = Perbill::one();
pub const ExistentialDeposit: u64 = 1;
pub const CreationFee: u64 = 0;
}
ord_parameter_types! {
pub const KickOrigin: u64 = 2;
@@ -71,17 +70,17 @@ impl frame_system::Trait for Test {
type AvailableBlockRatio = AvailableBlockRatio;
type Version = ();
type ModuleToIndex = ();
type AccountData = pallet_balances::AccountData<u64>;
type OnNewAccount = ();
type OnReapAccount = Balances;
}
impl pallet_balances::Trait for Test {
type Balance = u64;
type OnReapAccount = System;
type OnNewAccount = ();
type Event = ();
type TransferPayment = ();
type DustRemoval = ();
type ExistentialDeposit = ExistentialDeposit;
type CreationFee = CreationFee;
type AccountStore = System;
}
thread_local! {
+3
View File
@@ -176,6 +176,9 @@ impl frame_system::Trait for Test {
type MaximumBlockLength = MaximumBlockLength;
type Version = ();
type ModuleToIndex = ();
type AccountData = ();
type OnNewAccount = ();
type OnReapAccount = Session;
}
impl pallet_timestamp::Trait for Test {
+2 -2
View File
@@ -262,7 +262,7 @@ use sp_runtime::{Percent, ModuleId, RuntimeDebug,
use frame_support::{decl_error, decl_module, decl_storage, decl_event, ensure, dispatch::DispatchResult};
use frame_support::weights::SimpleDispatchInfo;
use frame_support::traits::{
Currency, ReservableCurrency, Randomness, Get, ChangeMembers,
Currency, ReservableCurrency, Randomness, Get, ChangeMembers, BalanceStatus,
ExistenceRequirement::AllowDeath,
};
use frame_system::{self as system, ensure_signed, ensure_root};
@@ -984,7 +984,7 @@ decl_module! {
match kind {
BidKind::Deposit(deposit) => {
// Slash deposit and move it to the society account
let _ = T::Currency::repatriate_reserved(&who, &Self::account_id(), deposit);
let _ = T::Currency::repatriate_reserved(&who, &Self::account_id(), deposit, BalanceStatus::Free);
}
BidKind::Vouch(voucher, _) => {
// Ban the voucher from vouching again
+4 -5
View File
@@ -53,7 +53,6 @@ parameter_types! {
pub const AvailableBlockRatio: Perbill = Perbill::one();
pub const ExistentialDeposit: u64 = 1;
pub const CreationFee: u64 = 0;
}
ord_parameter_types! {
@@ -78,17 +77,17 @@ impl frame_system::Trait for Test {
type AvailableBlockRatio = AvailableBlockRatio;
type Version = ();
type ModuleToIndex = ();
type OnNewAccount = ();
type OnReapAccount = Balances;
type AccountData = pallet_balances::AccountData<u64>;
}
impl pallet_balances::Trait for Test {
type Balance = u64;
type OnNewAccount = ();
type Event = ();
type TransferPayment = ();
type DustRemoval = ();
type ExistentialDeposit = ExistentialDeposit;
type CreationFee = CreationFee;
type OnReapAccount = System;
type AccountStore = System;
}
impl Trait for Test {
+4 -9
View File
@@ -250,7 +250,6 @@
mod mock;
#[cfg(test)]
mod tests;
mod migration;
mod slashing;
pub mod inflation;
@@ -761,9 +760,6 @@ decl_storage! {
/// The earliest era for which we have a pending, unapplied slash.
EarliestUnappliedSlash: Option<EraIndex>;
/// The version of storage for upgrade.
StorageVersion: u32;
}
add_extra_genesis {
config(stakers):
@@ -795,8 +791,6 @@ decl_storage! {
}, _ => Ok(())
};
}
StorageVersion::put(migration::CURRENT_VERSION);
});
}
}
@@ -1298,9 +1292,10 @@ impl<T: Trait> Module<T> {
}
/// Ensures storage is upgraded to most recent necessary state.
fn ensure_storage_upgraded() {
migration::perform_migrations::<T>();
}
///
/// Right now it's a no-op as all networks that are supported by Substrate Frame Core are
/// running with the latest staking storage scheme.
fn ensure_storage_upgraded() {}
/// Actually make a payment to a staker. This uses the currency's reward function
/// to pay the right payee for the given staker account.
-140
View File
@@ -1,140 +0,0 @@
// Copyright 2019-2020 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Substrate is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
//! Storage migrations for srml-staking.
/// Indicator of a version of a storage layout.
pub type VersionNumber = u32;
// the current expected version of the storage
pub const CURRENT_VERSION: VersionNumber = 2;
/// The inner logic of migrations.
#[cfg(any(test, feature = "migrate"))]
pub mod inner {
use crate::{Store, Module, Trait};
use frame_support::{StorageLinkedMap, StoragePrefixedMap, StorageValue};
use codec::{Encode, Decode};
use sp_std::vec::Vec;
use super::{CURRENT_VERSION, VersionNumber};
// the minimum supported version of the migration logic.
const MIN_SUPPORTED_VERSION: VersionNumber = 0;
// migrate storage from v0 to v1.
//
// this upgrades the `Nominators` linked_map value type from `Vec<T::AccountId>` to
// `Option<Nominations<T::AccountId>>`
pub fn to_v1<T: Trait>(version: &mut VersionNumber) {
if *version != 0 { return }
*version += 1;
let now = <Module<T>>::current_era();
let res = <Module<T> as Store>::Nominators::translate::<T::AccountId, Vec<T::AccountId>, _, _>(
|key| key,
|targets| crate::Nominations {
targets,
submitted_in: now,
suppressed: false,
},
);
if let Err(e) = res {
frame_support::print("Encountered error in migration of Staking::Nominators map.");
if e.is_none() {
frame_support::print("Staking::Nominators map reinitialized");
}
}
frame_support::print("Finished migrating Staking storage to v1.");
}
// migrate storage from v1 to v2: adds another field to the `SlashingSpans`
// struct.
pub fn to_v2<T: Trait>(version: &mut VersionNumber) {
use crate::{EraIndex, slashing::SpanIndex};
#[derive(Decode)]
struct V1SlashingSpans {
span_index: SpanIndex,
last_start: EraIndex,
prior: Vec<EraIndex>,
}
#[derive(Encode)]
struct V2SlashingSpans {
span_index: SpanIndex,
last_start: EraIndex,
last_nonzero_slash: EraIndex,
prior: Vec<EraIndex>,
}
if *version != 1 { return }
*version += 1;
let prefix = <Module<T> as Store>::SlashingSpans::final_prefix();
let mut current_key = prefix.to_vec();
loop {
let maybe_next_key = sp_io::storage::next_key(&current_key[..])
.filter(|v| v.starts_with(&prefix[..]));
match maybe_next_key {
Some(next_key) => {
let maybe_spans = sp_io::storage::get(&next_key[..])
.and_then(|v| V1SlashingSpans::decode(&mut &v[..]).ok());
if let Some(spans) = maybe_spans {
let new_val = V2SlashingSpans {
span_index: spans.span_index,
last_start: spans.last_start,
last_nonzero_slash: spans.last_start,
prior: spans.prior,
}.encode();
sp_io::storage::set(&next_key[..], &new_val[..]);
}
current_key = next_key;
}
None => break,
}
}
}
pub(super) fn perform_migrations<T: Trait>() {
<Module<T> as Store>::StorageVersion::mutate(|version| {
if *version < MIN_SUPPORTED_VERSION {
frame_support::print("Cannot migrate staking storage because version is less than\
minimum.");
frame_support::print(*version);
return
}
if *version == CURRENT_VERSION { return }
to_v1::<T>(version);
to_v2::<T>(version);
});
}
}
#[cfg(not(any(test, feature = "migrate")))]
mod inner {
pub(super) fn perform_migrations<T>() { }
}
/// Perform all necessary storage migrations to get storage into the expected stsate for current
/// logic. No-op if fully upgraded.
pub(crate) fn perform_migrations<T: crate::Trait>() {
inner::perform_migrations::<T>();
}
+4 -7
View File
@@ -138,19 +138,16 @@ impl frame_system::Trait for Test {
type MaximumBlockLength = MaximumBlockLength;
type Version = ();
type ModuleToIndex = ();
}
parameter_types! {
pub const CreationFee: Balance = 0;
type AccountData = pallet_balances::AccountData<u64>;
type OnNewAccount = ();
type OnReapAccount = (Balances, Staking, Session);
}
impl pallet_balances::Trait for Test {
type Balance = Balance;
type OnReapAccount = (System, Staking);
type OnNewAccount = ();
type Event = ();
type TransferPayment = ();
type DustRemoval = ();
type ExistentialDeposit = ExistentialDeposit;
type CreationFee = CreationFee;
type AccountStore = System;
}
parameter_types! {
pub const Period: BlockNumber = 1;
-58
View File
@@ -18,7 +18,6 @@
use super::*;
use mock::*;
use codec::Encode;
use sp_runtime::{assert_eq_error_rate, traits::{OnInitialize, BadOrigin}};
use sp_staking::offence::OffenceDetails;
use frame_support::{
@@ -2671,13 +2670,6 @@ fn remove_multi_deferred() {
})
}
#[test]
fn version_initialized() {
ExtBuilder::default().build().execute_with(|| {
assert_eq!(<Staking as Store>::StorageVersion::get(), crate::migration::CURRENT_VERSION);
});
}
#[test]
fn slash_kicks_validators_not_nominators() {
ExtBuilder::default().build().execute_with(|| {
@@ -2717,56 +2709,6 @@ fn slash_kicks_validators_not_nominators() {
});
}
#[test]
fn migration_v2() {
ExtBuilder::default().build().execute_with(|| {
use crate::{EraIndex, slashing::SpanIndex};
#[derive(Encode)]
struct V1SlashingSpans {
span_index: SpanIndex,
last_start: EraIndex,
prior: Vec<EraIndex>,
}
// inject old-style values directly into storage.
let set = |stash, spans: V1SlashingSpans| {
let key = <Staking as Store>::SlashingSpans::hashed_key_for(stash);
sp_io::storage::set(&key, &spans.encode());
};
let spans_11 = V1SlashingSpans {
span_index: 10,
last_start: 1,
prior: vec![0],
};
let spans_21 = V1SlashingSpans {
span_index: 1,
last_start: 5,
prior: vec![],
};
set(11, spans_11);
set(21, spans_21);
<Staking as Store>::StorageVersion::put(1);
// perform migration.
crate::migration::inner::to_v2::<Test>(&mut 1);
assert_eq!(
<Staking as Store>::SlashingSpans::get(&11).unwrap().last_nonzero_slash(),
1,
);
assert_eq!(
<Staking as Store>::SlashingSpans::get(&21).unwrap().last_nonzero_slash(),
5,
);
});
}
#[test]
fn zero_slash_keeps_nominators() {
ExtBuilder::default().build().execute_with(|| {
@@ -20,7 +20,7 @@ use frame_support_procedural_tools::syn_ext as ext;
use frame_support_procedural_tools::{generate_crate_access, generate_hidden_includes};
use parse::{ModuleDeclaration, RuntimeDefinition, WhereSection};
use proc_macro::TokenStream;
use proc_macro2::{Span, TokenStream as TokenStream2};
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::{Ident, Result, TypePath};
@@ -58,7 +58,7 @@ fn construct_runtime_parsed(definition: RuntimeDefinition) -> Result<TokenStream
return Err(syn::Error::new(
modules_token.span,
"`System` module declaration is missing. \
Please add this line: `System: system::{Module, Call, Storage, Config, Event},`",
Please add this line: `System: system::{Module, Call, Storage, Config, Event<T>},`",
))
}
};
@@ -68,19 +68,17 @@ fn construct_runtime_parsed(definition: RuntimeDefinition) -> Result<TokenStream
let all_but_system_modules = modules.iter().filter(|module| module.name != SYSTEM_MODULE_NAME);
let outer_event = decl_outer_event_or_origin(
let outer_event = decl_outer_event(
&name,
all_but_system_modules.clone(),
&system_module,
modules.iter(),
&scrate,
DeclOuterKind::Event,
)?;
let outer_origin = decl_outer_event_or_origin(
let outer_origin = decl_outer_origin(
&name,
all_but_system_modules.clone(),
&system_module,
&scrate,
DeclOuterKind::Origin,
)?;
let all_modules = decl_all_modules(&name, modules.iter());
let module_to_index = decl_module_to_index(modules.iter(), modules.len(), &scrate);
@@ -264,32 +262,24 @@ fn decl_outer_dispatch<'a>(
)
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
enum DeclOuterKind {
Event,
Origin,
}
fn decl_outer_event_or_origin<'a>(
fn decl_outer_origin<'a>(
runtime_name: &'a Ident,
module_declarations: impl Iterator<Item = &'a ModuleDeclaration>,
system_name: &'a Ident,
scrate: &'a TokenStream2,
kind: DeclOuterKind,
) -> syn::Result<TokenStream2> {
let mut modules_tokens = TokenStream2::new();
let kind_str = format!("{:?}", kind);
for module_declaration in module_declarations {
match module_declaration.find_part(&kind_str) {
match module_declaration.find_part("Origin") {
Some(module_entry) => {
let module = &module_declaration.module;
let instance = module_declaration.instance.as_ref();
let generics = &module_entry.generics;
if instance.is_some() && generics.params.len() == 0 {
let msg = format!(
"Instantiable module with no generic `{}` cannot \
be constructed: module `{}` must have generic `{}`",
kind_str, module_declaration.name, kind_str
"Instantiable module with no generic `Origin` cannot \
be constructed: module `{}` must have generic `Origin`",
module_declaration.name
);
return Err(syn::Error::new(module_declaration.name.span(), msg));
}
@@ -299,14 +289,46 @@ fn decl_outer_event_or_origin<'a>(
None => {}
}
}
let macro_call = match kind {
DeclOuterKind::Event => quote!(#scrate::impl_outer_event!),
DeclOuterKind::Origin => quote!(#scrate::impl_outer_origin!),
};
let enum_name = Ident::new(kind_str.as_str(), Span::call_site());
Ok(quote!(
#macro_call {
pub enum #enum_name for #runtime_name where system = #system_name {
#scrate::impl_outer_origin! {
pub enum Origin for #runtime_name where system = #system_name {
#modules_tokens
}
}
))
}
fn decl_outer_event<'a>(
runtime_name: &'a Ident,
module_declarations: impl Iterator<Item = &'a ModuleDeclaration>,
scrate: &'a TokenStream2,
) -> syn::Result<TokenStream2> {
let mut modules_tokens = TokenStream2::new();
for module_declaration in module_declarations {
match module_declaration.find_part("Event") {
Some(module_entry) => {
let module = &module_declaration.module;
let instance = module_declaration.instance.as_ref();
let generics = &module_entry.generics;
if instance.is_some() && generics.params.len() == 0 {
let msg = format!(
"Instantiable module with no generic `Event` cannot \
be constructed: module `{}` must have generic `Event`",
module_declaration.name,
);
return Err(syn::Error::new(module_declaration.name.span(), msg));
}
let tokens = quote!(#module #instance #generics ,);
modules_tokens.extend(tokens);
}
None => {}
}
}
Ok(quote!(
#scrate::impl_outer_event! {
pub enum Event for #runtime_name {
#modules_tokens
}
}
+7 -40
View File
@@ -337,30 +337,14 @@ macro_rules! impl_outer_event {
(
$(#[$attr:meta])*
pub enum $name:ident for $runtime:ident {
$( $rest_event_without_system:tt )*
$( $rest_events:tt )*
}
) => {
$crate::impl_outer_event!(
$( #[$attr] )*;
$name;
$runtime;
system;
Modules { $( $rest_event_without_system )* };
;
);
};
(
$(#[$attr:meta])*
pub enum $name:ident for $runtime:ident where system = $system:ident {
$( $rest_event_with_system:tt )*
}
) => {
$crate::impl_outer_event!(
$( #[$attr] )*;
$name;
$runtime;
$system;
Modules { $( $rest_event_with_system )* };
Modules { $( $rest_events )* };
;
);
};
@@ -369,7 +353,6 @@ macro_rules! impl_outer_event {
$(#[$attr:meta])*;
$name:ident;
$runtime:ident;
$system:ident;
Modules {
$module:ident $instance:ident<T>,
$( $rest_event_generic_instance:tt )*
@@ -380,7 +363,6 @@ macro_rules! impl_outer_event {
$( #[$attr] )*;
$name;
$runtime;
$system;
Modules { $( $rest_event_generic_instance )* };
$( $module_name::Event $( <$generic_param> )? $( { $generic_instance } )?, )* $module::Event<$runtime>{ $instance },;
);
@@ -390,7 +372,6 @@ macro_rules! impl_outer_event {
$(#[$attr:meta])*;
$name:ident;
$runtime:ident;
$system:ident;
Modules {
$module:ident $instance:ident,
$( $rest_event_instance:tt )*
@@ -401,7 +382,6 @@ macro_rules! impl_outer_event {
$( #[$attr] )*;
$name;
$runtime;
$system;
Modules { $( $rest_event_instance )* };
$( $module_name::Event $( <$generic_param> )* $( { $generic_instance } )?, )* $module::Event { $instance },;
);
@@ -411,7 +391,6 @@ macro_rules! impl_outer_event {
$(#[$attr:meta])*;
$name:ident;
$runtime:ident;
$system:ident;
Modules {
$module:ident<T>,
$( $rest_event_generic:tt )*
@@ -422,7 +401,6 @@ macro_rules! impl_outer_event {
$( #[$attr] )*;
$name;
$runtime;
$system;
Modules { $( $rest_event_generic )* };
$( $module_name::Event $( <$generic_param> )? $( { $generic_instance } )?, )* $module::Event<$runtime>,;
);
@@ -432,7 +410,6 @@ macro_rules! impl_outer_event {
$(#[$attr:meta])*;
$name:ident;
$runtime:ident;
$system:ident;
Modules {
$module:ident,
$( $rest_event_no_generic_no_instance:tt )*
@@ -443,7 +420,6 @@ macro_rules! impl_outer_event {
$( #[$attr] )*;
$name;
$runtime;
$system;
Modules { $( $rest_event_no_generic_no_instance )* };
$( $module_name::Event $( <$generic_param> )? $( { $generic_instance } )?, )* $module::Event,;
);
@@ -454,7 +430,6 @@ macro_rules! impl_outer_event {
$(#[$attr:meta])*;
$name:ident;
$runtime:ident;
$system:ident;
Modules {};
$( $module_name:ident::Event $( <$generic_param:ident> )? $( { $generic_instance:ident } )?, )*;
) => {
@@ -468,18 +443,12 @@ macro_rules! impl_outer_event {
$(#[$attr])*
#[allow(non_camel_case_types)]
pub enum $name {
system($system::Event),
$(
[< $module_name $(_ $generic_instance )? >](
$module_name::Event < $( $generic_param )? $(, $module_name::$generic_instance )? >
),
)*
}
impl From<$system::Event> for $name {
fn from(x: $system::Event) -> Self {
$name::system(x)
}
}
$(
impl From<$module_name::Event < $( $generic_param, )? $( $module_name::$generic_instance )? >> for $name {
fn from(x: $module_name::Event < $( $generic_param, )? $( $module_name::$generic_instance )? >) -> Self {
@@ -505,7 +474,6 @@ macro_rules! impl_outer_event {
$crate::__impl_outer_event_json_metadata!(
$runtime;
$name;
$system;
$(
$module_name::Event
< $( $generic_param )? $(, $module_name::$generic_instance )? >
@@ -521,7 +489,6 @@ macro_rules! __impl_outer_event_json_metadata {
(
$runtime:ident;
$event_name:ident;
$system:ident;
$( $module_name:ident::Event < $( $generic_params:path ),* > $( $instance:ident )?, )*;
) => {
impl $runtime {
@@ -530,22 +497,20 @@ macro_rules! __impl_outer_event_json_metadata {
$crate::event::OuterEventMetadata {
name: $crate::event::DecodeDifferent::Encode(stringify!($event_name)),
events: $crate::event::DecodeDifferent::Encode(&[
("system", $crate::event::FnEncode($system::Event::metadata))
$(
, (
(
stringify!($module_name),
$crate::event::FnEncode(
$module_name::Event ::< $( $generic_params ),* > ::metadata
)
)
)*
),*
])
}
}
$crate::__impl_outer_event_json_metadata! {
@DECL_MODULE_EVENT_FNS
$system <> ;
$( $module_name < $( $generic_params ),* > $( $instance )? ; )*
}
}
@@ -717,6 +682,7 @@ mod tests {
impl_outer_event! {
pub enum TestEvent for TestRuntime {
system,
event_module<T>,
event_module2<T>,
event_module3,
@@ -727,7 +693,8 @@ mod tests {
pub struct TestRuntime2;
impl_outer_event! {
pub enum TestEventSystemRenamed for TestRuntime2 where system = system_renamed {
pub enum TestEventSystemRenamed for TestRuntime2 {
system_renamed,
event_module<T>,
event_module2<T>,
event_module3,
+1
View File
@@ -404,6 +404,7 @@ mod tests {
impl_outer_event! {
pub enum TestEvent for TestRuntime {
system,
event_module<T>,
event_module2<T>,
}
@@ -104,7 +104,7 @@ impl<K: FullEncode, V: FullCodec, G: StorageMap<K, V>> storage::StorageMap<K, V>
}
fn insert<KeyArg: EncodeLike<K>, ValArg: EncodeLike<V>>(key: KeyArg, val: ValArg) {
unhashed::put(Self::storage_map_final_key(key).as_ref(), &val.borrow())
unhashed::put(Self::storage_map_final_key(key).as_ref(), &val)
}
fn remove<KeyArg: EncodeLike<K>>(key: KeyArg) {
@@ -117,12 +117,58 @@ impl<K: FullEncode, V: FullCodec, G: StorageMap<K, V>> storage::StorageMap<K, V>
let ret = f(&mut val);
match G::from_query_to_optional_value(val) {
Some(ref val) => unhashed::put(final_key.as_ref(), &val.borrow()),
Some(ref val) => unhashed::put(final_key.as_ref(), &val),
None => unhashed::kill(final_key.as_ref()),
}
ret
}
fn mutate_exists<KeyArg: EncodeLike<K>, R, F: FnOnce(&mut Option<V>) -> R>(key: KeyArg, f: F) -> R {
let final_key = Self::storage_map_final_key(key);
let mut val = unhashed::get(final_key.as_ref());
let ret = f(&mut val);
match val {
Some(ref val) => unhashed::put(final_key.as_ref(), &val),
None => unhashed::kill(final_key.as_ref()),
}
ret
}
fn try_mutate<KeyArg: EncodeLike<K>, R, E, F: FnOnce(&mut Self::Query) -> Result<R, E>>(
key: KeyArg,
f: F
) -> Result<R, E> {
let final_key = Self::storage_map_final_key(key);
let mut val = G::from_optional_value_to_query(unhashed::get(final_key.as_ref()));
let ret = f(&mut val);
if ret.is_ok() {
match G::from_query_to_optional_value(val) {
Some(ref val) => unhashed::put(final_key.as_ref(), &val.borrow()),
None => unhashed::kill(final_key.as_ref()),
}
}
ret
}
fn try_mutate_exists<KeyArg: EncodeLike<K>, R, E, F: FnOnce(&mut Option<V>) -> Result<R, E>>(
key: KeyArg,
f: F
) -> Result<R, E> {
let final_key = Self::storage_map_final_key(key);
let mut val = unhashed::get(final_key.as_ref());
let ret = f(&mut val);
if ret.is_ok() {
match val {
Some(ref val) => unhashed::put(final_key.as_ref(), &val.borrow()),
None => unhashed::kill(final_key.as_ref()),
}
}
ret
}
fn take<KeyArg: EncodeLike<K>>(key: KeyArg) -> Self::Query {
let key = Self::storage_map_final_key(key);
let value = unhashed::take(key.as_ref());
@@ -66,6 +66,10 @@ impl<T: FullCodec, G: StorageValue<T>> storage::StorageValue<T> for G {
G::from_optional_value_to_query(value)
}
fn try_get() -> Result<T, ()> {
unhashed::get(&Self::storage_value_final_key()).ok_or(())
}
fn translate<O: Decode, F: FnOnce(Option<O>) -> Option<T>>(f: F) -> Result<Option<T>, ()> {
let key = Self::storage_value_final_key();
@@ -18,8 +18,9 @@
use sp_std::prelude::*;
use codec::{Encode, Decode};
use frame_support::{StorageHasher, Twox128};
use crate::{StorageHasher, Twox128};
/// Utility to iterate through raw items in storage.
pub struct StorageIterator<T> {
prefix: [u8; 32],
previous_key: Vec<u8>,
@@ -28,12 +29,14 @@ pub struct StorageIterator<T> {
}
impl<T> StorageIterator<T> {
/// Construct iterator to iterate over map items in `module` for the map called `item`.
pub fn new(module: &[u8], item: &[u8]) -> Self {
let mut prefix = [0u8; 32];
prefix[0..16].copy_from_slice(&Twox128::hash(module));
prefix[16..32].copy_from_slice(&Twox128::hash(item));
Self { prefix, previous_key: prefix[..].to_vec(), drain: false, _phantom: Default::default() }
}
/// Mutate this iterator into a draining iterator; items iterated are removed from storage.
pub fn drain(mut self) -> Self {
self.drain = true;
self
@@ -67,6 +70,7 @@ impl<T: Decode + Sized> Iterator for StorageIterator<T> {
}
}
/// Get a particular value in storage by the `module`, the map's `item` name and the key `hash`.
pub fn get_storage_value<T: Decode + Sized>(module: &[u8], item: &[u8], hash: &[u8]) -> Option<T> {
let mut key = vec![0u8; 32 + hash.len()];
key[0..16].copy_from_slice(&Twox128::hash(module));
@@ -75,6 +79,16 @@ pub fn get_storage_value<T: Decode + Sized>(module: &[u8], item: &[u8], hash: &[
frame_support::storage::unhashed::get::<T>(&key)
}
/// Get a particular value in storage by the `module`, the map's `item` name and the key `hash`.
pub fn take_storage_value<T: Decode + Sized>(module: &[u8], item: &[u8], hash: &[u8]) -> Option<T> {
let mut key = vec![0u8; 32 + hash.len()];
key[0..16].copy_from_slice(&Twox128::hash(module));
key[16..32].copy_from_slice(&Twox128::hash(item));
key[32..].copy_from_slice(hash);
frame_support::storage::unhashed::take::<T>(&key)
}
/// Put a particular value into storage by the `module`, the map's `item` name and the key `hash`.
pub fn put_storage_value<T: Encode>(module: &[u8], item: &[u8], hash: &[u8], value: T) {
let mut key = vec![0u8; 32 + hash.len()];
key[0..16].copy_from_slice(&Twox128::hash(module));
+22 -4
View File
@@ -25,6 +25,7 @@ pub mod hashed;
pub mod child;
#[doc(hidden)]
pub mod generator;
pub mod migration;
/// A trait for working with macro-generated storage values under the substrate storage API.
///
@@ -43,6 +44,10 @@ pub trait StorageValue<T: FullCodec> {
/// Load the value from the provided storage instance.
fn get() -> Self::Query;
/// Try to get the underlying value from the provided storage instance; `Ok` if it exists,
/// `Err` if not.
fn try_get() -> Result<T, ()>;
/// Translate a value from some previous type (`O`) to the current type.
///
/// `f: F` is the translation function.
@@ -143,14 +148,28 @@ pub trait StorageMap<K: FullEncode, V: FullCodec> {
/// Mutate the value under a key.
fn mutate<KeyArg: EncodeLike<K>, R, F: FnOnce(&mut Self::Query) -> R>(key: KeyArg, f: F) -> R;
/// Mutate the item, only if an `Ok` value is returned.
fn try_mutate<KeyArg: EncodeLike<K>, R, E, F: FnOnce(&mut Self::Query) -> Result<R, E>>(
key: KeyArg,
f: F,
) -> Result<R, E>;
/// Mutate the value under a key. Deletes the item if mutated to a `None`.
fn mutate_exists<KeyArg: EncodeLike<K>, R, F: FnOnce(&mut Option<V>) -> R>(key: KeyArg, f: F) -> R;
/// Mutate the item, only if an `Ok` value is returned. Deletes the item if mutated to a `None`.
fn try_mutate_exists<KeyArg: EncodeLike<K>, R, E, F: FnOnce(&mut Option<V>) -> Result<R, E>>(
key: KeyArg,
f: F,
) -> Result<R, E>;
/// Take the value under a key.
fn take<KeyArg: EncodeLike<K>>(key: KeyArg) -> Self::Query;
/// Append the given items to the value in the storage.
///
/// `V` is required to implement `codec::EncodeAppend`.
fn append<Items, Item, EncodeLikeItem, KeyArg>(key: KeyArg, items: Items) -> Result<(), &'static str>
where
fn append<Items, Item, EncodeLikeItem, KeyArg>(key: KeyArg, items: Items) -> Result<(), &'static str> where
KeyArg: EncodeLike<K>,
Item: Encode,
EncodeLikeItem: EncodeLike<Item>,
@@ -162,8 +181,7 @@ pub trait StorageMap<K: FullEncode, V: FullCodec> {
/// old (presumably corrupt) value is replaced with the given `items`.
///
/// `V` is required to implement `codec::EncodeAppend`.
fn append_or_insert<Items, Item, EncodeLikeItem, KeyArg>(key: KeyArg, items: Items)
where
fn append_or_insert<Items, Item, EncodeLikeItem, KeyArg>(key: KeyArg, items: Items) where
KeyArg: EncodeLike<K>,
Item: Encode,
EncodeLikeItem: EncodeLike<Item>,
+142 -22
View File
@@ -28,6 +28,115 @@ use sp_runtime::{
};
use crate::dispatch::Parameter;
use crate::storage::StorageMap;
/// An abstraction of a value stored within storage, but possibly as part of a larger composite
/// item.
pub trait StoredMap<K, T> {
/// Get the item, or its default if it doesn't yet exist; we make no distinction between the
/// two.
fn get(k: &K) -> T;
/// Get whether the item takes up any storage. If this is `false`, then `get` will certainly
/// return the `T::default()`. If `true`, then there is no implication for `get` (i.e. it
/// may return any value, including the default).
///
/// NOTE: This may still be `true`, even after `remove` is called. This is the case where
/// a single storage entry is shared between multiple `StoredMap` items single, without
/// additional logic to enforce it, deletion of any one them doesn't automatically imply
/// deletion of them all.
fn is_explicit(k: &K) -> bool;
/// Mutate the item.
fn mutate<R>(k: &K, f: impl FnOnce(&mut T) -> R) -> R;
/// Mutate the item, removing or resetting to default value if it has been mutated to `None`.
fn mutate_exists<R>(k: &K, f: impl FnOnce(&mut Option<T>) -> R) -> R;
/// Maybe mutate the item only if an `Ok` value is returned from `f`. Do nothing if an `Err` is
/// returned. It is removed or reset to default value if it has been mutated to `None`
fn try_mutate_exists<R, E>(k: &K, f: impl FnOnce(&mut Option<T>) -> Result<R, E>) -> Result<R, E>;
/// Set the item to something new.
fn insert(k: &K, t: T) { Self::mutate(k, |i| *i = t); }
/// Remove the item or otherwise replace it with its default value; we don't care which.
fn remove(k: &K);
}
/// A simple, generic one-parameter event notifier/handler.
pub trait Happened<T> {
/// The thing happened.
fn happened(t: &T);
}
/// A shim for placing around a storage item in order to use it as a `StoredValue`. Ideally this
/// wouldn't be needed as `StorageValue`s should blanket implement `StoredValue`s, however this
/// would break the ability to have custom impls of `StoredValue`. The other workaround is to
/// implement it directly in the macro.
///
/// This form has the advantage that two additional types are provides, `Created` and `Removed`,
/// which are both generic events that can be tied to handlers to do something in the case of being
/// about to create an account where one didn't previously exist (at all; not just where it used to
/// be the default value), or where the account is being removed or reset back to the default value
/// where previously it did exist (though may have been in a default state). This works well with
/// system module's `CallOnCreatedAccount` and `CallKillAccount`.
pub struct StorageMapShim<
S,
Created,
Removed,
K,
T
>(sp_std::marker::PhantomData<(S, Created, Removed, K, T)>);
impl<
S: StorageMap<K, T, Query=T>,
Created: Happened<K>,
Removed: Happened<K>,
K: FullCodec,
T: FullCodec
> StoredMap<K, T> for StorageMapShim<S, Created, Removed, K, T> {
fn get(k: &K) -> T { S::get(k) }
fn is_explicit(k: &K) -> bool { S::contains_key(k) }
fn insert(k: &K, t: T) {
S::insert(k, t);
if !S::contains_key(&k) {
Created::happened(k);
}
}
fn remove(k: &K) {
if S::contains_key(&k) {
Removed::happened(&k);
}
S::remove(k);
}
fn mutate<R>(k: &K, f: impl FnOnce(&mut T) -> R) -> R {
let r = S::mutate(k, f);
if !S::contains_key(&k) {
Created::happened(k);
}
r
}
fn mutate_exists<R>(k: &K, f: impl FnOnce(&mut Option<T>) -> R) -> R {
let (existed, exists, r) = S::mutate_exists(k, |maybe_value| {
let existed = maybe_value.is_some();
let r = f(maybe_value);
(existed, maybe_value.is_some(), r)
});
if !existed && exists {
Created::happened(k);
} else if existed && !exists {
Removed::happened(k);
}
r
}
fn try_mutate_exists<R, E>(k: &K, f: impl FnOnce(&mut Option<T>) -> Result<R, E>) -> Result<R, E> {
S::try_mutate_exists(k, |maybe_value| {
let existed = maybe_value.is_some();
f(maybe_value).map(|v| (existed, maybe_value.is_some(), v))
}).map(|(existed, exists, v)| {
if !existed && exists {
Created::happened(k);
} else if existed && !exists {
Removed::happened(k);
}
v
})
}
}
/// Anything that can have a `::len()` method.
pub trait Len {
@@ -65,6 +174,25 @@ pub trait Contains<T: Ord> {
fn count() -> usize { Self::sorted_members().len() }
}
/// Determiner to say whether a given account is unused.
pub trait IsDeadAccount<AccountId> {
/// Is the given account dead?
fn is_dead_account(who: &AccountId) -> bool;
}
impl<AccountId> IsDeadAccount<AccountId> for () {
fn is_dead_account(_who: &AccountId) -> bool {
true
}
}
/// Handler for when a new account has been created.
#[impl_trait_for_tuples::impl_for_tuples(30)]
pub trait OnNewAccount<AccountId> {
/// A new account `who` has been registered.
fn on_new_account(who: &AccountId);
}
/// The account with the given id was reaped.
#[impl_trait_for_tuples::impl_for_tuples(30)]
pub trait OnReapAccount<AccountId> {
@@ -72,20 +200,6 @@ pub trait OnReapAccount<AccountId> {
fn on_reap_account(who: &AccountId);
}
/// Outcome of a balance update.
pub enum UpdateBalanceOutcome {
/// Account balance was simply updated.
Updated,
/// The update led to killing the account.
AccountKilled,
/// Free balance became zero as a result of this update.
FreeBalanceZero,
/// Reserved balance became zero as a result of this update.
ReservedBalanceZero,
/// The account started and ended non-existent.
StillDead,
}
/// A trait for finding the author of a block header based on the `PreRuntime` digests contained
/// within it.
pub trait FindAuthor<Author> {
@@ -494,10 +608,15 @@ pub trait Currency<AccountId> {
fn make_free_balance_be(
who: &AccountId,
balance: Self::Balance,
) -> (
SignedImbalance<Self::Balance, Self::PositiveImbalance>,
UpdateBalanceOutcome,
);
) -> SignedImbalance<Self::Balance, Self::PositiveImbalance>;
}
/// Status of funds.
pub enum BalanceStatus {
/// Funds are free, as corresponding to `free` item in Balances.
Free,
/// Funds are reserved, as corresponding to `reserved` item in Balances.
Reserved,
}
/// A currency where funds can be reserved from the user.
@@ -528,7 +647,6 @@ pub trait ReservableCurrency<AccountId>: Currency<AccountId> {
/// collapsed to zero if it ever becomes less than `ExistentialDeposit`.
fn reserved_balance(who: &AccountId) -> Self::Balance;
/// Moves `value` from balance to reserved balance.
///
/// If the free balance is lower than `value`, then no funds will be moved and an `Err` will
@@ -547,16 +665,18 @@ pub trait ReservableCurrency<AccountId>: Currency<AccountId> {
/// invoke `on_reserved_too_low` and could reap the account.
fn unreserve(who: &AccountId, value: Self::Balance) -> Self::Balance;
/// Moves up to `value` from reserved balance of account `slashed` to free balance of account
/// Moves up to `value` from reserved balance of account `slashed` to balance of account
/// `beneficiary`. `beneficiary` must exist for this to succeed. If it does not, `Err` will be
/// returned.
/// returned. Funds will be placed in either the `free` balance or the `reserved` balance,
/// depending on the `status`.
///
/// As much funds up to `value` will be deducted as possible. If this is less than `value`,
/// then `Ok(non_zero)` will be returned.
fn repatriate_reserved(
slashed: &AccountId,
beneficiary: &AccountId,
value: Self::Balance
value: Self::Balance,
status: BalanceStatus,
) -> result::Result<Self::Balance, DispatchError>;
}
@@ -1,4 +1,4 @@
error: `System` module declaration is missing. Please add this line: `System: system::{Module, Call, Storage, Config, Event},`
error: `System` module declaration is missing. Please add this line: `System: system::{Module, Call, Storage, Config, Event<T>},`
--> $DIR/missing_system_module.rs:8:2
|
8 | {
@@ -99,7 +99,7 @@ frame_support::construct_runtime!(
NodeBlock = Block,
UncheckedExtrinsic = UncheckedExtrinsic
{
System: system::{Module, Call, Event},
System: system::{Module, Call, Event<T>},
Module1_1: module1::<Instance1>::{Module, Call, Storage},
Module2: module2::{Module, Call, Storage},
Module1_2: module1::<Instance2>::{Module, Call, Storage},
@@ -247,7 +247,7 @@ frame_support::construct_runtime!(
NodeBlock = Block,
UncheckedExtrinsic = UncheckedExtrinsic
{
System: system::{Module, Call, Event},
System: system::{Module, Call, Event<T>},
Module1_1: module1::<Instance1>::{
Module, Call, Storage, Event<T>, Config<T>, Origin<T>, Inherent
},
@@ -173,7 +173,7 @@ frame_support::construct_runtime!(
NodeBlock = Block,
UncheckedExtrinsic = UncheckedExtrinsic
{
System: system::{Module, Call, Event},
System: system::{Module, Call, Event<T>},
Module: module::{Module, Call, Storage, Config},
}
);
+3 -2
View File
@@ -23,7 +23,7 @@ pub trait Trait: 'static + Eq + Clone {
type BlockNumber: Decode + Encode + EncodeLike + Clone + Default;
type Hash;
type AccountId: Encode + EncodeLike + Decode;
type Event: From<Event>;
type Event: From<Event<Self>>;
type ModuleToIndex: frame_support::traits::ModuleToIndex;
}
@@ -36,9 +36,10 @@ impl<T: Trait> Module<T> {
}
frame_support::decl_event!(
pub enum Event {
pub enum Event<T> where BlockNumber = <T as Trait>::BlockNumber {
ExtrinsicSuccess,
ExtrinsicFailed,
Ignore(BlockNumber),
}
);
+4
View File
@@ -46,6 +46,7 @@ impl_outer_origin!{
impl_outer_event! {
pub enum Event for Runtime {
system<T>,
module,
}
}
@@ -75,6 +76,9 @@ impl system::Trait for Runtime {
type AvailableBlockRatio = AvailableBlockRatio;
type Version = ();
type ModuleToIndex = ();
type AccountData = ();
type OnNewAccount = ();
type OnReapAccount = ();
}
impl module::Trait for Runtime {
+320 -183
View File
@@ -113,35 +113,19 @@ use sp_runtime::{
use sp_core::{ChangesTrieConfiguration, storage::well_known_keys};
use frame_support::{
decl_module, decl_event, decl_storage, decl_error, storage, Parameter,
traits::{Contains, Get, ModuleToIndex, OnReapAccount},
traits::{
Contains, Get, ModuleToIndex, OnNewAccount, OnReapAccount, IsDeadAccount, Happened,
StoredMap
},
weights::{Weight, DispatchInfo, DispatchClass, SimpleDispatchInfo},
};
use codec::{Encode, Decode};
use codec::{Encode, Decode, FullCodec, EncodeLike};
#[cfg(any(feature = "std", test))]
use sp_io::TestExternalities;
pub mod offchain;
/// Handler for when a new account has been created.
#[impl_trait_for_tuples::impl_for_tuples(30)]
pub trait OnNewAccount<AccountId> {
/// A new account `who` has been registered.
fn on_new_account(who: &AccountId);
}
/// Determiner to say whether a given account is unused.
pub trait IsDeadAccount<AccountId> {
/// Is the given account dead?
fn is_dead_account(who: &AccountId) -> bool;
}
impl<AccountId> IsDeadAccount<AccountId> for () {
fn is_dead_account(_who: &AccountId) -> bool {
true
}
}
/// Compute the trie root of a list of extrinsics.
pub fn extrinsics_root<H: Hash, E: codec::Encode>(extrinsics: &[E]) -> H::Output {
extrinsics_data_root::<H>(extrinsics.iter().map(codec::Encode::encode).collect())
@@ -200,7 +184,7 @@ pub trait Trait: 'static + Eq + Clone {
>;
/// The aggregated event type of the runtime.
type Event: Parameter + Member + From<Event> + Debug;
type Event: Parameter + Member + From<Event<Self>> + Debug;
/// Maximum number of block number to block hash mappings to keep (oldest pruned first).
type BlockHashCount: Get<Self::BlockNumber>;
@@ -224,6 +208,18 @@ pub trait Trait: 'static + Eq + Clone {
/// Expects the `ModuleToIndex` type that is being generated by `construct_runtime!` in the
/// runtime. For tests it is okay to use `()` as type (returns `0` for each input).
type ModuleToIndex: ModuleToIndex;
/// Data to be associated with an account (other than nonce/transaction counter, which this
/// module does regardless).
type AccountData: Member + FullCodec + Clone + Default;
/// Handler for when a new account has just been created.
type OnNewAccount: OnNewAccount<Self::AccountId>;
/// A function that is invoked when an account has been determined to be dead.
///
/// All resources should be cleaned up associated with the given account.
type OnReapAccount: OnReapAccount<Self::AccountId>;
}
pub type DigestOf<T> = generic::Digest<<T as Trait>::Hash>;
@@ -232,111 +228,6 @@ pub type DigestItemOf<T> = generic::DigestItem<<T as Trait>::Hash>;
pub type Key = Vec<u8>;
pub type KeyValue = (Vec<u8>, Vec<u8>);
decl_module! {
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
type Error = Error<T>;
/// A big dispatch that will disallow any other transaction to be included.
// TODO: this must be preferable available for testing really (not possible at the moment).
#[weight = SimpleDispatchInfo::MaxOperational]
fn fill_block(origin) {
ensure_root(origin)?;
}
/// Make some on-chain remark.
#[weight = SimpleDispatchInfo::FixedNormal(10_000)]
fn remark(origin, _remark: Vec<u8>) {
ensure_signed(origin)?;
}
/// Set the number of pages in the WebAssembly environment's heap.
#[weight = SimpleDispatchInfo::FixedOperational(10_000)]
fn set_heap_pages(origin, pages: u64) {
ensure_root(origin)?;
storage::unhashed::put_raw(well_known_keys::HEAP_PAGES, &pages.encode());
}
/// Set the new runtime code.
#[weight = SimpleDispatchInfo::FixedOperational(200_000)]
pub fn set_code(origin, code: Vec<u8>) {
ensure_root(origin)?;
let current_version = T::Version::get();
let new_version = sp_io::misc::runtime_version(&code)
.and_then(|v| RuntimeVersion::decode(&mut &v[..]).ok())
.ok_or_else(|| Error::<T>::FailedToExtractRuntimeVersion)?;
if new_version.spec_name != current_version.spec_name {
Err(Error::<T>::InvalidSpecName)?
}
if new_version.spec_version < current_version.spec_version {
Err(Error::<T>::SpecVersionNotAllowedToDecrease)?
} else if new_version.spec_version == current_version.spec_version {
if new_version.impl_version < current_version.impl_version {
Err(Error::<T>::ImplVersionNotAllowedToDecrease)?
} else if new_version.impl_version == current_version.impl_version {
Err(Error::<T>::SpecOrImplVersionNeedToIncrease)?
}
}
storage::unhashed::put_raw(well_known_keys::CODE, &code);
Self::deposit_event(Event::CodeUpdated);
}
/// Set the new runtime code without doing any checks of the given `code`.
#[weight = SimpleDispatchInfo::FixedOperational(200_000)]
pub fn set_code_without_checks(origin, code: Vec<u8>) {
ensure_root(origin)?;
storage::unhashed::put_raw(well_known_keys::CODE, &code);
Self::deposit_event(Event::CodeUpdated);
}
/// Set the new changes trie configuration.
#[weight = SimpleDispatchInfo::FixedOperational(20_000)]
pub fn set_changes_trie_config(origin, changes_trie_config: Option<ChangesTrieConfiguration>) {
ensure_root(origin)?;
match changes_trie_config.clone() {
Some(changes_trie_config) => storage::unhashed::put_raw(
well_known_keys::CHANGES_TRIE_CONFIG,
&changes_trie_config.encode(),
),
None => storage::unhashed::kill(well_known_keys::CHANGES_TRIE_CONFIG),
}
let log = generic::DigestItem::ChangesTrieSignal(
generic::ChangesTrieSignal::NewConfiguration(changes_trie_config),
);
Self::deposit_log(log.into());
}
/// Set some items of storage.
#[weight = SimpleDispatchInfo::FixedOperational(10_000)]
fn set_storage(origin, items: Vec<KeyValue>) {
ensure_root(origin)?;
for i in &items {
storage::unhashed::put_raw(&i.0, &i.1);
}
}
/// Kill some items from storage.
#[weight = SimpleDispatchInfo::FixedOperational(10_000)]
fn kill_storage(origin, keys: Vec<Key>) {
ensure_root(origin)?;
for key in &keys {
storage::unhashed::kill(&key);
}
}
/// Kill all storage items with a key that starts with the given prefix.
#[weight = SimpleDispatchInfo::FixedOperational(10_000)]
fn kill_prefix(origin, prefix: Key) {
ensure_root(origin)?;
storage::unhashed::kill_prefix(&prefix);
}
}
}
/// A phase of a block's execution.
#[derive(Encode, Decode, RuntimeDebug)]
#[cfg_attr(feature = "std", derive(Serialize, PartialEq, Eq, Clone))]
@@ -359,40 +250,6 @@ pub struct EventRecord<E: Parameter + Member, T> {
pub topics: Vec<T>,
}
decl_event!(
/// Event for the System module.
pub enum Event {
/// An extrinsic completed successfully.
ExtrinsicSuccess(DispatchInfo),
/// An extrinsic failed.
ExtrinsicFailed(DispatchError, DispatchInfo),
/// `:code` was updated.
CodeUpdated,
}
);
decl_error! {
/// Error for the System module
pub enum Error for Module<T: Trait> {
/// The name of specification does not match between the current runtime
/// and the new runtime.
InvalidSpecName,
/// The specification version is not allowed to decrease between the current runtime
/// and the new runtime.
SpecVersionNotAllowedToDecrease,
/// The implementation version is not allowed to decrease between the current runtime
/// and the new runtime.
ImplVersionNotAllowedToDecrease,
/// The specification or the implementation version need to increase between the
/// current runtime and the new runtime.
SpecOrImplVersionNeedToIncrease,
/// Failed to extract the runtime version from the new runtime.
///
/// Either calling `Core_version` or decoding `RuntimeVersion` failed.
FailedToExtractRuntimeVersion,
}
}
/// Origin for the System module.
#[derive(PartialEq, Eq, Clone, RuntimeDebug)]
pub enum RawOrigin<AccountId> {
@@ -435,29 +292,45 @@ type EventIndex = u32;
decl_storage! {
trait Store for Module<T: Trait> as System {
/// Extrinsics nonce for accounts.
pub AccountNonce get(fn account_nonce): map hasher(blake2_256) T::AccountId => T::Index;
/// The full account information for a particular account ID.
// TODO: should be hasher(twox64_concat) - will need staged migration
// TODO: should not including T::Index (the nonce)
// https://github.com/paritytech/substrate/issues/4917
pub Account get(fn account): map hasher(blake2_256) T::AccountId => (T::Index, T::AccountData);
/// Total extrinsics count for the current block.
ExtrinsicCount: Option<u32>;
/// Total weight for all extrinsics put together, for the current block.
AllExtrinsicsWeight: Option<Weight>;
/// Total length (in bytes) for all extrinsics put together, for the current block.
AllExtrinsicsLen: Option<u32>;
/// Map of block numbers to block hashes.
// TODO: should be hasher(twox64_concat) - will need one-off migration
// https://github.com/paritytech/substrate/issues/4917
pub BlockHash get(fn block_hash) build(|_| vec![(T::BlockNumber::zero(), hash69())]):
map hasher(blake2_256) T::BlockNumber => T::Hash;
/// Extrinsics data for the current block (maps an extrinsic's index to its data).
ExtrinsicData get(fn extrinsic_data): map hasher(blake2_256) u32 => Vec<u8>;
ExtrinsicData get(fn extrinsic_data): map hasher(twox_64_concat) u32 => Vec<u8>;
/// The current block number being processed. Set by `execute_block`.
Number get(fn block_number) build(|_| 1.into()): T::BlockNumber;
/// Hash of the previous block.
ParentHash get(fn parent_hash) build(|_| hash69()): T::Hash;
/// Extrinsics root of the current block, also part of the block header.
ExtrinsicsRoot get(fn extrinsics_root): T::Hash;
/// Digest of the current block, also part of the block header.
Digest get(fn digest): DigestOf<T>;
/// Events deposited for the current block.
Events get(fn events): Vec<EventRecord<T::Event, T::Hash>>;
/// The number of events in the `Events<T>` list.
EventCount get(fn event_count): EventIndex;
@@ -499,6 +372,150 @@ decl_storage! {
}
}
decl_event!(
/// Event for the System module.
pub enum Event<T> where AccountId = <T as Trait>::AccountId {
/// An extrinsic completed successfully.
ExtrinsicSuccess(DispatchInfo),
/// An extrinsic failed.
ExtrinsicFailed(DispatchError, DispatchInfo),
/// `:code` was updated.
CodeUpdated,
/// A new account was created.
NewAccount(AccountId),
/// An account was reaped.
ReapedAccount(AccountId),
}
);
decl_error! {
/// Error for the System module
pub enum Error for Module<T: Trait> {
/// The name of specification does not match between the current runtime
/// and the new runtime.
InvalidSpecName,
/// The specification version is not allowed to decrease between the current runtime
/// and the new runtime.
SpecVersionNotAllowedToDecrease,
/// The implementation version is not allowed to decrease between the current runtime
/// and the new runtime.
ImplVersionNotAllowedToDecrease,
/// The specification or the implementation version need to increase between the
/// current runtime and the new runtime.
SpecOrImplVersionNeedToIncrease,
/// Failed to extract the runtime version from the new runtime.
///
/// Either calling `Core_version` or decoding `RuntimeVersion` failed.
FailedToExtractRuntimeVersion,
}
}
decl_module! {
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
type Error = Error<T>;
/// A big dispatch that will disallow any other transaction to be included.
// TODO: This should only be available for testing, rather than in general usage, but
// that's not possible at present (since it's within the decl_module macro).
#[weight = SimpleDispatchInfo::MaxOperational]
fn fill_block(origin) {
ensure_root(origin)?;
}
/// Make some on-chain remark.
#[weight = SimpleDispatchInfo::FixedNormal(10_000)]
fn remark(origin, _remark: Vec<u8>) {
ensure_signed(origin)?;
}
/// Set the number of pages in the WebAssembly environment's heap.
#[weight = SimpleDispatchInfo::FixedOperational(10_000)]
fn set_heap_pages(origin, pages: u64) {
ensure_root(origin)?;
storage::unhashed::put_raw(well_known_keys::HEAP_PAGES, &pages.encode());
}
/// Set the new runtime code.
#[weight = SimpleDispatchInfo::FixedOperational(200_000)]
pub fn set_code(origin, code: Vec<u8>) {
ensure_root(origin)?;
let current_version = T::Version::get();
let new_version = sp_io::misc::runtime_version(&code)
.and_then(|v| RuntimeVersion::decode(&mut &v[..]).ok())
.ok_or_else(|| Error::<T>::FailedToExtractRuntimeVersion)?;
if new_version.spec_name != current_version.spec_name {
Err(Error::<T>::InvalidSpecName)?
}
if new_version.spec_version < current_version.spec_version {
Err(Error::<T>::SpecVersionNotAllowedToDecrease)?
} else if new_version.spec_version == current_version.spec_version {
if new_version.impl_version < current_version.impl_version {
Err(Error::<T>::ImplVersionNotAllowedToDecrease)?
} else if new_version.impl_version == current_version.impl_version {
Err(Error::<T>::SpecOrImplVersionNeedToIncrease)?
}
}
storage::unhashed::put_raw(well_known_keys::CODE, &code);
Self::deposit_event(RawEvent::CodeUpdated);
}
/// Set the new runtime code without doing any checks of the given `code`.
#[weight = SimpleDispatchInfo::FixedOperational(200_000)]
pub fn set_code_without_checks(origin, code: Vec<u8>) {
ensure_root(origin)?;
storage::unhashed::put_raw(well_known_keys::CODE, &code);
Self::deposit_event(RawEvent::CodeUpdated);
}
/// Set the new changes trie configuration.
#[weight = SimpleDispatchInfo::FixedOperational(20_000)]
pub fn set_changes_trie_config(origin, changes_trie_config: Option<ChangesTrieConfiguration>) {
ensure_root(origin)?;
match changes_trie_config.clone() {
Some(changes_trie_config) => storage::unhashed::put_raw(
well_known_keys::CHANGES_TRIE_CONFIG,
&changes_trie_config.encode(),
),
None => storage::unhashed::kill(well_known_keys::CHANGES_TRIE_CONFIG),
}
let log = generic::DigestItem::ChangesTrieSignal(
generic::ChangesTrieSignal::NewConfiguration(changes_trie_config),
);
Self::deposit_log(log.into());
}
/// Set some items of storage.
#[weight = SimpleDispatchInfo::FixedOperational(10_000)]
fn set_storage(origin, items: Vec<KeyValue>) {
ensure_root(origin)?;
for i in &items {
storage::unhashed::put_raw(&i.0, &i.1);
}
}
/// Kill some items from storage.
#[weight = SimpleDispatchInfo::FixedOperational(10_000)]
fn kill_storage(origin, keys: Vec<Key>) {
ensure_root(origin)?;
for key in &keys {
storage::unhashed::kill(&key);
}
}
/// Kill all storage items with a key that starts with the given prefix.
#[weight = SimpleDispatchInfo::FixedOperational(10_000)]
fn kill_prefix(origin, prefix: Key) {
ensure_root(origin)?;
storage::unhashed::kill_prefix(&prefix);
}
}
}
pub struct EnsureRoot<AccountId>(sp_std::marker::PhantomData<AccountId>);
impl<
O: Into<Result<RawOrigin<AccountId>, O>> + From<RawOrigin<AccountId>>,
@@ -622,7 +639,7 @@ impl<T: Trait> Module<T> {
Self::deposit_event_indexed(&[], event.into());
}
/// Deposits an event into this block's event record adding this event
/// Deposits an event into this block's event record adding this event
/// to the corresponding topic indexes.
///
/// This will update storage entries that correspond to the specified topics.
@@ -832,9 +849,14 @@ impl<T: Trait> Module<T> {
/// Return the chain's current runtime version.
pub fn runtime_version() -> RuntimeVersion { T::Version::get() }
/// Retrieve the account transaction counter from storage.
pub fn account_nonce(who: impl EncodeLike<T::AccountId>) -> T::Index {
Account::<T>::get(who).0
}
/// Increment a particular account's nonce by 1.
pub fn inc_account_nonce(who: &T::AccountId) {
<AccountNonce<T>>::insert(who, Self::account_nonce(who) + T::Index::one());
pub fn inc_account_nonce(who: impl EncodeLike<T::AccountId>) {
Account::<T>::mutate(who, |a| a.0 += T::Index::one());
}
/// Note what the extrinsic data of the current extrinsic index is. If this
@@ -851,10 +873,10 @@ impl<T: Trait> Module<T> {
pub fn note_applied_extrinsic(r: &DispatchOutcome, _encoded_len: u32, info: DispatchInfo) {
Self::deposit_event(
match r {
Ok(()) => Event::ExtrinsicSuccess(info),
Ok(()) => RawEvent::ExtrinsicSuccess(info),
Err(err) => {
sp_runtime::print(err);
Event::ExtrinsicFailed(err.clone(), info)
RawEvent::ExtrinsicFailed(err.clone(), info)
},
}
);
@@ -879,12 +901,118 @@ impl<T: Trait> Module<T> {
let xts_root = extrinsics_data_root::<T::Hashing>(extrinsics);
<ExtrinsicsRoot<T>>::put(xts_root);
}
/// An account is being created.
pub fn on_created_account(who: T::AccountId) {
T::OnNewAccount::on_new_account(&who);
Self::deposit_event(RawEvent::NewAccount(who));
}
/// Kill the account and reap any related information.
pub fn kill_account(who: T::AccountId) {
if Account::<T>::contains_key(&who) {
Account::<T>::remove(&who);
Self::on_killed_account(who);
}
}
/// Do anything that needs to be done after an account has been killed.
fn on_killed_account(who: T::AccountId) {
T::OnReapAccount::on_reap_account(&who);
Self::deposit_event(RawEvent::ReapedAccount(who));
}
}
impl<T: Trait> OnReapAccount<T::AccountId> for Module<T> {
/// Remove the nonce for the account. Account is considered fully removed from the system.
fn on_reap_account(who: &T::AccountId) {
<AccountNonce<T>>::remove(who);
/// Event handler which calls on_created_account when it happens.
pub struct CallOnCreatedAccount<T>(PhantomData<T>);
impl<T: Trait> Happened<T::AccountId> for CallOnCreatedAccount<T> {
fn happened(who: &T::AccountId) {
Module::<T>::on_created_account(who.clone());
}
}
/// Event handler which calls kill_account when it happens.
pub struct CallKillAccount<T>(PhantomData<T>);
impl<T: Trait> Happened<T::AccountId> for CallKillAccount<T> {
fn happened(who: &T::AccountId) {
Module::<T>::kill_account(who.clone());
}
}
// Implement StoredMap for a simple single-item, kill-account-on-remove system. This works fine for
// storing a single item which is required to not be empty/default for the account to exist.
// Anything more complex will need more sophisticated logic.
impl<T: Trait> StoredMap<T::AccountId, T::AccountData> for Module<T> {
fn get(k: &T::AccountId) -> T::AccountData {
Account::<T>::get(k).1
}
fn is_explicit(k: &T::AccountId) -> bool {
Account::<T>::contains_key(k)
}
fn insert(k: &T::AccountId, t: T::AccountData) {
let existed = Account::<T>::contains_key(k);
Account::<T>::insert(k, (T::Index::default(), t));
if !existed {
Self::on_created_account(k.clone());
}
}
fn remove(k: &T::AccountId) {
if Account::<T>::contains_key(&k) {
Self::kill_account(k.clone());
}
}
fn mutate<R>(k: &T::AccountId, f: impl FnOnce(&mut T::AccountData) -> R) -> R {
let existed = Account::<T>::contains_key(k);
let r = Account::<T>::mutate(k, |a| f(&mut a.1));
if !existed {
Self::on_created_account(k.clone());
}
r
}
fn mutate_exists<R>(k: &T::AccountId, f: impl FnOnce(&mut Option<T::AccountData>) -> R) -> R {
let (existed, exists, r) = Account::<T>::mutate_exists(k, |maybe_value| {
let existed = maybe_value.is_some();
let (maybe_nonce, mut maybe_extra) = split_inner(maybe_value.take(), |v| v);
let r = f(&mut maybe_extra);
*maybe_value = maybe_extra.map(|extra| (maybe_nonce.unwrap_or_default(), extra));
(existed, maybe_value.is_some(), r)
});
if !existed && exists {
Self::on_created_account(k.clone());
} else if existed && !exists {
Self::on_killed_account(k.clone());
}
r
}
fn try_mutate_exists<R, E>(k: &T::AccountId, f: impl FnOnce(&mut Option<T::AccountData>) -> Result<R, E>) -> Result<R, E> {
Account::<T>::try_mutate_exists(k, |maybe_value| {
let existed = maybe_value.is_some();
let (maybe_nonce, mut maybe_extra) = split_inner(maybe_value.take(), |v| v);
f(&mut maybe_extra).map(|v| {
*maybe_value = maybe_extra.map(|extra| (maybe_nonce.unwrap_or_default(), extra));
(existed, maybe_value.is_some(), v)
})
}).map(|(existed, exists, v)| {
if !existed && exists {
Self::on_created_account(k.clone());
} else if existed && !exists {
Self::on_killed_account(k.clone());
}
v
})
}
}
/// Split an `option` into two constituent options, as defined by a `splitter` function.
pub fn split_inner<T, R, S>(option: Option<T>, splitter: impl FnOnce(T) -> (R, S))
-> (Option<R>, Option<S>)
{
match option {
Some(inner) => {
let (r, s) = splitter(inner);
(Some(r), Some(s))
}
None => (None, None),
}
}
@@ -1052,7 +1180,7 @@ impl<T: Trait> SignedExtension for CheckNonce<T> {
_info: Self::DispatchInfo,
_len: usize,
) -> Result<(), TransactionValidityError> {
let expected = <AccountNonce<T>>::get(who);
let (expected, extra) = Account::<T>::get(who);
if self.0 != expected {
return Err(
if self.0 < expected {
@@ -1062,8 +1190,7 @@ impl<T: Trait> SignedExtension for CheckNonce<T> {
}.into()
)
}
<AccountNonce<T>>::insert(who, expected + T::Index::one());
Account::<T>::insert(who, (expected + T::Index::one(), extra));
Ok(())
}
@@ -1075,7 +1202,7 @@ impl<T: Trait> SignedExtension for CheckNonce<T> {
_len: usize,
) -> TransactionValidity {
// check index
let expected = <AccountNonce<T>>::get(who);
let (expected, _extra) = Account::<T>::get(who);
if self.0 < expected {
return InvalidTransaction::Stale.into()
}
@@ -1097,6 +1224,12 @@ impl<T: Trait> SignedExtension for CheckNonce<T> {
}
}
impl<T: Trait> IsDeadAccount<T::AccountId> for Module<T> {
fn is_dead_account(who: &T::AccountId) -> bool {
!Account::<T>::contains_key(who)
}
}
/// Check for transaction mortality.
#[derive(Encode, Decode, Clone, Eq, PartialEq)]
pub struct CheckEra<T: Trait + Send + Sync>((Era, sp_std::marker::PhantomData<T>));
@@ -1288,14 +1421,18 @@ mod tests {
type MaximumBlockLength = MaximumBlockLength;
type Version = Version;
type ModuleToIndex = ();
type AccountData = ();
type OnNewAccount = ();
type OnReapAccount = ();
}
impl From<Event> for u16 {
fn from(e: Event) -> u16 {
impl From<Event<Test>> for u16 {
fn from(e: Event<Test>) -> u16 {
match e {
Event::ExtrinsicSuccess(..) => 100,
Event::ExtrinsicFailed(..) => 101,
Event::CodeUpdated => 102,
Event::<Test>::ExtrinsicSuccess(..) => 100,
Event::<Test>::ExtrinsicFailed(..) => 101,
Event::<Test>::CodeUpdated => 102,
_ => 103,
}
}
}
@@ -1475,7 +1612,7 @@ mod tests {
#[test]
fn signed_ext_check_nonce_works() {
new_test_ext().execute_with(|| {
<AccountNonce<Test>>::insert(1, 1);
Account::<Test>::insert(1, (1, ()));
let info = DispatchInfo::default();
let len = 0_usize;
// stale
+6 -6
View File
@@ -20,8 +20,8 @@ use codec::Encode;
use sp_std::convert::TryInto;
use sp_std::prelude::Vec;
use sp_runtime::app_crypto::{RuntimeAppPublic, AppPublic, AppSignature};
use sp_runtime::traits::{Extrinsic as ExtrinsicT, IdentifyAccount};
use frame_support::debug;
use sp_runtime::traits::{Extrinsic as ExtrinsicT, IdentifyAccount, One};
use frame_support::{debug, storage::StorageMap};
/// Creates runtime-specific signed transaction.
///
@@ -128,19 +128,19 @@ pub trait SignAndSubmitTransaction<T: crate::Trait, Call> {
fn sign_and_submit(call: impl Into<Call>, public: PublicOf<T, Call, Self>) -> Result<(), ()> {
let call = call.into();
let id = public.clone().into_account();
let expected = <crate::Module<T>>::account_nonce(&id);
let (expected_nonce, extra) = super::Account::<T>::get(&id);
debug::native::debug!(
target: "offchain",
"Creating signed transaction from account: {:?} (nonce: {:?})",
id,
expected,
expected_nonce,
);
let (call, signature_data) = Self::CreateTransaction
::create_transaction::<Self::Signer>(call, public, id.clone(), expected)
::create_transaction::<Self::Signer>(call, public, id.clone(), expected_nonce)
.ok_or(())?;
// increment the nonce. This is fine, since the code should always
// be running in off-chain context, so we NEVER persists data.
<crate::Module<T>>::inc_account_nonce(&id);
super::Account::<T>::insert(&id, (expected_nonce + One::one(), extra));
let xt = Self::Extrinsic::new(call, Some(signature_data)).ok_or(())?;
sp_io::offchain::submit_transaction(xt.encode())
+3
View File
@@ -276,6 +276,9 @@ mod tests {
type MaximumBlockLength = MaximumBlockLength;
type Version = ();
type ModuleToIndex = ();
type AccountData = ();
type OnNewAccount = ();
type OnReapAccount = ();
}
parameter_types! {
pub const MinimumPeriod: u64 = 5;
@@ -302,25 +302,23 @@ mod tests {
type AvailableBlockRatio = AvailableBlockRatio;
type Version = ();
type ModuleToIndex = ();
type AccountData = pallet_balances::AccountData<u64>;
type OnNewAccount = ();
type OnReapAccount = Balances;
}
parameter_types! {
pub const CreationFee: u64 = 0;
pub const ExistentialDeposit: u64 = 1;
}
impl pallet_balances::Trait for Runtime {
type Balance = u64;
type OnReapAccount = System;
type OnNewAccount = ();
type Event = ();
type TransferPayment = ();
type DustRemoval = ();
type ExistentialDeposit = ExistentialDeposit;
type CreationFee = CreationFee;
type AccountStore = System;
}
thread_local! {
thread_local! {
static TRANSACTION_BASE_FEE: RefCell<u64> = RefCell::new(0);
static TRANSACTION_BYTE_FEE: RefCell<u64> = RefCell::new(1);
static WEIGHT_TO_FEE: RefCell<u64> = RefCell::new(1);
+5 -6
View File
@@ -758,20 +758,19 @@ mod tests {
type MaximumBlockLength = MaximumBlockLength;
type Version = ();
type ModuleToIndex = ();
type AccountData = pallet_balances::AccountData<u64>;
type OnNewAccount = ();
type OnReapAccount = Balances;
}
parameter_types! {
pub const ExistentialDeposit: u64 = 1;
pub const CreationFee: u64 = 0;
}
}
impl pallet_balances::Trait for Test {
type Balance = u64;
type OnNewAccount = ();
type OnReapAccount = System;
type Event = ();
type TransferPayment = ();
type DustRemoval = ();
type ExistentialDeposit = ExistentialDeposit;
type CreationFee = CreationFee;
type AccountStore = System;
}
pub struct TenToFourteen;
impl Contains<u64> for TenToFourteen {
+5 -5
View File
@@ -661,6 +661,7 @@ mod tests {
impl_outer_event! {
pub enum TestEvent for Test {
system<T>,
pallet_balances<T>,
utility<T>,
}
@@ -700,20 +701,19 @@ mod tests {
type AvailableBlockRatio = AvailableBlockRatio;
type Version = ();
type ModuleToIndex = ();
type AccountData = pallet_balances::AccountData<u64>;
type OnNewAccount = ();
type OnReapAccount = Balances;
}
parameter_types! {
pub const ExistentialDeposit: u64 = 1;
pub const CreationFee: u64 = 0;
}
impl pallet_balances::Trait for Test {
type Balance = u64;
type OnReapAccount = System;
type OnNewAccount = ();
type Event = TestEvent;
type TransferPayment = ();
type DustRemoval = ();
type ExistentialDeposit = ExistentialDeposit;
type CreationFee = CreationFee;
type AccountStore = System;
}
parameter_types! {
pub const MultisigDepositBase: u64 = 1;
+7 -9
View File
@@ -50,7 +50,7 @@ use sp_std::prelude::*;
use sp_std::fmt::Debug;
use codec::{Encode, Decode};
use sp_runtime::{DispatchResult, RuntimeDebug, traits::{
StaticLookup, Zero, AtLeast32Bit, MaybeSerializeDeserialize, Saturating, Convert
StaticLookup, Zero, AtLeast32Bit, MaybeSerializeDeserialize, Convert
}};
use frame_support::{decl_module, decl_event, decl_storage, decl_error};
use frame_support::traits::{
@@ -115,6 +115,7 @@ decl_storage! {
add_extra_genesis {
config(vesting): Vec<(T::AccountId, T::BlockNumber, T::BlockNumber, BalanceOf<T>)>;
build(|config: &GenesisConfig<T>| {
use sp_runtime::traits::Saturating;
// Generate initial vesting configuration
// * who - Account which we are generating vesting configuration for
// * begin - Block when the account will start to vest
@@ -336,19 +337,16 @@ mod tests {
type AvailableBlockRatio = AvailableBlockRatio;
type Version = ();
type ModuleToIndex = ();
}
parameter_types! {
pub const CreationFee: u64 = 0;
type AccountData = pallet_balances::AccountData<u64>;
type OnNewAccount = ();
type OnReapAccount = Balances;
}
impl pallet_balances::Trait for Test {
type Balance = u64;
type OnReapAccount = System;
type OnNewAccount = ();
type Event = ();
type TransferPayment = ();
type DustRemoval = ();
type Event = ();
type ExistentialDeposit = ExistentialDeposit;
type CreationFee = CreationFee;
type AccountStore = System;
}
impl Trait for Test {
type Event = ();