From 1ebcbe1c346adecc02bd2e408b3561f03ea476b8 Mon Sep 17 00:00:00 2001
From: Guillaume Thiolliere <gui.thiolliere@gmail.com>
Date: Mon, 27 Sep 2021 10:25:24 +0200
Subject: [PATCH] pallet macro: allow to declare individual unbounded storage
 for those who cannot go into PoV (#9670)

* allow unbounded individual storage

* better doc

* fix UI tests

* update doc

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

Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>

Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>
---
 .../src/pallet/expand/pallet_struct.rs        | 49 +++++++++------
 .../procedural/src/pallet/parse/storage.rs    | 62 +++++++++++++------
 substrate/frame/support/src/lib.rs            | 11 +++-
 substrate/frame/support/test/tests/pallet.rs  | 22 +++++++
 .../storage_invalid_attribute.stderr          |  2 +-
 .../pallet_ui/storage_multiple_getters.stderr |  2 +-
 .../pallet_ui/storage_multiple_renames.stderr |  2 +-
 7 files changed, 106 insertions(+), 44 deletions(-)

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 a217742fec5..0ad8a25b8e9 100644
--- a/substrate/frame/support/procedural/src/pallet/expand/pallet_struct.rs
+++ b/substrate/frame/support/procedural/src/pallet/expand/pallet_struct.rs
@@ -98,28 +98,39 @@ pub fn expand_pallet_struct(def: &mut Def) -> proc_macro2::TokenStream {
 		)
 	};
 
-	// Depending on the flag `generate_storage_info` we use partial or full storage info from
-	// storage.
-	let (storage_info_span, storage_info_trait, storage_info_method) =
-		if let Some(span) = def.pallet_struct.generate_storage_info {
-			(
-				span,
-				quote::quote_spanned!(span => StorageInfoTrait),
-				quote::quote_spanned!(span => storage_info),
-			)
-		} else {
-			let span = def.pallet_struct.attr_span;
-			(
-				span,
-				quote::quote_spanned!(span => PartialStorageInfoTrait),
-				quote::quote_spanned!(span => partial_storage_info),
-			)
-		};
+	let storage_info_span =
+		def.pallet_struct.generate_storage_info.unwrap_or(def.pallet_struct.attr_span);
 
 	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<_>>();
 
+	// Depending on the flag `generate_storage_info` and the storage attribute `unbounded`, we use
+	// partial or full storage info from storage.
+	let storage_info_traits = &def
+		.storages
+		.iter()
+		.map(|storage| {
+			if storage.unbounded || def.pallet_struct.generate_storage_info.is_none() {
+				quote::quote_spanned!(storage_info_span => PartialStorageInfoTrait)
+			} else {
+				quote::quote_spanned!(storage_info_span => StorageInfoTrait)
+			}
+		})
+		.collect::<Vec<_>>();
+
+	let storage_info_methods = &def
+		.storages
+		.iter()
+		.map(|storage| {
+			if storage.unbounded || def.pallet_struct.generate_storage_info.is_none() {
+				quote::quote_spanned!(storage_info_span => partial_storage_info)
+			} else {
+				quote::quote_spanned!(storage_info_span => storage_info)
+			}
+		})
+		.collect::<Vec<_>>();
+
 	let storage_info = quote::quote_spanned!(storage_info_span =>
 		impl<#type_impl_gen> #frame_support::traits::StorageInfoTrait
 			for #pallet_ident<#type_use_gen>
@@ -136,8 +147,8 @@ pub fn expand_pallet_struct(def: &mut Def) -> proc_macro2::TokenStream {
 					{
 						let mut storage_info = <
 							#storage_names<#type_use_gen>
-							as #frame_support::traits::#storage_info_trait
-						>::#storage_info_method();
+							as #frame_support::traits::#storage_info_traits
+						>::#storage_info_methods();
 						res.append(&mut storage_info);
 					}
 				)*
diff --git a/substrate/frame/support/procedural/src/pallet/parse/storage.rs b/substrate/frame/support/procedural/src/pallet/parse/storage.rs
index 8075daacb6f..cd29baf93d8 100644
--- a/substrate/frame/support/procedural/src/pallet/parse/storage.rs
+++ b/substrate/frame/support/procedural/src/pallet/parse/storage.rs
@@ -27,6 +27,7 @@ mod keyword {
 	syn::custom_keyword!(pallet);
 	syn::custom_keyword!(getter);
 	syn::custom_keyword!(storage_prefix);
+	syn::custom_keyword!(unbounded);
 	syn::custom_keyword!(OptionQuery);
 	syn::custom_keyword!(ValueQuery);
 }
@@ -34,15 +35,17 @@ mod keyword {
 /// Parse for one of the following:
 /// * `#[pallet::getter(fn dummy)]`
 /// * `#[pallet::storage_prefix = "CustomName"]`
+/// * `#[pallet::unbounded]`
 pub enum PalletStorageAttr {
 	Getter(syn::Ident, proc_macro2::Span),
 	StorageName(syn::LitStr, proc_macro2::Span),
+	Unbounded(proc_macro2::Span),
 }
 
 impl PalletStorageAttr {
 	fn attr_span(&self) -> proc_macro2::Span {
 		match self {
-			Self::Getter(_, span) | Self::StorageName(_, span) => *span,
+			Self::Getter(_, span) | Self::StorageName(_, span) | Self::Unbounded(span) => *span,
 		}
 	}
 }
@@ -76,12 +79,45 @@ impl syn::parse::Parse for PalletStorageAttr {
 			})?;
 
 			Ok(Self::StorageName(renamed_prefix, attr_span))
+		} else if lookahead.peek(keyword::unbounded) {
+			content.parse::<keyword::unbounded>()?;
+
+			Ok(Self::Unbounded(attr_span))
 		} else {
 			Err(lookahead.error())
 		}
 	}
 }
 
+struct PalletStorageAttrInfo {
+	getter: Option<syn::Ident>,
+	rename_as: Option<syn::LitStr>,
+	unbounded: bool,
+}
+
+impl PalletStorageAttrInfo {
+	fn from_attrs(attrs: Vec<PalletStorageAttr>) -> syn::Result<Self> {
+		let mut getter = None;
+		let mut rename_as = None;
+		let mut unbounded = false;
+		for attr in attrs {
+			match attr {
+				PalletStorageAttr::Getter(ident, ..) if getter.is_none() => getter = Some(ident),
+				PalletStorageAttr::StorageName(name, ..) if rename_as.is_none() =>
+					rename_as = Some(name),
+				PalletStorageAttr::Unbounded(..) if !unbounded => unbounded = true,
+				attr =>
+					return Err(syn::Error::new(
+						attr.attr_span(),
+						"Invalid attribute: Duplicate attribute",
+					)),
+			}
+		}
+
+		Ok(PalletStorageAttrInfo { getter, rename_as, unbounded })
+	}
+}
+
 /// The value and key types used by storages. Needed to expand metadata.
 pub enum Metadata {
 	Value { value: syn::Type },
@@ -131,6 +167,8 @@ pub struct StorageDef {
 	/// generics of the storage.
 	/// If generics are not named, this is none.
 	pub named_generics: Option<StorageGenerics>,
+	/// If the value stored in this storage is unbounded.
+	pub unbounded: bool,
 }
 
 /// The parsed generic from the
@@ -629,25 +667,8 @@ impl StorageDef {
 		};
 
 		let attrs: Vec<PalletStorageAttr> = helper::take_item_pallet_attrs(&mut item.attrs)?;
-		let (mut getters, mut names) = attrs
-			.into_iter()
-			.partition::<Vec<_>, _>(|attr| matches!(attr, PalletStorageAttr::Getter(..)));
-		if getters.len() > 1 {
-			let msg = "Invalid pallet::storage, multiple argument pallet::getter found";
-			return Err(syn::Error::new(getters[1].attr_span(), msg))
-		}
-		if names.len() > 1 {
-			let msg = "Invalid pallet::storage, multiple argument pallet::storage_prefix found";
-			return Err(syn::Error::new(names[1].attr_span(), msg))
-		}
-		let getter = getters.pop().map(|attr| match attr {
-			PalletStorageAttr::Getter(ident, _) => ident,
-			_ => unreachable!(),
-		});
-		let rename_as = names.pop().map(|attr| match attr {
-			PalletStorageAttr::StorageName(lit, _) => lit,
-			_ => unreachable!(),
-		});
+		let PalletStorageAttrInfo { getter, rename_as, unbounded } =
+			PalletStorageAttrInfo::from_attrs(attrs)?;
 
 		let cfg_attrs = helper::get_item_cfg_attrs(&item.attrs);
 
@@ -704,6 +725,7 @@ impl StorageDef {
 			where_clause,
 			cfg_attrs,
 			named_generics,
+			unbounded,
 		})
 	}
 }
diff --git a/substrate/frame/support/src/lib.rs b/substrate/frame/support/src/lib.rs
index 45969870736..f56af036eb2 100644
--- a/substrate/frame/support/src/lib.rs
+++ b/substrate/frame/support/src/lib.rs
@@ -1411,15 +1411,17 @@ pub mod pallet_prelude {
 /// `<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.:
+/// `#[pallet::generate_storage_info]`, e.g.:
 /// ```ignore
 /// #[pallet::pallet]
-/// #[pallet::set_storage_max_encoded_len]
+/// #[pallet::generate_storage_info]
 /// pub struct Pallet<T>(_);
 /// ```
 ///
 /// This require all storage to implement the trait [`traits::StorageInfoTrait`], thus all keys
 /// and value types must bound [`pallet_prelude::MaxEncodedLen`].
+/// Some individual storage can opt-out from this constraint by using `#[pallet::unbounded]`,
+/// see `#[pallet::storage]` documentation.
 ///
 /// As the macro implements [`traits::GetStorageVersion`], the current storage version needs to
 /// be communicated to the macro. This can be done by using the `storage_version` attribute:
@@ -1721,6 +1723,11 @@ pub mod pallet_prelude {
 /// pub(super) type MyStorage<T> = StorageMap<_, Blake2_128Concat, u32, u32>;
 /// ```
 ///
+/// The optional attribute `#[pallet::unbounded]` allows to declare the storage as unbounded.
+/// When implementating the storage info (when #[pallet::generate_storage_info]` is specified
+/// on the pallet struct placeholder), the size of the storage will be declared as unbounded.
+/// This can be useful for storage which can never go into PoV (Proof of Validity).
+///
 /// The optional attributes `#[cfg(..)]` allow conditional compilation for the storage.
 ///
 /// E.g:
diff --git a/substrate/frame/support/test/tests/pallet.rs b/substrate/frame/support/test/tests/pallet.rs
index 7eb8431ce14..25fc2d46d25 100644
--- a/substrate/frame/support/test/tests/pallet.rs
+++ b/substrate/frame/support/test/tests/pallet.rs
@@ -329,6 +329,10 @@ pub mod pallet {
 	pub type SomeCountedStorageMap<T> =
 		CountedStorageMap<Hasher = Twox64Concat, Key = u8, Value = u32>;
 
+	#[pallet::storage]
+	#[pallet::unbounded]
+	pub type Unbounded<T> = StorageValue<Value = Vec<u8>>;
+
 	#[pallet::genesis_config]
 	#[derive(Default)]
 	pub struct GenesisConfig {
@@ -917,6 +921,10 @@ fn storage_expand() {
 		assert_eq!(unhashed::get::<u32>(&k), Some(2u32));
 		let k = [twox_128(b"Example"), twox_128(b"CounterForRenamedCountedMap")].concat();
 		assert_eq!(unhashed::get::<u32>(&k), Some(1u32));
+
+		pallet::Unbounded::<Runtime>::put(vec![1, 2]);
+		let k = [twox_128(b"Example"), twox_128(b"Unbounded")].concat();
+		assert_eq!(unhashed::get::<Vec<u8>>(&k), Some(vec![1, 2]));
 	})
 }
 
@@ -1170,6 +1178,13 @@ fn metadata() {
 						default: vec![0, 0, 0, 0],
 						docs: vec!["Counter for the related counted storage map"],
 					},
+					StorageEntryMetadata {
+						name: "Unbounded",
+						modifier: StorageEntryModifier::Optional,
+						ty: StorageEntryType::Plain(meta_type::<Vec<u8>>()),
+						default: vec![0],
+						docs: vec![],
+					},
 				],
 			}),
 			calls: Some(meta_type::<pallet::Call<Runtime>>().into()),
@@ -1411,6 +1426,13 @@ fn test_storage_info() {
 				max_values: Some(1),
 				max_size: Some(4),
 			},
+			StorageInfo {
+				pallet_name: b"Example".to_vec(),
+				storage_name: b"Unbounded".to_vec(),
+				prefix: prefix(b"Example", b"Unbounded").to_vec(),
+				max_values: Some(1),
+				max_size: None,
+			},
 		],
 	);
 
diff --git a/substrate/frame/support/test/tests/pallet_ui/storage_invalid_attribute.stderr b/substrate/frame/support/test/tests/pallet_ui/storage_invalid_attribute.stderr
index bf93d99cf56..6313bd691f9 100644
--- a/substrate/frame/support/test/tests/pallet_ui/storage_invalid_attribute.stderr
+++ b/substrate/frame/support/test/tests/pallet_ui/storage_invalid_attribute.stderr
@@ -1,4 +1,4 @@
-error: expected `getter` or `storage_prefix`
+error: expected one of: `getter`, `storage_prefix`, `unbounded`
   --> $DIR/storage_invalid_attribute.rs:16:12
    |
 16 |     #[pallet::generate_store(pub trait Store)]
diff --git a/substrate/frame/support/test/tests/pallet_ui/storage_multiple_getters.stderr b/substrate/frame/support/test/tests/pallet_ui/storage_multiple_getters.stderr
index 188eed3cb0d..40f57f16e0d 100644
--- a/substrate/frame/support/test/tests/pallet_ui/storage_multiple_getters.stderr
+++ b/substrate/frame/support/test/tests/pallet_ui/storage_multiple_getters.stderr
@@ -1,4 +1,4 @@
-error: Invalid pallet::storage, multiple argument pallet::getter found
+error: Invalid attribute: Duplicate attribute
   --> $DIR/storage_multiple_getters.rs:20:3
    |
 20 |     #[pallet::getter(fn foo_error)]
diff --git a/substrate/frame/support/test/tests/pallet_ui/storage_multiple_renames.stderr b/substrate/frame/support/test/tests/pallet_ui/storage_multiple_renames.stderr
index 9288d131d95..52cb7e85adf 100644
--- a/substrate/frame/support/test/tests/pallet_ui/storage_multiple_renames.stderr
+++ b/substrate/frame/support/test/tests/pallet_ui/storage_multiple_renames.stderr
@@ -1,4 +1,4 @@
-error: Invalid pallet::storage, multiple argument pallet::storage_prefix found
+error: Invalid attribute: Duplicate attribute
   --> $DIR/storage_multiple_renames.rs:20:3
    |
 20 |     #[pallet::storage_prefix = "Baz"]
-- 
GitLab