diff --git a/Cargo.lock b/Cargo.lock index b6c9dd138b6f6..3a45aa9919f24 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2594,6 +2594,7 @@ dependencies = [ "array-bytes", "assert_matches", "bitflags", + "docify", "environmental", "frame-metadata", "frame-support-procedural", diff --git a/frame/democracy/src/migrations/unlock_and_unreserve_all_funds.rs b/frame/democracy/src/migrations/unlock_and_unreserve_all_funds.rs index 1f46070d1d7fc..188c475f64d0e 100644 --- a/frame/democracy/src/migrations/unlock_and_unreserve_all_funds.rs +++ b/frame/democracy/src/migrations/unlock_and_unreserve_all_funds.rs @@ -18,15 +18,67 @@ //! A migration that unreserves all deposit and unlocks all stake held in the context of this //! pallet. -use crate::{BalanceOf, DEMOCRACY_ID}; +use crate::{PropIndex, Voting, DEMOCRACY_ID}; use core::iter::Sum; -use frame_support::traits::{LockableCurrency, OnRuntimeUpgrade, ReservableCurrency}; +use frame_support::{ + pallet_prelude::ValueQuery, + storage_alias, + traits::{Currency, LockableCurrency, OnRuntimeUpgrade, ReservableCurrency}, + weights::RuntimeDbWeight, + Parameter, Twox64Concat, +}; use sp_core::Get; -use sp_runtime::{traits::Zero, Saturating}; +use sp_runtime::{traits::Zero, BoundedVec, Saturating}; use sp_std::{collections::btree_map::BTreeMap, vec::Vec}; const LOG_TARGET: &str = "runtime::democracy::migrations::unlock_and_unreserve_all_funds"; +type BalanceOf = + <::Currency as Currency<::AccountId>>::Balance; + +/// The configuration for [`UnlockAndUnreserveAllFunds`]. +pub trait UnlockConfig: 'static { + /// The account ID used in the runtime. + type AccountId: Parameter + Ord; + /// The currency type used in the runtime. + /// + /// Should match the currency type previously used for the pallet, if applicable. + type Currency: LockableCurrency + ReservableCurrency; + /// The name of the pallet as previously configured in + /// [`construct_runtime!`](frame_support::construct_runtime). + type PalletName: Get<&'static str>; + /// The maximum number of votes as configured previously in the runtime. + type MaxVotes: Get; + /// The maximum deposit as configured previously in the runtime. + type MaxDeposits: Get; + /// The DB weight as configured in the runtime to calculate the correct weight. + type DbWeight: Get; + /// The block number as configured in the runtime. + type BlockNumber: Parameter + Zero + Copy + Ord; +} + +#[storage_alias(dynamic)] +type DepositOf = StorageMap< + ::PalletName, + Twox64Concat, + PropIndex, + (BoundedVec<::AccountId, ::MaxDeposits>, BalanceOf), +>; + +#[storage_alias(dynamic)] +type VotingOf = StorageMap< + ::PalletName, + Twox64Concat, + ::AccountId, + Voting< + BalanceOf, + ::AccountId, + ::BlockNumber, + ::MaxVotes, + >, + ValueQuery, +>; + /// A migration that unreserves all deposit and unlocks all stake held in the context of this /// pallet. /// @@ -35,9 +87,9 @@ const LOG_TARGET: &str = "runtime::democracy::migrations::unlock_and_unreserve_a /// The pallet should be made inoperable before this migration is run. /// /// (See also [`RemovePallet`][frame_support::migrations::RemovePallet]) -pub struct UnlockAndUnreserveAllFunds(sp_std::marker::PhantomData); +pub struct UnlockAndUnreserveAllFunds(sp_std::marker::PhantomData); -impl UnlockAndUnreserveAllFunds { +impl UnlockAndUnreserveAllFunds { /// Calculates and returns the total amounts reserved by each account by this pallet, and all /// accounts with locks in the context of this pallet. /// @@ -63,7 +115,7 @@ impl UnlockAndUnreserveAllFunds { // Get all deposits (reserved). let mut total_voting_vec_entries: u64 = 0; - let account_deposits: BTreeMap> = crate::DepositOf::::iter() + let account_deposits: BTreeMap> = DepositOf::::iter() .flat_map(|(_prop_index, (accounts, balance))| { // Count the number of deposits deposit_of_len.saturating_inc(); @@ -81,7 +133,7 @@ impl UnlockAndUnreserveAllFunds { }); // Voter accounts have amounts locked. - let account_stakes: BTreeMap> = crate::VotingOf::::iter() + let account_stakes: BTreeMap> = VotingOf::::iter() .map(|(account_id, voting)| (account_id, voting.locked_balance())) .collect(); let voting_of_len = account_stakes.len() as u64; @@ -100,7 +152,7 @@ impl UnlockAndUnreserveAllFunds { } } -impl OnRuntimeUpgrade for UnlockAndUnreserveAllFunds +impl OnRuntimeUpgrade for UnlockAndUnreserveAllFunds where BalanceOf: Sum, { @@ -241,14 +293,32 @@ where mod test { use super::*; use crate::{ - tests::{new_test_ext, Test}, + tests::{new_test_ext, Balances, Test}, DepositOf, Voting, VotingOf, }; use frame_support::{ - assert_ok, + assert_ok, parameter_types, traits::{Currency, OnRuntimeUpgrade, ReservableCurrency, WithdrawReasons}, BoundedVec, }; + use frame_system::pallet_prelude::BlockNumberFor; + use sp_core::ConstU32; + + parameter_types! { + const PalletName: &'static str = "Democracy"; + } + + struct UnlockConfigImpl; + + impl super::UnlockConfig for UnlockConfigImpl { + type Currency = Balances; + type MaxVotes = ConstU32<100>; + type MaxDeposits = ConstU32<1000>; + type AccountId = u64; + type BlockNumber = BlockNumberFor; + type DbWeight = (); + type PalletName = PalletName; + } #[test] fn unreserve_works_for_depositer() { @@ -288,10 +358,10 @@ mod test { ); // Run the migration. - let bytes = UnlockAndUnreserveAllFunds::::pre_upgrade() + let bytes = UnlockAndUnreserveAllFunds::::pre_upgrade() .unwrap_or_else(|e| panic!("pre_upgrade failed: {:?}", e)); - UnlockAndUnreserveAllFunds::::on_runtime_upgrade(); - assert_ok!(UnlockAndUnreserveAllFunds::::post_upgrade(bytes)); + UnlockAndUnreserveAllFunds::::on_runtime_upgrade(); + assert_ok!(UnlockAndUnreserveAllFunds::::post_upgrade(bytes)); // Assert the reserved balance was reduced by the expected amount. assert_eq!( @@ -342,10 +412,10 @@ mod test { ); // Run the migration. - let bytes = UnlockAndUnreserveAllFunds::::pre_upgrade() + let bytes = UnlockAndUnreserveAllFunds::::pre_upgrade() .unwrap_or_else(|e| panic!("pre_upgrade failed: {:?}", e)); - UnlockAndUnreserveAllFunds::::on_runtime_upgrade(); - assert_ok!(UnlockAndUnreserveAllFunds::::post_upgrade(bytes)); + UnlockAndUnreserveAllFunds::::on_runtime_upgrade(); + assert_ok!(UnlockAndUnreserveAllFunds::::post_upgrade(bytes)); // Assert the voter lock was removed assert_eq!( diff --git a/frame/support/Cargo.toml b/frame/support/Cargo.toml index fab6167f3d50f..da6ee19e04a0d 100644 --- a/frame/support/Cargo.toml +++ b/frame/support/Cargo.toml @@ -42,6 +42,7 @@ k256 = { version = "0.13.1", default-features = false, features = ["ecdsa"] } environmental = { version = "1.1.4", default-features = false } sp-genesis-builder = { version = "0.1.0", default-features=false, path = "../../primitives/genesis-builder" } serde_json = { version = "1.0.85", default-features = false, features = ["alloc"] } +docify = "0.2.1" aquamarine = { version = "0.3.2" } diff --git a/frame/support/procedural/src/lib.rs b/frame/support/procedural/src/lib.rs index f0d4c30a939c1..2a46696ed4f70 100644 --- a/frame/support/procedural/src/lib.rs +++ b/frame/support/procedural/src/lib.rs @@ -547,8 +547,8 @@ pub fn __create_tt_macro(input: TokenStream) -> TokenStream { } #[proc_macro_attribute] -pub fn storage_alias(_: TokenStream, input: TokenStream) -> TokenStream { - storage_alias::storage_alias(input.into()) +pub fn storage_alias(attributes: TokenStream, input: TokenStream) -> TokenStream { + storage_alias::storage_alias(attributes.into(), input.into()) .unwrap_or_else(|r| r.into_compile_error()) .into() } diff --git a/frame/support/procedural/src/storage_alias.rs b/frame/support/procedural/src/storage_alias.rs index b44a7ee997fe2..d1d1aba47998e 100644 --- a/frame/support/procedural/src/storage_alias.rs +++ b/frame/support/procedural/src/storage_alias.rs @@ -22,78 +22,48 @@ use frame_support_procedural_tools::generate_crate_access_2018; use proc_macro2::{Span, TokenStream}; use quote::{quote, ToTokens}; use syn::{ - ext::IdentExt, parenthesized, parse::{Parse, ParseStream}, punctuated::Punctuated, - token, Attribute, Error, Ident, Result, Token, Type, TypeParam, Visibility, WhereClause, + spanned::Spanned, + token, + visit::Visit, + Attribute, Error, Ident, Result, Token, Type, TypeParam, Visibility, WhereClause, }; -/// Represents a path that only consists of [`Ident`] separated by `::`. -struct SimplePath { - leading_colon: Option, - segments: Punctuated, +/// Extension trait for [`Type`]. +trait TypeExt { + fn get_ident(&self) -> Option<&Ident>; + fn contains_ident(&self, ident: &Ident) -> bool; } -impl SimplePath { - /// Returns the [`Ident`] of this path. - /// - /// It only returns `Some(_)` if there is exactly one element and no leading colon. +impl TypeExt for Type { fn get_ident(&self) -> Option<&Ident> { - if self.segments.len() != 1 || self.leading_colon.is_some() { - None - } else { - self.segments.first() + match self { + Type::Path(p) => match &p.qself { + Some(qself) => qself.ty.get_ident(), + None => p.path.get_ident(), + }, + _ => None, } } -} - -impl Parse for SimplePath { - fn parse(input: ParseStream<'_>) -> Result { - Ok(Self { - leading_colon: if input.peek(Token![::]) { Some(input.parse()?) } else { None }, - segments: Punctuated::parse_separated_nonempty_with(input, |p| Ident::parse_any(p))?, - }) - } -} - -impl ToTokens for SimplePath { - fn to_tokens(&self, tokens: &mut TokenStream) { - self.leading_colon.to_tokens(tokens); - self.segments.to_tokens(tokens); - } -} - -/// Represents generics which only support [`Ident`] separated by commas as you would pass it to a -/// type. -struct TypeGenerics { - lt_token: Token![<], - params: Punctuated, - gt_token: Token![>], -} -impl TypeGenerics { - /// Returns the generics for types declarations etc. - fn iter(&self) -> impl Iterator { - self.params.iter() - } -} + fn contains_ident(&self, ident: &Ident) -> bool { + struct ContainsIdent<'a> { + ident: &'a Ident, + found: bool, + } + impl<'a, 'ast> Visit<'ast> for ContainsIdent<'a> { + fn visit_ident(&mut self, i: &'ast Ident) { + if i == self.ident { + self.found = true; + } + } + } -impl Parse for TypeGenerics { - fn parse(input: ParseStream<'_>) -> Result { - Ok(Self { - lt_token: input.parse()?, - params: Punctuated::parse_separated_nonempty(input)?, - gt_token: input.parse()?, - }) - } -} - -impl ToTokens for TypeGenerics { - fn to_tokens(&self, tokens: &mut TokenStream) { - self.lt_token.to_tokens(tokens); - self.params.to_tokens(tokens); - self.gt_token.to_tokens(tokens); + let mut visitor = ContainsIdent { ident, found: false }; + syn::visit::visit_type(&mut visitor, self); + visitor.found } } @@ -142,13 +112,22 @@ mod storage_types { syn::custom_keyword!(StorageNMap); } +/// The types of prefixes the storage alias macro supports. +mod prefix_types { + // Use the verbatim/unmodified input name as the prefix. + syn::custom_keyword!(verbatim); + // The input type is a pallet and its pallet name should be used as the prefix. + syn::custom_keyword!(pallet_name); + // The input type implements `Get<'static str>` and this `str` should be used as the prefix. + syn::custom_keyword!(dynamic); +} + /// The supported storage types enum StorageType { Value { _kw: storage_types::StorageValue, _lt_token: Token![<], - prefix: SimplePath, - prefix_generics: Option, + prefix: Type, _value_comma: Token![,], value_ty: Type, query_type: Option<(Token![,], Type)>, @@ -158,8 +137,7 @@ enum StorageType { Map { _kw: storage_types::StorageMap, _lt_token: Token![<], - prefix: SimplePath, - prefix_generics: Option, + prefix: Type, _hasher_comma: Token![,], hasher_ty: Type, _key_comma: Token![,], @@ -173,8 +151,7 @@ enum StorageType { CountedMap { _kw: storage_types::CountedStorageMap, _lt_token: Token![<], - prefix: SimplePath, - prefix_generics: Option, + prefix: Type, _hasher_comma: Token![,], hasher_ty: Type, _key_comma: Token![,], @@ -188,8 +165,7 @@ enum StorageType { DoubleMap { _kw: storage_types::StorageDoubleMap, _lt_token: Token![<], - prefix: SimplePath, - prefix_generics: Option, + prefix: Type, _hasher1_comma: Token![,], hasher1_ty: Type, _key1_comma: Token![,], @@ -207,8 +183,7 @@ enum StorageType { NMap { _kw: storage_types::StorageNMap, _lt_token: Token![<], - prefix: SimplePath, - prefix_generics: Option, + prefix: Type, _paren_comma: Token![,], _paren_token: token::Paren, key_types: Punctuated, @@ -231,6 +206,7 @@ impl StorageType { visibility: &Visibility, attributes: &[Attribute], ) -> TokenStream { + let storage_instance_generics = &storage_instance.generics; let storage_instance = &storage_instance.name; let attributes = attributes.iter(); let storage_generics = storage_generics.map(|g| { @@ -240,22 +216,20 @@ impl StorageType { }); match self { - Self::Value { value_ty, query_type, prefix_generics, .. } => { + Self::Value { value_ty, query_type, .. } => { let query_type = query_type.as_ref().map(|(c, t)| quote!(#c #t)); quote! { #( #attributes )* #visibility type #storage_name #storage_generics = #crate_::storage::types::StorageValue< - #storage_instance #prefix_generics, + #storage_instance #storage_instance_generics, #value_ty #query_type >; } }, - Self::CountedMap { - value_ty, query_type, hasher_ty, key_ty, prefix_generics, .. - } | - Self::Map { value_ty, query_type, hasher_ty, key_ty, prefix_generics, .. } => { + Self::CountedMap { value_ty, query_type, hasher_ty, key_ty, .. } | + Self::Map { value_ty, query_type, hasher_ty, key_ty, .. } => { let query_type = query_type.as_ref().map(|(c, t)| quote!(#c #t)); let map_type = Ident::new( match self { @@ -268,7 +242,7 @@ impl StorageType { quote! { #( #attributes )* #visibility type #storage_name #storage_generics = #crate_::storage::types::#map_type< - #storage_instance #prefix_generics, + #storage_instance #storage_instance_generics, #hasher_ty, #key_ty, #value_ty @@ -283,7 +257,6 @@ impl StorageType { key1_ty, hasher2_ty, key2_ty, - prefix_generics, .. } => { let query_type = query_type.as_ref().map(|(c, t)| quote!(#c #t)); @@ -291,7 +264,7 @@ impl StorageType { quote! { #( #attributes )* #visibility type #storage_name #storage_generics = #crate_::storage::types::StorageDoubleMap< - #storage_instance #prefix_generics, + #storage_instance #storage_instance_generics, #hasher1_ty, #key1_ty, #hasher2_ty, @@ -301,14 +274,14 @@ impl StorageType { >; } }, - Self::NMap { value_ty, query_type, key_types, prefix_generics, .. } => { + Self::NMap { value_ty, query_type, key_types, .. } => { let query_type = query_type.as_ref().map(|(c, t)| quote!(#c #t)); let key_types = key_types.iter(); quote! { #( #attributes )* #visibility type #storage_name #storage_generics = #crate_::storage::types::StorageNMap< - #storage_instance #prefix_generics, + #storage_instance #storage_instance_generics, ( #( #key_types ),* ), #value_ty #query_type @@ -319,7 +292,7 @@ impl StorageType { } /// The prefix for this storage type. - fn prefix(&self) -> &SimplePath { + fn prefix(&self) -> &Type { match self { Self::Value { prefix, .. } | Self::Map { prefix, .. } | @@ -328,17 +301,6 @@ impl StorageType { Self::DoubleMap { prefix, .. } => prefix, } } - - /// The prefix generics for this storage type. - fn prefix_generics(&self) -> Option<&TypeGenerics> { - match self { - Self::Value { prefix_generics, .. } | - Self::Map { prefix_generics, .. } | - Self::CountedMap { prefix_generics, .. } | - Self::NMap { prefix_generics, .. } | - Self::DoubleMap { prefix_generics, .. } => prefix_generics.as_ref(), - } - } } impl Parse for StorageType { @@ -353,23 +315,11 @@ impl Parse for StorageType { } }; - let parse_pallet_generics = |input: ParseStream<'_>| -> Result> { - let lookahead = input.lookahead1(); - if lookahead.peek(Token![<]) { - Ok(Some(input.parse()?)) - } else if lookahead.peek(Token![,]) { - Ok(None) - } else { - Err(lookahead.error()) - } - }; - if lookahead.peek(storage_types::StorageValue) { Ok(Self::Value { _kw: input.parse()?, _lt_token: input.parse()?, prefix: input.parse()?, - prefix_generics: parse_pallet_generics(input)?, _value_comma: input.parse()?, value_ty: input.parse()?, query_type: parse_query_type(input)?, @@ -381,7 +331,6 @@ impl Parse for StorageType { _kw: input.parse()?, _lt_token: input.parse()?, prefix: input.parse()?, - prefix_generics: parse_pallet_generics(input)?, _hasher_comma: input.parse()?, hasher_ty: input.parse()?, _key_comma: input.parse()?, @@ -397,7 +346,6 @@ impl Parse for StorageType { _kw: input.parse()?, _lt_token: input.parse()?, prefix: input.parse()?, - prefix_generics: parse_pallet_generics(input)?, _hasher_comma: input.parse()?, hasher_ty: input.parse()?, _key_comma: input.parse()?, @@ -413,7 +361,6 @@ impl Parse for StorageType { _kw: input.parse()?, _lt_token: input.parse()?, prefix: input.parse()?, - prefix_generics: parse_pallet_generics(input)?, _hasher1_comma: input.parse()?, hasher1_ty: input.parse()?, _key1_comma: input.parse()?, @@ -434,7 +381,6 @@ impl Parse for StorageType { _kw: input.parse()?, _lt_token: input.parse()?, prefix: input.parse()?, - prefix_generics: parse_pallet_generics(input)?, _paren_comma: input.parse()?, _paren_token: parenthesized!(content in input), key_types: Punctuated::parse_terminated(&content)?, @@ -508,20 +454,50 @@ impl Parse for Input { } } +/// Defines which type of prefix the storage alias is using. +#[derive(Clone, Copy)] +enum PrefixType { + /// An appropriate prefix will be determined automatically. + /// + /// If generics are passed, this is assumed to be a pallet and the pallet name should be used. + /// Otherwise use the verbatim passed name as prefix. + Compatibility, + /// The provided ident/name will be used as the prefix. + Verbatim, + /// The provided type will be used to determine the prefix. This type must + /// implement `PalletInfoAccess` which specifies the proper name. This + /// name is then used as the prefix. + PalletName, + /// Uses the provided type implementing `Get<'static str>` to determine the prefix. + Dynamic, +} + /// Implementation of the `storage_alias` attribute macro. -pub fn storage_alias(input: TokenStream) -> Result { +pub fn storage_alias(attributes: TokenStream, input: TokenStream) -> Result { let input = syn::parse2::(input)?; let crate_ = generate_crate_access_2018("frame-support")?; + let prefix_type = if attributes.is_empty() { + PrefixType::Compatibility + } else if syn::parse2::(attributes.clone()).is_ok() { + PrefixType::Verbatim + } else if syn::parse2::(attributes.clone()).is_ok() { + PrefixType::PalletName + } else if syn::parse2::(attributes.clone()).is_ok() { + PrefixType::Dynamic + } else { + return Err(Error::new(attributes.span(), "Unknown attributes")) + }; + let storage_instance = generate_storage_instance( &crate_, &input.storage_name, input.storage_generics.as_ref(), input.where_clause.as_ref(), input.storage_type.prefix(), - input.storage_type.prefix_generics(), &input.visibility, matches!(input.storage_type, StorageType::CountedMap { .. }), + prefix_type, )?; let definition = input.storage_type.generate_type_declaration( @@ -545,6 +521,7 @@ pub fn storage_alias(input: TokenStream) -> Result { /// The storage instance to use for the storage alias. struct StorageInstance { name: Ident, + generics: TokenStream, code: TokenStream, } @@ -554,42 +531,84 @@ fn generate_storage_instance( storage_name: &Ident, storage_generics: Option<&SimpleGenerics>, storage_where_clause: Option<&WhereClause>, - prefix: &SimplePath, - prefix_generics: Option<&TypeGenerics>, + prefix: &Type, visibility: &Visibility, is_counted_map: bool, + prefix_type: PrefixType, ) -> Result { - if let Some(ident) = prefix.get_ident().filter(|i| *i == "_") { - return Err(Error::new(ident.span(), "`_` is not allowed as prefix by `storage_alias`.")) + if let Type::Infer(_) = prefix { + return Err(Error::new(prefix.span(), "`_` is not allowed as prefix by `storage_alias`.")) } - let (pallet_prefix, impl_generics, type_generics) = - if let Some((prefix_generics, storage_generics)) = - prefix_generics.and_then(|p| storage_generics.map(|s| (p, s))) - { - let type_generics = prefix_generics.iter(); - let type_generics2 = prefix_generics.iter(); - let impl_generics = storage_generics - .impl_generics() - .filter(|g| prefix_generics.params.iter().any(|pg| *pg == g.ident)); + let impl_generics_used_by_prefix = storage_generics + .as_ref() + .map(|g| { + g.impl_generics() + .filter(|g| prefix.contains_ident(&g.ident)) + .collect::>() + }) + .unwrap_or_default(); + + let (pallet_prefix, impl_generics, type_generics) = match prefix_type { + PrefixType::Compatibility => + if !impl_generics_used_by_prefix.is_empty() { + let type_generics = impl_generics_used_by_prefix.iter().map(|g| &g.ident); + let impl_generics = impl_generics_used_by_prefix.iter(); + + ( + quote! { + < #prefix as #crate_::traits::PalletInfoAccess>::name() + }, + quote!( #( #impl_generics ),* ), + quote!( #( #type_generics ),* ), + ) + } else if let Some(prefix) = prefix.get_ident() { + let prefix_str = prefix.to_string(); + + (quote!(#prefix_str), quote!(), quote!()) + } else { + return Err(Error::new_spanned( + prefix, + "If there are no generics, the prefix is only allowed to be an identifier.", + )) + }, + PrefixType::Verbatim => { + let prefix_str = match prefix.get_ident() { + Some(p) => p.to_string(), + None => + return Err(Error::new_spanned( + prefix, + "Prefix type `verbatim` requires that the prefix is an ident.", + )), + }; + + (quote!(#prefix_str), quote!(), quote!()) + }, + PrefixType::PalletName => { + let type_generics = impl_generics_used_by_prefix.iter().map(|g| &g.ident); + let impl_generics = impl_generics_used_by_prefix.iter(); ( quote! { - <#prefix < #( #type_generics2 ),* > as #crate_::traits::PalletInfoAccess>::name() + <#prefix as #crate_::traits::PalletInfoAccess>::name() }, quote!( #( #impl_generics ),* ), quote!( #( #type_generics ),* ), ) - } else if let Some(prefix) = prefix.get_ident() { - let prefix_str = prefix.to_string(); + }, + PrefixType::Dynamic => { + let type_generics = impl_generics_used_by_prefix.iter().map(|g| &g.ident); + let impl_generics = impl_generics_used_by_prefix.iter(); - (quote!(#prefix_str), quote!(), quote!()) - } else { - return Err(Error::new_spanned( - prefix, - "If there are no generics, the prefix is only allowed to be an identifier.", - )) - }; + ( + quote! { + <#prefix as #crate_::traits::Get<_>>::get() + }, + quote!( #( #impl_generics ),* ), + quote!( #( #type_generics ),* ), + ) + }, + }; let where_clause = storage_where_clause.map(|w| quote!(#w)).unwrap_or_default(); @@ -644,5 +663,5 @@ fn generate_storage_instance( #counter_code }; - Ok(StorageInstance { name, code }) + Ok(StorageInstance { name, code, generics: quote!( < #type_generics > ) }) } diff --git a/frame/support/src/lib.rs b/frame/support/src/lib.rs index d37490c341724..4cbbe17a9968b 100644 --- a/frame/support/src/lib.rs +++ b/frame/support/src/lib.rs @@ -74,6 +74,8 @@ pub mod inherent; pub mod instances; pub mod migrations; pub mod storage; +#[cfg(test)] +mod tests; pub mod traits; pub mod weights; #[doc(hidden)] @@ -127,80 +129,50 @@ impl TypeId for PalletId { const TYPE_ID: [u8; 4] = *b"modl"; } -/// Generate a new type alias for [`storage::types::StorageValue`], -/// [`storage::types::StorageMap`], [`storage::types::StorageDoubleMap`] -/// and [`storage::types::StorageNMap`]. +/// Generate a [`#[pallet::storage]`](pallet_macros::storage) alias outside of a pallet. /// -/// Useful for creating a *storage-like* struct for test and migrations. +/// This storage alias works similarly to the [`#[pallet::storage]`](pallet_macros::storage) +/// attribute macro. It supports [`StorageValue`](storage::types::StorageValue), +/// [`StorageMap`](storage::types::StorageMap), +/// [`StorageDoubleMap`](storage::types::StorageDoubleMap) and +/// [`StorageNMap`](storage::types::StorageNMap). The main difference to the normal +/// [`#[pallet::storage]`](pallet_macros::storage) is the flexibility around declaring the +/// storage prefix to use. The storage prefix determines where to find the value in the +/// storage. [`#[pallet::storage]`](pallet_macros::storage) uses the name of the pallet as +/// declared in [`construct_runtime!`]. /// -/// ``` -/// # use frame_support::storage_alias; -/// use frame_support::codec; -/// use frame_support::Twox64Concat; -/// // generate a storage value with type u32. -/// #[storage_alias] -/// type StorageName = StorageValue; -/// -/// // generate a double map from `(u32, u32)` (with hashers `Twox64Concat` for each key) -/// // to `Vec` -/// #[storage_alias] -/// type OtherStorageName = StorageDoubleMap< -/// OtherPrefix, -/// Twox64Concat, -/// u32, -/// Twox64Concat, -/// u32, -/// Vec, -/// >; -/// -/// // optionally specify the query type -/// use frame_support::pallet_prelude::{ValueQuery, OptionQuery}; -/// #[storage_alias] -/// type ValueName = StorageValue; -/// #[storage_alias] -/// type SomeStorageName = StorageMap< -/// Prefix, -/// Twox64Concat, -/// u32, -/// Vec, -/// ValueQuery, -/// >; -/// -/// // generate a map from `Config::AccountId` (with hasher `Twox64Concat`) to `Vec` -/// trait Config { type AccountId: codec::FullCodec; } -/// #[storage_alias] -/// type GenericStorage = StorageMap::AccountId, Vec>; -/// -/// // It also supports NMap -/// use frame_support::storage::types::Key as NMapKey; -/// -/// #[storage_alias] -/// type SomeNMap = StorageNMap, NMapKey), Vec>; -/// -/// // Using pallet name as prefix. -/// // -/// // When the first generic argument is taking generic arguments it is expected to be a pallet. -/// // The prefix will then be the pallet name as configured in the runtime through -/// // `construct_runtime!`. -/// -/// # struct Pallet(std::marker::PhantomData<(T, I)>); -/// # impl frame_support::traits::PalletInfoAccess for Pallet { -/// # fn index() -> usize { 0 } -/// # fn name() -> &'static str { "pallet" } -/// # fn module_name() -> &'static str { "module" } -/// # fn crate_version() -> frame_support::traits::CrateVersion { unimplemented!() } -/// # } -/// -/// #[storage_alias] -/// type SomeValue = StorageValue, u64>; -/// -/// // Pallet with instance -/// -/// #[storage_alias] -/// type SomeValue2 = StorageValue, u64>; -/// -/// # fn main() {} -/// ``` +/// The flexibility around declaring the storage prefix makes this macro very useful for +/// writing migrations etc. +/// +/// # Examples +/// +/// There are different ways to declare the `prefix` to use. The `prefix` type can either be +/// declared explicetly by passing it to the macro as an attribute or by letting the macro +/// guess on what the `prefix` type is. The `prefix` is always passed as the first generic +/// argument to the type declaration. When using [`#[pallet::storage]`](pallet_macros::storage) +/// this first generic argument is always `_`. Besides declaring the `prefix`, the rest of the +/// type declaration works as with [`#[pallet::storage]`](pallet_macros::storage). +/// +/// 1. Use the `verbatim` prefix type. This prefix type uses the given identifier as the +/// `prefix`: +#[doc = docify::embed!("src/tests/storage_alias.rs", verbatim_attribute)] +/// +/// 2. Use the `pallet_name` prefix type. This prefix type uses the name of the pallet as +/// configured in [`construct_runtime!`] as the `prefix`: +#[doc = docify::embed!("src/tests/storage_alias.rs", pallet_name_attribute)] +/// It requires that the given prefix type implements +/// [`PalletInfoAccess`](traits::PalletInfoAccess) (which is always the case for FRAME pallet +/// structs). In the example above, `Pallet` is the prefix type. +/// +/// 3. Use the `dynamic` prefix type. This prefix type calls [`Get::get()`](traits::Get::get) +/// to get the `prefix`: +#[doc = docify::embed!("src/tests/storage_alias.rs", dynamic_attribute)] +/// It requires that the given prefix type implements [`Get<'static str>`](traits::Get). +/// +/// 4. Let the macro "guess" what kind of prefix type to use. This only supports verbatim or +/// pallet name. The macro uses the presence of generic arguments to the prefix type as +/// an indication that it should use the pallet name as the `prefix`: +#[doc = docify::embed!("src/tests/storage_alias.rs", storage_alias_guess)] pub use frame_support_procedural::storage_alias; pub use frame_support_procedural::derive_impl; @@ -818,703 +790,6 @@ pub use serde::{Deserialize, Serialize}; #[cfg(not(no_std))] pub use macro_magic; -#[cfg(test)] -pub mod tests { - use super::*; - use crate::metadata_ir::{ - PalletStorageMetadataIR, StorageEntryMetadataIR, StorageEntryModifierIR, - StorageEntryTypeIR, StorageHasherIR, - }; - use sp_io::{MultiRemovalResults, TestExternalities}; - use sp_runtime::{generic, traits::BlakeTwo256, BuildStorage}; - use sp_std::result; - - pub use self::frame_system::{pallet_prelude::*, Config, Pallet}; - - #[pallet] - pub mod frame_system { - #[allow(unused)] - use super::{frame_system, frame_system::pallet_prelude::*}; - pub use crate::dispatch::RawOrigin; - use crate::pallet_prelude::*; - - #[pallet::pallet] - pub struct Pallet(_); - - #[pallet::config] - #[pallet::disable_frame_system_supertrait_check] - pub trait Config: 'static { - type Block: Parameter + sp_runtime::traits::Block; - type AccountId; - type BaseCallFilter: crate::traits::Contains; - type RuntimeOrigin; - type RuntimeCall; - type PalletInfo: crate::traits::PalletInfo; - type DbWeight: Get; - } - - #[pallet::error] - pub enum Error { - /// Required by construct_runtime - CallFiltered, - } - - #[pallet::origin] - pub type Origin = RawOrigin<::AccountId>; - - #[pallet::call] - impl Pallet {} - - #[pallet::storage] - pub type Data = StorageMap<_, Twox64Concat, u32, u64, ValueQuery>; - - #[pallet::storage] - pub type OptionLinkedMap = StorageMap<_, Blake2_128Concat, u32, u32, OptionQuery>; - - #[pallet::storage] - #[pallet::getter(fn generic_data)] - pub type GenericData = - StorageMap<_, Identity, BlockNumberFor, BlockNumberFor, ValueQuery>; - - #[pallet::storage] - #[pallet::getter(fn generic_data2)] - pub type GenericData2 = - StorageMap<_, Blake2_128Concat, BlockNumberFor, BlockNumberFor, OptionQuery>; - - #[pallet::storage] - pub type DataDM = - StorageDoubleMap<_, Twox64Concat, u32, Blake2_128Concat, u32, u64, ValueQuery>; - - #[pallet::storage] - pub type GenericDataDM = StorageDoubleMap< - _, - Blake2_128Concat, - BlockNumberFor, - Identity, - BlockNumberFor, - BlockNumberFor, - ValueQuery, - >; - - #[pallet::storage] - pub type GenericData2DM = StorageDoubleMap< - _, - Blake2_128Concat, - BlockNumberFor, - Twox64Concat, - BlockNumberFor, - BlockNumberFor, - OptionQuery, - >; - - #[pallet::storage] - #[pallet::unbounded] - pub type AppendableDM = StorageDoubleMap< - _, - Blake2_128Concat, - u32, - Blake2_128Concat, - BlockNumberFor, - Vec, - ValueQuery, - >; - - #[pallet::genesis_config] - pub struct GenesisConfig { - pub data: Vec<(u32, u64)>, - pub test_config: Vec<(u32, u32, u64)>, - #[serde(skip)] - pub _config: sp_std::marker::PhantomData, - } - - impl Default for GenesisConfig { - fn default() -> Self { - Self { - _config: Default::default(), - data: vec![(15u32, 42u64)], - test_config: vec![(15u32, 16u32, 42u64)], - } - } - } - - #[pallet::genesis_build] - impl BuildGenesisConfig for GenesisConfig { - fn build(&self) { - for (k, v) in &self.data { - >::insert(k, v); - } - for (k1, k2, v) in &self.test_config { - >::insert(k1, k2, v); - } - } - } - - pub mod pallet_prelude { - pub type OriginFor = ::RuntimeOrigin; - - pub type HeaderFor = - <::Block as sp_runtime::traits::HeaderProvider>::HeaderT; - - pub type BlockNumberFor = as sp_runtime::traits::Header>::Number; - } - } - - type BlockNumber = u32; - type AccountId = u32; - type Header = generic::Header; - type UncheckedExtrinsic = generic::UncheckedExtrinsic; - type Block = generic::Block; - - crate::construct_runtime!( - pub enum Runtime - { - System: self::frame_system, - } - ); - - impl Config for Runtime { - type Block = Block; - type AccountId = AccountId; - type BaseCallFilter = crate::traits::Everything; - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type PalletInfo = PalletInfo; - type DbWeight = (); - } - - fn new_test_ext() -> TestExternalities { - RuntimeGenesisConfig::default().build_storage().unwrap().into() - } - - trait Sorted { - fn sorted(self) -> Self; - } - - impl Sorted for Vec { - fn sorted(mut self) -> Self { - self.sort(); - self - } - } - - #[test] - fn storage_alias_works() { - new_test_ext().execute_with(|| { - #[crate::storage_alias] - type GenericData2 = - StorageMap, BlockNumberFor>; - - assert_eq!(Pallet::::generic_data2(5), None); - GenericData2::::insert(5, 5); - assert_eq!(Pallet::::generic_data2(5), Some(5)); - - /// Some random docs that ensure that docs are accepted - #[crate::storage_alias] - pub type GenericData = - StorageMap, BlockNumberFor>; - }); - } - - #[test] - fn storage_value_mutate_exists_should_work() { - new_test_ext().execute_with(|| { - #[crate::storage_alias] - pub type Value = StorageValue; - - assert!(!Value::exists()); - - Value::mutate_exists(|v| *v = Some(1)); - assert!(Value::exists()); - assert_eq!(Value::get(), Some(1)); - - // removed if mutated to `None` - Value::mutate_exists(|v| *v = None); - assert!(!Value::exists()); - }); - } - - #[test] - fn storage_value_try_mutate_exists_should_work() { - new_test_ext().execute_with(|| { - #[crate::storage_alias] - pub type Value = StorageValue; - - type TestResult = result::Result<(), &'static str>; - - assert!(!Value::exists()); - - // mutated if `Ok` - assert_ok!(Value::try_mutate_exists(|v| -> TestResult { - *v = Some(1); - Ok(()) - })); - assert!(Value::exists()); - assert_eq!(Value::get(), Some(1)); - - // no-op if `Err` - assert_noop!( - Value::try_mutate_exists(|v| -> TestResult { - *v = Some(2); - Err("nah") - }), - "nah" - ); - assert_eq!(Value::get(), Some(1)); - - // removed if mutated to`None` - assert_ok!(Value::try_mutate_exists(|v| -> TestResult { - *v = None; - Ok(()) - })); - assert!(!Value::exists()); - }); - } - - #[test] - fn map_issue_3318() { - new_test_ext().execute_with(|| { - type OptionLinkedMap = self::frame_system::OptionLinkedMap; - - OptionLinkedMap::insert(1, 1); - assert_eq!(OptionLinkedMap::get(1), Some(1)); - OptionLinkedMap::insert(1, 2); - assert_eq!(OptionLinkedMap::get(1), Some(2)); - }); - } - - #[test] - fn map_swap_works() { - new_test_ext().execute_with(|| { - type OptionLinkedMap = self::frame_system::OptionLinkedMap; - - OptionLinkedMap::insert(0, 0); - OptionLinkedMap::insert(1, 1); - OptionLinkedMap::insert(2, 2); - OptionLinkedMap::insert(3, 3); - - let collect = || OptionLinkedMap::iter().collect::>().sorted(); - assert_eq!(collect(), vec![(0, 0), (1, 1), (2, 2), (3, 3)]); - - // Two existing - OptionLinkedMap::swap(1, 2); - assert_eq!(collect(), vec![(0, 0), (1, 2), (2, 1), (3, 3)]); - - // Back to normal - OptionLinkedMap::swap(2, 1); - assert_eq!(collect(), vec![(0, 0), (1, 1), (2, 2), (3, 3)]); - - // Left existing - OptionLinkedMap::swap(2, 5); - assert_eq!(collect(), vec![(0, 0), (1, 1), (3, 3), (5, 2)]); - - // Right existing - OptionLinkedMap::swap(5, 2); - assert_eq!(collect(), vec![(0, 0), (1, 1), (2, 2), (3, 3)]); - }); - } - - #[test] - fn double_map_swap_works() { - new_test_ext().execute_with(|| { - type DataDM = self::frame_system::DataDM; - - DataDM::insert(0, 1, 1); - DataDM::insert(1, 0, 2); - DataDM::insert(1, 1, 3); - - let get_all = || { - vec![ - DataDM::get(0, 1), - DataDM::get(1, 0), - DataDM::get(1, 1), - DataDM::get(2, 0), - DataDM::get(2, 1), - ] - }; - assert_eq!(get_all(), vec![1, 2, 3, 0, 0]); - - // Two existing - DataDM::swap(0, 1, 1, 0); - assert_eq!(get_all(), vec![2, 1, 3, 0, 0]); - - // Left existing - DataDM::swap(1, 0, 2, 0); - assert_eq!(get_all(), vec![2, 0, 3, 1, 0]); - - // Right existing - DataDM::swap(2, 1, 1, 1); - assert_eq!(get_all(), vec![2, 0, 0, 1, 3]); - }); - } - - #[test] - fn map_basic_insert_remove_should_work() { - new_test_ext().execute_with(|| { - type Map = self::frame_system::Data; - - // initialized during genesis - assert_eq!(Map::get(&15u32), 42u64); - - // get / insert / take - let key = 17u32; - assert_eq!(Map::get(&key), 0u64); - Map::insert(key, 4u64); - assert_eq!(Map::get(&key), 4u64); - assert_eq!(Map::take(&key), 4u64); - assert_eq!(Map::get(&key), 0u64); - - // mutate - Map::mutate(&key, |val| { - *val = 15; - }); - assert_eq!(Map::get(&key), 15u64); - - // remove - Map::remove(&key); - assert_eq!(Map::get(&key), 0u64); - }); - } - - #[test] - fn map_iteration_should_work() { - new_test_ext().execute_with(|| { - type Map = self::frame_system::Data; - - assert_eq!(Map::iter().collect::>().sorted(), vec![(15, 42)]); - // insert / remove - let key = 17u32; - Map::insert(key, 4u64); - assert_eq!(Map::iter().collect::>().sorted(), vec![(15, 42), (key, 4)]); - assert_eq!(Map::take(&15), 42u64); - assert_eq!(Map::take(&key), 4u64); - assert_eq!(Map::iter().collect::>().sorted(), vec![]); - - // Add couple of more elements - Map::insert(key, 42u64); - assert_eq!(Map::iter().collect::>().sorted(), vec![(key, 42)]); - Map::insert(key + 1, 43u64); - assert_eq!(Map::iter().collect::>().sorted(), vec![(key, 42), (key + 1, 43)]); - - // mutate - let key = key + 2; - Map::mutate(&key, |val| { - *val = 15; - }); - assert_eq!( - Map::iter().collect::>().sorted(), - vec![(key - 2, 42), (key - 1, 43), (key, 15)] - ); - Map::mutate(&key, |val| { - *val = 17; - }); - assert_eq!( - Map::iter().collect::>().sorted(), - vec![(key - 2, 42), (key - 1, 43), (key, 17)] - ); - - // remove first - Map::remove(&key); - assert_eq!( - Map::iter().collect::>().sorted(), - vec![(key - 2, 42), (key - 1, 43)] - ); - - // remove last from the list - Map::remove(&(key - 2)); - assert_eq!(Map::iter().collect::>().sorted(), vec![(key - 1, 43)]); - - // remove the last element - Map::remove(&(key - 1)); - assert_eq!(Map::iter().collect::>().sorted(), vec![]); - }); - } - - #[test] - fn double_map_basic_insert_remove_remove_prefix_with_commit_should_work() { - let key1 = 17u32; - let key2 = 18u32; - type DoubleMap = self::frame_system::DataDM; - let mut e = new_test_ext(); - e.execute_with(|| { - // initialized during genesis - assert_eq!(DoubleMap::get(&15u32, &16u32), 42u64); - - // get / insert / take - assert_eq!(DoubleMap::get(&key1, &key2), 0u64); - DoubleMap::insert(&key1, &key2, &4u64); - assert_eq!(DoubleMap::get(&key1, &key2), 4u64); - assert_eq!(DoubleMap::take(&key1, &key2), 4u64); - assert_eq!(DoubleMap::get(&key1, &key2), 0u64); - - // mutate - DoubleMap::mutate(&key1, &key2, |val| *val = 15); - assert_eq!(DoubleMap::get(&key1, &key2), 15u64); - - // remove - DoubleMap::remove(&key1, &key2); - assert_eq!(DoubleMap::get(&key1, &key2), 0u64); - - // remove prefix - DoubleMap::insert(&key1, &key2, &4u64); - DoubleMap::insert(&key1, &(key2 + 1), &4u64); - DoubleMap::insert(&(key1 + 1), &key2, &4u64); - DoubleMap::insert(&(key1 + 1), &(key2 + 1), &4u64); - }); - e.commit_all().unwrap(); - e.execute_with(|| { - assert!(matches!( - DoubleMap::clear_prefix(&key1, u32::max_value(), None), - MultiRemovalResults { maybe_cursor: None, backend: 2, unique: 2, loops: 2 } - )); - assert_eq!(DoubleMap::get(&key1, &key2), 0u64); - assert_eq!(DoubleMap::get(&key1, &(key2 + 1)), 0u64); - assert_eq!(DoubleMap::get(&(key1 + 1), &key2), 4u64); - assert_eq!(DoubleMap::get(&(key1 + 1), &(key2 + 1)), 4u64); - }); - } - - #[test] - fn double_map_basic_insert_remove_remove_prefix_should_work() { - new_test_ext().execute_with(|| { - let key1 = 17u32; - let key2 = 18u32; - type DoubleMap = self::frame_system::DataDM; - - // initialized during genesis - assert_eq!(DoubleMap::get(&15u32, &16u32), 42u64); - - // get / insert / take - assert_eq!(DoubleMap::get(&key1, &key2), 0u64); - DoubleMap::insert(&key1, &key2, &4u64); - assert_eq!(DoubleMap::get(&key1, &key2), 4u64); - assert_eq!(DoubleMap::take(&key1, &key2), 4u64); - assert_eq!(DoubleMap::get(&key1, &key2), 0u64); - - // mutate - DoubleMap::mutate(&key1, &key2, |val| *val = 15); - assert_eq!(DoubleMap::get(&key1, &key2), 15u64); - - // remove - DoubleMap::remove(&key1, &key2); - assert_eq!(DoubleMap::get(&key1, &key2), 0u64); - - // remove prefix - DoubleMap::insert(&key1, &key2, &4u64); - DoubleMap::insert(&key1, &(key2 + 1), &4u64); - DoubleMap::insert(&(key1 + 1), &key2, &4u64); - DoubleMap::insert(&(key1 + 1), &(key2 + 1), &4u64); - // all in overlay - assert!(matches!( - DoubleMap::clear_prefix(&key1, u32::max_value(), None), - MultiRemovalResults { maybe_cursor: None, backend: 0, unique: 0, loops: 0 } - )); - // Note this is the incorrect answer (for now), since we are using v2 of - // `clear_prefix`. - // When we switch to v3, then this will become: - // MultiRemovalResults:: { maybe_cursor: None, backend: 0, unique: 2, loops: 2 }, - assert!(matches!( - DoubleMap::clear_prefix(&key1, u32::max_value(), None), - MultiRemovalResults { maybe_cursor: None, backend: 0, unique: 0, loops: 0 } - )); - assert_eq!(DoubleMap::get(&key1, &key2), 0u64); - assert_eq!(DoubleMap::get(&key1, &(key2 + 1)), 0u64); - assert_eq!(DoubleMap::get(&(key1 + 1), &key2), 4u64); - assert_eq!(DoubleMap::get(&(key1 + 1), &(key2 + 1)), 4u64); - }); - } - - #[test] - fn double_map_append_should_work() { - new_test_ext().execute_with(|| { - type DoubleMap = self::frame_system::AppendableDM; - - let key1 = 17u32; - let key2 = 18u32; - - DoubleMap::insert(&key1, &key2, &vec![1]); - DoubleMap::append(&key1, &key2, 2); - assert_eq!(DoubleMap::get(&key1, &key2), &[1, 2]); - }); - } - - #[test] - fn double_map_mutate_exists_should_work() { - new_test_ext().execute_with(|| { - type DoubleMap = self::frame_system::DataDM; - - let (key1, key2) = (11, 13); - - // mutated - DoubleMap::mutate_exists(key1, key2, |v| *v = Some(1)); - assert_eq!(DoubleMap::get(&key1, key2), 1); - - // removed if mutated to `None` - DoubleMap::mutate_exists(key1, key2, |v| *v = None); - assert!(!DoubleMap::contains_key(&key1, key2)); - }); - } - - #[test] - fn double_map_try_mutate_exists_should_work() { - new_test_ext().execute_with(|| { - type DoubleMap = self::frame_system::DataDM; - type TestResult = Result<(), &'static str>; - - let (key1, key2) = (11, 13); - - // mutated if `Ok` - assert_ok!(DoubleMap::try_mutate_exists(key1, key2, |v| -> TestResult { - *v = Some(1); - Ok(()) - })); - assert_eq!(DoubleMap::get(&key1, key2), 1); - - // no-op if `Err` - assert_noop!( - DoubleMap::try_mutate_exists(key1, key2, |v| -> TestResult { - *v = Some(2); - Err("nah") - }), - "nah" - ); - - // removed if mutated to`None` - assert_ok!(DoubleMap::try_mutate_exists(key1, key2, |v| -> TestResult { - *v = None; - Ok(()) - })); - assert!(!DoubleMap::contains_key(&key1, key2)); - }); - } - - fn expected_metadata() -> PalletStorageMetadataIR { - PalletStorageMetadataIR { - prefix: "System", - entries: vec![ - StorageEntryMetadataIR { - name: "Data", - modifier: StorageEntryModifierIR::Default, - ty: StorageEntryTypeIR::Map { - hashers: vec![StorageHasherIR::Twox64Concat], - key: scale_info::meta_type::(), - value: scale_info::meta_type::(), - }, - default: vec![0, 0, 0, 0, 0, 0, 0, 0], - docs: vec![], - }, - StorageEntryMetadataIR { - name: "OptionLinkedMap", - modifier: StorageEntryModifierIR::Optional, - ty: StorageEntryTypeIR::Map { - hashers: vec![StorageHasherIR::Blake2_128Concat], - key: scale_info::meta_type::(), - value: scale_info::meta_type::(), - }, - default: vec![0], - docs: vec![], - }, - StorageEntryMetadataIR { - name: "GenericData", - modifier: StorageEntryModifierIR::Default, - ty: StorageEntryTypeIR::Map { - hashers: vec![StorageHasherIR::Identity], - key: scale_info::meta_type::(), - value: scale_info::meta_type::(), - }, - default: vec![0, 0, 0, 0], - docs: vec![], - }, - StorageEntryMetadataIR { - name: "GenericData2", - modifier: StorageEntryModifierIR::Optional, - ty: StorageEntryTypeIR::Map { - hashers: vec![StorageHasherIR::Blake2_128Concat], - key: scale_info::meta_type::(), - value: scale_info::meta_type::(), - }, - default: vec![0], - docs: vec![], - }, - StorageEntryMetadataIR { - name: "DataDM", - modifier: StorageEntryModifierIR::Default, - ty: StorageEntryTypeIR::Map { - hashers: vec![ - StorageHasherIR::Twox64Concat, - StorageHasherIR::Blake2_128Concat, - ], - key: scale_info::meta_type::<(u32, u32)>(), - value: scale_info::meta_type::(), - }, - default: vec![0, 0, 0, 0, 0, 0, 0, 0], - docs: vec![], - }, - StorageEntryMetadataIR { - name: "GenericDataDM", - modifier: StorageEntryModifierIR::Default, - ty: StorageEntryTypeIR::Map { - hashers: vec![StorageHasherIR::Blake2_128Concat, StorageHasherIR::Identity], - key: scale_info::meta_type::<(u32, u32)>(), - value: scale_info::meta_type::(), - }, - default: vec![0, 0, 0, 0], - docs: vec![], - }, - StorageEntryMetadataIR { - name: "GenericData2DM", - modifier: StorageEntryModifierIR::Optional, - ty: StorageEntryTypeIR::Map { - hashers: vec![ - StorageHasherIR::Blake2_128Concat, - StorageHasherIR::Twox64Concat, - ], - key: scale_info::meta_type::<(u32, u32)>(), - value: scale_info::meta_type::(), - }, - default: vec![0], - docs: vec![], - }, - StorageEntryMetadataIR { - name: "AppendableDM", - modifier: StorageEntryModifierIR::Default, - ty: StorageEntryTypeIR::Map { - hashers: vec![ - StorageHasherIR::Blake2_128Concat, - StorageHasherIR::Blake2_128Concat, - ], - key: scale_info::meta_type::<(u32, u32)>(), - value: scale_info::meta_type::>(), - }, - default: vec![0], - docs: vec![], - }, - ], - } - } - - #[test] - fn store_metadata() { - let metadata = Pallet::::storage_metadata(); - pretty_assertions::assert_eq!(expected_metadata(), metadata); - } - - parameter_types! { - storage StorageParameter: u64 = 10; - } - - #[test] - fn check_storage_parameter_type_works() { - TestExternalities::default().execute_with(|| { - assert_eq!(sp_io::hashing::twox_128(b":StorageParameter:"), StorageParameter::key()); - - assert_eq!(10, StorageParameter::get()); - - StorageParameter::set(&300); - assert_eq!(300, StorageParameter::get()); - }) - } -} - /// Private module re-exporting items used by frame support macros. #[doc(hidden)] pub mod _private { diff --git a/frame/support/src/tests/mod.rs b/frame/support/src/tests/mod.rs new file mode 100644 index 0000000000000..cb4b4e82418b1 --- /dev/null +++ b/frame/support/src/tests/mod.rs @@ -0,0 +1,629 @@ +// This file is part of Substrate. + +// Copyright (C) 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. + +use super::*; +use crate::metadata_ir::{ + PalletStorageMetadataIR, StorageEntryMetadataIR, StorageEntryModifierIR, StorageEntryTypeIR, + StorageHasherIR, +}; +use sp_io::{MultiRemovalResults, TestExternalities}; +use sp_runtime::{generic, traits::BlakeTwo256, BuildStorage}; + +pub use self::frame_system::{pallet_prelude::*, Config, Pallet}; + +mod storage_alias; + +#[pallet] +pub mod frame_system { + #[allow(unused)] + use super::{frame_system, frame_system::pallet_prelude::*}; + pub use crate::dispatch::RawOrigin; + use crate::pallet_prelude::*; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + #[pallet::disable_frame_system_supertrait_check] + pub trait Config: 'static { + type Block: Parameter + sp_runtime::traits::Block; + type AccountId; + type BaseCallFilter: crate::traits::Contains; + type RuntimeOrigin; + type RuntimeCall; + type PalletInfo: crate::traits::PalletInfo; + type DbWeight: Get; + } + + #[pallet::error] + pub enum Error { + /// Required by construct_runtime + CallFiltered, + } + + #[pallet::origin] + pub type Origin = RawOrigin<::AccountId>; + + #[pallet::call] + impl Pallet {} + + #[pallet::storage] + pub type Data = StorageMap<_, Twox64Concat, u32, u64, ValueQuery>; + + #[pallet::storage] + pub type OptionLinkedMap = StorageMap<_, Blake2_128Concat, u32, u32, OptionQuery>; + + #[pallet::storage] + #[pallet::getter(fn generic_data)] + pub type GenericData = + StorageMap<_, Identity, BlockNumberFor, BlockNumberFor, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn generic_data2)] + pub type GenericData2 = + StorageMap<_, Blake2_128Concat, BlockNumberFor, BlockNumberFor, OptionQuery>; + + #[pallet::storage] + pub type DataDM = + StorageDoubleMap<_, Twox64Concat, u32, Blake2_128Concat, u32, u64, ValueQuery>; + + #[pallet::storage] + pub type GenericDataDM = StorageDoubleMap< + _, + Blake2_128Concat, + BlockNumberFor, + Identity, + BlockNumberFor, + BlockNumberFor, + ValueQuery, + >; + + #[pallet::storage] + pub type GenericData2DM = StorageDoubleMap< + _, + Blake2_128Concat, + BlockNumberFor, + Twox64Concat, + BlockNumberFor, + BlockNumberFor, + OptionQuery, + >; + + #[pallet::storage] + #[pallet::unbounded] + pub type AppendableDM = StorageDoubleMap< + _, + Blake2_128Concat, + u32, + Blake2_128Concat, + BlockNumberFor, + Vec, + ValueQuery, + >; + + #[pallet::genesis_config] + pub struct GenesisConfig { + pub data: Vec<(u32, u64)>, + pub test_config: Vec<(u32, u32, u64)>, + #[serde(skip)] + pub _config: sp_std::marker::PhantomData, + } + + impl Default for GenesisConfig { + fn default() -> Self { + Self { + _config: Default::default(), + data: vec![(15u32, 42u64)], + test_config: vec![(15u32, 16u32, 42u64)], + } + } + } + + #[pallet::genesis_build] + impl BuildGenesisConfig for GenesisConfig { + fn build(&self) { + for (k, v) in &self.data { + >::insert(k, v); + } + for (k1, k2, v) in &self.test_config { + >::insert(k1, k2, v); + } + } + } + + pub mod pallet_prelude { + pub type OriginFor = ::RuntimeOrigin; + + pub type HeaderFor = + <::Block as sp_runtime::traits::HeaderProvider>::HeaderT; + + pub type BlockNumberFor = as sp_runtime::traits::Header>::Number; + } +} + +type BlockNumber = u32; +type AccountId = u32; +type Header = generic::Header; +type UncheckedExtrinsic = generic::UncheckedExtrinsic; +type Block = generic::Block; + +crate::construct_runtime!( + pub enum Runtime + { + System: self::frame_system, + } +); + +impl Config for Runtime { + type Block = Block; + type AccountId = AccountId; + type BaseCallFilter = crate::traits::Everything; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type PalletInfo = PalletInfo; + type DbWeight = (); +} + +fn new_test_ext() -> TestExternalities { + RuntimeGenesisConfig::default().build_storage().unwrap().into() +} + +trait Sorted { + fn sorted(self) -> Self; +} + +impl Sorted for Vec { + fn sorted(mut self) -> Self { + self.sort(); + self + } +} + +#[test] +fn map_issue_3318() { + new_test_ext().execute_with(|| { + type OptionLinkedMap = self::frame_system::OptionLinkedMap; + + OptionLinkedMap::insert(1, 1); + assert_eq!(OptionLinkedMap::get(1), Some(1)); + OptionLinkedMap::insert(1, 2); + assert_eq!(OptionLinkedMap::get(1), Some(2)); + }); +} + +#[test] +fn map_swap_works() { + new_test_ext().execute_with(|| { + type OptionLinkedMap = self::frame_system::OptionLinkedMap; + + OptionLinkedMap::insert(0, 0); + OptionLinkedMap::insert(1, 1); + OptionLinkedMap::insert(2, 2); + OptionLinkedMap::insert(3, 3); + + let collect = || OptionLinkedMap::iter().collect::>().sorted(); + assert_eq!(collect(), vec![(0, 0), (1, 1), (2, 2), (3, 3)]); + + // Two existing + OptionLinkedMap::swap(1, 2); + assert_eq!(collect(), vec![(0, 0), (1, 2), (2, 1), (3, 3)]); + + // Back to normal + OptionLinkedMap::swap(2, 1); + assert_eq!(collect(), vec![(0, 0), (1, 1), (2, 2), (3, 3)]); + + // Left existing + OptionLinkedMap::swap(2, 5); + assert_eq!(collect(), vec![(0, 0), (1, 1), (3, 3), (5, 2)]); + + // Right existing + OptionLinkedMap::swap(5, 2); + assert_eq!(collect(), vec![(0, 0), (1, 1), (2, 2), (3, 3)]); + }); +} + +#[test] +fn double_map_swap_works() { + new_test_ext().execute_with(|| { + type DataDM = self::frame_system::DataDM; + + DataDM::insert(0, 1, 1); + DataDM::insert(1, 0, 2); + DataDM::insert(1, 1, 3); + + let get_all = || { + vec![ + DataDM::get(0, 1), + DataDM::get(1, 0), + DataDM::get(1, 1), + DataDM::get(2, 0), + DataDM::get(2, 1), + ] + }; + assert_eq!(get_all(), vec![1, 2, 3, 0, 0]); + + // Two existing + DataDM::swap(0, 1, 1, 0); + assert_eq!(get_all(), vec![2, 1, 3, 0, 0]); + + // Left existing + DataDM::swap(1, 0, 2, 0); + assert_eq!(get_all(), vec![2, 0, 3, 1, 0]); + + // Right existing + DataDM::swap(2, 1, 1, 1); + assert_eq!(get_all(), vec![2, 0, 0, 1, 3]); + }); +} + +#[test] +fn map_basic_insert_remove_should_work() { + new_test_ext().execute_with(|| { + type Map = self::frame_system::Data; + + // initialized during genesis + assert_eq!(Map::get(&15u32), 42u64); + + // get / insert / take + let key = 17u32; + assert_eq!(Map::get(&key), 0u64); + Map::insert(key, 4u64); + assert_eq!(Map::get(&key), 4u64); + assert_eq!(Map::take(&key), 4u64); + assert_eq!(Map::get(&key), 0u64); + + // mutate + Map::mutate(&key, |val| { + *val = 15; + }); + assert_eq!(Map::get(&key), 15u64); + + // remove + Map::remove(&key); + assert_eq!(Map::get(&key), 0u64); + }); +} + +#[test] +fn map_iteration_should_work() { + new_test_ext().execute_with(|| { + type Map = self::frame_system::Data; + + assert_eq!(Map::iter().collect::>().sorted(), vec![(15, 42)]); + // insert / remove + let key = 17u32; + Map::insert(key, 4u64); + assert_eq!(Map::iter().collect::>().sorted(), vec![(15, 42), (key, 4)]); + assert_eq!(Map::take(&15), 42u64); + assert_eq!(Map::take(&key), 4u64); + assert_eq!(Map::iter().collect::>().sorted(), vec![]); + + // Add couple of more elements + Map::insert(key, 42u64); + assert_eq!(Map::iter().collect::>().sorted(), vec![(key, 42)]); + Map::insert(key + 1, 43u64); + assert_eq!(Map::iter().collect::>().sorted(), vec![(key, 42), (key + 1, 43)]); + + // mutate + let key = key + 2; + Map::mutate(&key, |val| { + *val = 15; + }); + assert_eq!( + Map::iter().collect::>().sorted(), + vec![(key - 2, 42), (key - 1, 43), (key, 15)] + ); + Map::mutate(&key, |val| { + *val = 17; + }); + assert_eq!( + Map::iter().collect::>().sorted(), + vec![(key - 2, 42), (key - 1, 43), (key, 17)] + ); + + // remove first + Map::remove(&key); + assert_eq!(Map::iter().collect::>().sorted(), vec![(key - 2, 42), (key - 1, 43)]); + + // remove last from the list + Map::remove(&(key - 2)); + assert_eq!(Map::iter().collect::>().sorted(), vec![(key - 1, 43)]); + + // remove the last element + Map::remove(&(key - 1)); + assert_eq!(Map::iter().collect::>().sorted(), vec![]); + }); +} + +#[test] +fn double_map_basic_insert_remove_remove_prefix_with_commit_should_work() { + let key1 = 17u32; + let key2 = 18u32; + type DoubleMap = self::frame_system::DataDM; + let mut e = new_test_ext(); + e.execute_with(|| { + // initialized during genesis + assert_eq!(DoubleMap::get(&15u32, &16u32), 42u64); + + // get / insert / take + assert_eq!(DoubleMap::get(&key1, &key2), 0u64); + DoubleMap::insert(&key1, &key2, &4u64); + assert_eq!(DoubleMap::get(&key1, &key2), 4u64); + assert_eq!(DoubleMap::take(&key1, &key2), 4u64); + assert_eq!(DoubleMap::get(&key1, &key2), 0u64); + + // mutate + DoubleMap::mutate(&key1, &key2, |val| *val = 15); + assert_eq!(DoubleMap::get(&key1, &key2), 15u64); + + // remove + DoubleMap::remove(&key1, &key2); + assert_eq!(DoubleMap::get(&key1, &key2), 0u64); + + // remove prefix + DoubleMap::insert(&key1, &key2, &4u64); + DoubleMap::insert(&key1, &(key2 + 1), &4u64); + DoubleMap::insert(&(key1 + 1), &key2, &4u64); + DoubleMap::insert(&(key1 + 1), &(key2 + 1), &4u64); + }); + e.commit_all().unwrap(); + e.execute_with(|| { + assert!(matches!( + DoubleMap::clear_prefix(&key1, u32::max_value(), None), + MultiRemovalResults { maybe_cursor: None, backend: 2, unique: 2, loops: 2 } + )); + assert_eq!(DoubleMap::get(&key1, &key2), 0u64); + assert_eq!(DoubleMap::get(&key1, &(key2 + 1)), 0u64); + assert_eq!(DoubleMap::get(&(key1 + 1), &key2), 4u64); + assert_eq!(DoubleMap::get(&(key1 + 1), &(key2 + 1)), 4u64); + }); +} + +#[test] +fn double_map_basic_insert_remove_remove_prefix_should_work() { + new_test_ext().execute_with(|| { + let key1 = 17u32; + let key2 = 18u32; + type DoubleMap = self::frame_system::DataDM; + + // initialized during genesis + assert_eq!(DoubleMap::get(&15u32, &16u32), 42u64); + + // get / insert / take + assert_eq!(DoubleMap::get(&key1, &key2), 0u64); + DoubleMap::insert(&key1, &key2, &4u64); + assert_eq!(DoubleMap::get(&key1, &key2), 4u64); + assert_eq!(DoubleMap::take(&key1, &key2), 4u64); + assert_eq!(DoubleMap::get(&key1, &key2), 0u64); + + // mutate + DoubleMap::mutate(&key1, &key2, |val| *val = 15); + assert_eq!(DoubleMap::get(&key1, &key2), 15u64); + + // remove + DoubleMap::remove(&key1, &key2); + assert_eq!(DoubleMap::get(&key1, &key2), 0u64); + + // remove prefix + DoubleMap::insert(&key1, &key2, &4u64); + DoubleMap::insert(&key1, &(key2 + 1), &4u64); + DoubleMap::insert(&(key1 + 1), &key2, &4u64); + DoubleMap::insert(&(key1 + 1), &(key2 + 1), &4u64); + // all in overlay + assert!(matches!( + DoubleMap::clear_prefix(&key1, u32::max_value(), None), + MultiRemovalResults { maybe_cursor: None, backend: 0, unique: 0, loops: 0 } + )); + // Note this is the incorrect answer (for now), since we are using v2 of + // `clear_prefix`. + // When we switch to v3, then this will become: + // MultiRemovalResults:: { maybe_cursor: None, backend: 0, unique: 2, loops: 2 }, + assert!(matches!( + DoubleMap::clear_prefix(&key1, u32::max_value(), None), + MultiRemovalResults { maybe_cursor: None, backend: 0, unique: 0, loops: 0 } + )); + assert_eq!(DoubleMap::get(&key1, &key2), 0u64); + assert_eq!(DoubleMap::get(&key1, &(key2 + 1)), 0u64); + assert_eq!(DoubleMap::get(&(key1 + 1), &key2), 4u64); + assert_eq!(DoubleMap::get(&(key1 + 1), &(key2 + 1)), 4u64); + }); +} + +#[test] +fn double_map_append_should_work() { + new_test_ext().execute_with(|| { + type DoubleMap = self::frame_system::AppendableDM; + + let key1 = 17u32; + let key2 = 18u32; + + DoubleMap::insert(&key1, &key2, &vec![1]); + DoubleMap::append(&key1, &key2, 2); + assert_eq!(DoubleMap::get(&key1, &key2), &[1, 2]); + }); +} + +#[test] +fn double_map_mutate_exists_should_work() { + new_test_ext().execute_with(|| { + type DoubleMap = self::frame_system::DataDM; + + let (key1, key2) = (11, 13); + + // mutated + DoubleMap::mutate_exists(key1, key2, |v| *v = Some(1)); + assert_eq!(DoubleMap::get(&key1, key2), 1); + + // removed if mutated to `None` + DoubleMap::mutate_exists(key1, key2, |v| *v = None); + assert!(!DoubleMap::contains_key(&key1, key2)); + }); +} + +#[test] +fn double_map_try_mutate_exists_should_work() { + new_test_ext().execute_with(|| { + type DoubleMap = self::frame_system::DataDM; + type TestResult = Result<(), &'static str>; + + let (key1, key2) = (11, 13); + + // mutated if `Ok` + assert_ok!(DoubleMap::try_mutate_exists(key1, key2, |v| -> TestResult { + *v = Some(1); + Ok(()) + })); + assert_eq!(DoubleMap::get(&key1, key2), 1); + + // no-op if `Err` + assert_noop!( + DoubleMap::try_mutate_exists(key1, key2, |v| -> TestResult { + *v = Some(2); + Err("nah") + }), + "nah" + ); + + // removed if mutated to`None` + assert_ok!(DoubleMap::try_mutate_exists(key1, key2, |v| -> TestResult { + *v = None; + Ok(()) + })); + assert!(!DoubleMap::contains_key(&key1, key2)); + }); +} + +fn expected_metadata() -> PalletStorageMetadataIR { + PalletStorageMetadataIR { + prefix: "System", + entries: vec![ + StorageEntryMetadataIR { + name: "Data", + modifier: StorageEntryModifierIR::Default, + ty: StorageEntryTypeIR::Map { + hashers: vec![StorageHasherIR::Twox64Concat], + key: scale_info::meta_type::(), + value: scale_info::meta_type::(), + }, + default: vec![0, 0, 0, 0, 0, 0, 0, 0], + docs: vec![], + }, + StorageEntryMetadataIR { + name: "OptionLinkedMap", + modifier: StorageEntryModifierIR::Optional, + ty: StorageEntryTypeIR::Map { + hashers: vec![StorageHasherIR::Blake2_128Concat], + key: scale_info::meta_type::(), + value: scale_info::meta_type::(), + }, + default: vec![0], + docs: vec![], + }, + StorageEntryMetadataIR { + name: "GenericData", + modifier: StorageEntryModifierIR::Default, + ty: StorageEntryTypeIR::Map { + hashers: vec![StorageHasherIR::Identity], + key: scale_info::meta_type::(), + value: scale_info::meta_type::(), + }, + default: vec![0, 0, 0, 0], + docs: vec![], + }, + StorageEntryMetadataIR { + name: "GenericData2", + modifier: StorageEntryModifierIR::Optional, + ty: StorageEntryTypeIR::Map { + hashers: vec![StorageHasherIR::Blake2_128Concat], + key: scale_info::meta_type::(), + value: scale_info::meta_type::(), + }, + default: vec![0], + docs: vec![], + }, + StorageEntryMetadataIR { + name: "DataDM", + modifier: StorageEntryModifierIR::Default, + ty: StorageEntryTypeIR::Map { + hashers: vec![StorageHasherIR::Twox64Concat, StorageHasherIR::Blake2_128Concat], + key: scale_info::meta_type::<(u32, u32)>(), + value: scale_info::meta_type::(), + }, + default: vec![0, 0, 0, 0, 0, 0, 0, 0], + docs: vec![], + }, + StorageEntryMetadataIR { + name: "GenericDataDM", + modifier: StorageEntryModifierIR::Default, + ty: StorageEntryTypeIR::Map { + hashers: vec![StorageHasherIR::Blake2_128Concat, StorageHasherIR::Identity], + key: scale_info::meta_type::<(u32, u32)>(), + value: scale_info::meta_type::(), + }, + default: vec![0, 0, 0, 0], + docs: vec![], + }, + StorageEntryMetadataIR { + name: "GenericData2DM", + modifier: StorageEntryModifierIR::Optional, + ty: StorageEntryTypeIR::Map { + hashers: vec![StorageHasherIR::Blake2_128Concat, StorageHasherIR::Twox64Concat], + key: scale_info::meta_type::<(u32, u32)>(), + value: scale_info::meta_type::(), + }, + default: vec![0], + docs: vec![], + }, + StorageEntryMetadataIR { + name: "AppendableDM", + modifier: StorageEntryModifierIR::Default, + ty: StorageEntryTypeIR::Map { + hashers: vec![ + StorageHasherIR::Blake2_128Concat, + StorageHasherIR::Blake2_128Concat, + ], + key: scale_info::meta_type::<(u32, u32)>(), + value: scale_info::meta_type::>(), + }, + default: vec![0], + docs: vec![], + }, + ], + } +} + +#[test] +fn store_metadata() { + let metadata = Pallet::::storage_metadata(); + pretty_assertions::assert_eq!(expected_metadata(), metadata); +} + +parameter_types! { + storage StorageParameter: u64 = 10; +} + +#[test] +fn check_storage_parameter_type_works() { + TestExternalities::default().execute_with(|| { + assert_eq!(sp_io::hashing::twox_128(b":StorageParameter:"), StorageParameter::key()); + + assert_eq!(10, StorageParameter::get()); + + StorageParameter::set(&300); + assert_eq!(300, StorageParameter::get()); + }) +} diff --git a/frame/support/src/tests/storage_alias.rs b/frame/support/src/tests/storage_alias.rs new file mode 100644 index 0000000000000..05ea1b5f712c6 --- /dev/null +++ b/frame/support/src/tests/storage_alias.rs @@ -0,0 +1,192 @@ +// This file is part of Substrate. + +// Copyright (C) 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. + +use sp_core::Get; + +use super::{new_test_ext, BlockNumberFor, Config, Pallet, Runtime}; +use crate::{ + assert_noop, assert_ok, parameter_types, storage::generator::StorageValue, Blake2_128Concat, +}; + +#[test] +fn storage_alias_works() { + new_test_ext().execute_with(|| { + #[crate::storage_alias] + type GenericData2 = + StorageMap, BlockNumberFor>; + + assert_eq!(Pallet::::generic_data2(5), None); + GenericData2::::insert(5, 5); + assert_eq!(Pallet::::generic_data2(5), Some(5)); + + /// Some random docs that ensure that docs are accepted + #[crate::storage_alias] + pub type GenericData = + StorageMap, BlockNumberFor>; + + #[crate::storage_alias] + pub type GenericDataPallet = + StorageMap, Blake2_128Concat, BlockNumberFor, BlockNumberFor>; + }); +} + +#[test] +fn storage_value_mutate_exists_should_work() { + new_test_ext().execute_with(|| { + #[crate::storage_alias] + pub type Value = StorageValue; + + assert!(!Value::exists()); + + Value::mutate_exists(|v| *v = Some(1)); + assert!(Value::exists()); + assert_eq!(Value::get(), Some(1)); + + // removed if mutated to `None` + Value::mutate_exists(|v| *v = None); + assert!(!Value::exists()); + }); +} + +#[test] +fn storage_value_try_mutate_exists_should_work() { + new_test_ext().execute_with(|| { + #[crate::storage_alias] + pub type Value = StorageValue; + + type TestResult = std::result::Result<(), &'static str>; + + assert!(!Value::exists()); + + // mutated if `Ok` + assert_ok!(Value::try_mutate_exists(|v| -> TestResult { + *v = Some(1); + Ok(()) + })); + assert!(Value::exists()); + assert_eq!(Value::get(), Some(1)); + + // no-op if `Err` + assert_noop!( + Value::try_mutate_exists(|v| -> TestResult { + *v = Some(2); + Err("nah") + }), + "nah" + ); + assert_eq!(Value::get(), Some(1)); + + // removed if mutated to`None` + assert_ok!(Value::try_mutate_exists(|v| -> TestResult { + *v = None; + Ok(()) + })); + assert!(!Value::exists()); + }); +} + +#[docify::export] +#[test] +fn verbatim_attribute() { + new_test_ext().execute_with(|| { + // Declare the alias that will use the verbatim identifier as prefix. + #[crate::storage_alias(verbatim)] + pub type Value = StorageValue; + + // Check that it works as expected. + Value::put(1); + assert_eq!(1, Value::get().unwrap()); + + // The prefix is the one we declared above. + assert_eq!(&b"Test"[..], Value::module_prefix()); + }); +} + +#[docify::export] +#[test] +fn pallet_name_attribute() { + new_test_ext().execute_with(|| { + // Declare the alias that will use the pallet name as prefix. + #[crate::storage_alias(pallet_name)] + pub type Value = StorageValue, u32>; + + // Check that it works as expected. + Value::::put(1); + assert_eq!(1, Value::::get().unwrap()); + + // The prefix is the pallet name. In this case the pallet name is `System` as declared in + // `construct_runtime!`. + assert_eq!(&b"System"[..], Value::::module_prefix()); + }); +} + +#[docify::export] +#[test] +fn dynamic_attribute() { + new_test_ext().execute_with(|| { + // First let's declare our prefix. + // + // It could be any type that, as long as it implements `Get<&'static str>`. + parameter_types! { + pub Prefix: &'static str = "Hello"; + } + + // Declare the alias that will use the dynamic `Get` as prefix. + #[crate::storage_alias(dynamic)] + pub type Value> = StorageValue; + + // Check that it works as expected. + Value::::put(1); + assert_eq!(1, Value::::get().unwrap()); + + // The prefix is the one we declared above. + assert_eq!(&b"Hello"[..], Value::::module_prefix()); + }); +} + +#[docify::export] +#[test] +fn storage_alias_guess() { + new_test_ext().execute_with(|| { + // The macro will use `Test` as prefix. + #[crate::storage_alias] + pub type Value = StorageValue; + + assert_eq!(&b"Test"[..], Value::module_prefix()); + + // The macro will use the pallet name as prefix. + #[crate::storage_alias] + pub type PalletValue = StorageValue, u32>; + + assert_eq!(&b"System"[..], PalletValue::::module_prefix()); + }); +} + +#[test] +fn dynamic_attribute_without_generics_works() { + new_test_ext().execute_with(|| { + parameter_types! { + pub Prefix: &'static str = "Hello"; + } + + #[crate::storage_alias(dynamic)] + pub type Value = StorageValue; + + Value::put(1); + assert_eq!(1, Value::get().unwrap()) + }); +}