From 02e50adf7ba6cc65a9ef5c332b3e2974c8d23f48 Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Wed, 10 Jul 2024 17:46:08 +0100 Subject: [PATCH] Explain usage of `` in FRAME storage + Update parachain pallet template (#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 Co-authored-by: command-bot <> --- Cargo.lock | 1 + docs/sdk/src/polkadot_sdk/frame_runtime.rs | 4 +- .../reference_docs/frame_storage_derives.rs | 199 ++++++++++++++++++ docs/sdk/src/reference_docs/mod.rs | 4 + substrate/frame/bags-list/src/list/tests.rs | 3 +- substrate/frame/nis/src/lib.rs | 8 +- substrate/frame/nomination-pools/src/lib.rs | 1 - substrate/frame/src/lib.rs | 42 +++- .../frame/state-trie-migration/src/lib.rs | 2 - substrate/frame/support/src/lib.rs | 12 ++ templates/minimal/README.md | 19 +- templates/minimal/pallets/template/src/lib.rs | 3 + .../parachain/pallets/template/Cargo.toml | 12 +- .../pallets/template/src/benchmarking.rs | 17 +- .../parachain/pallets/template/src/lib.rs | 91 +++++++- .../parachain/pallets/template/src/tests.rs | 2 +- 16 files changed, 365 insertions(+), 55 deletions(-) create mode 100644 docs/sdk/src/reference_docs/frame_storage_derives.rs diff --git a/Cargo.lock b/Cargo.lock index ab38930b1b07..3b46a2680e8d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11270,6 +11270,7 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", + "sp-std 14.0.0", ] [[package]] diff --git a/docs/sdk/src/polkadot_sdk/frame_runtime.rs b/docs/sdk/src/polkadot_sdk/frame_runtime.rs index f9b8a381365c..39255c8f51ad 100644 --- a/docs/sdk/src/polkadot_sdk/frame_runtime.rs +++ b/docs/sdk/src/polkadot_sdk/frame_runtime.rs @@ -87,8 +87,6 @@ //! * 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 @@ -96,7 +94,7 @@ use frame::prelude::*; #[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 diff --git a/docs/sdk/src/reference_docs/frame_storage_derives.rs b/docs/sdk/src/reference_docs/frame_storage_derives.rs new file mode 100644 index 000000000000..3d9edef398a0 --- /dev/null +++ b/docs/sdk/src/reference_docs/frame_storage_derives.rs @@ -0,0 +1,199 @@ +//!
+//! In all examples, a few lines of boilerplate have been hidden from each snippet for conciseness. +//!
+//! +//! 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(_); +//! pub struct NewType(u32); +// +//! #[pallet::storage] +//! pub type Something = 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, 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(_); +//! #[derive(codec::Encode, codec::Decode, codec::MaxEncodedLen, scale_info::TypeInfo)] +//! pub struct NewType(u32); +//! +//! #[pallet::storage] +//! pub type Something = 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(_); +//! #[derive(codec::Encode, codec::Decode, codec::MaxEncodedLen, scale_info::TypeInfo)] +//! pub struct NewType(BlockNumberFor); +//! +//! #[pallet::storage] +//! pub type Something = StorageValue<_, NewType>; +//! } +//! ``` +//! +//! 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::NewType>: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 TypeInfo for NewType 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(_); +//! #[derive(codec::Encode, codec::Decode, codec::MaxEncodedLen, scale_info::TypeInfo)] +//! #[scale_info(skip_type_params(T))] +//! pub struct NewType(BlockNumberFor); +//! +//! #[pallet::storage] +//! pub type Something = StorageValue<_, NewType>; +//! } +//! ``` +//! +//! 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(_); +//! #[derive(codec::Encode, codec::Decode, codec::MaxEncodedLen, scale_info::TypeInfo, Default)] +//! #[scale_info(skip_type_params(T))] +//! pub struct NewType(BlockNumberFor); +//! +//! #[pallet::storage] +//! pub type Something = StorageValue<_, NewType, 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`. +//! `BlockNumberFor` 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(_); +//! #[derive( +//! codec::Encode, +//! codec::Decode, +//! codec::MaxEncodedLen, +//! scale_info::TypeInfo, +//! DefaultNoBound +//! )] +//! #[scale_info(skip_type_params(T))] +//! pub struct NewType(BlockNumberFor); +//! +//! #[pallet::storage] +//! pub type Something = StorageValue<_, NewType, 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(_); +//! #[pallet::storage] +//! pub type Something = 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(_); +//! #[pallet::storage] +//! pub type Something = StorageValue<_, T::CustomType>; +//! } +//! ``` diff --git a/docs/sdk/src/reference_docs/mod.rs b/docs/sdk/src/reference_docs/mod.rs index 688339b7e380..c69c79365427 100644 --- a/docs/sdk/src/reference_docs/mod.rs +++ b/docs/sdk/src/reference_docs/mod.rs @@ -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; diff --git a/substrate/frame/bags-list/src/list/tests.rs b/substrate/frame/bags-list/src/list/tests.rs index cd39b0831726..e5fff76d75c7 100644 --- a/substrate/frame/bags-list/src/list/tests.rs +++ b/substrate/frame/bags-list/src/list/tests.rs @@ -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. }); } diff --git a/substrate/frame/nis/src/lib.rs b/substrate/frame/nis/src/lib.rs index f38755836fb9..d815ea6ac115 100644 --- a/substrate/frame/nis/src/lib.rs +++ b/substrate/frame/nis/src/lib.rs @@ -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); } diff --git a/substrate/frame/nomination-pools/src/lib.rs b/substrate/frame/nomination-pools/src/lib.rs index 2aaea0446366..2b5fe8b60412 100644 --- a/substrate/frame/nomination-pools/src/lib.rs +++ b/substrate/frame/nomination-pools/src/lib.rs @@ -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 { /// The identifier of the pool to which `who` belongs. diff --git a/substrate/frame/src/lib.rs b/substrate/frame/src/lib.rs index e41f7f1c0ef3..e09d8fc4fa1d 100644 --- a/substrate/frame/src/lib.rs +++ b/substrate/frame/src/lib.rs @@ -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(_); +//! } +//! +//! 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 //! @@ -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")] diff --git a/substrate/frame/state-trie-migration/src/lib.rs b/substrate/frame/state-trie-migration/src/lib.rs index 4ec649f9080d..22ad640d3bd2 100644 --- a/substrate/frame/state-trie-migration/src/lib.rs +++ b/substrate/frame/state-trie-migration/src/lib.rs @@ -109,7 +109,6 @@ pub mod pallet { MaxEncodedLen, )] #[scale_info(skip_type_params(MaxKeyLen))] - #[codec(mel_bound())] pub enum Progress> { /// Yet to begin. ToStart, @@ -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 { /// The current top trie migration progress. diff --git a/substrate/frame/support/src/lib.rs b/substrate/frame/support/src/lib.rs index 138091689a59..94f4b9dd4bdc 100644 --- a/substrate/frame/support/src/lib.rs +++ b/substrate/frame/support/src/lib.rs @@ -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: . + /// + /// 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 diff --git a/templates/minimal/README.md b/templates/minimal/README.md index eaa21a05ee89..b556a4536089 100644 --- a/templates/minimal/README.md +++ b/templates/minimal/README.md @@ -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: @@ -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 diff --git a/templates/minimal/pallets/template/src/lib.rs b/templates/minimal/pallets/template/src/lib.rs index 713f014bbe61..92b90ad4412b 100644 --- a/templates/minimal/pallets/template/src/lib.rs +++ b/templates/minimal/pallets/template/src/lib.rs @@ -1,4 +1,7 @@ //! A shell pallet built with [`frame`]. +//! +//! To get started with this pallet, try implementing the guide in +//! #![cfg_attr(not(feature = "std"), no_std)] diff --git a/templates/parachain/pallets/template/Cargo.toml b/templates/parachain/pallets/template/Cargo.toml index 250895843e4f..3b651ce427f9 100644 --- a/templates/parachain/pallets/template/Cargo.toml +++ b/templates/parachain/pallets/template/Cargo.toml @@ -25,10 +25,13 @@ frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +# primitive deps +sp-runtime = { workspace = true } +sp-std = { workspace = true } + [dev-dependencies] sp-core = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } -sp-runtime = { workspace = true, default-features = true } [features] default = ["std"] @@ -40,13 +43,14 @@ runtime-benchmarks = [ ] std = [ "codec/std", + "scale-info/std", + "frame-benchmarking?/std", "frame-support/std", "frame-system/std", - "scale-info/std", - "sp-core/std", - "sp-io/std", + "sp-runtime/std", + "sp-std/std", ] try-runtime = [ "frame-support/try-runtime", diff --git a/templates/parachain/pallets/template/src/benchmarking.rs b/templates/parachain/pallets/template/src/benchmarking.rs index d1a9554aed6d..5acad6e60dec 100644 --- a/templates/parachain/pallets/template/src/benchmarking.rs +++ b/templates/parachain/pallets/template/src/benchmarking.rs @@ -1,34 +1,33 @@ //! Benchmarking setup for pallet-template #![cfg(feature = "runtime-benchmarks")] -use super::*; -#[allow(unused)] -use crate::Pallet as Template; +use super::*; use frame_benchmarking::v2::*; -use frame_system::RawOrigin; #[benchmarks] mod benchmarks { use super::*; + #[cfg(test)] + use crate::pallet::Pallet as Template; + use frame_system::RawOrigin; #[benchmark] fn do_something() { - let value = 100u32; let caller: T::AccountId = whitelisted_caller(); #[extrinsic_call] - do_something(RawOrigin::Signed(caller), value); + do_something(RawOrigin::Signed(caller), 100); - assert_eq!(Something::::get(), Some(value)); + assert_eq!(Something::::get().map(|v| v.block_number), Some(100u32.into())); } #[benchmark] fn cause_error() { - Something::::put(100u32); + Something::::put(CompositeStruct { block_number: 100u32.into() }); let caller: T::AccountId = whitelisted_caller(); #[extrinsic_call] cause_error(RawOrigin::Signed(caller)); - assert_eq!(Something::::get(), Some(101u32)); + assert_eq!(Something::::get().map(|v| v.block_number), Some(101u32.into())); } impl_benchmark_test_suite!(Template, crate::mock::new_test_ext(), crate::mock::Test); diff --git a/templates/parachain/pallets/template/src/lib.rs b/templates/parachain/pallets/template/src/lib.rs index 0420e2b90821..6bfb98972aed 100644 --- a/templates/parachain/pallets/template/src/lib.rs +++ b/templates/parachain/pallets/template/src/lib.rs @@ -1,3 +1,50 @@ +//! # Template Pallet +//! +//! A pallet with minimal functionality to help developers understand the essential components of +//! writing a FRAME pallet. It is typically used in beginner tutorials or in Polkadot SDK template +//! as a starting point for creating a new pallet and **not meant to be used in production**. +//! +//! ## Overview +//! +//! This template pallet contains basic examples of: +//! - declaring a storage item that stores a single block-number +//! - declaring and using events +//! - declaring and using errors +//! - a dispatchable function that allows a user to set a new value to storage and emits an event +//! upon success +//! - another dispatchable function that causes a custom error to be thrown +//! +//! Each pallet section is annotated with an attribute using the `#[pallet::...]` procedural macro. +//! This macro generates the necessary code for a pallet to be aggregated into a FRAME runtime. +//! +//! To get started with pallet development, consider using this tutorial: +//! +//! +//! +//! And reading the main documentation of the `frame` crate: +//! +//! +//! +//! And looking at the frame [`kitchen-sink`](https://paritytech.github.io/polkadot-sdk/master/pallet_example_kitchensink/index.html) +//! pallet, a showcase of all pallet macros. +//! +//! ### Pallet Sections +//! +//! The pallet sections in this template are: +//! +//! - A **configuration trait** that defines the types and parameters which the pallet depends on +//! (denoted by the `#[pallet::config]` attribute). See: [`Config`]. +//! - A **means to store pallet-specific data** (denoted by the `#[pallet::storage]` attribute). +//! See: [`storage_types`]. +//! - A **declaration of the events** this pallet emits (denoted by the `#[pallet::event]` +//! attribute). See: [`Event`]. +//! - A **declaration of the errors** that this pallet can throw (denoted by the `#[pallet::error]` +//! attribute). See: [`Error`]. +//! - A **set of dispatchable functions** that define the pallet's functionality (denoted by the +//! `#[pallet::call]` attribute). See: [`dispatchables`]. +//! +//! Run `cargo doc --package pallet-template --open` to view this pallet's documentation. + #![cfg_attr(not(feature = "std"), no_std)] pub use pallet::*; @@ -21,8 +68,9 @@ mod benchmarking; // #[frame_support::pallet] pub mod pallet { - use frame_support::{dispatch::DispatchResultWithPostInfo, pallet_prelude::*}; + use frame_support::{dispatch::DispatchResultWithPostInfo, pallet_prelude::*, DefaultNoBound}; use frame_system::pallet_prelude::*; + use sp_runtime::traits::{CheckedAdd, One}; /// Configure the pallet by specifying the parameters and types on which it depends. #[pallet::config] @@ -38,11 +86,22 @@ pub mod pallet { #[pallet::pallet] pub struct Pallet(_); + /// A struct to store a single block-number. Has all the right derives to store it in storage. + /// + #[derive( + Encode, Decode, MaxEncodedLen, TypeInfo, CloneNoBound, PartialEqNoBound, DefaultNoBound, + )] + #[scale_info(skip_type_params(T))] + pub struct CompositeStruct { + /// A block number. + pub(crate) block_number: BlockNumberFor, + } + /// The pallet's storage items. /// /// #[pallet::storage] - pub type Something = StorageValue<_, u32>; + pub type Something = StorageValue<_, CompositeStruct>; /// Pallets use events to inform users when important changes are made. /// @@ -50,7 +109,7 @@ pub mod pallet { #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { /// We usually use passive tense for events. - SomethingStored { something: u32, who: T::AccountId }, + SomethingStored { block_number: BlockNumberFor, who: T::AccountId }, } /// Errors inform users that something went wrong. @@ -76,19 +135,23 @@ pub mod pallet { /// storage and emits an event. This function must be dispatched by a signed extrinsic. #[pallet::call_index(0)] #[pallet::weight(Weight::from_parts(10_000, 0) + T::DbWeight::get().writes(1))] - pub fn do_something(origin: OriginFor, something: u32) -> DispatchResultWithPostInfo { + pub fn do_something(origin: OriginFor, bn: u32) -> DispatchResultWithPostInfo { // Check that the extrinsic was signed and get the signer. // This function will return an error if the extrinsic is not signed. // let who = ensure_signed(origin)?; + // Convert the u32 into a block number. This is possible because the set of trait bounds + // defined in [`frame_system::Config::BlockNumber`]. + let block_number: BlockNumberFor = bn.into(); + // Update storage. - >::put(something); + >::put(CompositeStruct { block_number }); // Emit an event. - Self::deposit_event(Event::SomethingStored { something, who }); + Self::deposit_event(Event::SomethingStored { block_number, who }); - // Return a successful DispatchResultWithPostInfo + // Return a successful [`DispatchResultWithPostInfo`] or [`DispatchResult`]. Ok(().into()) } @@ -102,11 +165,19 @@ pub mod pallet { match >::get() { // Return an error if the value has not been set. None => Err(Error::::NoneValue)?, - Some(old) => { + Some(mut old) => { // Increment the value read from storage; will error in the event of overflow. - let new = old.checked_add(1).ok_or(Error::::StorageOverflow)?; + old.block_number = old + .block_number + .checked_add(&One::one()) + // ^^ equivalent is to: + // .checked_add(&1u32.into()) + // both of which build a `One` instance for the type `BlockNumber`. + .ok_or(Error::::StorageOverflow)?; // Update the value in storage with the incremented result. - >::put(new); + >::put(old); + // Explore how you can rewrite this using + // [`frame_support::storage::StorageValue::mutate`]. Ok(().into()) }, } diff --git a/templates/parachain/pallets/template/src/tests.rs b/templates/parachain/pallets/template/src/tests.rs index 9ad3076be2cc..a4a41af63c2e 100644 --- a/templates/parachain/pallets/template/src/tests.rs +++ b/templates/parachain/pallets/template/src/tests.rs @@ -7,7 +7,7 @@ fn it_works_for_default_value() { // Dispatch a signed extrinsic. assert_ok!(TemplateModule::do_something(RuntimeOrigin::signed(1), 42)); // Read pallet storage and assert an expected result. - assert_eq!(Something::::get(), Some(42)); + assert_eq!(Something::::get().map(|v| v.block_number), Some(42)); }); }