diff --git a/srml/metadata/src/lib.rs b/srml/metadata/src/lib.rs index d85a6837fc6e0..12f19a951baec 100644 --- a/srml/metadata/src/lib.rs +++ b/srml/metadata/src/lib.rs @@ -278,6 +278,15 @@ pub enum StorageHasher { Twox64Concat, } +/// The kind of map for a map storage entry type. +#[derive(Clone, PartialEq, Eq, Encode, RuntimeDebug)] +#[cfg_attr(feature = "std", derive(Decode, Serialize))] +pub enum StorageMapKind { + Map, + LinkedMap, + PrefixedMap, +} + /// A storage entry type. #[derive(Clone, PartialEq, Eq, Encode, RuntimeDebug)] #[cfg_attr(feature = "std", derive(Decode, Serialize))] @@ -287,7 +296,7 @@ pub enum StorageEntryType { hasher: StorageHasher, key: DecodeDifferentStr, value: DecodeDifferentStr, - is_linked: bool, + kind: StorageMapKind, }, DoubleMap { hasher: StorageHasher, @@ -342,8 +351,10 @@ pub enum RuntimeMetadata { V6(RuntimeMetadataDeprecated), /// Version 7 for runtime metadata. No longer used. V7(RuntimeMetadataDeprecated), - /// Version 8 for runtime metadata. - V8(RuntimeMetadataV8), + /// Version 8 for runtime metadata. No longer used. + V8(RuntimeMetadataDeprecated), + /// Version 9 for runtime metadata. + V9(RuntimeMetadataV9), } /// Enum that should fail. @@ -367,12 +378,12 @@ impl Decode for RuntimeMetadataDeprecated { /// The metadata of a runtime. #[derive(Eq, Encode, PartialEq, RuntimeDebug)] #[cfg_attr(feature = "std", derive(Decode, Serialize))] -pub struct RuntimeMetadataV8 { +pub struct RuntimeMetadataV9 { pub modules: DecodeDifferentArray, } /// The latest version of the metadata. -pub type RuntimeMetadataLastVersion = RuntimeMetadataV8; +pub type RuntimeMetadataLastVersion = RuntimeMetadataV9; /// All metadata about an runtime module. #[derive(Clone, PartialEq, Eq, Encode, RuntimeDebug)] @@ -397,6 +408,6 @@ impl Into for RuntimeMetadataPrefixed { impl Into for RuntimeMetadataLastVersion { fn into(self) -> RuntimeMetadataPrefixed { - RuntimeMetadataPrefixed(META_RESERVED, RuntimeMetadata::V8(self)) + RuntimeMetadataPrefixed(META_RESERVED, RuntimeMetadata::V9(self)) } } diff --git a/srml/support/procedural/src/storage/genesis_config/builder_def.rs b/srml/support/procedural/src/storage/genesis_config/builder_def.rs index fc425e4a6d532..7feb61b7e6be6 100644 --- a/srml/support/procedural/src/storage/genesis_config/builder_def.rs +++ b/srml/support/procedural/src/storage/genesis_config/builder_def.rs @@ -66,7 +66,10 @@ impl BuilderDef { <#storage_struct as #scrate::#storage_trait>::put::<&#value_type>(v); }} }, - StorageLineTypeDef::Map(map) | StorageLineTypeDef::LinkedMap(map) => { + StorageLineTypeDef::Map(map) + | StorageLineTypeDef::LinkedMap(map) + | StorageLineTypeDef::PrefixedMap(map) + => { let key = &map.key; quote!{{ let data: &#scrate::rstd::vec::Vec<(#key, #value_type)> = #data; diff --git a/srml/support/procedural/src/storage/genesis_config/genesis_config_def.rs b/srml/support/procedural/src/storage/genesis_config/genesis_config_def.rs index f9d2f8abe8055..66ba2f9ee5c74 100644 --- a/srml/support/procedural/src/storage/genesis_config/genesis_config_def.rs +++ b/srml/support/procedural/src/storage/genesis_config/genesis_config_def.rs @@ -91,7 +91,10 @@ impl GenesisConfigDef { let typ = match &line.storage_type { StorageLineTypeDef::Simple(_) => (*value_type).clone(), - StorageLineTypeDef::Map(map) | StorageLineTypeDef::LinkedMap(map) => { + StorageLineTypeDef::Map(map) + | StorageLineTypeDef::LinkedMap(map) + | StorageLineTypeDef::PrefixedMap(map) + => { let key = &map.key; parse_quote!( Vec<(#key, #value_type)> ) }, diff --git a/srml/support/procedural/src/storage/getters.rs b/srml/support/procedural/src/storage/getters.rs index f30e489eb58bc..fd6b2d7624587 100644 --- a/srml/support/procedural/src/storage/getters.rs +++ b/srml/support/procedural/src/storage/getters.rs @@ -40,7 +40,10 @@ pub fn impl_getters(scrate: &TokenStream, def: &DeclStorageDefExt) -> TokenStrea } } }, - StorageLineTypeDef::Map(map) | StorageLineTypeDef::LinkedMap(map) => { + StorageLineTypeDef::Map(map) + | StorageLineTypeDef::LinkedMap(map) + | StorageLineTypeDef::PrefixedMap(map) + => { let key = &map.key; let value = &map.value; quote!{ diff --git a/srml/support/procedural/src/storage/metadata.rs b/srml/support/procedural/src/storage/metadata.rs index e280c7d8a20c6..5c84b469fac47 100644 --- a/srml/support/procedural/src/storage/metadata.rs +++ b/srml/support/procedural/src/storage/metadata.rs @@ -41,7 +41,7 @@ fn storage_line_metadata_type(scrate: &TokenStream, line: &StorageLineDefExt) -> hasher: #scrate::metadata::#hasher, key: #scrate::metadata::DecodeDifferent::Encode(#key), value: #scrate::metadata::DecodeDifferent::Encode(#value_type), - is_linked: false, + kind: #scrate::metadata::StorageMapKind::Map, } } }, @@ -54,7 +54,20 @@ fn storage_line_metadata_type(scrate: &TokenStream, line: &StorageLineDefExt) -> hasher: #scrate::metadata::#hasher, key: #scrate::metadata::DecodeDifferent::Encode(#key), value: #scrate::metadata::DecodeDifferent::Encode(#value_type), - is_linked: true, + kind: #scrate::metadata::StorageMapKind::LinkedMap, + } + } + }, + StorageLineTypeDef::PrefixedMap(map) => { + let hasher = map.hasher.into_metadata(); + let key = &map.key; + let key = clean_type_string("e!(#key).to_string()); + quote!{ + #scrate::metadata::StorageEntryType::Map { + hasher: #scrate::metadata::#hasher, + key: #scrate::metadata::DecodeDifferent::Encode(#key), + value: #scrate::metadata::DecodeDifferent::Encode(#value_type), + kind: #scrate::metadata::StorageMapKind::PrefixedMap, } } }, diff --git a/srml/support/procedural/src/storage/mod.rs b/srml/support/procedural/src/storage/mod.rs index 9a6931d87e9c8..ecf57bfcbc567 100644 --- a/srml/support/procedural/src/storage/mod.rs +++ b/srml/support/procedural/src/storage/mod.rs @@ -239,6 +239,10 @@ impl StorageLineDefExt { ext::type_contains_ident(&map.key, &def.module_runtime_generic) || ext::type_contains_ident(&map.value, &def.module_runtime_generic) } + StorageLineTypeDef::PrefixedMap(map) => { + ext::type_contains_ident(&map.key, &def.module_runtime_generic) + || ext::type_contains_ident(&map.value, &def.module_runtime_generic) + } StorageLineTypeDef::DoubleMap(map) => { ext::type_contains_ident(&map.key1, &def.module_runtime_generic) || ext::type_contains_ident(&map.key2, &def.module_runtime_generic) @@ -250,6 +254,7 @@ impl StorageLineDefExt { StorageLineTypeDef::Simple(value) => value.clone(), StorageLineTypeDef::Map(map) => map.value.clone(), StorageLineTypeDef::LinkedMap(map) => map.value.clone(), + StorageLineTypeDef::PrefixedMap(map) => map.value.clone(), StorageLineTypeDef::DoubleMap(map) => map.value.clone(), }; let is_option = ext::extract_type_option(&query_type).is_some(); @@ -295,6 +300,10 @@ impl StorageLineDefExt { let key = &map.key; quote!( StorageLinkedMap<#key, #value_type> ) }, + StorageLineTypeDef::PrefixedMap(map) => { + let key = &map.key; + quote!( StoragePrefixedMap<#key, #value_type> ) + }, StorageLineTypeDef::DoubleMap(map) => { let key1 = &map.key1; let key2 = &map.key2; @@ -337,6 +346,7 @@ impl StorageLineDefExt { pub enum StorageLineTypeDef { Map(MapDef), LinkedMap(MapDef), + PrefixedMap(MapDef), DoubleMap(DoubleMapDef), Simple(syn::Type), } @@ -418,6 +428,7 @@ pub fn decl_storage_impl(input: proc_macro::TokenStream) -> proc_macro::TokenStr StorageValue as _, StorageMap as _, StorageLinkedMap as _, + StoragePrefixedMap as _, StorageDoubleMap as _ }; diff --git a/srml/support/procedural/src/storage/parse.rs b/srml/support/procedural/src/storage/parse.rs index e428fbe24f295..94278cd1654b8 100644 --- a/srml/support/procedural/src/storage/parse.rs +++ b/srml/support/procedural/src/storage/parse.rs @@ -28,6 +28,7 @@ mod keyword { syn::custom_keyword!(get); syn::custom_keyword!(map); syn::custom_keyword!(linked_map); + syn::custom_keyword!(prefixed_map); syn::custom_keyword!(double_map); syn::custom_keyword!(blake2_256); syn::custom_keyword!(blake2_128); @@ -141,6 +142,7 @@ struct DeclStorageBuild { enum DeclStorageType { Map(DeclStorageMap), LinkedMap(DeclStorageLinkedMap), + PrefixedMap(DeclStoragePrefixedMap), DoubleMap(DeclStorageDoubleMap), Simple(syn::Type), } @@ -163,6 +165,15 @@ struct DeclStorageLinkedMap { pub value: syn::Type, } +#[derive(Parse, ToTokens, Debug)] +struct DeclStoragePrefixedMap { + pub map_keyword: keyword::prefixed_map, + pub hasher: ext::Opt, + pub key: syn::Type, + pub ass_keyword: Token![=>], + pub value: syn::Type, +} + #[derive(Parse, ToTokens, Debug)] struct DeclStorageDoubleMap { pub map_keyword: keyword::double_map, @@ -375,6 +386,14 @@ fn parse_storage_line_defs( value: map.value, } ), + DeclStorageType::PrefixedMap(map) => super::StorageLineTypeDef::PrefixedMap( + super::MapDef { + hasher: map.hasher.inner.map(Into::into) + .unwrap_or(super::HasherKind::Blake2_256), + key: map.key, + value: map.value, + } + ), DeclStorageType::DoubleMap(map) => super::StorageLineTypeDef::DoubleMap( super::DoubleMapDef { hasher1: map.hasher.inner.map(Into::into) diff --git a/srml/support/procedural/src/storage/storage_struct.rs b/srml/support/procedural/src/storage/storage_struct.rs index e195fb53e8a2c..d031c4e5e2e1d 100644 --- a/srml/support/procedural/src/storage/storage_struct.rs +++ b/srml/support/procedural/src/storage/storage_struct.rs @@ -181,6 +181,29 @@ pub fn decl_and_impl(scrate: &TokenStream, def: &DeclStorageDefExt) -> TokenStre } ) }, + StorageLineTypeDef::PrefixedMap(map) => { + let hasher = map.hasher.to_storage_hasher_struct(); + quote!( + impl<#impl_trait> #scrate::#storage_generator_trait for #storage_struct + #optional_storage_where_clause + { + type Query = #query_type; + type Hasher = #scrate::#hasher; + + fn prefix() -> &'static [u8] { + #final_prefix + } + + fn from_optional_value_to_query(v: Option<#value_type>) -> Self::Query { + #from_optional_value_to_query + } + + fn from_query_to_optional_value(v: Self::Query) -> Option<#value_type> { + #from_query_to_optional_value + } + } + ) + }, StorageLineTypeDef::DoubleMap(map) => { let hasher1 = map.hasher1.to_storage_hasher_struct(); let hasher2 = map.hasher2.to_storage_hasher_struct(); diff --git a/srml/support/src/lib.rs b/srml/support/src/lib.rs index cfe6487203ddd..6942e47e30644 100644 --- a/srml/support/src/lib.rs +++ b/srml/support/src/lib.rs @@ -66,7 +66,7 @@ pub mod error; pub mod traits; pub use self::hash::{Twox256, Twox128, Blake2_256, Blake2_128, Twox64Concat, Hashable}; -pub use self::storage::{StorageValue, StorageMap, StorageLinkedMap, StorageDoubleMap}; +pub use self::storage::{StorageValue, StorageMap, StorageLinkedMap, StoragePrefixedMap, StorageDoubleMap}; pub use self::dispatch::{Parameter, Callable, IsSubType}; pub use sr_primitives::{self, ConsensusEngineId, print, traits::Printable}; @@ -240,7 +240,7 @@ mod tests { use codec::{Codec, EncodeLike}; use srml_metadata::{ DecodeDifferent, StorageEntryMetadata, StorageMetadata, StorageEntryType, - StorageEntryModifier, DefaultByteGetter, StorageHasher, + StorageEntryModifier, DefaultByteGetter, StorageHasher, StorageMapKind, }; use rstd::marker::PhantomData; @@ -470,7 +470,7 @@ mod tests { hasher: StorageHasher::Twox64Concat, key: DecodeDifferent::Encode("u32"), value: DecodeDifferent::Encode("u64"), - is_linked: true, + kind: StorageMapKind::LinkedMap, }, default: DecodeDifferent::Encode( DefaultByteGetter(&__GetByteStructData(PhantomData::)) @@ -484,7 +484,7 @@ mod tests { hasher: StorageHasher::Blake2_256, key: DecodeDifferent::Encode("u32"), value: DecodeDifferent::Encode("u32"), - is_linked: true, + kind: StorageMapKind::LinkedMap, }, default: DecodeDifferent::Encode( DefaultByteGetter(&__GetByteStructOptionLinkedMap(PhantomData::)) @@ -498,7 +498,7 @@ mod tests { hasher: StorageHasher::Twox128, key: DecodeDifferent::Encode("T::BlockNumber"), value: DecodeDifferent::Encode("T::BlockNumber"), - is_linked: true + kind: StorageMapKind::LinkedMap }, default: DecodeDifferent::Encode( DefaultByteGetter(&__GetByteStructGenericData(PhantomData::)) @@ -512,7 +512,7 @@ mod tests { hasher: StorageHasher::Blake2_256, key: DecodeDifferent::Encode("T::BlockNumber"), value: DecodeDifferent::Encode("T::BlockNumber"), - is_linked: true + kind: StorageMapKind::LinkedMap }, default: DecodeDifferent::Encode( DefaultByteGetter(&__GetByteStructGenericData2(PhantomData::)) diff --git a/srml/support/src/metadata.rs b/srml/support/src/metadata.rs index a223a14f9e405..d080263be6fed 100644 --- a/srml/support/src/metadata.rs +++ b/srml/support/src/metadata.rs @@ -17,7 +17,8 @@ pub use srml_metadata::{ DecodeDifferent, FnEncode, RuntimeMetadata, ModuleMetadata, RuntimeMetadataLastVersion, DefaultByteGetter, RuntimeMetadataPrefixed, StorageEntryMetadata, StorageMetadata, - StorageEntryType, StorageEntryModifier, DefaultByte, StorageHasher, ModuleErrorMetadata + StorageEntryType, StorageEntryModifier, DefaultByte, StorageHasher, ModuleErrorMetadata, + StorageMapKind }; /// Implements the metadata support for the given runtime and all its modules. diff --git a/srml/support/src/storage/generator/mod.rs b/srml/support/src/storage/generator/mod.rs index 1bda791023792..25cdd2c8e7d3b 100644 --- a/srml/support/src/storage/generator/mod.rs +++ b/srml/support/src/storage/generator/mod.rs @@ -23,15 +23,16 @@ mod linked_map; mod map; +mod prefixed_map; mod double_map; mod value; pub use linked_map::{StorageLinkedMap, Enumerator, Linkage}; pub use map::StorageMap; +pub use prefixed_map::StoragePrefixedMap; pub use double_map::StorageDoubleMap; pub use value::StorageValue; - #[cfg(test)] #[allow(dead_code)] mod tests { diff --git a/srml/support/src/storage/generator/prefixed_map.rs b/srml/support/src/storage/generator/prefixed_map.rs new file mode 100644 index 0000000000000..3dbf266aa8928 --- /dev/null +++ b/srml/support/src/storage/generator/prefixed_map.rs @@ -0,0 +1,370 @@ +// 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 . + +#[cfg(not(feature = "std"))] +use rstd::prelude::*; +use rstd::borrow::Borrow; +use codec::{FullCodec, FullEncode, Encode, EncodeLike, Ref, EncodeAppend}; +use crate::{storage::{self, unhashed}, hash::{Twox256, StorageHasher}, traits::Len}; + +/// Generator for `StoragePrefixedMap` used by `decl_storage`. +/// +/// For each key value is stored at: +/// ```nocompile +/// twox_256(prefix) ++ Hasher(key) +/// ``` +/// +/// # Warning +/// +/// If the keys are not trusted (e.g. can be set by a user), a cryptographic `hasher` such as +/// `blake2_256` must be used. Otherwise, other values in storage can be compromised. +/// +/// Besides this map no key prefixed with `twox_256(prefix)` must be inserted to the trie, +/// otherwise they are considered part of this map. +pub trait StoragePrefixedMap { + /// The type that get/take returns. + type Query; + + /// Hasher used to insert into storage. + type Hasher: StorageHasher; + + /// Prefix used to prepend each key. + fn prefix() -> &'static [u8]; + + /// Convert an optional value retrieved from storage to the type queried. + fn from_optional_value_to_query(v: Option) -> Self::Query; + + /// Convert a query to an optional value into storage. + fn from_query_to_optional_value(v: Self::Query) -> Option; + + /// Generate the full key used in top storage. + fn storage_prefixed_map_final_key(key: KeyArg) -> Vec + where + KeyArg: EncodeLike, + { + let mut final_key = Twox256::hash(&Self::prefix()).to_vec(); + final_key.extend_from_slice(key.using_encoded(Self::Hasher::hash).as_ref()); + final_key + } +} + +impl> + storage::StoragePrefixedMap for G +{ + type Query = G::Query; + + fn remove_all() { + unhashed::kill_prefix(&Twox256::hash(&Self::prefix())); + } + + fn swap, KeyArg2: EncodeLike>(key1: KeyArg1, key2: KeyArg2) { + let k1 = Self::storage_prefixed_map_final_key(key1); + let k2 = Self::storage_prefixed_map_final_key(key2); + + let v1 = unhashed::get_raw(k1.as_ref()); + if let Some(val) = unhashed::get_raw(k2.as_ref()) { + unhashed::put_raw(k1.as_ref(), &val); + } else { + unhashed::kill(k1.as_ref()) + } + if let Some(val) = v1 { + unhashed::put_raw(k2.as_ref(), &val); + } else { + unhashed::kill(k2.as_ref()) + } + } + + fn exists>(key: KeyArg) -> bool { + unhashed::exists(Self::storage_prefixed_map_final_key(key).as_ref()) + } + + fn get>(key: KeyArg) -> Self::Query { + G::from_optional_value_to_query(unhashed::get(Self::storage_prefixed_map_final_key(key).as_ref())) + } + + fn insert, ValArg: EncodeLike>(key: KeyArg, val: ValArg) { + unhashed::put(Self::storage_prefixed_map_final_key(key).as_ref(), &val.borrow()) + } + + fn remove>(key: KeyArg) { + unhashed::kill(Self::storage_prefixed_map_final_key(key).as_ref()) + } + + fn mutate, R, F: FnOnce(&mut Self::Query) -> R>(key: KeyArg, f: F) -> R { + let final_key = Self::storage_prefixed_map_final_key(key); + let mut val = G::from_optional_value_to_query(unhashed::get(final_key.as_ref())); + + let ret = f(&mut val); + match G::from_query_to_optional_value(val) { + Some(ref val) => unhashed::put(final_key.as_ref(), &val.borrow()), + None => unhashed::kill(final_key.as_ref()), + } + ret + } + + fn take>(key: KeyArg) -> Self::Query { + let key = Self::storage_prefixed_map_final_key(key); + let value = unhashed::take(key.as_ref()); + G::from_optional_value_to_query(value) + } + + fn append(key: KeyArg, items: Items) -> Result<(), &'static str> + where + KeyArg: EncodeLike, + Item: Encode, + EncodeLikeItem: EncodeLike, + V: EncodeAppend, + Items: IntoIterator, + Items::IntoIter: ExactSizeIterator, + { + let key = Self::storage_prefixed_map_final_key(key); + let encoded_value = unhashed::get_raw(key.as_ref()) + .unwrap_or_else(|| { + match G::from_query_to_optional_value(G::from_optional_value_to_query(None)) { + Some(value) => value.encode(), + None => vec![], + } + }); + + let new_val = V::append_or_new( + encoded_value, + items, + ).map_err(|_| "Could not append given item")?; + unhashed::put_raw(key.as_ref(), &new_val); + Ok(()) + } + + fn append_or_insert(key: KeyArg, items: Items) + where + KeyArg: EncodeLike, + Item: Encode, + EncodeLikeItem: EncodeLike, + V: EncodeAppend, + Items: IntoIterator + Clone + EncodeLike, + Items::IntoIter: ExactSizeIterator, + { + Self::append(Ref::from(&key), items.clone()) + .unwrap_or_else(|_| Self::insert(key, items)); + } + + fn decode_len>(key: KeyArg) -> Result + where V: codec::DecodeLength + Len + { + let key = Self::storage_prefixed_map_final_key(key); + if let Some(v) = unhashed::get_raw(key.as_ref()) { + ::len(&v).map_err(|e| e.what()) + } else { + let len = G::from_query_to_optional_value(G::from_optional_value_to_query(None)) + .map(|v| v.len()) + .unwrap_or(0); + + Ok(len) + } + } +} + +#[cfg(test)] +mod tests { + pub trait Trait { + type BlockNumber: codec::FullCodec + Default; + type Origin; + } + + // Put into module to avoid dead code warning. + #[allow(dead_code)] + mod module { + use super::Trait; + decl_module! { + pub struct Module for enum Call where origin: T::Origin {} + } + } + use self::module::Module; + + crate::decl_storage! { + trait Store for Module as Example { + pub BuildWorks build(|_| vec![(1, 2)]): prefixed_map u32 => u32; + pub ConfigAndGetWorks get(fn getter) config(config): prefixed_map u32 => u32; + pub PrefixedMap: prefixed_map hasher(blake2_128) u32 => u64; + pub OptionPrefixedMap: prefixed_map hasher(blake2_128) u32 => Option; + + pub PrefixedMapVec: prefixed_map hasher(twox_256) u32 => Vec; + pub OptionPrefixedMapVec: prefixed_map hasher(blake2_128) u32 => Option>; + pub PrefixedMapVecWithDefault: prefixed_map u32 => Vec = vec![6, 9]; + } + } + + struct Test; + impl Trait for Test { + type BlockNumber = u32; + type Origin = u32; + } + + fn new_test_ext() -> runtime_io::TestExternalities { + GenesisConfig { + config: vec![(3, 4)], + .. Default::default() + }.build_storage().unwrap().into() + } + + #[test] + fn prefixed_map_config_and_get_works() { + new_test_ext().execute_with(|| { + assert_eq!(ConfigAndGetWorks::get(3), 4); + assert_eq!(>::getter(3), 4); + }) + } + + #[test] + fn prefixed_map_build_works() { + new_test_ext().execute_with(|| { + assert_eq!(BuildWorks::get(1), 2); + }) + } + + #[test] + fn prefixed_map_basic_insert_remove_works() { + new_test_ext().execute_with(|| { + // get / insert / take + let key = 17; + assert_eq!(PrefixedMap::get(&key), 0); + PrefixedMap::insert(key, 4); + assert_eq!(PrefixedMap::get(&key), 4); + assert_eq!(PrefixedMap::take(&key), 4); + assert_eq!(PrefixedMap::get(&key), 0); + + // mutate + PrefixedMap::mutate(&key, |val| { + *val = 15; + }); + assert_eq!(PrefixedMap::get(&key), 15); + + // remove + PrefixedMap::remove(&key); + assert_eq!(PrefixedMap::get(&key), 0); + + // swap + PrefixedMap::insert(1, 1); + PrefixedMap::insert(2, 2); + PrefixedMap::swap(1, 2); + assert_eq!(PrefixedMap::get(1), 2); + assert_eq!(PrefixedMap::get(2), 1); + PrefixedMap::swap(1, 3); + assert_eq!(PrefixedMap::get(1), 0); + assert_eq!(PrefixedMap::get(3), 2); + + // remove all + PrefixedMap::insert(1, 1); + PrefixedMap::insert(2, 2); + PrefixedMap::remove_all(); + assert_eq!(PrefixedMap::get(1), 0); + assert_eq!(PrefixedMap::get(2), 0); + }); + } + + #[test] + fn option_prefixed_map_basic_insert_remove_works() { + new_test_ext().execute_with(|| { + // get / insert / take + let key = 17; + assert_eq!(OptionPrefixedMap::get(&key), None); + OptionPrefixedMap::insert(key, 4); + assert_eq!(OptionPrefixedMap::get(&key), Some(4)); + assert_eq!(OptionPrefixedMap::take(&key), Some(4)); + assert_eq!(OptionPrefixedMap::get(&key), None); + + // mutate + OptionPrefixedMap::mutate(&key, |val| { + *val = Some(15); + }); + assert_eq!(OptionPrefixedMap::get(&key), Some(15)); + OptionPrefixedMap::mutate(&key, |val| { + *val = None; + }); + assert_eq!(OptionPrefixedMap::get(&key), None); + + // remove + OptionPrefixedMap::remove(&key); + assert_eq!(OptionPrefixedMap::get(&key), None); + + // swap + OptionPrefixedMap::insert(1, 1); + OptionPrefixedMap::insert(2, 2); + OptionPrefixedMap::swap(1, 2); + assert_eq!(OptionPrefixedMap::get(1), Some(2)); + assert_eq!(OptionPrefixedMap::get(2), Some(1)); + OptionPrefixedMap::swap(1, 3); + assert_eq!(OptionPrefixedMap::get(1), None); + assert_eq!(OptionPrefixedMap::get(3), Some(2)); + + // remove all + OptionPrefixedMap::insert(1, 1); + OptionPrefixedMap::insert(2, 2); + OptionPrefixedMap::remove_all(); + assert_eq!(OptionPrefixedMap::get(1), None); + assert_eq!(OptionPrefixedMap::get(2), None); + }); + } + + #[test] + fn append_works() { + new_test_ext().execute_with(|| { + let _ = PrefixedMapVec::append(1, [1, 2, 3].iter()); + let _ = PrefixedMapVec::append(1, [4, 5].iter()); + assert_eq!(PrefixedMapVec::get(1), vec![1, 2, 3, 4, 5]); + }); + } + + #[test] + fn append_works_for_default() { + new_test_ext().execute_with(|| { + assert_eq!(PrefixedMapVecWithDefault::exists(0), false); + assert_eq!(PrefixedMapVecWithDefault::get(0), vec![6, 9]); + let _ = PrefixedMapVecWithDefault::append(0, [1].iter()); + assert_eq!(PrefixedMapVecWithDefault::get(0), vec![6, 9, 1]); + }); + } + + #[test] + fn append_or_put_works() { + new_test_ext().execute_with(|| { + let _ = PrefixedMapVec::append_or_insert(1, &[1, 2, 3][..]); + let _ = PrefixedMapVec::append_or_insert(1, &[4, 5][..]); + assert_eq!(PrefixedMapVec::get(1), vec![1, 2, 3, 4, 5]); + }); + } + + #[test] + fn len_works() { + new_test_ext().execute_with(|| { + PrefixedMapVec::insert(1, &vec![1, 2, 3, 4, 5, 6]); + assert_eq!(PrefixedMapVec::decode_len(1).unwrap(), 6); + }); + } + + #[test] + fn len_works_for_default() { + new_test_ext().execute_with(|| { + assert_eq!(PrefixedMapVec::get(0), vec![]); + assert_eq!(PrefixedMapVec::decode_len(0), Ok(0)); + + assert_eq!(PrefixedMapVecWithDefault::get(0), vec![6, 9]); + assert_eq!(PrefixedMapVecWithDefault::decode_len(0), Ok(2)); + + assert_eq!(OptionPrefixedMapVec::get(0), None); + assert_eq!(OptionPrefixedMapVec::decode_len(0), Ok(0)); + }); + } +} diff --git a/srml/support/src/storage/mod.rs b/srml/support/src/storage/mod.rs index f10deb93d241a..8135eb8e06c1f 100644 --- a/srml/support/src/storage/mod.rs +++ b/srml/support/src/storage/mod.rs @@ -232,6 +232,74 @@ pub trait StorageLinkedMap { where V: codec::DecodeLength + Len; } +/// A strongly-typed map in storage. All keys are prefixed in the storage. This isolate and for +/// instance allow to kill them all. +/// +/// Details on implementation can be found at [`generator::StoragePrefixedMap`] +pub trait StoragePrefixedMap { + /// The type that get/take return. + type Query; + + /// Does the value (explicitly) exist in storage? + fn exists>(key: KeyArg) -> bool; + + /// Remove all values of this storage. + fn remove_all(); + + /// Load the value associated with the given key from the map. + fn get>(key: KeyArg) -> Self::Query; + + /// Swap the values of two keys. + fn swap, KeyArg2: EncodeLike>(key1: KeyArg1, key2: KeyArg2); + + /// Store a value to be associated with the given key from the map. + fn insert, ValArg: EncodeLike>(key: KeyArg, val: ValArg); + + /// Remove the value under a key. + fn remove>(key: KeyArg); + + /// Mutate the value under a key. + fn mutate, R, F: FnOnce(&mut Self::Query) -> R>(key: KeyArg, f: F) -> R; + + /// Take the value under a key. + fn take>(key: KeyArg) -> Self::Query; + + /// Append the given items to the value in the storage. + /// + /// `V` is required to implement `codec::EncodeAppend`. + fn append(key: KeyArg, items: Items) -> Result<(), &'static str> + where + KeyArg: EncodeLike, + Item: Encode, + EncodeLikeItem: EncodeLike, + V: EncodeAppend, + Items: IntoIterator, + Items::IntoIter: ExactSizeIterator; + + /// Safely append the given items to the value in the storage. If a codec error occurs, then the + /// old (presumably corrupt) value is replaced with the given `items`. + /// + /// `V` is required to implement `codec::EncodeAppend`. + fn append_or_insert(key: KeyArg, items: Items) + where + KeyArg: EncodeLike, + Item: Encode, + EncodeLikeItem: EncodeLike, + V: EncodeAppend, + Items: IntoIterator + Clone + EncodeLike, + Items::IntoIter: ExactSizeIterator; + + /// Read the length of the value in a fast way, without decoding the entire value. + /// + /// `T` is required to implement `Codec::DecodeLength`. + /// + /// Note that `0` is returned as the default value if no encoded value exists at the given key. + /// Therefore, this function cannot be used as a sign of _existence_. use the `::exists()` + /// function for this purpose. + fn decode_len>(key: KeyArg) -> Result + where V: codec::DecodeLength + Len; +} + /// An implementation of a map with a two keys. /// /// It provides an important ability to efficiently remove all entries diff --git a/srml/support/test/tests/decl_storage.rs b/srml/support/test/tests/decl_storage.rs index c9dd96791b164..02149dee33cae 100644 --- a/srml/support/test/tests/decl_storage.rs +++ b/srml/support/test/tests/decl_storage.rs @@ -218,7 +218,7 @@ mod tests { hasher: StorageHasher::Blake2_256, key: DecodeDifferent::Encode("u32"), value: DecodeDifferent::Encode("String"), - is_linked: false, + kind: StorageMapKind::Map, }, default: DecodeDifferent::Encode( DefaultByteGetter(&__GetByteStructMAPU32(PhantomData::)) @@ -232,7 +232,7 @@ mod tests { hasher: StorageHasher::Blake2_256, key: DecodeDifferent::Encode("u32"), value: DecodeDifferent::Encode("String"), - is_linked: false, + kind: StorageMapKind::Map, }, default: DecodeDifferent::Encode( DefaultByteGetter(&__GetByteStructPUBMAPU32(PhantomData::)) @@ -246,7 +246,7 @@ mod tests { hasher: StorageHasher::Blake2_256, key: DecodeDifferent::Encode("u32"), value: DecodeDifferent::Encode("String"), - is_linked: false, + kind: StorageMapKind::Map, }, default: DecodeDifferent::Encode( DefaultByteGetter(&__GetByteStructMAPU32MYDEF(PhantomData::)) @@ -260,7 +260,7 @@ mod tests { hasher: StorageHasher::Blake2_256, key: DecodeDifferent::Encode("u32"), value: DecodeDifferent::Encode("String"), - is_linked: false, + kind: StorageMapKind::Map, }, default: DecodeDifferent::Encode( DefaultByteGetter(&__GetByteStructPUBMAPU32MYDEF(PhantomData::)) @@ -274,7 +274,7 @@ mod tests { hasher: StorageHasher::Blake2_256, key: DecodeDifferent::Encode("u32"), value: DecodeDifferent::Encode("String"), - is_linked: false, + kind: StorageMapKind::Map, }, default: DecodeDifferent::Encode( DefaultByteGetter(&__GetByteStructGETMAPU32(PhantomData::)) @@ -288,7 +288,7 @@ mod tests { hasher: StorageHasher::Blake2_256, key: DecodeDifferent::Encode("u32"), value: DecodeDifferent::Encode("String"), - is_linked: false, + kind: StorageMapKind::Map, }, default: DecodeDifferent::Encode( DefaultByteGetter(&__GetByteStructPUBGETMAPU32(PhantomData::)) @@ -302,7 +302,7 @@ mod tests { hasher: StorageHasher::Blake2_256, key: DecodeDifferent::Encode("u32"), value: DecodeDifferent::Encode("String"), - is_linked: false, + kind: StorageMapKind::Map, }, default: DecodeDifferent::Encode( DefaultByteGetter(&__GetByteStructGETMAPU32MYDEF(PhantomData::)) @@ -316,7 +316,7 @@ mod tests { hasher: StorageHasher::Blake2_256, key: DecodeDifferent::Encode("u32"), value: DecodeDifferent::Encode("String"), - is_linked: false, + kind: StorageMapKind::Map, }, default: DecodeDifferent::Encode( DefaultByteGetter(&__GetByteStructPUBGETMAPU32MYDEF(PhantomData::)) @@ -330,7 +330,7 @@ mod tests { hasher: StorageHasher::Blake2_256, key: DecodeDifferent::Encode("u32"), value: DecodeDifferent::Encode("String"), - is_linked: true, + kind: StorageMapKind::LinkedMap, }, default: DecodeDifferent::Encode( DefaultByteGetter(&__GetByteStructLINKEDMAPU32(PhantomData::)) @@ -344,7 +344,7 @@ mod tests { hasher: StorageHasher::Blake2_256, key: DecodeDifferent::Encode("u32"), value: DecodeDifferent::Encode("String"), - is_linked: true, + kind: StorageMapKind::LinkedMap, }, default: DecodeDifferent::Encode( DefaultByteGetter(&__GetByteStructPUBLINKEDMAPU32MYDEF(PhantomData::)) @@ -358,7 +358,7 @@ mod tests { hasher: StorageHasher::Blake2_256, key: DecodeDifferent::Encode("u32"), value: DecodeDifferent::Encode("String"), - is_linked: true, + kind: StorageMapKind::LinkedMap, }, default: DecodeDifferent::Encode( DefaultByteGetter(&__GetByteStructGETLINKEDMAPU32(PhantomData::)) @@ -372,7 +372,7 @@ mod tests { hasher: StorageHasher::Blake2_256, key: DecodeDifferent::Encode("u32"), value: DecodeDifferent::Encode("String"), - is_linked: true, + kind: StorageMapKind::LinkedMap, }, default: DecodeDifferent::Encode( DefaultByteGetter(&__GetByteStructPUBGETLINKEDMAPU32MYDEF(PhantomData::)) diff --git a/srml/support/test/tests/final_keys.rs b/srml/support/test/tests/final_keys.rs index 44a6b540a7a0e..860a05aaa7dfd 100644 --- a/srml/support/test/tests/final_keys.rs +++ b/srml/support/test/tests/final_keys.rs @@ -17,7 +17,7 @@ use runtime_io::with_storage; use support::storage::unhashed; use codec::Encode; -use support::{StorageDoubleMap, StorageLinkedMap, StorageMap, StorageValue}; +use support::{StorageDoubleMap, StorageLinkedMap, StorageMap, StorageValue, StoragePrefixedMap}; mod no_instance { use codec::{Encode, Decode, EncodeLike}; @@ -41,6 +41,9 @@ mod no_instance { pub LinkedMap: linked_map u32 => u32; pub LinkedMap2: linked_map hasher(twox_128) u32 => u32; + pub PrefixedMap: prefixed_map u32 => u32; + pub PrefixedMap2: prefixed_map hasher(twox_128) u32 => u32; + pub DoubleMap: double_map u32, blake2_256(u32) => u32; pub DoubleMap2: double_map hasher(twox_128) u32, blake2_128(u32) => u32; @@ -71,6 +74,9 @@ mod instance { pub LinkedMap: linked_map u32 => u32; pub LinkedMap2: linked_map hasher(twox_128) u32 => u32; + pub PrefixedMap: prefixed_map u32 => u32; + pub PrefixedMap2: prefixed_map hasher(twox_128) u32 => u32; + pub DoubleMap: double_map u32, blake2_256(u32) => u32; pub DoubleMap2: double_map hasher(twox_128) u32, blake2_128(u32) => u32; @@ -115,6 +121,16 @@ fn final_keys_no_instance() { k.extend(1u32.encode()); assert_eq!(unhashed::get::(&runtime_io::twox_128(&k)), Some(2u32)); + no_instance::PrefixedMap::insert(1, 2); + let mut k = runtime_io::twox_256(b"FinalKeysNone PrefixedMap").to_vec(); + k.extend(&runtime_io::blake2_256(&1u32.encode())); + assert_eq!(unhashed::get::(&k), Some(2u32)); + + no_instance::PrefixedMap2::insert(1, 2); + let mut k = runtime_io::twox_256(b"FinalKeysNone PrefixedMap2").to_vec(); + k.extend(&runtime_io::twox_128(&1u32.encode())); + assert_eq!(unhashed::get::(&k), Some(2u32)); + no_instance::DoubleMap::insert(&1, &2, &3); let mut k = b"FinalKeysNone DoubleMap".to_vec(); k.extend(1u32.encode()); @@ -161,6 +177,16 @@ fn final_keys_default_instance() { k.extend(1u32.encode()); assert_eq!(unhashed::get::(&runtime_io::twox_128(&k)), Some(2u32)); + >::insert(1, 2); + let mut k = runtime_io::twox_256(b"FinalKeysSome PrefixedMap").to_vec(); + k.extend(&runtime_io::blake2_256(&1u32.encode())); + assert_eq!(unhashed::get::(&k), Some(2u32)); + + >::insert(1, 2); + let mut k = runtime_io::twox_256(b"FinalKeysSome PrefixedMap2").to_vec(); + k.extend(&runtime_io::twox_128(&1u32.encode())); + assert_eq!(unhashed::get::(&k), Some(2u32)); + >::insert(&1, &2, &3); let mut k = b"FinalKeysSome DoubleMap".to_vec(); k.extend(1u32.encode()); @@ -210,6 +236,16 @@ fn final_keys_instance_2() { k.extend(1u32.encode()); assert_eq!(unhashed::get::(&runtime_io::twox_128(&k)), Some(2u32)); + >::insert(1, 2); + let mut k = runtime_io::twox_256(b"Instance2FinalKeysSome PrefixedMap").to_vec(); + k.extend(&runtime_io::blake2_256(&1u32.encode())); + assert_eq!(unhashed::get::(&k), Some(2u32)); + + >::insert(1, 2); + let mut k = runtime_io::twox_256(b"Instance2FinalKeysSome PrefixedMap2").to_vec(); + k.extend(&runtime_io::twox_128(&1u32.encode())); + assert_eq!(unhashed::get::(&k), Some(2u32)); + >::insert(&1, &2, &3); let mut k = b"Instance2FinalKeysSome DoubleMap".to_vec(); k.extend(1u32.encode()); diff --git a/srml/support/test/tests/instance.rs b/srml/support/test/tests/instance.rs index 282fb9a6e299f..30b3aaa143665 100644 --- a/srml/support/test/tests/instance.rs +++ b/srml/support/test/tests/instance.rs @@ -21,7 +21,7 @@ use support::{ Parameter, traits::Get, parameter_types, metadata::{ DecodeDifferent, StorageMetadata, StorageEntryModifier, StorageEntryType, DefaultByteGetter, - StorageEntryMetadata, StorageHasher, + StorageEntryMetadata, StorageHasher, StorageMapKind, }, StorageValue, StorageMap, StorageLinkedMap, StorageDoubleMap, }; @@ -417,7 +417,7 @@ const EXPECTED_METADATA: StorageMetadata = StorageMetadata { hasher: StorageHasher::Blake2_256, key: DecodeDifferent::Encode("u64"), value: DecodeDifferent::Encode("u64"), - is_linked: false, + kind: StorageMapKind::Map, }, default: DecodeDifferent::Encode( DefaultByteGetter( @@ -435,7 +435,7 @@ const EXPECTED_METADATA: StorageMetadata = StorageMetadata { hasher: StorageHasher::Blake2_256, key: DecodeDifferent::Encode("u64"), value: DecodeDifferent::Encode("Vec"), - is_linked: true, + kind: StorageMapKind::LinkedMap, }, default: DecodeDifferent::Encode( DefaultByteGetter(