mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-09 20:11:09 +00:00
storage doublemap in decl_storage (#1918)
* factorization * introduce GenericUnhashedStorage * implement generator and storage * impl double map in storage macro * improve StorageDoubleMapXX methods * remove storage from example and impl test * remove old comments * wasm compatible * improve imports * rename storages * update runtime impl version * make code less verbose * impl hash config for second key in double map hash available are all of Hashable trait * use double map in decl_storage for contract * fix double map config issue * add hasher into metadata * update impl version and build wasm * doc * add attrs * update metadata version * update runtime version * fix unused storage
This commit is contained in:
BIN
Binary file not shown.
@@ -58,8 +58,8 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
|
||||
spec_name: create_runtime_str!("node"),
|
||||
impl_name: create_runtime_str!("substrate-node"),
|
||||
authoring_version: 10,
|
||||
spec_version: 47,
|
||||
impl_version: 47,
|
||||
spec_version: 48,
|
||||
impl_version: 48,
|
||||
apis: RUNTIME_API_VERSIONS,
|
||||
};
|
||||
|
||||
|
||||
BIN
Binary file not shown.
@@ -66,7 +66,6 @@ decl_storage! {
|
||||
// A map that has enumerable entries.
|
||||
Bar get(bar) config(): linked_map T::AccountId => T::Balance;
|
||||
|
||||
|
||||
// this one uses the default, we'll demonstrate the usage of 'mutate' API.
|
||||
Foo get(foo) config(): T::Balance;
|
||||
}
|
||||
|
||||
@@ -263,6 +263,12 @@ pub enum StorageFunctionType {
|
||||
value: DecodeDifferentStr,
|
||||
is_linked: bool,
|
||||
},
|
||||
DoubleMap {
|
||||
key1: DecodeDifferentStr,
|
||||
key2: DecodeDifferentStr,
|
||||
value: DecodeDifferentStr,
|
||||
key2_hasher: DecodeDifferentStr,
|
||||
},
|
||||
}
|
||||
|
||||
/// A storage function modifier.
|
||||
@@ -304,8 +310,10 @@ pub enum RuntimeMetadata {
|
||||
V0(RuntimeMetadataDeprecated),
|
||||
/// Version 1 for runtime metadata. No longer used.
|
||||
V1(RuntimeMetadataDeprecated),
|
||||
/// Version 2 for runtime metadata.
|
||||
V2(RuntimeMetadataV2),
|
||||
/// Version 2 for runtime metadata. No longer used.
|
||||
V2(RuntimeMetadataDeprecated),
|
||||
/// Version 3 for runtime metadata.
|
||||
V3(RuntimeMetadataV3),
|
||||
}
|
||||
|
||||
/// Enum that should fail.
|
||||
@@ -325,10 +333,10 @@ impl Decode for RuntimeMetadataDeprecated {
|
||||
}
|
||||
}
|
||||
|
||||
/// The metadata of a runtime version 2.
|
||||
/// The metadata of a runtime.
|
||||
#[derive(Eq, Encode, PartialEq)]
|
||||
#[cfg_attr(feature = "std", derive(Decode, Debug, Serialize))]
|
||||
pub struct RuntimeMetadataV2 {
|
||||
pub struct RuntimeMetadataV3 {
|
||||
pub modules: DecodeDifferentArray<ModuleMetadata>,
|
||||
}
|
||||
|
||||
|
||||
@@ -49,6 +49,8 @@ use proc_macro::TokenStream;
|
||||
/// * storage value: `Foo: type`: implements [StorageValue](https://crates.parity.io/srml_support/storage/trait.StorageValue.html)
|
||||
/// * storage map: `Foo: map type => type`: implements [StorageMap](https://crates.parity.io/srml_support/storage/trait.StorageMap.html)
|
||||
/// * storage linked map: `Foo: linked_map type => type`: implements [StorageMap](https://crates.parity.io/srml_support/storage/trait.StorageMap.html) and [EnumarableStorageMap](https://crates.parity.io/srml_support/storage/trait.EnumerableStorageMap.html)
|
||||
/// * storage double map: Foo: double_map u32, $hash(u32) => u32;` implements `StorageDoubleMap` with hasher $hash one available in `Hashable` trait
|
||||
/// /!\ be careful while choosing the Hash, indeed malicious could craft second keys to lower the trie.
|
||||
///
|
||||
/// And it can be extended as such:
|
||||
///
|
||||
@@ -87,7 +89,7 @@ use proc_macro::TokenStream;
|
||||
/// ```
|
||||
/// This struct can be expose as `Config` by `decl_runtime` macro.
|
||||
///
|
||||
/// ## Module with instances
|
||||
/// ### Module with instances
|
||||
///
|
||||
/// `decl_storage!` macro support building modules with instances with the following syntax: (DefaultInstance type
|
||||
/// is optional)
|
||||
|
||||
@@ -541,4 +541,93 @@ impl<'a, I: Iterator<Item=syn::Meta>> Impls<'a, I> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn double_map(self, k1ty: &syn::Type, k2ty: &syn::Type, k2_hasher: TokenStream2) -> TokenStream2 {
|
||||
let Self {
|
||||
scrate,
|
||||
visibility,
|
||||
traitinstance,
|
||||
traittype,
|
||||
type_infos,
|
||||
fielddefault,
|
||||
prefix,
|
||||
name,
|
||||
attrs,
|
||||
instance_opts,
|
||||
..
|
||||
} = self;
|
||||
|
||||
let DeclStorageTypeInfos { typ, value_type, is_option, .. } = type_infos;
|
||||
let option_simple_1 = option_unwrap(is_option);
|
||||
|
||||
let as_double_map = quote!{ <Self as #scrate::storage::unhashed::generator::StorageDoubleMap<#k1ty, #k2ty, #typ>> };
|
||||
|
||||
let mutate_impl = if !is_option {
|
||||
quote!{
|
||||
#as_double_map::insert(key1, key2, &val, storage)
|
||||
}
|
||||
} else {
|
||||
quote!{
|
||||
match val {
|
||||
Some(ref val) => #as_double_map::insert(key1, key2, &val, storage),
|
||||
None => #as_double_map::remove(key1, key2, storage),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let InstanceOpts {
|
||||
comma_instance,
|
||||
equal_default_instance,
|
||||
bound_instantiable,
|
||||
instance,
|
||||
..
|
||||
} = instance_opts;
|
||||
|
||||
let final_prefix = if let Some(instance) = instance {
|
||||
let const_name = syn::Ident::new(&format!("{}{}", PREFIX_FOR, name.to_string()), proc_macro2::Span::call_site());
|
||||
quote!{ #instance::#const_name.as_bytes() }
|
||||
} else {
|
||||
quote!{ #prefix.as_bytes() }
|
||||
};
|
||||
|
||||
// generator for double map
|
||||
quote!{
|
||||
#( #[ #attrs ] )*
|
||||
#visibility struct #name<#traitinstance: #traittype, #instance #bound_instantiable #equal_default_instance>(#scrate::storage::generator::PhantomData<(#traitinstance #comma_instance)>);
|
||||
|
||||
impl<#traitinstance: #traittype, #instance #bound_instantiable> #scrate::storage::unhashed::generator::StorageDoubleMap<#k1ty, #k2ty, #typ> for #name<#traitinstance, #instance> {
|
||||
type Query = #value_type;
|
||||
|
||||
fn prefix() -> &'static [u8] {
|
||||
#final_prefix
|
||||
}
|
||||
|
||||
fn key_for(k1: &#k1ty, k2: &#k2ty) -> Vec<u8> {
|
||||
let mut key = #as_double_map::prefix_for(k1);
|
||||
key.extend(&#scrate::Hashable::#k2_hasher(k2));
|
||||
key
|
||||
}
|
||||
|
||||
fn get<S: #scrate::GenericUnhashedStorage>(key1: &#k1ty, key2: &#k2ty, storage: &S) -> Self::Query {
|
||||
let key = #as_double_map::key_for(key1, key2);
|
||||
storage.get(&key).#option_simple_1(|| #fielddefault)
|
||||
}
|
||||
|
||||
fn take<S: #scrate::GenericUnhashedStorage>(key1: &#k1ty, key2: &#k2ty, storage: &S) -> Self::Query {
|
||||
let key = #as_double_map::key_for(key1, key2);
|
||||
storage.take(&key).#option_simple_1(|| #fielddefault)
|
||||
}
|
||||
|
||||
fn mutate<R, F: FnOnce(&mut Self::Query) -> R, S: #scrate::GenericUnhashedStorage>(key1: &#k1ty, key2: &#k2ty, f: F, storage: &S) -> R {
|
||||
let mut val = #as_double_map::get(key1, key2, storage);
|
||||
|
||||
let ret = f(&mut val);
|
||||
#mutate_impl ;
|
||||
ret
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,6 +131,7 @@ struct DeclStorageBuild {
|
||||
enum DeclStorageType {
|
||||
Map(DeclStorageMap),
|
||||
LinkedMap(DeclStorageLinkedMap),
|
||||
DoubleMap(DeclStorageDoubleMap),
|
||||
Simple(syn::Type),
|
||||
}
|
||||
|
||||
@@ -150,6 +151,24 @@ struct DeclStorageLinkedMap {
|
||||
pub value: syn::Type,
|
||||
}
|
||||
|
||||
#[derive(Parse, ToTokens, Debug)]
|
||||
struct DeclStorageDoubleMap {
|
||||
pub map_keyword: ext::CustomToken<DoubleMapKeyword>,
|
||||
pub key1: syn::Type,
|
||||
pub comma_keyword: Token![,],
|
||||
pub key2_hasher: DeclStorageDoubleMapHasher,
|
||||
pub key2: ext::Parens<syn::Type>,
|
||||
pub ass_keyword: Token![=>],
|
||||
pub value: syn::Type,
|
||||
}
|
||||
|
||||
#[derive(Parse, ToTokens, Debug)]
|
||||
enum DeclStorageDoubleMapHasher {
|
||||
Blake2_256(ext::CustomToken<Blake2_256Keyword>),
|
||||
Twox256(ext::CustomToken<Twox256Keyword>),
|
||||
Twox128(ext::CustomToken<Twox128Keyword>),
|
||||
}
|
||||
|
||||
#[derive(Parse, ToTokens, Debug)]
|
||||
struct DeclStorageDefault {
|
||||
pub equal_token: Token![=],
|
||||
@@ -165,4 +184,8 @@ custom_keyword_impl!(AddExtraGenesis, "add_extra_genesis", "storage extra genesi
|
||||
custom_keyword_impl!(DeclStorageGetter, "get", "storage getter");
|
||||
custom_keyword!(MapKeyword, "map", "map as keyword");
|
||||
custom_keyword!(LinkedMapKeyword, "linked_map", "linked_map as keyword");
|
||||
custom_keyword!(DoubleMapKeyword, "double_map", "double_map as keyword");
|
||||
custom_keyword!(Blake2_256Keyword, "blake2_256", "Blake2_256 as keyword");
|
||||
custom_keyword!(Twox256Keyword, "twox_256", "Twox_256 as keyword");
|
||||
custom_keyword!(Twox128Keyword, "twox_128", "Twox_128 as keyword");
|
||||
custom_keyword_impl!(ExtraGenesisSkipPhantomDataField, "extra_genesis_skip_phantom_data_field", "extra_genesis_skip_phantom_data_field as keyword");
|
||||
|
||||
@@ -255,6 +255,9 @@ fn decl_store_extra_genesis(
|
||||
DeclStorageTypeInfosKind::Map {key_type, .. } => {
|
||||
quote!( #( #[ #attrs ] )* pub #ident: Vec<(#key_type, #storage_type)>, )
|
||||
},
|
||||
DeclStorageTypeInfosKind::DoubleMap {key1_type, key2_type, .. } => {
|
||||
quote!( #( #[ #attrs ] )* pub #ident: Vec<(#key1_type, #key2_type, #storage_type)>, )
|
||||
},
|
||||
});
|
||||
opt_build = Some(build.as_ref().map(|b| &b.expr.content).map(|b|quote!( #b ))
|
||||
.unwrap_or_else(|| quote!( (|config: &GenesisConfig<#traitinstance, #instance>| config.#ident.clone()) )));
|
||||
@@ -295,6 +298,17 @@ fn decl_store_extra_genesis(
|
||||
}
|
||||
}}
|
||||
},
|
||||
DeclStorageTypeInfosKind::DoubleMap { key1_type, key2_type, .. } => {
|
||||
quote!{{
|
||||
use #scrate::rstd::{cell::RefCell, marker::PhantomData};
|
||||
use #scrate::codec::{Encode, Decode};
|
||||
|
||||
let data = (#builder)(&self);
|
||||
for (k1, k2, v) in data.into_iter() {
|
||||
<#name<#traitinstance, #instance> as #scrate::storage::unhashed::generator::StorageDoubleMap<#key1_type, #key2_type, #typ>>::insert(&k1, &k2, &v, &storage);
|
||||
}
|
||||
}}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -581,6 +595,9 @@ fn decl_storage_items(
|
||||
DeclStorageTypeInfosKind::Map { key_type, is_linked: true } => {
|
||||
i.linked_map(key_type)
|
||||
},
|
||||
DeclStorageTypeInfosKind::DoubleMap { key1_type, key2_type, key2_hasher } => {
|
||||
i.double_map(key1_type, key2_type, key2_hasher)
|
||||
},
|
||||
};
|
||||
impls.extend(implementation)
|
||||
}
|
||||
@@ -657,6 +674,17 @@ fn impl_store_fns(
|
||||
}
|
||||
}
|
||||
}
|
||||
DeclStorageTypeInfosKind::DoubleMap { key1_type, key2_type, .. } => {
|
||||
quote!{
|
||||
pub fn #get_fn<KArg1, KArg2>(k1: KArg1, k2: KArg2) -> #value_type
|
||||
where
|
||||
KArg1: #scrate::storage::generator::Borrow<#key1_type>,
|
||||
KArg2: #scrate::storage::generator::Borrow<#key2_type>,
|
||||
{
|
||||
<#name<#traitinstance> as #scrate::storage::unhashed::generator::StorageDoubleMap<#key1_type, #key2_type, #typ>> :: get(k1.borrow(), k2.borrow(), &#scrate::storage::RuntimeStorage)
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
items.extend(item);
|
||||
}
|
||||
@@ -714,6 +742,19 @@ fn store_functions_to_metadata (
|
||||
}
|
||||
}
|
||||
},
|
||||
DeclStorageTypeInfosKind::DoubleMap { key1_type, key2_type, key2_hasher } => {
|
||||
let k1ty = clean_type_string("e!(#key1_type).to_string());
|
||||
let k2ty = clean_type_string("e!(#key2_type).to_string());
|
||||
let k2_hasher = clean_type_string(&key2_hasher.to_string());
|
||||
quote!{
|
||||
#scrate::storage::generator::StorageFunctionType::DoubleMap {
|
||||
key1: #scrate::storage::generator::DecodeDifferent::Encode(#k1ty),
|
||||
key2: #scrate::storage::generator::DecodeDifferent::Encode(#k2ty),
|
||||
value: #scrate::storage::generator::DecodeDifferent::Encode(#styp),
|
||||
key2_hasher: #scrate::storage::generator::DecodeDifferent::Encode(#k2_hasher),
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
let modifier = if type_infos.is_option {
|
||||
quote!{
|
||||
@@ -810,6 +851,11 @@ enum DeclStorageTypeInfosKind<'a> {
|
||||
key_type: &'a syn::Type,
|
||||
is_linked: bool,
|
||||
},
|
||||
DoubleMap {
|
||||
key1_type: &'a syn::Type,
|
||||
key2_type: &'a syn::Type,
|
||||
key2_hasher: TokenStream2,
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> DeclStorageTypeInfosKind<'a> {
|
||||
@@ -832,6 +878,11 @@ fn get_type_infos(storage_type: &DeclStorageType) -> DeclStorageTypeInfos {
|
||||
key_type: &map.key,
|
||||
is_linked: true,
|
||||
}),
|
||||
DeclStorageType::DoubleMap(ref map) => (&map.value, DeclStorageTypeInfosKind::DoubleMap {
|
||||
key1_type: &map.key1,
|
||||
key2_type: &map.key2.content,
|
||||
key2_hasher: { let h = &map.key2_hasher; quote! { #h } },
|
||||
}),
|
||||
};
|
||||
|
||||
let extracted_type = ext::extract_type_option(value_type);
|
||||
|
||||
@@ -31,7 +31,9 @@ use sr_std::borrow::Borrow;
|
||||
/// The storage key (i.e. the key under which the `Value` will be stored) is created from two parts.
|
||||
/// The first part is a hash of a concatenation of the `PREFIX` and `Key1`. And the second part
|
||||
/// is a hash of a `Key2`.
|
||||
pub trait StorageDoubleMap {
|
||||
///
|
||||
/// Hasher are implemented in derive_key* methods.
|
||||
pub trait StorageDoubleMapWithHasher {
|
||||
type Key1: Codec;
|
||||
type Key2: Codec;
|
||||
type Value: Codec + Default;
|
||||
|
||||
@@ -27,12 +27,12 @@ pub trait Hashable: Sized {
|
||||
|
||||
impl<T: Codec> Hashable for T {
|
||||
fn blake2_256(&self) -> [u8; 32] {
|
||||
blake2_256(&self.encode())
|
||||
self.using_encoded(blake2_256)
|
||||
}
|
||||
fn twox_128(&self) -> [u8; 16] {
|
||||
twox_128(&self.encode())
|
||||
self.using_encoded(twox_128)
|
||||
}
|
||||
fn twox_256(&self) -> [u8; 32] {
|
||||
twox_256(&self.encode())
|
||||
self.using_encoded(twox_256)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ pub use paste;
|
||||
pub use sr_primitives as runtime_primitives;
|
||||
|
||||
pub use self::storage::generator::Storage as GenericStorage;
|
||||
pub use self::storage::unhashed::generator::UnhashedStorage as GenericUnhashedStorage;
|
||||
|
||||
#[macro_use]
|
||||
pub mod dispatch;
|
||||
@@ -55,10 +56,10 @@ pub mod inherent;
|
||||
mod double_map;
|
||||
pub mod traits;
|
||||
|
||||
pub use self::storage::{StorageVec, StorageList, StorageValue, StorageMap, EnumerableStorageMap};
|
||||
pub use self::storage::{StorageVec, StorageList, StorageValue, StorageMap, EnumerableStorageMap, StorageDoubleMap};
|
||||
pub use self::hashable::Hashable;
|
||||
pub use self::dispatch::{Parameter, Dispatchable, Callable, IsSubType};
|
||||
pub use self::double_map::StorageDoubleMap;
|
||||
pub use self::double_map::StorageDoubleMapWithHasher;
|
||||
pub use runtime_io::print;
|
||||
|
||||
#[doc(inline)]
|
||||
@@ -140,6 +141,12 @@ mod tests {
|
||||
use parity_codec::Codec;
|
||||
use runtime_io::{with_externalities, Blake2Hasher};
|
||||
use runtime_primitives::BuildStorage;
|
||||
pub use srml_metadata::{
|
||||
DecodeDifferent, StorageMetadata, StorageFunctionMetadata,
|
||||
StorageFunctionType, StorageFunctionModifier,
|
||||
DefaultByte, DefaultByteGetter,
|
||||
};
|
||||
pub use rstd::marker::PhantomData;
|
||||
|
||||
pub trait Trait {
|
||||
type BlockNumber: Codec + Default;
|
||||
@@ -164,6 +171,10 @@ mod tests {
|
||||
pub Data get(data) build(|_| vec![(15u32, 42u64)]): linked_map u32 => u64;
|
||||
pub GenericData get(generic_data): linked_map T::BlockNumber => T::BlockNumber;
|
||||
pub GenericData2 get(generic_data2): linked_map T::BlockNumber => Option<T::BlockNumber>;
|
||||
|
||||
pub DataDM config(test_config) build(|_| vec![(15u32, 16u32, 42u64)]): double_map u32, blake2_256(u32) => u64;
|
||||
pub GenericDataDM: double_map T::BlockNumber, twox_128(T::BlockNumber) => T::BlockNumber;
|
||||
pub GenericData2DM: double_map T::BlockNumber, twox_256(T::BlockNumber) => Option<T::BlockNumber>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,7 +191,7 @@ mod tests {
|
||||
type Map = Data<Test>;
|
||||
|
||||
#[test]
|
||||
fn basic_insert_remove_should_work() {
|
||||
fn linked_map_basic_insert_remove_should_work() {
|
||||
with_externalities(&mut new_test_ext(), || {
|
||||
// initialised during genesis
|
||||
assert_eq!(Map::get(&15u32), 42u64);
|
||||
@@ -206,7 +217,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enumeration_and_head_should_work() {
|
||||
fn linked_map_enumeration_and_head_should_work() {
|
||||
with_externalities(&mut new_test_ext(), || {
|
||||
assert_eq!(Map::head(), Some(15));
|
||||
assert_eq!(Map::enumerate().collect::<Vec<_>>(), vec![(15, 42)]);
|
||||
@@ -257,4 +268,128 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn double_map_basic_insert_remove_remove_prefix_should_work() {
|
||||
with_externalities(&mut new_test_ext(), || {
|
||||
type DoubleMap = DataDM<Test>;
|
||||
// initialised 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);
|
||||
});
|
||||
}
|
||||
|
||||
const EXPECTED_METADATA: StorageMetadata = StorageMetadata {
|
||||
functions: DecodeDifferent::Encode(&[
|
||||
StorageFunctionMetadata {
|
||||
name: DecodeDifferent::Encode("Data"),
|
||||
modifier: StorageFunctionModifier::Default,
|
||||
ty: StorageFunctionType::Map{
|
||||
key: DecodeDifferent::Encode("u32"), value: DecodeDifferent::Encode("u64"), is_linked: true
|
||||
},
|
||||
default: DecodeDifferent::Encode(
|
||||
DefaultByteGetter(&__GetByteStructData(PhantomData::<Test>))
|
||||
),
|
||||
documentation: DecodeDifferent::Encode(&[]),
|
||||
},
|
||||
StorageFunctionMetadata {
|
||||
name: DecodeDifferent::Encode("GenericData"),
|
||||
modifier: StorageFunctionModifier::Default,
|
||||
ty: StorageFunctionType::Map{
|
||||
key: DecodeDifferent::Encode("T::BlockNumber"), value: DecodeDifferent::Encode("T::BlockNumber"), is_linked: true
|
||||
},
|
||||
default: DecodeDifferent::Encode(
|
||||
DefaultByteGetter(&__GetByteStructGenericData(PhantomData::<Test>))
|
||||
),
|
||||
documentation: DecodeDifferent::Encode(&[]),
|
||||
},
|
||||
StorageFunctionMetadata {
|
||||
name: DecodeDifferent::Encode("GenericData2"),
|
||||
modifier: StorageFunctionModifier::Optional,
|
||||
ty: StorageFunctionType::Map{
|
||||
key: DecodeDifferent::Encode("T::BlockNumber"), value: DecodeDifferent::Encode("T::BlockNumber"), is_linked: true
|
||||
},
|
||||
default: DecodeDifferent::Encode(
|
||||
DefaultByteGetter(&__GetByteStructGenericData2(PhantomData::<Test>))
|
||||
),
|
||||
documentation: DecodeDifferent::Encode(&[]),
|
||||
},
|
||||
StorageFunctionMetadata {
|
||||
name: DecodeDifferent::Encode("DataDM"),
|
||||
modifier: StorageFunctionModifier::Default,
|
||||
ty: StorageFunctionType::DoubleMap{
|
||||
key1: DecodeDifferent::Encode("u32"),
|
||||
key2: DecodeDifferent::Encode("u32"),
|
||||
value: DecodeDifferent::Encode("u64"),
|
||||
key2_hasher: DecodeDifferent::Encode("blake2_256"),
|
||||
},
|
||||
default: DecodeDifferent::Encode(
|
||||
DefaultByteGetter(&__GetByteStructDataDM(PhantomData::<Test>))
|
||||
),
|
||||
documentation: DecodeDifferent::Encode(&[]),
|
||||
},
|
||||
StorageFunctionMetadata {
|
||||
name: DecodeDifferent::Encode("GenericDataDM"),
|
||||
modifier: StorageFunctionModifier::Default,
|
||||
ty: StorageFunctionType::DoubleMap{
|
||||
key1: DecodeDifferent::Encode("T::BlockNumber"),
|
||||
key2: DecodeDifferent::Encode("T::BlockNumber"),
|
||||
value: DecodeDifferent::Encode("T::BlockNumber"),
|
||||
key2_hasher: DecodeDifferent::Encode("twox_128"),
|
||||
},
|
||||
default: DecodeDifferent::Encode(
|
||||
DefaultByteGetter(&__GetByteStructGenericDataDM(PhantomData::<Test>))
|
||||
),
|
||||
documentation: DecodeDifferent::Encode(&[]),
|
||||
},
|
||||
StorageFunctionMetadata {
|
||||
name: DecodeDifferent::Encode("GenericData2DM"),
|
||||
modifier: StorageFunctionModifier::Optional,
|
||||
ty: StorageFunctionType::DoubleMap{
|
||||
key1: DecodeDifferent::Encode("T::BlockNumber"),
|
||||
key2: DecodeDifferent::Encode("T::BlockNumber"),
|
||||
value: DecodeDifferent::Encode("T::BlockNumber"),
|
||||
key2_hasher: DecodeDifferent::Encode("twox_256"),
|
||||
},
|
||||
default: DecodeDifferent::Encode(
|
||||
DefaultByteGetter(&__GetByteStructGenericData2DM(PhantomData::<Test>))
|
||||
),
|
||||
documentation: DecodeDifferent::Encode(&[]),
|
||||
},
|
||||
])
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn store_metadata() {
|
||||
let metadata = Module::<Test>::store_metadata();
|
||||
assert_eq!(EXPECTED_METADATA, metadata);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
pub use srml_metadata::{
|
||||
DecodeDifferent, FnEncode, RuntimeMetadata,
|
||||
ModuleMetadata, RuntimeMetadataV2,
|
||||
ModuleMetadata, RuntimeMetadataV3,
|
||||
DefaultByteGetter, RuntimeMetadataPrefixed,
|
||||
};
|
||||
|
||||
@@ -36,8 +36,8 @@ macro_rules! impl_runtime_metadata {
|
||||
) => {
|
||||
impl $runtime {
|
||||
pub fn metadata() -> $crate::metadata::RuntimeMetadataPrefixed {
|
||||
$crate::metadata::RuntimeMetadata::V2 (
|
||||
$crate::metadata::RuntimeMetadataV2 {
|
||||
$crate::metadata::RuntimeMetadata::V3 (
|
||||
$crate::metadata::RuntimeMetadataV3 {
|
||||
modules: $crate::__runtime_modules_to_metadata!($runtime;; $( $rest )*),
|
||||
}
|
||||
).into()
|
||||
@@ -377,8 +377,8 @@ mod tests {
|
||||
event_module2::Module with Event Storage Call,
|
||||
);
|
||||
|
||||
const EXPECTED_METADATA: RuntimeMetadata = RuntimeMetadata::V2(
|
||||
RuntimeMetadataV2 {
|
||||
const EXPECTED_METADATA: RuntimeMetadata = RuntimeMetadata::V3(
|
||||
RuntimeMetadataV3 {
|
||||
modules: DecodeDifferent::Encode(&[
|
||||
ModuleMetadata {
|
||||
name: DecodeDifferent::Encode("system"),
|
||||
|
||||
@@ -48,6 +48,8 @@
|
||||
|
||||
use crate::codec;
|
||||
use crate::rstd::vec::Vec;
|
||||
#[cfg(feature = "std")]
|
||||
use crate::storage::unhashed::generator::UnhashedStorage;
|
||||
#[doc(hidden)]
|
||||
pub use crate::rstd::borrow::Borrow;
|
||||
#[doc(hidden)]
|
||||
@@ -101,20 +103,19 @@ pub trait Storage {
|
||||
#[cfg(feature = "std")]
|
||||
impl<S: sr_primitives::BuildStorage> Storage for (crate::rstd::cell::RefCell<&mut sr_primitives::StorageOverlay>, PhantomData<S>) {
|
||||
fn exists(&self, key: &[u8]) -> bool {
|
||||
self.0.borrow().contains_key(S::hash(key).as_ref())
|
||||
UnhashedStorage::exists(self, &S::hash(key))
|
||||
}
|
||||
|
||||
fn get<T: codec::Decode>(&self, key: &[u8]) -> Option<T> {
|
||||
self.0.borrow().get(S::hash(key).as_ref())
|
||||
.map(|x| codec::Decode::decode(&mut x.as_slice()).expect("Unable to decode expected type."))
|
||||
UnhashedStorage::get(self, &S::hash(key))
|
||||
}
|
||||
|
||||
fn put<T: codec::Encode>(&self, key: &[u8], val: &T) {
|
||||
self.0.borrow_mut().insert(S::hash(key).to_vec(), codec::Encode::encode(val));
|
||||
UnhashedStorage::put(self, &S::hash(key), val)
|
||||
}
|
||||
|
||||
fn kill(&self, key: &[u8]) {
|
||||
self.0.borrow_mut().remove(S::hash(key).as_ref());
|
||||
UnhashedStorage::kill(self, &S::hash(key))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ use crate::codec::{Codec, Encode, Decode, KeyedVec, Input};
|
||||
|
||||
#[macro_use]
|
||||
pub mod generator;
|
||||
pub mod unhashed;
|
||||
|
||||
struct IncrementalInput<'a> {
|
||||
key: &'a [u8],
|
||||
@@ -56,84 +57,73 @@ impl<'a> Input for IncrementalChildInput<'a> {
|
||||
|
||||
/// Return the value of the item in storage under `key`, or `None` if there is no explicit entry.
|
||||
pub fn get<T: Decode + Sized>(key: &[u8]) -> Option<T> {
|
||||
let key = twox_128(key);
|
||||
runtime_io::read_storage(&key[..], &mut [0; 0][..], 0).map(|_| {
|
||||
let mut input = IncrementalInput {
|
||||
key: &key[..],
|
||||
pos: 0,
|
||||
};
|
||||
Decode::decode(&mut input).expect("storage is not null, therefore must be a valid type")
|
||||
})
|
||||
unhashed::get(&twox_128(key))
|
||||
}
|
||||
|
||||
/// Return the value of the item in storage under `key`, or the type's default if there is no
|
||||
/// explicit entry.
|
||||
pub fn get_or_default<T: Decode + Sized + Default>(key: &[u8]) -> T {
|
||||
get(key).unwrap_or_else(Default::default)
|
||||
unhashed::get_or_default(&twox_128(key))
|
||||
}
|
||||
|
||||
/// Return the value of the item in storage under `key`, or `default_value` if there is no
|
||||
/// explicit entry.
|
||||
pub fn get_or<T: Decode + Sized>(key: &[u8], default_value: T) -> T {
|
||||
get(key).unwrap_or(default_value)
|
||||
unhashed::get_or(&twox_128(key), default_value)
|
||||
}
|
||||
|
||||
/// Return the value of the item in storage under `key`, or `default_value()` if there is no
|
||||
/// explicit entry.
|
||||
pub fn get_or_else<T: Decode + Sized, F: FnOnce() -> T>(key: &[u8], default_value: F) -> T {
|
||||
get(key).unwrap_or_else(default_value)
|
||||
unhashed::get_or_else(&twox_128(key), default_value)
|
||||
}
|
||||
|
||||
/// Put `value` in storage under `key`.
|
||||
pub fn put<T: Encode>(key: &[u8], value: &T) {
|
||||
value.using_encoded(|slice| runtime_io::set_storage(&twox_128(key)[..], slice));
|
||||
unhashed::put(&twox_128(key), value)
|
||||
}
|
||||
|
||||
/// Remove `key` from storage, returning its value if it had an explicit entry or `None` otherwise.
|
||||
pub fn take<T: Decode + Sized>(key: &[u8]) -> Option<T> {
|
||||
let r = get(key);
|
||||
if r.is_some() {
|
||||
kill(key);
|
||||
}
|
||||
r
|
||||
unhashed::take(&twox_128(key))
|
||||
}
|
||||
|
||||
/// Remove `key` from storage, returning its value, or, if there was no explicit entry in storage,
|
||||
/// the default for its type.
|
||||
pub fn take_or_default<T: Decode + Sized + Default>(key: &[u8]) -> T {
|
||||
take(key).unwrap_or_else(Default::default)
|
||||
unhashed::take_or_default(&twox_128(key))
|
||||
}
|
||||
|
||||
/// Return the value of the item in storage under `key`, or `default_value` if there is no
|
||||
/// explicit entry. Ensure there is no explicit entry on return.
|
||||
pub fn take_or<T: Decode + Sized>(key: &[u8], default_value: T) -> T {
|
||||
take(key).unwrap_or(default_value)
|
||||
unhashed::take_or(&twox_128(key), default_value)
|
||||
}
|
||||
|
||||
/// Return the value of the item in storage under `key`, or `default_value()` if there is no
|
||||
/// explicit entry. Ensure there is no explicit entry on return.
|
||||
pub fn take_or_else<T: Decode + Sized, F: FnOnce() -> T>(key: &[u8], default_value: F) -> T {
|
||||
take(key).unwrap_or_else(default_value)
|
||||
unhashed::take_or_else(&twox_128(key), default_value)
|
||||
}
|
||||
|
||||
/// Check to see if `key` has an explicit entry in storage.
|
||||
pub fn exists(key: &[u8]) -> bool {
|
||||
runtime_io::exists_storage(&twox_128(key)[..])
|
||||
unhashed::exists(&twox_128(key))
|
||||
}
|
||||
|
||||
/// Ensure `key` has no explicit entry in storage.
|
||||
pub fn kill(key: &[u8]) {
|
||||
runtime_io::clear_storage(&twox_128(key)[..]);
|
||||
unhashed::kill(&twox_128(key))
|
||||
}
|
||||
|
||||
/// Get a Vec of bytes from storage.
|
||||
pub fn get_raw(key: &[u8]) -> Option<Vec<u8>> {
|
||||
runtime_io::storage(&twox_128(key)[..])
|
||||
unhashed::get_raw(&twox_128(key))
|
||||
}
|
||||
|
||||
/// Put a raw byte slice into storage.
|
||||
pub fn put_raw(key: &[u8], value: &[u8]) {
|
||||
runtime_io::set_storage(&twox_128(key)[..], value)
|
||||
unhashed::put_raw(&twox_128(key), value)
|
||||
}
|
||||
|
||||
/// The underlying runtime storage.
|
||||
@@ -141,27 +131,58 @@ pub struct RuntimeStorage;
|
||||
|
||||
impl crate::GenericStorage for RuntimeStorage {
|
||||
fn exists(&self, key: &[u8]) -> bool {
|
||||
super::storage::exists(key)
|
||||
exists(key)
|
||||
}
|
||||
|
||||
/// Load the bytes of a key from storage. Can panic if the type is incorrect.
|
||||
fn get<T: Decode>(&self, key: &[u8]) -> Option<T> {
|
||||
super::storage::get(key)
|
||||
get(key)
|
||||
}
|
||||
|
||||
/// Put a value in under a key.
|
||||
fn put<T: Encode>(&self, key: &[u8], val: &T) {
|
||||
super::storage::put(key, val)
|
||||
put(key, val)
|
||||
}
|
||||
|
||||
/// Remove the bytes of a key from storage.
|
||||
fn kill(&self, key: &[u8]) {
|
||||
super::storage::kill(key)
|
||||
kill(key)
|
||||
}
|
||||
|
||||
/// Take a value from storage, deleting it after reading.
|
||||
fn take<T: Decode>(&self, key: &[u8]) -> Option<T> {
|
||||
super::storage::take(key)
|
||||
take(key)
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::GenericUnhashedStorage for RuntimeStorage {
|
||||
fn exists(&self, key: &[u8]) -> bool {
|
||||
unhashed::exists(key)
|
||||
}
|
||||
|
||||
/// Load the bytes of a key from storage. Can panic if the type is incorrect.
|
||||
fn get<T: Decode>(&self, key: &[u8]) -> Option<T> {
|
||||
unhashed::get(key)
|
||||
}
|
||||
|
||||
/// Put a value in under a key.
|
||||
fn put<T: Encode>(&self, key: &[u8], val: &T) {
|
||||
unhashed::put(key, val)
|
||||
}
|
||||
|
||||
/// Remove the bytes of a key from storage.
|
||||
fn kill(&self, key: &[u8]) {
|
||||
unhashed::kill(key)
|
||||
}
|
||||
|
||||
/// Remove the bytes of a key from storage.
|
||||
fn kill_prefix(&self, prefix: &[u8]) {
|
||||
unhashed::kill_prefix(prefix)
|
||||
}
|
||||
|
||||
/// Take a value from storage, deleting it after reading.
|
||||
fn take<T: Decode>(&self, key: &[u8]) -> Option<T> {
|
||||
unhashed::take(key)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -374,6 +395,109 @@ impl<K: Codec, V: Codec, U> EnumerableStorageMap<K, V> for U where U: generator:
|
||||
}
|
||||
}
|
||||
|
||||
/// An implementation of a map with a two keys.
|
||||
///
|
||||
/// It provides an important ability to efficiently remove all entries
|
||||
/// that have a common first key.
|
||||
///
|
||||
/// # Mapping of keys to a storage path
|
||||
///
|
||||
/// The storage key (i.e. the key under which the `Value` will be stored) is created from two parts.
|
||||
/// The first part is a hash of a concatenation of the `PREFIX` and `Key1`. And the second part
|
||||
/// is a hash of a `Key2`.
|
||||
///
|
||||
/// /!\ be careful while choosing the Hash, indeed malicious could craft second keys to lower the trie.
|
||||
pub trait StorageDoubleMap<K1: Codec, K2: Codec, V: Codec> {
|
||||
/// The type that get/take returns.
|
||||
type Query;
|
||||
|
||||
/// Get the prefix key in storage.
|
||||
fn prefix() -> &'static [u8];
|
||||
|
||||
/// Get the storage key used to fetch a value corresponding to a specific key.
|
||||
fn key_for<KArg1: Borrow<K1>, KArg2: Borrow<K2>>(k1: KArg1, k2: KArg2) -> Vec<u8>;
|
||||
|
||||
/// Get the storage prefix used to fetch keys corresponding to a specific key1.
|
||||
fn prefix_for<KArg1: Borrow<K1>>(k1: KArg1) -> Vec<u8>;
|
||||
|
||||
/// true if the value is defined in storage.
|
||||
fn exists<KArg1: Borrow<K1>, KArg2: Borrow<K2>>(k1: KArg1, k2: KArg2) -> bool;
|
||||
|
||||
/// Load the value associated with the given key from the map.
|
||||
fn get<KArg1: Borrow<K1>, KArg2: Borrow<K2>>(k1: KArg1, k2: KArg2) -> Self::Query;
|
||||
|
||||
/// Take the value under a key.
|
||||
fn take<KArg1: Borrow<K1>, KArg2: Borrow<K2>>(k1: KArg1, k2: KArg2) -> Self::Query;
|
||||
|
||||
/// Store a value to be associated with the given key from the map.
|
||||
fn insert<KArg1: Borrow<K1>, KArg2: Borrow<K2>, VArg: Borrow<V>>(k1: KArg1, k2: KArg2, val: VArg);
|
||||
|
||||
/// Remove the value under a key.
|
||||
fn remove<KArg1: Borrow<K1>, KArg2: Borrow<K2>>(k1: KArg1, k2: KArg2);
|
||||
|
||||
/// Removes all entries that shares the `k1` as the first key.
|
||||
fn remove_prefix<KArg1: Borrow<K1>>(k1: KArg1);
|
||||
|
||||
/// Mutate the value under a key.
|
||||
fn mutate<KArg1, KArg2, R, F>(k1: KArg1, k2: KArg2, f: F) -> R
|
||||
where
|
||||
KArg1: Borrow<K1>,
|
||||
KArg2: Borrow<K2>,
|
||||
F: FnOnce(&mut Self::Query) -> R;
|
||||
}
|
||||
|
||||
impl<K1: Codec, K2: Codec, V: Codec, U> StorageDoubleMap<K1, K2, V> for U
|
||||
where
|
||||
U: unhashed::generator::StorageDoubleMap<K1, K2, V>
|
||||
{
|
||||
type Query = U::Query;
|
||||
|
||||
fn prefix() -> &'static [u8] {
|
||||
<U as unhashed::generator::StorageDoubleMap<K1, K2, V>>::prefix()
|
||||
}
|
||||
|
||||
fn key_for<KArg1: Borrow<K1>, KArg2: Borrow<K2>>(k1: KArg1, k2: KArg2) -> Vec<u8> {
|
||||
<U as unhashed::generator::StorageDoubleMap<K1, K2, V>>::key_for(k1.borrow(), k2.borrow())
|
||||
}
|
||||
|
||||
fn prefix_for<KArg1: Borrow<K1>>(k1: KArg1) -> Vec<u8> {
|
||||
<U as unhashed::generator::StorageDoubleMap<K1, K2, V>>::prefix_for(k1.borrow())
|
||||
}
|
||||
|
||||
fn exists<KArg1: Borrow<K1>, KArg2: Borrow<K2>>(k1: KArg1, k2: KArg2) -> bool {
|
||||
U::exists(k1.borrow(), k2.borrow(), &RuntimeStorage)
|
||||
}
|
||||
|
||||
fn get<KArg1: Borrow<K1>, KArg2: Borrow<K2>>(k1: KArg1, k2: KArg2) -> Self::Query {
|
||||
U::get(k1.borrow(), k2.borrow(), &RuntimeStorage)
|
||||
}
|
||||
|
||||
fn take<KArg1: Borrow<K1>, KArg2: Borrow<K2>>(k1: KArg1, k2: KArg2) -> Self::Query {
|
||||
U::take(k1.borrow(), k2.borrow(), &RuntimeStorage)
|
||||
}
|
||||
|
||||
fn insert<KArg1: Borrow<K1>, KArg2: Borrow<K2>, VArg: Borrow<V>>(k1: KArg1, k2: KArg2, val: VArg) {
|
||||
U::insert(k1.borrow(), k2.borrow(), val.borrow(), &RuntimeStorage)
|
||||
}
|
||||
|
||||
fn remove<KArg1: Borrow<K1>, KArg2: Borrow<K2>>(k1: KArg1, k2: KArg2) {
|
||||
U::remove(k1.borrow(), k2.borrow(), &RuntimeStorage)
|
||||
}
|
||||
|
||||
fn remove_prefix<KArg1: Borrow<K1>>(k1: KArg1) {
|
||||
U::remove_prefix(k1.borrow(), &RuntimeStorage)
|
||||
}
|
||||
|
||||
fn mutate<KArg1, KArg2, R, F>(k1: KArg1, k2: KArg2, f: F) -> R
|
||||
where
|
||||
KArg1: Borrow<K1>,
|
||||
KArg2: Borrow<K2>,
|
||||
F: FnOnce(&mut Self::Query) -> R
|
||||
{
|
||||
U::mutate(k1.borrow(), k2.borrow(), f, &RuntimeStorage)
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait to conveniently store a vector of storable data.
|
||||
pub trait StorageVec {
|
||||
type Item: Default + Sized + Codec;
|
||||
@@ -433,149 +557,6 @@ pub trait StorageVec {
|
||||
}
|
||||
}
|
||||
|
||||
pub mod unhashed {
|
||||
use crate::rstd::borrow::Borrow;
|
||||
use super::{runtime_io, Codec, Decode, KeyedVec, Vec, IncrementalInput};
|
||||
|
||||
/// Return the value of the item in storage under `key`, or `None` if there is no explicit entry.
|
||||
pub fn get<T: Codec + Sized>(key: &[u8]) -> Option<T> {
|
||||
runtime_io::read_storage(key, &mut [0; 0][..], 0).map(|_| {
|
||||
let mut input = IncrementalInput {
|
||||
key,
|
||||
pos: 0,
|
||||
};
|
||||
Decode::decode(&mut input).expect("storage is not null, therefore must be a valid type")
|
||||
})
|
||||
}
|
||||
|
||||
/// Return the value of the item in storage under `key`, or the type's default if there is no
|
||||
/// explicit entry.
|
||||
pub fn get_or_default<T: Codec + Sized + Default>(key: &[u8]) -> T {
|
||||
get(key).unwrap_or_else(Default::default)
|
||||
}
|
||||
|
||||
/// Return the value of the item in storage under `key`, or `default_value` if there is no
|
||||
/// explicit entry.
|
||||
pub fn get_or<T: Codec + Sized>(key: &[u8], default_value: T) -> T {
|
||||
get(key).unwrap_or(default_value)
|
||||
}
|
||||
|
||||
/// Return the value of the item in storage under `key`, or `default_value()` if there is no
|
||||
/// explicit entry.
|
||||
pub fn get_or_else<T: Codec + Sized, F: FnOnce() -> T>(key: &[u8], default_value: F) -> T {
|
||||
get(key).unwrap_or_else(default_value)
|
||||
}
|
||||
|
||||
/// Put `value` in storage under `key`.
|
||||
pub fn put<T: Codec>(key: &[u8], value: &T) {
|
||||
value.using_encoded(|slice| runtime_io::set_storage(key, slice));
|
||||
}
|
||||
|
||||
/// Remove `key` from storage, returning its value if it had an explicit entry or `None` otherwise.
|
||||
pub fn take<T: Codec + Sized>(key: &[u8]) -> Option<T> {
|
||||
let r = get(key);
|
||||
if r.is_some() {
|
||||
kill(key);
|
||||
}
|
||||
r
|
||||
}
|
||||
|
||||
/// Remove `key` from storage, returning its value, or, if there was no explicit entry in storage,
|
||||
/// the default for its type.
|
||||
pub fn take_or_default<T: Codec + Sized + Default>(key: &[u8]) -> T {
|
||||
take(key).unwrap_or_else(Default::default)
|
||||
}
|
||||
|
||||
/// Return the value of the item in storage under `key`, or `default_value` if there is no
|
||||
/// explicit entry. Ensure there is no explicit entry on return.
|
||||
pub fn take_or<T: Codec + Sized>(key: &[u8], default_value: T) -> T {
|
||||
take(key).unwrap_or(default_value)
|
||||
}
|
||||
|
||||
/// Return the value of the item in storage under `key`, or `default_value()` if there is no
|
||||
/// explicit entry. Ensure there is no explicit entry on return.
|
||||
pub fn take_or_else<T: Codec + Sized, F: FnOnce() -> T>(key: &[u8], default_value: F) -> T {
|
||||
take(key).unwrap_or_else(default_value)
|
||||
}
|
||||
|
||||
/// Check to see if `key` has an explicit entry in storage.
|
||||
pub fn exists(key: &[u8]) -> bool {
|
||||
runtime_io::read_storage(key, &mut [0;0][..], 0).is_some()
|
||||
}
|
||||
|
||||
/// Ensure `key` has no explicit entry in storage.
|
||||
pub fn kill(key: &[u8]) {
|
||||
runtime_io::clear_storage(key);
|
||||
}
|
||||
|
||||
/// Ensure keys with the given `prefix` have no entries in storage.
|
||||
pub fn kill_prefix(prefix: &[u8]) {
|
||||
runtime_io::clear_prefix(prefix);
|
||||
}
|
||||
|
||||
/// Get a Vec of bytes from storage.
|
||||
pub fn get_raw(key: &[u8]) -> Option<Vec<u8>> {
|
||||
runtime_io::storage(key)
|
||||
}
|
||||
|
||||
/// Put a raw byte slice into storage.
|
||||
pub fn put_raw(key: &[u8], value: &[u8]) {
|
||||
runtime_io::set_storage(key, value)
|
||||
}
|
||||
|
||||
/// A trait to conveniently store a vector of storable data.
|
||||
pub trait StorageVec {
|
||||
type Item: Default + Sized + Codec;
|
||||
const PREFIX: &'static [u8];
|
||||
|
||||
/// Get the current set of items.
|
||||
fn items() -> Vec<Self::Item> {
|
||||
(0..Self::count()).into_iter().map(Self::item).collect()
|
||||
}
|
||||
|
||||
/// Set the current set of items.
|
||||
fn set_items<I, T>(items: I)
|
||||
where
|
||||
I: IntoIterator<Item=T>,
|
||||
T: Borrow<Self::Item>,
|
||||
{
|
||||
let mut count: u32 = 0;
|
||||
|
||||
for i in items.into_iter() {
|
||||
put(&count.to_keyed_vec(Self::PREFIX), i.borrow());
|
||||
count = count.checked_add(1).expect("exceeded runtime storage capacity");
|
||||
}
|
||||
|
||||
Self::set_count(count);
|
||||
}
|
||||
|
||||
fn set_item(index: u32, item: &Self::Item) {
|
||||
if index < Self::count() {
|
||||
put(&index.to_keyed_vec(Self::PREFIX), item);
|
||||
}
|
||||
}
|
||||
|
||||
fn clear_item(index: u32) {
|
||||
if index < Self::count() {
|
||||
kill(&index.to_keyed_vec(Self::PREFIX));
|
||||
}
|
||||
}
|
||||
|
||||
fn item(index: u32) -> Self::Item {
|
||||
get_or_default(&index.to_keyed_vec(Self::PREFIX))
|
||||
}
|
||||
|
||||
fn set_count(count: u32) {
|
||||
(count..Self::count()).for_each(Self::clear_item);
|
||||
put(&b"len".to_keyed_vec(Self::PREFIX), &count);
|
||||
}
|
||||
|
||||
fn count() -> u32 {
|
||||
get_or_default(&b"len".to_keyed_vec(Self::PREFIX))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// child storage NOTE could replace unhashed by having only one kind of storage (root being null storage
|
||||
/// key (storage_key can become Option<&[u8]>).
|
||||
/// This module is a currently only a variant of unhashed with additional `storage_key`.
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
// Copyright 2019 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::codec;
|
||||
use runtime_io::twox_128;
|
||||
use crate::rstd::vec::Vec;
|
||||
|
||||
/// Abstraction around storage with unhashed access.
|
||||
pub trait UnhashedStorage {
|
||||
/// true if the key exists in storage.
|
||||
fn exists(&self, key: &[u8]) -> bool;
|
||||
|
||||
/// Load the bytes of a key from storage. Can panic if the type is incorrect.
|
||||
fn get<T: codec::Decode>(&self, key: &[u8]) -> Option<T>;
|
||||
|
||||
/// Load the bytes of a key from storage. Can panic if the type is incorrect. Will panic if
|
||||
/// it's not there.
|
||||
fn require<T: codec::Decode>(&self, key: &[u8]) -> T { self.get(key).expect("Required values must be in storage") }
|
||||
|
||||
/// Load the bytes of a key from storage. Can panic if the type is incorrect. The type's
|
||||
/// default is returned if it's not there.
|
||||
fn get_or_default<T: codec::Decode + Default>(&self, key: &[u8]) -> T { self.get(key).unwrap_or_default() }
|
||||
|
||||
/// Put a value in under a key.
|
||||
fn put<T: codec::Encode>(&self, key: &[u8], val: &T);
|
||||
|
||||
/// Remove the bytes of a key from storage.
|
||||
fn kill(&self, key: &[u8]);
|
||||
|
||||
/// Remove the bytes of a key from storage.
|
||||
fn kill_prefix(&self, prefix: &[u8]);
|
||||
|
||||
/// Take a value from storage, deleting it after reading.
|
||||
fn take<T: codec::Decode>(&self, key: &[u8]) -> Option<T> {
|
||||
let value = self.get(key);
|
||||
self.kill(key);
|
||||
value
|
||||
}
|
||||
|
||||
/// Take a value from storage, deleting it after reading.
|
||||
fn take_or_panic<T: codec::Decode>(&self, key: &[u8]) -> T { self.take(key).expect("Required values must be in storage") }
|
||||
|
||||
/// Take a value from storage, deleting it after reading.
|
||||
fn take_or_default<T: codec::Decode + Default>(&self, key: &[u8]) -> T { self.take(key).unwrap_or_default() }
|
||||
}
|
||||
|
||||
// We use a construct like this during when genesis storage is being built.
|
||||
#[cfg(feature = "std")]
|
||||
impl<H> UnhashedStorage for (crate::rstd::cell::RefCell<&mut sr_primitives::StorageOverlay>, H) {
|
||||
fn exists(&self, key: &[u8]) -> bool {
|
||||
self.0.borrow().contains_key(key)
|
||||
}
|
||||
|
||||
fn get<T: codec::Decode>(&self, key: &[u8]) -> Option<T> {
|
||||
self.0.borrow().get(key)
|
||||
.map(|x| codec::Decode::decode(&mut x.as_slice()).expect("Unable to decode expected type."))
|
||||
}
|
||||
|
||||
fn put<T: codec::Encode>(&self, key: &[u8], val: &T) {
|
||||
self.0.borrow_mut().insert(key.to_vec(), codec::Encode::encode(val));
|
||||
}
|
||||
|
||||
fn kill(&self, key: &[u8]) {
|
||||
self.0.borrow_mut().remove(key);
|
||||
}
|
||||
|
||||
fn kill_prefix(&self, prefix: &[u8]) {
|
||||
self.0.borrow_mut().retain(|key, _| {
|
||||
!key.starts_with(prefix)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// An implementation of a map with a two keys.
|
||||
///
|
||||
/// It provides an important ability to efficiently remove all entries
|
||||
/// that have a common first key.
|
||||
///
|
||||
/// # Mapping of keys to a storage path
|
||||
///
|
||||
/// The storage key (i.e. the key under which the `Value` will be stored) is created from two parts.
|
||||
/// The first part is a hash of a concatenation of the `PREFIX` and `Key1`. And the second part
|
||||
/// is a hash of a `Key2`.
|
||||
///
|
||||
/// /!\ be careful while choosing the Hash, indeed malicious could craft second keys to lower the trie.
|
||||
pub trait StorageDoubleMap<K1: codec::Codec, K2: codec::Codec, V: codec::Codec> {
|
||||
/// The type that get/take returns.
|
||||
type Query;
|
||||
|
||||
/// Get the prefix key in storage.
|
||||
fn prefix() -> &'static [u8];
|
||||
|
||||
/// Get the storage key used to fetch a value corresponding to a specific key.
|
||||
fn key_for(k1: &K1, k2: &K2) -> Vec<u8>;
|
||||
|
||||
/// Get the storage prefix used to fetch keys corresponding to a specific key1.
|
||||
fn prefix_for(k1: &K1) -> Vec<u8> {
|
||||
let mut key = Self::prefix().to_vec();
|
||||
codec::Encode::encode_to(k1, &mut key);
|
||||
twox_128(&key).to_vec()
|
||||
}
|
||||
|
||||
/// true if the value is defined in storage.
|
||||
fn exists<S: UnhashedStorage>(k1: &K1, k2: &K2, storage: &S) -> bool {
|
||||
storage.exists(&Self::key_for(k1, k2))
|
||||
}
|
||||
|
||||
/// Load the value associated with the given key from the map.
|
||||
fn get<S: UnhashedStorage>(k1: &K1, k2: &K2, storage: &S) -> Self::Query;
|
||||
|
||||
/// Take the value under a key.
|
||||
fn take<S: UnhashedStorage>(k1: &K1, k2: &K2, storage: &S) -> Self::Query;
|
||||
|
||||
/// Store a value to be associated with the given key from the map.
|
||||
fn insert<S: UnhashedStorage>(k1: &K1, k2: &K2, val: &V, storage: &S) {
|
||||
storage.put(&Self::key_for(k1, k2), val);
|
||||
}
|
||||
|
||||
/// Remove the value under a key.
|
||||
fn remove<S: UnhashedStorage>(k1: &K1, k2: &K2, storage: &S) {
|
||||
storage.kill(&Self::key_for(k1, k2));
|
||||
}
|
||||
|
||||
/// Removes all entries that shares the `k1` as the first key.
|
||||
fn remove_prefix<S: UnhashedStorage>(k1: &K1, storage: &S) {
|
||||
storage.kill_prefix(&Self::prefix_for(k1));
|
||||
}
|
||||
|
||||
/// Mutate the value under a key.
|
||||
fn mutate<R, F: FnOnce(&mut Self::Query) -> R, S: UnhashedStorage>(k1: &K1, k2: &K2, f: F, storage: &S) -> R;
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
// Copyright 2019 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Operation on unhashed runtime storage
|
||||
|
||||
use crate::rstd::borrow::Borrow;
|
||||
use super::{runtime_io, Codec, Encode, Decode, KeyedVec, Vec, IncrementalInput};
|
||||
|
||||
pub mod generator;
|
||||
|
||||
/// Return the value of the item in storage under `key`, or `None` if there is no explicit entry.
|
||||
pub fn get<T: Decode + Sized>(key: &[u8]) -> Option<T> {
|
||||
runtime_io::read_storage(key, &mut [0; 0][..], 0).map(|_| {
|
||||
let mut input = IncrementalInput {
|
||||
key,
|
||||
pos: 0,
|
||||
};
|
||||
Decode::decode(&mut input).expect("storage is not null, therefore must be a valid type")
|
||||
})
|
||||
}
|
||||
|
||||
/// Return the value of the item in storage under `key`, or the type's default if there is no
|
||||
/// explicit entry.
|
||||
pub fn get_or_default<T: Decode + Sized + Default>(key: &[u8]) -> T {
|
||||
get(key).unwrap_or_else(Default::default)
|
||||
}
|
||||
|
||||
/// Return the value of the item in storage under `key`, or `default_value` if there is no
|
||||
/// explicit entry.
|
||||
pub fn get_or<T: Decode + Sized>(key: &[u8], default_value: T) -> T {
|
||||
get(key).unwrap_or(default_value)
|
||||
}
|
||||
|
||||
/// Return the value of the item in storage under `key`, or `default_value()` if there is no
|
||||
/// explicit entry.
|
||||
pub fn get_or_else<T: Decode + Sized, F: FnOnce() -> T>(key: &[u8], default_value: F) -> T {
|
||||
get(key).unwrap_or_else(default_value)
|
||||
}
|
||||
|
||||
/// Put `value` in storage under `key`.
|
||||
pub fn put<T: Encode>(key: &[u8], value: &T) {
|
||||
value.using_encoded(|slice| runtime_io::set_storage(key, slice));
|
||||
}
|
||||
|
||||
/// Remove `key` from storage, returning its value if it had an explicit entry or `None` otherwise.
|
||||
pub fn take<T: Decode + Sized>(key: &[u8]) -> Option<T> {
|
||||
let r = get(key);
|
||||
if r.is_some() {
|
||||
kill(key);
|
||||
}
|
||||
r
|
||||
}
|
||||
|
||||
/// Remove `key` from storage, returning its value, or, if there was no explicit entry in storage,
|
||||
/// the default for its type.
|
||||
pub fn take_or_default<T: Decode + Sized + Default>(key: &[u8]) -> T {
|
||||
take(key).unwrap_or_else(Default::default)
|
||||
}
|
||||
|
||||
/// Return the value of the item in storage under `key`, or `default_value` if there is no
|
||||
/// explicit entry. Ensure there is no explicit entry on return.
|
||||
pub fn take_or<T: Decode + Sized>(key: &[u8], default_value: T) -> T {
|
||||
take(key).unwrap_or(default_value)
|
||||
}
|
||||
|
||||
/// Return the value of the item in storage under `key`, or `default_value()` if there is no
|
||||
/// explicit entry. Ensure there is no explicit entry on return.
|
||||
pub fn take_or_else<T: Decode + Sized, F: FnOnce() -> T>(key: &[u8], default_value: F) -> T {
|
||||
take(key).unwrap_or_else(default_value)
|
||||
}
|
||||
|
||||
/// Check to see if `key` has an explicit entry in storage.
|
||||
pub fn exists(key: &[u8]) -> bool {
|
||||
runtime_io::read_storage(key, &mut [0;0][..], 0).is_some()
|
||||
}
|
||||
|
||||
/// Ensure `key` has no explicit entry in storage.
|
||||
pub fn kill(key: &[u8]) {
|
||||
runtime_io::clear_storage(key);
|
||||
}
|
||||
|
||||
/// Ensure keys with the given `prefix` have no entries in storage.
|
||||
pub fn kill_prefix(prefix: &[u8]) {
|
||||
runtime_io::clear_prefix(prefix);
|
||||
}
|
||||
|
||||
/// Get a Vec of bytes from storage.
|
||||
pub fn get_raw(key: &[u8]) -> Option<Vec<u8>> {
|
||||
runtime_io::storage(key)
|
||||
}
|
||||
|
||||
/// Put a raw byte slice into storage.
|
||||
pub fn put_raw(key: &[u8], value: &[u8]) {
|
||||
runtime_io::set_storage(key, value)
|
||||
}
|
||||
|
||||
/// A trait to conveniently store a vector of storable data.
|
||||
pub trait StorageVec {
|
||||
type Item: Default + Sized + Codec;
|
||||
const PREFIX: &'static [u8];
|
||||
|
||||
/// Get the current set of items.
|
||||
fn items() -> Vec<Self::Item> {
|
||||
(0..Self::count()).into_iter().map(Self::item).collect()
|
||||
}
|
||||
|
||||
/// Set the current set of items.
|
||||
fn set_items<I, T>(items: I)
|
||||
where
|
||||
I: IntoIterator<Item=T>,
|
||||
T: Borrow<Self::Item>,
|
||||
{
|
||||
let mut count: u32 = 0;
|
||||
|
||||
for i in items.into_iter() {
|
||||
put(&count.to_keyed_vec(Self::PREFIX), i.borrow());
|
||||
count = count.checked_add(1).expect("exceeded runtime storage capacity");
|
||||
}
|
||||
|
||||
Self::set_count(count);
|
||||
}
|
||||
|
||||
fn set_item(index: u32, item: &Self::Item) {
|
||||
if index < Self::count() {
|
||||
put(&index.to_keyed_vec(Self::PREFIX), item);
|
||||
}
|
||||
}
|
||||
|
||||
fn clear_item(index: u32) {
|
||||
if index < Self::count() {
|
||||
kill(&index.to_keyed_vec(Self::PREFIX));
|
||||
}
|
||||
}
|
||||
|
||||
fn item(index: u32) -> Self::Item {
|
||||
get_or_default(&index.to_keyed_vec(Self::PREFIX))
|
||||
}
|
||||
|
||||
fn set_count(count: u32) {
|
||||
(count..Self::count()).for_each(Self::clear_item);
|
||||
put(&b"len".to_keyed_vec(Self::PREFIX), &count);
|
||||
}
|
||||
|
||||
fn count() -> u32 {
|
||||
get_or_default(&b"len".to_keyed_vec(Self::PREFIX))
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ use srml_support::Parameter;
|
||||
use inherents::{
|
||||
ProvideInherent, InherentData, InherentIdentifier, RuntimeString, MakeFatalError
|
||||
};
|
||||
use srml_support::{StorageValue, StorageMap};
|
||||
use srml_support::{StorageValue, StorageMap, StorageDoubleMap};
|
||||
use primitives::{H256, sr25519};
|
||||
|
||||
pub trait Currency {
|
||||
@@ -218,6 +218,7 @@ mod module2 {
|
||||
pub Value config(value): T::Amount;
|
||||
pub Map config(map): map u64 => u64;
|
||||
pub LinkedMap config(linked_map): linked_map u64 => u64;
|
||||
pub DoubleMap config(double_map): double_map u64, blake2_256(u64) => u64;
|
||||
}
|
||||
extra_genesis_skip_phantom_data_field;
|
||||
}
|
||||
@@ -368,10 +369,16 @@ fn new_test_ext() -> runtime_io::TestExternalities<Blake2Hasher> {
|
||||
}),
|
||||
module2: Some(module2::GenesisConfig {
|
||||
value: 4,
|
||||
map: vec![],
|
||||
linked_map: vec![],
|
||||
map: vec![(0, 0)],
|
||||
linked_map: vec![(0, 0)],
|
||||
double_map: vec![(0, 0, 0)],
|
||||
}),
|
||||
module2_Instance1: Some(module2::GenesisConfig {
|
||||
value: 4,
|
||||
map: vec![(0, 0)],
|
||||
linked_map: vec![(0, 0)],
|
||||
double_map: vec![(0, 0, 0)],
|
||||
}),
|
||||
module2_Instance1: None,
|
||||
module2_Instance2: None,
|
||||
module2_Instance3: None,
|
||||
}.build_storage().unwrap().0.into()
|
||||
@@ -381,7 +388,7 @@ fn new_test_ext() -> runtime_io::TestExternalities<Blake2Hasher> {
|
||||
fn storage_instance_independance() {
|
||||
with_externalities(&mut new_test_ext(), || {
|
||||
let mut map = rstd::collections::btree_map::BTreeMap::new();
|
||||
for key in &[
|
||||
for key in [
|
||||
module2::Value::<Runtime>::key().to_vec(),
|
||||
module2::Value::<Runtime, module2::Instance1>::key().to_vec(),
|
||||
module2::Value::<Runtime, module2::Instance2>::key().to_vec(),
|
||||
@@ -394,6 +401,10 @@ fn storage_instance_independance() {
|
||||
module2::LinkedMap::<Runtime, module2::Instance1>::prefix().to_vec(),
|
||||
module2::LinkedMap::<Runtime, module2::Instance2>::prefix().to_vec(),
|
||||
module2::LinkedMap::<Runtime, module2::Instance3>::prefix().to_vec(),
|
||||
module2::DoubleMap::<Runtime>::prefix().to_vec(),
|
||||
module2::DoubleMap::<Runtime, module2::Instance1>::prefix().to_vec(),
|
||||
module2::DoubleMap::<Runtime, module2::Instance2>::prefix().to_vec(),
|
||||
module2::DoubleMap::<Runtime, module2::Instance3>::prefix().to_vec(),
|
||||
module2::Map::<Runtime>::key_for(0),
|
||||
module2::Map::<Runtime, module2::Instance1>::key_for(0).to_vec(),
|
||||
module2::Map::<Runtime, module2::Instance2>::key_for(0).to_vec(),
|
||||
@@ -410,20 +421,32 @@ fn storage_instance_independance() {
|
||||
module2::LinkedMap::<Runtime, module2::Instance1>::key_for(1).to_vec(),
|
||||
module2::LinkedMap::<Runtime, module2::Instance2>::key_for(1).to_vec(),
|
||||
module2::LinkedMap::<Runtime, module2::Instance3>::key_for(1).to_vec(),
|
||||
] {
|
||||
module2::DoubleMap::<Runtime>::prefix_for(1),
|
||||
module2::DoubleMap::<Runtime, module2::Instance1>::prefix_for(1).to_vec(),
|
||||
module2::DoubleMap::<Runtime, module2::Instance2>::prefix_for(1).to_vec(),
|
||||
module2::DoubleMap::<Runtime, module2::Instance3>::prefix_for(1).to_vec(),
|
||||
module2::DoubleMap::<Runtime>::key_for(1, 1),
|
||||
module2::DoubleMap::<Runtime, module2::Instance1>::key_for(1, 1).to_vec(),
|
||||
module2::DoubleMap::<Runtime, module2::Instance2>::key_for(1, 1).to_vec(),
|
||||
module2::DoubleMap::<Runtime, module2::Instance3>::key_for(1, 1).to_vec(),
|
||||
].iter() {
|
||||
assert!(map.insert(key, ()).is_none())
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// TODO TODO: check configuration doublemapstorage in instances
|
||||
|
||||
#[test]
|
||||
fn storage_with_instance_basic_operation() {
|
||||
with_externalities(&mut new_test_ext(), || {
|
||||
type Value = module2::Value<Runtime, module2::Instance1>;
|
||||
type Map = module2::Map<Runtime, module2::Instance1>;
|
||||
type LinkedMap = module2::LinkedMap<Runtime, module2::Instance1>;
|
||||
type DoubleMap = module2::DoubleMap<Runtime, module2::Instance1>;
|
||||
|
||||
assert_eq!(Value::exists(), false);
|
||||
assert_eq!(Value::exists(), true);
|
||||
assert_eq!(Value::get(), 4);
|
||||
Value::put(1);
|
||||
assert_eq!(Value::get(), 1);
|
||||
assert_eq!(Value::take(), 1);
|
||||
@@ -431,10 +454,12 @@ fn storage_with_instance_basic_operation() {
|
||||
Value::mutate(|a| *a=2);
|
||||
assert_eq!(Value::get(), 2);
|
||||
Value::kill();
|
||||
assert_eq!(Value::exists(), false);
|
||||
assert_eq!(Value::get(), 0);
|
||||
|
||||
let key = 1;
|
||||
assert_eq!(Map::exists(1), false);
|
||||
assert_eq!(Map::exists(0), true);
|
||||
assert_eq!(Map::exists(key), false);
|
||||
Map::insert(key, 1);
|
||||
assert_eq!(Map::get(key), 1);
|
||||
assert_eq!(Map::take(key), 1);
|
||||
@@ -442,9 +467,11 @@ fn storage_with_instance_basic_operation() {
|
||||
Map::mutate(key, |a| *a=2);
|
||||
assert_eq!(Map::get(key), 2);
|
||||
Map::remove(key);
|
||||
assert_eq!(Map::exists(key), false);
|
||||
assert_eq!(Map::get(key), 0);
|
||||
|
||||
assert_eq!(LinkedMap::exists(1), false);
|
||||
assert_eq!(LinkedMap::exists(0), true);
|
||||
assert_eq!(LinkedMap::exists(key), false);
|
||||
LinkedMap::insert(key, 1);
|
||||
assert_eq!(LinkedMap::get(key), 1);
|
||||
assert_eq!(LinkedMap::take(key), 1);
|
||||
@@ -452,6 +479,20 @@ fn storage_with_instance_basic_operation() {
|
||||
LinkedMap::mutate(key, |a| *a=2);
|
||||
assert_eq!(LinkedMap::get(key), 2);
|
||||
LinkedMap::remove(key);
|
||||
assert_eq!(LinkedMap::exists(key), false);
|
||||
assert_eq!(LinkedMap::get(key), 0);
|
||||
|
||||
let key1 = 1;
|
||||
let key2 = 1;
|
||||
assert_eq!(DoubleMap::exists(0, 0), true);
|
||||
assert_eq!(DoubleMap::exists(key1, key2), false);
|
||||
DoubleMap::insert(key1, key2, 1);
|
||||
assert_eq!(DoubleMap::get(key1, key2), 1);
|
||||
assert_eq!(DoubleMap::take(key1, key2), 1);
|
||||
assert_eq!(DoubleMap::get(key1, key2), 0);
|
||||
DoubleMap::mutate(key1, key2, |a| *a=2);
|
||||
assert_eq!(DoubleMap::get(key1, key2), 2);
|
||||
DoubleMap::remove(key1, key2);
|
||||
assert_eq!(DoubleMap::get(key1, key2), 0);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user