Skip to content

Commit

Permalink
Explain usage of <T: Config> in FRAME storage + Update parachain pa…
Browse files Browse the repository at this point in the history
…llet template (paritytech#4941)

Explains one of the annoying parts of FRAME storage that we have seen
multiple times in PBA everyone gets stuck on.

I have not updated the other two templates for now, and only reflected
it in the parachain template. That can happen in a follow-up.

- [x] Update possible answers in SE about the same topic.

---------

Co-authored-by: Serban Iorga <[email protected]>
Co-authored-by: command-bot <>
  • Loading branch information
2 people authored and TomaszWaszczyk committed Jul 13, 2024
1 parent c7ce360 commit 2876979
Show file tree
Hide file tree
Showing 16 changed files with 365 additions and 55 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 1 addition & 3 deletions docs/sdk/src/polkadot_sdk/frame_runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,16 +87,14 @@
//! * writing a runtime in pure Rust, as done in [this template](https://github.com/JoshOrndorff/frameless-node-template).
//! * writing a runtime in AssemblyScript,as explored in [this project](https://github.com/LimeChain/subsembly).

use frame::prelude::*;

/// A FRAME based pallet. This `mod` is the entry point for everything else. All
/// `#[pallet::xxx]` macros must be defined in this `mod`. Although, frame also provides an
/// experimental feature to break these parts into different `mod`s. See [`pallet_examples`] for
/// more.
#[docify::export]
#[frame::pallet(dev_mode)]
pub mod pallet {
use super::*;
use frame::prelude::*;

/// The configuration trait of a pallet. Mandatory. Allows a pallet to receive types at a
/// later point from the runtime that wishes to contain it. It allows the pallet to be
Expand Down
199 changes: 199 additions & 0 deletions docs/sdk/src/reference_docs/frame_storage_derives.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
//! <section class="info">
//! In all examples, a few lines of boilerplate have been hidden from each snippet for conciseness.
//! </section>
//!
//! Let's begin by starting to store a `NewType` in a storage item:
//!
//! ```compile_fail
//! #[frame::pallet]
//! pub mod pallet {
//! # use frame::prelude::*;
//! # #[pallet::config]
//! # pub trait Config: frame_system::Config {}
//! # #[pallet::pallet]
//! # pub struct Pallet<T>(_);
//! pub struct NewType(u32);
//
//! #[pallet::storage]
//! pub type Something<T> = StorageValue<_, NewType>;
//! }
//! ```
//!
//! This raises a number of compiler errors, like:
//! ```text
//! the trait `MaxEncodedLen` is not implemented for `NewType`, which is required by
//! `frame::prelude::StorageValue<_GeneratedPrefixForStorageSomething<T>, NewType>:
//! StorageInfoTrait`
//! ```
//!
//! This implies the following set of traits that need to be derived for a type to be stored in
//! `frame` storage:
//! ```rust
//! #[frame::pallet]
//! pub mod pallet {
//! # use frame::prelude::*;
//! # #[pallet::config]
//! # pub trait Config: frame_system::Config {}
//! # #[pallet::pallet]
//! # pub struct Pallet<T>(_);
//! #[derive(codec::Encode, codec::Decode, codec::MaxEncodedLen, scale_info::TypeInfo)]
//! pub struct NewType(u32);
//!
//! #[pallet::storage]
//! pub type Something<T> = StorageValue<_, NewType>;
//! }
//! ```
//!
//! Next, let's look at how this will differ if we are to store a type that is derived from `T` in
//! storage, such as [`frame::prelude::BlockNumberFor`]:
//! ```compile_fail
//! #[frame::pallet]
//! pub mod pallet {
//! # use frame::prelude::*;
//! # #[pallet::config]
//! # pub trait Config: frame_system::Config {}
//! # #[pallet::pallet]
//! # pub struct Pallet<T>(_);
//! #[derive(codec::Encode, codec::Decode, codec::MaxEncodedLen, scale_info::TypeInfo)]
//! pub struct NewType<T: Config>(BlockNumberFor<T>);
//!
//! #[pallet::storage]
//! pub type Something<T: Config> = StorageValue<_, NewType<T>>;
//! }
//! ```
//!
//! Surprisingly, this will also raise a number of errors, like:
//! ```text
//! the trait `TypeInfo` is not implemented for `T`, which is required
//! by`frame_support::pallet_prelude::StorageValue<pallet_2::_GeneratedPrefixForStorageSomething<T>,
//! pallet_2::NewType<T>>:StorageEntryMetadataBuilder
//! ```
//!
//! Why is that? The underlying reason is that the `TypeInfo` `derive` macro will only work for
//! `NewType` if all of `NewType`'s generics also implement `TypeInfo`. This is not the case for `T`
//! in the example above.
//!
//! If you expand an instance of the derive, you will find something along the lines of:
//! `impl<T> TypeInfo for NewType<T> where T: TypeInfo { ... }`. This is the reason why the
//! `TypeInfo` trait is required for `T`.
//!
//! To fix this, we need to add a `#[scale_info(skip_type_params(T))]`
//! attribute to `NewType`. This additional macro will instruct the `derive` to skip the bound on
//! `T`.
//! ```rust
//! #[frame::pallet]
//! pub mod pallet {
//! # use frame::prelude::*;
//! # #[pallet::config]
//! # pub trait Config: frame_system::Config {}
//! # #[pallet::pallet]
//! # pub struct Pallet<T>(_);
//! #[derive(codec::Encode, codec::Decode, codec::MaxEncodedLen, scale_info::TypeInfo)]
//! #[scale_info(skip_type_params(T))]
//! pub struct NewType<T: Config>(BlockNumberFor<T>);
//!
//! #[pallet::storage]
//! pub type Something<T: Config> = StorageValue<_, NewType<T>>;
//! }
//! ```
//!
//! Next, let's say we wish to store `NewType` as [`frame::prelude::ValueQuery`], which means it
//! must also implement `Default`. This should be as simple as adding `derive(Default)` to it,
//! right?
//! ```compile_fail
//! #[frame::pallet]
//! pub mod pallet {
//! # use frame::prelude::*;
//! # #[pallet::config]
//! # pub trait Config: frame_system::Config {}
//! # #[pallet::pallet]
//! # pub struct Pallet<T>(_);
//! #[derive(codec::Encode, codec::Decode, codec::MaxEncodedLen, scale_info::TypeInfo, Default)]
//! #[scale_info(skip_type_params(T))]
//! pub struct NewType<T: Config>(BlockNumberFor<T>);
//!
//! #[pallet::storage]
//! pub type Something<T: Config> = StorageValue<_, NewType<T>, ValueQuery>;
//! }
//! ```
//!
//! Under the hood, the expansion of the `derive(Default)` will suffer from the same restriction as
//! before: it will only work if `T: Default`, and `T` is not `Default`. Note that this is an
//! expected issue: `T` is merely a wrapper of many other types, such as `BlockNumberFor<T>`.
//! `BlockNumberFor<T>` should indeed implement `Default`, but `T` implementing `Default` is rather
//! meaningless.
//!
//! To fix this, frame provides a set of macros that are analogous to normal rust derive macros, but
//! work nicely on top of structs that are generic over `T: Config`. These macros are:
//!
//! - [`frame::prelude::DefaultNoBound`]
//! - [`frame::prelude::DebugNoBound`]
//! - [`frame::prelude::PartialEqNoBound`]
//! - [`frame::prelude::EqNoBound`]
//! - [`frame::prelude::CloneNoBound`]
//! - [`frame::prelude::PartialOrdNoBound`]
//! - [`frame::prelude::OrdNoBound`]
//!
//! The above traits are almost certainly needed for your tests: To print your type, assert equality
//! or clone it.
//!
//! We can fix the following example by using [`frame::prelude::DefaultNoBound`].
//! ```rust
//! #[frame::pallet]
//! pub mod pallet {
//! # use frame::prelude::*;
//! # #[pallet::config]
//! # pub trait Config: frame_system::Config {}
//! # #[pallet::pallet]
//! # pub struct Pallet<T>(_);
//! #[derive(
//! codec::Encode,
//! codec::Decode,
//! codec::MaxEncodedLen,
//! scale_info::TypeInfo,
//! DefaultNoBound
//! )]
//! #[scale_info(skip_type_params(T))]
//! pub struct NewType<T:Config>(BlockNumberFor<T>);
//!
//! #[pallet::storage]
//! pub type Something<T: Config> = StorageValue<_, NewType<T>, ValueQuery>;
//! }
//! ```
//!
//! Finally, if a custom type that is provided through `Config` is to be stored in the storage, it
//! is subject to the same trait requirements. The following does not work:
//! ```compile_fail
//! #[frame::pallet]
//! pub mod pallet {
//! use frame::prelude::*;
//! #[pallet::config]
//! pub trait Config: frame_system::Config {
//! type CustomType;
//! }
//! #[pallet::pallet]
//! pub struct Pallet<T>(_);
//! #[pallet::storage]
//! pub type Something<T: Config> = StorageValue<_, T::CustomType>;
//! }
//! ```
//!
//! But adding the right trait bounds will fix it.
//! ```rust
//! #[frame::pallet]
//! pub mod pallet {
//! use frame::prelude::*;
//! #[pallet::config]
//! pub trait Config: frame_system::Config {
//! type CustomType: codec::FullCodec
//! + codec::MaxEncodedLen
//! + scale_info::TypeInfo
//! + Debug
//! + Default;
//! }
//! #[pallet::pallet]
//! pub struct Pallet<T>(_);
//! #[pallet::storage]
//! pub type Something<T: Config> = StorageValue<_, T::CustomType>;
//! }
//! ```
4 changes: 4 additions & 0 deletions docs/sdk/src/reference_docs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ pub mod signed_extensions;
/// Learn about *Origins*, a topic in FRAME that enables complex account abstractions to be built.
pub mod frame_origin;

/// Learn about the details of what derives are needed for a type to be store-able in `frame`
/// storage.
pub mod frame_storage_derives;

/// Learn about how to write safe and defensive code in your FRAME runtime.
pub mod defensive_programming;

Expand Down
3 changes: 2 additions & 1 deletion substrate/frame/bags-list/src/list/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -777,7 +777,8 @@ mod bags {
assert_eq!(bag_1000.tail, Some(4));
assert_eq!(bag_1000.iter().count(), 3);
bag_1000.insert_node_unchecked(node(4, None, None, bag_1000.bag_upper)); // panics in debug
assert_eq!(bag_1000.iter().count(), 3); // in release we expect it to silently ignore the request.
assert_eq!(bag_1000.iter().count(), 3); // in release we expect it to silently ignore the
// request.
});
}

Expand Down
8 changes: 7 additions & 1 deletion substrate/frame/nis/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -755,7 +755,13 @@ pub mod pallet {
// We ignore this error as it just means the amount we're trying to deposit is
// dust and the beneficiary account doesn't exist.
.or_else(
|e| if e == TokenError::CannotCreate.into() { Ok(()) } else { Err(e) },
|e| {
if e == TokenError::CannotCreate.into() {
Ok(())
} else {
Err(e)
}
},
)?;
summary.receipts_on_hold.saturating_reduce(on_hold);
}
Expand Down
1 change: 0 additions & 1 deletion substrate/frame/nomination-pools/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -494,7 +494,6 @@ impl ClaimPermission {
frame_support::PartialEqNoBound,
)]
#[cfg_attr(feature = "std", derive(DefaultNoBound))]
#[codec(mel_bound(T: Config))]
#[scale_info(skip_type_params(T))]
pub struct PoolMember<T: Config> {
/// The identifier of the pool to which `who` belongs.
Expand Down
42 changes: 36 additions & 6 deletions substrate/frame/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,43 @@
//! > **F**ramework for **R**untime **A**ggregation of **M**odularized **E**ntities: Substrate's
//! > State Transition Function (Runtime) Framework.
//!
//! ## Documentation
//! //! ## Usage
//!
//! See [`polkadot_sdk::frame`](../polkadot_sdk_docs/polkadot_sdk/frame_runtime/index.html).
//! The main intended use of this crate is for it to be imported with its preludes:
//!
//! ## WARNING: Experimental
//! ```
//! # use polkadot_sdk_frame as frame;
//! #[frame::pallet]
//! pub mod pallet {
//! # use polkadot_sdk_frame as frame;
//! use frame::prelude::*;
//! // ^^ using the prelude!
//!
//! **This crate and all of its content is experimental, and should not yet be used in production.**
//! #[pallet::config]
//! pub trait Config: frame_system::Config {}
//!
//! #[pallet::pallet]
//! pub struct Pallet<T>(_);
//! }
//!
//! pub mod tests {
//! # use polkadot_sdk_frame as frame;
//! use frame::testing_prelude::*;
//! }
//!
//! pub mod runtime {
//! # use polkadot_sdk_frame as frame;
//! use frame::runtime::prelude::*;
//! }
//! ```
//!
//! See: [`prelude`], [`testing_prelude`] and [`runtime::prelude`].
//!
//! Please note that this crate can only be imported as `polkadot-sdk-frame` or `frame`.
//!
//! ## Documentation
//!
//! See [`polkadot_sdk::frame`](../polkadot_sdk_docs/polkadot_sdk/frame_runtime/index.html).
//!
//! ## Underlying dependencies
//!
Expand All @@ -46,9 +76,9 @@
//! In short, this crate only re-exports types and traits from multiple sources. All of these
//! sources are listed (and re-exported again) in [`deps`].
//!
//! ## Usage
//! ## WARNING: Experimental
//!
//! Please note that this crate can only be imported as `polkadot-sdk-frame` or `frame`.
//! **This crate and all of its content is experimental, and should not yet be used in production.**

#![cfg_attr(not(feature = "std"), no_std)]
#![cfg(feature = "experimental")]
Expand Down
2 changes: 0 additions & 2 deletions substrate/frame/state-trie-migration/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,6 @@ pub mod pallet {
MaxEncodedLen,
)]
#[scale_info(skip_type_params(MaxKeyLen))]
#[codec(mel_bound())]
pub enum Progress<MaxKeyLen: Get<u32>> {
/// Yet to begin.
ToStart,
Expand All @@ -126,7 +125,6 @@ pub mod pallet {
///
/// It tracks the last top and child keys read.
#[derive(Clone, Encode, Decode, scale_info::TypeInfo, PartialEq, Eq, MaxEncodedLen)]
#[codec(mel_bound(T: Config))]
#[scale_info(skip_type_params(T))]
pub struct MigrationTask<T: Config> {
/// The current top trie migration progress.
Expand Down
12 changes: 12 additions & 0 deletions substrate/frame/support/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2297,6 +2297,18 @@ pub mod pallet_macros {
/// }
/// ```
///
/// ### Value Trait Bounds
///
/// To use a type as the value of a storage type, be it `StorageValue`, `StorageMap` or
/// anything else, you need to meet a number of trait bound constraints.
///
/// See: <https://paritytech.github.io/polkadot-sdk/master/polkadot_sdk_docs/reference_docs/frame_storage_derives/index.html>.
///
/// Notably, all value types need to implement `Encode`, `Decode`, `MaxEncodedLen` and
/// `TypeInfo`, and possibly `Default`, if
/// [`ValueQuery`](frame_support::storage::types::ValueQuery) is used, explained in the
/// next section.
///
/// ### QueryKind
///
/// Every storage type mentioned above has a generic type called
Expand Down
19 changes: 2 additions & 17 deletions templates/minimal/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,21 +37,6 @@ A Polkadot SDK based project such as this one consists of:
* 🛠️ Depending on your operating system and Rust version, there might be additional
packages required to compile this template - please take note of the Rust compiler output.

## Customization

In case you would like to change the pallet's name from `pallet-minimal-template` to `pallet-test`,
you would need to implement the following changes:
* Change the pallet folder name from `template` to `test` for consistency
* Also for consistency, in `/runtime/src/lib.rs`, change from `pub type Template` to `pub type Test`,
and don't forget to also customize the corresponding comments.
* Change `pallet-minimal-template` to `pallet-test` in the following files:
* `/runtime/Cargo.toml`
* `/runtime/src/lib.rs`
* `/pallets/test/Cargo.toml`
* `/src/lib.rs`
* Change `pallet_minimal_template` to `pallet_test` in the following files:
* `/runtime/src/lib.rs`

### Build

🔨 Use the following command to build the node without launching it:
Expand Down Expand Up @@ -80,8 +65,8 @@ docker run --rm polkadot-sdk-minimal-template --dev
Development chains:

* 🧹 Do not persist the state.
* 💰 Are preconfigured with a genesis state that includes several pre-funded development accounts.
* 🧑‍⚖️ Development accounts are used as `sudo` accounts.
* 💰 Are pre-configured with a genesis state that includes several pre-funded development accounts.
* 🧑‍⚖️ One development account (`ALICE`) is used as `sudo` accounts.

### Connect with the Polkadot-JS Apps Front-End

Expand Down
Loading

0 comments on commit 2876979

Please sign in to comment.