Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FRAME: Consideration (abstraction over storage deposits) #1361

Merged
merged 13 commits into from
Sep 8, 2023
Merged
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
70 changes: 69 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,69 @@ 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.
gavofyork marked this conversation as resolved.
Show resolved Hide resolved
///
/// The cost may be increased, reduced or dropped entirely as the footprint changes.
pub trait Consideration<AccountId> {
/// 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.
type Ticket: Member + FullCodec + TypeInfo + MaxEncodedLen + Default;

/// 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(
who: &AccountId,
old: Option<Self::Ticket>,
new: Option<Footprint>,
) -> Result<Self::Ticket, DispatchError>;

/// Create a ticket for the `new` footprint attributable to `who`. This ticket *must* be
/// consumed (through either `drop` or `update`) if the footprint changes or is removed.
fn new(who: &AccountId, new: Footprint) -> Result<Self::Ticket, DispatchError> {
gavofyork marked this conversation as resolved.
Show resolved Hide resolved
Self::update(who, None, Some(new))
}

/// Consume a ticket for some `old` footprint attributable to `who` which has now been freed.
fn drop(who: &AccountId, old: Self::Ticket) -> Result<(), DispatchError> {
Self::update(who, Some(old), None).map(|_| ())
}
}

impl<A> Consideration<A> for () {
type Ticket = ();
fn update(_: &A, _: Option<()>, _: Option<Footprint>) -> 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 lock, failing in the case of
gavofyork marked this conversation as resolved.
Show resolved Hide resolved
/// 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 lock, failing in the case that
gavofyork marked this conversation as resolved.
Show resolved Hide resolved
/// 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)
}
}
37 changes: 36 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
Loading