From ef0238dd296e8ac5f871c0d5bcea4e695621ecfb Mon Sep 17 00:00:00 2001 From: Alex Pozhylenkov Date: Fri, 4 Aug 2023 18:06:08 +0300 Subject: [PATCH] CountedNMap implementation (#10621) * add initial CountedDoubleMap implementation * extend CountedDoubleMap functionality * add some traits implementation for CountedStorageDoubleMap * add basic tests for CountedStorageDoubleMap * add mutate functions implementation * add additional tests * add test_option_query test * add try_append_decode_len_works, append_decode_len_works tests * add migrate_keys_works, translate_values tests * add test_iter_drain_translate test * add test_metadata test * add remove_prefix implementation, add test_iter_drain_prefix test * update * refactor PrefixIterator usage * Fix CI build * fix storage_ensure_span_are_ok_wrong_gen.rs storage_ensure_span_are_ok_wrong_gen_unnamed.rs * add counted_nmap implementation * add tests, fixes * remove counted double map impl * fix metadata checks * update clear func * fix clear, clear with prefix * fix set function * update * final fix * Update frame/support/src/storage/types/counted_nmap.rs Co-authored-by: Anton * Update frame/support/src/storage/types/counted_nmap.rs Co-authored-by: Anton * Update frame/support/src/storage/types/counted_nmap.rs Co-authored-by: Anton * fix comments * fix suggestion * cargo update * Relocate impl of Sealed for Ref to module root * fix StorageEntryMetadata type * Update frame/support/src/storage/types/nmap.rs Co-authored-by: Guillaume Yu Thiolliere * removed StorageNMap and StoragePrefixedMap traits impl * fix tests * Update frame/support/src/storage/types/counted_nmap.rs Co-authored-by: Guillaume Yu Thiolliere * extend pallet::storage macro with CountedStorageNMap usage * fix * add tests * fix * fix * Add counter_storage_final_key(), map_storage_final_prefix() functions * update tests * fix * fix * fix * update tests * fix fmt * fix fmt --------- Co-authored-by: Anton Co-authored-by: Keith Yeung Co-authored-by: Guillaume Yu Thiolliere Co-authored-by: parity-processbot <> --- .../procedural/src/pallet/expand/storage.rs | 163 +- .../procedural/src/pallet/parse/storage.rs | 50 +- substrate/frame/support/src/lib.rs | 4 +- substrate/frame/support/src/storage/mod.rs | 1 + .../support/src/storage/types/counted_map.rs | 9 +- .../support/src/storage/types/counted_nmap.rs | 1427 +++++++++++++++++ .../frame/support/src/storage/types/key.rs | 16 +- .../frame/support/src/storage/types/mod.rs | 2 + .../frame/support/src/storage/types/nmap.rs | 4 +- substrate/frame/support/test/tests/pallet.rs | 221 +++ .../pallet_ui/storage_not_storage_type.stderr | 2 +- 11 files changed, 1840 insertions(+), 59 deletions(-) create mode 100644 substrate/frame/support/src/storage/types/counted_nmap.rs diff --git a/substrate/frame/support/procedural/src/pallet/expand/storage.rs b/substrate/frame/support/procedural/src/pallet/expand/storage.rs index 253b429bb6..f3c394d731 100644 --- a/substrate/frame/support/procedural/src/pallet/expand/storage.rs +++ b/substrate/frame/support/procedural/src/pallet/expand/storage.rs @@ -194,18 +194,7 @@ pub fn process_generics(def: &mut Def) -> syn::Result { - args.args.push(syn::GenericArgument::Type(hasher)); - args.args.push(syn::GenericArgument::Type(key)); - args.args.push(syn::GenericArgument::Type(value.clone())); - let mut query_kind = query_kind.unwrap_or_else(|| default_query_kind.clone()); - set_result_query_type_parameter(&mut query_kind)?; - args.args.push(syn::GenericArgument::Type(query_kind)); - let on_empty = on_empty.unwrap_or_else(|| default_on_empty(value)); - args.args.push(syn::GenericArgument::Type(on_empty)); - let max_values = max_values.unwrap_or_else(|| default_max_values.clone()); - args.args.push(syn::GenericArgument::Type(max_values)); - }, + StorageGenerics::Map { hasher, key, value, query_kind, on_empty, max_values } | StorageGenerics::CountedMap { hasher, key, @@ -248,7 +237,14 @@ pub fn process_generics(def: &mut Def) -> syn::Result { + StorageGenerics::NMap { keygen, value, query_kind, on_empty, max_values } | + StorageGenerics::CountedNMap { + keygen, + value, + query_kind, + on_empty, + max_values, + } => { args.args.push(syn::GenericArgument::Type(keygen)); args.args.push(syn::GenericArgument::Type(value.clone())); let mut query_kind = query_kind.unwrap_or_else(|| default_query_kind.clone()); @@ -265,7 +261,7 @@ pub fn process_generics(def: &mut Def) -> syn::Result (1, 2, 3), - Metadata::NMap { .. } => (2, 3, 4), + Metadata::NMap { .. } | Metadata::CountedNMap { .. } => (2, 3, 4), Metadata::Map { .. } | Metadata::CountedMap { .. } => (3, 4, 5), Metadata::DoubleMap { .. } => (5, 6, 7), }; @@ -359,6 +355,17 @@ fn augment_final_docs(def: &mut Def) { ); push_string_literal(&doc_line, storage); }, + Metadata::CountedNMap { keys, value, .. } => { + let doc_line = format!( + "Storage type is [`CountedStorageNMap`] with keys type ({}) and value type {}.", + keys.iter() + .map(|k| k.to_token_stream().to_string()) + .collect::>() + .join(", "), + value.to_token_stream() + ); + push_string_literal(&doc_line, storage); + }, Metadata::CountedMap { key, value } => { let doc_line = format!( "Storage type is [`CountedStorageMap`] with key type {} and value type {}.", @@ -579,6 +586,36 @@ pub fn expand_storages(def: &mut Def) -> proc_macro2::TokenStream { } ) }, + Metadata::CountedNMap { keygen, value, .. } => { + let query = match storage.query_kind.as_ref().expect("Checked by def") { + QueryKind::OptionQuery => quote::quote_spanned!(storage.attr_span => + Option<#value> + ), + QueryKind::ResultQuery(error_path, _) => { + quote::quote_spanned!(storage.attr_span => + Result<#value, #error_path> + ) + }, + QueryKind::ValueQuery => quote::quote!(#value), + }; + quote::quote_spanned!(storage.attr_span => + #(#cfg_attrs)* + impl<#type_impl_gen> #pallet_ident<#type_use_gen> #completed_where_clause { + #[doc = #getter_doc_line] + pub fn #getter(key: KArg) -> #query + where + KArg: #frame_support::storage::types::EncodeLikeTuple< + <#keygen as #frame_support::storage::types::KeyGenerator>::KArg + > + + #frame_support::storage::types::TupleToEncodedIter, + { + // NOTE: we can't use any trait here because CountedStorageNMap + // doesn't implement any. + <#full_ident>::get(key) + } + } + ) + }, } } else { Default::default() @@ -595,40 +632,72 @@ pub fn expand_storages(def: &mut Def) -> proc_macro2::TokenStream { let cfg_attrs = &storage_def.cfg_attrs; - let maybe_counter = if let Metadata::CountedMap { .. } = storage_def.metadata { - let counter_prefix_struct_ident = counter_prefix_ident(&storage_def.ident); - let counter_prefix_struct_const = counter_prefix(&prefix_struct_const); - - quote::quote_spanned!(storage_def.attr_span => - #(#cfg_attrs)* - #[doc(hidden)] - #prefix_struct_vis struct #counter_prefix_struct_ident<#type_use_gen>( - core::marker::PhantomData<(#type_use_gen,)> - ); - #(#cfg_attrs)* - impl<#type_impl_gen> #frame_support::traits::StorageInstance - for #counter_prefix_struct_ident<#type_use_gen> - #config_where_clause - { - fn pallet_prefix() -> &'static str { - < - ::PalletInfo - as #frame_support::traits::PalletInfo - >::name::>() - .expect("No name found for the pallet in the runtime! This usually means that the pallet wasn't added to `construct_runtime!`.") + let maybe_counter = match storage_def.metadata { + Metadata::CountedMap { .. } => { + let counter_prefix_struct_ident = counter_prefix_ident(&storage_def.ident); + let counter_prefix_struct_const = counter_prefix(&prefix_struct_const); + quote::quote_spanned!(storage_def.attr_span => + #(#cfg_attrs)* + #[doc(hidden)] + #prefix_struct_vis struct #counter_prefix_struct_ident<#type_use_gen>( + core::marker::PhantomData<(#type_use_gen,)> + ); + #(#cfg_attrs)* + impl<#type_impl_gen> #frame_support::traits::StorageInstance + for #counter_prefix_struct_ident<#type_use_gen> + #config_where_clause + { + fn pallet_prefix() -> &'static str { + < + ::PalletInfo + as #frame_support::traits::PalletInfo + >::name::>() + .expect("No name found for the pallet in the runtime! This usually means that the pallet wasn't added to `construct_runtime!`.") + } + const STORAGE_PREFIX: &'static str = #counter_prefix_struct_const; } - const STORAGE_PREFIX: &'static str = #counter_prefix_struct_const; - } - #(#cfg_attrs)* - impl<#type_impl_gen> #frame_support::storage::types::CountedStorageMapInstance - for #prefix_struct_ident<#type_use_gen> - #config_where_clause - { - type CounterPrefix = #counter_prefix_struct_ident<#type_use_gen>; - } - ) - } else { - proc_macro2::TokenStream::default() + #(#cfg_attrs)* + impl<#type_impl_gen> #frame_support::storage::types::CountedStorageMapInstance + for #prefix_struct_ident<#type_use_gen> + #config_where_clause + { + type CounterPrefix = #counter_prefix_struct_ident<#type_use_gen>; + } + ) + }, + Metadata::CountedNMap { .. } => { + let counter_prefix_struct_ident = counter_prefix_ident(&storage_def.ident); + let counter_prefix_struct_const = counter_prefix(&prefix_struct_const); + quote::quote_spanned!(storage_def.attr_span => + #(#cfg_attrs)* + #[doc(hidden)] + #prefix_struct_vis struct #counter_prefix_struct_ident<#type_use_gen>( + core::marker::PhantomData<(#type_use_gen,)> + ); + #(#cfg_attrs)* + impl<#type_impl_gen> #frame_support::traits::StorageInstance + for #counter_prefix_struct_ident<#type_use_gen> + #config_where_clause + { + fn pallet_prefix() -> &'static str { + < + ::PalletInfo + as #frame_support::traits::PalletInfo + >::name::>() + .expect("No name found for the pallet in the runtime! This usually means that the pallet wasn't added to `construct_runtime!`.") + } + const STORAGE_PREFIX: &'static str = #counter_prefix_struct_const; + } + #(#cfg_attrs)* + impl<#type_impl_gen> #frame_support::storage::types::CountedStorageNMapInstance + for #prefix_struct_ident<#type_use_gen> + #config_where_clause + { + type CounterPrefix = #counter_prefix_struct_ident<#type_use_gen>; + } + ) + }, + _ => proc_macro2::TokenStream::default(), }; quote::quote_spanned!(storage_def.attr_span => diff --git a/substrate/frame/support/procedural/src/pallet/parse/storage.rs b/substrate/frame/support/procedural/src/pallet/parse/storage.rs index 12e06b214b..3a0ec47471 100644 --- a/substrate/frame/support/procedural/src/pallet/parse/storage.rs +++ b/substrate/frame/support/procedural/src/pallet/parse/storage.rs @@ -138,6 +138,7 @@ pub enum Metadata { CountedMap { value: syn::Type, key: syn::Type }, DoubleMap { value: syn::Type, key1: syn::Type, key2: syn::Type }, NMap { keys: Vec, keygen: syn::Type, value: syn::Type }, + CountedNMap { keys: Vec, keygen: syn::Type, value: syn::Type }, } pub enum QueryKind { @@ -230,6 +231,13 @@ pub enum StorageGenerics { on_empty: Option, max_values: Option, }, + CountedNMap { + keygen: syn::Type, + value: syn::Type, + query_kind: Option, + on_empty: Option, + max_values: Option, + }, } impl StorageGenerics { @@ -242,6 +250,8 @@ impl StorageGenerics { Self::Value { value, .. } => Metadata::Value { value }, Self::NMap { keygen, value, .. } => Metadata::NMap { keys: collect_keys(&keygen)?, keygen, value }, + Self::CountedNMap { keygen, value, .. } => + Metadata::CountedNMap { keys: collect_keys(&keygen)?, keygen, value }, }; Ok(res) @@ -254,7 +264,8 @@ impl StorageGenerics { Self::Map { query_kind, .. } | Self::CountedMap { query_kind, .. } | Self::Value { query_kind, .. } | - Self::NMap { query_kind, .. } => query_kind.clone(), + Self::NMap { query_kind, .. } | + Self::CountedNMap { query_kind, .. } => query_kind.clone(), } } } @@ -265,6 +276,7 @@ enum StorageKind { CountedMap, DoubleMap, NMap, + CountedNMap, } /// Check the generics in the `map` contains the generics in `gen` may contains generics in @@ -493,6 +505,29 @@ fn process_named_generics( max_values: parsed.remove("MaxValues").map(|binding| binding.ty), } }, + StorageKind::CountedNMap => { + check_generics( + &parsed, + &["Key", "Value"], + &["QueryKind", "OnEmpty", "MaxValues"], + "CountedStorageNMap", + args_span, + )?; + + StorageGenerics::CountedNMap { + keygen: parsed + .remove("Key") + .map(|binding| binding.ty) + .expect("checked above as mandatory generic"), + value: parsed + .remove("Value") + .map(|binding| binding.ty) + .expect("checked above as mandatory generic"), + query_kind: parsed.remove("QueryKind").map(|binding| binding.ty), + on_empty: parsed.remove("OnEmpty").map(|binding| binding.ty), + max_values: parsed.remove("MaxValues").map(|binding| binding.ty), + } + }, }; let metadata = generics.metadata()?; @@ -578,6 +613,16 @@ fn process_unnamed_generics( false, ) }, + StorageKind::CountedNMap => { + let keygen = retrieve_arg(1)?; + let keys = collect_keys(&keygen)?; + ( + None, + Metadata::CountedNMap { keys, keygen, value: retrieve_arg(2)? }, + retrieve_arg(3).ok(), + false, + ) + }, }; Ok(res) @@ -594,10 +639,11 @@ fn process_generics( "CountedStorageMap" => StorageKind::CountedMap, "StorageDoubleMap" => StorageKind::DoubleMap, "StorageNMap" => StorageKind::NMap, + "CountedStorageNMap" => StorageKind::CountedNMap, found => { let msg = format!( "Invalid pallet::storage, expected ident: `StorageValue` or \ - `StorageMap` or `CountedStorageMap` or `StorageDoubleMap` or `StorageNMap` \ + `StorageMap` or `CountedStorageMap` or `StorageDoubleMap` or `StorageNMap` or `CountedStorageNMap` \ in order to expand metadata, found `{}`.", found, ); diff --git a/substrate/frame/support/src/lib.rs b/substrate/frame/support/src/lib.rs index 230a3ab5a3..d37490c341 100644 --- a/substrate/frame/support/src/lib.rs +++ b/substrate/frame/support/src/lib.rs @@ -1545,8 +1545,8 @@ pub mod pallet_prelude { storage::{ bounded_vec::BoundedVec, types::{ - CountedStorageMap, Key as NMapKey, OptionQuery, ResultQuery, StorageDoubleMap, - StorageMap, StorageNMap, StorageValue, ValueQuery, + CountedStorageMap, CountedStorageNMap, Key as NMapKey, OptionQuery, ResultQuery, + StorageDoubleMap, StorageMap, StorageNMap, StorageValue, ValueQuery, }, StorageList, }, diff --git a/substrate/frame/support/src/storage/mod.rs b/substrate/frame/support/src/storage/mod.rs index 4eecd4febf..36e2c47383 100644 --- a/substrate/frame/support/src/storage/mod.rs +++ b/substrate/frame/support/src/storage/mod.rs @@ -1428,6 +1428,7 @@ mod private { impl Sealed for bounded_btree_map::BoundedBTreeMap {} impl Sealed for bounded_btree_set::BoundedBTreeSet {} impl Sealed for BTreeSet {} + impl<'a, T: EncodeLike, U: Encode> Sealed for codec::Ref<'a, T, U> {} macro_rules! impl_sealed_for_tuple { ($($elem:ident),+) => { diff --git a/substrate/frame/support/src/storage/types/counted_map.rs b/substrate/frame/support/src/storage/types/counted_map.rs index e57942cbe0..081f99fa16 100644 --- a/substrate/frame/support/src/storage/types/counted_map.rs +++ b/substrate/frame/support/src/storage/types/counted_map.rs @@ -612,8 +612,9 @@ mod test { assert_eq!(A::count(), 2); // Insert an existing key, shouldn't increment counted values. - A::insert(3, 11); + A::insert(3, 12); + assert_eq!(A::try_get(3), Ok(12)); assert_eq!(A::count(), 2); // Remove non-existing. @@ -706,17 +707,17 @@ mod test { // Try succeed mutate existing to existing. A::try_mutate_exists(1, |query| { assert_eq!(*query, Some(43)); - *query = Some(43); + *query = Some(45); Result::<(), ()>::Ok(()) }) .unwrap(); - assert_eq!(A::try_get(1), Ok(43)); + assert_eq!(A::try_get(1), Ok(45)); assert_eq!(A::count(), 4); // Try succeed mutate existing to non-existing. A::try_mutate_exists(1, |query| { - assert_eq!(*query, Some(43)); + assert_eq!(*query, Some(45)); *query = None; Result::<(), ()>::Ok(()) }) diff --git a/substrate/frame/support/src/storage/types/counted_nmap.rs b/substrate/frame/support/src/storage/types/counted_nmap.rs new file mode 100644 index 0000000000..43a243cc5d --- /dev/null +++ b/substrate/frame/support/src/storage/types/counted_nmap.rs @@ -0,0 +1,1427 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Counted storage n-map type. + +use crate::{ + storage::{ + types::{ + EncodeLikeTuple, HasKeyPrefix, HasReversibleKeyPrefix, OptionQuery, QueryKindTrait, + StorageEntryMetadataBuilder, StorageNMap, StorageValue, TupleToEncodedIter, ValueQuery, + }, + KeyGenerator, PrefixIterator, StorageAppend, StorageDecodeLength, + }, + traits::{Get, GetDefault, StorageInfo, StorageInstance}, + Never, +}; +use codec::{Decode, Encode, EncodeLike, FullCodec, MaxEncodedLen, Ref}; +use sp_api::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. +/// +/// This storage item has some additional storage read and write overhead when manipulating values +/// compared to a regular storage map. +/// +/// For functions where we only add or remove a value, a single storage read is needed to check if +/// that value already exists. For mutate functions, two storage reads are used to check if the +/// value existed before and after the mutation. +/// +/// Whenever the counter needs to be updated, an additional read and write occurs to update that +/// counter. +pub struct CountedStorageNMap< + Prefix, + Key, + Value, + QueryKind = OptionQuery, + OnEmpty = GetDefault, + MaxValues = GetDefault, +>(core::marker::PhantomData<(Prefix, Key, Value, QueryKind, OnEmpty, MaxValues)>); + +/// The requirement for an instance of [`CountedStorageNMap`]. +pub trait CountedStorageNMapInstance: StorageInstance { + /// The prefix to use for the counter storage value. + type CounterPrefix: StorageInstance; +} + +// Private helper trait to access map from counted storage n-map +trait MapWrapper { + type Map; +} + +impl MapWrapper + for CountedStorageNMap +{ + type Map = StorageNMap; +} + +type CounterFor

= + StorageValue<

::CounterPrefix, u32, ValueQuery>; + +/// On removal logic for updating counter while draining upon some prefix with +/// [`crate::storage::PrefixIterator`]. +pub struct OnRemovalCounterUpdate(core::marker::PhantomData); + +impl crate::storage::PrefixIteratorOnRemoval + for OnRemovalCounterUpdate +{ + fn on_removal(_key: &[u8], _value: &[u8]) { + CounterFor::::mutate(|value| value.saturating_dec()); + } +} + +impl + CountedStorageNMap +where + Prefix: CountedStorageNMapInstance, + Key: super::key::KeyGenerator, + Value: FullCodec, + QueryKind: QueryKindTrait, + OnEmpty: Get + 'static, + MaxValues: Get>, +{ + /// The key used to store the counter of the map. + pub fn counter_storage_final_key() -> [u8; 32] { + CounterFor::::hashed_key() + } + + /// The prefix used to generate the key of the map. + pub fn map_storage_final_prefix() -> Vec { + use crate::storage::generator::StorageNMap; + ::Map::prefix_hash() + } + + /// Get the storage key used to fetch a value corresponding to a specific key. + pub fn hashed_key_for + TupleToEncodedIter>( + key: KArg, + ) -> Vec { + ::Map::hashed_key_for(key) + } + + /// Does the value (explicitly) exist in storage? + pub fn contains_key + TupleToEncodedIter>(key: KArg) -> bool { + ::Map::contains_key(key) + } + + /// Load the value associated with the given key from the map. + pub fn get + TupleToEncodedIter>( + key: KArg, + ) -> QueryKind::Query { + ::Map::get(key) + } + + /// Try to get the value for the given key from the map. + /// + /// Returns `Ok` if it exists, `Err` if not. + pub fn try_get + TupleToEncodedIter>( + key: KArg, + ) -> Result { + ::Map::try_get(key) + } + + /// Store or remove the value to be associated with `key` so that `get` returns the `query`. + /// It decrements the counter when the value is removed. + pub fn set + TupleToEncodedIter>( + key: KArg, + query: QueryKind::Query, + ) { + let option = QueryKind::from_query_to_optional_value(query); + if option.is_none() { + CounterFor::::mutate(|value| value.saturating_dec()); + } + ::Map::set(key, QueryKind::from_optional_value_to_query(option)) + } + + /// Take a value from storage, removing it afterwards. + pub fn take + TupleToEncodedIter>( + key: KArg, + ) -> QueryKind::Query { + let removed_value = + ::Map::mutate_exists(key, |value| core::mem::replace(value, None)); + if removed_value.is_some() { + CounterFor::::mutate(|value| value.saturating_dec()); + } + QueryKind::from_optional_value_to_query(removed_value) + } + + /// Swap the values of two key-pairs. + pub fn swap(key1: KArg1, key2: KArg2) + where + KOther: KeyGenerator, + KArg1: EncodeLikeTuple + TupleToEncodedIter, + KArg2: EncodeLikeTuple + TupleToEncodedIter, + { + ::Map::swap::(key1, key2) + } + + /// Store a value to be associated with the given keys from the map. + pub fn insert(key: KArg, val: VArg) + where + KArg: EncodeLikeTuple + EncodeLike + TupleToEncodedIter, + VArg: EncodeLike, + { + if !::Map::contains_key(Ref::from(&key)) { + CounterFor::::mutate(|value| value.saturating_inc()); + } + ::Map::insert(key, val) + } + + /// Remove the value under the given keys. + pub fn remove + EncodeLike + TupleToEncodedIter>( + key: KArg, + ) { + if ::Map::contains_key(Ref::from(&key)) { + CounterFor::::mutate(|value| value.saturating_dec()); + } + ::Map::remove(key) + } + + /// Attempt to remove items from the map matching a `partial_key` prefix. + /// + /// Returns [`MultiRemovalResults`](sp_io::MultiRemovalResults) to inform about the result. Once + /// the resultant `maybe_cursor` field is `None`, then no further items remain to be deleted. + /// + /// NOTE: After the initial call for any given map, it is important that no further items + /// are inserted into the map which match the `partial key`. If so, then the map may not be + /// empty when the resultant `maybe_cursor` is `None`. + /// + /// # Limit + /// + /// A `limit` must be provided in order to cap the maximum + /// amount of deletions done in a single call. This is one fewer than the + /// maximum number of backend iterations which may be done by this operation and as such + /// represents the maximum number of backend deletions which may happen. A `limit` of zero + /// implies that no keys will be deleted, though there may be a single iteration done. + /// + /// # Cursor + /// + /// A *cursor* may be passed in to this operation with `maybe_cursor`. `None` should only be + /// passed once (in the initial call) for any given storage map and `partial_key`. Subsequent + /// calls operating on the same map/`partial_key` should always pass `Some`, and this should be + /// equal to the previous call result's `maybe_cursor` field. + pub fn clear_prefix( + partial_key: KP, + limit: u32, + maybe_cursor: Option<&[u8]>, + ) -> sp_io::MultiRemovalResults + where + Key: HasKeyPrefix, + { + let result = ::Map::clear_prefix(partial_key, limit, maybe_cursor); + match result.maybe_cursor { + None => CounterFor::::kill(), + Some(_) => CounterFor::::mutate(|x| x.saturating_reduce(result.unique)), + } + result + } + + /// Iterate over values that share the first key. + pub fn iter_prefix_values(partial_key: KP) -> PrefixIterator + where + Key: HasKeyPrefix, + { + ::Map::iter_prefix_values(partial_key) + } + + /// Mutate the value under the given keys. + pub fn mutate(key: KArg, f: F) -> R + where + KArg: EncodeLikeTuple + TupleToEncodedIter, + F: FnOnce(&mut QueryKind::Query) -> R, + { + Self::try_mutate(key, |v| Ok::(f(v))) + .expect("`Never` can not be constructed; qed") + } + + /// Mutate the value under the given keys when the closure returns `Ok`. + pub fn try_mutate(key: KArg, f: F) -> Result + where + KArg: EncodeLikeTuple + TupleToEncodedIter, + F: FnOnce(&mut QueryKind::Query) -> Result, + { + Self::try_mutate_exists(key, |option_value_ref| { + let option_value = core::mem::replace(option_value_ref, None); + let mut query = QueryKind::from_optional_value_to_query(option_value); + let res = f(&mut query); + let option_value = QueryKind::from_query_to_optional_value(query); + let _ = core::mem::replace(option_value_ref, option_value); + res + }) + } + + /// Mutate the value under the given keys. Deletes the item if mutated to a `None`. + pub fn mutate_exists(key: KArg, f: F) -> R + where + KArg: EncodeLikeTuple + TupleToEncodedIter, + F: FnOnce(&mut Option) -> R, + { + Self::try_mutate_exists(key, |v| Ok::(f(v))) + .expect("`Never` can not be constructed; qed") + } + + /// Mutate the item, only if an `Ok` value is returned. Deletes the item if mutated to a `None`. + /// `f` will always be called with an option representing if the storage item exists (`Some`) + /// or if the storage item does not exist (`None`), independent of the `QueryType`. + pub fn try_mutate_exists(key: KArg, f: F) -> Result + where + KArg: EncodeLikeTuple + TupleToEncodedIter, + F: FnOnce(&mut Option) -> Result, + { + ::Map::try_mutate_exists(key, |option_value| { + let existed = option_value.is_some(); + let res = f(option_value); + let exist = option_value.is_some(); + + if res.is_ok() { + if existed && !exist { + // Value was deleted + CounterFor::::mutate(|value| value.saturating_dec()); + } else if !existed && exist { + // Value was added + CounterFor::::mutate(|value| value.saturating_inc()); + } + } + res + }) + } + + /// Append the given item to the value in the storage. + /// + /// `Value` is required to implement [`StorageAppend`]. + /// + /// # Warning + /// + /// If the storage item is not encoded properly, the storage will be overwritten + /// and set to `[item]`. Any default value set for the storage item will be ignored + /// on overwrite. + pub fn append(key: KArg, item: EncodeLikeItem) + where + KArg: EncodeLikeTuple + EncodeLike + TupleToEncodedIter, + Item: Encode, + EncodeLikeItem: EncodeLike, + Value: StorageAppend, + { + if !::Map::contains_key(Ref::from(&key)) { + CounterFor::::mutate(|value| value.saturating_inc()); + } + ::Map::append(key, item) + } + + /// Read the length of the storage value without decoding the entire value under the + /// given `key1` and `key2`. + /// + /// `Value` is required to implement [`StorageDecodeLength`]. + /// + /// If the value does not exists or it fails to decode the length, `None` is returned. + /// Otherwise `Some(len)` is returned. + /// + /// # Warning + /// + /// `None` does not mean that `get()` does not return a value. The default value is completly + /// ignored by this function. + pub fn decode_len + TupleToEncodedIter>( + key: KArg, + ) -> Option + where + Value: StorageDecodeLength, + { + ::Map::decode_len(key) + } + + /// Migrate an item with the given `key` from defunct `hash_fns` to the current hashers. + /// + /// If the key doesn't exist, then it's a no-op. If it does, then it returns its value. + pub fn migrate_keys(key: KArg, hash_fns: Key::HArg) -> Option + where + KArg: EncodeLikeTuple + TupleToEncodedIter, + { + ::Map::migrate_keys::<_>(key, hash_fns) + } + + /// Attempt to remove all items from the map. + /// + /// Returns [`MultiRemovalResults`](sp_io::MultiRemovalResults) to inform about the result. Once + /// the resultant `maybe_cursor` field is `None`, then no further items remain to be deleted. + /// + /// NOTE: After the initial call for any given map, it is important that no further items + /// are inserted into the map. If so, then the map may not be empty when the resultant + /// `maybe_cursor` is `None`. + /// + /// # Limit + /// + /// A `limit` must always be provided through in order to cap the maximum + /// amount of deletions done in a single call. This is one fewer than the + /// maximum number of backend iterations which may be done by this operation and as such + /// represents the maximum number of backend deletions which may happen. A `limit` of zero + /// implies that no keys will be deleted, though there may be a single iteration done. + /// + /// # Cursor + /// + /// A *cursor* may be passed in to this operation with `maybe_cursor`. `None` should only be + /// passed once (in the initial call) for any given storage map. Subsequent calls + /// operating on the same map should always pass `Some`, and this should be equal to the + /// previous call result's `maybe_cursor` field. + pub fn clear(limit: u32, maybe_cursor: Option<&[u8]>) -> sp_io::MultiRemovalResults { + let result = ::Map::clear(limit, maybe_cursor); + match result.maybe_cursor { + None => CounterFor::::kill(), + Some(_) => CounterFor::::mutate(|x| x.saturating_reduce(result.unique)), + } + result + } + + /// Iter over all value of the storage. + /// + /// NOTE: If a value failed to decode because storage is corrupted then it is skipped. + pub fn iter_values() -> crate::storage::PrefixIterator { + ::Map::iter_values() + } + + /// Translate the values of all elements by a function `f`, in the map in no particular order. + /// By returning `None` from `f` for an element, you'll remove it from the map. + /// + /// NOTE: If a value fail to decode because storage is corrupted then it is skipped. + /// + /// # Warning + /// + /// This function must be used with care, before being updated the storage still contains the + /// old type, thus other calls (such as `get`) will fail at decoding it. + /// + /// # Usage + /// + /// This would typically be called inside the module implementation of on_runtime_upgrade. + pub fn translate_values Option>(mut f: F) { + ::Map::translate_values(|old_value| { + let res = f(old_value); + if res.is_none() { + CounterFor::::mutate(|value| value.saturating_dec()); + } + res + }) + } + + /// Initialize the counter with the actual number of items in the map. + /// + /// This function iterates through all the items in the map and sets the counter. This operation + /// can be very heavy, so use with caution. + /// + /// Returns the number of items in the map which is used to set the counter. + pub fn initialize_counter() -> u32 { + let count = Self::iter_values().count() as u32; + CounterFor::::set(count); + count + } + + /// Return the count. + pub fn count() -> u32 { + CounterFor::::get() + } +} + +impl + CountedStorageNMap +where + Prefix: CountedStorageNMapInstance, + Key: super::key::ReversibleKeyGenerator, + Value: FullCodec, + QueryKind: QueryKindTrait, + OnEmpty: Get + 'static, + MaxValues: Get>, +{ + /// Enumerate all elements in the map with prefix key `kp` in no particular order. + /// + /// If you add or remove values whose prefix key is `kp` to the map while doing this, you'll get + /// undefined results. + pub fn iter_prefix( + kp: KP, + ) -> crate::storage::PrefixIterator<(>::Suffix, Value)> + where + Key: HasReversibleKeyPrefix, + { + ::Map::iter_prefix(kp) + } + + /// Enumerate all elements in the map with prefix key `kp` after a specified `starting_raw_key` + /// in no particular order. + /// + /// If you add or remove values whose prefix key is `kp` to the map while doing this, you'll get + /// undefined results. + pub fn iter_prefix_from( + kp: KP, + starting_raw_key: Vec, + ) -> crate::storage::PrefixIterator< + (>::Suffix, Value), + OnRemovalCounterUpdate, + > + where + Key: HasReversibleKeyPrefix, + { + ::Map::iter_prefix_from(kp, starting_raw_key).convert_on_removal() + } + + /// Enumerate all suffix keys in the map with prefix key `kp` in no particular order. + /// + /// If you add or remove values whose prefix key is `kp` to the map while doing this, you'll get + /// undefined results. + pub fn iter_key_prefix( + kp: KP, + ) -> crate::storage::KeyPrefixIterator<>::Suffix> + where + Key: HasReversibleKeyPrefix, + { + ::Map::iter_key_prefix(kp) + } + + /// Enumerate all suffix keys in the map with prefix key `kp` after a specified + /// `starting_raw_key` in no particular order. + /// + /// If you add or remove values whose prefix key is `kp` to the map while doing this, you'll get + /// undefined results. + pub fn iter_key_prefix_from( + kp: KP, + starting_raw_key: Vec, + ) -> crate::storage::KeyPrefixIterator<>::Suffix> + where + Key: HasReversibleKeyPrefix, + { + ::Map::iter_key_prefix_from(kp, starting_raw_key) + } + + /// Remove all elements from the map with prefix key `kp` and iterate through them in no + /// particular order. + /// + /// If you add elements with prefix key `k1` to the map while doing this, you'll get undefined + /// results. + pub fn drain_prefix( + kp: KP, + ) -> crate::storage::PrefixIterator< + (>::Suffix, Value), + OnRemovalCounterUpdate, + > + where + Key: HasReversibleKeyPrefix, + { + ::Map::drain_prefix(kp).convert_on_removal() + } + + /// Enumerate all elements in the map in no particular order. + /// + /// If you add or remove values to the map while doing this, you'll get undefined results. + pub fn iter( + ) -> crate::storage::PrefixIterator<(Key::Key, Value), OnRemovalCounterUpdate> { + ::Map::iter().convert_on_removal() + } + + /// Enumerate all elements in the map after a specified `starting_key` in no particular order. + /// + /// If you add or remove values to the map while doing this, you'll get undefined results. + pub fn iter_from( + starting_raw_key: Vec, + ) -> crate::storage::PrefixIterator<(Key::Key, Value), OnRemovalCounterUpdate> { + ::Map::iter_from(starting_raw_key).convert_on_removal() + } + + /// Enumerate all keys in the map in no particular order. + /// + /// If you add or remove values to the map while doing this, you'll get undefined results. + pub fn iter_keys() -> crate::storage::KeyPrefixIterator { + ::Map::iter_keys() + } + + /// Enumerate all keys in the map after a specified `starting_raw_key` in no particular order. + /// + /// If you add or remove values to the map while doing this, you'll get undefined results. + pub fn iter_keys_from( + starting_raw_key: Vec, + ) -> crate::storage::KeyPrefixIterator { + ::Map::iter_keys_from(starting_raw_key) + } + + /// Remove all elements from the map and iterate through them in no particular order. + /// + /// If you add elements to the map while doing this, you'll get undefined results. + pub fn drain( + ) -> crate::storage::PrefixIterator<(Key::Key, Value), OnRemovalCounterUpdate> { + ::Map::drain().convert_on_removal() + } + + /// Translate the values of all elements by a function `f`, in the map in no particular order. + /// + /// By returning `None` from `f` for an element, you'll remove it from the map. + /// + /// NOTE: If a value can't be decoded because the storage is corrupted, then it is skipped. + pub fn translate Option>(mut f: F) { + ::Map::translate(|key, old_value| { + let res = f(key, old_value); + if res.is_none() { + CounterFor::::mutate(|value| value.saturating_dec()); + } + res + }) + } +} + +impl StorageEntryMetadataBuilder + for CountedStorageNMap +where + Prefix: CountedStorageNMapInstance, + Key: super::key::KeyGenerator, + Value: FullCodec + scale_info::StaticTypeInfo, + QueryKind: QueryKindTrait, + OnEmpty: Get + 'static, + MaxValues: Get>, +{ + fn build_metadata(docs: Vec<&'static str>, entries: &mut Vec) { + ::Map::build_metadata(docs, entries); + CounterFor::::build_metadata( + vec![&"Counter for the related counted storage map"], + entries, + ); + } +} + +impl crate::traits::StorageInfoTrait + for CountedStorageNMap +where + Prefix: CountedStorageNMapInstance, + Key: super::key::KeyGenerator + super::key::KeyGeneratorMaxEncodedLen, + Value: FullCodec + MaxEncodedLen, + QueryKind: QueryKindTrait, + OnEmpty: Get + 'static, + MaxValues: Get>, +{ + fn storage_info() -> Vec { + [::Map::storage_info(), CounterFor::::storage_info()].concat() + } +} + +/// It doesn't require to implement `MaxEncodedLen` and give no information for `max_size`. +impl crate::traits::PartialStorageInfoTrait + for CountedStorageNMap +where + Prefix: CountedStorageNMapInstance, + Key: super::key::KeyGenerator, + Value: FullCodec, + QueryKind: QueryKindTrait, + OnEmpty: Get + 'static, + MaxValues: Get>, +{ + fn partial_storage_info() -> Vec { + [ + ::Map::partial_storage_info(), + CounterFor::::partial_storage_info(), + ] + .concat() + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::{ + hash::{StorageHasher as _, *}, + storage::types::{Key as NMapKey, ValueQuery}, + }; + use sp_api::metadata_ir::{StorageEntryModifierIR, StorageEntryTypeIR, StorageHasherIR}; + 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"; + } + impl CountedStorageNMapInstance for Prefix { + type CounterPrefix = Prefix; + } + + struct ADefault; + impl crate::traits::Get for ADefault { + fn get() -> u32 { + 98 + } + } + + #[test] + fn test_1_key() { + type A = CountedStorageNMap, u32, OptionQuery>; + type AValueQueryWithAnOnEmpty = + CountedStorageNMap, u32, ValueQuery, ADefault>; + type B = CountedStorageNMap, u32, ValueQuery>; + type C = CountedStorageNMap, u8, ValueQuery>; + type WithLen = CountedStorageNMap, Vec>; + + TestExternalities::default().execute_with(|| { + let mut k: Vec = vec![]; + k.extend(&twox_128(b"test")); + k.extend(&twox_128(b"Foo")); + k.extend(&3u16.blake2_128_concat()); + assert_eq!(A::hashed_key_for((&3,)).to_vec(), k); + + assert_eq!(A::contains_key((3,)), false); + assert_eq!(A::get((3,)), None); + assert_eq!(AValueQueryWithAnOnEmpty::get((3,)), 98); + assert_eq!(A::count(), 0); + + A::insert((3,), 10); + assert_eq!(A::contains_key((3,)), true); + assert_eq!(A::get((3,)), Some(10)); + assert_eq!(AValueQueryWithAnOnEmpty::get((3,)), 10); + assert_eq!(A::count(), 1); + + A::swap::, _, _>((3,), (2,)); + assert_eq!(A::contains_key((3,)), false); + assert_eq!(A::contains_key((2,)), true); + assert_eq!(A::get((3,)), None); + assert_eq!(AValueQueryWithAnOnEmpty::get((3,)), 98); + assert_eq!(A::get((2,)), Some(10)); + assert_eq!(AValueQueryWithAnOnEmpty::get((2,)), 10); + assert_eq!(A::count(), 1); + + A::remove((2,)); + assert_eq!(A::contains_key((2,)), false); + assert_eq!(A::get((2,)), None); + assert_eq!(A::count(), 0); + + AValueQueryWithAnOnEmpty::mutate((2,), |v| *v = *v * 2); + AValueQueryWithAnOnEmpty::mutate((2,), |v| *v = *v * 2); + assert_eq!(A::contains_key((2,)), true); + assert_eq!(A::get((2,)), Some(98 * 4)); + assert_eq!(A::count(), 1); + + A::remove((2,)); + let _: Result<(), ()> = AValueQueryWithAnOnEmpty::try_mutate((2,), |v| { + *v = *v * 2; + Ok(()) + }); + let _: Result<(), ()> = AValueQueryWithAnOnEmpty::try_mutate((2,), |v| { + *v = *v * 2; + Ok(()) + }); + assert_eq!(A::contains_key((2,)), true); + assert_eq!(A::get((2,)), Some(98 * 4)); + assert_eq!(A::count(), 1); + + A::remove((2,)); + let _: Result<(), ()> = AValueQueryWithAnOnEmpty::try_mutate((2,), |v| { + *v = *v * 2; + Err(()) + }); + assert_eq!(A::contains_key((2,)), false); + assert_eq!(A::count(), 0); + + A::remove((2,)); + AValueQueryWithAnOnEmpty::mutate_exists((2,), |v| { + assert!(v.is_none()); + *v = Some(10); + }); + assert_eq!(A::contains_key((2,)), true); + assert_eq!(A::get((2,)), Some(10)); + AValueQueryWithAnOnEmpty::mutate_exists((2,), |v| { + *v = Some(v.unwrap() * 10); + }); + assert_eq!(A::contains_key((2,)), true); + assert_eq!(A::get((2,)), Some(100)); + assert_eq!(A::count(), 1); + + A::remove((2,)); + let _: Result<(), ()> = AValueQueryWithAnOnEmpty::try_mutate_exists((2,), |v| { + assert!(v.is_none()); + *v = Some(10); + Ok(()) + }); + assert_eq!(A::contains_key((2,)), true); + assert_eq!(A::get((2,)), Some(10)); + let _: Result<(), ()> = AValueQueryWithAnOnEmpty::try_mutate_exists((2,), |v| { + *v = Some(v.unwrap() * 10); + Ok(()) + }); + assert_eq!(A::contains_key((2,)), true); + assert_eq!(A::get((2,)), Some(100)); + assert_eq!(A::try_get((2,)), Ok(100)); + let _: Result<(), ()> = AValueQueryWithAnOnEmpty::try_mutate_exists((2,), |v| { + *v = Some(v.unwrap() * 10); + Err(()) + }); + assert_eq!(A::contains_key((2,)), true); + assert_eq!(A::get((2,)), Some(100)); + assert_eq!(A::count(), 1); + + A::insert((2,), 10); + assert_eq!(A::take((2,)), Some(10)); + assert_eq!(A::contains_key((2,)), false); + assert_eq!(AValueQueryWithAnOnEmpty::take((2,)), 98); + assert_eq!(A::contains_key((2,)), false); + assert_eq!(A::try_get((2,)), Err(())); + assert_eq!(A::count(), 0); + + B::insert((2,), 10); + assert_eq!( + A::migrate_keys((2,), (Box::new(|key| Blake2_256::hash(key).to_vec()),),), + Some(10) + ); + assert_eq!(A::contains_key((2,)), true); + assert_eq!(A::get((2,)), Some(10)); + assert_eq!(A::count(), 1); + + A::insert((3,), 10); + A::insert((4,), 10); + assert_eq!(A::count(), 3); + let _ = A::clear(u32::max_value(), None); + assert!(!A::contains_key((2,)) && !A::contains_key((3,)) && !A::contains_key((4,))); + assert_eq!(A::count(), 0); + + A::insert((3,), 10); + A::insert((4,), 10); + assert_eq!(A::iter_values().collect::>(), vec![10, 10]); + assert_eq!(A::count(), 2); + + C::insert((3,), 10); + C::insert((4,), 10); + A::translate_values::(|v| Some((v * 2).into())); + assert_eq!(A::iter().collect::>(), vec![(4, 20), (3, 20)]); + assert_eq!(A::count(), 2); + + A::insert((3,), 10); + A::insert((4,), 10); + assert_eq!(A::iter().collect::>(), vec![(4, 10), (3, 10)]); + assert_eq!(A::drain().collect::>(), vec![(4, 10), (3, 10)]); + assert_eq!(A::iter().collect::>(), vec![]); + assert_eq!(A::count(), 0); + + C::insert((3,), 10); + C::insert((4,), 10); + A::translate::(|k1, v| Some((k1 as u16 * v as u16).into())); + assert_eq!(A::iter().collect::>(), vec![(4, 40), (3, 30)]); + assert_eq!(A::count(), 2); + + let mut entries = vec![]; + A::build_metadata(vec![], &mut entries); + AValueQueryWithAnOnEmpty::build_metadata(vec![], &mut entries); + assert_eq!( + entries, + vec![ + StorageEntryMetadataIR { + name: "Foo", + modifier: StorageEntryModifierIR::Optional, + ty: StorageEntryTypeIR::Map { + hashers: vec![StorageHasherIR::Blake2_128Concat], + key: scale_info::meta_type::(), + value: scale_info::meta_type::(), + }, + default: Option::::None.encode(), + docs: vec![], + }, + StorageEntryMetadataIR { + name: "Foo", + modifier: StorageEntryModifierIR::Default, + ty: StorageEntryTypeIR::Plain(scale_info::meta_type::()), + default: vec![0, 0, 0, 0], + docs: if cfg!(feature = "no-metadata-docs") { + vec![] + } else { + vec!["Counter for the related counted storage map"] + }, + }, + StorageEntryMetadataIR { + name: "Foo", + modifier: StorageEntryModifierIR::Default, + ty: StorageEntryTypeIR::Map { + hashers: vec![StorageHasherIR::Blake2_128Concat], + key: scale_info::meta_type::(), + value: scale_info::meta_type::(), + }, + default: 98u32.encode(), + docs: vec![], + }, + StorageEntryMetadataIR { + name: "Foo", + modifier: StorageEntryModifierIR::Default, + ty: StorageEntryTypeIR::Plain(scale_info::meta_type::()), + default: vec![0, 0, 0, 0], + docs: if cfg!(feature = "no-metadata-docs") { + vec![] + } else { + vec!["Counter for the related counted storage map"] + }, + }, + ] + ); + + let _ = WithLen::clear(u32::max_value(), None); + assert_eq!(WithLen::decode_len((3,)), None); + WithLen::append((0,), 10); + assert_eq!(WithLen::decode_len((0,)), Some(1)); + }); + } + + #[test] + fn test_2_keys() { + type A = CountedStorageNMap< + Prefix, + (NMapKey, NMapKey), + u32, + OptionQuery, + >; + type AValueQueryWithAnOnEmpty = CountedStorageNMap< + Prefix, + (NMapKey, NMapKey), + u32, + ValueQuery, + ADefault, + >; + type B = CountedStorageNMap< + Prefix, + (NMapKey, NMapKey), + u32, + ValueQuery, + >; + type C = CountedStorageNMap< + Prefix, + (NMapKey, NMapKey), + u8, + ValueQuery, + >; + type WithLen = CountedStorageNMap< + Prefix, + (NMapKey, NMapKey), + Vec, + >; + + TestExternalities::default().execute_with(|| { + let mut k: Vec = vec![]; + k.extend(&twox_128(b"test")); + k.extend(&twox_128(b"Foo")); + k.extend(&3u16.blake2_128_concat()); + k.extend(&30u8.twox_64_concat()); + assert_eq!(A::hashed_key_for((3, 30)).to_vec(), k); + + assert_eq!(A::contains_key((3, 30)), false); + assert_eq!(A::get((3, 30)), None); + assert_eq!(AValueQueryWithAnOnEmpty::get((3, 30)), 98); + assert_eq!(A::count(), 0); + + A::insert((3, 30), 10); + assert_eq!(A::contains_key((3, 30)), true); + assert_eq!(A::get((3, 30)), Some(10)); + assert_eq!(AValueQueryWithAnOnEmpty::get((3, 30)), 10); + assert_eq!(A::count(), 1); + + A::swap::<(NMapKey, NMapKey), _, _>( + (3, 30), + (2, 20), + ); + assert_eq!(A::contains_key((3, 30)), false); + assert_eq!(A::contains_key((2, 20)), true); + assert_eq!(A::get((3, 30)), None); + assert_eq!(AValueQueryWithAnOnEmpty::get((3, 30)), 98); + assert_eq!(A::get((2, 20)), Some(10)); + assert_eq!(AValueQueryWithAnOnEmpty::get((2, 20)), 10); + assert_eq!(A::count(), 1); + + A::remove((2, 20)); + assert_eq!(A::contains_key((2, 20)), false); + assert_eq!(A::get((2, 20)), None); + assert_eq!(A::count(), 0); + + AValueQueryWithAnOnEmpty::mutate((2, 20), |v| *v = *v * 2); + AValueQueryWithAnOnEmpty::mutate((2, 20), |v| *v = *v * 2); + assert_eq!(A::contains_key((2, 20)), true); + assert_eq!(A::get((2, 20)), Some(98 * 4)); + assert_eq!(A::count(), 1); + + A::remove((2, 20)); + let _: Result<(), ()> = AValueQueryWithAnOnEmpty::try_mutate((2, 20), |v| { + *v = *v * 2; + Err(()) + }); + assert_eq!(A::contains_key((2, 20)), false); + assert_eq!(A::count(), 0); + + A::remove((2, 20)); + let _: Result<(), ()> = AValueQueryWithAnOnEmpty::try_mutate((2, 20), |v| { + *v = *v * 2; + Err(()) + }); + assert_eq!(A::contains_key((2, 20)), false); + assert_eq!(A::count(), 0); + + A::remove((2, 20)); + AValueQueryWithAnOnEmpty::mutate_exists((2, 20), |v| { + assert!(v.is_none()); + *v = Some(10); + }); + assert_eq!(A::contains_key((2, 20)), true); + assert_eq!(A::get((2, 20)), Some(10)); + assert_eq!(A::count(), 1); + AValueQueryWithAnOnEmpty::mutate_exists((2, 20), |v| { + *v = Some(v.unwrap() * 10); + }); + assert_eq!(A::contains_key((2, 20)), true); + assert_eq!(A::get((2, 20)), Some(100)); + assert_eq!(A::count(), 1); + + A::remove((2, 20)); + let _: Result<(), ()> = AValueQueryWithAnOnEmpty::try_mutate_exists((2, 20), |v| { + assert!(v.is_none()); + *v = Some(10); + Ok(()) + }); + assert_eq!(A::contains_key((2, 20)), true); + assert_eq!(A::get((2, 20)), Some(10)); + let _: Result<(), ()> = AValueQueryWithAnOnEmpty::try_mutate_exists((2, 20), |v| { + *v = Some(v.unwrap() * 10); + Ok(()) + }); + assert_eq!(A::contains_key((2, 20)), true); + assert_eq!(A::get((2, 20)), Some(100)); + assert_eq!(A::try_get((2, 20)), Ok(100)); + assert_eq!(A::count(), 1); + let _: Result<(), ()> = AValueQueryWithAnOnEmpty::try_mutate_exists((2, 20), |v| { + *v = Some(v.unwrap() * 10); + Err(()) + }); + assert_eq!(A::contains_key((2, 20)), true); + assert_eq!(A::get((2, 20)), Some(100)); + assert_eq!(A::count(), 1); + + A::insert((2, 20), 10); + assert_eq!(A::take((2, 20)), Some(10)); + assert_eq!(A::contains_key((2, 20)), false); + assert_eq!(AValueQueryWithAnOnEmpty::take((2, 20)), 98); + assert_eq!(A::contains_key((2, 20)), false); + assert_eq!(A::try_get((2, 20)), Err(())); + assert_eq!(A::count(), 0); + + B::insert((2, 20), 10); + assert_eq!( + A::migrate_keys( + (2, 20), + ( + Box::new(|key| Blake2_256::hash(key).to_vec()), + Box::new(|key| Twox128::hash(key).to_vec()), + ), + ), + Some(10) + ); + assert_eq!(A::contains_key((2, 20)), true); + assert_eq!(A::get((2, 20)), Some(10)); + assert_eq!(A::count(), 1); + + A::insert((3, 30), 10); + A::insert((4, 40), 10); + assert_eq!(A::count(), 3); + let _ = A::clear(u32::max_value(), None); + // one of the item has been removed + assert!( + !A::contains_key((2, 20)) && !A::contains_key((3, 30)) && !A::contains_key((4, 40)) + ); + assert_eq!(A::count(), 0); + + assert_eq!(A::count(), 0); + + A::insert((3, 30), 10); + A::insert((4, 40), 10); + assert_eq!(A::iter_values().collect::>(), vec![10, 10]); + assert_eq!(A::count(), 2); + + C::insert((3, 30), 10); + C::insert((4, 40), 10); + A::translate_values::(|v| Some((v * 2).into())); + assert_eq!(A::iter().collect::>(), vec![((4, 40), 20), ((3, 30), 20)]); + assert_eq!(A::count(), 2); + + A::insert((3, 30), 10); + A::insert((4, 40), 10); + assert_eq!(A::iter().collect::>(), vec![((4, 40), 10), ((3, 30), 10)]); + assert_eq!(A::drain().collect::>(), vec![((4, 40), 10), ((3, 30), 10)]); + assert_eq!(A::iter().collect::>(), vec![]); + assert_eq!(A::count(), 0); + + C::insert((3, 30), 10); + C::insert((4, 40), 10); + A::translate::(|(k1, k2), v| Some((k1 * k2 as u16 * v as u16).into())); + assert_eq!(A::iter().collect::>(), vec![((4, 40), 1600), ((3, 30), 900)]); + assert_eq!(A::count(), 2); + + let mut entries = vec![]; + A::build_metadata(vec![], &mut entries); + AValueQueryWithAnOnEmpty::build_metadata(vec![], &mut entries); + assert_eq!( + entries, + vec![ + StorageEntryMetadataIR { + name: "Foo", + modifier: StorageEntryModifierIR::Optional, + ty: StorageEntryTypeIR::Map { + hashers: vec![ + StorageHasherIR::Blake2_128Concat, + StorageHasherIR::Twox64Concat + ], + key: scale_info::meta_type::<(u16, u8)>(), + value: scale_info::meta_type::(), + }, + default: Option::::None.encode(), + docs: vec![], + }, + StorageEntryMetadataIR { + name: "Foo", + modifier: StorageEntryModifierIR::Default, + ty: StorageEntryTypeIR::Plain(scale_info::meta_type::()), + default: vec![0, 0, 0, 0], + docs: if cfg!(feature = "no-metadata-docs") { + vec![] + } else { + vec!["Counter for the related counted storage map"] + }, + }, + StorageEntryMetadataIR { + name: "Foo", + modifier: StorageEntryModifierIR::Default, + ty: StorageEntryTypeIR::Map { + hashers: vec![ + StorageHasherIR::Blake2_128Concat, + StorageHasherIR::Twox64Concat + ], + key: scale_info::meta_type::<(u16, u8)>(), + value: scale_info::meta_type::(), + }, + default: 98u32.encode(), + docs: vec![], + }, + StorageEntryMetadataIR { + name: "Foo", + modifier: StorageEntryModifierIR::Default, + ty: StorageEntryTypeIR::Plain(scale_info::meta_type::()), + default: vec![0, 0, 0, 0], + docs: if cfg!(feature = "no-metadata-docs") { + vec![] + } else { + vec!["Counter for the related counted storage map"] + }, + }, + ] + ); + + let _ = WithLen::clear(u32::max_value(), None); + assert_eq!(WithLen::decode_len((3, 30)), None); + WithLen::append((0, 100), 10); + assert_eq!(WithLen::decode_len((0, 100)), Some(1)); + + A::insert((3, 30), 11); + A::insert((3, 31), 12); + A::insert((4, 40), 13); + A::insert((4, 41), 14); + assert_eq!(A::iter_prefix_values((3,)).collect::>(), vec![12, 11]); + assert_eq!(A::iter_prefix_values((4,)).collect::>(), vec![13, 14]); + assert_eq!(A::count(), 5); + }); + } + + #[test] + fn test_3_keys() { + type A = CountedStorageNMap< + Prefix, + ( + NMapKey, + NMapKey, + NMapKey, + ), + u32, + OptionQuery, + >; + type AValueQueryWithAnOnEmpty = CountedStorageNMap< + Prefix, + ( + NMapKey, + NMapKey, + NMapKey, + ), + u32, + ValueQuery, + ADefault, + >; + type B = CountedStorageNMap< + Prefix, + (NMapKey, NMapKey, NMapKey), + u32, + ValueQuery, + >; + type C = CountedStorageNMap< + Prefix, + ( + NMapKey, + NMapKey, + NMapKey, + ), + u8, + ValueQuery, + >; + type WithLen = CountedStorageNMap< + Prefix, + ( + NMapKey, + NMapKey, + NMapKey, + ), + Vec, + >; + + TestExternalities::default().execute_with(|| { + let mut k: Vec = vec![]; + k.extend(&twox_128(b"test")); + k.extend(&twox_128(b"Foo")); + k.extend(&1u16.blake2_128_concat()); + k.extend(&10u16.blake2_128_concat()); + k.extend(&100u16.twox_64_concat()); + assert_eq!(A::hashed_key_for((1, 10, 100)).to_vec(), k); + + assert_eq!(A::contains_key((1, 10, 100)), false); + assert_eq!(A::get((1, 10, 100)), None); + assert_eq!(AValueQueryWithAnOnEmpty::get((1, 10, 100)), 98); + assert_eq!(A::count(), 0); + + A::insert((1, 10, 100), 30); + assert_eq!(A::contains_key((1, 10, 100)), true); + assert_eq!(A::get((1, 10, 100)), Some(30)); + assert_eq!(AValueQueryWithAnOnEmpty::get((1, 10, 100)), 30); + assert_eq!(A::count(), 1); + + A::swap::< + ( + NMapKey, + NMapKey, + NMapKey, + ), + _, + _, + >((1, 10, 100), (2, 20, 200)); + assert_eq!(A::contains_key((1, 10, 100)), false); + assert_eq!(A::contains_key((2, 20, 200)), true); + assert_eq!(A::get((1, 10, 100)), None); + assert_eq!(AValueQueryWithAnOnEmpty::get((1, 10, 100)), 98); + assert_eq!(A::get((2, 20, 200)), Some(30)); + assert_eq!(AValueQueryWithAnOnEmpty::get((2, 20, 200)), 30); + assert_eq!(A::count(), 1); + + A::remove((2, 20, 200)); + assert_eq!(A::contains_key((2, 20, 200)), false); + assert_eq!(A::get((2, 20, 200)), None); + assert_eq!(A::count(), 0); + + AValueQueryWithAnOnEmpty::mutate((2, 20, 200), |v| *v = *v * 2); + AValueQueryWithAnOnEmpty::mutate((2, 20, 200), |v| *v = *v * 2); + assert_eq!(A::contains_key((2, 20, 200)), true); + assert_eq!(A::get((2, 20, 200)), Some(98 * 4)); + assert_eq!(A::count(), 1); + + A::remove((2, 20, 200)); + let _: Result<(), ()> = AValueQueryWithAnOnEmpty::try_mutate((2, 20, 200), |v| { + *v = *v * 2; + Err(()) + }); + assert_eq!(A::contains_key((2, 20, 200)), false); + assert_eq!(A::count(), 0); + + A::remove((2, 20, 200)); + AValueQueryWithAnOnEmpty::mutate_exists((2, 20, 200), |v| { + assert!(v.is_none()); + *v = Some(10); + }); + assert_eq!(A::contains_key((2, 20, 200)), true); + assert_eq!(A::get((2, 20, 200)), Some(10)); + assert_eq!(A::count(), 1); + AValueQueryWithAnOnEmpty::mutate_exists((2, 20, 200), |v| { + *v = Some(v.unwrap() * 10); + }); + assert_eq!(A::contains_key((2, 20, 200)), true); + assert_eq!(A::get((2, 20, 200)), Some(100)); + assert_eq!(A::count(), 1); + + A::remove((2, 20, 200)); + let _: Result<(), ()> = + AValueQueryWithAnOnEmpty::try_mutate_exists((2, 20, 200), |v| { + assert!(v.is_none()); + *v = Some(10); + Ok(()) + }); + assert_eq!(A::contains_key((2, 20, 200)), true); + assert_eq!(A::get((2, 20, 200)), Some(10)); + let _: Result<(), ()> = + AValueQueryWithAnOnEmpty::try_mutate_exists((2, 20, 200), |v| { + *v = Some(v.unwrap() * 10); + Ok(()) + }); + assert_eq!(A::contains_key((2, 20, 200)), true); + assert_eq!(A::get((2, 20, 200)), Some(100)); + assert_eq!(A::try_get((2, 20, 200)), Ok(100)); + assert_eq!(A::count(), 1); + let _: Result<(), ()> = + AValueQueryWithAnOnEmpty::try_mutate_exists((2, 20, 200), |v| { + *v = Some(v.unwrap() * 10); + Err(()) + }); + assert_eq!(A::contains_key((2, 20, 200)), true); + assert_eq!(A::get((2, 20, 200)), Some(100)); + assert_eq!(A::count(), 1); + + A::insert((2, 20, 200), 10); + assert_eq!(A::take((2, 20, 200)), Some(10)); + assert_eq!(A::contains_key((2, 20, 200)), false); + assert_eq!(AValueQueryWithAnOnEmpty::take((2, 20, 200)), 98); + assert_eq!(A::contains_key((2, 20, 200)), false); + assert_eq!(A::try_get((2, 20, 200)), Err(())); + assert_eq!(A::count(), 0); + + B::insert((2, 20, 200), 10); + assert_eq!( + A::migrate_keys( + (2, 20, 200), + ( + Box::new(|key| Blake2_256::hash(key).to_vec()), + Box::new(|key| Blake2_256::hash(key).to_vec()), + Box::new(|key| Twox128::hash(key).to_vec()), + ), + ), + Some(10) + ); + assert_eq!(A::contains_key((2, 20, 200)), true); + assert_eq!(A::get((2, 20, 200)), Some(10)); + assert_eq!(A::count(), 1); + + A::insert((3, 30, 300), 10); + A::insert((4, 40, 400), 10); + assert_eq!(A::count(), 3); + let _ = A::clear(u32::max_value(), None); + // one of the item has been removed + assert!( + !A::contains_key((2, 20, 200)) && + !A::contains_key((3, 30, 300)) && + !A::contains_key((4, 40, 400)) + ); + assert_eq!(A::count(), 0); + + A::insert((3, 30, 300), 10); + A::insert((4, 40, 400), 10); + assert_eq!(A::iter_values().collect::>(), vec![10, 10]); + assert_eq!(A::count(), 2); + + C::insert((3, 30, 300), 10); + C::insert((4, 40, 400), 10); + A::translate_values::(|v| Some((v * 2).into())); + assert_eq!(A::iter().collect::>(), vec![((4, 40, 400), 20), ((3, 30, 300), 20)]); + assert_eq!(A::count(), 2); + + A::insert((3, 30, 300), 10); + A::insert((4, 40, 400), 10); + assert_eq!(A::iter().collect::>(), vec![((4, 40, 400), 10), ((3, 30, 300), 10)]); + assert_eq!( + A::drain().collect::>(), + vec![((4, 40, 400), 10), ((3, 30, 300), 10)] + ); + assert_eq!(A::iter().collect::>(), vec![]); + assert_eq!(A::count(), 0); + + C::insert((3, 30, 300), 10); + C::insert((4, 40, 400), 10); + A::translate::(|(k1, k2, k3), v| { + Some((k1 * k2 as u16 * v as u16 / k3 as u16).into()) + }); + assert_eq!(A::iter().collect::>(), vec![((4, 40, 400), 4), ((3, 30, 300), 3)]); + assert_eq!(A::count(), 2); + + let mut entries = vec![]; + A::build_metadata(vec![], &mut entries); + AValueQueryWithAnOnEmpty::build_metadata(vec![], &mut entries); + assert_eq!( + entries, + vec![ + StorageEntryMetadataIR { + name: "Foo", + modifier: StorageEntryModifierIR::Optional, + ty: StorageEntryTypeIR::Map { + hashers: vec![ + StorageHasherIR::Blake2_128Concat, + StorageHasherIR::Blake2_128Concat, + StorageHasherIR::Twox64Concat + ], + key: scale_info::meta_type::<(u16, u16, u16)>(), + value: scale_info::meta_type::(), + }, + default: Option::::None.encode(), + docs: vec![], + }, + StorageEntryMetadataIR { + name: "Foo", + modifier: StorageEntryModifierIR::Default, + ty: StorageEntryTypeIR::Plain(scale_info::meta_type::()), + default: vec![0, 0, 0, 0], + docs: if cfg!(feature = "no-metadata-docs") { + vec![] + } else { + vec!["Counter for the related counted storage map"] + }, + }, + StorageEntryMetadataIR { + name: "Foo", + modifier: StorageEntryModifierIR::Default, + ty: StorageEntryTypeIR::Map { + hashers: vec![ + StorageHasherIR::Blake2_128Concat, + StorageHasherIR::Blake2_128Concat, + StorageHasherIR::Twox64Concat + ], + key: scale_info::meta_type::<(u16, u16, u16)>(), + value: scale_info::meta_type::(), + }, + default: 98u32.encode(), + docs: vec![], + }, + StorageEntryMetadataIR { + name: "Foo", + modifier: StorageEntryModifierIR::Default, + ty: StorageEntryTypeIR::Plain(scale_info::meta_type::()), + default: vec![0, 0, 0, 0], + docs: if cfg!(feature = "no-metadata-docs") { + vec![] + } else { + vec!["Counter for the related counted storage map"] + }, + }, + ] + ); + + let _ = WithLen::clear(u32::max_value(), None); + assert_eq!(WithLen::decode_len((3, 30, 300)), None); + WithLen::append((0, 100, 1000), 10); + assert_eq!(WithLen::decode_len((0, 100, 1000)), Some(1)); + + A::insert((3, 30, 300), 11); + A::insert((3, 30, 301), 12); + A::insert((4, 40, 400), 13); + A::insert((4, 40, 401), 14); + assert_eq!(A::iter_prefix_values((3,)).collect::>(), vec![11, 12]); + assert_eq!(A::iter_prefix_values((4,)).collect::>(), vec![14, 13]); + assert_eq!(A::iter_prefix_values((3, 30)).collect::>(), vec![11, 12]); + assert_eq!(A::iter_prefix_values((4, 40)).collect::>(), vec![14, 13]); + assert_eq!(A::count(), 5); + }); + } +} diff --git a/substrate/frame/support/src/storage/types/key.rs b/substrate/frame/support/src/storage/types/key.rs index bf87e593b0..ec055aba80 100755 --- a/substrate/frame/support/src/storage/types/key.rs +++ b/substrate/frame/support/src/storage/types/key.rs @@ -37,7 +37,7 @@ pub struct Key(core::marker::PhantomData<(Hasher, KeyType)>); /// A trait that contains the current key as an associated type. pub trait KeyGenerator { type Key: EncodeLike + StaticTypeInfo; - type KArg: Encode; + type KArg: Encode + EncodeLike; type HashFn: FnOnce(&[u8]) -> Vec; type HArg; @@ -196,6 +196,11 @@ impl_encode_like_tuples!(A, B, C, D, E, F, G, H, I, J, K, L, M, O, P); impl_encode_like_tuples!(A, B, C, D, E, F, G, H, I, J, K, L, M, O, P, Q); impl_encode_like_tuples!(A, B, C, D, E, F, G, H, I, J, K, L, M, O, P, Q, R); +impl<'a, T: EncodeLike + EncodeLikeTuple, U: Encode> EncodeLikeTuple + for codec::Ref<'a, T, U> +{ +} + /// Trait to indicate that a tuple can be converted into an iterator of a vector of encoded bytes. pub trait TupleToEncodedIter { fn to_encoded_iter(&self) -> sp_std::vec::IntoIter>; @@ -215,6 +220,15 @@ impl TupleToEncodedIter for &T { } } +impl<'a, T: EncodeLike + TupleToEncodedIter, U: Encode> TupleToEncodedIter + for codec::Ref<'a, T, U> +{ + fn to_encoded_iter(&self) -> sp_std::vec::IntoIter> { + use core::ops::Deref as _; + self.deref().to_encoded_iter() + } +} + /// A trait that indicates the hashers for the keys generated are all reversible. pub trait ReversibleKeyGenerator: KeyGenerator { type ReversibleHasher; diff --git a/substrate/frame/support/src/storage/types/mod.rs b/substrate/frame/support/src/storage/types/mod.rs index 3a5bae2e60..99b0455fc6 100644 --- a/substrate/frame/support/src/storage/types/mod.rs +++ b/substrate/frame/support/src/storage/types/mod.rs @@ -23,6 +23,7 @@ use codec::FullCodec; use sp_std::prelude::*; mod counted_map; +mod counted_nmap; mod double_map; mod key; mod map; @@ -30,6 +31,7 @@ mod nmap; mod value; pub use counted_map::{CountedStorageMap, CountedStorageMapInstance}; +pub use counted_nmap::{CountedStorageNMap, CountedStorageNMapInstance}; pub use double_map::StorageDoubleMap; pub use key::{ EncodeLikeTuple, HasKeyPrefix, HasReversibleKeyPrefix, Key, KeyGenerator, diff --git a/substrate/frame/support/src/storage/types/nmap.rs b/substrate/frame/support/src/storage/types/nmap.rs index 9b63ca7b0f..0fac1fc933 100755 --- a/substrate/frame/support/src/storage/types/nmap.rs +++ b/substrate/frame/support/src/storage/types/nmap.rs @@ -15,8 +15,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Storage map type. Implements StorageDoubleMap, StorageIterableDoubleMap, -//! StoragePrefixedDoubleMap traits and their methods directly. +//! Storage n-map type. Particularly implements `StorageNMap` and `StoragePrefixedMap` +//! traits and their methods directly. use crate::{ metadata_ir::{StorageEntryMetadataIR, StorageEntryTypeIR}, diff --git a/substrate/frame/support/test/tests/pallet.rs b/substrate/frame/support/test/tests/pallet.rs index 99010ffc8d..cb78bded1a 100644 --- a/substrate/frame/support/test/tests/pallet.rs +++ b/substrate/frame/support/test/tests/pallet.rs @@ -368,6 +368,27 @@ pub mod pallet { ResultQuery::NonExistentStorageValue>, >; + #[pallet::storage] + #[pallet::getter(fn counted_nmap)] + pub type CountedNMap = CountedStorageNMap<_, storage::Key, u32>; + + #[pallet::storage] + #[pallet::getter(fn counted_nmap2)] + pub type CountedNMap2 = CountedStorageNMap< + Key = (NMapKey, NMapKey), + Value = u64, + MaxValues = ConstU32<11>, + >; + + #[pallet::storage] + #[pallet::getter(fn counted_nmap3)] + pub type CountedNMap3 = CountedStorageNMap< + _, + (NMapKey, NMapKey), + u128, + ResultQuery::NonExistentStorageValue>, + >; + #[pallet::storage] #[pallet::getter(fn conditional_value)] #[cfg(feature = "frame-feature-testing")] @@ -391,6 +412,15 @@ pub mod pallet { pub type ConditionalNMap = StorageNMap<_, (storage::Key, storage::Key), u32>; + #[cfg(feature = "frame-feature-testing")] + #[pallet::storage] + #[pallet::getter(fn conditional_counted_nmap)] + pub type ConditionalCountedNMap = CountedStorageNMap< + _, + (storage::Key, storage::Key), + u32, + >; + #[pallet::storage] #[pallet::storage_prefix = "RenamedCountedMap"] #[pallet::getter(fn counted_storage_map)] @@ -1125,6 +1155,7 @@ fn storage_expand() { k.extend(2u32.using_encoded(blake2_128_concat)); assert_eq!(unhashed::get::(&k), Some(3u64)); assert_eq!(&k[..32], &>::final_prefix()); + assert_eq!(pallet::Pallet::::nmap2((1, 2)), Some(3u64)); pallet::NMap3::::insert((&1, &2), &3); let mut k = [twox_128(b"Example"), twox_128(b"NMap3")].concat(); @@ -1132,11 +1163,56 @@ fn storage_expand() { k.extend(2u16.using_encoded(twox_64_concat)); assert_eq!(unhashed::get::(&k), Some(3u128)); assert_eq!(&k[..32], &>::final_prefix()); + assert_eq!(pallet::Pallet::::nmap3((1, 2)), Ok(3u128)); assert_eq!( pallet::NMap3::::get((2, 3)), Err(pallet::Error::::NonExistentStorageValue), ); + pallet::CountedNMap::::insert((&1,), &3); + let mut k = [twox_128(b"Example"), twox_128(b"CountedNMap")].concat(); + k.extend(1u8.using_encoded(blake2_128_concat)); + assert_eq!(unhashed::get::(&k), Some(3u32)); + assert_eq!(pallet::CountedNMap::::count(), 1); + assert_eq!( + unhashed::get::( + &[twox_128(b"Example"), twox_128(b"CounterForCountedNMap")].concat() + ), + Some(1u32) + ); + + pallet::CountedNMap2::::insert((&1, &2), &3); + let mut k = [twox_128(b"Example"), twox_128(b"CountedNMap2")].concat(); + k.extend(1u16.using_encoded(twox_64_concat)); + k.extend(2u32.using_encoded(blake2_128_concat)); + assert_eq!(unhashed::get::(&k), Some(3u64)); + assert_eq!(pallet::CountedNMap2::::count(), 1); + assert_eq!( + unhashed::get::( + &[twox_128(b"Example"), twox_128(b"CounterForCountedNMap2")].concat() + ), + Some(1u32) + ); + assert_eq!(pallet::Pallet::::counted_nmap2((1, 2)), Some(3u64)); + + pallet::CountedNMap3::::insert((&1, &2), &3); + let mut k = [twox_128(b"Example"), twox_128(b"CountedNMap3")].concat(); + k.extend(1u8.using_encoded(blake2_128_concat)); + k.extend(2u16.using_encoded(twox_64_concat)); + assert_eq!(pallet::CountedNMap3::::count(), 1); + assert_eq!(unhashed::get::(&k), Some(3u128)); + assert_eq!(pallet::Pallet::::counted_nmap3((1, 2)), Ok(3u128)); + assert_eq!( + pallet::CountedNMap3::::get((2, 3)), + Err(pallet::Error::::NonExistentStorageValue), + ); + assert_eq!( + unhashed::get::( + &[twox_128(b"Example"), twox_128(b"CounterForCountedNMap3")].concat() + ), + Some(1u32) + ); + #[cfg(feature = "frame-feature-testing")] { pallet::ConditionalValue::::put(1); @@ -1457,6 +1533,66 @@ fn metadata() { default: vec![1, 1], docs: vec![], }, + StorageEntryMetadata { + name: "CountedNMap", + modifier: StorageEntryModifier::Optional, + ty: StorageEntryType::Map { + key: meta_type::(), + hashers: vec![StorageHasher::Blake2_128Concat], + value: meta_type::(), + }, + default: vec![0], + docs: vec![], + }, + StorageEntryMetadata { + name: "CounterForCountedNMap", + modifier: StorageEntryModifier::Default, + ty: StorageEntryType::Plain(meta_type::()), + default: vec![0, 0, 0, 0], + docs: maybe_docs(vec!["Counter for the related counted storage map"]), + }, + StorageEntryMetadata { + name: "CountedNMap2", + modifier: StorageEntryModifier::Optional, + ty: StorageEntryType::Map { + key: meta_type::<(u16, u32)>(), + hashers: vec![ + StorageHasher::Twox64Concat, + StorageHasher::Blake2_128Concat, + ], + value: meta_type::(), + }, + default: vec![0], + docs: vec![], + }, + StorageEntryMetadata { + name: "CounterForCountedNMap2", + modifier: StorageEntryModifier::Default, + ty: StorageEntryType::Plain(meta_type::()), + default: vec![0, 0, 0, 0], + docs: maybe_docs(vec!["Counter for the related counted storage map"]), + }, + StorageEntryMetadata { + name: "CountedNMap3", + modifier: StorageEntryModifier::Optional, + ty: StorageEntryType::Map { + key: meta_type::<(u8, u16)>(), + hashers: vec![ + StorageHasher::Blake2_128Concat, + StorageHasher::Twox64Concat, + ], + value: meta_type::(), + }, + default: vec![1, 1], + docs: vec![], + }, + StorageEntryMetadata { + name: "CounterForCountedNMap3", + modifier: StorageEntryModifier::Default, + ty: StorageEntryType::Plain(meta_type::()), + default: vec![0, 0, 0, 0], + docs: maybe_docs(vec!["Counter for the related counted storage map"]), + }, #[cfg(feature = "frame-feature-testing")] StorageEntryMetadata { name: "ConditionalValue", @@ -1507,6 +1643,29 @@ fn metadata() { default: vec![0], docs: vec![], }, + #[cfg(feature = "frame-feature-testing")] + StorageEntryMetadata { + name: "ConditionalCountedNMap", + modifier: StorageEntryModifier::Optional, + ty: StorageEntryType::Map { + key: meta_type::<(u8, u16)>(), + hashers: vec![ + StorageHasher::Blake2_128Concat, + StorageHasher::Twox64Concat, + ], + value: meta_type::(), + }, + default: vec![0], + docs: vec![], + }, + #[cfg(feature = "frame-feature-testing")] + StorageEntryMetadata { + name: "CounterForConditionalCountedNMap", + modifier: StorageEntryModifier::Default, + ty: StorageEntryType::Plain(meta_type::()), + default: vec![0, 0, 0, 0], + docs: maybe_docs(vec!["Counter for the related counted storage map"]), + }, StorageEntryMetadata { name: "RenamedCountedMap", modifier: StorageEntryModifier::Optional, @@ -1873,6 +2032,48 @@ fn test_storage_info() { max_values: None, max_size: Some(16 + 1 + 8 + 2 + 16), }, + StorageInfo { + pallet_name: b"Example".to_vec(), + storage_name: b"CountedNMap".to_vec(), + prefix: prefix(b"Example", b"CountedNMap").to_vec(), + max_values: None, + max_size: Some(16 + 1 + 4), + }, + StorageInfo { + pallet_name: b"Example".to_vec(), + storage_name: b"CounterForCountedNMap".to_vec(), + prefix: prefix(b"Example", b"CounterForCountedNMap").to_vec(), + max_values: Some(1), + max_size: Some(4), + }, + StorageInfo { + pallet_name: b"Example".to_vec(), + storage_name: b"CountedNMap2".to_vec(), + prefix: prefix(b"Example", b"CountedNMap2").to_vec(), + max_values: Some(11), + max_size: Some(8 + 2 + 16 + 4 + 8), + }, + StorageInfo { + pallet_name: b"Example".to_vec(), + storage_name: b"CounterForCountedNMap2".to_vec(), + prefix: prefix(b"Example", b"CounterForCountedNMap2").to_vec(), + max_values: Some(1), + max_size: Some(4), + }, + StorageInfo { + pallet_name: b"Example".to_vec(), + storage_name: b"CountedNMap3".to_vec(), + prefix: prefix(b"Example", b"CountedNMap3").to_vec(), + max_values: None, + max_size: Some(16 + 1 + 8 + 2 + 16), + }, + StorageInfo { + pallet_name: b"Example".to_vec(), + storage_name: b"CounterForCountedNMap3".to_vec(), + prefix: prefix(b"Example", b"CounterForCountedNMap3").to_vec(), + max_values: Some(1), + max_size: Some(4), + }, #[cfg(feature = "frame-feature-testing")] { StorageInfo { @@ -1913,6 +2114,26 @@ fn test_storage_info() { max_size: Some(16 + 1 + 8 + 2 + 4), } }, + #[cfg(feature = "frame-feature-testing")] + { + StorageInfo { + pallet_name: b"Example".to_vec(), + storage_name: b"ConditionalCountedNMap".to_vec(), + prefix: prefix(b"Example", b"ConditionalCountedNMap").to_vec(), + max_values: None, + max_size: Some(16 + 1 + 8 + 2 + 4), + } + }, + #[cfg(feature = "frame-feature-testing")] + { + StorageInfo { + pallet_name: b"Example".to_vec(), + storage_name: b"CounterForConditionalCountedNMap".to_vec(), + prefix: prefix(b"Example", b"CounterForConditionalCountedNMap").to_vec(), + max_values: Some(1), + max_size: Some(4), + } + }, StorageInfo { pallet_name: b"Example".to_vec(), storage_name: b"RenamedCountedMap".to_vec(), diff --git a/substrate/frame/support/test/tests/pallet_ui/storage_not_storage_type.stderr b/substrate/frame/support/test/tests/pallet_ui/storage_not_storage_type.stderr index 223e9cfa3e..3358f00151 100644 --- a/substrate/frame/support/test/tests/pallet_ui/storage_not_storage_type.stderr +++ b/substrate/frame/support/test/tests/pallet_ui/storage_not_storage_type.stderr @@ -1,4 +1,4 @@ -error: Invalid pallet::storage, expected ident: `StorageValue` or `StorageMap` or `CountedStorageMap` or `StorageDoubleMap` or `StorageNMap` in order to expand metadata, found `u8`. +error: Invalid pallet::storage, expected ident: `StorageValue` or `StorageMap` or `CountedStorageMap` or `StorageDoubleMap` or `StorageNMap` or `CountedStorageNMap` in order to expand metadata, found `u8`. --> $DIR/storage_not_storage_type.rs:19:16 | 19 | type Foo = u8;