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`