From 9bf62ef65d23be5eccc994aab058c8373a3db7b6 Mon Sep 17 00:00:00 2001
From: Guillaume Thiolliere <gui.thiolliere@gmail.com>
Date: Mon, 17 May 2021 15:44:24 +0200
Subject: [PATCH] Allow to specify some max number of values for storages in
 pallet macro. (#8735)

* implement max_values + storages info

* some formatting + doc

* rename StoragesInfo -> PalletStorageInfo

* merge both StorageInfoTrait and PalletStorageInfo

I think it is more future proof. In the future some storage could make
use of multiple prefix. Like one to store how much value has been
inserted, etc...

* Update frame/support/procedural/src/storage/parse.rs

Co-authored-by: Peter Goodspeed-Niklaus <coriolinus@users.noreply.github.com>

* Update frame/support/procedural/src/storage/storage_struct.rs

Co-authored-by: Peter Goodspeed-Niklaus <coriolinus@users.noreply.github.com>

* Fix max_size using hasher information

hasher now expose `max_len` which allows to computes their maximum len.
For hasher without concatenation, it is the size of the hash part,
for hasher with concatenation, it is the size of the hash part + max
encoded len of the key.

* fix tests

* fix ui tests

Co-authored-by: Peter Goodspeed-Niklaus <coriolinus@users.noreply.github.com>
---
 substrate/frame/support/procedural/src/lib.rs |  14 +-
 .../src/pallet/expand/pallet_struct.rs        |  44 ++-
 .../src/pallet/parse/pallet_struct.rs         |  76 +++--
 .../support/procedural/src/storage/mod.rs     |  13 +
 .../support/procedural/src/storage/parse.rs   |  34 ++
 .../procedural/src/storage/storage_info.rs    |  57 ++++
 .../procedural/src/storage/storage_struct.rs  | 158 +++++++++
 substrate/frame/support/src/hash.rs           |  38 +++
 substrate/frame/support/src/lib.rs            |  25 +-
 .../support/src/storage/types/double_map.rs   |  96 ++++--
 .../frame/support/src/storage/types/key.rs    |  27 +-
 .../frame/support/src/storage/types/map.rs    |  81 +++--
 .../frame/support/src/storage/types/mod.rs    |   2 +-
 .../frame/support/src/storage/types/nmap.rs   |  72 ++++-
 .../frame/support/src/storage/types/value.rs  |  27 +-
 substrate/frame/support/src/traits.rs         |   4 +-
 substrate/frame/support/src/traits/misc.rs    |  15 +
 substrate/frame/support/src/traits/storage.rs |  29 ++
 .../frame/support/test/tests/decl_storage.rs  | 299 +++++++++++++++---
 substrate/frame/support/test/tests/pallet.rs  | 114 ++++++-
 .../support/test/tests/pallet_instance.rs     |   2 +-
 .../pallet_ui/duplicate_store_attr.stderr     |   6 +-
 .../pallet_ui/storage_info_unsatisfied.rs     |  27 ++
 .../pallet_ui/storage_info_unsatisfied.stderr |   8 +
 .../storage_info_unsatisfied_nmap.rs          |  27 ++
 .../storage_info_unsatisfied_nmap.stderr      |   9 +
 26 files changed, 1158 insertions(+), 146 deletions(-)
 create mode 100644 substrate/frame/support/procedural/src/storage/storage_info.rs
 create mode 100644 substrate/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.rs
 create mode 100644 substrate/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr
 create mode 100644 substrate/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.rs
 create mode 100644 substrate/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr

diff --git a/substrate/frame/support/procedural/src/lib.rs b/substrate/frame/support/procedural/src/lib.rs
index 069339a9794..6b163ed5d79 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 556c6515d47..b655227cfc1 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 6c2c90bd61a..ba85da2d9e6 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 71bcf704f0d..3a1915e4314 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 93a1b844a84..ca97b7957c1 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 00000000000..ed07ccbfc71
--- /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 51b55bdc4f1..c1af0ee0701 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 22ccbeb6cee..5c4bfb34f5f 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 d87ab8e6ed4..0f96cdd0231 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 70b0c19f762..8c23354817f 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 5eb608233b8..79fc33a24e8 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 b9c3044f93f..ac2817c6887 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 5b7aa61d376..f6106567131 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 1a2b6d4d55d..f018ccc38b4 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 6a92a2a632c..67d2e374192 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 2d7fb3db736..295995b1bfe 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 d3010358dd8..7ec29522cbc 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 c42e1abf73e..37957ceb677 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 a2690b1379d..ef7b577ab6b 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 5db5856fd9d..0a768c79e77 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 46ff301f671..7d6c6983b01 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 eed6ad4494e..232144b8dea 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 00000000000..569e59ef6ec
--- /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 00000000000..ad415911bc9
--- /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 00000000000..3d03099c3c4
--- /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 00000000000..545520124bf
--- /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`
-- 
GitLab