diff --git a/substrate/frame/support/src/traits.rs b/substrate/frame/support/src/traits.rs index f669046f858f..40e348b5e373 100644 --- a/substrate/frame/support/src/traits.rs +++ b/substrate/frame/support/src/traits.rs @@ -90,8 +90,8 @@ pub use hooks::{ pub mod schedule; mod storage; pub use storage::{ - Incrementable, Instance, PartialStorageInfoTrait, StorageInfo, StorageInfoTrait, - StorageInstance, TrackedStorageKey, WhitelistedStorageKeys, + Consideration, Footprint, Incrementable, Instance, PartialStorageInfoTrait, StorageInfo, + StorageInfoTrait, StorageInstance, TrackedStorageKey, WhitelistedStorageKeys, }; mod dispatch; @@ -111,9 +111,8 @@ pub use preimages::{Bounded, BoundedInline, FetchResult, Hash, QueryPreimage, St mod messages; pub use messages::{ - EnqueueMessage, EnqueueWithOrigin, ExecuteOverweightError, Footprint, HandleMessage, - NoopServiceQueues, ProcessMessage, ProcessMessageError, QueuePausedQuery, ServiceQueues, - TransformOrigin, + EnqueueMessage, EnqueueWithOrigin, ExecuteOverweightError, HandleMessage, NoopServiceQueues, + ProcessMessage, ProcessMessageError, QueuePausedQuery, ServiceQueues, TransformOrigin, }; mod safe_mode; diff --git a/substrate/frame/support/src/traits/messages.rs b/substrate/frame/support/src/traits/messages.rs index 36fa7957dff7..0db163e072b1 100644 --- a/substrate/frame/support/src/traits/messages.rs +++ b/substrate/frame/support/src/traits/messages.rs @@ -17,6 +17,7 @@ //! Traits for managing message queuing and handling. +use super::storage::Footprint; use codec::{Decode, Encode, FullCodec, MaxEncodedLen}; use scale_info::TypeInfo; use sp_core::{ConstU32, Get, TypedGet}; @@ -115,13 +116,6 @@ impl ServiceQueues for NoopServiceQueues { } } -/// The resource footprint of a queue. -#[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug)] -pub struct Footprint { - pub count: u64, - pub size: u64, -} - /// Can enqueue messages for multiple origins. pub trait EnqueueMessage { /// The maximal length any enqueued message may have. diff --git a/substrate/frame/support/src/traits/preimages.rs b/substrate/frame/support/src/traits/preimages.rs index 3e78116202b4..bf08a286dd7c 100644 --- a/substrate/frame/support/src/traits/preimages.rs +++ b/substrate/frame/support/src/traits/preimages.rs @@ -18,6 +18,7 @@ //! Stuff for dealing with 32-byte hashed preimages. use codec::{Decode, Encode, EncodeLike, MaxEncodedLen}; +use scale_info::TypeInfo; use sp_core::{RuntimeDebug, H256}; use sp_io::hashing::blake2_256; use sp_runtime::{traits::ConstU32, DispatchError}; @@ -29,9 +30,7 @@ pub type BoundedInline = crate::BoundedVec>; /// The maximum we expect a single legacy hash lookup to be. const MAX_LEGACY_LEN: u32 = 1_000_000; -#[derive( - Encode, Decode, MaxEncodedLen, Clone, Eq, PartialEq, scale_info::TypeInfo, RuntimeDebug, -)] +#[derive(Encode, Decode, MaxEncodedLen, Clone, Eq, PartialEq, TypeInfo, RuntimeDebug)] #[codec(mel_bound())] pub enum Bounded { /// A Blake2 256 hash with no preimage length. We diff --git a/substrate/frame/support/src/traits/storage.rs b/substrate/frame/support/src/traits/storage.rs index 64eddf51b7fa..cfa24d73f94f 100644 --- a/substrate/frame/support/src/traits/storage.rs +++ b/substrate/frame/support/src/traits/storage.rs @@ -17,9 +17,14 @@ //! Traits for encoding data related to pallet's storage items. +use codec::{Encode, FullCodec, MaxEncodedLen}; use impl_trait_for_tuples::impl_for_tuples; +use scale_info::TypeInfo; pub use sp_core::storage::TrackedStorageKey; -use sp_runtime::{traits::Saturating, RuntimeDebug}; +use sp_runtime::{ + traits::{Member, Saturating}, + DispatchError, RuntimeDebug, +}; use sp_std::{collections::btree_set::BTreeSet, prelude::*}; /// An instance of a pallet in the storage. @@ -127,6 +132,75 @@ impl WhitelistedStorageKeys for Tuple { } } +/// The resource footprint of a bunch of blobs. We assume only the number of blobs and their total +/// size in bytes matter. +#[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug)] +pub struct Footprint { + /// The number of blobs. + pub count: u64, + /// The total size of the blobs in bytes. + pub size: u64, +} + +impl Footprint { + pub fn from_parts(items: usize, len: usize) -> Self { + Self { count: items as u64, size: len as u64 } + } + + pub fn from_encodable(e: impl Encode) -> Self { + Self::from_parts(1, e.encoded_size()) + } +} + +/// Some sort of cost taken from account temporarily in order to offset the cost to the chain of +/// holding some data [`Footprint`] in state. +/// +/// The cost may be increased, reduced or dropped entirely as the footprint changes. +/// +/// A single ticket corresponding to some particular datum held in storage. This is an opaque +/// type, but must itself be stored and generally it should be placed alongside whatever data +/// the ticket was created for. +/// +/// While not technically a linear type owing to the need for `FullCodec`, *this should be +/// treated as one*. Don't type to duplicate it, and remember to drop it when you're done with +/// it. +#[must_use] +pub trait Consideration: Member + FullCodec + TypeInfo + MaxEncodedLen { + /// Create a ticket for the `new` footprint attributable to `who`. This ticket *must* ultimately + /// be consumed through `update` or `drop` once the footprint changes or is removed. + fn new(who: &AccountId, new: Footprint) -> Result; + + /// Optionally consume an old ticket and alter the footprint, enforcing the new cost to `who` + /// and returning the new ticket (or an error if there was an issue). + /// + /// For creating tickets and dropping them, you can use the simpler `new` and `drop` instead. + fn update(self, who: &AccountId, new: Footprint) -> Result; + + /// Consume a ticket for some `old` footprint attributable to `who` which should now been freed. + fn drop(self, who: &AccountId) -> Result<(), DispatchError>; + + /// Consume a ticket for some `old` footprint attributable to `who` which should be sacrificed. + /// + /// This is infallible. In the general case (and it is left unimplemented), then it is + /// equivalent to the consideration never being dropped. Cases which can handle this properly + /// should implement, but it *MUST* rely on the loss of the consideration to the owner. + fn burn(self, _: &AccountId) { + let _ = self; + } +} + +impl Consideration for () { + fn new(_: &A, _: Footprint) -> Result { + Ok(()) + } + fn update(self, _: &A, _: Footprint) -> Result<(), DispatchError> { + Ok(()) + } + fn drop(self, _: &A) -> Result<(), DispatchError> { + Ok(()) + } +} + macro_rules! impl_incrementable { ($($type:ty),+) => { $( diff --git a/substrate/frame/support/src/traits/tokens/fungible/freeze.rs b/substrate/frame/support/src/traits/tokens/fungible/freeze.rs index bc8f22b959cb..8b542ee4c606 100644 --- a/substrate/frame/support/src/traits/tokens/fungible/freeze.rs +++ b/substrate/frame/support/src/traits/tokens/fungible/freeze.rs @@ -18,7 +18,13 @@ //! The traits for putting freezes within a single fungible token class. use scale_info::TypeInfo; -use sp_runtime::DispatchResult; +use sp_arithmetic::{ + traits::{CheckedAdd, CheckedSub}, + ArithmeticError, +}; +use sp_runtime::{DispatchResult, TokenError}; + +use crate::{ensure, traits::tokens::Fortitude}; /// Trait for inspecting a fungible asset which can be frozen. Freezing is essentially setting a /// minimum balance bellow which the total balance (inclusive of any funds placed on hold) may not @@ -65,4 +71,53 @@ pub trait Mutate: Inspect { /// Remove an existing lock. fn thaw(id: &Self::Id, who: &AccountId) -> DispatchResult; + + /// Attempt to alter the amount frozen under the given `id` to `amount`. + /// + /// Fail if the account of `who` has fewer freezable funds than `amount`, unless `fortitude` is + /// `Fortitude::Force`. + fn set_frozen( + id: &Self::Id, + who: &AccountId, + amount: Self::Balance, + fortitude: Fortitude, + ) -> DispatchResult { + let force = fortitude == Fortitude::Force; + ensure!(force || Self::balance_freezable(who) >= amount, TokenError::FundsUnavailable); + Self::set_freeze(id, who, amount) + } + + /// Attempt to set the amount frozen under the given `id` to `amount`, iff this would increase + /// the amount frozen under `id`. Do nothing otherwise. + /// + /// Fail if the account of `who` has fewer freezable funds than `amount`, unless `fortitude` is + /// `Fortitude::Force`. + fn ensure_frozen( + id: &Self::Id, + who: &AccountId, + amount: Self::Balance, + fortitude: Fortitude, + ) -> DispatchResult { + let force = fortitude == Fortitude::Force; + ensure!(force || Self::balance_freezable(who) >= amount, TokenError::FundsUnavailable); + Self::extend_freeze(id, who, amount) + } + + /// Decrease the amount which is being frozen for a particular freeze, failing in the case of + /// underflow. + fn decrease_frozen(id: &Self::Id, who: &AccountId, amount: Self::Balance) -> DispatchResult { + let a = Self::balance_frozen(id, who) + .checked_sub(&amount) + .ok_or(ArithmeticError::Underflow)?; + Self::set_freeze(id, who, a) + } + + /// Increase the amount which is being frozen for a particular freeze, failing in the case that + /// too little balance is available for being frozen. + fn increase_frozen(id: &Self::Id, who: &AccountId, amount: Self::Balance) -> DispatchResult { + let a = Self::balance_frozen(id, who) + .checked_add(&amount) + .ok_or(ArithmeticError::Overflow)?; + Self::set_frozen(id, who, a, Fortitude::Polite) + } } diff --git a/substrate/frame/support/src/traits/tokens/fungible/hold.rs b/substrate/frame/support/src/traits/tokens/fungible/hold.rs index 15c046fbe9a2..6da652d2998d 100644 --- a/substrate/frame/support/src/traits/tokens/fungible/hold.rs +++ b/substrate/frame/support/src/traits/tokens/fungible/hold.rs @@ -92,11 +92,11 @@ pub trait Inspect: super::Inspect { who: &AccountId, amount: Self::Balance, ) -> DispatchResult { - ensure!(Self::hold_available(reason, who), TokenError::CannotCreateHold); ensure!( amount <= Self::reducible_balance(who, Protect, Force), TokenError::FundsUnavailable ); + ensure!(Self::hold_available(reason, who), TokenError::CannotCreateHold); Ok(()) } @@ -242,6 +242,41 @@ pub trait Mutate: Ok(actual) } + /// Hold or release funds in the account of `who` to bring the balance on hold for `reason` to + /// exactly `amount`. + fn set_on_hold( + reason: &Self::Reason, + who: &AccountId, + amount: Self::Balance, + ) -> DispatchResult { + let current_amount = Self::balance_on_hold(reason, who); + if current_amount < amount { + Self::hold(reason, who, amount - current_amount) + } else if current_amount > amount { + Self::release(reason, who, current_amount - amount, Precision::Exact).map(|_| ()) + } else { + Ok(()) + } + } + + /// Release all funds in the account of `who` on hold for `reason`. + /// + /// The actual amount released is returned with `Ok`. + /// + /// If `precision` is `BestEffort`, then the amount actually unreserved and returned as the + /// inner value of `Ok` may be smaller than the `amount` passed. + /// + /// NOTE! The inner of the `Ok` result variant returns the *actual* amount released. This is the + /// opposite of the `ReservableCurrency::unreserve()` result, which gives the amount not able + /// to be released! + fn release_all( + reason: &Self::Reason, + who: &AccountId, + precision: Precision, + ) -> Result { + Self::release(reason, who, Self::balance_on_hold(reason, who), precision) + } + /// Attempt to decrease the balance of `who` which is held for the given `reason` by `amount`. /// /// If `precision` is `BestEffort`, then as much as possible is reduced, up to `amount`, and the @@ -271,6 +306,25 @@ pub trait Mutate: Ok(amount) } + /// Attempt to decrease the balance of `who` which is held for the given `reason` to zero. + /// + /// If `precision` is `BestEffort`, then as much as possible is reduced, up to `amount`, and the + /// amount of tokens reduced is returned. Otherwise, if the total amount can be reduced, then it + /// is and the amount returned, and if not, then nothing changes and `Err` is returned. + /// + /// If `force` is `Force`, then locks/freezes will be ignored. This should only be used when + /// conducting slashing or other activity which materially disadvantages the account holder + /// since it could provide a means of circumventing freezes. + fn burn_all_held( + reason: &Self::Reason, + who: &AccountId, + precision: Precision, + force: Fortitude, + ) -> Result { + let amount = Self::balance_on_hold(reason, who); + Self::burn_held(reason, who, amount, precision, force) + } + /// Transfer held funds into a destination account. /// /// If `mode` is `OnHold`, then the destination account must already exist and the assets diff --git a/substrate/frame/support/src/traits/tokens/fungible/mod.rs b/substrate/frame/support/src/traits/tokens/fungible/mod.rs index 2205fd5dc589..61b75fd6563c 100644 --- a/substrate/frame/support/src/traits/tokens/fungible/mod.rs +++ b/substrate/frame/support/src/traits/tokens/fungible/mod.rs @@ -45,6 +45,15 @@ mod imbalance; mod item_of; mod regular; +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support_procedural::{CloneNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound}; +use scale_info::TypeInfo; +use sp_std::marker::PhantomData; + +use super::{ + Fortitude::{Force, Polite}, + Precision::BestEffort, +}; pub use freeze::{Inspect as InspectFreeze, Mutate as MutateFreeze}; pub use hold::{ Balanced as BalancedHold, Inspect as InspectHold, Mutate as MutateHold, @@ -55,3 +64,182 @@ pub use item_of::ItemOf; pub use regular::{ Balanced, DecreaseIssuance, Dust, IncreaseIssuance, Inspect, Mutate, Unbalanced, }; +use sp_arithmetic::traits::Zero; +use sp_core::Get; +use sp_runtime::{traits::Convert, DispatchError}; + +use crate::{ + ensure, + traits::{Consideration, Footprint}, +}; + +/// Consideration method using a `fungible` balance frozen as the cost exacted for the footprint. +/// +/// The aggregate amount frozen under `R::get()` for any account which has multiple tickets, +/// is the *cumulative* amounts of each ticket's footprint (each individually determined by `D`). +#[derive( + CloneNoBound, + EqNoBound, + PartialEqNoBound, + Encode, + Decode, + TypeInfo, + MaxEncodedLen, + RuntimeDebugNoBound, +)] +#[scale_info(skip_type_params(A, F, R, D))] +#[codec(mel_bound())] +pub struct FreezeConsideration(F::Balance, PhantomData (A, R, D)>) +where + F: MutateFreeze; +impl< + A: 'static, + F: 'static + MutateFreeze, + R: 'static + Get, + D: 'static + Convert, + > Consideration for FreezeConsideration +{ + fn new(who: &A, footprint: Footprint) -> Result { + let new = D::convert(footprint); + F::increase_frozen(&R::get(), who, new)?; + Ok(Self(new, PhantomData)) + } + fn update(self, who: &A, footprint: Footprint) -> Result { + let new = D::convert(footprint); + if self.0 > new { + F::decrease_frozen(&R::get(), who, self.0 - new)?; + } else if new > self.0 { + F::increase_frozen(&R::get(), who, new - self.0)?; + } + Ok(Self(new, PhantomData)) + } + fn drop(self, who: &A) -> Result<(), DispatchError> { + F::decrease_frozen(&R::get(), who, self.0).map(|_| ()) + } +} + +/// Consideration method using a `fungible` balance frozen as the cost exacted for the footprint. +#[derive( + CloneNoBound, + EqNoBound, + PartialEqNoBound, + Encode, + Decode, + TypeInfo, + MaxEncodedLen, + RuntimeDebugNoBound, +)] +#[scale_info(skip_type_params(A, F, R, D))] +#[codec(mel_bound())] +pub struct HoldConsideration(F::Balance, PhantomData (A, R, D)>) +where + F: MutateHold; +impl< + A: 'static, + F: 'static + MutateHold, + R: 'static + Get, + D: 'static + Convert, + > Consideration for HoldConsideration +{ + fn new(who: &A, footprint: Footprint) -> Result { + let new = D::convert(footprint); + F::hold(&R::get(), who, new)?; + Ok(Self(new, PhantomData)) + } + fn update(self, who: &A, footprint: Footprint) -> Result { + let new = D::convert(footprint); + if self.0 > new { + F::release(&R::get(), who, self.0 - new, BestEffort)?; + } else if new > self.0 { + F::hold(&R::get(), who, new - self.0)?; + } + Ok(Self(new, PhantomData)) + } + fn drop(self, who: &A) -> Result<(), DispatchError> { + F::release(&R::get(), who, self.0, BestEffort).map(|_| ()) + } + fn burn(self, who: &A) { + let _ = F::burn_held(&R::get(), who, self.0, BestEffort, Force); + } +} + +/// Basic consideration method using a `fungible` balance frozen as the cost exacted for the +/// footprint. +/// +/// NOTE: This is an optimized implementation, which can only be used for systems where each +/// account has only a single active ticket associated with it since individual tickets do not +/// track the specific balance which is frozen. If you are uncertain then use `FreezeConsideration` +/// instead, since this works in all circumstances. +#[derive( + CloneNoBound, + EqNoBound, + PartialEqNoBound, + Encode, + Decode, + TypeInfo, + MaxEncodedLen, + RuntimeDebugNoBound, +)] +#[scale_info(skip_type_params(A, Fx, Rx, D))] +#[codec(mel_bound())] +pub struct LoneFreezeConsideration(PhantomData (A, Fx, Rx, D)>); +impl< + A: 'static, + Fx: 'static + MutateFreeze, + Rx: 'static + Get, + D: 'static + Convert, + > Consideration for LoneFreezeConsideration +{ + fn new(who: &A, footprint: Footprint) -> Result { + ensure!(Fx::balance_frozen(&Rx::get(), who).is_zero(), DispatchError::Unavailable); + Fx::set_frozen(&Rx::get(), who, D::convert(footprint), Polite).map(|_| Self(PhantomData)) + } + fn update(self, who: &A, footprint: Footprint) -> Result { + Fx::set_frozen(&Rx::get(), who, D::convert(footprint), Polite).map(|_| Self(PhantomData)) + } + fn drop(self, who: &A) -> Result<(), DispatchError> { + Fx::thaw(&Rx::get(), who).map(|_| ()) + } +} + +/// Basic consideration method using a `fungible` balance placed on hold as the cost exacted for the +/// footprint. +/// +/// NOTE: This is an optimized implementation, which can only be used for systems where each +/// account has only a single active ticket associated with it since individual tickets do not +/// track the specific balance which is frozen. If you are uncertain then use `FreezeConsideration` +/// instead, since this works in all circumstances. +#[derive( + CloneNoBound, + EqNoBound, + PartialEqNoBound, + Encode, + Decode, + TypeInfo, + MaxEncodedLen, + RuntimeDebugNoBound, +)] +#[scale_info(skip_type_params(A, Fx, Rx, D))] +#[codec(mel_bound())] +pub struct LoneHoldConsideration(PhantomData (A, Fx, Rx, D)>); +impl< + A: 'static, + F: 'static + MutateHold, + R: 'static + Get, + D: 'static + Convert, + > Consideration for LoneHoldConsideration +{ + fn new(who: &A, footprint: Footprint) -> Result { + ensure!(F::balance_on_hold(&R::get(), who).is_zero(), DispatchError::Unavailable); + F::set_on_hold(&R::get(), who, D::convert(footprint)).map(|_| Self(PhantomData)) + } + fn update(self, who: &A, footprint: Footprint) -> Result { + F::set_on_hold(&R::get(), who, D::convert(footprint)).map(|_| Self(PhantomData)) + } + fn drop(self, who: &A) -> Result<(), DispatchError> { + F::release_all(&R::get(), who, BestEffort).map(|_| ()) + } + fn burn(self, who: &A) { + let _ = F::burn_all_held(&R::get(), who, BestEffort, Force); + } +} diff --git a/substrate/frame/support/src/traits/tokens/fungibles/freeze.rs b/substrate/frame/support/src/traits/tokens/fungibles/freeze.rs index 88f083b0a210..b07d20d6c41c 100644 --- a/substrate/frame/support/src/traits/tokens/fungibles/freeze.rs +++ b/substrate/frame/support/src/traits/tokens/fungibles/freeze.rs @@ -17,8 +17,13 @@ //! The traits for putting freezes within a single fungible token class. +use crate::{ensure, traits::tokens::Fortitude}; use scale_info::TypeInfo; -use sp_runtime::DispatchResult; +use sp_arithmetic::{ + traits::{CheckedAdd, CheckedSub}, + ArithmeticError, +}; +use sp_runtime::{DispatchResult, TokenError}; /// Trait for inspecting a fungible asset which can be frozen. Freezing is essentially setting a /// minimum balance below which the total balance (inclusive of any funds placed on hold) may not @@ -75,4 +80,71 @@ pub trait Mutate: Inspect { /// Remove an existing lock. fn thaw(asset: Self::AssetId, id: &Self::Id, who: &AccountId) -> DispatchResult; + + /// Attempt to alter the amount frozen under the given `id` to `amount`. + /// + /// Fail if the account of `who` has fewer freezable funds than `amount`, unless `fortitude` is + /// `Fortitude::Force`. + fn set_frozen( + asset: Self::AssetId, + id: &Self::Id, + who: &AccountId, + amount: Self::Balance, + fortitude: Fortitude, + ) -> DispatchResult { + let force = fortitude == Fortitude::Force; + ensure!( + force || Self::balance_freezable(asset.clone(), who) >= amount, + TokenError::FundsUnavailable + ); + Self::set_freeze(asset, id, who, amount) + } + + /// Attempt to set the amount frozen under the given `id` to `amount`, iff this would increase + /// the amount frozen under `id`. Do nothing otherwise. + /// + /// Fail if the account of `who` has fewer freezable funds than `amount`, unless `fortitude` is + /// `Fortitude::Force`. + fn ensure_frozen( + asset: Self::AssetId, + id: &Self::Id, + who: &AccountId, + amount: Self::Balance, + fortitude: Fortitude, + ) -> DispatchResult { + let force = fortitude == Fortitude::Force; + ensure!( + force || Self::balance_freezable(asset.clone(), who) >= amount, + TokenError::FundsUnavailable + ); + Self::extend_freeze(asset, id, who, amount) + } + + /// Decrease the amount which is being frozen for a particular lock, failing in the case of + /// underflow. + fn decrease_frozen( + asset: Self::AssetId, + id: &Self::Id, + who: &AccountId, + amount: Self::Balance, + ) -> DispatchResult { + let a = Self::balance_frozen(asset.clone(), id, who) + .checked_sub(&amount) + .ok_or(ArithmeticError::Underflow)?; + Self::set_frozen(asset, id, who, a, Fortitude::Polite) + } + + /// Increase the amount which is being frozen for a particular lock, failing in the case that + /// too little balance is available for being frozen. + fn increase_frozen( + asset: Self::AssetId, + id: &Self::Id, + who: &AccountId, + amount: Self::Balance, + ) -> DispatchResult { + let a = Self::balance_frozen(asset.clone(), id, who) + .checked_add(&amount) + .ok_or(ArithmeticError::Overflow)?; + Self::set_frozen(asset, id, who, a, Fortitude::Polite) + } } diff --git a/substrate/frame/support/src/traits/tokens/fungibles/hold.rs b/substrate/frame/support/src/traits/tokens/fungibles/hold.rs index 4f86704df168..1efd1594213c 100644 --- a/substrate/frame/support/src/traits/tokens/fungibles/hold.rs +++ b/substrate/frame/support/src/traits/tokens/fungibles/hold.rs @@ -301,7 +301,8 @@ pub trait Mutate: Ok(actual) } - /// Attempt to decrease the balance of `who` which is held for the given `reason` by `amount`. + /// Attempt to decrease the `asset` balance of `who` which is held for the given `reason` by + /// `amount`. /// /// If `precision` is true, then as much as possible is reduced, up to `amount`, and the /// amount of tokens reduced is returned. Otherwise, if the total amount can be reduced, then it @@ -334,6 +335,27 @@ pub trait Mutate: Ok(amount) } + /// Attempt to decrease the `asset` balance of `who` which is held for the given `reason` to + /// zero. + /// + /// If `precision` is `BestEffort`, then as much as possible is reduced, up to `amount`, and the + /// amount of tokens reduced is returned. Otherwise, if the total amount can be reduced, then it + /// is and the amount returned, and if not, then nothing changes and `Err` is returned. + /// + /// If `force` is `Force`, then locks/freezes will be ignored. This should only be used when + /// conducting slashing or other activity which materially disadvantages the account holder + /// since it could provide a means of circumventing freezes. + fn burn_all_held( + asset: Self::AssetId, + reason: &Self::Reason, + who: &AccountId, + precision: Precision, + force: Fortitude, + ) -> Result { + let amount = Self::balance_on_hold(asset.clone(), reason, who); + Self::burn_held(asset, reason, who, amount, precision, force) + } + /// Transfer held funds into a destination account. /// /// If `mode` is `OnHold`, then the destination account must already exist and the assets diff --git a/substrate/frame/support/src/traits/tokens/misc.rs b/substrate/frame/support/src/traits/tokens/misc.rs index baf3fd5f3546..84bbe3e8d9c8 100644 --- a/substrate/frame/support/src/traits/tokens/misc.rs +++ b/substrate/frame/support/src/traits/tokens/misc.rs @@ -231,7 +231,16 @@ impl Balance for T { }