mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-09 20:11:09 +00:00
Improve storage_alias and make UnlockAndUnreserveAllFunds independent of the pallet (#14773)
* Make `storage_alias` more generic over the `prefix` * Make `UnlockAndUnreserveAllFunds` indepenend from the pallet * FMT * Fix error reporting * Rename prefix type * Add test * Apply suggestions from code review Co-authored-by: Sam Johnson <sam@durosoft.com> * ".git/.scripts/commands/fmt/fmt.sh" --------- Co-authored-by: Sam Johnson <sam@durosoft.com> Co-authored-by: command-bot <>
This commit is contained in:
Generated
+1
@@ -2594,6 +2594,7 @@ dependencies = [
|
||||
"array-bytes",
|
||||
"assert_matches",
|
||||
"bitflags",
|
||||
"docify",
|
||||
"environmental",
|
||||
"frame-metadata",
|
||||
"frame-support-procedural",
|
||||
|
||||
@@ -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<T> =
|
||||
<<T as UnlockConfig>::Currency as Currency<<T as UnlockConfig>::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<Self::AccountId> + ReservableCurrency<Self::AccountId>;
|
||||
/// 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<u32>;
|
||||
/// The maximum deposit as configured previously in the runtime.
|
||||
type MaxDeposits: Get<u32>;
|
||||
/// The DB weight as configured in the runtime to calculate the correct weight.
|
||||
type DbWeight: Get<RuntimeDbWeight>;
|
||||
/// The block number as configured in the runtime.
|
||||
type BlockNumber: Parameter + Zero + Copy + Ord;
|
||||
}
|
||||
|
||||
#[storage_alias(dynamic)]
|
||||
type DepositOf<T: UnlockConfig> = StorageMap<
|
||||
<T as UnlockConfig>::PalletName,
|
||||
Twox64Concat,
|
||||
PropIndex,
|
||||
(BoundedVec<<T as UnlockConfig>::AccountId, <T as UnlockConfig>::MaxDeposits>, BalanceOf<T>),
|
||||
>;
|
||||
|
||||
#[storage_alias(dynamic)]
|
||||
type VotingOf<T: UnlockConfig> = StorageMap<
|
||||
<T as UnlockConfig>::PalletName,
|
||||
Twox64Concat,
|
||||
<T as UnlockConfig>::AccountId,
|
||||
Voting<
|
||||
BalanceOf<T>,
|
||||
<T as UnlockConfig>::AccountId,
|
||||
<T as UnlockConfig>::BlockNumber,
|
||||
<T as UnlockConfig>::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<T: crate::Config>(sp_std::marker::PhantomData<T>);
|
||||
pub struct UnlockAndUnreserveAllFunds<T: UnlockConfig>(sp_std::marker::PhantomData<T>);
|
||||
|
||||
impl<T: crate::Config> UnlockAndUnreserveAllFunds<T> {
|
||||
impl<T: UnlockConfig> UnlockAndUnreserveAllFunds<T> {
|
||||
/// 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<T: crate::Config> UnlockAndUnreserveAllFunds<T> {
|
||||
|
||||
// Get all deposits (reserved).
|
||||
let mut total_voting_vec_entries: u64 = 0;
|
||||
let account_deposits: BTreeMap<T::AccountId, BalanceOf<T>> = crate::DepositOf::<T>::iter()
|
||||
let account_deposits: BTreeMap<T::AccountId, BalanceOf<T>> = DepositOf::<T>::iter()
|
||||
.flat_map(|(_prop_index, (accounts, balance))| {
|
||||
// Count the number of deposits
|
||||
deposit_of_len.saturating_inc();
|
||||
@@ -81,7 +133,7 @@ impl<T: crate::Config> UnlockAndUnreserveAllFunds<T> {
|
||||
});
|
||||
|
||||
// Voter accounts have amounts locked.
|
||||
let account_stakes: BTreeMap<T::AccountId, BalanceOf<T>> = crate::VotingOf::<T>::iter()
|
||||
let account_stakes: BTreeMap<T::AccountId, BalanceOf<T>> = VotingOf::<T>::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<T: crate::Config> UnlockAndUnreserveAllFunds<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: crate::Config> OnRuntimeUpgrade for UnlockAndUnreserveAllFunds<T>
|
||||
impl<T: UnlockConfig> OnRuntimeUpgrade for UnlockAndUnreserveAllFunds<T>
|
||||
where
|
||||
BalanceOf<T>: 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<Test>;
|
||||
type DbWeight = ();
|
||||
type PalletName = PalletName;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unreserve_works_for_depositer() {
|
||||
@@ -288,10 +358,10 @@ mod test {
|
||||
);
|
||||
|
||||
// Run the migration.
|
||||
let bytes = UnlockAndUnreserveAllFunds::<Test>::pre_upgrade()
|
||||
let bytes = UnlockAndUnreserveAllFunds::<UnlockConfigImpl>::pre_upgrade()
|
||||
.unwrap_or_else(|e| panic!("pre_upgrade failed: {:?}", e));
|
||||
UnlockAndUnreserveAllFunds::<Test>::on_runtime_upgrade();
|
||||
assert_ok!(UnlockAndUnreserveAllFunds::<Test>::post_upgrade(bytes));
|
||||
UnlockAndUnreserveAllFunds::<UnlockConfigImpl>::on_runtime_upgrade();
|
||||
assert_ok!(UnlockAndUnreserveAllFunds::<UnlockConfigImpl>::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::<Test>::pre_upgrade()
|
||||
let bytes = UnlockAndUnreserveAllFunds::<UnlockConfigImpl>::pre_upgrade()
|
||||
.unwrap_or_else(|e| panic!("pre_upgrade failed: {:?}", e));
|
||||
UnlockAndUnreserveAllFunds::<Test>::on_runtime_upgrade();
|
||||
assert_ok!(UnlockAndUnreserveAllFunds::<Test>::post_upgrade(bytes));
|
||||
UnlockAndUnreserveAllFunds::<UnlockConfigImpl>::on_runtime_upgrade();
|
||||
assert_ok!(UnlockAndUnreserveAllFunds::<UnlockConfigImpl>::post_upgrade(bytes));
|
||||
|
||||
// Assert the voter lock was removed
|
||||
assert_eq!(
|
||||
|
||||
@@ -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" }
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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<Token![::]>,
|
||||
segments: Punctuated<Ident, Token![::]>,
|
||||
/// 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<Self> {
|
||||
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))?,
|
||||
})
|
||||
}
|
||||
}
|
||||
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 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<Ident, token::Comma>,
|
||||
gt_token: Token![>],
|
||||
}
|
||||
|
||||
impl TypeGenerics {
|
||||
/// Returns the generics for types declarations etc.
|
||||
fn iter(&self) -> impl Iterator<Item = &Ident> {
|
||||
self.params.iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for TypeGenerics {
|
||||
fn parse(input: ParseStream<'_>) -> Result<Self> {
|
||||
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<TypeGenerics>,
|
||||
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<TypeGenerics>,
|
||||
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<TypeGenerics>,
|
||||
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<TypeGenerics>,
|
||||
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<TypeGenerics>,
|
||||
prefix: Type,
|
||||
_paren_comma: Token![,],
|
||||
_paren_token: token::Paren,
|
||||
key_types: Punctuated<Type, Token![,]>,
|
||||
@@ -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<Option<TypeGenerics>> {
|
||||
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<TokenStream> {
|
||||
pub fn storage_alias(attributes: TokenStream, input: TokenStream) -> Result<TokenStream> {
|
||||
let input = syn::parse2::<Input>(input)?;
|
||||
let crate_ = generate_crate_access_2018("frame-support")?;
|
||||
|
||||
let prefix_type = if attributes.is_empty() {
|
||||
PrefixType::Compatibility
|
||||
} else if syn::parse2::<prefix_types::verbatim>(attributes.clone()).is_ok() {
|
||||
PrefixType::Verbatim
|
||||
} else if syn::parse2::<prefix_types::pallet_name>(attributes.clone()).is_ok() {
|
||||
PrefixType::PalletName
|
||||
} else if syn::parse2::<prefix_types::dynamic>(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<TokenStream> {
|
||||
/// 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<StorageInstance> {
|
||||
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::<Vec<_>>()
|
||||
})
|
||||
.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 > ) })
|
||||
}
|
||||
|
||||
@@ -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<Prefix, u32>;
|
||||
/// The flexibility around declaring the storage prefix makes this macro very useful for
|
||||
/// writing migrations etc.
|
||||
///
|
||||
/// // generate a double map from `(u32, u32)` (with hashers `Twox64Concat` for each key)
|
||||
/// // to `Vec<u8>`
|
||||
/// #[storage_alias]
|
||||
/// type OtherStorageName = StorageDoubleMap<
|
||||
/// OtherPrefix,
|
||||
/// Twox64Concat,
|
||||
/// u32,
|
||||
/// Twox64Concat,
|
||||
/// u32,
|
||||
/// Vec<u8>,
|
||||
/// >;
|
||||
/// # Examples
|
||||
///
|
||||
/// // optionally specify the query type
|
||||
/// use frame_support::pallet_prelude::{ValueQuery, OptionQuery};
|
||||
/// #[storage_alias]
|
||||
/// type ValueName = StorageValue<Prefix, u32, OptionQuery>;
|
||||
/// #[storage_alias]
|
||||
/// type SomeStorageName = StorageMap<
|
||||
/// Prefix,
|
||||
/// Twox64Concat,
|
||||
/// u32,
|
||||
/// Vec<u8>,
|
||||
/// ValueQuery,
|
||||
/// >;
|
||||
/// 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).
|
||||
///
|
||||
/// // generate a map from `Config::AccountId` (with hasher `Twox64Concat`) to `Vec<u8>`
|
||||
/// trait Config { type AccountId: codec::FullCodec; }
|
||||
/// #[storage_alias]
|
||||
/// type GenericStorage<T> = StorageMap<Prefix, Twox64Concat, <T as Config>::AccountId, Vec<u8>>;
|
||||
/// 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)]
|
||||
///
|
||||
/// // It also supports NMap
|
||||
/// use frame_support::storage::types::Key as NMapKey;
|
||||
/// 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<T>` is the prefix type.
|
||||
///
|
||||
/// #[storage_alias]
|
||||
/// type SomeNMap = StorageNMap<Prefix, (NMapKey<Twox64Concat, u32>, NMapKey<Twox64Concat, u64>), Vec<u8>>;
|
||||
/// 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).
|
||||
///
|
||||
/// // 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<T: Config, I = ()>(std::marker::PhantomData<(T, I)>);
|
||||
/// # impl<T: Config, I: 'static> frame_support::traits::PalletInfoAccess for Pallet<T, I> {
|
||||
/// # 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<T: Config> = StorageValue<Pallet<T>, u64>;
|
||||
///
|
||||
/// // Pallet with instance
|
||||
///
|
||||
/// #[storage_alias]
|
||||
/// type SomeValue2<T: Config, I: 'static> = StorageValue<Pallet<T, I>, u64>;
|
||||
///
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
/// 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<T>(_);
|
||||
|
||||
#[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<Self::RuntimeCall>;
|
||||
type RuntimeOrigin;
|
||||
type RuntimeCall;
|
||||
type PalletInfo: crate::traits::PalletInfo;
|
||||
type DbWeight: Get<crate::weights::RuntimeDbWeight>;
|
||||
}
|
||||
|
||||
#[pallet::error]
|
||||
pub enum Error<T> {
|
||||
/// Required by construct_runtime
|
||||
CallFiltered,
|
||||
}
|
||||
|
||||
#[pallet::origin]
|
||||
pub type Origin<T> = RawOrigin<<T as Config>::AccountId>;
|
||||
|
||||
#[pallet::call]
|
||||
impl<T: Config> Pallet<T> {}
|
||||
|
||||
#[pallet::storage]
|
||||
pub type Data<T> = StorageMap<_, Twox64Concat, u32, u64, ValueQuery>;
|
||||
|
||||
#[pallet::storage]
|
||||
pub type OptionLinkedMap<T> = StorageMap<_, Blake2_128Concat, u32, u32, OptionQuery>;
|
||||
|
||||
#[pallet::storage]
|
||||
#[pallet::getter(fn generic_data)]
|
||||
pub type GenericData<T: Config> =
|
||||
StorageMap<_, Identity, BlockNumberFor<T>, BlockNumberFor<T>, ValueQuery>;
|
||||
|
||||
#[pallet::storage]
|
||||
#[pallet::getter(fn generic_data2)]
|
||||
pub type GenericData2<T: Config> =
|
||||
StorageMap<_, Blake2_128Concat, BlockNumberFor<T>, BlockNumberFor<T>, OptionQuery>;
|
||||
|
||||
#[pallet::storage]
|
||||
pub type DataDM<T> =
|
||||
StorageDoubleMap<_, Twox64Concat, u32, Blake2_128Concat, u32, u64, ValueQuery>;
|
||||
|
||||
#[pallet::storage]
|
||||
pub type GenericDataDM<T: Config> = StorageDoubleMap<
|
||||
_,
|
||||
Blake2_128Concat,
|
||||
BlockNumberFor<T>,
|
||||
Identity,
|
||||
BlockNumberFor<T>,
|
||||
BlockNumberFor<T>,
|
||||
ValueQuery,
|
||||
>;
|
||||
|
||||
#[pallet::storage]
|
||||
pub type GenericData2DM<T: Config> = StorageDoubleMap<
|
||||
_,
|
||||
Blake2_128Concat,
|
||||
BlockNumberFor<T>,
|
||||
Twox64Concat,
|
||||
BlockNumberFor<T>,
|
||||
BlockNumberFor<T>,
|
||||
OptionQuery,
|
||||
>;
|
||||
|
||||
#[pallet::storage]
|
||||
#[pallet::unbounded]
|
||||
pub type AppendableDM<T: Config> = StorageDoubleMap<
|
||||
_,
|
||||
Blake2_128Concat,
|
||||
u32,
|
||||
Blake2_128Concat,
|
||||
BlockNumberFor<T>,
|
||||
Vec<u32>,
|
||||
ValueQuery,
|
||||
>;
|
||||
|
||||
#[pallet::genesis_config]
|
||||
pub struct GenesisConfig<T: Config> {
|
||||
pub data: Vec<(u32, u64)>,
|
||||
pub test_config: Vec<(u32, u32, u64)>,
|
||||
#[serde(skip)]
|
||||
pub _config: sp_std::marker::PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: Config> Default for GenesisConfig<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
_config: Default::default(),
|
||||
data: vec![(15u32, 42u64)],
|
||||
test_config: vec![(15u32, 16u32, 42u64)],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[pallet::genesis_build]
|
||||
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
|
||||
fn build(&self) {
|
||||
for (k, v) in &self.data {
|
||||
<Data<T>>::insert(k, v);
|
||||
}
|
||||
for (k1, k2, v) in &self.test_config {
|
||||
<DataDM<T>>::insert(k1, k2, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod pallet_prelude {
|
||||
pub type OriginFor<T> = <T as super::Config>::RuntimeOrigin;
|
||||
|
||||
pub type HeaderFor<T> =
|
||||
<<T as super::Config>::Block as sp_runtime::traits::HeaderProvider>::HeaderT;
|
||||
|
||||
pub type BlockNumberFor<T> = <HeaderFor<T> as sp_runtime::traits::Header>::Number;
|
||||
}
|
||||
}
|
||||
|
||||
type BlockNumber = u32;
|
||||
type AccountId = u32;
|
||||
type Header = generic::Header<BlockNumber, BlakeTwo256>;
|
||||
type UncheckedExtrinsic = generic::UncheckedExtrinsic<u32, RuntimeCall, (), ()>;
|
||||
type Block = generic::Block<Header, UncheckedExtrinsic>;
|
||||
|
||||
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<T: Ord> Sorted for Vec<T> {
|
||||
fn sorted(mut self) -> Self {
|
||||
self.sort();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn storage_alias_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
#[crate::storage_alias]
|
||||
type GenericData2<T> =
|
||||
StorageMap<System, Blake2_128Concat, BlockNumberFor<T>, BlockNumberFor<T>>;
|
||||
|
||||
assert_eq!(Pallet::<Runtime>::generic_data2(5), None);
|
||||
GenericData2::<Runtime>::insert(5, 5);
|
||||
assert_eq!(Pallet::<Runtime>::generic_data2(5), Some(5));
|
||||
|
||||
/// Some random docs that ensure that docs are accepted
|
||||
#[crate::storage_alias]
|
||||
pub type GenericData<T> =
|
||||
StorageMap<Test2, Blake2_128Concat, BlockNumberFor<T>, BlockNumberFor<T>>;
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn storage_value_mutate_exists_should_work() {
|
||||
new_test_ext().execute_with(|| {
|
||||
#[crate::storage_alias]
|
||||
pub type Value = StorageValue<Test, u32>;
|
||||
|
||||
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<Test, u32>;
|
||||
|
||||
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<Runtime>;
|
||||
|
||||
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<Runtime>;
|
||||
|
||||
OptionLinkedMap::insert(0, 0);
|
||||
OptionLinkedMap::insert(1, 1);
|
||||
OptionLinkedMap::insert(2, 2);
|
||||
OptionLinkedMap::insert(3, 3);
|
||||
|
||||
let collect = || OptionLinkedMap::iter().collect::<Vec<_>>().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<Runtime>;
|
||||
|
||||
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<Runtime>;
|
||||
|
||||
// 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<Runtime>;
|
||||
|
||||
assert_eq!(Map::iter().collect::<Vec<_>>().sorted(), vec![(15, 42)]);
|
||||
// insert / remove
|
||||
let key = 17u32;
|
||||
Map::insert(key, 4u64);
|
||||
assert_eq!(Map::iter().collect::<Vec<_>>().sorted(), vec![(15, 42), (key, 4)]);
|
||||
assert_eq!(Map::take(&15), 42u64);
|
||||
assert_eq!(Map::take(&key), 4u64);
|
||||
assert_eq!(Map::iter().collect::<Vec<_>>().sorted(), vec![]);
|
||||
|
||||
// Add couple of more elements
|
||||
Map::insert(key, 42u64);
|
||||
assert_eq!(Map::iter().collect::<Vec<_>>().sorted(), vec![(key, 42)]);
|
||||
Map::insert(key + 1, 43u64);
|
||||
assert_eq!(Map::iter().collect::<Vec<_>>().sorted(), vec![(key, 42), (key + 1, 43)]);
|
||||
|
||||
// mutate
|
||||
let key = key + 2;
|
||||
Map::mutate(&key, |val| {
|
||||
*val = 15;
|
||||
});
|
||||
assert_eq!(
|
||||
Map::iter().collect::<Vec<_>>().sorted(),
|
||||
vec![(key - 2, 42), (key - 1, 43), (key, 15)]
|
||||
);
|
||||
Map::mutate(&key, |val| {
|
||||
*val = 17;
|
||||
});
|
||||
assert_eq!(
|
||||
Map::iter().collect::<Vec<_>>().sorted(),
|
||||
vec![(key - 2, 42), (key - 1, 43), (key, 17)]
|
||||
);
|
||||
|
||||
// remove first
|
||||
Map::remove(&key);
|
||||
assert_eq!(
|
||||
Map::iter().collect::<Vec<_>>().sorted(),
|
||||
vec![(key - 2, 42), (key - 1, 43)]
|
||||
);
|
||||
|
||||
// remove last from the list
|
||||
Map::remove(&(key - 2));
|
||||
assert_eq!(Map::iter().collect::<Vec<_>>().sorted(), vec![(key - 1, 43)]);
|
||||
|
||||
// remove the last element
|
||||
Map::remove(&(key - 1));
|
||||
assert_eq!(Map::iter().collect::<Vec<_>>().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<Runtime>;
|
||||
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<Runtime>;
|
||||
|
||||
// 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<Runtime>;
|
||||
|
||||
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<Runtime>;
|
||||
|
||||
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<Runtime>;
|
||||
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::<u32>(),
|
||||
value: scale_info::meta_type::<u64>(),
|
||||
},
|
||||
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::<u32>(),
|
||||
value: scale_info::meta_type::<u32>(),
|
||||
},
|
||||
default: vec![0],
|
||||
docs: vec![],
|
||||
},
|
||||
StorageEntryMetadataIR {
|
||||
name: "GenericData",
|
||||
modifier: StorageEntryModifierIR::Default,
|
||||
ty: StorageEntryTypeIR::Map {
|
||||
hashers: vec![StorageHasherIR::Identity],
|
||||
key: scale_info::meta_type::<u32>(),
|
||||
value: scale_info::meta_type::<u32>(),
|
||||
},
|
||||
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::<u32>(),
|
||||
value: scale_info::meta_type::<u32>(),
|
||||
},
|
||||
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::<u64>(),
|
||||
},
|
||||
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::<u32>(),
|
||||
},
|
||||
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::<u32>(),
|
||||
},
|
||||
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::<Vec<u32>>(),
|
||||
},
|
||||
default: vec![0],
|
||||
docs: vec![],
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn store_metadata() {
|
||||
let metadata = Pallet::<Runtime>::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 {
|
||||
|
||||
@@ -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<T>(_);
|
||||
|
||||
#[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<Self::RuntimeCall>;
|
||||
type RuntimeOrigin;
|
||||
type RuntimeCall;
|
||||
type PalletInfo: crate::traits::PalletInfo;
|
||||
type DbWeight: Get<crate::weights::RuntimeDbWeight>;
|
||||
}
|
||||
|
||||
#[pallet::error]
|
||||
pub enum Error<T> {
|
||||
/// Required by construct_runtime
|
||||
CallFiltered,
|
||||
}
|
||||
|
||||
#[pallet::origin]
|
||||
pub type Origin<T> = RawOrigin<<T as Config>::AccountId>;
|
||||
|
||||
#[pallet::call]
|
||||
impl<T: Config> Pallet<T> {}
|
||||
|
||||
#[pallet::storage]
|
||||
pub type Data<T> = StorageMap<_, Twox64Concat, u32, u64, ValueQuery>;
|
||||
|
||||
#[pallet::storage]
|
||||
pub type OptionLinkedMap<T> = StorageMap<_, Blake2_128Concat, u32, u32, OptionQuery>;
|
||||
|
||||
#[pallet::storage]
|
||||
#[pallet::getter(fn generic_data)]
|
||||
pub type GenericData<T: Config> =
|
||||
StorageMap<_, Identity, BlockNumberFor<T>, BlockNumberFor<T>, ValueQuery>;
|
||||
|
||||
#[pallet::storage]
|
||||
#[pallet::getter(fn generic_data2)]
|
||||
pub type GenericData2<T: Config> =
|
||||
StorageMap<_, Blake2_128Concat, BlockNumberFor<T>, BlockNumberFor<T>, OptionQuery>;
|
||||
|
||||
#[pallet::storage]
|
||||
pub type DataDM<T> =
|
||||
StorageDoubleMap<_, Twox64Concat, u32, Blake2_128Concat, u32, u64, ValueQuery>;
|
||||
|
||||
#[pallet::storage]
|
||||
pub type GenericDataDM<T: Config> = StorageDoubleMap<
|
||||
_,
|
||||
Blake2_128Concat,
|
||||
BlockNumberFor<T>,
|
||||
Identity,
|
||||
BlockNumberFor<T>,
|
||||
BlockNumberFor<T>,
|
||||
ValueQuery,
|
||||
>;
|
||||
|
||||
#[pallet::storage]
|
||||
pub type GenericData2DM<T: Config> = StorageDoubleMap<
|
||||
_,
|
||||
Blake2_128Concat,
|
||||
BlockNumberFor<T>,
|
||||
Twox64Concat,
|
||||
BlockNumberFor<T>,
|
||||
BlockNumberFor<T>,
|
||||
OptionQuery,
|
||||
>;
|
||||
|
||||
#[pallet::storage]
|
||||
#[pallet::unbounded]
|
||||
pub type AppendableDM<T: Config> = StorageDoubleMap<
|
||||
_,
|
||||
Blake2_128Concat,
|
||||
u32,
|
||||
Blake2_128Concat,
|
||||
BlockNumberFor<T>,
|
||||
Vec<u32>,
|
||||
ValueQuery,
|
||||
>;
|
||||
|
||||
#[pallet::genesis_config]
|
||||
pub struct GenesisConfig<T: Config> {
|
||||
pub data: Vec<(u32, u64)>,
|
||||
pub test_config: Vec<(u32, u32, u64)>,
|
||||
#[serde(skip)]
|
||||
pub _config: sp_std::marker::PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: Config> Default for GenesisConfig<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
_config: Default::default(),
|
||||
data: vec![(15u32, 42u64)],
|
||||
test_config: vec![(15u32, 16u32, 42u64)],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[pallet::genesis_build]
|
||||
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
|
||||
fn build(&self) {
|
||||
for (k, v) in &self.data {
|
||||
<Data<T>>::insert(k, v);
|
||||
}
|
||||
for (k1, k2, v) in &self.test_config {
|
||||
<DataDM<T>>::insert(k1, k2, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod pallet_prelude {
|
||||
pub type OriginFor<T> = <T as super::Config>::RuntimeOrigin;
|
||||
|
||||
pub type HeaderFor<T> =
|
||||
<<T as super::Config>::Block as sp_runtime::traits::HeaderProvider>::HeaderT;
|
||||
|
||||
pub type BlockNumberFor<T> = <HeaderFor<T> as sp_runtime::traits::Header>::Number;
|
||||
}
|
||||
}
|
||||
|
||||
type BlockNumber = u32;
|
||||
type AccountId = u32;
|
||||
type Header = generic::Header<BlockNumber, BlakeTwo256>;
|
||||
type UncheckedExtrinsic = generic::UncheckedExtrinsic<u32, RuntimeCall, (), ()>;
|
||||
type Block = generic::Block<Header, UncheckedExtrinsic>;
|
||||
|
||||
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<T: Ord> Sorted for Vec<T> {
|
||||
fn sorted(mut self) -> Self {
|
||||
self.sort();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn map_issue_3318() {
|
||||
new_test_ext().execute_with(|| {
|
||||
type OptionLinkedMap = self::frame_system::OptionLinkedMap<Runtime>;
|
||||
|
||||
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<Runtime>;
|
||||
|
||||
OptionLinkedMap::insert(0, 0);
|
||||
OptionLinkedMap::insert(1, 1);
|
||||
OptionLinkedMap::insert(2, 2);
|
||||
OptionLinkedMap::insert(3, 3);
|
||||
|
||||
let collect = || OptionLinkedMap::iter().collect::<Vec<_>>().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<Runtime>;
|
||||
|
||||
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<Runtime>;
|
||||
|
||||
// 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<Runtime>;
|
||||
|
||||
assert_eq!(Map::iter().collect::<Vec<_>>().sorted(), vec![(15, 42)]);
|
||||
// insert / remove
|
||||
let key = 17u32;
|
||||
Map::insert(key, 4u64);
|
||||
assert_eq!(Map::iter().collect::<Vec<_>>().sorted(), vec![(15, 42), (key, 4)]);
|
||||
assert_eq!(Map::take(&15), 42u64);
|
||||
assert_eq!(Map::take(&key), 4u64);
|
||||
assert_eq!(Map::iter().collect::<Vec<_>>().sorted(), vec![]);
|
||||
|
||||
// Add couple of more elements
|
||||
Map::insert(key, 42u64);
|
||||
assert_eq!(Map::iter().collect::<Vec<_>>().sorted(), vec![(key, 42)]);
|
||||
Map::insert(key + 1, 43u64);
|
||||
assert_eq!(Map::iter().collect::<Vec<_>>().sorted(), vec![(key, 42), (key + 1, 43)]);
|
||||
|
||||
// mutate
|
||||
let key = key + 2;
|
||||
Map::mutate(&key, |val| {
|
||||
*val = 15;
|
||||
});
|
||||
assert_eq!(
|
||||
Map::iter().collect::<Vec<_>>().sorted(),
|
||||
vec![(key - 2, 42), (key - 1, 43), (key, 15)]
|
||||
);
|
||||
Map::mutate(&key, |val| {
|
||||
*val = 17;
|
||||
});
|
||||
assert_eq!(
|
||||
Map::iter().collect::<Vec<_>>().sorted(),
|
||||
vec![(key - 2, 42), (key - 1, 43), (key, 17)]
|
||||
);
|
||||
|
||||
// remove first
|
||||
Map::remove(&key);
|
||||
assert_eq!(Map::iter().collect::<Vec<_>>().sorted(), vec![(key - 2, 42), (key - 1, 43)]);
|
||||
|
||||
// remove last from the list
|
||||
Map::remove(&(key - 2));
|
||||
assert_eq!(Map::iter().collect::<Vec<_>>().sorted(), vec![(key - 1, 43)]);
|
||||
|
||||
// remove the last element
|
||||
Map::remove(&(key - 1));
|
||||
assert_eq!(Map::iter().collect::<Vec<_>>().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<Runtime>;
|
||||
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<Runtime>;
|
||||
|
||||
// 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<Runtime>;
|
||||
|
||||
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<Runtime>;
|
||||
|
||||
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<Runtime>;
|
||||
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::<u32>(),
|
||||
value: scale_info::meta_type::<u64>(),
|
||||
},
|
||||
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::<u32>(),
|
||||
value: scale_info::meta_type::<u32>(),
|
||||
},
|
||||
default: vec![0],
|
||||
docs: vec![],
|
||||
},
|
||||
StorageEntryMetadataIR {
|
||||
name: "GenericData",
|
||||
modifier: StorageEntryModifierIR::Default,
|
||||
ty: StorageEntryTypeIR::Map {
|
||||
hashers: vec![StorageHasherIR::Identity],
|
||||
key: scale_info::meta_type::<u32>(),
|
||||
value: scale_info::meta_type::<u32>(),
|
||||
},
|
||||
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::<u32>(),
|
||||
value: scale_info::meta_type::<u32>(),
|
||||
},
|
||||
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::<u64>(),
|
||||
},
|
||||
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::<u32>(),
|
||||
},
|
||||
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::<u32>(),
|
||||
},
|
||||
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::<Vec<u32>>(),
|
||||
},
|
||||
default: vec![0],
|
||||
docs: vec![],
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn store_metadata() {
|
||||
let metadata = Pallet::<Runtime>::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());
|
||||
})
|
||||
}
|
||||
@@ -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<T> =
|
||||
StorageMap<System, Blake2_128Concat, BlockNumberFor<T>, BlockNumberFor<T>>;
|
||||
|
||||
assert_eq!(Pallet::<Runtime>::generic_data2(5), None);
|
||||
GenericData2::<Runtime>::insert(5, 5);
|
||||
assert_eq!(Pallet::<Runtime>::generic_data2(5), Some(5));
|
||||
|
||||
/// Some random docs that ensure that docs are accepted
|
||||
#[crate::storage_alias]
|
||||
pub type GenericData<T> =
|
||||
StorageMap<Test2, Blake2_128Concat, BlockNumberFor<T>, BlockNumberFor<T>>;
|
||||
|
||||
#[crate::storage_alias]
|
||||
pub type GenericDataPallet<T: Config> =
|
||||
StorageMap<Pallet<T>, Blake2_128Concat, BlockNumberFor<T>, BlockNumberFor<T>>;
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn storage_value_mutate_exists_should_work() {
|
||||
new_test_ext().execute_with(|| {
|
||||
#[crate::storage_alias]
|
||||
pub type Value = StorageValue<Test, u32>;
|
||||
|
||||
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<Test, u32>;
|
||||
|
||||
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<Test, u32>;
|
||||
|
||||
// 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<T: Config> = StorageValue<Pallet<T>, u32>;
|
||||
|
||||
// Check that it works as expected.
|
||||
Value::<Runtime>::put(1);
|
||||
assert_eq!(1, Value::<Runtime>::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::<Runtime>::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<T: Get<&'static str>> = StorageValue<T, u32>;
|
||||
|
||||
// Check that it works as expected.
|
||||
Value::<Prefix>::put(1);
|
||||
assert_eq!(1, Value::<Prefix>::get().unwrap());
|
||||
|
||||
// The prefix is the one we declared above.
|
||||
assert_eq!(&b"Hello"[..], Value::<Prefix>::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<Test, u32>;
|
||||
|
||||
assert_eq!(&b"Test"[..], Value::module_prefix());
|
||||
|
||||
// The macro will use the pallet name as prefix.
|
||||
#[crate::storage_alias]
|
||||
pub type PalletValue<T: Config> = StorageValue<Pallet<T>, u32>;
|
||||
|
||||
assert_eq!(&b"System"[..], PalletValue::<Runtime>::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<Prefix, u32>;
|
||||
|
||||
Value::put(1);
|
||||
assert_eq!(1, Value::get().unwrap())
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user