Skip to content

Commit

Permalink
FRAME: Consideration (abstraction over storage deposits) (paritytech#…
Browse files Browse the repository at this point in the history
…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 <>
  • Loading branch information
4 people committed Sep 8, 2023
1 parent b9a0906 commit 890b8cf
Show file tree
Hide file tree
Showing 10 changed files with 491 additions and 22 deletions.
9 changes: 4 additions & 5 deletions substrate/frame/support/src/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down
8 changes: 1 addition & 7 deletions substrate/frame/support/src/traits/messages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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.
Expand Down
5 changes: 2 additions & 3 deletions substrate/frame/support/src/traits/preimages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -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
Expand Down
76 changes: 75 additions & 1 deletion substrate/frame/support/src/traits/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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),+) => {
$(
Expand Down
57 changes: 56 additions & 1 deletion substrate/frame/support/src/traits/tokens/fungible/freeze.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
}
56 changes: 55 additions & 1 deletion substrate/frame/support/src/traits/tokens/fungible/hold.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Loading

0 comments on commit 890b8cf

Please sign in to comment.