// This file is part of Substrate. // Copyright (C) 2017-2020 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. //! Support code for the runtime. #![cfg_attr(not(feature = "std"), no_std)] /// Export ourself as `frame_support` to make tests happy. extern crate self as frame_support; #[doc(hidden)] pub use sp_tracing; #[cfg(feature = "std")] pub use serde; pub use sp_core::Void; #[doc(hidden)] pub use sp_std; #[doc(hidden)] pub use codec; #[cfg(feature = "std")] #[doc(hidden)] pub use once_cell; #[doc(hidden)] pub use paste; #[cfg(feature = "std")] #[doc(hidden)] pub use sp_state_machine::BasicExternalities; #[doc(hidden)] pub use sp_io::{storage::root as storage_root, self}; #[doc(hidden)] pub use sp_runtime::RuntimeDebug; #[macro_use] pub mod debug; #[macro_use] mod origin; #[macro_use] pub mod dispatch; pub mod storage; mod hash; #[macro_use] pub mod event; #[macro_use] pub mod metadata; #[macro_use] pub mod genesis_config; #[macro_use] pub mod inherent; #[macro_use] pub mod unsigned; #[macro_use] pub mod error; pub mod traits; pub mod weights; pub use self::hash::{ Twox256, Twox128, Blake2_256, Blake2_128, Identity, Twox64Concat, Blake2_128Concat, Hashable, StorageHasher, ReversibleStorageHasher }; pub use self::storage::{ StorageValue, StorageMap, StorageDoubleMap, StoragePrefixedMap, IterableStorageMap, IterableStorageDoubleMap, migration }; pub use self::dispatch::{Parameter, Callable}; pub use sp_runtime::{self, ConsensusEngineId, print, traits::Printable}; /// A type that cannot be instantiated. #[derive(Debug)] pub enum Never {} /// Create new implementations of the [`Get`](crate::traits::Get) trait. /// /// The so-called parameter type can be created in four different ways: /// /// - Using `const` to create a parameter type that provides a `const` getter. It is required that /// the `value` is const. /// /// - Declare the parameter type without `const` to have more freedom when creating the value. /// /// - Using `storage` to create a storage parameter type. This type is special as it tries to load /// the value from the storage under a fixed key. If the value could not be found in the storage, /// the given default value will be returned. It is required that the value implements /// [`Encode`](codec::Encode) and [`Decode`](codec::Decode). The key for looking up the value in /// the storage is built using the following formula: /// /// `twox_128(":" ++ NAME ++ ":")` where `NAME` is the name that is passed as type name. /// /// - Using `static` to create a static parameter type. Its value is /// being provided by a static variable with the equivalent name in `UPPER_SNAKE_CASE`. An /// additional `set` function is provided in this case to alter the static variable. /// **This is intended for testing ONLY and is ONLY available when `std` is enabled.** /// /// # Examples /// /// ``` /// # use frame_support::traits::Get; /// # use frame_support::parameter_types; /// // This function cannot be used in a const context. /// fn non_const_expression() -> u64 { 99 } /// /// const FIXED_VALUE: u64 = 10; /// parameter_types! { /// pub const Argument: u64 = 42 + FIXED_VALUE; /// /// Visibility of the type is optional /// OtherArgument: u64 = non_const_expression(); /// pub storage StorageArgument: u64 = 5; /// pub static StaticArgument: u32 = 7; /// } /// /// trait Config { /// type Parameter: Get; /// type OtherParameter: Get; /// type StorageParameter: Get; /// type StaticParameter: Get; /// } /// /// struct Runtime; /// impl Config for Runtime { /// type Parameter = Argument; /// type OtherParameter = OtherArgument; /// type StorageParameter = StorageArgument; /// type StaticParameter = StaticArgument; /// } /// /// // In testing, `StaticArgument` can be altered later: `StaticArgument::set(8)`. /// ``` /// /// # Invalid example: /// /// ```compile_fail /// # use frame_support::traits::Get; /// # use frame_support::parameter_types; /// // This function cannot be used in a const context. /// fn non_const_expression() -> u64 { 99 } /// /// parameter_types! { /// pub const Argument: u64 = non_const_expression(); /// } /// ``` #[macro_export] macro_rules! parameter_types { ( $( #[ $attr:meta ] )* $vis:vis const $name:ident: $type:ty = $value:expr; $( $rest:tt )* ) => ( $( #[ $attr ] )* $vis struct $name; $crate::parameter_types!(IMPL_CONST $name , $type , $value); $crate::parameter_types!( $( $rest )* ); ); ( $( #[ $attr:meta ] )* $vis:vis $name:ident: $type:ty = $value:expr; $( $rest:tt )* ) => ( $( #[ $attr ] )* $vis struct $name; $crate::parameter_types!(IMPL $name, $type, $value); $crate::parameter_types!( $( $rest )* ); ); ( $( #[ $attr:meta ] )* $vis:vis storage $name:ident: $type:ty = $value:expr; $( $rest:tt )* ) => ( $( #[ $attr ] )* $vis struct $name; $crate::parameter_types!(IMPL_STORAGE $name, $type, $value); $crate::parameter_types!( $( $rest )* ); ); () => (); (IMPL_CONST $name:ident, $type:ty, $value:expr) => { impl $name { /// Returns the value of this parameter type. pub const fn get() -> $type { $value } } impl> $crate::traits::Get for $name { fn get() -> I { I::from($value) } } }; (IMPL $name:ident, $type:ty, $value:expr) => { impl $name { /// Returns the value of this parameter type. pub fn get() -> $type { $value } } impl> $crate::traits::Get for $name { fn get() -> I { I::from($value) } } }; (IMPL_STORAGE $name:ident, $type:ty, $value:expr) => { impl $name { /// Returns the key for this parameter type. pub fn key() -> [u8; 16] { $crate::sp_io::hashing::twox_128( concat!(":", stringify!($name), ":").as_bytes() ) } /// Set the value of this parameter type in the storage. /// /// This needs to be executed in an externalities provided /// environment. pub fn set(value: &$type) { $crate::storage::unhashed::put(&Self::key(), value); } /// Returns the value of this parameter type. /// /// This needs to be executed in an externalities provided /// environment. pub fn get() -> $type { $crate::storage::unhashed::get(&Self::key()).unwrap_or_else(|| $value) } } impl> $crate::traits::Get for $name { fn get() -> I { I::from(Self::get()) } } }; ( $( $( #[ $attr:meta ] )* $vis:vis static $name:ident: $type:ty = $value:expr; )* ) => ( $crate::parameter_types_impl_thread_local!( $( $( #[ $attr ] )* $vis static $name: $type = $value; )* ); ); } #[cfg(not(feature = "std"))] #[macro_export] macro_rules! parameter_types_impl_thread_local { ( $( $any:tt )* ) => { compile_error!("static parameter types is only available in std and for testing."); }; } #[cfg(feature = "std")] #[macro_export] macro_rules! parameter_types_impl_thread_local { ( $( $( #[ $attr:meta ] )* $vis:vis static $name:ident: $type:ty = $value:expr; )* ) => { $crate::parameter_types_impl_thread_local!( IMPL_THREAD_LOCAL $( $vis, $name, $type, $value, )* ); $crate::paste::item! { $crate::parameter_types!( $( $( #[ $attr ] )* $vis $name: $type = [<$name:snake:upper>].with(|v| v.borrow().clone()); )* ); $( impl $name { /// Set the internal value. pub fn set(t: $type) { [<$name:snake:upper>].with(|v| *v.borrow_mut() = t); } } )* } }; (IMPL_THREAD_LOCAL $( $vis:vis, $name:ident, $type:ty, $value:expr, )* ) => { $crate::paste::item! { thread_local! { $( pub static [<$name:snake:upper>]: std::cell::RefCell<$type> = std::cell::RefCell::new($value); )* } } }; } /// Macro for easily creating a new implementation of both the `Get` and `Contains` traits. Use /// exactly as with `parameter_types`, only the type must be `Ord`. #[macro_export] macro_rules! ord_parameter_types { ( $( #[ $attr:meta ] )* $vis:vis const $name:ident: $type:ty = $value:expr; $( $rest:tt )* ) => ( $( #[ $attr ] )* $vis struct $name; $crate::parameter_types!{IMPL $name , $type , $value} $crate::ord_parameter_types!{IMPL $name , $type , $value} $crate::ord_parameter_types!{ $( $rest )* } ); () => (); (IMPL $name:ident , $type:ty , $value:expr) => { impl $crate::traits::Contains<$type> for $name { fn contains(t: &$type) -> bool { &$value == t } fn sorted_members() -> $crate::sp_std::prelude::Vec<$type> { vec![$value] } fn count() -> usize { 1 } #[cfg(feature = "runtime-benchmarks")] fn add(_: &$type) {} } } } #[doc(inline)] pub use frame_support_procedural::{ decl_storage, construct_runtime, transactional, RuntimeDebugNoBound }; /// Derive [`Clone`] but do not bound any generic. /// /// This is useful for type generic over runtime: /// ``` /// # use frame_support::CloneNoBound; /// trait Config { /// type C: Clone; /// } /// /// // Foo implements [`Clone`] because `C` bounds [`Clone`]. /// // Otherwise compilation will fail with an output telling `c` doesn't implement [`Clone`]. /// #[derive(CloneNoBound)] /// struct Foo { /// c: T::C, /// } /// ``` pub use frame_support_procedural::CloneNoBound; /// Derive [`Eq`] but do not bound any generic. /// /// This is useful for type generic over runtime: /// ``` /// # use frame_support::{EqNoBound, PartialEqNoBound}; /// trait Config { /// type C: Eq; /// } /// /// // Foo implements [`Eq`] because `C` bounds [`Eq`]. /// // Otherwise compilation will fail with an output telling `c` doesn't implement [`Eq`]. /// #[derive(PartialEqNoBound, EqNoBound)] /// struct Foo { /// c: T::C, /// } /// ``` pub use frame_support_procedural::EqNoBound; /// Derive [`PartialEq`] but do not bound any generic. /// /// This is useful for type generic over runtime: /// ``` /// # use frame_support::PartialEqNoBound; /// trait Config { /// type C: PartialEq; /// } /// /// // Foo implements [`PartialEq`] because `C` bounds [`PartialEq`]. /// // Otherwise compilation will fail with an output telling `c` doesn't implement [`PartialEq`]. /// #[derive(PartialEqNoBound)] /// struct Foo { /// c: T::C, /// } /// ``` pub use frame_support_procedural::PartialEqNoBound; /// Derive [`Debug`] but do not bound any generic. /// /// This is useful for type generic over runtime: /// ``` /// # use frame_support::DebugNoBound; /// # use core::fmt::Debug; /// trait Config { /// type C: Debug; /// } /// /// // Foo implements [`Debug`] because `C` bounds [`Debug`]. /// // Otherwise compilation will fail with an output telling `c` doesn't implement [`Debug`]. /// #[derive(DebugNoBound)] /// struct Foo { /// c: T::C, /// } /// ``` pub use frame_support_procedural::DebugNoBound; /// Assert the annotated function is executed within a storage transaction. /// /// The assertion is enabled for native execution and when `debug_assertions` are enabled. /// /// # Example /// /// ``` /// # use frame_support::{ /// # require_transactional, transactional, dispatch::DispatchResult /// # }; /// /// #[require_transactional] /// fn update_all(value: u32) -> DispatchResult { /// // Update multiple storages. /// // Return `Err` to indicate should revert. /// Ok(()) /// } /// /// #[transactional] /// fn safe_update(value: u32) -> DispatchResult { /// // This is safe /// update_all(value) /// } /// /// fn unsafe_update(value: u32) -> DispatchResult { /// // this may panic if unsafe_update is not called within a storage transaction /// update_all(value) /// } /// ``` pub use frame_support_procedural::require_transactional; /// Convert the current crate version into a [`PalletVersion`](crate::traits::PalletVersion). /// /// It uses the `CARGO_PKG_VERSION_MAJOR`, `CARGO_PKG_VERSION_MINOR` and /// `CARGO_PKG_VERSION_PATCH` environment variables to fetch the crate version. /// This means that the [`PalletVersion`](crate::traits::PalletVersion) /// object will correspond to the version of the crate the macro is called in! /// /// # Example /// /// ``` /// # use frame_support::{traits::PalletVersion, crate_to_pallet_version}; /// const Version: PalletVersion = crate_to_pallet_version!(); /// ``` pub use frame_support_procedural::crate_to_pallet_version; /// Return Err of the expression: `return Err($expression);`. /// /// Used as `fail!(expression)`. #[macro_export] macro_rules! fail { ( $y:expr ) => {{ return Err($y.into()); }} } /// Evaluate `$x:expr` and if not true return `Err($y:expr)`. /// /// Used as `ensure!(expression_to_ensure, expression_to_return_on_false)`. #[macro_export] macro_rules! ensure { ( $x:expr, $y:expr $(,)? ) => {{ if !$x { $crate::fail!($y); } }} } /// Evaluate an expression, assert it returns an expected `Err` value and that /// runtime storage has not been mutated (i.e. expression is a no-operation). /// /// Used as `assert_noop(expression_to_assert, expected_error_expression)`. #[macro_export] macro_rules! assert_noop { ( $x:expr, $y:expr $(,)? ) => { let h = $crate::storage_root(); $crate::assert_err!($x, $y); assert_eq!(h, $crate::storage_root()); } } /// Assert an expression returns an error specified. /// /// Used as `assert_err!(expression_to_assert, expected_error_expression)` #[macro_export] macro_rules! assert_err { ( $x:expr , $y:expr $(,)? ) => { assert_eq!($x, Err($y.into())); } } /// Assert an expression returns an error specified. /// /// This can be used on`DispatchResultWithPostInfo` when the post info should /// be ignored. #[macro_export] macro_rules! assert_err_ignore_postinfo { ( $x:expr , $y:expr $(,)? ) => { $crate::assert_err!($x.map(|_| ()).map_err(|e| e.error), $y); } } /// Assert an expression returns error with the given weight. #[macro_export] macro_rules! assert_err_with_weight { ($call:expr, $err:expr, $weight:expr $(,)? ) => { if let Err(dispatch_err_with_post) = $call { $crate::assert_err!($call.map(|_| ()).map_err(|e| e.error), $err); assert_eq!(dispatch_err_with_post.post_info.actual_weight, $weight.into()); } else { panic!("expected Err(_), got Ok(_).") } } } /// Panic if an expression doesn't evaluate to `Ok`. /// /// Used as `assert_ok!(expression_to_assert, expected_ok_expression)`, /// or `assert_ok!(expression_to_assert)` which would assert against `Ok(())`. #[macro_export] macro_rules! assert_ok { ( $x:expr $(,)? ) => { let is = $x; match is { Ok(_) => (), _ => assert!(false, "Expected Ok(_). Got {:#?}", is), } }; ( $x:expr, $y:expr $(,)? ) => { assert_eq!($x, Ok($y)); } } #[cfg(feature = "std")] #[doc(hidden)] pub use serde::{Serialize, Deserialize}; #[cfg(test)] mod tests { use super::*; use codec::{Codec, EncodeLike}; use frame_metadata::{ DecodeDifferent, StorageEntryMetadata, StorageMetadata, StorageEntryType, StorageEntryModifier, DefaultByteGetter, StorageHasher, }; use sp_std::{marker::PhantomData, result}; use sp_io::TestExternalities; pub trait Config: 'static { type BlockNumber: Codec + EncodeLike + Default; type Origin; type PalletInfo: crate::traits::PalletInfo; type DbWeight: crate::traits::Get; } mod module { #![allow(dead_code)] use super::Config; decl_module! { pub struct Module for enum Call where origin: T::Origin, system=self {} } } use self::module::Module; decl_storage! { trait Store for Module as Test { pub Data get(fn data) build(|_| vec![(15u32, 42u64)]): map hasher(twox_64_concat) u32 => u64; pub OptionLinkedMap: map hasher(blake2_128_concat) u32 => Option; pub GenericData get(fn generic_data): map hasher(identity) T::BlockNumber => T::BlockNumber; pub GenericData2 get(fn generic_data2): map hasher(blake2_128_concat) T::BlockNumber => Option; pub DataDM config(test_config) build(|_| vec![(15u32, 16u32, 42u64)]): double_map hasher(twox_64_concat) u32, hasher(blake2_128_concat) u32 => u64; pub GenericDataDM: double_map hasher(blake2_128_concat) T::BlockNumber, hasher(identity) T::BlockNumber => T::BlockNumber; pub GenericData2DM: double_map hasher(blake2_128_concat) T::BlockNumber, hasher(twox_64_concat) T::BlockNumber => Option; pub AppendableDM: double_map hasher(blake2_128_concat) u32, hasher(blake2_128_concat) T::BlockNumber => Vec; } } struct Test; impl Config for Test { type BlockNumber = u32; type Origin = u32; type PalletInfo = (); type DbWeight = (); } fn new_test_ext() -> TestExternalities { GenesisConfig::default().build_storage().unwrap().into() } type Map = Data; 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(|| { 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(|| { 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(|| { 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(|| { // 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(|| { 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_should_work() { new_test_ext().execute_with(|| { type DoubleMap = DataDM; // initialized during genesis assert_eq!(DoubleMap::get(&15u32, &16u32), 42u64); // get / insert / take let key1 = 17u32; let key2 = 18u32; 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); DoubleMap::remove_prefix(&key1); 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 = 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 = 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 = DataDM; type TestResult = result::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)); }); } const EXPECTED_METADATA: StorageMetadata = StorageMetadata { prefix: DecodeDifferent::Encode("Test"), entries: DecodeDifferent::Encode( &[ StorageEntryMetadata { name: DecodeDifferent::Encode("Data"), modifier: StorageEntryModifier::Default, ty: StorageEntryType::Map{ hasher: StorageHasher::Twox64Concat, key: DecodeDifferent::Encode("u32"), value: DecodeDifferent::Encode("u64"), unused: false, }, default: DecodeDifferent::Encode( DefaultByteGetter(&__GetByteStructData(PhantomData::)) ), documentation: DecodeDifferent::Encode(&[]), }, StorageEntryMetadata { name: DecodeDifferent::Encode("OptionLinkedMap"), modifier: StorageEntryModifier::Optional, ty: StorageEntryType::Map { hasher: StorageHasher::Blake2_128Concat, key: DecodeDifferent::Encode("u32"), value: DecodeDifferent::Encode("u32"), unused: false, }, default: DecodeDifferent::Encode( DefaultByteGetter(&__GetByteStructOptionLinkedMap(PhantomData::)) ), documentation: DecodeDifferent::Encode(&[]), }, StorageEntryMetadata { name: DecodeDifferent::Encode("GenericData"), modifier: StorageEntryModifier::Default, ty: StorageEntryType::Map{ hasher: StorageHasher::Identity, key: DecodeDifferent::Encode("T::BlockNumber"), value: DecodeDifferent::Encode("T::BlockNumber"), unused: false }, default: DecodeDifferent::Encode( DefaultByteGetter(&__GetByteStructGenericData(PhantomData::)) ), documentation: DecodeDifferent::Encode(&[]), }, StorageEntryMetadata { name: DecodeDifferent::Encode("GenericData2"), modifier: StorageEntryModifier::Optional, ty: StorageEntryType::Map{ hasher: StorageHasher::Blake2_128Concat, key: DecodeDifferent::Encode("T::BlockNumber"), value: DecodeDifferent::Encode("T::BlockNumber"), unused: false }, default: DecodeDifferent::Encode( DefaultByteGetter(&__GetByteStructGenericData2(PhantomData::)) ), documentation: DecodeDifferent::Encode(&[]), }, StorageEntryMetadata { name: DecodeDifferent::Encode("DataDM"), modifier: StorageEntryModifier::Default, ty: StorageEntryType::DoubleMap{ hasher: StorageHasher::Twox64Concat, key1: DecodeDifferent::Encode("u32"), key2: DecodeDifferent::Encode("u32"), value: DecodeDifferent::Encode("u64"), key2_hasher: StorageHasher::Blake2_128Concat, }, default: DecodeDifferent::Encode( DefaultByteGetter(&__GetByteStructDataDM(PhantomData::)) ), documentation: DecodeDifferent::Encode(&[]), }, StorageEntryMetadata { name: DecodeDifferent::Encode("GenericDataDM"), modifier: StorageEntryModifier::Default, ty: StorageEntryType::DoubleMap{ hasher: StorageHasher::Blake2_128Concat, key1: DecodeDifferent::Encode("T::BlockNumber"), key2: DecodeDifferent::Encode("T::BlockNumber"), value: DecodeDifferent::Encode("T::BlockNumber"), key2_hasher: StorageHasher::Identity, }, default: DecodeDifferent::Encode( DefaultByteGetter(&__GetByteStructGenericDataDM(PhantomData::)) ), documentation: DecodeDifferent::Encode(&[]), }, StorageEntryMetadata { name: DecodeDifferent::Encode("GenericData2DM"), modifier: StorageEntryModifier::Optional, ty: StorageEntryType::DoubleMap{ hasher: StorageHasher::Blake2_128Concat, key1: DecodeDifferent::Encode("T::BlockNumber"), key2: DecodeDifferent::Encode("T::BlockNumber"), value: DecodeDifferent::Encode("T::BlockNumber"), key2_hasher: StorageHasher::Twox64Concat, }, default: DecodeDifferent::Encode( DefaultByteGetter(&__GetByteStructGenericData2DM(PhantomData::)) ), documentation: DecodeDifferent::Encode(&[]), }, StorageEntryMetadata { name: DecodeDifferent::Encode("AppendableDM"), modifier: StorageEntryModifier::Default, ty: StorageEntryType::DoubleMap{ hasher: StorageHasher::Blake2_128Concat, key1: DecodeDifferent::Encode("u32"), key2: DecodeDifferent::Encode("T::BlockNumber"), value: DecodeDifferent::Encode("Vec"), key2_hasher: StorageHasher::Blake2_128Concat, }, default: DecodeDifferent::Encode( DefaultByteGetter(&__GetByteStructGenericData2DM(PhantomData::)) ), documentation: DecodeDifferent::Encode(&[]), }, ] ), }; #[test] fn store_metadata() { let metadata = Module::::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()); }) } }