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:
<img width="674" alt="image"
src="https://github.com/paritytech/polkadot-sdk/assets/10713977/c129e622-3942-4eeb-8acf-93ee4efdc99d">
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 <kian@parity.io>
This commit is contained in:
Kevin Krone
2023-11-01 16:28:02 +01:00
committed by GitHub
parent b2bb8cbcf3
commit b6965af493
10 changed files with 741 additions and 136 deletions
+17 -13
View File
@@ -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",
+33 -61
View File
@@ -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<u32>;
/// }
/// ```
/// ---
///
/// **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::<Pallet<..>>()` and the name of the storage type. E.g. if runtime names
/// the pallet "MyExample" then the storage `type Foo<T> = ...` 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<T> = CountedStorageaMap<...>` will store
/// its counter at the prefix: `Twox128(b"MyExample") ++ Twox128(b"CounterForFoo")`.
///
/// E.g:
///
/// ```ignore
/// #[pallet::storage]
/// pub(super) type MyStorage<T> = StorageMap<Hasher = Blake2_128Concat, Key = u32, Value = u32>;
/// ```
///
/// 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]
+324 -6
View File
@@ -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<T>(_);
/// #[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<u32>;
/// }
/// }
/// ```
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<T> = StorageValue<Value = u32>`.
/// 2. Positional type parameters, e.g., `type Foo<T> = StorageValue<_, u32>`.
///
/// In both instances, declaring the generic parameter `<T>` is mandatory. Optionally, it
/// can also be explicitly declared as `<T: Config>`. 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<T>(_);
/// /// Positional syntax, without bounding `T`.
/// #[pallet::storage]
/// pub type Foo<T> = StorageValue<_, u32>;
///
/// /// Positional syntax, with bounding `T`.
/// #[pallet::storage]
/// pub type Bar<T: Config> = StorageValue<_, u32>;
///
/// /// Named syntax.
/// #[pallet::storage]
/// pub type Baz<T> = StorageMap<Hasher = Blake2_128Concat, Key = u32, Value = u32>;
/// }
/// ```
///
/// ### 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<V, E>`,
/// 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<T> = StorageValue<_, Vec<u8>,
/// 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<T> = 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<T>(_);
/// /// A kitchen-sink StorageValue, with all possible additional attributes.
/// #[pallet::storage]
/// #[pallet::getter(fn foo)]
/// #[pallet::storage_prefix = "OtherFoo"]
/// #[pallet::unbounded]
/// pub type Foo<T> = 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<Prefix, BoundedVec<u8, ConstU32<10>>, 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<Prefix, Vec<u8>, 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<Prefix, BoundedVec<u8, ConstU32<10>>, 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<Prefix1, u32, ValueQuery>;
type MyMap = StorageMap<Prefix2, Blake2_128Concat, u16, u32, ValueQuery>;
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<u8> = 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);
});
}
}
@@ -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<Value=u32>` 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<T>(_);
/// /// A kitchen-sink CountedStorageMap, with all possible additional attributes.
/// #[pallet::storage]
/// #[pallet::getter(fn foo)]
/// #[pallet::storage_prefix = "OtherFoo"]
/// #[pallet::unbounded]
/// pub type Foo<T> = CountedStorageMap<
/// _,
/// Blake2_128Concat,
/// u32,
/// u32,
/// ValueQuery,
/// >;
///
/// /// Alternative named syntax.
/// #[pallet::storage]
/// pub type Bar<T> = 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<Prefix, Twox64Concat, u16, u32>;
TestExternalities::default().execute_with(|| {
FooCountedMap::insert(1, 1);
FooCountedMap::insert(2, 2);
assert_eq!(FooCountedMap::count(), 2);
});
}
}
@@ -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<Value=u32>` 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<T>(_);
/// /// A kitchen-sink CountedStorageNMap, with all possible additional attributes.
/// #[pallet::storage]
/// #[pallet::getter(fn foo)]
/// #[pallet::storage_prefix = "OtherFoo"]
/// #[pallet::unbounded]
/// pub type Foo<T> = CountedStorageNMap<
/// _,
/// (
/// NMapKey<Blake2_128Concat, u8>,
/// NMapKey<Identity, u16>,
/// NMapKey<Twox64Concat, u32>
/// ),
/// u64,
/// ValueQuery,
/// >;
///
/// /// Alternative named syntax.
/// #[pallet::storage]
/// pub type Bar<T> = CountedStorageNMap<
/// Key = (
/// NMapKey<Blake2_128Concat, u8>,
/// NMapKey<Identity, u16>,
/// NMapKey<Twox64Concat, u32>
/// ),
/// Value = u64,
/// QueryKind = ValueQuery,
/// >;
/// }
/// ```
pub struct CountedStorageNMap<
Prefix,
Key,
@@ -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<T>(_);
/// /// A kitchen-sink StorageDoubleMap, with all possible additional attributes.
/// #[pallet::storage]
/// #[pallet::getter(fn foo)]
/// #[pallet::storage_prefix = "OtherFoo"]
/// #[pallet::unbounded]
/// pub type Foo<T> = StorageDoubleMap<
/// _,
/// Blake2_128Concat,
/// u8,
/// Twox64Concat,
/// u16,
/// u32,
/// ValueQuery
/// >;
///
/// /// Alternative named syntax.
/// #[pallet::storage]
/// pub type Bar<T> = 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<_>>(), vec![]);
})
}
#[docify::export]
#[test]
fn example_double_map_partial_operations() {
type FooDoubleMap =
StorageDoubleMap<Prefix, Blake2_128Concat, u32, Blake2_128Concat, u32, u32, ValueQuery>;
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::<BTreeSet<_>>());
// 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::<BTreeSet<_>>());
// 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<_>>(), vec![]);
});
}
}
@@ -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.
///
/// For general information regarding the `#[pallet::storage]` attribute, refer to
/// [`crate::pallet_macros::storage`].
///
/// # Example
///
/// Each value is stored at:
/// ```nocompile
/// Twox128(Prefix::pallet_prefix())
/// ++ Twox128(Prefix::STORAGE_PREFIX)
/// ++ Hasher1(encode(key))
/// ```
/// #[frame_support::pallet]
/// mod pallet {
/// # use frame_support::pallet_prelude::*;
/// # #[pallet::config]
/// # pub trait Config: frame_system::Config {}
/// # #[pallet::pallet]
/// # pub struct Pallet<T>(_);
/// /// A kitchen-sink StorageMap, with all possible additional attributes.
/// #[pallet::storage]
/// #[pallet::getter(fn foo)]
/// #[pallet::storage_prefix = "OtherFoo"]
/// #[pallet::unbounded]
/// pub type Foo<T> = StorageMap<
/// _
/// Blake2_128Concat,
/// u32,
/// u32,
/// ValueQuery
/// >;
///
/// # Warning
///
/// 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<T> = StorageMap<
/// Hasher = Blake2_128Concat,
/// Key = u32,
/// Value = u32,
/// QueryKind = ValueQuery
/// >;
/// }
/// ```
pub struct StorageMap<
Prefix,
Hasher,
@@ -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<Value, OnEmpty> {
/// Metadata for the storage kind.
const METADATA: StorageEntryModifierIR;
@@ -65,11 +69,10 @@ pub trait QueryKindTrait<Value, OnEmpty> {
fn from_query_to_optional_value(v: Self::Query) -> Option<Value>;
}
/// Implement QueryKindTrait with query being `Option<Value>`
/// 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<Value> QueryKindTrait<Value, crate::traits::GetDefault> for OptionQuery
where
@@ -89,7 +92,7 @@ where
}
}
/// Implement QueryKindTrait with query being `Result<Value, PalletError>`
/// Implements [`QueryKindTrait`] with `Query` type being `Result<Value, PalletError>`.
pub struct ResultQuery<Error>(sp_std::marker::PhantomData<Error>);
impl<Value, Error, OnEmpty> QueryKindTrait<Value, OnEmpty> for ResultQuery<Error>
where
@@ -113,7 +116,7 @@ where
}
}
/// Implement QueryKindTrait with query being `Value`
/// Implements [`QueryKindTrait`] with `Query` type being `Value`.
pub struct ValueQuery;
impl<Value, OnEmpty> QueryKindTrait<Value, OnEmpty> 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<StorageEntryMetadataIR>);
}
#[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<u32> for UniverseSecret {
fn get() -> u32 {
42
}
}
/// Custom default impl to be used with `ResultQuery`.
struct GetDefaultForResult;
impl Get<Result<u32, ()>> for GetDefaultForResult {
fn get() -> Result<u32, ()> {
Err(())
}
}
type A = StorageValue<Prefix, u32, ValueQuery>;
type B = StorageValue<Prefix, u32, OptionQuery>;
type C = StorageValue<Prefix, u32, ResultQuery<()>, GetDefaultForResult>;
type D = StorageValue<Prefix, u32, ValueQuery, UniverseSecret>;
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);
});
}
}
@@ -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<Hasher1, key1>, Key<Hasher2, key2>, ..., Key<HasherN, keyN>)`.
/// 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.
///
/// 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`].
///
/// # Example
///
/// Each value is stored at:
/// ```nocompile
/// Twox128(Prefix::pallet_prefix())
/// ++ Twox128(Prefix::STORAGE_PREFIX)
/// ++ Hasher1(encode(key1))
/// ++ Hasher2(encode(key2))
/// ++ ...
/// ++ HasherN(encode(keyN))
/// ```
/// #[frame_support::pallet]
/// mod pallet {
/// # use frame_support::pallet_prelude::*;
/// # #[pallet::config]
/// # pub trait Config: frame_system::Config {}
/// # #[pallet::pallet]
/// # pub struct Pallet<T>(_);
/// /// A kitchen-sink StorageNMap, with all possible additional attributes.
/// #[pallet::storage]
/// #[pallet::getter(fn foo)]
/// #[pallet::storage_prefix = "OtherFoo"]
/// #[pallet::unbounded]
/// pub type Foo<T> = StorageNMap<
/// _,
/// (
/// NMapKey<Blake2_128Concat, u8>,
/// NMapKey<Identity, u16>,
/// NMapKey<Twox64Concat, u32>
/// ),
/// u64,
/// ValueQuery,
/// >;
///
/// # Warning
///
/// 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.
/// /// Named alternative syntax.
/// #[pallet::storage]
/// pub type Bar<T> = StorageNMap<
/// Key = (
/// NMapKey<Blake2_128Concat, u8>,
/// NMapKey<Identity, u16>,
/// NMapKey<Twox64Concat, u32>
/// ),
/// Value = u64,
/// QueryKind = ValueQuery,
/// >;
/// }
/// ```
pub struct StorageNMap<
Prefix,
Key,
@@ -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<T>(_);
/// /// A kitchen-sink StorageValue, with all possible additional attributes.
/// #[pallet::storage]
/// #[pallet::getter(fn foo)]
/// #[pallet::storage_prefix = "OtherFoo"]
/// #[pallet::unbounded]
/// pub type Foo<T> = StorageValue<_, u32,ValueQuery>;
///
/// /// Named alternative syntax.
/// #[pallet::storage]
/// pub type Bar<T> = StorageValue<
/// Value = u32,
/// QueryKind = ValueQuery
/// >;
/// }
/// ```
pub struct StorageValue<Prefix, Value, QueryKind = OptionQuery, OnEmpty = GetDefault>(
core::marker::PhantomData<(Prefix, Value, QueryKind, OnEmpty)>,