From b6965af493eaa277bf7983201311362cf62ce5a9 Mon Sep 17 00:00:00 2001 From: Kevin Krone Date: Wed, 1 Nov 2023 16:28:02 +0100 Subject: [PATCH] Improve FRAME storage docs (#1714) This is a port (and hopefully a small improvement) of @kianenigma's PR from the old Substrate repo: https://github.com/paritytech/substrate/pull/13987. Following #1689 I moved the documentation of all macros relevant to this PR from `frame_support_procedural` to `pallet_macros` while including a hint for RA users. Question: Again with respect to #1689: Is there a good reason why we should *not* enhance paths with links to our current rustdocs? For example, instead of ```rust /// **Rust-Analyzer users**: See the documentation of the Rust item in /// `frame_support::pallet_macros::storage`. ``` we could write ```rust /// **Rust-Analyzer users**: See the documentation of the Rust item in /// [`frame_support::pallet_macros::storage`](https://paritytech.github.io/polkadot-sdk/master/frame_support/pallet_macros/attr.storage.html). ``` This results in a clickable link like this: image I don't really expect the links to become outdated any time soon, but I think this would be a great UX improvement over just having paths. TODOs: - [ ] Add documentation for `constant_name` macro - [x] Add proper documentation for different `QueryKinds`, i.e. `OptionQuery`, `ValueQuery`, `ResultQuery`. One example for each. Custom `OnEmpty` should be moved to `QueryKinds` trait doc page. - [ ] Rework `type_value` docs --------- Co-authored-by: kianenigma --- substrate/frame/bags-list/Cargo.toml | 30 +- substrate/frame/support/procedural/src/lib.rs | 94 ++--- substrate/frame/support/src/lib.rs | 330 +++++++++++++++++- .../support/src/storage/types/counted_map.rs | 60 +++- .../support/src/storage/types/counted_nmap.rs | 47 ++- .../support/src/storage/types/double_map.rs | 95 ++++- .../frame/support/src/storage/types/map.rs | 46 ++- .../frame/support/src/storage/types/mod.rs | 82 ++++- .../frame/support/src/storage/types/nmap.rs | 60 +++- .../frame/support/src/storage/types/value.rs | 33 +- 10 files changed, 741 insertions(+), 136 deletions(-) diff --git a/substrate/frame/bags-list/Cargo.toml b/substrate/frame/bags-list/Cargo.toml index 05b86f6c7239..b99726ebf2dd 100644 --- a/substrate/frame/bags-list/Cargo.toml +++ b/substrate/frame/bags-list/Cargo.toml @@ -13,17 +13,21 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] # parity -codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } -scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = [ + "derive", +] } +scale-info = { version = "2.10.0", default-features = false, features = [ + "derive", +] } # primitives -sp-runtime = { path = "../../primitives/runtime", default-features = false} -sp-std = { path = "../../primitives/std", default-features = false} +sp-runtime = { path = "../../primitives/runtime", default-features = false } +sp-std = { path = "../../primitives/std", default-features = false } # FRAME -frame-support = { path = "../support", default-features = false} -frame-system = { path = "../system", default-features = false} -frame-election-provider-support = { path = "../election-provider-support", default-features = false} +frame-support = { path = "../support", default-features = false } +frame-system = { path = "../system", default-features = false } +frame-election-provider-support = { path = "../election-provider-support", default-features = false } # third party log = { version = "0.4.17", default-features = false } @@ -31,11 +35,11 @@ docify = "0.2.6" aquamarine = { version = "0.3.2" } # Optional imports for benchmarking -frame-benchmarking = { path = "../benchmarking", default-features = false , optional = true} -pallet-balances = { path = "../balances", default-features = false , optional = true} -sp-core = { path = "../../primitives/core", default-features = false , optional = true} -sp-io = { path = "../../primitives/io", default-features = false , optional = true} -sp-tracing = { path = "../../primitives/tracing", default-features = false , optional = true} +frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } +pallet-balances = { path = "../balances", default-features = false, optional = true } +sp-core = { path = "../../primitives/core", default-features = false, optional = true } +sp-io = { path = "../../primitives/io", default-features = false, optional = true } +sp-tracing = { path = "../../primitives/tracing", default-features = false, optional = true } [dev-dependencies] sp-core = { path = "../../primitives/core" } @@ -46,7 +50,7 @@ frame-election-provider-support = { path = "../election-provider-support" } frame-benchmarking = { path = "../benchmarking" } [features] -default = [ "std" ] +default = ["std"] std = [ "codec/std", "frame-benchmarking?/std", diff --git a/substrate/frame/support/procedural/src/lib.rs b/substrate/frame/support/procedural/src/lib.rs index 9c551b9f2306..68bf3e4874be 100644 --- a/substrate/frame/support/procedural/src/lib.rs +++ b/substrate/frame/support/procedural/src/lib.rs @@ -979,21 +979,26 @@ pub fn config(_: TokenStream, _: TokenStream) -> TokenStream { pallet_macro_stub() } -/// The `#[pallet::constant]` attribute can be used to add an associated type trait bounded by `Get` -/// from [`pallet::config`](`macro@config`) into metadata, e.g.: /// -/// ```ignore -/// #[pallet::config] -/// pub trait Config: frame_system::Config { -/// #[pallet::constant] -/// type Foo: Get; -/// } -/// ``` +/// --- +/// +/// **Rust-Analyzer users**: See the documentation of the Rust item in +/// `frame_support::pallet_macros::constant`. #[proc_macro_attribute] pub fn constant(_: TokenStream, _: TokenStream) -> TokenStream { pallet_macro_stub() } +/// +/// --- +/// +/// **Rust-Analyzer users**: See the documentation of the Rust item in +/// `frame_support::pallet_macros::constant_name`. +#[proc_macro_attribute] +pub fn constant_name(_: TokenStream, _: TokenStream) -> TokenStream { + pallet_macro_stub() +} + /// To bypass the `frame_system::Config` supertrait check, use the attribute /// `pallet::disable_frame_system_supertrait_check`, e.g.: /// @@ -1099,6 +1104,16 @@ pub fn compact(_: TokenStream, _: TokenStream) -> TokenStream { pallet_macro_stub() } +/// +/// --- +/// +/// **Rust-Analyzer users**: See the documentation of the Rust item in +/// `frame_support::pallet_macros::call`. +#[proc_macro_attribute] +pub fn call(_: TokenStream, _: TokenStream) -> TokenStream { + pallet_macro_stub() +} + /// Each dispatchable may also be annotated with the `#[pallet::call_index($idx)]` attribute, /// which explicitly defines the codec index for the dispatchable function in the `Call` enum. /// @@ -1268,60 +1283,11 @@ pub fn generate_deposit(_: TokenStream, _: TokenStream) -> TokenStream { pallet_macro_stub() } -/// The `#[pallet::storage]` attribute lets you define some abstract storage inside of runtime -/// storage and also set its metadata. This attribute can be used multiple times. /// -/// Item should be defined as: +/// --- /// -/// ```ignore -/// #[pallet::storage] -/// #[pallet::getter(fn $getter_name)] // optional -/// $vis type $StorageName<$some_generic> $optional_where_clause -/// = $StorageType<$generic_name = $some_generics, $other_name = $some_other, ...>; -/// ``` -/// -/// or with unnamed generic: -/// -/// ```ignore -/// #[pallet::storage] -/// #[pallet::getter(fn $getter_name)] // optional -/// $vis type $StorageName<$some_generic> $optional_where_clause -/// = $StorageType<_, $some_generics, ...>; -/// ``` -/// -/// I.e. it must be a type alias, with generics: `T` or `T: Config`. The aliased type must be -/// one of `StorageValue`, `StorageMap` or `StorageDoubleMap`. The generic arguments of the -/// storage type can be given in two manners: named and unnamed. For named generic arguments, -/// the name for each argument should match the name defined for it on the storage struct: -/// * `StorageValue` expects `Value` and optionally `QueryKind` and `OnEmpty`, -/// * `StorageMap` expects `Hasher`, `Key`, `Value` and optionally `QueryKind` and `OnEmpty`, -/// * `CountedStorageMap` expects `Hasher`, `Key`, `Value` and optionally `QueryKind` and `OnEmpty`, -/// * `StorageDoubleMap` expects `Hasher1`, `Key1`, `Hasher2`, `Key2`, `Value` and optionally -/// `QueryKind` and `OnEmpty`. -/// -/// For unnamed generic arguments: Their first generic must be `_` as it is replaced by the -/// macro and other generic must declared as a normal generic type declaration. -/// -/// The `Prefix` generic written by the macro is generated using -/// `PalletInfo::name::>()` and the name of the storage type. E.g. if runtime names -/// the pallet "MyExample" then the storage `type Foo = ...` should use the prefix: -/// `Twox128(b"MyExample") ++ Twox128(b"Foo")`. -/// -/// For the `CountedStorageMap` variant, the `Prefix` also implements -/// `CountedStorageMapInstance`. It also associates a `CounterPrefix`, which is implemented the -/// same as above, but the storage prefix is prepend with `"CounterFor"`. E.g. if runtime names -/// the pallet "MyExample" then the storage `type Foo = CountedStorageaMap<...>` will store -/// its counter at the prefix: `Twox128(b"MyExample") ++ Twox128(b"CounterForFoo")`. -/// -/// E.g: -/// -/// ```ignore -/// #[pallet::storage] -/// pub(super) type MyStorage = StorageMap; -/// ``` -/// -/// In this case the final prefix used by the map is `Twox128(b"MyExample") ++ -/// Twox128(b"OtherName")`. +/// **Rust-Analyzer users**: See the documentation of the Rust item in +/// `frame_support::pallet_macros::storage`. #[proc_macro_attribute] pub fn storage(_: TokenStream, _: TokenStream) -> TokenStream { pallet_macro_stub() @@ -1424,6 +1390,9 @@ pub fn type_value(_: TokenStream, _: TokenStream) -> TokenStream { pallet_macro_stub() } +/// +/// --- +/// /// **Rust-Analyzer users**: See the documentation of the Rust item in /// `frame_support::pallet_macros::genesis_config`. #[proc_macro_attribute] @@ -1431,6 +1400,9 @@ pub fn genesis_config(_: TokenStream, _: TokenStream) -> TokenStream { pallet_macro_stub() } +/// +/// --- +/// /// **Rust-Analyzer users**: See the documentation of the Rust item in /// `frame_support::pallet_macros::genesis_build`. #[proc_macro_attribute] diff --git a/substrate/frame/support/src/lib.rs b/substrate/frame/support/src/lib.rs index 1c696bbb84ac..4888b8996d1a 100644 --- a/substrate/frame/support/src/lib.rs +++ b/substrate/frame/support/src/lib.rs @@ -2199,11 +2199,10 @@ pub use frame_support_procedural::pallet; /// Contains macro stubs for all of the pallet:: macros pub mod pallet_macros { pub use frame_support_procedural::{ - call_index, compact, composite_enum, config, constant, - disable_frame_system_supertrait_check, error, event, extra_constants, generate_deposit, - generate_store, getter, hooks, import_section, inherent, no_default, no_default_bounds, - origin, pallet_section, storage, storage_prefix, storage_version, type_value, unbounded, - validate_unsigned, weight, whitelist_storage, + call_index, compact, composite_enum, config, disable_frame_system_supertrait_check, error, + event, extra_constants, generate_deposit, generate_store, getter, hooks, import_section, + inherent, no_default, no_default_bounds, origin, pallet_section, storage_prefix, + storage_version, type_value, unbounded, validate_unsigned, weight, whitelist_storage, }; /// Allows you to define the genesis configuration for the pallet. @@ -2220,7 +2219,7 @@ pub mod pallet_macros { /// /// The fields of the `GenesisConfig` can in turn be populated by the chain-spec. /// - /// ## Example: + /// ## Example /// /// ``` /// #[frame_support::pallet] @@ -2275,6 +2274,230 @@ pub mod pallet_macros { /// } /// ``` pub use frame_support_procedural::genesis_build; + + /// The `#[pallet::constant]` attribute can be used to add an associated type trait bounded + /// by [`Get`](frame_support::pallet_prelude::Get) from [`pallet::config`](`macro@config`) + /// into metadata. + /// + /// ## Example + /// + /// ``` + /// #[frame_support::pallet] + /// mod pallet { + /// use frame_support::pallet_prelude::*; + /// # #[pallet::pallet] + /// # pub struct Pallet(_); + /// #[pallet::config] + /// pub trait Config: frame_system::Config { + /// /// This is like a normal `Get` trait, but it will be added into metadata. + /// #[pallet::constant] + /// type Foo: Get; + /// } + /// } + /// ``` + pub use frame_support_procedural::constant; + + /// Declares a type alias as a storage item. Storage items are pointers to data stored + /// on-chain (the *blockchain state*), under a specific key. The exact key is dependent on + /// the type of the storage. + /// + /// > From the perspective of this pallet, the entire blockchain state is abstracted behind + /// > a key-value api, namely [`sp_io::storage`]. + /// + /// ## Storage Types + /// + /// The following storage types are supported by the `#[storage]` macro. For specific + /// information about each storage type, refer to the documentation of the respective type. + /// + /// * [`StorageValue`](crate::storage::types::StorageValue) + /// * [`StorageMap`](crate::storage::types::StorageMap) + /// * [`CountedStorageMap`](crate::storage::types::CountedStorageMap) + /// * [`StorageDoubleMap`](crate::storage::types::StorageDoubleMap) + /// * [`StorageNMap`](crate::storage::types::StorageNMap) + /// * [`CountedStorageNMap`](crate::storage::types::CountedStorageNMap) + /// + /// ## Storage Type Usage + /// + /// The following details are relevant to all of the aforementioned storage types. + /// Depending on the exact storage type, it may require the following generic parameters: + /// + /// * [`Prefix`](#prefixes) - Used to give the storage item a unique key in the underlying + /// storage. + /// * `Key` - Type of the keys used to store the values, + /// * `Value` - Type of the value being stored, + /// * [`Hasher`](#hashers) - Used to ensure the keys of a map are uniformly distributed, + /// * [`QueryKind`](#querykind) - Used to configure how to handle queries to the underlying + /// storage, + /// * `OnEmpty` - Used to handle missing values when querying the underlying storage, + /// * `MaxValues` - _not currently used_. + /// + /// Each `Key` type requires its own designated `Hasher` declaration, so that + /// [`StorageDoubleMap`](frame_support::storage::types::StorageDoubleMap) needs two of + /// each, and [`StorageNMap`](frame_support::storage::types::StorageNMap) needs `N` such + /// pairs. Since [`StorageValue`](frame_support::storage::types::StorageValue) only stores + /// a single element, no configuration of hashers is needed. + /// + /// ### Syntax + /// + /// Two general syntaxes are supported, as demonstrated below: + /// + /// 1. Named type parameters, e.g., `type Foo = StorageValue`. + /// 2. Positional type parameters, e.g., `type Foo = StorageValue<_, u32>`. + /// + /// In both instances, declaring the generic parameter `` is mandatory. Optionally, it + /// can also be explicitly declared as ``. In the compiled code, `T` will + /// automatically include the trait bound `Config`. + /// + /// Note that in positional syntax, the first generic type parameter must be `_`. + /// + /// #### Example + /// + /// ``` + /// #[frame_support::pallet] + /// mod pallet { + /// # use frame_support::pallet_prelude::*; + /// # #[pallet::config] + /// # pub trait Config: frame_system::Config {} + /// # #[pallet::pallet] + /// # pub struct Pallet(_); + /// /// Positional syntax, without bounding `T`. + /// #[pallet::storage] + /// pub type Foo = StorageValue<_, u32>; + /// + /// /// Positional syntax, with bounding `T`. + /// #[pallet::storage] + /// pub type Bar = StorageValue<_, u32>; + /// + /// /// Named syntax. + /// #[pallet::storage] + /// pub type Baz = StorageMap; + /// } + /// ``` + /// + /// ### QueryKind + /// + /// Every storage type mentioned above has a generic type called + /// [`QueryKind`](frame_support::storage::types::QueryKindTrait) that determines its + /// "query" type. This refers to the kind of value returned when querying the storage, for + /// instance, through a `::get()` method. + /// + /// There are three types of queries: + /// + /// 1. [`OptionQuery`](frame_support::storage::types::OptionQuery): The default query type. + /// It returns `Some(V)` if the value is present, or `None` if it isn't, where `V` is + /// the value type. + /// 2. [`ValueQuery`](frame_support::storage::types::ValueQuery): Returns the value itself + /// if present; otherwise, it returns `Default::default()`. This behavior can be + /// adjusted with the `OnEmpty` generic parameter, which defaults to `OnEmpty = + /// GetDefault`. + /// 3. [`ResultQuery`](frame_support::storage::types::ResultQuery): Returns `Result`, + /// where `V` is the value type. + /// + /// See [`QueryKind`](frame_support::storage::types::QueryKindTrait) for further examples. + /// + /// ### Optimized Appending + /// + /// All storage items — such as + /// [`StorageValue`](frame_support::storage::types::StorageValue), + /// [`StorageMap`](frame_support::storage::types::StorageMap), and their variants—offer an + /// `::append()` method optimized for collections. Using this method avoids the + /// inefficiency of decoding and re-encoding entire collections when adding items. For + /// instance, consider the storage declaration `type MyVal = StorageValue<_, Vec, + /// ValueQuery>`. With `MyVal` storing a large list of bytes, `::append()` lets you + /// directly add bytes to the end in storage without processing the full list. Depending on + /// the storage type, additional key specifications may be needed. + /// + /// #### Example + #[doc = docify::embed!("src/lib.rs", example_storage_value_append)] + /// Similarly, there also exists a `::try_append()` method, which can be used when handling + /// types where an append operation might fail, such as a + /// [`BoundedVec`](frame_support::BoundedVec). + /// + /// #### Example + #[doc = docify::embed!("src/lib.rs", example_storage_value_try_append)] + /// ### Optimized Length Decoding + /// + /// All storage items — such as + /// [`StorageValue`](frame_support::storage::types::StorageValue), + /// [`StorageMap`](frame_support::storage::types::StorageMap), and their counterparts — + /// incorporate the `::decode_len()` method. This method allows for efficient retrieval of + /// a collection's length without the necessity of decoding the entire dataset. + /// #### Example + #[doc = docify::embed!("src/lib.rs", example_storage_value_decode_len)] + /// ### Hashers + /// + /// For all storage types, except + /// [`StorageValue`](frame_support::storage::types::StorageValue), a set of hashers needs + /// to be specified. The choice of hashers is crucial, especially in production chains. The + /// purpose of storage hashers in maps is to ensure the keys of a map are + /// uniformly distributed. An unbalanced map/trie can lead to inefficient performance. + /// + /// In general, hashers are categorized as either cryptographically secure or not. The + /// former is slower than the latter. `Blake2` and `Twox` serve as examples of each, + /// respectively. + /// + /// As a rule of thumb: + /// + /// 1. If the map keys are not controlled by end users, or are cryptographically secure by + /// definition (e.g., `AccountId`), then the use of cryptographically secure hashers is NOT + /// required. + /// 2. If the map keys are controllable by the end users, cryptographically secure hashers + /// should be used. + /// + /// For more information, look at the types that implement + /// [`frame_support::StorageHasher`](frame_support::StorageHasher). + /// + /// Lastly, it's recommended for hashers with "concat" to have reversible hashes. Refer to + /// the implementors section of + /// [`hash::ReversibleStorageHasher`](frame_support::hash::ReversibleStorageHasher). + /// + /// ### Prefixes + /// + /// Internally, every storage type generates a "prefix". This prefix serves as the initial + /// segment of the key utilized to store values in the on-chain state (i.e., the final key + /// used in [`sp_io::storage`](sp_io::storage)). For all storage types, the following rule + /// applies: + /// + /// > The storage prefix begins with `twox128(pallet_prefix) ++ twox128(STORAGE_PREFIX)`, + /// > where + /// > `pallet_prefix` is the name assigned to the pallet instance in + /// > [`frame_support::construct_runtime`](frame_support::construct_runtime), and + /// > `STORAGE_PREFIX` is the name of the `type` aliased to a particular storage type, such + /// > as + /// > `Foo` in `type Foo = StorageValue<..>`. + /// + /// For [`StorageValue`](frame_support::storage::types::StorageValue), no additional key is + /// required. For map types, the prefix is extended with one or more keys defined by the + /// map. + /// + /// #### Example + #[doc = docify::embed!("src/lib.rs", example_storage_value_map_prefixes)] + /// ## Related Macros + /// + /// The following attribute macros can be used in conjunction with the `#[storage]` macro: + /// + /// * [`macro@getter`]: Creates a custom getter function. + /// * [`macro@storage_prefix`]: Overrides the default prefix of the storage item. + /// * [`macro@unbounded`]: Declares the storage item as unbounded. + /// + /// #### Example + /// ``` + /// #[frame_support::pallet] + /// mod pallet { + /// # use frame_support::pallet_prelude::*; + /// # #[pallet::config] + /// # pub trait Config: frame_system::Config {} + /// # #[pallet::pallet] + /// # pub struct Pallet(_); + /// /// A kitchen-sink StorageValue, with all possible additional attributes. + /// #[pallet::storage] + /// #[pallet::getter(fn foo)] + /// #[pallet::storage_prefix = "OtherFoo"] + /// #[pallet::unbounded] + /// pub type Foo = StorageValue<_, u32, ValueQuery>; + /// } + /// ``` + pub use frame_support_procedural::storage; } #[deprecated(note = "Will be removed after July 2023; Use `sp_runtime::traits` directly instead.")] @@ -2291,3 +2514,98 @@ sp_core::generate_feature_enabled_macro!(std_enabled, feature = "std", $); // Helper for implementing GenesisBuilder runtime API pub mod genesis_builder_helper; + +#[cfg(test)] +mod test { + // use super::*; + use crate::{ + hash::*, + storage::types::{StorageMap, StorageValue, ValueQuery}, + traits::{ConstU32, StorageInstance}, + BoundedVec, + }; + use sp_io::{hashing::twox_128, TestExternalities}; + + struct Prefix; + impl StorageInstance for Prefix { + fn pallet_prefix() -> &'static str { + "test" + } + const STORAGE_PREFIX: &'static str = "foo"; + } + + struct Prefix1; + impl StorageInstance for Prefix1 { + fn pallet_prefix() -> &'static str { + "test" + } + const STORAGE_PREFIX: &'static str = "MyVal"; + } + struct Prefix2; + impl StorageInstance for Prefix2 { + fn pallet_prefix() -> &'static str { + "test" + } + const STORAGE_PREFIX: &'static str = "MyMap"; + } + + #[docify::export] + #[test] + pub fn example_storage_value_try_append() { + type MyVal = StorageValue>, ValueQuery>; + + TestExternalities::default().execute_with(|| { + MyVal::set(BoundedVec::try_from(vec![42, 43]).unwrap()); + assert_eq!(MyVal::get(), vec![42, 43]); + // Try to append a single u32 to BoundedVec stored in `MyVal` + assert_ok!(MyVal::try_append(40)); + assert_eq!(MyVal::get(), vec![42, 43, 40]); + }); + } + + #[docify::export] + #[test] + pub fn example_storage_value_append() { + type MyVal = StorageValue, ValueQuery>; + + TestExternalities::default().execute_with(|| { + MyVal::set(vec![42, 43]); + assert_eq!(MyVal::get(), vec![42, 43]); + // Append a single u32 to Vec stored in `MyVal` + MyVal::append(40); + assert_eq!(MyVal::get(), vec![42, 43, 40]); + }); + } + + #[docify::export] + #[test] + pub fn example_storage_value_decode_len() { + type MyVal = StorageValue>, ValueQuery>; + + TestExternalities::default().execute_with(|| { + MyVal::set(BoundedVec::try_from(vec![42, 43]).unwrap()); + assert_eq!(MyVal::decode_len().unwrap(), 2); + }); + } + + #[docify::export] + #[test] + pub fn example_storage_value_map_prefixes() { + type MyVal = StorageValue; + type MyMap = StorageMap; + TestExternalities::default().execute_with(|| { + // This example assumes `pallet_prefix` to be "test" + // Get storage key for `MyVal` StorageValue + assert_eq!( + MyVal::hashed_key().to_vec(), + [twox_128(b"test"), twox_128(b"MyVal")].concat() + ); + // Get storage key for `MyMap` StorageMap and `key` = 1 + let mut k: Vec = vec![]; + k.extend(&twox_128(b"test")); + k.extend(&twox_128(b"MyMap")); + k.extend(&1u16.blake2_128_concat()); + assert_eq!(MyMap::hashed_key_for(1).to_vec(), k); + }); + } +} diff --git a/substrate/frame/support/src/storage/types/counted_map.rs b/substrate/frame/support/src/storage/types/counted_map.rs index 50e2c678248c..75fbdf2617d1 100644 --- a/substrate/frame/support/src/storage/types/counted_map.rs +++ b/substrate/frame/support/src/storage/types/counted_map.rs @@ -35,8 +35,8 @@ use sp_metadata_ir::StorageEntryMetadataIR; use sp_runtime::traits::Saturating; use sp_std::prelude::*; -/// A wrapper around a `StorageMap` and a `StorageValue` to keep track of how many items -/// are in a map, without needing to iterate all the values. +/// A wrapper around a [`StorageMap`] and a [`StorageValue`] (with the value being `u32`) to keep +/// track of how many items are in a map, without needing to iterate all the values. /// /// This storage item has additional storage read and write overhead when manipulating values /// compared to a regular storage map. @@ -47,6 +47,51 @@ use sp_std::prelude::*; /// /// Whenever the counter needs to be updated, an additional read and write occurs to update that /// counter. +/// +/// The total number of items currently stored in the map can be retrieved with the +/// [`CountedStorageMap::count`] method. +/// +/// For general information regarding the `#[pallet::storage]` attribute, refer to +/// [`crate::pallet_macros::storage`]. +/// +/// # Examples +/// +/// Declaring a counted map: +/// +/// ``` +/// #[frame_support::pallet] +/// mod pallet { +/// # use frame_support::pallet_prelude::*; +/// # #[pallet::config] +/// # pub trait Config: frame_system::Config {} +/// # #[pallet::pallet] +/// # pub struct Pallet(_); +/// /// A kitchen-sink CountedStorageMap, with all possible additional attributes. +/// #[pallet::storage] +/// #[pallet::getter(fn foo)] +/// #[pallet::storage_prefix = "OtherFoo"] +/// #[pallet::unbounded] +/// pub type Foo = CountedStorageMap< +/// _, +/// Blake2_128Concat, +/// u32, +/// u32, +/// ValueQuery, +/// >; +/// +/// /// Alternative named syntax. +/// #[pallet::storage] +/// pub type Bar = CountedStorageMap< +/// Hasher = Blake2_128Concat, +/// Key = u32, +/// Value = u32, +/// QueryKind = ValueQuery +/// >; +/// } +/// ``` +/// +/// Using a counted map in action: +#[doc = docify::embed!("src/storage/types/counted_map.rs", test_simple_count_works)] pub struct CountedStorageMap< Prefix, Hasher, @@ -1173,4 +1218,15 @@ mod test { ] ); } + + #[docify::export] + #[test] + fn test_simple_count_works() { + type FooCountedMap = CountedStorageMap; + TestExternalities::default().execute_with(|| { + FooCountedMap::insert(1, 1); + FooCountedMap::insert(2, 2); + assert_eq!(FooCountedMap::count(), 2); + }); + } } diff --git a/substrate/frame/support/src/storage/types/counted_nmap.rs b/substrate/frame/support/src/storage/types/counted_nmap.rs index 5da31c059225..c2c2197aceee 100644 --- a/substrate/frame/support/src/storage/types/counted_nmap.rs +++ b/substrate/frame/support/src/storage/types/counted_nmap.rs @@ -33,8 +33,8 @@ use sp_metadata_ir::StorageEntryMetadataIR; use sp_runtime::traits::Saturating; use sp_std::prelude::*; -/// A wrapper around a `StorageNMap` and a `StorageValue` to keep track of how many items -/// are in a map, without needing to iterate over all of the values. +/// A wrapper around a [`StorageNMap`] and a [`StorageValue`] (with the value being `u32`) to keep +/// track of how many items are in a map, without needing to iterate all the values. /// /// This storage item has some additional storage read and write overhead when manipulating values /// compared to a regular storage map. @@ -45,6 +45,49 @@ use sp_std::prelude::*; /// /// Whenever the counter needs to be updated, an additional read and write occurs to update that /// counter. +/// +/// For general information regarding the `#[pallet::storage]` attribute, refer to +/// [`crate::pallet_macros::storage`]. +/// +/// # Example +/// +/// ``` +/// #[frame_support::pallet] +/// mod pallet { +/// # use frame_support::pallet_prelude::*; +/// # #[pallet::config] +/// # pub trait Config: frame_system::Config {} +/// # #[pallet::pallet] +/// # pub struct Pallet(_); +/// /// A kitchen-sink CountedStorageNMap, with all possible additional attributes. +/// #[pallet::storage] +/// #[pallet::getter(fn foo)] +/// #[pallet::storage_prefix = "OtherFoo"] +/// #[pallet::unbounded] +/// pub type Foo = CountedStorageNMap< +/// _, +/// ( +/// NMapKey, +/// NMapKey, +/// NMapKey +/// ), +/// u64, +/// ValueQuery, +/// >; +/// +/// /// Alternative named syntax. +/// #[pallet::storage] +/// pub type Bar = CountedStorageNMap< +/// Key = ( +/// NMapKey, +/// NMapKey, +/// NMapKey +/// ), +/// Value = u64, +/// QueryKind = ValueQuery, +/// >; +/// } +/// ``` pub struct CountedStorageNMap< Prefix, Key, diff --git a/substrate/frame/support/src/storage/types/double_map.rs b/substrate/frame/support/src/storage/types/double_map.rs index 519ffcbafade..1002222a895c 100644 --- a/substrate/frame/support/src/storage/types/double_map.rs +++ b/substrate/frame/support/src/storage/types/double_map.rs @@ -31,22 +31,66 @@ use sp_arithmetic::traits::SaturatedConversion; use sp_metadata_ir::{StorageEntryMetadataIR, StorageEntryTypeIR}; use sp_std::prelude::*; -/// A type that allow to store values for `(key1, key2)` couple. Similar to `StorageMap` but allow -/// to iterate and remove value associated to first key. +/// A type representing a *double map* in storage. This structure associates a pair of keys with a +/// value of a specified type stored on-chain. /// -/// Each value is stored at: -/// ```nocompile -/// Twox128(Prefix::pallet_prefix()) -/// ++ Twox128(Prefix::STORAGE_PREFIX) -/// ++ Hasher1(encode(key1)) -/// ++ Hasher2(encode(key2)) +/// A double map with keys `k1` and `k2` can be likened to a +/// [`StorageMap`](frame_support::storage::types::StorageMap) with a key of type `(k1, k2)`. +/// However, a double map offers functions specific to each key, enabling partial iteration and +/// deletion based on one key alone. +/// +/// Also, conceptually, a double map is a special case of a +/// [`StorageNMap`](frame_support::storage::types::StorageNMap) using two keys. +/// +/// For general information regarding the `#[pallet::storage]` attribute, refer to +/// [`crate::pallet_macros::storage`]. +/// +/// # Examples +/// +/// ### Kitchen-sink +/// +/// ``` +/// #[frame_support::pallet] +/// mod pallet { +/// # use frame_support::pallet_prelude::*; +/// # #[pallet::config] +/// # pub trait Config: frame_system::Config {} +/// # #[pallet::pallet] +/// # pub struct Pallet(_); +/// /// A kitchen-sink StorageDoubleMap, with all possible additional attributes. +/// #[pallet::storage] +/// #[pallet::getter(fn foo)] +/// #[pallet::storage_prefix = "OtherFoo"] +/// #[pallet::unbounded] +/// pub type Foo = StorageDoubleMap< +/// _, +/// Blake2_128Concat, +/// u8, +/// Twox64Concat, +/// u16, +/// u32, +/// ValueQuery +/// >; +/// +/// /// Alternative named syntax. +/// #[pallet::storage] +/// pub type Bar = StorageDoubleMap< +/// Hasher1 = Blake2_128Concat, +/// Key1 = u8, +/// Hasher2 = Twox64Concat, +/// Key2 = u16, +/// Value = u32, +/// QueryKind = ValueQuery +/// >; +/// } /// ``` /// -/// # Warning +/// ### Partial Iteration & Removal /// -/// If the key1s (or key2s) are not trusted (e.g. can be set by a user), a cryptographic `hasher` -/// such as `blake2_128_concat` must be used for Hasher1 (resp. Hasher2). Otherwise, other values -/// in storage can be compromised. +/// When `Hasher1` and `Hasher2` implement the +/// [`ReversibleStorageHasher`](frame_support::ReversibleStorageHasher) trait, the first key `k1` +/// can be used to partially iterate over keys and values of the double map, and to delete items. +#[doc = docify::embed!("src/storage/types/double_map.rs", example_double_map_partial_operations)] pub struct StorageDoubleMap< Prefix, Hasher1, @@ -742,6 +786,7 @@ mod test { use crate::{hash::*, storage::types::ValueQuery}; use sp_io::{hashing::twox_128, TestExternalities}; use sp_metadata_ir::{StorageEntryModifierIR, StorageEntryTypeIR, StorageHasherIR}; + use std::collections::BTreeSet; struct Prefix; impl StorageInstance for Prefix { @@ -972,4 +1017,30 @@ mod test { assert_eq!(A::drain_prefix(4).collect::>(), vec![]); }) } + + #[docify::export] + #[test] + fn example_double_map_partial_operations() { + type FooDoubleMap = + StorageDoubleMap; + + TestExternalities::default().execute_with(|| { + FooDoubleMap::insert(0, 0, 42); + FooDoubleMap::insert(0, 1, 43); + FooDoubleMap::insert(1, 0, 314); + + // should be equal to {0,1} (ordering is random) + let collected_k2_keys: BTreeSet<_> = FooDoubleMap::iter_key_prefix(0).collect(); + assert_eq!(collected_k2_keys, [0, 1].iter().copied().collect::>()); + + // should be equal to {42,43} (ordering is random) + let collected_k2_values: BTreeSet<_> = FooDoubleMap::iter_prefix_values(0).collect(); + assert_eq!(collected_k2_values, [42, 43].iter().copied().collect::>()); + + // Remove items from the map using k1 = 0 + let _ = FooDoubleMap::clear_prefix(0, u32::max_value(), None); + // Values associated with (0, _) should have been removed + assert_eq!(FooDoubleMap::iter_prefix(0).collect::>(), vec![]); + }); + } } diff --git a/substrate/frame/support/src/storage/types/map.rs b/substrate/frame/support/src/storage/types/map.rs index 7f936a8a35a6..81a3dd270d81 100644 --- a/substrate/frame/support/src/storage/types/map.rs +++ b/substrate/frame/support/src/storage/types/map.rs @@ -31,19 +31,45 @@ use sp_arithmetic::traits::SaturatedConversion; use sp_metadata_ir::{StorageEntryMetadataIR, StorageEntryTypeIR}; use sp_std::prelude::*; -/// A type that allow to store value for given key. Allowing to insert/remove/iterate on values. +/// A type representing a *map* in storage. A *storage map* is a mapping of keys to values of a +/// given type stored on-chain. /// -/// Each value is stored at: -/// ```nocompile -/// Twox128(Prefix::pallet_prefix()) -/// ++ Twox128(Prefix::STORAGE_PREFIX) -/// ++ Hasher1(encode(key)) -/// ``` +/// For general information regarding the `#[pallet::storage]` attribute, refer to +/// [`crate::pallet_macros::storage`]. +/// +/// # Example /// -/// # Warning +/// ``` +/// #[frame_support::pallet] +/// mod pallet { +/// # use frame_support::pallet_prelude::*; +/// # #[pallet::config] +/// # pub trait Config: frame_system::Config {} +/// # #[pallet::pallet] +/// # pub struct Pallet(_); +/// /// A kitchen-sink StorageMap, with all possible additional attributes. +/// #[pallet::storage] +/// #[pallet::getter(fn foo)] +/// #[pallet::storage_prefix = "OtherFoo"] +/// #[pallet::unbounded] +/// pub type Foo = StorageMap< +/// _ +/// Blake2_128Concat, +/// u32, +/// u32, +/// ValueQuery +/// >; /// -/// If the keys are not trusted (e.g. can be set by a user), a cryptographic `hasher` such as -/// `blake2_128_concat` must be used. Otherwise, other values in storage can be compromised. +/// /// Alternative named syntax. +/// #[pallet::storage] +/// pub type Bar = StorageMap< +/// Hasher = Blake2_128Concat, +/// Key = u32, +/// Value = u32, +/// QueryKind = ValueQuery +/// >; +/// } +/// ``` pub struct StorageMap< Prefix, Hasher, diff --git a/substrate/frame/support/src/storage/types/mod.rs b/substrate/frame/support/src/storage/types/mod.rs index c7f2557099b3..1d995d93e882 100644 --- a/substrate/frame/support/src/storage/types/mod.rs +++ b/substrate/frame/support/src/storage/types/mod.rs @@ -43,13 +43,17 @@ pub use value::StorageValue; /// Trait implementing how the storage optional value is converted into the queried type. /// -/// It is implemented by: -/// * `OptionQuery` which converts an optional value to an optional value, used when querying +/// It is implemented most notable by: +/// +/// * [`OptionQuery`] which converts an optional value to an optional value, used when querying /// storage returns an optional value. -/// * `ResultQuery` which converts an optional value to a result value, used when querying storage +/// * [`ResultQuery`] which converts an optional value to a result value, used when querying storage /// returns a result value. -/// * `ValueQuery` which converts an optional value to a value, used when querying storage returns a -/// value. +/// * [`ValueQuery`] which converts an optional value to a value, used when querying storage returns +/// a value. +/// +/// ## Example +#[doc = docify::embed!("src/storage/types/mod.rs", value_query_examples)] pub trait QueryKindTrait { /// Metadata for the storage kind. const METADATA: StorageEntryModifierIR; @@ -65,11 +69,10 @@ pub trait QueryKindTrait { fn from_query_to_optional_value(v: Self::Query) -> Option; } -/// Implement QueryKindTrait with query being `Option` +/// Implements [`QueryKindTrait`] with `Query` type being `Option<_>`. /// -/// NOTE: it doesn't support a generic `OnEmpty`. This means only `None` can be -/// returned when no value is found. To use another `OnEmpty` implementation, `ValueQuery` can be -/// used instead. +/// NOTE: it doesn't support a generic `OnEmpty`. This means only `None` can be returned when no +/// value is found. To use another `OnEmpty` implementation, `ValueQuery` can be used instead. pub struct OptionQuery; impl QueryKindTrait for OptionQuery where @@ -89,7 +92,7 @@ where } } -/// Implement QueryKindTrait with query being `Result` +/// Implements [`QueryKindTrait`] with `Query` type being `Result`. pub struct ResultQuery(sp_std::marker::PhantomData); impl QueryKindTrait for ResultQuery where @@ -113,7 +116,7 @@ where } } -/// Implement QueryKindTrait with query being `Value` +/// Implements [`QueryKindTrait`] with `Query` type being `Value`. pub struct ValueQuery; impl QueryKindTrait for ValueQuery where @@ -140,3 +143,60 @@ pub trait StorageEntryMetadataBuilder { /// Build into `entries` the storage metadata entries of a storage given some `docs`. fn build_metadata(doc: Vec<&'static str>, entries: &mut Vec); } + +#[cfg(test)] +mod test { + use super::*; + use crate::{ + storage::types::ValueQuery, + traits::{Get, StorageInstance}, + }; + use sp_io::TestExternalities; + + struct Prefix; + impl StorageInstance for Prefix { + fn pallet_prefix() -> &'static str { + "test" + } + const STORAGE_PREFIX: &'static str = "foo"; + } + + #[docify::export] + #[test] + pub fn value_query_examples() { + /// Custom default impl to be used with `ValueQuery`. + struct UniverseSecret; + impl Get for UniverseSecret { + fn get() -> u32 { + 42 + } + } + + /// Custom default impl to be used with `ResultQuery`. + struct GetDefaultForResult; + impl Get> for GetDefaultForResult { + fn get() -> Result { + Err(()) + } + } + + type A = StorageValue; + type B = StorageValue; + type C = StorageValue, GetDefaultForResult>; + type D = StorageValue; + + TestExternalities::default().execute_with(|| { + // normal value query returns default + assert_eq!(A::get(), 0); + + // option query returns none + assert_eq!(B::get(), None); + + // result query returns error + assert_eq!(C::get(), Err(())); + + // value query with custom onempty returns 42 + assert_eq!(D::get(), 42); + }); + } +} diff --git a/substrate/frame/support/src/storage/types/nmap.rs b/substrate/frame/support/src/storage/types/nmap.rs index 406fd42eaf7b..0723db689002 100755 --- a/substrate/frame/support/src/storage/types/nmap.rs +++ b/substrate/frame/support/src/storage/types/nmap.rs @@ -33,24 +33,54 @@ use sp_metadata_ir::{StorageEntryMetadataIR, StorageEntryTypeIR}; use sp_runtime::SaturatedConversion; use sp_std::prelude::*; -/// A type that allow to store values for an arbitrary number of keys in the form of -/// `(Key, Key, ..., Key)`. +/// A type representing an *NMap* in storage. This structure associates an arbitrary number of keys +/// with a value of a specified type stored on-chain. /// -/// Each value is stored at: -/// ```nocompile -/// Twox128(Prefix::pallet_prefix()) -/// ++ Twox128(Prefix::STORAGE_PREFIX) -/// ++ Hasher1(encode(key1)) -/// ++ Hasher2(encode(key2)) -/// ++ ... -/// ++ HasherN(encode(keyN)) -/// ``` +/// For example, [`StorageDoubleMap`](frame_support::storage::types::StorageDoubleMap) is a special +/// case of an *NMap* with N = 2. +/// +/// For general information regarding the `#[pallet::storage]` attribute, refer to +/// [`crate::pallet_macros::storage`]. /// -/// # Warning +/// # Example /// -/// If the keys are not trusted (e.g. can be set by a user), a cryptographic `hasher` -/// such as `blake2_128_concat` must be used for the key hashers. Otherwise, other values -/// in storage can be compromised. +/// ``` +/// #[frame_support::pallet] +/// mod pallet { +/// # use frame_support::pallet_prelude::*; +/// # #[pallet::config] +/// # pub trait Config: frame_system::Config {} +/// # #[pallet::pallet] +/// # pub struct Pallet(_); +/// /// A kitchen-sink StorageNMap, with all possible additional attributes. +/// #[pallet::storage] +/// #[pallet::getter(fn foo)] +/// #[pallet::storage_prefix = "OtherFoo"] +/// #[pallet::unbounded] +/// pub type Foo = StorageNMap< +/// _, +/// ( +/// NMapKey, +/// NMapKey, +/// NMapKey +/// ), +/// u64, +/// ValueQuery, +/// >; +/// +/// /// Named alternative syntax. +/// #[pallet::storage] +/// pub type Bar = StorageNMap< +/// Key = ( +/// NMapKey, +/// NMapKey, +/// NMapKey +/// ), +/// Value = u64, +/// QueryKind = ValueQuery, +/// >; +/// } +/// ``` pub struct StorageNMap< Prefix, Key, diff --git a/substrate/frame/support/src/storage/types/value.rs b/substrate/frame/support/src/storage/types/value.rs index 3e1f2fe9551d..9fff1774d7b4 100644 --- a/substrate/frame/support/src/storage/types/value.rs +++ b/substrate/frame/support/src/storage/types/value.rs @@ -30,11 +30,36 @@ use sp_arithmetic::traits::SaturatedConversion; use sp_metadata_ir::{StorageEntryMetadataIR, StorageEntryTypeIR}; use sp_std::prelude::*; -/// A type that allow to store a value. +/// A type representing a *value* in storage. A *storage value* is a single value of a given type +/// stored on-chain. /// -/// Each value is stored at: -/// ```nocompile -/// Twox128(Prefix::pallet_prefix()) ++ Twox128(Prefix::STORAGE_PREFIX) +/// For general information regarding the `#[pallet::storage]` attribute, refer to +/// [`crate::pallet_macros::storage`]. +/// +/// # Example +/// +/// ``` +/// #[frame_support::pallet] +/// mod pallet { +/// # use frame_support::pallet_prelude::*; +/// # #[pallet::config] +/// # pub trait Config: frame_system::Config {} +/// # #[pallet::pallet] +/// # pub struct Pallet(_); +/// /// A kitchen-sink StorageValue, with all possible additional attributes. +/// #[pallet::storage] +/// #[pallet::getter(fn foo)] +/// #[pallet::storage_prefix = "OtherFoo"] +/// #[pallet::unbounded] +/// pub type Foo = StorageValue<_, u32,ValueQuery>; +/// +/// /// Named alternative syntax. +/// #[pallet::storage] +/// pub type Bar = StorageValue< +/// Value = u32, +/// QueryKind = ValueQuery +/// >; +/// } /// ``` pub struct StorageValue( core::marker::PhantomData<(Prefix, Value, QueryKind, OnEmpty)>,