Newer
Older
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! # Balances Pallet
//! The Balances pallet provides functionality for handling accounts and balances for a single
//! token.
//! It makes heavy use of concepts such as Holds and Freezes from the
//! [`frame_support::traits::fungible`] traits, therefore you should read and understand those docs
//! as a prerequisite to understanding this pallet.
//!
//! Also see the [`frame_tokens`] reference docs for higher level information regarding the
//! place of this palet in FRAME.
//! The Balances pallet provides functions for:
//! - Getting and setting free balances.
//! - Retrieving total, reserved and unreserved balances.
//! - Repatriating a reserved balance to a beneficiary account that exists.
//! - Transferring a balance between accounts (when not reserved).
//! - Slashing an account balance.
//! - Account creation and removal.
//! - Managing total issuance.
//! - Setting and managing locks.
//!
//! ### Terminology
//!
//! - **Reaping an account:** The act of removing an account by resetting its nonce. Happens after
//! its total balance has become less than the Existential Deposit.
//!
//! ### Implementations
//!
//! The Balances pallet provides implementations for the following [`fungible`] traits. If these
//! traits provide the functionality that you need, then you should avoid tight coupling with the
//! Balances pallet.
//!
//! - [`fungible::Inspect`]
//! - [`fungible::Mutate`]
//! - [`fungible::Unbalanced`]
//! - [`fungible::Balanced`]
//! - [`fungible::BalancedHold`]
//! - [`fungible::InspectHold`]
//! - [`fungible::MutateHold`]
//! - [`fungible::InspectFreeze`]
//! - [`fungible::MutateFreeze`]
//! - [`fungible::Imbalance`]
//!
//! It also implements the following [`Currency`] related traits, however they are deprecated and
//! will eventually be removed.
//!
//! - [`Currency`]: Functions for dealing with a fungible assets system.
//! - [`ReservableCurrency`]
//! - [`NamedReservableCurrency`](frame_support::traits::NamedReservableCurrency):
//! Functions for dealing with assets that can be reserved from an account.
Anthony Alaribe
committed
//! - [`LockableCurrency`](frame_support::traits::LockableCurrency): Functions for
//! dealing with accounts that allow liquidity restrictions.
//! - [`Imbalance`](frame_support::traits::Imbalance): Functions for handling
//! imbalances between total issuance in the system and account balances. Must be used when a
//! function creates new funds (e.g. a reward) or destroys some funds (e.g. a system fee).
//! The following examples show how to use the Balances pallet in your custom pallet.
//! ### Examples from the FRAME
//! The Contract pallet uses the `Currency` trait to handle gas payment, and its types inherit from
//! `Currency`:
//! use frame_support::traits::Currency;
//! # pub trait Config: frame_system::Config {
//! # type Currency: Currency<Self::AccountId>;
//! pub type BalanceOf<T> = <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
//! pub type NegativeImbalanceOf<T> = <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::NegativeImbalance;
Anthony Alaribe
committed
//! The Staking pallet uses the `LockableCurrency` trait to lock a stash account's funds:
Anthony Alaribe
committed
//! use frame_support::traits::{WithdrawReasons, LockableCurrency};
//! pub trait Config: frame_system::Config {
//! type Currency: LockableCurrency<Self::AccountId, Moment=frame_system::pallet_prelude::BlockNumberFor<Self>>;
//! # struct StakingLedger<T: Config> {
//! # stash: <T as frame_system::Config>::AccountId,
//! # total: <<T as Config>::Currency as frame_support::traits::Currency<<T as frame_system::Config>::AccountId>>::Balance,
//! # phantom: std::marker::PhantomData<T>,
//! # }
//! # const STAKING_ID: [u8; 8] = *b"staking ";
//! fn update_ledger<T: Config>(
//! controller: &T::AccountId,
//! ledger: &StakingLedger<T>
//! T::Currency::set_lock(
//! STAKING_ID,
//! &ledger.stash,
//! ledger.total,
//! WithdrawReasons::all()
//! );
//! // <Ledger<T>>::insert(controller, ledger); // Commented out as we don't have access to Staking's storage here.
//! ```
//!
//! ## Genesis config
//!
//! The Balances pallet depends on the [`GenesisConfig`].
//!
//! ## Assumptions
//!
//! * Total issued balanced of all accounts should be less than `Config::Balance::max_value()`.
//! * Existential Deposit is set to a value greater than zero.
//!
//! Note, you may find the Balances pallet still functions with an ED of zero when the
//! `insecure_zero_ed` cargo feature is enabled. However this is not a configuration which is
//! generally supported, nor will it be.
//!
//! [`frame_tokens`]: ../polkadot_sdk_docs/reference_docs/frame_tokens/index.html
#![cfg_attr(not(feature = "std"), no_std)]
Gavin Wood
committed
mod impl_currency;
mod impl_fungible;
Gavin Wood
committed
mod tests;
mod types;
Gavin Wood
committed
use codec::{Codec, MaxEncodedLen};
Gavin Wood
committed
tokens::{
fungible, BalanceStatus as Status, DepositConsequence,
Fortitude::{self, Force, Polite},
Preservation::{Expendable, Preserve, Protect},
WithdrawConsequence,
},
Currency, Defensive, Get, OnUnbalanced, ReservableCurrency, StoredMap,
Gavin Wood
committed
BoundedSlice, WeakBoundedVec,
use frame_system as system;
Gavin Wood
committed
pub use impl_currency::{NegativeImbalance, PositiveImbalance};
AtLeast32BitUnsigned, Bounded, CheckedAdd, CheckedSub, MaybeSerializeDeserialize,
Saturating, StaticLookup, Zero,
Gavin Wood
committed
ArithmeticError, DispatchError, FixedPointOperand, Perbill, RuntimeDebug, TokenError,
Gavin Wood
committed
use sp_std::{cmp, fmt::Debug, mem, prelude::*, result};
AccountData, AdjustmentDirection, BalanceLock, DustCleaner, ExtraFlags, IdAmount, Reasons,
ReserveData,
pub use pallet::*;
const LOG_TARGET: &str = "runtime::balances";
type AccountIdLookupOf<T> = <<T as frame_system::Config>::Lookup as StaticLookup>::Source;
#[frame_support::pallet]
pub mod pallet {
use frame_support::{
pallet_prelude::*,
Branislav Kontur
committed
traits::{fungible::Credit, tokens::Precision, VariantCount, VariantCountOf},
use frame_system::pallet_prelude::*;
Gavin Wood
committed
pub type CreditOf<T, I> = Credit<<T as frame_system::Config>::AccountId, Pallet<T, I>>;
Kian Paimani
committed
/// Default implementations of [`DefaultConfig`], which can be used to implement [`Config`].
pub mod config_preludes {
use super::*;
use frame_support::{derive_impl, traits::ConstU64};
Kian Paimani
committed
pub struct TestDefaultConfig;
#[derive_impl(frame_system::config_preludes::TestDefaultConfig, no_aggregated_types)]
Kian Paimani
committed
impl frame_system::DefaultConfig for TestDefaultConfig {}
#[frame_support::register_default_impl(TestDefaultConfig)]
impl DefaultConfig for TestDefaultConfig {
gupnik
committed
#[inject_runtime_type]
type RuntimeEvent = ();
#[inject_runtime_type]
type RuntimeHoldReason = ();
#[inject_runtime_type]
type RuntimeFreezeReason = ();
gupnik
committed
Kian Paimani
committed
type Balance = u64;
type ExistentialDeposit = ConstU64<1>;
Kian Paimani
committed
type ReserveIdentifier = ();
type FreezeIdentifier = ();
type DustRemoval = ();
type MaxLocks = ConstU32<100>;
type MaxReserves = ConstU32<100>;
type MaxFreezes = ConstU32<100>;
Kian Paimani
committed
type WeightInfo = ();
}
}
#[pallet::config(with_default)]
pub trait Config<I: 'static = ()>: frame_system::Config {
Gavin Wood
committed
/// The overarching event type.
gupnik
committed
#[pallet::no_default_bounds]
Gavin Wood
committed
type RuntimeEvent: From<Event<Self, I>>
+ IsType<<Self as frame_system::Config>::RuntimeEvent>;
/// The overarching hold reason.
#[pallet::no_default_bounds]
type RuntimeHoldReason: Parameter + Member + MaxEncodedLen + Copy + VariantCount;
/// The overarching freeze reason.
#[pallet::no_default_bounds]
type RuntimeFreezeReason: VariantCount;
Gavin Wood
committed
/// Weight information for extrinsics in this pallet.
type WeightInfo: WeightInfo;
/// The balance of an account.
type Balance: Parameter
+ Member
+ AtLeast32BitUnsigned
+ Codec
+ Default
+ Copy
+ MaybeSerializeDeserialize
+ Debug
Alexander Theißen
committed
+ TypeInfo
+ FixedPointOperand;
/// Handler for the unbalanced reduction when removing a dust account.
#[pallet::no_default_bounds]
Gavin Wood
committed
type DustRemoval: OnUnbalanced<CreditOf<Self, I>>;
/// The minimum amount required to keep an account open. MUST BE GREATER THAN ZERO!
///
/// If you *really* need it to be zero, you can enable the feature `insecure_zero_ed` for
/// this pallet. However, you do so at your own risk: this will open up a major DoS vector.
/// In case you have multiple sources of provider references, you may also get unexpected
/// behaviour if you set this to zero.
///
/// Bottom line: Do yourself a favour and make it at least one!
#[pallet::constant]
#[pallet::no_default_bounds]
type ExistentialDeposit: Get<Self::Balance>;
/// The means of storing the balances of an account.
Kian Paimani
committed
#[pallet::no_default]
type AccountStore: StoredMap<Self::AccountId, AccountData<Self::Balance>>;
Gavin Wood
committed
/// The ID type for reserves.
///
/// Use of reserves is deprecated in favour of holds. See `https://github.com/paritytech/substrate/pull/12951/`
type ReserveIdentifier: Parameter + Member + MaxEncodedLen + Ord + Copy;
/// The ID type for freezes.
type FreezeIdentifier: Parameter + Member + MaxEncodedLen + Copy;
/// The maximum number of locks that should exist on an account.
/// Not strictly enforced, but used for weight estimation.
///
/// Use of locks is deprecated in favour of freezes. See `https://github.com/paritytech/substrate/pull/12951/`
#[pallet::constant]
type MaxLocks: Get<u32>;
/// The maximum number of named reserves that can exist on an account.
///
/// Use of reserves is deprecated in favour of holds. See `https://github.com/paritytech/substrate/pull/12951/`
#[pallet::constant]
Gavin Wood
committed
/// The maximum number of individual freeze locks that can exist on an account at any time.
#[pallet::constant]
type MaxFreezes: Get<u32>;
/// The in-code storage version.
const STORAGE_VERSION: frame_support::traits::StorageVersion =
frame_support::traits::StorageVersion::new(1);
#[pallet::pallet]
#[pallet::storage_version(STORAGE_VERSION)]
pub struct Pallet<T, I = ()>(PhantomData<(T, I)>);
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config<I>, I: 'static = ()> {
/// An account was created with some free balance.
Endowed { account: T::AccountId, free_balance: T::Balance },
/// An account was removed whose balance was non-zero but below ExistentialDeposit,
/// resulting in an outright loss.
DustLost { account: T::AccountId, amount: T::Balance },
/// Transfer succeeded.
Transfer { from: T::AccountId, to: T::AccountId, amount: T::Balance },
/// A balance was set by root.
Gavin Wood
committed
BalanceSet { who: T::AccountId, free: T::Balance },
/// Some balance was reserved (moved from free to reserved).
Reserved { who: T::AccountId, amount: T::Balance },
/// Some balance was unreserved (moved from reserved to free).
Unreserved { who: T::AccountId, amount: T::Balance },
/// Some balance was moved from the reserve of the first account to the second account.
/// Final argument indicates the destination balance type.
ReserveRepatriated {
from: T::AccountId,
to: T::AccountId,
amount: T::Balance,
destination_status: Status,
},
/// Some amount was deposited (e.g. for transaction fees).
Deposit { who: T::AccountId, amount: T::Balance },
/// Some amount was withdrawn from the account (e.g. for transaction fees).
Withdraw { who: T::AccountId, amount: T::Balance },
/// Some amount was removed from the account (e.g. for misbehavior).
Slashed { who: T::AccountId, amount: T::Balance },
Gavin Wood
committed
/// Some amount was minted into an account.
Minted { who: T::AccountId, amount: T::Balance },
/// Some amount was burned from an account.
Burned { who: T::AccountId, amount: T::Balance },
/// Some amount was suspended from an account (it can be restored later).
Suspended { who: T::AccountId, amount: T::Balance },
/// Some amount was restored into an account.
Restored { who: T::AccountId, amount: T::Balance },
/// An account was upgraded.
Upgraded { who: T::AccountId },
/// Total issuance was increased by `amount`, creating a credit to be balanced.
Issued { amount: T::Balance },
/// Total issuance was decreased by `amount`, creating a debt to be balanced.
Rescinded { amount: T::Balance },
/// Some balance was locked.
Locked { who: T::AccountId, amount: T::Balance },
/// Some balance was unlocked.
Unlocked { who: T::AccountId, amount: T::Balance },
/// Some balance was frozen.
Frozen { who: T::AccountId, amount: T::Balance },
/// Some balance was thawed.
Thawed { who: T::AccountId, amount: T::Balance },
/// The `TotalIssuance` was forcefully changed.
TotalIssuanceForced { old: T::Balance, new: T::Balance },
#[pallet::error]
pub enum Error<T, I = ()> {
Gavin Wood
committed
/// Vesting balance too high to send value.
Gavin Wood
committed
/// Account liquidity restrictions prevent withdrawal.
/// Balance too low to send value.
Gavin Wood
committed
/// Value too low to create account due to existential deposit.
Gavin Wood
committed
/// Transfer/payment would kill account.
Expendability,
/// A vesting schedule already exists for this account.
ExistingVestingSchedule,
Gavin Wood
committed
/// Beneficiary account must pre-exist.
Gavin Wood
committed
/// Number of named reserves exceed `MaxReserves`.
Branislav Kontur
committed
/// Number of holds exceed `VariantCountOf<T::RuntimeHoldReason>`.
Gavin Wood
committed
TooManyHolds,
/// Number of freezes exceed `MaxFreezes`.
TooManyFreezes,
/// The issuance cannot be modified since it is already deactivated.
IssuanceDeactivated,
/// The delta cannot be zero.
DeltaZero,
/// The total units issued in the system.
#[pallet::storage]
#[pallet::getter(fn total_issuance)]
#[pallet::whitelist_storage]
pub type TotalIssuance<T: Config<I>, I: 'static = ()> = StorageValue<_, T::Balance, ValueQuery>;
/// The total units of outstanding deactivated balance in the system.
#[pallet::storage]
#[pallet::getter(fn inactive_issuance)]
#[pallet::whitelist_storage]
pub type InactiveIssuance<T: Config<I>, I: 'static = ()> =
StorageValue<_, T::Balance, ValueQuery>;
/// The Balances pallet example of storing the balance of an account.
/// # Example
///
/// ```nocompile
/// impl pallet_balances::Config for Runtime {
/// type AccountStore = StorageMapShim<Self::Account<Runtime>, frame_system::Provider<Runtime>, AccountId, Self::AccountData<Balance>>
/// }
/// ```
///
/// You can also store the balance of an account in the `System` pallet.
///
/// # Example
///
/// ```nocompile
/// impl pallet_balances::Config for Runtime {
/// type AccountStore = System
/// }
/// ```
///
/// But this comes with tradeoffs, storing account balances in the system pallet stores
/// `frame_system` data alongside the account data contrary to storing account balances in the
/// `Balances` pallet, which uses a `StorageMap` to store balances data only.
/// NOTE: This is only used in the case that this pallet is used to store balances.
#[pallet::storage]
pub type Account<T: Config<I>, I: 'static = ()> =
StorageMap<_, Blake2_128Concat, T::AccountId, AccountData<T::Balance>, ValueQuery>;
/// Any liquidity locks on some account balances.
/// NOTE: Should only be accessed when setting, changing and freeing a lock.
///
/// Use of locks is deprecated in favour of freezes. See `https://github.com/paritytech/substrate/pull/12951/`
#[pallet::storage]
#[pallet::getter(fn locks)]
pub type Locks<T: Config<I>, I: 'static = ()> = StorageMap<
_,
Blake2_128Concat,
T::AccountId,
WeakBoundedVec<BalanceLock<T::Balance>, T::MaxLocks>,
ValueQuery,
///
/// Use of reserves is deprecated in favour of holds. See `https://github.com/paritytech/substrate/pull/12951/`
#[pallet::storage]
#[pallet::getter(fn reserves)]
pub type Reserves<T: Config<I>, I: 'static = ()> = StorageMap<
_,
Blake2_128Concat,
T::AccountId,
BoundedVec<ReserveData<T::ReserveIdentifier, T::Balance>, T::MaxReserves>,
Gavin Wood
committed
/// Holds on account balances.
#[pallet::storage]
pub type Holds<T: Config<I>, I: 'static = ()> = StorageMap<
_,
Blake2_128Concat,
T::AccountId,
Branislav Kontur
committed
BoundedVec<
IdAmount<T::RuntimeHoldReason, T::Balance>,
VariantCountOf<T::RuntimeHoldReason>,
>,
Gavin Wood
committed
ValueQuery,
>;
/// Freeze locks on account balances.
#[pallet::storage]
pub type Freezes<T: Config<I>, I: 'static = ()> = StorageMap<
_,
Blake2_128Concat,
T::AccountId,
BoundedVec<IdAmount<T::FreezeIdentifier, T::Balance>, T::MaxFreezes>,
ValueQuery,
>;
#[pallet::genesis_config]
pub struct GenesisConfig<T: Config<I>, I: 'static = ()> {
pub balances: Vec<(T::AccountId, T::Balance)>,
}
impl<T: Config<I>, I: 'static> Default for GenesisConfig<T, I> {
fn default() -> Self {
Self { balances: Default::default() }
}
}
#[pallet::genesis_build]
impl<T: Config<I>, I: 'static> BuildGenesisConfig for GenesisConfig<T, I> {
fn build(&self) {
let total = self.balances.iter().fold(Zero::zero(), |acc: T::Balance, &(_, n)| acc + n);
<TotalIssuance<T, I>>::put(total);
for (_, balance) in &self.balances {
assert!(
*balance >= <T as Config<I>>::ExistentialDeposit::get(),
"the balance of any account should always be at least the existential deposit.",
)
}
// ensure no duplicates exist.
let endowed_accounts = self
.balances
.iter()
.map(|(x, _)| x)
.cloned()
.collect::<sp_std::collections::btree_set::BTreeSet<_>>();
assert!(
endowed_accounts.len() == self.balances.len(),
"duplicate balances in genesis."
);
for &(ref who, free) in self.balances.iter() {
Gavin Wood
committed
frame_system::Pallet::<T>::inc_providers(who);
assert!(T::AccountStore::insert(who, AccountData { free, ..Default::default() })
.is_ok());
impl<T: Config<I>, I: 'static> Hooks<BlockNumberFor<T>> for Pallet<T, I> {
#[cfg(not(feature = "insecure_zero_ed"))]
fn integrity_test() {
assert!(
!<T as Config<I>>::ExistentialDeposit::get().is_zero(),
"The existential deposit must be greater than zero!"
);
assert!(
T::MaxFreezes::get() >= <T::RuntimeFreezeReason as VariantCount>::VARIANT_COUNT,
"MaxFreezes should be greater than or equal to the number of freeze reasons: {} < {}",
T::MaxFreezes::get(), <T::RuntimeFreezeReason as VariantCount>::VARIANT_COUNT,
);
#[pallet::call(weight(<T as Config<I>>::WeightInfo))]
Gavin Wood
committed
impl<T: Config<I>, I: 'static> Pallet<T, I> {
/// Transfer some liquid free balance to another account.
///
/// `transfer_allow_death` will set the `FreeBalance` of the sender and receiver.
/// If the sender's account is below the existential deposit as a result
/// of the transfer, the account will be reaped.
///
/// The dispatch origin for this call must be `Signed` by the transactor.
#[pallet::call_index(0)]
pub fn transfer_allow_death(
origin: OriginFor<T>,
dest: AccountIdLookupOf<T>,
#[pallet::compact] value: T::Balance,
Gavin Wood
committed
let source = ensure_signed(origin)?;
let dest = T::Lookup::lookup(dest)?;
<Self as fungible::Mutate<_>>::transfer(&source, &dest, value, Expendable)?;
Gavin Wood
committed
/// Exactly as `transfer_allow_death`, except the origin must be root and the source account
/// may be specified.
#[pallet::call_index(2)]
pub fn force_transfer(
origin: OriginFor<T>,
source: AccountIdLookupOf<T>,
dest: AccountIdLookupOf<T>,
#[pallet::compact] value: T::Balance,
Gavin Wood
committed
ensure_root(origin)?;
let source = T::Lookup::lookup(source)?;
let dest = T::Lookup::lookup(dest)?;
<Self as fungible::Mutate<_>>::transfer(&source, &dest, value, Expendable)?;
}
Gavin Wood
committed
/// Same as the [`transfer_allow_death`] call, but with a check that the transfer will not
/// kill the origin account.
///
/// 99% of the time you want [`transfer_allow_death`] instead.
///
/// [`transfer_allow_death`]: struct.Pallet.html#method.transfer
#[pallet::call_index(3)]
pub fn transfer_keep_alive(
origin: OriginFor<T>,
dest: AccountIdLookupOf<T>,
#[pallet::compact] value: T::Balance,
Gavin Wood
committed
let source = ensure_signed(origin)?;
let dest = T::Lookup::lookup(dest)?;
<Self as fungible::Mutate<_>>::transfer(&source, &dest, value, Preserve)?;
}
Gavin Wood
committed
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
/// Transfer the entire transferable balance from the caller account.
///
/// NOTE: This function only attempts to transfer _transferable_ balances. This means that
/// any locked, reserved, or existential deposits (when `keep_alive` is `true`), will not be
/// transferred by this function. To ensure that this function results in a killed account,
/// you might need to prepare the account by removing any reference counters, storage
/// deposits, etc...
///
/// The dispatch origin of this call must be Signed.
///
/// - `dest`: The recipient of the transfer.
/// - `keep_alive`: A boolean to determine if the `transfer_all` operation should send all
/// of the funds the account has, causing the sender account to be killed (false), or
/// transfer everything except at least the existential deposit, which will guarantee to
/// keep the sender account alive (true).
#[pallet::call_index(4)]
pub fn transfer_all(
origin: OriginFor<T>,
dest: AccountIdLookupOf<T>,
keep_alive: bool,
) -> DispatchResult {
let transactor = ensure_signed(origin)?;
let keep_alive = if keep_alive { Preserve } else { Expendable };
let reducible_balance = <Self as fungible::Inspect<_>>::reducible_balance(
&transactor,
keep_alive,
Fortitude::Polite,
Gavin Wood
committed
let dest = T::Lookup::lookup(dest)?;
<Self as fungible::Mutate<_>>::transfer(
&transactor,
&dest,
reducible_balance,
keep_alive,
)?;
Ok(())
Gavin Wood
committed
/// Unreserve some balance from a user by force.
///
/// Can only be called by ROOT.
#[pallet::call_index(5)]
pub fn force_unreserve(
origin: OriginFor<T>,
who: AccountIdLookupOf<T>,
amount: T::Balance,
) -> DispatchResult {
ensure_root(origin)?;
let who = T::Lookup::lookup(who)?;
let _leftover = <Self as ReservableCurrency<_>>::unreserve(&who, amount);
Gavin Wood
committed
/// Upgrade a specified account.
///
/// - `origin`: Must be `Signed`.
/// - `who`: The account to be upgraded.
///
/// This will waive the transaction fee if at least all but 10% of the accounts needed to
/// be upgraded. (We let some not have to be upgraded just in order to allow for the
/// possibility of churn).
Gavin Wood
committed
#[pallet::call_index(6)]
#[pallet::weight(T::WeightInfo::upgrade_accounts(who.len() as u32))]
pub fn upgrade_accounts(
origin: OriginFor<T>,
who: Vec<T::AccountId>,
) -> DispatchResultWithPostInfo {
ensure_signed(origin)?;
if who.is_empty() {
return Ok(Pays::Yes.into())
Gavin Wood
committed
let mut upgrade_count = 0;
for i in &who {
let upgraded = Self::ensure_upgraded(i);
if upgraded {
upgrade_count.saturating_inc();
}
}
let proportion_upgraded = Perbill::from_rational(upgrade_count, who.len() as u32);
if proportion_upgraded >= Perbill::from_percent(90) {
Ok(Pays::No.into())
Gavin Wood
committed
Ok(Pays::Yes.into())
Gavin Wood
committed
/// Set the regular balance of a given account.
///
/// The dispatch origin for this call is `root`.
#[pallet::call_index(8)]
#[pallet::weight(
T::WeightInfo::force_set_balance_creating() // Creates a new account.
.max(T::WeightInfo::force_set_balance_killing()) // Kills an existing account.
)]
pub fn force_set_balance(
origin: OriginFor<T>,
who: AccountIdLookupOf<T>,
#[pallet::compact] new_free: T::Balance,
Gavin Wood
committed
ensure_root(origin)?;
let who = T::Lookup::lookup(who)?;
let existential_deposit = Self::ed();
Gavin Wood
committed
let wipeout = new_free < existential_deposit;
let new_free = if wipeout { Zero::zero() } else { new_free };
Gavin Wood
committed
// First we try to modify the account's balance to the forced balance.
let old_free = Self::mutate_account_handling_dust(&who, |account| {
let old_free = account.free;
account.free = new_free;
old_free
})?;
Gavin Wood
committed
// This will adjust the total issuance, which was not done by the `mutate_account`
// above.
if new_free > old_free {
mem::drop(PositiveImbalance::<T, I>::new(new_free - old_free));
} else if new_free < old_free {
mem::drop(NegativeImbalance::<T, I>::new(old_free - new_free));
Gavin Wood
committed
Self::deposit_event(Event::BalanceSet { who, free: new_free });
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
/// Adjust the total issuance in a saturating way.
///
/// Can only be called by root and always needs a positive `delta`.
///
/// # Example
#[doc = docify::embed!("./src/tests/dispatchable_tests.rs", force_adjust_total_issuance_example)]
#[pallet::call_index(9)]
#[pallet::weight(T::WeightInfo::force_adjust_total_issuance())]
pub fn force_adjust_total_issuance(
origin: OriginFor<T>,
direction: AdjustmentDirection,
#[pallet::compact] delta: T::Balance,
) -> DispatchResult {
ensure_root(origin)?;
ensure!(delta > Zero::zero(), Error::<T, I>::DeltaZero);
let old = TotalIssuance::<T, I>::get();
let new = match direction {
AdjustmentDirection::Increase => old.saturating_add(delta),
AdjustmentDirection::Decrease => old.saturating_sub(delta),
};
ensure!(InactiveIssuance::<T, I>::get() <= new, Error::<T, I>::IssuanceDeactivated);
TotalIssuance::<T, I>::set(new);
Self::deposit_event(Event::<T, I>::TotalIssuanceForced { old, new });
Ok(())
}
Gavin Wood
committed
impl<T: Config<I>, I: 'static> Pallet<T, I> {
fn ed() -> T::Balance {
T::ExistentialDeposit::get()
}
Gavin Wood
committed
/// Ensure the account `who` is using the new logic.
///
/// Returns `true` if the account did get upgraded, `false` if it didn't need upgrading.
pub fn ensure_upgraded(who: &T::AccountId) -> bool {
let mut a = T::AccountStore::get(who);
if a.flags.is_new_logic() {
return false
}
a.flags.set_new_logic();
if !a.reserved.is_zero() && a.frozen.is_zero() {
if system::Pallet::<T>::providers(who) == 0 {
// Gah!! We have no provider refs :(
Gavin Wood
committed
// This shouldn't practically happen, but we need a failsafe anyway: let's give
// them enough for an ED.
log::warn!(
target: LOG_TARGET,
"account with a non-zero reserve balance has no provider refs, account_id: '{:?}'.",
who
);
a.free = a.free.max(Self::ed());
Gavin Wood
committed
system::Pallet::<T>::inc_providers(who);
}
let _ = system::Pallet::<T>::inc_consumers_without_limit(who).defensive();
Gavin Wood
committed
}
// Should never fail - we're only setting a bit.
let _ = T::AccountStore::try_mutate_exists(who, |account| -> DispatchResult {
*account = Some(a);
Ok(())
});
Self::deposit_event(Event::Upgraded { who: who.clone() });
Gavin Wood
committed
/// Get the free balance of an account.
pub fn free_balance(who: impl sp_std::borrow::Borrow<T::AccountId>) -> T::Balance {
Self::account(who.borrow()).free
Gavin Wood
committed
/// 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 {
<Self as fungible::Inspect<_>>::reducible_balance(who.borrow(), Expendable, Polite)
Gavin Wood
committed
/// 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`.
///
/// This requires that the account stays alive.
pub fn usable_balance_for_fees(
who: impl sp_std::borrow::Borrow<T::AccountId>,
) -> T::Balance {
<Self as fungible::Inspect<_>>::reducible_balance(who.borrow(), Protect, Polite)
Gavin Wood
committed
/// Get the reserved balance of an account.
pub fn reserved_balance(who: impl sp_std::borrow::Borrow<T::AccountId>) -> T::Balance {
Self::account(who.borrow()).reserved
Gavin Wood
committed
/// Get both the free and reserved balances of an account.
pub(crate) fn account(who: &T::AccountId) -> AccountData<T::Balance> {
T::AccountStore::get(who)
Gavin Wood
committed
/// Mutate an account to some new value, or delete it entirely with `None`. Will enforce
/// `ExistentialDeposit` law, annulling the account as needed.
///
/// It returns the result from the closure. Any dust is handled through the low-level
/// `fungible::Unbalanced` trap-door for legacy dust management.
///
/// 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.
pub(crate) fn mutate_account_handling_dust<R>(
who: &T::AccountId,
f: impl FnOnce(&mut AccountData<T::Balance>) -> R,
) -> Result<R, DispatchError> {
let (r, maybe_dust) = Self::mutate_account(who, f)?;
if let Some(dust) = maybe_dust {
<Self as fungible::Unbalanced<_>>::handle_raw_dust(dust);
Gavin Wood
committed
Ok(r)
Gavin Wood
committed
/// Mutate an account to some new value, or delete it entirely with `None`. Will enforce
/// `ExistentialDeposit` law, annulling the account as needed.
///
/// It returns the result from the closure. Any dust is handled through the low-level
/// `fungible::Unbalanced` trap-door for legacy dust management.
///
/// 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.
pub(crate) fn try_mutate_account_handling_dust<R, E: From<DispatchError>>(
who: &T::AccountId,
f: impl FnOnce(&mut AccountData<T::Balance>, bool) -> Result<R, E>,
) -> Result<R, E> {
let (r, maybe_dust) = Self::try_mutate_account(who, f)?;
if let Some(dust) = maybe_dust {
<Self as fungible::Unbalanced<_>>::handle_raw_dust(dust);
}
Ok(r)
Gavin Wood
committed
/// Mutate an account to some new value, or delete it entirely with `None`. Will enforce
/// `ExistentialDeposit` law, annulling the account as needed.
///
/// It returns both the result from the closure, and an optional amount of dust
/// which should be handled once it is known that all nested mutates that could affect
/// storage items what the dust handler touches have completed.
///
/// 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.
pub(crate) fn mutate_account<R>(
who: &T::AccountId,
f: impl FnOnce(&mut AccountData<T::Balance>) -> R,
) -> Result<(R, Option<T::Balance>), DispatchError> {
Self::try_mutate_account(who, |a, _| -> Result<R, DispatchError> { Ok(f(a)) })
}
/// Returns `true` when `who` has some providers or `insecure_zero_ed` feature is disabled.
/// Returns `false` otherwise.
#[cfg(not(feature = "insecure_zero_ed"))]
fn have_providers_or_no_zero_ed(_: &T::AccountId) -> bool {
true
}
/// Returns `true` when `who` has some providers or `insecure_zero_ed` feature is disabled.
/// Returns `false` otherwise.
#[cfg(feature = "insecure_zero_ed")]
fn have_providers_or_no_zero_ed(who: &T::AccountId) -> bool {
frame_system::Pallet::<T>::providers(who) > 0
}
Gavin Wood
committed
/// 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`.
///
/// It returns both the result from the closure, and an optional amount of dust
/// which should be handled once it is known that all nested mutates that could affect
/// storage items what the dust handler touches have completed.
///
/// 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.
pub(crate) fn try_mutate_account<R, E: From<DispatchError>>(
who: &T::AccountId,
f: impl FnOnce(&mut AccountData<T::Balance>, bool) -> Result<R, E>,
) -> Result<(R, Option<T::Balance>), E> {
Self::ensure_upgraded(who);
let result = T::AccountStore::try_mutate_exists(who, |maybe_account| {
let is_new = maybe_account.is_none();
let mut account = maybe_account.take().unwrap_or_default();
let did_provide =
account.free >= Self::ed() && Self::have_providers_or_no_zero_ed(who);
Gavin Wood
committed
let did_consume =
!is_new && (!account.reserved.is_zero() || !account.frozen.is_zero());
let result = f(&mut account, is_new)?;
let does_provide = account.free >= Self::ed();
Gavin Wood
committed
let does_consume = !account.reserved.is_zero() || !account.frozen.is_zero();
if !did_provide && does_provide {
frame_system::Pallet::<T>::inc_providers(who);
}
if did_consume && !does_consume {
frame_system::Pallet::<T>::dec_consumers(who);
}
if !did_consume && does_consume {
frame_system::Pallet::<T>::inc_consumers(who)?;
}
if did_provide && !does_provide {
// This could reap the account so must go last.
frame_system::Pallet::<T>::dec_providers(who).map_err(|r| {
Liam Aharon
committed
// best-effort revert consumer change.
Gavin Wood
committed
if did_consume && !does_consume {
let _ = frame_system::Pallet::<T>::inc_consumers(who).defensive();
}
if !did_consume && does_consume {
let _ = frame_system::Pallet::<T>::dec_consumers(who);
}
r
})?;
}
Gavin Wood
committed
let maybe_endowed = if is_new { Some(account.free) } else { None };
Gavin Wood
committed
// Handle 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.
//
// Updates `maybe_account` to `Some` iff the account has sufficient balance.
// Evaluates `maybe_dust`, which is `Some` containing the dust to be dropped, iff
// some dust should be dropped.
//
// We should never be dropping if reserved is non-zero. Reserved being non-zero
// should imply that we have a consumer ref, so this is economically safe.
Gavin Wood
committed
let maybe_dust = if account.free < ed && account.reserved.is_zero() {
if account.free.is_zero() {
None
} else {
Some(account.free)
}
Gavin Wood
committed
assert!(
account.free.is_zero() || account.free >= ed || !account.reserved.is_zero()
);
*maybe_account = Some(account);
None
Gavin Wood
committed
Ok((maybe_endowed, maybe_dust, result))
});
result.map(|(maybe_endowed, maybe_dust, result)| {