mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-27 15:07:59 +00:00
FRAME: Consideration (abstraction over storage deposits) (#1361)
* Consideration trait * Avoid associated type in Consideration * Update substrate/frame/support/src/traits/tokens/fungible/freeze.rs Co-authored-by: Francisco Aguirre <franciscoaguirreperez@gmail.com> * Update substrate/frame/support/src/traits/tokens/fungible/freeze.rs Co-authored-by: Francisco Aguirre <franciscoaguirreperez@gmail.com> * Update substrate/frame/support/src/traits/storage.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * Update substrate/frame/support/src/traits/tokens/fungible/mod.rs Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * ".git/.scripts/commands/fmt/fmt.sh" * Add --------- Co-authored-by: Francisco Aguirre <franciscoaguirreperez@gmail.com> Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> Co-authored-by: command-bot <>
This commit is contained in:
@@ -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<OverweightAddr> ServiceQueues for NoopServiceQueues<OverweightAddr> {
|
||||
}
|
||||
}
|
||||
|
||||
/// 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<Origin: MaxEncodedLen> {
|
||||
/// The maximal length any enqueued message may have.
|
||||
|
||||
@@ -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<u8, ConstU32<128>>;
|
||||
/// 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<T> {
|
||||
/// A Blake2 256 hash with no preimage length. We
|
||||
|
||||
@@ -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<AccountId>: 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<Self, DispatchError>;
|
||||
|
||||
/// 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<Self, DispatchError>;
|
||||
|
||||
/// 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<A> Consideration<A> for () {
|
||||
fn new(_: &A, _: Footprint) -> Result<Self, DispatchError> {
|
||||
Ok(())
|
||||
}
|
||||
fn update(self, _: &A, _: Footprint) -> Result<(), DispatchError> {
|
||||
Ok(())
|
||||
}
|
||||
fn drop(self, _: &A) -> Result<(), DispatchError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_incrementable {
|
||||
($($type:ty),+) => {
|
||||
$(
|
||||
|
||||
@@ -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<AccountId>: Inspect<AccountId> {
|
||||
|
||||
/// 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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,11 +92,11 @@ pub trait Inspect<AccountId>: super::Inspect<AccountId> {
|
||||
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<AccountId>:
|
||||
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::Balance, DispatchError> {
|
||||
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<AccountId>:
|
||||
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<Self::Balance, DispatchError> {
|
||||
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
|
||||
|
||||
@@ -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<A, F, R, D>(F::Balance, PhantomData<fn() -> (A, R, D)>)
|
||||
where
|
||||
F: MutateFreeze<A>;
|
||||
impl<
|
||||
A: 'static,
|
||||
F: 'static + MutateFreeze<A>,
|
||||
R: 'static + Get<F::Id>,
|
||||
D: 'static + Convert<Footprint, F::Balance>,
|
||||
> Consideration<A> for FreezeConsideration<A, F, R, D>
|
||||
{
|
||||
fn new(who: &A, footprint: Footprint) -> Result<Self, DispatchError> {
|
||||
let new = D::convert(footprint);
|
||||
F::increase_frozen(&R::get(), who, new)?;
|
||||
Ok(Self(new, PhantomData))
|
||||
}
|
||||
fn update(self, who: &A, footprint: Footprint) -> Result<Self, DispatchError> {
|
||||
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<A, F, R, D>(F::Balance, PhantomData<fn() -> (A, R, D)>)
|
||||
where
|
||||
F: MutateHold<A>;
|
||||
impl<
|
||||
A: 'static,
|
||||
F: 'static + MutateHold<A>,
|
||||
R: 'static + Get<F::Reason>,
|
||||
D: 'static + Convert<Footprint, F::Balance>,
|
||||
> Consideration<A> for HoldConsideration<A, F, R, D>
|
||||
{
|
||||
fn new(who: &A, footprint: Footprint) -> Result<Self, DispatchError> {
|
||||
let new = D::convert(footprint);
|
||||
F::hold(&R::get(), who, new)?;
|
||||
Ok(Self(new, PhantomData))
|
||||
}
|
||||
fn update(self, who: &A, footprint: Footprint) -> Result<Self, DispatchError> {
|
||||
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<A, Fx, Rx, D>(PhantomData<fn() -> (A, Fx, Rx, D)>);
|
||||
impl<
|
||||
A: 'static,
|
||||
Fx: 'static + MutateFreeze<A>,
|
||||
Rx: 'static + Get<Fx::Id>,
|
||||
D: 'static + Convert<Footprint, Fx::Balance>,
|
||||
> Consideration<A> for LoneFreezeConsideration<A, Fx, Rx, D>
|
||||
{
|
||||
fn new(who: &A, footprint: Footprint) -> Result<Self, DispatchError> {
|
||||
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<Self, DispatchError> {
|
||||
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<A, Fx, Rx, D>(PhantomData<fn() -> (A, Fx, Rx, D)>);
|
||||
impl<
|
||||
A: 'static,
|
||||
F: 'static + MutateHold<A>,
|
||||
R: 'static + Get<F::Reason>,
|
||||
D: 'static + Convert<Footprint, F::Balance>,
|
||||
> Consideration<A> for LoneHoldConsideration<A, F, R, D>
|
||||
{
|
||||
fn new(who: &A, footprint: Footprint) -> Result<Self, DispatchError> {
|
||||
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<Self, DispatchError> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<AccountId>: Inspect<AccountId> {
|
||||
|
||||
/// 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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -301,7 +301,8 @@ pub trait Mutate<AccountId>:
|
||||
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<AccountId>:
|
||||
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<Self::Balance, DispatchError> {
|
||||
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
|
||||
|
||||
@@ -231,7 +231,16 @@ impl<T: FullCodec + Clone + Eq + PartialEq + Debug + scale_info::TypeInfo + MaxE
|
||||
|
||||
/// Simple amalgamation trait to collect together properties for a Balance under one roof.
|
||||
pub trait Balance:
|
||||
AtLeast32BitUnsigned + FullCodec + Copy + Default + Debug + scale_info::TypeInfo + MaxEncodedLen
|
||||
AtLeast32BitUnsigned
|
||||
+ FullCodec
|
||||
+ Copy
|
||||
+ Default
|
||||
+ Debug
|
||||
+ scale_info::TypeInfo
|
||||
+ MaxEncodedLen
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static
|
||||
{
|
||||
}
|
||||
impl<
|
||||
@@ -241,7 +250,10 @@ impl<
|
||||
+ Default
|
||||
+ Debug
|
||||
+ scale_info::TypeInfo
|
||||
+ MaxEncodedLen,
|
||||
+ MaxEncodedLen
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
> Balance for T
|
||||
{
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user