diff --git a/substrate/frame/support/procedural/src/lib.rs b/substrate/frame/support/procedural/src/lib.rs index 069339a9794c8163eb42cd4e764340336ad12071..6b163ed5d79e34f7c17dc286999eff26dc97394f 100644 --- a/substrate/frame/support/procedural/src/lib.rs +++ b/substrate/frame/support/procedural/src/lib.rs @@ -156,6 +156,9 @@ use proc_macro::TokenStream; /// * \[optional\] `config(#field_name)`: `field_name` is optional if get is set. /// Will include the item in `GenesisConfig`. /// * \[optional\] `build(#closure)`: Closure called with storage overlays. +/// * \[optional\] `max_values(#expr)`: `expr` is an expression returning a `u32`. It is used to +/// implement `StorageInfoTrait`. Note this attribute is not available for storage value as the maximum +/// number of values is 1. /// * `#type`: Storage type. /// * \[optional\] `#default`: Value returned when none. /// @@ -234,11 +237,20 @@ use proc_macro::TokenStream; /// add_extra_genesis { /// config(phantom): std::marker::PhantomData<I>, /// } -/// ... +/// ``` /// /// This adds a field to your `GenesisConfig` with the name `phantom` that you can initialize with /// `Default::default()`. /// +/// ## PoV information +/// +/// To implement the trait `StorageInfoTrait` for storages an additional attribute can be used +/// `generate_storage_info`: +/// ```nocompile +/// decl_storage! { generate_storage_info +/// trait Store for ... +/// } +/// ``` #[proc_macro] pub fn decl_storage(input: TokenStream) -> TokenStream { storage::decl_storage_impl(input) diff --git a/substrate/frame/support/procedural/src/pallet/expand/pallet_struct.rs b/substrate/frame/support/procedural/src/pallet/expand/pallet_struct.rs index 556c6515d470612ef4f5b44cfcf2e14adbd593c3..b655227cfc10ddb80ab77f3cab02014072732113 100644 --- a/substrate/frame/support/procedural/src/pallet/expand/pallet_struct.rs +++ b/substrate/frame/support/procedural/src/pallet/expand/pallet_struct.rs @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::pallet::{Def, parse::helper::get_doc_literals}; +use crate::pallet::{Def, expand::merge_where_clauses, parse::helper::get_doc_literals}; /// * Add derive trait on Pallet /// * Implement GetPalletVersion on Pallet @@ -24,6 +24,7 @@ use crate::pallet::{Def, parse::helper::get_doc_literals}; /// * declare Module type alias for construct_runtime /// * replace the first field type of `struct Pallet` with `PhantomData` if it is `_` /// * implementation of `PalletInfoAccess` information +/// * implementation of `StorageInfoTrait` on Pallet pub fn expand_pallet_struct(def: &mut Def) -> proc_macro2::TokenStream { let frame_support = &def.frame_support; let frame_system = &def.frame_system; @@ -33,6 +34,10 @@ pub fn expand_pallet_struct(def: &mut Def) -> proc_macro2::TokenStream { let pallet_ident = &def.pallet_struct.pallet; let config_where_clause = &def.config.where_clause; + let mut storages_where_clauses = vec![&def.config.where_clause]; + storages_where_clauses.extend(def.storages.iter().map(|storage| &storage.where_clause)); + let storages_where_clauses = merge_where_clauses(&storages_where_clauses); + let pallet_item = { let pallet_module_items = &mut def.item.content.as_mut().expect("Checked by def").1; let item = &mut pallet_module_items[def.pallet_struct.index]; @@ -97,6 +102,41 @@ pub fn expand_pallet_struct(def: &mut Def) -> proc_macro2::TokenStream { ) }; + let storage_info = if let Some(storage_info_span) = def.pallet_struct.generate_storage_info { + let storage_names = &def.storages.iter().map(|storage| &storage.ident).collect::<Vec<_>>(); + let storage_cfg_attrs = &def.storages.iter() + .map(|storage| &storage.cfg_attrs) + .collect::<Vec<_>>(); + + quote::quote_spanned!(storage_info_span => + impl<#type_impl_gen> #frame_support::traits::StorageInfoTrait + for #pallet_ident<#type_use_gen> + #storages_where_clauses + { + fn storage_info() + -> #frame_support::sp_std::vec::Vec<#frame_support::traits::StorageInfo> + { + let mut res = #frame_support::sp_std::vec![]; + + #( + #(#storage_cfg_attrs)* + { + let mut storage_info = < + #storage_names<#type_use_gen> + as #frame_support::traits::StorageInfoTrait + >::storage_info(); + res.append(&mut storage_info); + } + )* + + res + } + } + ) + } else { + Default::default() + }; + quote::quote_spanned!(def.pallet_struct.attr_span => #module_error_metadata @@ -157,5 +197,7 @@ pub fn expand_pallet_struct(def: &mut Def) -> proc_macro2::TokenStream { implemented by the runtime") } } + + #storage_info ) } diff --git a/substrate/frame/support/procedural/src/pallet/parse/pallet_struct.rs b/substrate/frame/support/procedural/src/pallet/parse/pallet_struct.rs index 6c2c90bd61a5f40cbaf9999d6ae8d806d2b626e7..ba85da2d9e6843380668466097229a262318be23 100644 --- a/substrate/frame/support/procedural/src/pallet/parse/pallet_struct.rs +++ b/substrate/frame/support/procedural/src/pallet/parse/pallet_struct.rs @@ -24,6 +24,7 @@ mod keyword { syn::custom_keyword!(pallet); syn::custom_keyword!(Pallet); syn::custom_keyword!(generate_store); + syn::custom_keyword!(generate_storage_info); syn::custom_keyword!(Store); } @@ -39,12 +40,30 @@ pub struct PalletStructDef { pub store: Option<(syn::Visibility, keyword::Store)>, /// The span of the pallet::pallet attribute. pub attr_span: proc_macro2::Span, + /// Whether to specify the storages max encoded len when implementing `StorageInfoTrait`. + /// Contains the span of the attribute. + pub generate_storage_info: Option<proc_macro2::Span>, } -/// Parse for `#[pallet::generate_store($vis trait Store)]` -pub struct PalletStructAttr { - vis: syn::Visibility, - keyword: keyword::Store, +/// Parse for one variant of: +/// * `#[pallet::generate_store($vis trait Store)]` +/// * `#[pallet::generate_storage_info]` +pub enum PalletStructAttr { + GenerateStore { + span: proc_macro2::Span, + vis: syn::Visibility, + keyword: keyword::Store, + }, + GenerateStorageInfoTrait(proc_macro2::Span), +} + +impl PalletStructAttr { + fn span(&self) -> proc_macro2::Span { + match self { + Self::GenerateStore { span, .. } => *span, + Self::GenerateStorageInfoTrait(span) => *span, + } + } } impl syn::parse::Parse for PalletStructAttr { @@ -54,14 +73,23 @@ impl syn::parse::Parse for PalletStructAttr { syn::bracketed!(content in input); content.parse::<keyword::pallet>()?; content.parse::<syn::Token![::]>()?; - content.parse::<keyword::generate_store>()?; - - let generate_content; - syn::parenthesized!(generate_content in content); - let vis = generate_content.parse::<syn::Visibility>()?; - generate_content.parse::<syn::Token![trait]>()?; - let keyword = generate_content.parse::<keyword::Store>()?; - Ok(Self { vis, keyword }) + + let lookahead = content.lookahead1(); + if lookahead.peek(keyword::generate_store) { + let span = content.parse::<keyword::generate_store>()?.span(); + + let generate_content; + syn::parenthesized!(generate_content in content); + let vis = generate_content.parse::<syn::Visibility>()?; + generate_content.parse::<syn::Token![trait]>()?; + let keyword = generate_content.parse::<keyword::Store>()?; + Ok(Self::GenerateStore { vis, keyword, span }) + } else if lookahead.peek(keyword::generate_storage_info) { + let span = content.parse::<keyword::generate_storage_info>()?.span(); + Ok(Self::GenerateStorageInfoTrait(span)) + } else { + Err(lookahead.error()) + } } } @@ -78,12 +106,24 @@ impl PalletStructDef { return Err(syn::Error::new(item.span(), msg)); }; - let mut event_attrs: Vec<PalletStructAttr> = helper::take_item_pallet_attrs(&mut item.attrs)?; - if event_attrs.len() > 1 { - let msg = "Invalid pallet::pallet, multiple argument pallet::generate_store found"; - return Err(syn::Error::new(event_attrs[1].keyword.span(), msg)); + let mut store = None; + let mut generate_storage_info = None; + + let struct_attrs: Vec<PalletStructAttr> = helper::take_item_pallet_attrs(&mut item.attrs)?; + for attr in struct_attrs { + match attr { + PalletStructAttr::GenerateStore { vis, keyword, .. } if store.is_none() => { + store = Some((vis, keyword)); + }, + PalletStructAttr::GenerateStorageInfoTrait(span) if generate_storage_info.is_none() => { + generate_storage_info = Some(span); + }, + attr => { + let msg = "Unexpected duplicated attribute"; + return Err(syn::Error::new(attr.span(), msg)); + }, + } } - let store = event_attrs.pop().map(|attr| (attr.vis, attr.keyword)); let pallet = syn::parse2::<keyword::Pallet>(item.ident.to_token_stream())?; @@ -100,6 +140,6 @@ impl PalletStructDef { let mut instances = vec![]; instances.push(helper::check_type_def_gen_no_bounds(&item.generics, item.ident.span())?); - Ok(Self { index, instances, pallet, store, attr_span }) + Ok(Self { index, instances, pallet, store, attr_span, generate_storage_info }) } } diff --git a/substrate/frame/support/procedural/src/storage/mod.rs b/substrate/frame/support/procedural/src/storage/mod.rs index 71bcf704f0d73b597282af8f3dfd4ce4b781b389..3a1915e43144dc98331d05f90f706b19d2e7e9a6 100644 --- a/substrate/frame/support/procedural/src/storage/mod.rs +++ b/substrate/frame/support/procedural/src/storage/mod.rs @@ -18,6 +18,7 @@ //! `decl_storage` input definition and expansion. mod storage_struct; +mod storage_info; mod parse; mod store_trait; mod getters; @@ -35,6 +36,8 @@ use frame_support_procedural_tools::{ /// All information contained in input of decl_storage pub struct DeclStorageDef { + /// Whether to generate the storage info + generate_storage_info: bool, /// Name of the module used to import hidden imports. hidden_crate: Option<syn::Ident>, /// Visibility of store trait. @@ -69,6 +72,8 @@ impl syn::parse::Parse for DeclStorageDef { /// Extended version of `DeclStorageDef` with useful precomputed value. pub struct DeclStorageDefExt { + /// Whether to generate the storage info + generate_storage_info: bool, /// Name of the module used to import hidden imports. hidden_crate: proc_macro2::TokenStream, /// Hidden imports used by the module. @@ -154,6 +159,7 @@ impl From<DeclStorageDef> for DeclStorageDefExt { Self { hidden_crate, hidden_imports, + generate_storage_info: def.generate_storage_info, visibility: def.visibility, store_trait: def.store_trait, module_name: def.module_name, @@ -193,6 +199,8 @@ pub struct StorageLineDef { getter: Option<syn::Ident>, /// The name of the field to be used in genesis config if any. config: Option<syn::Ident>, + /// The given max values with `max_values` attribute, or a none if not specified. + max_values: Option<syn::Expr>, /// The build function of the storage if any. build: Option<syn::Expr>, /// Default value of genesis config field and also for storage when no value available. @@ -210,6 +218,8 @@ pub struct StorageLineDefExt { getter: Option<syn::Ident>, /// The name of the field to be used in genesis config if any. config: Option<syn::Ident>, + /// The given max values with `max_values` attribute, or a none if not specified. + max_values: Option<syn::Expr>, /// The build function of the storage if any. build: Option<syn::Expr>, /// Default value of genesis config field and also for storage when no value available. @@ -333,6 +343,7 @@ impl StorageLineDefExt { name: storage_def.name, getter: storage_def.getter, config: storage_def.config, + max_values: storage_def.max_values, build: storage_def.build, default_value: storage_def.default_value, storage_type: storage_def.storage_type, @@ -469,6 +480,7 @@ pub fn decl_storage_impl(input: proc_macro::TokenStream) -> proc_macro::TokenStr let instance_trait = instance_trait::decl_and_impl(&def_ext); let genesis_config = genesis_config::genesis_config_and_build_storage(&def_ext); let storage_struct = storage_struct::decl_and_impl(&def_ext); + let storage_info = storage_info::impl_storage_info(&def_ext); quote!( use #scrate::{ @@ -489,5 +501,6 @@ pub fn decl_storage_impl(input: proc_macro::TokenStream) -> proc_macro::TokenStr #instance_trait #genesis_config #storage_struct + #storage_info ).into() } diff --git a/substrate/frame/support/procedural/src/storage/parse.rs b/substrate/frame/support/procedural/src/storage/parse.rs index 93a1b844a84a269607328a097bc3b0768a8db77c..ca97b7957c108bdfa5b9ea7e51d4ffd693b7e4ef 100644 --- a/substrate/frame/support/procedural/src/storage/parse.rs +++ b/substrate/frame/support/procedural/src/storage/parse.rs @@ -21,10 +21,12 @@ use frame_support_procedural_tools::{ToTokens, Parse, syn_ext as ext}; use syn::{Ident, Token, spanned::Spanned}; mod keyword { + syn::custom_keyword!(generate_storage_info); syn::custom_keyword!(hiddencrate); syn::custom_keyword!(add_extra_genesis); syn::custom_keyword!(extra_genesis_skip_phantom_data_field); syn::custom_keyword!(config); + syn::custom_keyword!(max_values); syn::custom_keyword!(build); syn::custom_keyword!(get); syn::custom_keyword!(map); @@ -73,6 +75,7 @@ macro_rules! impl_parse_for_opt { /// Parsing usage only #[derive(Parse, ToTokens, Debug)] struct StorageDefinition { + pub generate_storage_info: Opt<GenerateStorageInfo>, pub hidden_crate: Opt<SpecificHiddenCrate>, pub visibility: syn::Visibility, pub trait_token: Token![trait], @@ -97,6 +100,12 @@ struct StorageDefinition { pub extra_genesis: Opt<AddExtraGenesis>, } +#[derive(Parse, ToTokens, Debug)] +struct GenerateStorageInfo { + pub keyword: keyword::generate_storage_info, +} +impl_parse_for_opt!(GenerateStorageInfo => keyword::generate_storage_info); + #[derive(Parse, ToTokens, Debug)] struct SpecificHiddenCrate { pub keyword: keyword::hiddencrate, @@ -160,6 +169,7 @@ struct DeclStorageLine { pub name: Ident, pub getter: Opt<DeclStorageGetter>, pub config: Opt<DeclStorageConfig>, + pub max_values: Opt<DeclStorageMaxValues>, pub build: Opt<DeclStorageBuild>, pub coldot_token: Token![:], pub storage_type: DeclStorageType, @@ -188,6 +198,13 @@ struct DeclStorageConfig { impl_parse_for_opt!(DeclStorageConfig => keyword::config); +#[derive(Parse, ToTokens, Debug)] +struct DeclStorageMaxValues { + pub max_values_keyword: keyword::max_values, + pub expr: ext::Parens<syn::Expr>, +} +impl_parse_for_opt!(DeclStorageMaxValues => keyword::max_values); + #[derive(Parse, ToTokens, Debug)] struct DeclStorageBuild { pub build_keyword: keyword::build, @@ -437,6 +454,7 @@ pub fn parse(input: syn::parse::ParseStream) -> syn::Result<super::DeclStorageDe let storage_lines = parse_storage_line_defs(def.content.content.inner.into_iter())?; Ok(super::DeclStorageDef { + generate_storage_info: def.generate_storage_info.inner.is_some(), hidden_crate: def.hidden_crate.inner.map(|i| i.ident.content), visibility: def.visibility, module_name: def.module_ident, @@ -490,6 +508,21 @@ fn parse_storage_line_defs( })?; } + let max_values = match &line.storage_type { + DeclStorageType::Map(_) | DeclStorageType::DoubleMap(_) | DeclStorageType::NMap(_) => { + line.max_values.inner.map(|i| i.expr.content) + }, + DeclStorageType::Simple(_) => { + if let Some(max_values) = line.max_values.inner { + let msg = "unexpected max_values attribute for storage value."; + let span = max_values.max_values_keyword.span(); + return Err(syn::Error::new(span, msg)); + } else { + Some(syn::parse_quote!(1u32)) + } + }, + }; + let span = line.storage_type.span(); let no_hasher_error = || syn::Error::new( span, @@ -534,6 +567,7 @@ fn parse_storage_line_defs( name: line.name, getter, config, + max_values, build: line.build.inner.map(|o| o.expr.content), default_value: line.default_value.inner.map(|o| o.expr), storage_type, diff --git a/substrate/frame/support/procedural/src/storage/storage_info.rs b/substrate/frame/support/procedural/src/storage/storage_info.rs new file mode 100644 index 0000000000000000000000000000000000000000..ed07ccbfc71d6431ea56a84fed7370e8f4430535 --- /dev/null +++ b/substrate/frame/support/procedural/src/storage/storage_info.rs @@ -0,0 +1,57 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2021 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. + +//! Implementation of trait `StorageInfoTrait` on module structure. + +use proc_macro2::TokenStream; +use quote::quote; +use super::DeclStorageDefExt; + +pub fn impl_storage_info(def: &DeclStorageDefExt) -> TokenStream { + if !def.generate_storage_info { + return Default::default() + } + + let scrate = &def.hidden_crate; + + let mut res_append_storage = TokenStream::new(); + + for line in def.storage_lines.iter() { + let storage_struct = &line.storage_struct; + + res_append_storage.extend(quote!( + let mut storage_info = < + #storage_struct as #scrate::traits::StorageInfoTrait + >::storage_info(); + res.append(&mut storage_info); + )); + } + + let module_struct = &def.module_struct; + let module_impl = &def.module_impl; + let where_clause = &def.where_clause; + + quote!( + impl#module_impl #scrate::traits::StorageInfoTrait for #module_struct #where_clause { + fn storage_info() -> #scrate::sp_std::vec::Vec<#scrate::traits::StorageInfo> { + let mut res = #scrate::sp_std::vec![]; + #res_append_storage + res + } + } + ) +} diff --git a/substrate/frame/support/procedural/src/storage/storage_struct.rs b/substrate/frame/support/procedural/src/storage/storage_struct.rs index 51b55bdc4f139f967563250c8f3b76fbd9f93bb7..c1af0ee0701fbb7ac2e41218f6dce28e907acfa9 100644 --- a/substrate/frame/support/procedural/src/storage/storage_struct.rs +++ b/substrate/frame/support/procedural/src/storage/storage_struct.rs @@ -245,9 +245,167 @@ pub fn decl_and_impl(def: &DeclStorageDefExt) -> TokenStream { } }; + let max_values = if let Some(max_values) = &line.max_values { + quote::quote!({ + let max_values: u32 = (|| #max_values)(); + Some(max_values) + }) + } else { + quote::quote!(None) + }; + + let storage_info_impl = if def.generate_storage_info { + match &line.storage_type { + StorageLineTypeDef::Simple(_) => { + quote!( + impl<#impl_trait> #scrate::traits::StorageInfoTrait for #storage_struct + #optional_storage_where_clause + { + fn storage_info() + -> #scrate::sp_std::vec::Vec<#scrate::traits::StorageInfo> + { + use #scrate::sp_runtime::SaturatedConversion; + + let max_size = < + #value_type as #scrate::traits::MaxEncodedLen + >::max_encoded_len() + .saturated_into(); + + #scrate::sp_std::vec![ + #scrate::traits::StorageInfo { + prefix: < + #storage_struct as #scrate::#storage_generator_trait + >::storage_value_final_key(), + max_values: Some(1), + max_size: Some(max_size), + } + ] + } + } + ) + }, + StorageLineTypeDef::Map(map) => { + let key = &map.key; + quote!( + impl<#impl_trait> #scrate::traits::StorageInfoTrait for #storage_struct + #optional_storage_where_clause + { + fn storage_info() + -> #scrate::sp_std::vec::Vec<#scrate::traits::StorageInfo> + { + use #scrate::sp_runtime::SaturatedConversion; + use #scrate::StorageHasher; + + let key_max_size = < + Self as #scrate::storage::generator::StorageMap<_, _> + >::Hasher::max_len::<#key>(); + + let max_size = < + #value_type as #scrate::traits::MaxEncodedLen + >::max_encoded_len() + .saturating_add(key_max_size) + .saturated_into(); + + #scrate::sp_std::vec![ + #scrate::traits::StorageInfo { + prefix: < + #storage_struct + as #scrate::storage::StoragePrefixedMap<#value_type> + >::final_prefix(), + max_values: #max_values, + max_size: Some(max_size), + } + ] + } + } + ) + }, + StorageLineTypeDef::DoubleMap(map) => { + let key1 = &map.key1; + let key2 = &map.key2; + quote!( + impl<#impl_trait> #scrate::traits::StorageInfoTrait for #storage_struct + #optional_storage_where_clause + { + fn storage_info() + -> #scrate::sp_std::vec::Vec<#scrate::traits::StorageInfo> + { + use #scrate::sp_runtime::SaturatedConversion; + use #scrate::StorageHasher; + + let key1_max_size = < + Self as #scrate::storage::generator::StorageDoubleMap<_, _, _> + >::Hasher1::max_len::<#key1>(); + + let key2_max_size = < + Self as #scrate::storage::generator::StorageDoubleMap<_, _, _> + >::Hasher2::max_len::<#key2>(); + + let max_size = < + #value_type as #scrate::traits::MaxEncodedLen + >::max_encoded_len() + .saturating_add(key1_max_size) + .saturating_add(key2_max_size) + .saturated_into(); + + #scrate::sp_std::vec![ + #scrate::traits::StorageInfo { + prefix: < + #storage_struct + as #scrate::storage::StoragePrefixedMap<#value_type> + >::final_prefix(), + max_values: #max_values, + max_size: Some(max_size), + } + ] + } + } + ) + }, + StorageLineTypeDef::NMap(map) => { + let key = &map.to_keygen_struct(scrate); + quote!( + impl<#impl_trait> #scrate::traits::StorageInfoTrait for #storage_struct + #optional_storage_where_clause + { + fn storage_info() + -> #scrate::sp_std::vec::Vec<#scrate::traits::StorageInfo> + { + use #scrate::sp_runtime::SaturatedConversion; + + let key_max_size = < + #key as #scrate::storage::types::KeyGeneratorMaxEncodedLen + >::key_max_encoded_len(); + + let max_size = < + #value_type as #scrate::traits::MaxEncodedLen + >::max_encoded_len() + .saturating_add(key_max_size) + .saturated_into(); + + #scrate::sp_std::vec![ + #scrate::traits::StorageInfo { + prefix: < + #storage_struct + as #scrate::storage::StoragePrefixedMap<#value_type> + >::final_prefix(), + max_values: #max_values, + max_size: Some(max_size), + } + ] + } + } + ) + }, + } + } else { + TokenStream::default() + }; + impls.extend(quote!( #struct_decl #struct_impl + #storage_info_impl )) } diff --git a/substrate/frame/support/src/hash.rs b/substrate/frame/support/src/hash.rs index 22ccbeb6ceee370508cf47826e973b4ea9ffe7ba..5c4bfb34f5f963244f38250b8c5db7fe4d55676c 100644 --- a/substrate/frame/support/src/hash.rs +++ b/substrate/frame/support/src/hash.rs @@ -20,6 +20,7 @@ use codec::Codec; use sp_std::prelude::Vec; use sp_io::hashing::{blake2_128, blake2_256, twox_64, twox_128, twox_256}; +use crate::traits::MaxEncodedLen; // This trait must be kept coherent with frame-support-procedural HasherKind usage pub trait Hashable: Sized { @@ -59,6 +60,9 @@ pub trait StorageHasher: 'static { const METADATA: frame_metadata::StorageHasher; type Output: AsRef<[u8]>; fn hash(x: &[u8]) -> Self::Output; + + /// The max length of the final hash, for the given key type. + fn max_len<K: MaxEncodedLen>() -> usize; } /// Hasher to use to hash keys to insert to storage. @@ -79,6 +83,9 @@ impl StorageHasher for Identity { fn hash(x: &[u8]) -> Vec<u8> { x.to_vec() } + fn max_len<K: MaxEncodedLen>() -> usize { + K::max_encoded_len() + } } impl ReversibleStorageHasher for Identity { fn reverse(x: &[u8]) -> &[u8] { @@ -98,6 +105,9 @@ impl StorageHasher for Twox64Concat { .cloned() .collect::<Vec<_>>() } + fn max_len<K: MaxEncodedLen>() -> usize { + K::max_encoded_len().saturating_add(8) + } } impl ReversibleStorageHasher for Twox64Concat { fn reverse(x: &[u8]) -> &[u8] { @@ -121,6 +131,9 @@ impl StorageHasher for Blake2_128Concat { .cloned() .collect::<Vec<_>>() } + fn max_len<K: MaxEncodedLen>() -> usize { + K::max_encoded_len().saturating_add(16) + } } impl ReversibleStorageHasher for Blake2_128Concat { fn reverse(x: &[u8]) -> &[u8] { @@ -140,6 +153,9 @@ impl StorageHasher for Blake2_128 { fn hash(x: &[u8]) -> [u8; 16] { blake2_128(x) } + fn max_len<K: MaxEncodedLen>() -> usize { + 16 + } } /// Hash storage keys with blake2 256 @@ -150,6 +166,9 @@ impl StorageHasher for Blake2_256 { fn hash(x: &[u8]) -> [u8; 32] { blake2_256(x) } + fn max_len<K: MaxEncodedLen>() -> usize { + 32 + } } /// Hash storage keys with twox 128 @@ -160,6 +179,9 @@ impl StorageHasher for Twox128 { fn hash(x: &[u8]) -> [u8; 16] { twox_128(x) } + fn max_len<K: MaxEncodedLen>() -> usize { + 16 + } } /// Hash storage keys with twox 256 @@ -170,6 +192,9 @@ impl StorageHasher for Twox256 { fn hash(x: &[u8]) -> [u8; 32] { twox_256(x) } + fn max_len<K: MaxEncodedLen>() -> usize { + 32 + } } #[cfg(test)] @@ -187,4 +212,17 @@ mod tests { let r = Blake2_128Concat::hash(b"foo"); assert_eq!(r.split_at(16), (&blake2_128(b"foo")[..], &b"foo"[..])) } + + #[test] + fn max_lengths() { + use codec::Encode; + let encoded_0u32 = &0u32.encode()[..]; + assert_eq!(Twox64Concat::hash(encoded_0u32).len(), Twox64Concat::max_len::<u32>()); + assert_eq!(Twox128::hash(encoded_0u32).len(), Twox128::max_len::<u32>()); + assert_eq!(Twox256::hash(encoded_0u32).len(), Twox256::max_len::<u32>()); + assert_eq!(Blake2_128::hash(encoded_0u32).len(), Blake2_128::max_len::<u32>()); + assert_eq!(Blake2_128Concat::hash(encoded_0u32).len(), Blake2_128Concat::max_len::<u32>()); + assert_eq!(Blake2_256::hash(encoded_0u32).len(), Blake2_256::max_len::<u32>()); + assert_eq!(Identity::hash(encoded_0u32).len(), Identity::max_len::<u32>()); + } } diff --git a/substrate/frame/support/src/lib.rs b/substrate/frame/support/src/lib.rs index d87ab8e6ed4606f7f2e068a7aa31fc8648d7225a..0f96cdd0231953560215c9752861775fada2ae95 100644 --- a/substrate/frame/support/src/lib.rs +++ b/substrate/frame/support/src/lib.rs @@ -1234,7 +1234,10 @@ pub mod pallet_prelude { EqNoBound, PartialEqNoBound, RuntimeDebugNoBound, DebugNoBound, CloneNoBound, Twox256, Twox128, Blake2_256, Blake2_128, Identity, Twox64Concat, Blake2_128Concat, ensure, RuntimeDebug, storage, - traits::{Get, Hooks, IsType, GetPalletVersion, EnsureOrigin, PalletInfoAccess}, + traits::{ + Get, Hooks, IsType, GetPalletVersion, EnsureOrigin, PalletInfoAccess, StorageInfoTrait, + ConstU32, GetDefault, + }, dispatch::{DispatchResultWithPostInfo, Parameter, DispatchError, DispatchResult}, weights::{DispatchClass, Pays, Weight}, storage::types::{ @@ -1346,6 +1349,17 @@ pub mod pallet_prelude { /// Thus when defining a storage named `Foo`, it can later be accessed from `Pallet` using /// `<Pallet as Store>::Foo`. /// +/// To generate the full storage info (used for PoV calculation) use the attribute +/// `#[pallet::set_storage_max_encoded_len]`, e.g.: +/// ```ignore +/// #[pallet::pallet] +/// #[pallet::set_storage_max_encoded_len] +/// pub struct Pallet<T>(_); +/// ``` +/// +/// This require all storage to implement the trait [`traits::StorageInfoTrait`], thus all keys +/// and value types must bound [`traits::MaxEncodedLen`]. +/// /// ### Macro expansion: /// /// The macro add this attribute to the struct definition: @@ -1370,7 +1384,14 @@ pub mod pallet_prelude { /// given by [`frame_support::traits::PalletInfo`]. /// (The implementation use the associated type `frame_system::Config::PalletInfo`). /// -/// If attribute generate_store then macro create the trait `Store` and implement it on `Pallet`. +/// It implements [`traits::StorageInfoTrait`] on `Pallet` which give information about all storages. +/// +/// If the attribute generate_store is set then the macro creates the trait `Store` and implements +/// it on `Pallet`. +/// +/// If the attribute set_storage_max_encoded_len is set then the macro call +/// [`traits::StorageInfoTrait`] for each storage in the implementation of +/// [`traits::StorageInfoTrait`] for the pallet. /// /// # Hooks: `#[pallet::hooks]` mandatory /// diff --git a/substrate/frame/support/src/storage/types/double_map.rs b/substrate/frame/support/src/storage/types/double_map.rs index 70b0c19f76246401397262fd2fe08e9995fd92ba..8c23354817f4ef117c3f2b82460f4c9d6d65784a 100644 --- a/substrate/frame/support/src/storage/types/double_map.rs +++ b/substrate/frame/support/src/storage/types/double_map.rs @@ -21,14 +21,15 @@ use codec::{Decode, Encode, EncodeLike, FullCodec}; use crate::{ storage::{ - StorageAppend, StorageDecodeLength, + StorageAppend, StorageDecodeLength, StoragePrefixedMap, bounded_vec::BoundedVec, types::{OptionQuery, QueryKindTrait, OnEmptyGetter}, }, - traits::{GetDefault, StorageInstance, Get}, + traits::{GetDefault, StorageInstance, Get, MaxEncodedLen, StorageInfo}, }; use frame_metadata::{DefaultByteGetter, StorageEntryModifier}; -use sp_std::vec::Vec; +use sp_arithmetic::traits::SaturatedConversion; +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. @@ -47,14 +48,24 @@ use sp_std::vec::Vec; /// such as `blake2_128_concat` must be used for Hasher1 (resp. Hasher2). Otherwise, other values /// in storage can be compromised. pub struct StorageDoubleMap< - Prefix, Hasher1, Key1, Hasher2, Key2, Value, QueryKind=OptionQuery, OnEmpty=GetDefault + Prefix, + Hasher1, + Key1, + Hasher2, + Key2, + Value, + QueryKind=OptionQuery, + OnEmpty=GetDefault, + MaxValues=GetDefault, >( - core::marker::PhantomData<(Prefix, Hasher1, Key1, Hasher2, Key2, Value, QueryKind, OnEmpty)> + core::marker::PhantomData< + (Prefix, Hasher1, Key1, Hasher2, Key2, Value, QueryKind, OnEmpty, MaxValues) + > ); -impl<Prefix, Hasher1, Key1, Hasher2, Key2, Value, QueryKind, OnEmpty> +impl<Prefix, Hasher1, Key1, Hasher2, Key2, Value, QueryKind, OnEmpty, MaxValues> crate::storage::generator::StorageDoubleMap<Key1, Key2, Value> for - StorageDoubleMap<Prefix, Hasher1, Key1, Hasher2, Key2, Value, QueryKind, OnEmpty> + StorageDoubleMap<Prefix, Hasher1, Key1, Hasher2, Key2, Value, QueryKind, OnEmpty, MaxValues> where Prefix: StorageInstance, Hasher1: crate::hash::StorageHasher, @@ -63,7 +74,8 @@ where Key2: FullCodec, Value: FullCodec, QueryKind: QueryKindTrait<Value, OnEmpty>, - OnEmpty: crate::traits::Get<QueryKind::Query> + 'static + OnEmpty: Get<QueryKind::Query> + 'static, + MaxValues: Get<Option<u32>>, { type Query = QueryKind::Query; type Hasher1 = Hasher1; @@ -82,9 +94,9 @@ where } } -impl<Prefix, Hasher1, Key1, Hasher2, Key2, Value, QueryKind, OnEmpty> - crate::storage::StoragePrefixedMap<Value> for - StorageDoubleMap<Prefix, Hasher1, Key1, Hasher2, Key2, Value, QueryKind, OnEmpty> +impl<Prefix, Hasher1, Key1, Hasher2, Key2, Value, QueryKind, OnEmpty, MaxValues> + StoragePrefixedMap<Value> for + StorageDoubleMap<Prefix, Hasher1, Key1, Hasher2, Key2, Value, QueryKind, OnEmpty, MaxValues> where Prefix: StorageInstance, Hasher1: crate::hash::StorageHasher, @@ -93,7 +105,8 @@ where Key2: FullCodec, Value: FullCodec, QueryKind: QueryKindTrait<Value, OnEmpty>, - OnEmpty: crate::traits::Get<QueryKind::Query> + 'static + OnEmpty: Get<QueryKind::Query> + 'static, + MaxValues: Get<Option<u32>>, { fn module_prefix() -> &'static [u8] { <Self as crate::storage::generator::StorageDoubleMap<Key1, Key2, Value>>::module_prefix() @@ -103,7 +116,7 @@ where } } -impl<Prefix, Hasher1, Key1, Hasher2, Key2, QueryKind, OnEmpty, VecValue, VecBound> +impl<Prefix, Hasher1, Key1, Hasher2, Key2, QueryKind, OnEmpty, MaxValues, VecValue, VecBound> StorageDoubleMap< Prefix, Hasher1, @@ -113,6 +126,7 @@ impl<Prefix, Hasher1, Key1, Hasher2, Key2, QueryKind, OnEmpty, VecValue, VecBoun BoundedVec<VecValue, VecBound>, QueryKind, OnEmpty, + MaxValues, > where Prefix: StorageInstance, Hasher1: crate::hash::StorageHasher, @@ -120,7 +134,8 @@ impl<Prefix, Hasher1, Key1, Hasher2, Key2, QueryKind, OnEmpty, VecValue, VecBoun Key1: FullCodec, Key2: FullCodec, QueryKind: QueryKindTrait<BoundedVec<VecValue, VecBound>, OnEmpty>, - OnEmpty: crate::traits::Get<QueryKind::Query> + 'static, + OnEmpty: Get<QueryKind::Query> + 'static, + MaxValues: Get<Option<u32>>, VecValue: FullCodec, VecBound: Get<u32>, { @@ -147,8 +162,8 @@ impl<Prefix, Hasher1, Key1, Hasher2, Key2, QueryKind, OnEmpty, VecValue, VecBoun } } -impl<Prefix, Hasher1, Key1, Hasher2, Key2, Value, QueryKind, OnEmpty> - StorageDoubleMap<Prefix, Hasher1, Key1, Hasher2, Key2, Value, QueryKind, OnEmpty> +impl<Prefix, Hasher1, Key1, Hasher2, Key2, Value, QueryKind, OnEmpty, MaxValues> + StorageDoubleMap<Prefix, Hasher1, Key1, Hasher2, Key2, Value, QueryKind, OnEmpty, MaxValues> where Prefix: StorageInstance, Hasher1: crate::hash::StorageHasher, @@ -157,7 +172,8 @@ where Key2: FullCodec, Value: FullCodec, QueryKind: QueryKindTrait<Value, OnEmpty>, - OnEmpty: crate::traits::Get<QueryKind::Query> + 'static + OnEmpty: Get<QueryKind::Query> + 'static, + MaxValues: Get<Option<u32>>, { /// Get the storage key used to fetch a value corresponding to a specific key. pub fn hashed_key_for<KArg1, KArg2>(k1: KArg1, k2: KArg2) -> Vec<u8> @@ -376,8 +392,8 @@ where } } -impl<Prefix, Hasher1, Key1, Hasher2, Key2, Value, QueryKind, OnEmpty> - StorageDoubleMap<Prefix, Hasher1, Key1, Hasher2, Key2, Value, QueryKind, OnEmpty> +impl<Prefix, Hasher1, Key1, Hasher2, Key2, Value, QueryKind, OnEmpty, MaxValues> + StorageDoubleMap<Prefix, Hasher1, Key1, Hasher2, Key2, Value, QueryKind, OnEmpty, MaxValues> where Prefix: StorageInstance, Hasher1: crate::hash::StorageHasher + crate::ReversibleStorageHasher, @@ -386,7 +402,8 @@ where Key2: FullCodec, Value: FullCodec, QueryKind: QueryKindTrait<Value, OnEmpty>, - OnEmpty: crate::traits::Get<QueryKind::Query> + 'static + OnEmpty: Get<QueryKind::Query> + 'static, + MaxValues: Get<Option<u32>>, { /// Enumerate all elements in the map with first key `k1` in no particular order. /// @@ -440,8 +457,10 @@ pub trait StorageDoubleMapMetadata { const HASHER2: frame_metadata::StorageHasher; } -impl<Prefix, Hasher1, Hasher2, Key1, Key2, Value, QueryKind, OnEmpty> StorageDoubleMapMetadata - for StorageDoubleMap<Prefix, Hasher1, Key1, Hasher2, Key2, Value, QueryKind, OnEmpty> where +impl<Prefix, Hasher1, Hasher2, Key1, Key2, Value, QueryKind, OnEmpty, MaxValues> + StorageDoubleMapMetadata for + StorageDoubleMap<Prefix, Hasher1, Key1, Hasher2, Key2, Value, QueryKind, OnEmpty, MaxValues> +where Prefix: StorageInstance, Hasher1: crate::hash::StorageHasher, Hasher2: crate::hash::StorageHasher, @@ -449,7 +468,8 @@ impl<Prefix, Hasher1, Hasher2, Key1, Key2, Value, QueryKind, OnEmpty> StorageDou Key2: FullCodec, Value: FullCodec, QueryKind: QueryKindTrait<Value, OnEmpty>, - OnEmpty: crate::traits::Get<QueryKind::Query> + 'static + OnEmpty: Get<QueryKind::Query> + 'static, + MaxValues: Get<Option<u32>>, { const MODIFIER: StorageEntryModifier = QueryKind::METADATA; const HASHER1: frame_metadata::StorageHasher = Hasher1::METADATA; @@ -459,6 +479,36 @@ impl<Prefix, Hasher1, Hasher2, Key1, Key2, Value, QueryKind, OnEmpty> StorageDou DefaultByteGetter(&OnEmptyGetter::<QueryKind::Query, OnEmpty>(core::marker::PhantomData)); } +impl<Prefix, Hasher1, Hasher2, Key1, Key2, Value, QueryKind, OnEmpty, MaxValues> + crate::traits::StorageInfoTrait for + StorageDoubleMap<Prefix, Hasher1, Key1, Hasher2, Key2, Value, QueryKind, OnEmpty, MaxValues> +where + Prefix: StorageInstance, + Hasher1: crate::hash::StorageHasher, + Hasher2: crate::hash::StorageHasher, + Key1: FullCodec + MaxEncodedLen, + Key2: FullCodec + MaxEncodedLen, + Value: FullCodec + MaxEncodedLen, + QueryKind: QueryKindTrait<Value, OnEmpty>, + OnEmpty: Get<QueryKind::Query> + 'static, + MaxValues: Get<Option<u32>>, +{ + fn storage_info() -> Vec<StorageInfo> { + vec![ + StorageInfo { + prefix: Self::final_prefix(), + max_values: MaxValues::get(), + max_size: Some( + Hasher1::max_len::<Key1>() + .saturating_add(Hasher2::max_len::<Key2>()) + .saturating_add(Value::max_encoded_len()) + .saturated_into(), + ), + } + ] + } +} + #[cfg(test)] mod test { use super::*; diff --git a/substrate/frame/support/src/storage/types/key.rs b/substrate/frame/support/src/storage/types/key.rs index 5eb608233b85d898f6d80e60bb559458a35a0569..79fc33a24e8320acc2a6a82e620dacf1a6a52817 100755 --- a/substrate/frame/support/src/storage/types/key.rs +++ b/substrate/frame/support/src/storage/types/key.rs @@ -17,7 +17,7 @@ //! Storage key type. -use crate::hash::{ReversibleStorageHasher, StorageHasher}; +use crate::{hash::{ReversibleStorageHasher, StorageHasher}, traits::MaxEncodedLen}; use codec::{Encode, EncodeLike, FullCodec}; use paste::paste; use sp_std::prelude::*; @@ -53,6 +53,11 @@ pub trait KeyGenerator { ) -> Vec<u8>; } +/// The maximum length used by the key in storage. +pub trait KeyGeneratorMaxEncodedLen: KeyGenerator { + fn key_max_encoded_len() -> usize; +} + /// A trait containing methods that are only implemented on the Key struct instead of the entire tuple. pub trait KeyGeneratorInner: KeyGenerator { type Hasher: StorageHasher; @@ -91,6 +96,12 @@ impl<H: StorageHasher, K: FullCodec> KeyGenerator for Key<H, K> { } } +impl<H: StorageHasher, K: FullCodec + MaxEncodedLen> KeyGeneratorMaxEncodedLen for Key<H, K> { + fn key_max_encoded_len() -> usize { + H::max_len::<K>() + } +} + impl<H: StorageHasher, K: FullCodec> KeyGeneratorInner for Key<H, K> { type Hasher = H; @@ -139,6 +150,20 @@ impl KeyGenerator for Tuple { } } +#[impl_trait_for_tuples::impl_for_tuples(2, 18)] +#[tuple_types_custom_trait_bound(KeyGeneratorInner + KeyGeneratorMaxEncodedLen)] +impl KeyGeneratorMaxEncodedLen for Tuple { + fn key_max_encoded_len() -> usize { + let mut len = 0usize; + for_tuples!( + #( + len = len.saturating_add(Tuple::key_max_encoded_len()); + )* + ); + len + } +} + /// Marker trait to indicate that each element in the tuple encodes like the corresponding element /// in another tuple. /// diff --git a/substrate/frame/support/src/storage/types/map.rs b/substrate/frame/support/src/storage/types/map.rs index b9c3044f93f06df65dd0161e3568d5eeb68c4c09..ac2817c6887fd4b826178a5107554606c311e6d2 100644 --- a/substrate/frame/support/src/storage/types/map.rs +++ b/substrate/frame/support/src/storage/types/map.rs @@ -21,13 +21,14 @@ use codec::{FullCodec, Decode, EncodeLike, Encode}; use crate::{ storage::{ - StorageAppend, StorageDecodeLength, + StorageAppend, StorageDecodeLength, StoragePrefixedMap, bounded_vec::BoundedVec, types::{OptionQuery, QueryKindTrait, OnEmptyGetter}, }, - traits::{GetDefault, StorageInstance, Get}, + traits::{GetDefault, StorageInstance, Get, MaxEncodedLen, StorageInfo}, }; use frame_metadata::{DefaultByteGetter, StorageEntryModifier}; +use sp_arithmetic::traits::SaturatedConversion; use sp_std::prelude::*; /// A type that allow to store value for given key. Allowing to insert/remove/iterate on values. @@ -43,20 +44,23 @@ use sp_std::prelude::*; /// /// 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. -pub struct StorageMap<Prefix, Hasher, Key, Value, QueryKind=OptionQuery, OnEmpty=GetDefault>( - core::marker::PhantomData<(Prefix, Hasher, Key, Value, QueryKind, OnEmpty)> +pub struct StorageMap< + Prefix, Hasher, Key, Value, QueryKind=OptionQuery, OnEmpty=GetDefault, MaxValues=GetDefault, +>( + core::marker::PhantomData<(Prefix, Hasher, Key, Value, QueryKind, OnEmpty, MaxValues)> ); -impl<Prefix, Hasher, Key, Value, QueryKind, OnEmpty> +impl<Prefix, Hasher, Key, Value, QueryKind, OnEmpty, MaxValues> crate::storage::generator::StorageMap<Key, Value> - for StorageMap<Prefix, Hasher, Key, Value, QueryKind, OnEmpty> + for StorageMap<Prefix, Hasher, Key, Value, QueryKind, OnEmpty, MaxValues> where Prefix: StorageInstance, Hasher: crate::hash::StorageHasher, Key: FullCodec, Value: FullCodec, QueryKind: QueryKindTrait<Value, OnEmpty>, - OnEmpty: crate::traits::Get<QueryKind::Query> + 'static, + OnEmpty: Get<QueryKind::Query> + 'static, + MaxValues: Get<Option<u32>>, { type Query = QueryKind::Query; type Hasher = Hasher; @@ -74,15 +78,17 @@ where } } -impl<Prefix, Hasher, Key, Value, QueryKind, OnEmpty> crate::storage::StoragePrefixedMap<Value> for - StorageMap<Prefix, Hasher, Key, Value, QueryKind, OnEmpty> +impl<Prefix, Hasher, Key, Value, QueryKind, OnEmpty, MaxValues> + StoragePrefixedMap<Value> for + StorageMap<Prefix, Hasher, Key, Value, QueryKind, OnEmpty, MaxValues> where Prefix: StorageInstance, Hasher: crate::hash::StorageHasher, Key: FullCodec, Value: FullCodec, QueryKind: QueryKindTrait<Value, OnEmpty>, - OnEmpty: crate::traits::Get<QueryKind::Query> + 'static, + OnEmpty: Get<QueryKind::Query> + 'static, + MaxValues: Get<Option<u32>>, { fn module_prefix() -> &'static [u8] { <Self as crate::storage::generator::StorageMap<Key, Value>>::module_prefix() @@ -92,14 +98,15 @@ where } } -impl<Prefix, Hasher, Key, QueryKind, OnEmpty, VecValue, VecBound> - StorageMap<Prefix, Hasher, Key, BoundedVec<VecValue, VecBound>, QueryKind, OnEmpty> +impl<Prefix, Hasher, Key, QueryKind, OnEmpty, MaxValues, VecValue, VecBound> + StorageMap<Prefix, Hasher, Key, BoundedVec<VecValue, VecBound>, QueryKind, OnEmpty, MaxValues> where Prefix: StorageInstance, Hasher: crate::hash::StorageHasher, Key: FullCodec, QueryKind: QueryKindTrait<BoundedVec<VecValue, VecBound>, OnEmpty>, - OnEmpty: crate::traits::Get<QueryKind::Query> + 'static, + OnEmpty: Get<QueryKind::Query> + 'static, + MaxValues: Get<Option<u32>>, VecValue: FullCodec, VecBound: Get<u32>, { @@ -120,15 +127,16 @@ where } } -impl<Prefix, Hasher, Key, Value, QueryKind, OnEmpty> - StorageMap<Prefix, Hasher, Key, Value, QueryKind, OnEmpty> +impl<Prefix, Hasher, Key, Value, QueryKind, OnEmpty, MaxValues> + StorageMap<Prefix, Hasher, Key, Value, QueryKind, OnEmpty, MaxValues> where Prefix: StorageInstance, Hasher: crate::hash::StorageHasher, Key: FullCodec, Value: FullCodec, QueryKind: QueryKindTrait<Value, OnEmpty>, - OnEmpty: crate::traits::Get<QueryKind::Query> + 'static, + OnEmpty: Get<QueryKind::Query> + 'static, + MaxValues: Get<Option<u32>>, { /// Get the storage key used to fetch a value corresponding to a specific key. pub fn hashed_key_for<KeyArg: EncodeLike<Key>>(key: KeyArg) -> Vec<u8> { @@ -283,15 +291,16 @@ where } } -impl<Prefix, Hasher, Key, Value, QueryKind, OnEmpty> - StorageMap<Prefix, Hasher, Key, Value, QueryKind, OnEmpty> +impl<Prefix, Hasher, Key, Value, QueryKind, OnEmpty, MaxValues> + StorageMap<Prefix, Hasher, Key, Value, QueryKind, OnEmpty, MaxValues> where Prefix: StorageInstance, Hasher: crate::hash::StorageHasher + crate::ReversibleStorageHasher, Key: FullCodec, Value: FullCodec, QueryKind: QueryKindTrait<Value, OnEmpty>, - OnEmpty: crate::traits::Get<QueryKind::Query> + 'static, + OnEmpty: Get<QueryKind::Query> + 'static, + MaxValues: Get<Option<u32>>, { /// Enumerate all elements in the map in no particular order. /// @@ -327,14 +336,15 @@ pub trait StorageMapMetadata { const HASHER: frame_metadata::StorageHasher; } -impl<Prefix, Hasher, Key, Value, QueryKind, OnEmpty> StorageMapMetadata - for StorageMap<Prefix, Hasher, Key, Value, QueryKind, OnEmpty> where +impl<Prefix, Hasher, Key, Value, QueryKind, OnEmpty, MaxValues> StorageMapMetadata + for StorageMap<Prefix, Hasher, Key, Value, QueryKind, OnEmpty, MaxValues> where Prefix: StorageInstance, Hasher: crate::hash::StorageHasher, Key: FullCodec, Value: FullCodec, QueryKind: QueryKindTrait<Value, OnEmpty>, - OnEmpty: crate::traits::Get<QueryKind::Query> + 'static, + OnEmpty: Get<QueryKind::Query> + 'static, + MaxValues: Get<Option<u32>>, { const MODIFIER: StorageEntryModifier = QueryKind::METADATA; const HASHER: frame_metadata::StorageHasher = Hasher::METADATA; @@ -343,6 +353,33 @@ impl<Prefix, Hasher, Key, Value, QueryKind, OnEmpty> StorageMapMetadata DefaultByteGetter(&OnEmptyGetter::<QueryKind::Query, OnEmpty>(core::marker::PhantomData)); } +impl<Prefix, Hasher, Key, Value, QueryKind, OnEmpty, MaxValues> + crate::traits::StorageInfoTrait for + StorageMap<Prefix, Hasher, Key, Value, QueryKind, OnEmpty, MaxValues> +where + Prefix: StorageInstance, + Hasher: crate::hash::StorageHasher, + Key: FullCodec + MaxEncodedLen, + Value: FullCodec + MaxEncodedLen, + QueryKind: QueryKindTrait<Value, OnEmpty>, + OnEmpty: Get<QueryKind::Query> + 'static, + MaxValues: Get<Option<u32>>, +{ + fn storage_info() -> Vec<StorageInfo> { + vec![ + StorageInfo { + prefix: Self::final_prefix(), + max_values: MaxValues::get(), + max_size: Some( + Hasher::max_len::<Key>() + .saturating_add(Value::max_encoded_len()) + .saturated_into(), + ), + } + ] + } +} + #[cfg(test)] mod test { use super::*; diff --git a/substrate/frame/support/src/storage/types/mod.rs b/substrate/frame/support/src/storage/types/mod.rs index 5b7aa61d37693217fc1b388992275f5bbb573011..f61065671315f63adf2941cb39477b4e710d9551 100644 --- a/substrate/frame/support/src/storage/types/mod.rs +++ b/substrate/frame/support/src/storage/types/mod.rs @@ -30,7 +30,7 @@ mod value; pub use double_map::{StorageDoubleMap, StorageDoubleMapMetadata}; pub use key::{ EncodeLikeTuple, HasKeyPrefix, HasReversibleKeyPrefix, Key, KeyGenerator, - ReversibleKeyGenerator, TupleToEncodedIter, + ReversibleKeyGenerator, TupleToEncodedIter, KeyGeneratorMaxEncodedLen, }; pub use map::{StorageMap, StorageMapMetadata}; pub use nmap::{StorageNMap, StorageNMapMetadata}; diff --git a/substrate/frame/support/src/storage/types/nmap.rs b/substrate/frame/support/src/storage/types/nmap.rs index 1a2b6d4d55dccc749d1fc5142f34e064247f07c9..f018ccc38b4fe3ee9577e821a79db7e364fb4293 100755 --- a/substrate/frame/support/src/storage/types/nmap.rs +++ b/substrate/frame/support/src/storage/types/nmap.rs @@ -24,12 +24,13 @@ use crate::{ EncodeLikeTuple, HasKeyPrefix, HasReversibleKeyPrefix, OnEmptyGetter, OptionQuery, QueryKindTrait, TupleToEncodedIter, }, - KeyGenerator, PrefixIterator, StorageAppend, StorageDecodeLength, + KeyGenerator, PrefixIterator, StorageAppend, StorageDecodeLength, StoragePrefixedMap, }, - traits::{GetDefault, StorageInstance}, + traits::{Get, GetDefault, StorageInstance, StorageInfo, MaxEncodedLen}, }; use codec::{Decode, Encode, EncodeLike, FullCodec}; use frame_metadata::{DefaultByteGetter, StorageEntryModifier}; +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 @@ -50,18 +51,22 @@ use sp_std::prelude::*; /// 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. -pub struct StorageNMap<Prefix, Key, Value, QueryKind = OptionQuery, OnEmpty = GetDefault>( - core::marker::PhantomData<(Prefix, Key, Value, QueryKind, OnEmpty)>, +pub struct StorageNMap< + Prefix, Key, Value, QueryKind = OptionQuery, OnEmpty = GetDefault, MaxValues=GetDefault, +>( + core::marker::PhantomData<(Prefix, Key, Value, QueryKind, OnEmpty, MaxValues)>, ); -impl<Prefix, Key, Value, QueryKind, OnEmpty> crate::storage::generator::StorageNMap<Key, Value> - for StorageNMap<Prefix, Key, Value, QueryKind, OnEmpty> +impl<Prefix, Key, Value, QueryKind, OnEmpty, MaxValues> + crate::storage::generator::StorageNMap<Key, Value> + for StorageNMap<Prefix, Key, Value, QueryKind, OnEmpty, MaxValues> where Prefix: StorageInstance, Key: super::key::KeyGenerator, Value: FullCodec, QueryKind: QueryKindTrait<Value, OnEmpty>, - OnEmpty: crate::traits::Get<QueryKind::Query> + 'static, + OnEmpty: Get<QueryKind::Query> + 'static, + MaxValues: Get<Option<u32>>, { type Query = QueryKind::Query; fn module_prefix() -> &'static [u8] { @@ -78,14 +83,16 @@ where } } -impl<Prefix, Key, Value, QueryKind, OnEmpty> crate::storage::StoragePrefixedMap<Value> - for StorageNMap<Prefix, Key, Value, QueryKind, OnEmpty> +impl<Prefix, Key, Value, QueryKind, OnEmpty, MaxValues> + crate::storage::StoragePrefixedMap<Value> + for StorageNMap<Prefix, Key, Value, QueryKind, OnEmpty, MaxValues> where Prefix: StorageInstance, Key: super::key::KeyGenerator, Value: FullCodec, QueryKind: QueryKindTrait<Value, OnEmpty>, - OnEmpty: crate::traits::Get<QueryKind::Query> + 'static, + OnEmpty: Get<QueryKind::Query> + 'static, + MaxValues: Get<Option<u32>>, { fn module_prefix() -> &'static [u8] { <Self as crate::storage::generator::StorageNMap<Key, Value>>::module_prefix() @@ -95,13 +102,15 @@ where } } -impl<Prefix, Key, Value, QueryKind, OnEmpty> StorageNMap<Prefix, Key, Value, QueryKind, OnEmpty> +impl<Prefix, Key, Value, QueryKind, OnEmpty, MaxValues> + StorageNMap<Prefix, Key, Value, QueryKind, OnEmpty, MaxValues> where Prefix: StorageInstance, Key: super::key::KeyGenerator, Value: FullCodec, QueryKind: QueryKindTrait<Value, OnEmpty>, - OnEmpty: crate::traits::Get<QueryKind::Query> + 'static, + OnEmpty: Get<QueryKind::Query> + 'static, + MaxValues: Get<Option<u32>>, { /// Get the storage key used to fetch a value corresponding to a specific key. pub fn hashed_key_for<KArg: EncodeLikeTuple<Key::KArg> + TupleToEncodedIter>(key: KArg) -> Vec<u8> { @@ -286,13 +295,15 @@ where } } -impl<Prefix, Key, Value, QueryKind, OnEmpty> StorageNMap<Prefix, Key, Value, QueryKind, OnEmpty> +impl<Prefix, Key, Value, QueryKind, OnEmpty, MaxValues> + StorageNMap<Prefix, Key, Value, QueryKind, OnEmpty, MaxValues> where Prefix: StorageInstance, Key: super::key::ReversibleKeyGenerator, Value: FullCodec, QueryKind: QueryKindTrait<Value, OnEmpty>, - OnEmpty: crate::traits::Get<QueryKind::Query> + 'static, + OnEmpty: Get<QueryKind::Query> + 'static, + MaxValues: Get<Option<u32>>, { /// Enumerate all elements in the map with prefix key `kp` in no particular order. /// @@ -355,14 +366,15 @@ pub trait StorageNMapMetadata { const HASHERS: &'static [frame_metadata::StorageHasher]; } -impl<Prefix, Key, Value, QueryKind, OnEmpty> StorageNMapMetadata - for StorageNMap<Prefix, Key, Value, QueryKind, OnEmpty> +impl<Prefix, Key, Value, QueryKind, OnEmpty, MaxValues> StorageNMapMetadata + for StorageNMap<Prefix, Key, Value, QueryKind, OnEmpty, MaxValues> where Prefix: StorageInstance, Key: super::key::KeyGenerator, Value: FullCodec, QueryKind: QueryKindTrait<Value, OnEmpty>, - OnEmpty: crate::traits::Get<QueryKind::Query> + 'static, + OnEmpty: Get<QueryKind::Query> + 'static, + MaxValues: Get<Option<u32>>, { const MODIFIER: StorageEntryModifier = QueryKind::METADATA; const NAME: &'static str = Prefix::STORAGE_PREFIX; @@ -372,6 +384,32 @@ where const HASHERS: &'static [frame_metadata::StorageHasher] = Key::HASHER_METADATA; } +impl<Prefix, Key, Value, QueryKind, OnEmpty, MaxValues> + crate::traits::StorageInfoTrait for + StorageNMap<Prefix, Key, Value, QueryKind, OnEmpty, MaxValues> +where + Prefix: StorageInstance, + Key: super::key::KeyGenerator + super::key::KeyGeneratorMaxEncodedLen, + Value: FullCodec + MaxEncodedLen, + QueryKind: QueryKindTrait<Value, OnEmpty>, + OnEmpty: Get<QueryKind::Query> + 'static, + MaxValues: Get<Option<u32>>, +{ + fn storage_info() -> Vec<StorageInfo> { + vec![ + StorageInfo { + prefix: Self::final_prefix(), + max_values: MaxValues::get(), + max_size: Some( + Key::key_max_encoded_len() + .saturating_add(Value::max_encoded_len()) + .saturated_into(), + ), + } + ] + } +} + #[cfg(test)] mod test { use super::*; diff --git a/substrate/frame/support/src/storage/types/value.rs b/substrate/frame/support/src/storage/types/value.rs index 6a92a2a632c76be1f2c5459f534cfecb6f18d7f5..67d2e3741929e69a12dc38c522fd2a10d4f30dfc 100644 --- a/substrate/frame/support/src/storage/types/value.rs +++ b/substrate/frame/support/src/storage/types/value.rs @@ -24,9 +24,11 @@ use crate::{ bounded_vec::BoundedVec, types::{OptionQuery, QueryKindTrait, OnEmptyGetter}, }, - traits::{GetDefault, StorageInstance, Get}, + traits::{GetDefault, StorageInstance, Get, MaxEncodedLen, StorageInfo}, }; use frame_metadata::{DefaultByteGetter, StorageEntryModifier}; +use sp_arithmetic::traits::SaturatedConversion; +use sp_std::prelude::*; /// A type that allow to store a value. /// @@ -212,6 +214,29 @@ impl<Prefix, Value, QueryKind, OnEmpty> StorageValueMetadata DefaultByteGetter(&OnEmptyGetter::<QueryKind::Query, OnEmpty>(core::marker::PhantomData)); } +impl<Prefix, Value, QueryKind, OnEmpty> + crate::traits::StorageInfoTrait for + StorageValue<Prefix, Value, QueryKind, OnEmpty> +where + Prefix: StorageInstance, + Value: FullCodec + MaxEncodedLen, + QueryKind: QueryKindTrait<Value, OnEmpty>, + OnEmpty: crate::traits::Get<QueryKind::Query> + 'static +{ + fn storage_info() -> Vec<StorageInfo> { + vec![ + StorageInfo { + prefix: Self::hashed_key(), + max_values: Some(1), + max_size: Some( + Value::max_encoded_len() + .saturated_into(), + ), + } + ] + } +} + #[cfg(test)] mod test { use super::*; diff --git a/substrate/frame/support/src/traits.rs b/substrate/frame/support/src/traits.rs index 2d7fb3db7366d57e39262ae35888eb67ad57ec58..295995b1bfebd9955ffd92befa11358a15395f2d 100644 --- a/substrate/frame/support/src/traits.rs +++ b/substrate/frame/support/src/traits.rs @@ -50,7 +50,7 @@ mod misc; pub use misc::{ Len, Get, GetDefault, HandleLifetime, TryDrop, Time, UnixTime, IsType, IsSubType, ExecuteBlock, SameOrOther, OnNewAccount, OnKilledAccount, OffchainWorker, GetBacking, Backing, ExtrinsicCall, - EnsureInherentsAreFirst, + EnsureInherentsAreFirst, ConstU32, }; mod stored_map; @@ -73,7 +73,7 @@ pub use hooks::GenesisBuild; pub mod schedule; mod storage; -pub use storage::{Instance, StorageInstance}; +pub use storage::{Instance, StorageInstance, StorageInfo, StorageInfoTrait}; mod dispatch; pub use dispatch::{EnsureOrigin, OriginTrait, UnfilteredDispatchable}; diff --git a/substrate/frame/support/src/traits/misc.rs b/substrate/frame/support/src/traits/misc.rs index d3010358dd883d1f30c2c6c1a5077b3ba8a1c2db..7ec29522cbc751651afb3321dd5921a57e76c29c 100644 --- a/substrate/frame/support/src/traits/misc.rs +++ b/substrate/frame/support/src/traits/misc.rs @@ -53,6 +53,21 @@ impl<T: Default> Get<T> for GetDefault { } } +/// Implement `Get<u32>` and `Get<Option<u32>>` using the given const. +pub struct ConstU32<const T: u32>; + +impl<const T: u32> Get<u32> for ConstU32<T> { + fn get() -> u32 { + T + } +} + +impl<const T: u32> Get<Option<u32>> for ConstU32<T> { + fn get() -> Option<u32> { + Some(T) + } +} + /// A type for which some values make sense to be able to drop without further consideration. pub trait TryDrop: Sized { /// Drop an instance cleanly. Only works if its value represents "no-operation". diff --git a/substrate/frame/support/src/traits/storage.rs b/substrate/frame/support/src/traits/storage.rs index c42e1abf73ea3296c61ffa32c2d064bb2eb4e20e..37957ceb67765cf6fc2b99dfb2cac1987baa7acc 100644 --- a/substrate/frame/support/src/traits/storage.rs +++ b/substrate/frame/support/src/traits/storage.rs @@ -17,6 +17,8 @@ //! Traits for encoding data related to pallet's storage items. +use sp_std::prelude::*; + /// An instance of a pallet in the storage. /// /// It is required that these instances are unique, to support multiple instances per pallet in the same runtime! @@ -45,3 +47,30 @@ pub trait StorageInstance { /// Prefix given to a storage to isolate from other storages in the pallet. const STORAGE_PREFIX: &'static str; } + +/// Some info about an individual storage in a pallet. +#[derive(codec::Encode, codec::Decode, crate::RuntimeDebug, Eq, PartialEq, Clone)] +pub struct StorageInfo { + /// The prefix of the storage. All keys after the prefix are considered part of the storage + pub prefix: [u8; 32], + /// The maximum number of values in the storage, or none if no maximum specified. + pub max_values: Option<u32>, + /// The maximum size of key/values in the storage, or none if no maximum specified. + pub max_size: Option<u32>, +} + +/// A trait to give information about storage. +/// +/// It can be used to calculate PoV worst case size. +pub trait StorageInfoTrait { + fn storage_info() -> Vec<StorageInfo>; +} + +#[impl_trait_for_tuples::impl_for_tuples(30)] +impl StorageInfoTrait for Tuple { + fn storage_info() -> Vec<StorageInfo> { + let mut res = vec![]; + for_tuples!( #( res.extend_from_slice(&Tuple::storage_info()); )* ); + res + } +} diff --git a/substrate/frame/support/test/tests/decl_storage.rs b/substrate/frame/support/test/tests/decl_storage.rs index a2690b1379db5938c8dd8d073ca5bdfb914dc291..ef7b577ab6b8d559534eddd09bef94928b6f8ea9 100644 --- a/substrate/frame/support/test/tests/decl_storage.rs +++ b/substrate/frame/support/test/tests/decl_storage.rs @@ -27,9 +27,13 @@ mod tests { pub struct Module<T: Config> for enum Call where origin: T::Origin, system=frame_support_test {} } - pub trait Config: frame_support_test::Config {} + pub trait Config: frame_support_test::Config { + type Origin2: codec::Codec + codec::EncodeLike + Default + + frame_support::traits::MaxEncodedLen; + } frame_support::decl_storage! { + generate_storage_info trait Store for Module<T: Config> as TestStorage { // non-getters: pub / $default @@ -41,7 +45,7 @@ mod tests { // getters: pub / $default // we need at least one type which uses T, otherwise GenesisConfig will complain. - GETU32 get(fn u32_getter): T::Origin; + GETU32 get(fn u32_getter): T::Origin2; pub PUBGETU32 get(fn pub_u32_getter): u32; GETU32WITHCONFIG get(fn u32_getter_with_config) config(): u32; pub PUBGETU32WITHCONFIG get(fn pub_u32_getter_with_config) config(): u32; @@ -56,23 +60,29 @@ mod tests { GetOptU32WithBuilderNone get(fn opt_u32_with_builder_none) build(|_| None): Option<u32>; // map non-getters: pub / $default - MAPU32: map hasher(blake2_128_concat) u32 => Option<String>; - pub PUBMAPU32: map hasher(blake2_128_concat) u32 => Option<String>; - MAPU32MYDEF: map hasher(blake2_128_concat) u32 => Option<String>; - pub PUBMAPU32MYDEF: map hasher(blake2_128_concat) u32 => Option<String>; + MAPU32 max_values(3): map hasher(blake2_128_concat) u32 => Option<[u8; 4]>; + pub PUBMAPU32: map hasher(blake2_128_concat) u32 => Option<[u8; 4]>; // map getters: pub / $default - GETMAPU32 get(fn map_u32_getter): map hasher(blake2_128_concat) u32 => String; - pub PUBGETMAPU32 get(fn pub_map_u32_getter): map hasher(blake2_128_concat) u32 => String; - + GETMAPU32 get(fn map_u32_getter): map hasher(blake2_128_concat) u32 => [u8; 4]; + pub PUBGETMAPU32 get(fn pub_map_u32_getter): map hasher(blake2_128_concat) u32 => [u8; 4]; GETMAPU32MYDEF get(fn map_u32_getter_mydef): - map hasher(blake2_128_concat) u32 => String = "map".into(); + map hasher(blake2_128_concat) u32 => [u8; 4] = *b"mapd"; pub PUBGETMAPU32MYDEF get(fn pub_map_u32_getter_mydef): - map hasher(blake2_128_concat) u32 => String = "pubmap".into(); + map hasher(blake2_128_concat) u32 => [u8; 4] = *b"pubm"; - COMPLEXTYPE1: ::std::vec::Vec<T::Origin>; - COMPLEXTYPE2: (Vec<Vec<(u16, Box<()>)>>, u32); + DOUBLEMAP max_values(3): double_map + hasher(blake2_128_concat) u32, hasher(blake2_128_concat) u32 => Option<[u8; 4]>; + + DOUBLEMAP2: double_map + hasher(blake2_128_concat) u32, hasher(blake2_128_concat) u32 => Option<[u8; 4]>; + + COMPLEXTYPE1: (::std::option::Option<T::Origin2>,); + COMPLEXTYPE2: ([[(u16, Option<()>); 32]; 12], u32); COMPLEXTYPE3: [u32; 25]; + + NMAP: nmap hasher(blake2_128_concat) u32, hasher(twox_64_concat) u16 => u8; + NMAP2: nmap hasher(blake2_128_concat) u32 => u8; } add_extra_genesis { build(|_| {}); @@ -88,7 +98,9 @@ mod tests { type DbWeight = (); } - impl Config for TraitImpl {} + impl Config for TraitImpl { + type Origin2 = u32; + } const EXPECTED_METADATA: StorageMetadata = StorageMetadata { prefix: DecodeDifferent::Encode("TestStorage"), @@ -133,7 +145,7 @@ mod tests { StorageEntryMetadata { name: DecodeDifferent::Encode("GETU32"), modifier: StorageEntryModifier::Default, - ty: StorageEntryType::Plain(DecodeDifferent::Encode("T::Origin")), + ty: StorageEntryType::Plain(DecodeDifferent::Encode("T::Origin2")), default: DecodeDifferent::Encode( DefaultByteGetter(&__GetByteStructGETU32(PhantomData::<TraitImpl>)) ), @@ -244,7 +256,7 @@ mod tests { ty: StorageEntryType::Map { hasher: StorageHasher::Blake2_128Concat, key: DecodeDifferent::Encode("u32"), - value: DecodeDifferent::Encode("String"), + value: DecodeDifferent::Encode("[u8; 4]"), unused: false, }, default: DecodeDifferent::Encode( @@ -258,7 +270,7 @@ mod tests { ty: StorageEntryType::Map { hasher: StorageHasher::Blake2_128Concat, key: DecodeDifferent::Encode("u32"), - value: DecodeDifferent::Encode("String"), + value: DecodeDifferent::Encode("[u8; 4]"), unused: false, }, default: DecodeDifferent::Encode( @@ -267,93 +279,95 @@ mod tests { documentation: DecodeDifferent::Encode(&[]), }, StorageEntryMetadata { - name: DecodeDifferent::Encode("MAPU32MYDEF"), - modifier: StorageEntryModifier::Optional, + name: DecodeDifferent::Encode("GETMAPU32"), + modifier: StorageEntryModifier::Default, ty: StorageEntryType::Map { hasher: StorageHasher::Blake2_128Concat, key: DecodeDifferent::Encode("u32"), - value: DecodeDifferent::Encode("String"), + value: DecodeDifferent::Encode("[u8; 4]"), unused: false, }, default: DecodeDifferent::Encode( - DefaultByteGetter(&__GetByteStructMAPU32MYDEF(PhantomData::<TraitImpl>)) + DefaultByteGetter(&__GetByteStructGETMAPU32(PhantomData::<TraitImpl>)) ), documentation: DecodeDifferent::Encode(&[]), }, StorageEntryMetadata { - name: DecodeDifferent::Encode("PUBMAPU32MYDEF"), - modifier: StorageEntryModifier::Optional, + name: DecodeDifferent::Encode("PUBGETMAPU32"), + modifier: StorageEntryModifier::Default, ty: StorageEntryType::Map { hasher: StorageHasher::Blake2_128Concat, key: DecodeDifferent::Encode("u32"), - value: DecodeDifferent::Encode("String"), + value: DecodeDifferent::Encode("[u8; 4]"), unused: false, }, default: DecodeDifferent::Encode( - DefaultByteGetter(&__GetByteStructPUBMAPU32MYDEF(PhantomData::<TraitImpl>)) + DefaultByteGetter(&__GetByteStructPUBGETMAPU32(PhantomData::<TraitImpl>)) ), documentation: DecodeDifferent::Encode(&[]), }, StorageEntryMetadata { - name: DecodeDifferent::Encode("GETMAPU32"), + name: DecodeDifferent::Encode("GETMAPU32MYDEF"), modifier: StorageEntryModifier::Default, ty: StorageEntryType::Map { hasher: StorageHasher::Blake2_128Concat, key: DecodeDifferent::Encode("u32"), - value: DecodeDifferent::Encode("String"), + value: DecodeDifferent::Encode("[u8; 4]"), unused: false, }, default: DecodeDifferent::Encode( - DefaultByteGetter(&__GetByteStructGETMAPU32(PhantomData::<TraitImpl>)) + DefaultByteGetter(&__GetByteStructGETMAPU32MYDEF(PhantomData::<TraitImpl>)) ), documentation: DecodeDifferent::Encode(&[]), }, StorageEntryMetadata { - name: DecodeDifferent::Encode("PUBGETMAPU32"), + name: DecodeDifferent::Encode("PUBGETMAPU32MYDEF"), modifier: StorageEntryModifier::Default, ty: StorageEntryType::Map { hasher: StorageHasher::Blake2_128Concat, key: DecodeDifferent::Encode("u32"), - value: DecodeDifferent::Encode("String"), + value: DecodeDifferent::Encode("[u8; 4]"), unused: false, }, default: DecodeDifferent::Encode( - DefaultByteGetter(&__GetByteStructPUBGETMAPU32(PhantomData::<TraitImpl>)) + DefaultByteGetter(&__GetByteStructPUBGETMAPU32MYDEF(PhantomData::<TraitImpl>)) ), documentation: DecodeDifferent::Encode(&[]), }, StorageEntryMetadata { - name: DecodeDifferent::Encode("GETMAPU32MYDEF"), - modifier: StorageEntryModifier::Default, - ty: StorageEntryType::Map { + name: DecodeDifferent::Encode("DOUBLEMAP"), + modifier: StorageEntryModifier::Optional, + ty: StorageEntryType::DoubleMap { hasher: StorageHasher::Blake2_128Concat, - key: DecodeDifferent::Encode("u32"), - value: DecodeDifferent::Encode("String"), - unused: false, + key1: DecodeDifferent::Encode("u32"), + key2: DecodeDifferent::Encode("u32"), + value: DecodeDifferent::Encode("[u8; 4]"), + key2_hasher: StorageHasher::Blake2_128Concat, }, default: DecodeDifferent::Encode( - DefaultByteGetter(&__GetByteStructGETMAPU32MYDEF(PhantomData::<TraitImpl>)) + DefaultByteGetter(&__GetByteStructDOUBLEMAP(PhantomData::<TraitImpl>)) ), documentation: DecodeDifferent::Encode(&[]), }, StorageEntryMetadata { - name: DecodeDifferent::Encode("PUBGETMAPU32MYDEF"), - modifier: StorageEntryModifier::Default, - ty: StorageEntryType::Map { + name: DecodeDifferent::Encode("DOUBLEMAP2"), + modifier: StorageEntryModifier::Optional, + ty: StorageEntryType::DoubleMap { hasher: StorageHasher::Blake2_128Concat, - key: DecodeDifferent::Encode("u32"), - value: DecodeDifferent::Encode("String"), - unused: false, + key1: DecodeDifferent::Encode("u32"), + key2: DecodeDifferent::Encode("u32"), + value: DecodeDifferent::Encode("[u8; 4]"), + key2_hasher: StorageHasher::Blake2_128Concat, }, default: DecodeDifferent::Encode( - DefaultByteGetter(&__GetByteStructPUBGETMAPU32MYDEF(PhantomData::<TraitImpl>)) + DefaultByteGetter(&__GetByteStructDOUBLEMAP2(PhantomData::<TraitImpl>)) ), documentation: DecodeDifferent::Encode(&[]), }, StorageEntryMetadata { name: DecodeDifferent::Encode("COMPLEXTYPE1"), modifier: StorageEntryModifier::Default, - ty: StorageEntryType::Plain(DecodeDifferent::Encode("::std::vec::Vec<T::Origin>")), + ty: StorageEntryType::Plain(DecodeDifferent::Encode("(::std::option::Option<T::Origin2>,)")), default: DecodeDifferent::Encode( DefaultByteGetter(&__GetByteStructCOMPLEXTYPE1(PhantomData::<TraitImpl>)) ), @@ -362,7 +376,7 @@ mod tests { StorageEntryMetadata { name: DecodeDifferent::Encode("COMPLEXTYPE2"), modifier: StorageEntryModifier::Default, - ty: StorageEntryType::Plain(DecodeDifferent::Encode("(Vec<Vec<(u16, Box<()>)>>, u32)")), + ty: StorageEntryType::Plain(DecodeDifferent::Encode("([[(u16, Option<()>); 32]; 12], u32)")), default: DecodeDifferent::Encode( DefaultByteGetter(&__GetByteStructCOMPLEXTYPE2(PhantomData::<TraitImpl>)) ), @@ -377,10 +391,201 @@ mod tests { ), documentation: DecodeDifferent::Encode(&[]), }, + StorageEntryMetadata { + name: DecodeDifferent::Encode("NMAP"), + modifier: StorageEntryModifier::Default, + ty: StorageEntryType::NMap { + keys: DecodeDifferent::Encode(&["u32", "u16"]), + hashers: DecodeDifferent::Encode(&[StorageHasher::Blake2_128Concat, StorageHasher::Twox64Concat]), + value: DecodeDifferent::Encode("u8"), + }, + default: DecodeDifferent::Encode( + DefaultByteGetter(&__GetByteStructNMAP(PhantomData::<TraitImpl>)) + ), + documentation: DecodeDifferent::Encode(&[]), + }, + StorageEntryMetadata { + name: DecodeDifferent::Encode("NMAP2"), + modifier: StorageEntryModifier::Default, + ty: StorageEntryType::NMap { + keys: DecodeDifferent::Encode(&["u32"]), + hashers: DecodeDifferent::Encode(&[StorageHasher::Blake2_128Concat]), + value: DecodeDifferent::Encode("u8"), + }, + default: DecodeDifferent::Encode( + DefaultByteGetter(&__GetByteStructNMAP(PhantomData::<TraitImpl>)) + ), + documentation: DecodeDifferent::Encode(&[]), + }, ] ), }; + #[test] + fn storage_info() { + use frame_support::{ + StorageHasher, + traits::{StorageInfoTrait, StorageInfo}, + pallet_prelude::*, + }; + let prefix = |pallet_name, storage_name| { + let mut res = [0u8; 32]; + res[0..16].copy_from_slice(&Twox128::hash(pallet_name)); + res[16..32].copy_from_slice(&Twox128::hash(storage_name)); + res + }; + pretty_assertions::assert_eq!( + <Module<TraitImpl>>::storage_info(), + vec![ + StorageInfo { + prefix: prefix(b"TestStorage", b"U32"), + max_values: Some(1), + max_size: Some(4), + }, + StorageInfo { + prefix: prefix(b"TestStorage", b"PUBU32"), + max_values: Some(1), + max_size: Some(4), + }, + StorageInfo { + prefix: prefix(b"TestStorage", b"U32MYDEF"), + max_values: Some(1), + max_size: Some(4), + }, + StorageInfo { + prefix: prefix(b"TestStorage", b"PUBU32MYDEF"), + max_values: Some(1), + max_size: Some(4), + }, + StorageInfo { + prefix: prefix(b"TestStorage", b"GETU32"), + max_values: Some(1), + max_size: Some(4), + }, + StorageInfo { + prefix: prefix(b"TestStorage", b"PUBGETU32"), + max_values: Some(1), + max_size: Some(4), + }, + StorageInfo { + prefix: prefix(b"TestStorage", b"GETU32WITHCONFIG"), + max_values: Some(1), + max_size: Some(4), + }, + StorageInfo { + prefix: prefix(b"TestStorage", b"PUBGETU32WITHCONFIG"), + max_values: Some(1), + max_size: Some(4), + }, + StorageInfo { + prefix: prefix(b"TestStorage", b"GETU32MYDEF"), + max_values: Some(1), + max_size: Some(4), + }, + StorageInfo { + prefix: prefix(b"TestStorage", b"PUBGETU32MYDEF"), + max_values: Some(1), + max_size: Some(4), + }, + StorageInfo { + prefix: prefix(b"TestStorage", b"GETU32WITHCONFIGMYDEF"), + max_values: Some(1), + max_size: Some(4), + }, + StorageInfo { + prefix: prefix(b"TestStorage", b"PUBGETU32WITHCONFIGMYDEF"), + max_values: Some(1), + max_size: Some(4), + }, + StorageInfo { + prefix: prefix(b"TestStorage", b"PUBGETU32WITHCONFIGMYDEFOPT"), + max_values: Some(1), + max_size: Some(4), + }, + StorageInfo { + prefix: prefix(b"TestStorage", b"GetU32WithBuilder"), + max_values: Some(1), + max_size: Some(4), + }, + StorageInfo { + prefix: prefix(b"TestStorage", b"GetOptU32WithBuilderSome"), + max_values: Some(1), + max_size: Some(4), + }, + StorageInfo { + prefix: prefix(b"TestStorage", b"GetOptU32WithBuilderNone"), + max_values: Some(1), + max_size: Some(4), + }, + StorageInfo { + prefix: prefix(b"TestStorage", b"MAPU32"), + max_values: Some(3), + max_size: Some(8 + 16), + }, + StorageInfo { + prefix: prefix(b"TestStorage", b"PUBMAPU32"), + max_values: None, + max_size: Some(8 + 16), + }, + StorageInfo { + prefix: prefix(b"TestStorage", b"GETMAPU32"), + max_values: None, + max_size: Some(8 + 16), + }, + StorageInfo { + prefix: prefix(b"TestStorage", b"PUBGETMAPU32"), + max_values: None, + max_size: Some(8 + 16), + }, + StorageInfo { + prefix: prefix(b"TestStorage", b"GETMAPU32MYDEF"), + max_values: None, + max_size: Some(8 + 16), + }, + StorageInfo { + prefix: prefix(b"TestStorage", b"PUBGETMAPU32MYDEF"), + max_values: None, + max_size: Some(8 + 16), + }, + StorageInfo { + prefix: prefix(b"TestStorage", b"DOUBLEMAP"), + max_values: Some(3), + max_size: Some(12 + 16 + 16), + }, + StorageInfo { + prefix: prefix(b"TestStorage", b"DOUBLEMAP2"), + max_values: None, + max_size: Some(12 + 16 + 16), + }, + StorageInfo { + prefix: prefix(b"TestStorage", b"COMPLEXTYPE1"), + max_values: Some(1), + max_size: Some(5), + }, + StorageInfo { + prefix: prefix(b"TestStorage", b"COMPLEXTYPE2"), + max_values: Some(1), + max_size: Some(1156), + }, + StorageInfo { + prefix: prefix(b"TestStorage", b"COMPLEXTYPE3"), + max_values: Some(1), + max_size: Some(100), + }, + StorageInfo { + prefix: prefix(b"TestStorage", b"NMAP"), + max_values: None, + max_size: Some(16 + 4 + 8 + 2 + 1), + }, + StorageInfo { + prefix: prefix(b"TestStorage", b"NMAP2"), + max_values: None, + max_size: Some(16 + 4 + 1), + }, + ], + ); + } + #[test] fn store_metadata() { let metadata = Module::<TraitImpl>::storage_metadata(); diff --git a/substrate/frame/support/test/tests/pallet.rs b/substrate/frame/support/test/tests/pallet.rs index 5db5856fd9d97839f0169d1ada5dea1216da07c4..0a768c79e779c4ff9af728c5c75200937c6c3db5 100644 --- a/substrate/frame/support/test/tests/pallet.rs +++ b/substrate/frame/support/test/tests/pallet.rs @@ -19,6 +19,7 @@ use frame_support::{ weights::{DispatchInfo, DispatchClass, Pays, GetDispatchInfo}, traits::{ GetCallName, OnInitialize, OnFinalize, OnRuntimeUpgrade, GetPalletVersion, OnGenesis, + MaxEncodedLen, }, dispatch::{UnfilteredDispatchable, Parameter}, storage::unhashed, @@ -47,10 +48,10 @@ impl From<SomeType6> for u64 { fn from(_t: SomeType6) -> Self { 0u64 } } pub struct SomeType7; impl From<SomeType7> for u64 { fn from(_t: SomeType7) -> Self { 0u64 } } -pub trait SomeAssociation1 { type _1: Parameter; } +pub trait SomeAssociation1 { type _1: Parameter + MaxEncodedLen; } impl SomeAssociation1 for u64 { type _1 = u64; } -pub trait SomeAssociation2 { type _2: Parameter; } +pub trait SomeAssociation2 { type _2: Parameter + MaxEncodedLen; } impl SomeAssociation2 for u64 { type _2 = u64; } #[frame_support::pallet] @@ -100,6 +101,7 @@ pub mod pallet { #[pallet::pallet] #[pallet::generate_store(pub(crate) trait Store)] + #[pallet::generate_storage_info] pub struct Pallet<T>(_); #[pallet::hooks] @@ -209,13 +211,15 @@ pub mod pallet { StorageMap<_, Blake2_128Concat, u8, u16, ValueQuery, MyDefault<T>>; #[pallet::storage] - pub type Map2<T> = StorageMap<_, Twox64Concat, u16, u32>; + pub type Map2<T> = StorageMap<_, Twox64Concat, u16, u32, OptionQuery, GetDefault, ConstU32<3>>; #[pallet::storage] pub type DoubleMap<T> = StorageDoubleMap<_, Blake2_128Concat, u8, Twox64Concat, u16, u32>; #[pallet::storage] - pub type DoubleMap2<T> = StorageDoubleMap<_, Twox64Concat, u16, Blake2_128Concat, u32, u64>; + pub type DoubleMap2<T> = StorageDoubleMap< + _, Twox64Concat, u16, Blake2_128Concat, u32, u64, OptionQuery, GetDefault, ConstU32<5>, + >; #[pallet::storage] #[pallet::getter(fn nmap)] @@ -230,6 +234,9 @@ pub mod pallet { NMapKey<Blake2_128Concat, u32>, ), u64, + OptionQuery, + GetDefault, + ConstU32<11>, >; #[pallet::storage] @@ -240,7 +247,8 @@ pub mod pallet { #[cfg(feature = "conditional-storage")] #[pallet::storage] #[pallet::getter(fn conditional_map)] - pub type ConditionalMap<T> = StorageMap<_, Twox64Concat, u16, u32>; + pub type ConditionalMap<T> = + StorageMap<_, Twox64Concat, u16, u32, OptionQuery, GetDefault, ConstU32<12>>; #[cfg(feature = "conditional-storage")] #[pallet::storage] @@ -560,7 +568,7 @@ fn pallet_expand_deposit_event() { #[test] fn storage_expand() { use frame_support::pallet_prelude::*; - use frame_support::StoragePrefixedMap; + use frame_support::storage::StoragePrefixedMap; fn twox_64_concat(d: &[u8]) -> Vec<u8> { let mut v = twox_64(d).to_vec(); @@ -966,3 +974,97 @@ fn test_pallet_info_access() { assert_eq!(<Example as frame_support::traits::PalletInfoAccess>::index(), 1); assert_eq!(<Example2 as frame_support::traits::PalletInfoAccess>::index(), 2); } + +#[test] +fn test_storage_info() { + use frame_support::{ + StorageHasher, + traits::{StorageInfoTrait, StorageInfo}, + pallet_prelude::*, + }; + + let prefix = |pallet_name, storage_name| { + let mut res = [0u8; 32]; + res[0..16].copy_from_slice(&Twox128::hash(pallet_name)); + res[16..32].copy_from_slice(&Twox128::hash(storage_name)); + res + }; + + assert_eq!( + Example::storage_info(), + vec![ + StorageInfo { + prefix: prefix(b"Example", b"ValueWhereClause"), + max_values: Some(1), + max_size: Some(8), + }, + StorageInfo { + prefix: prefix(b"Example", b"Value"), + max_values: Some(1), + max_size: Some(4), + }, + StorageInfo { + prefix: prefix(b"Example", b"Map"), + max_values: None, + max_size: Some(3 + 16), + }, + StorageInfo { + prefix: prefix(b"Example", b"Map2"), + max_values: Some(3), + max_size: Some(6 + 8), + }, + StorageInfo { + prefix: prefix(b"Example", b"DoubleMap"), + max_values: None, + max_size: Some(7 + 16 + 8), + }, + StorageInfo { + prefix: prefix(b"Example", b"DoubleMap2"), + max_values: Some(5), + max_size: Some(14 + 8 + 16), + }, + StorageInfo { + prefix: prefix(b"Example", b"NMap"), + max_values: None, + max_size: Some(5 + 16), + }, + StorageInfo { + prefix: prefix(b"Example", b"NMap2"), + max_values: Some(11), + max_size: Some(14 + 8 + 16), + }, + #[cfg(feature = "conditional-storage")] + { + StorageInfo { + prefix: prefix(b"Example", b"ConditionalValue"), + max_values: Some(1), + max_size: Some(4), + } + }, + #[cfg(feature = "conditional-storage")] + { + StorageInfo { + prefix: prefix(b"Example", b"ConditionalMap"), + max_values: Some(12), + max_size: Some(6 + 8), + } + }, + #[cfg(feature = "conditional-storage")] + { + StorageInfo { + prefix: prefix(b"Example", b"ConditionalDoubleMap"), + max_values: None, + max_size: Some(7 + 16 + 8), + } + }, + #[cfg(feature = "conditional-storage")] + { + StorageInfo { + prefix: prefix(b"Example", b"ConditionalNMap"), + max_values: None, + max_size: Some(7 + 16 + 8), + } + }, + ], + ); +} diff --git a/substrate/frame/support/test/tests/pallet_instance.rs b/substrate/frame/support/test/tests/pallet_instance.rs index 46ff301f6712da8a38c8c21940f99a8d827d5ad0..7d6c6983b01b3844fea6b2a3a6e0c99b0c0fc5ab 100644 --- a/substrate/frame/support/test/tests/pallet_instance.rs +++ b/substrate/frame/support/test/tests/pallet_instance.rs @@ -418,7 +418,7 @@ fn pallet_expand_deposit_event() { #[test] fn storage_expand() { use frame_support::pallet_prelude::*; - use frame_support::StoragePrefixedMap; + use frame_support::storage::StoragePrefixedMap; fn twox_64_concat(d: &[u8]) -> Vec<u8> { let mut v = twox_64(d).to_vec(); diff --git a/substrate/frame/support/test/tests/pallet_ui/duplicate_store_attr.stderr b/substrate/frame/support/test/tests/pallet_ui/duplicate_store_attr.stderr index eed6ad4494edccfb7249c56ede85d95f20dac331..232144b8deaca9f701a1ff9cce3d6a59a0ed7695 100644 --- a/substrate/frame/support/test/tests/pallet_ui/duplicate_store_attr.stderr +++ b/substrate/frame/support/test/tests/pallet_ui/duplicate_store_attr.stderr @@ -1,5 +1,5 @@ -error: Invalid pallet::pallet, multiple argument pallet::generate_store found - --> $DIR/duplicate_store_attr.rs:12:33 +error: Unexpected duplicated attribute + --> $DIR/duplicate_store_attr.rs:12:12 | 12 | #[pallet::generate_store(trait Store)] - | ^^^^^ + | ^^^^^^^^^^^^^^ diff --git a/substrate/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.rs b/substrate/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.rs new file mode 100644 index 0000000000000000000000000000000000000000..569e59ef6ec27d1d12606048aca631235e99e4c6 --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.rs @@ -0,0 +1,27 @@ +#[frame_support::pallet] +mod pallet { + use frame_support::pallet_prelude::{Hooks, StorageValue}; + use frame_system::pallet_prelude::BlockNumberFor; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + #[pallet::generate_storage_info] + pub struct Pallet<T>(core::marker::PhantomData<T>); + + #[pallet::hooks] + impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {} + + #[pallet::call] + impl<T: Config> Pallet<T> {} + + #[derive(codec::Encode, codec::Decode)] + struct Bar; + + #[pallet::storage] + type Foo<T> = StorageValue<_, Bar>; +} + +fn main() { +} diff --git a/substrate/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr b/substrate/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr new file mode 100644 index 0000000000000000000000000000000000000000..ad415911bc933519282ec80152739491a0740eab --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr @@ -0,0 +1,8 @@ +error[E0277]: the trait bound `Bar: MaxEncodedLen` is not satisfied + --> $DIR/storage_info_unsatisfied.rs:10:12 + | +10 | #[pallet::generate_storage_info] + | ^^^^^^^^^^^^^^^^^^^^^ the trait `MaxEncodedLen` is not implemented for `Bar` + | + = note: required because of the requirements on the impl of `StorageInfoTrait` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo<T>, Bar>` + = note: required by `storage_info` diff --git a/substrate/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.rs b/substrate/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.rs new file mode 100644 index 0000000000000000000000000000000000000000..3d03099c3c4b69acdbb9273916199a61144b775c --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.rs @@ -0,0 +1,27 @@ +#[frame_support::pallet] +mod pallet { + use frame_support::pallet_prelude::{Hooks, StorageNMap, Twox64Concat, NMapKey}; + use frame_system::pallet_prelude::BlockNumberFor; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + #[pallet::generate_storage_info] + pub struct Pallet<T>(core::marker::PhantomData<T>); + + #[pallet::hooks] + impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {} + + #[pallet::call] + impl<T: Config> Pallet<T> {} + + #[derive(codec::Encode, codec::Decode)] + struct Bar; + + #[pallet::storage] + type Foo<T> = StorageNMap<_, NMapKey<Twox64Concat, Bar>, u32>; +} + +fn main() { +} diff --git a/substrate/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr b/substrate/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr new file mode 100644 index 0000000000000000000000000000000000000000..545520124bfee5a486e8c2e54a361050bfb88858 --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr @@ -0,0 +1,9 @@ +error[E0277]: the trait bound `Bar: MaxEncodedLen` is not satisfied + --> $DIR/storage_info_unsatisfied_nmap.rs:10:12 + | +10 | #[pallet::generate_storage_info] + | ^^^^^^^^^^^^^^^^^^^^^ the trait `MaxEncodedLen` is not implemented for `Bar` + | + = note: required because of the requirements on the impl of `KeyGeneratorMaxEncodedLen` for `NMapKey<frame_support::Twox64Concat, Bar>` + = note: required because of the requirements on the impl of `StorageInfoTrait` for `frame_support::pallet_prelude::StorageNMap<_GeneratedPrefixForStorageFoo<T>, NMapKey<frame_support::Twox64Concat, Bar>, u32>` + = note: required by `storage_info`