feat: Rebrand Polkadot/Substrate references to PezkuwiChain
This commit systematically rebrands various references from Parity Technologies' Polkadot/Substrate ecosystem to PezkuwiChain within the kurdistan-sdk. Key changes include: - Updated external repository URLs (zombienet-sdk, parity-db, parity-scale-codec, wasm-instrument) to point to pezkuwichain forks. - Modified internal documentation and code comments to reflect PezkuwiChain naming and structure. - Replaced direct references to with or specific paths within the for XCM, Pezkuwi, and other modules. - Cleaned up deprecated issue and PR references in various and files, particularly in and modules. - Adjusted image and logo URLs in documentation to point to PezkuwiChain assets. - Removed or rephrased comments related to external Polkadot/Substrate PRs and issues. This is a significant step towards fully customizing the SDK for the PezkuwiChain ecosystem.
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Traits, types and structs to support a bounded BTreeMap.
|
||||
|
||||
use crate::storage::StorageDecodeLength;
|
||||
pub use pezsp_runtime::BoundedBTreeMap;
|
||||
|
||||
impl<K, V, S> StorageDecodeLength for BoundedBTreeMap<K, V, S> {}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod test {
|
||||
use super::*;
|
||||
use crate::Twox128;
|
||||
use alloc::collections::btree_map::BTreeMap;
|
||||
use pezframe_support::traits::{ConstU32, Get};
|
||||
use pezsp_io::TestExternalities;
|
||||
|
||||
#[crate::storage_alias]
|
||||
type Foo = StorageValue<Prefix, BoundedBTreeMap<u32, (), ConstU32<7>>>;
|
||||
|
||||
#[crate::storage_alias]
|
||||
type FooMap = StorageMap<Prefix, Twox128, u32, BoundedBTreeMap<u32, (), ConstU32<7>>>;
|
||||
|
||||
#[crate::storage_alias]
|
||||
type FooDoubleMap =
|
||||
StorageDoubleMap<Prefix, Twox128, u32, Twox128, u32, BoundedBTreeMap<u32, (), ConstU32<7>>>;
|
||||
|
||||
fn map_from_keys<K>(keys: &[K]) -> BTreeMap<K, ()>
|
||||
where
|
||||
K: Ord + Copy,
|
||||
{
|
||||
keys.iter().copied().zip(std::iter::repeat(())).collect()
|
||||
}
|
||||
|
||||
fn boundedmap_from_keys<K, S>(keys: &[K]) -> BoundedBTreeMap<K, (), S>
|
||||
where
|
||||
K: Ord + Copy,
|
||||
S: Get<u32>,
|
||||
{
|
||||
map_from_keys(keys).try_into().unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_len_works() {
|
||||
TestExternalities::default().execute_with(|| {
|
||||
let bounded = boundedmap_from_keys::<u32, ConstU32<7>>(&[1, 2, 3]);
|
||||
Foo::put(bounded);
|
||||
assert_eq!(Foo::decode_len().unwrap(), 3);
|
||||
});
|
||||
|
||||
TestExternalities::default().execute_with(|| {
|
||||
let bounded = boundedmap_from_keys::<u32, ConstU32<7>>(&[1, 2, 3]);
|
||||
FooMap::insert(1, bounded);
|
||||
assert_eq!(FooMap::decode_len(1).unwrap(), 3);
|
||||
assert!(FooMap::decode_len(0).is_none());
|
||||
assert!(FooMap::decode_len(2).is_none());
|
||||
});
|
||||
|
||||
TestExternalities::default().execute_with(|| {
|
||||
let bounded = boundedmap_from_keys::<u32, ConstU32<7>>(&[1, 2, 3]);
|
||||
FooDoubleMap::insert(1, 1, bounded);
|
||||
assert_eq!(FooDoubleMap::decode_len(1, 1).unwrap(), 3);
|
||||
assert!(FooDoubleMap::decode_len(2, 1).is_none());
|
||||
assert!(FooDoubleMap::decode_len(1, 2).is_none());
|
||||
assert!(FooDoubleMap::decode_len(2, 2).is_none());
|
||||
});
|
||||
|
||||
TestExternalities::default().execute_with(|| {
|
||||
let bounded = boundedmap_from_keys::<u32, ConstU32<7>>(&[1, 2, 3]);
|
||||
FooDoubleMap::insert(1, 1, bounded.clone());
|
||||
FooDoubleMap::insert(2, 2, bounded); // duplicate value
|
||||
|
||||
assert_eq!(FooDoubleMap::decode_len(1, 1).unwrap(), 3);
|
||||
assert_eq!(FooDoubleMap::decode_len(2, 2).unwrap(), 3);
|
||||
assert!(FooDoubleMap::decode_len(2, 1).is_none());
|
||||
assert!(FooDoubleMap::decode_len(1, 2).is_none());
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Traits, types and structs to support a bounded `BTreeSet`.
|
||||
|
||||
use pezframe_support::storage::StorageDecodeNonDedupLength;
|
||||
pub use pezsp_runtime::BoundedBTreeSet;
|
||||
|
||||
impl<T, S> StorageDecodeNonDedupLength for BoundedBTreeSet<T, S> {}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod test {
|
||||
use super::*;
|
||||
use crate::Twox128;
|
||||
use alloc::collections::btree_set::BTreeSet;
|
||||
use pezframe_support::traits::{ConstU32, Get};
|
||||
use pezsp_io::TestExternalities;
|
||||
|
||||
#[crate::storage_alias]
|
||||
type Foo = StorageValue<Prefix, BoundedBTreeSet<u32, ConstU32<7>>>;
|
||||
|
||||
#[crate::storage_alias]
|
||||
type FooMap = StorageMap<Prefix, Twox128, u32, BoundedBTreeSet<u32, ConstU32<7>>>;
|
||||
|
||||
#[crate::storage_alias]
|
||||
type FooDoubleMap =
|
||||
StorageDoubleMap<Prefix, Twox128, u32, Twox128, u32, BoundedBTreeSet<u32, ConstU32<7>>>;
|
||||
|
||||
fn set_from_keys<T>(keys: &[T]) -> BTreeSet<T>
|
||||
where
|
||||
T: Ord + Copy,
|
||||
{
|
||||
keys.iter().copied().collect()
|
||||
}
|
||||
|
||||
fn boundedset_from_keys<T, S>(keys: &[T]) -> BoundedBTreeSet<T, S>
|
||||
where
|
||||
T: Ord + Copy,
|
||||
S: Get<u32>,
|
||||
{
|
||||
set_from_keys(keys).try_into().unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_non_dedup_len_works() {
|
||||
TestExternalities::default().execute_with(|| {
|
||||
let bounded = boundedset_from_keys::<u32, ConstU32<7>>(&[1, 2, 3]);
|
||||
Foo::put(bounded);
|
||||
assert_eq!(Foo::decode_non_dedup_len().unwrap(), 3);
|
||||
});
|
||||
|
||||
TestExternalities::default().execute_with(|| {
|
||||
let bounded = boundedset_from_keys::<u32, ConstU32<7>>(&[1, 2, 3]);
|
||||
FooMap::insert(1, bounded);
|
||||
assert_eq!(FooMap::decode_non_dedup_len(1).unwrap(), 3);
|
||||
assert!(FooMap::decode_non_dedup_len(0).is_none());
|
||||
assert!(FooMap::decode_non_dedup_len(2).is_none());
|
||||
});
|
||||
|
||||
TestExternalities::default().execute_with(|| {
|
||||
let bounded = boundedset_from_keys::<u32, ConstU32<7>>(&[1, 2, 3]);
|
||||
FooDoubleMap::insert(1, 1, bounded);
|
||||
assert_eq!(FooDoubleMap::decode_non_dedup_len(1, 1).unwrap(), 3);
|
||||
assert!(FooDoubleMap::decode_non_dedup_len(2, 1).is_none());
|
||||
assert!(FooDoubleMap::decode_non_dedup_len(1, 2).is_none());
|
||||
assert!(FooDoubleMap::decode_non_dedup_len(2, 2).is_none());
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Traits, types and structs to support putting a bounded vector into storage, as a raw value, map
|
||||
//! or a double map.
|
||||
|
||||
use crate::{
|
||||
storage::{StorageDecodeLength, StorageTryAppend},
|
||||
traits::Get,
|
||||
};
|
||||
pub use pezsp_runtime::{BoundedSlice, BoundedVec};
|
||||
|
||||
impl<T, S> StorageDecodeLength for BoundedVec<T, S> {}
|
||||
|
||||
impl<T, S: Get<u32>> StorageTryAppend<T> for BoundedVec<T, S> {
|
||||
fn bound() -> usize {
|
||||
S::get() as usize
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod test {
|
||||
use super::*;
|
||||
use crate::{traits::ConstU32, Twox128};
|
||||
use pezsp_io::TestExternalities;
|
||||
use pezsp_runtime::bounded_vec;
|
||||
|
||||
#[crate::storage_alias]
|
||||
type Foo = StorageValue<Prefix, BoundedVec<u32, ConstU32<7>>>;
|
||||
|
||||
#[crate::storage_alias]
|
||||
type FooMap = StorageMap<Prefix, Twox128, u32, BoundedVec<u32, ConstU32<7>>>;
|
||||
|
||||
#[crate::storage_alias]
|
||||
type FooDoubleMap =
|
||||
StorageDoubleMap<Prefix, Twox128, u32, Twox128, u32, BoundedVec<u32, ConstU32<7>>>;
|
||||
|
||||
#[test]
|
||||
fn decode_len_works() {
|
||||
TestExternalities::default().execute_with(|| {
|
||||
let bounded: BoundedVec<u32, ConstU32<7>> = bounded_vec![1, 2, 3];
|
||||
Foo::put(bounded);
|
||||
assert_eq!(Foo::decode_len().unwrap(), 3);
|
||||
});
|
||||
|
||||
TestExternalities::default().execute_with(|| {
|
||||
let bounded: BoundedVec<u32, ConstU32<7>> = bounded_vec![1, 2, 3];
|
||||
FooMap::insert(1, bounded);
|
||||
assert_eq!(FooMap::decode_len(1).unwrap(), 3);
|
||||
assert!(FooMap::decode_len(0).is_none());
|
||||
assert!(FooMap::decode_len(2).is_none());
|
||||
});
|
||||
|
||||
TestExternalities::default().execute_with(|| {
|
||||
let bounded: BoundedVec<u32, ConstU32<7>> = bounded_vec![1, 2, 3];
|
||||
FooDoubleMap::insert(1, 1, bounded);
|
||||
assert_eq!(FooDoubleMap::decode_len(1, 1).unwrap(), 3);
|
||||
assert!(FooDoubleMap::decode_len(2, 1).is_none());
|
||||
assert!(FooDoubleMap::decode_len(1, 2).is_none());
|
||||
assert!(FooDoubleMap::decode_len(2, 2).is_none());
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Operation on runtime child storages.
|
||||
//!
|
||||
//! This module is a currently only a variant of unhashed with additional `child_info`.
|
||||
// NOTE: could replace unhashed by having only one kind of storage (top trie being the child info
|
||||
// of null length parent storage key).
|
||||
|
||||
use alloc::vec::Vec;
|
||||
use codec::{Codec, Decode, Encode};
|
||||
pub use pezsp_core::storage::{ChildInfo, ChildType, StateVersion};
|
||||
pub use pezsp_io::{KillStorageResult, MultiRemovalResults};
|
||||
|
||||
/// Return the value of the item in storage under `key`, or `None` if there is no explicit entry.
|
||||
pub fn get<T: Decode + Sized>(child_info: &ChildInfo, key: &[u8]) -> Option<T> {
|
||||
match child_info.child_type() {
|
||||
ChildType::ParentKeyId => {
|
||||
let storage_key = child_info.storage_key();
|
||||
pezsp_io::default_child_storage::get(storage_key, key).and_then(|v| {
|
||||
Decode::decode(&mut &v[..]).map(Some).unwrap_or_else(|_| {
|
||||
// TODO #3700: error should be handleable.
|
||||
log::error!(
|
||||
target: "runtime::storage",
|
||||
"Corrupted state in child trie at {:?}/{:?}",
|
||||
storage_key,
|
||||
key,
|
||||
);
|
||||
None
|
||||
})
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// 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>(child_info: &ChildInfo, key: &[u8]) -> T {
|
||||
get(child_info, key).unwrap_or_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>(child_info: &ChildInfo, key: &[u8], default_value: T) -> T {
|
||||
get(child_info, 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>(
|
||||
child_info: &ChildInfo,
|
||||
key: &[u8],
|
||||
default_value: F,
|
||||
) -> T {
|
||||
get(child_info, key).unwrap_or_else(default_value)
|
||||
}
|
||||
|
||||
/// Put `value` in storage under `key`.
|
||||
pub fn put<T: Encode>(child_info: &ChildInfo, key: &[u8], value: &T) {
|
||||
match child_info.child_type() {
|
||||
ChildType::ParentKeyId => value.using_encoded(|slice| {
|
||||
pezsp_io::default_child_storage::set(child_info.storage_key(), key, slice)
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove `key` from storage, returning its value if it had an explicit entry or `None` otherwise.
|
||||
pub fn take<T: Decode + Sized>(child_info: &ChildInfo, key: &[u8]) -> Option<T> {
|
||||
let r = get(child_info, key);
|
||||
if r.is_some() {
|
||||
kill(child_info, 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>(child_info: &ChildInfo, key: &[u8]) -> T {
|
||||
take(child_info, key).unwrap_or_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>(child_info: &ChildInfo, key: &[u8], default_value: T) -> T {
|
||||
take(child_info, 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>(
|
||||
child_info: &ChildInfo,
|
||||
key: &[u8],
|
||||
default_value: F,
|
||||
) -> T {
|
||||
take(child_info, key).unwrap_or_else(default_value)
|
||||
}
|
||||
|
||||
/// Check to see if `key` has an explicit entry in storage.
|
||||
pub fn exists(child_info: &ChildInfo, key: &[u8]) -> bool {
|
||||
match child_info.child_type() {
|
||||
ChildType::ParentKeyId =>
|
||||
pezsp_io::default_child_storage::exists(child_info.storage_key(), key),
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove all `storage_key` key/values
|
||||
///
|
||||
/// Deletes all keys from the overlay and up to `limit` keys from the backend if
|
||||
/// it is set to `Some`. No limit is applied when `limit` is set to `None`.
|
||||
///
|
||||
/// The limit can be used to partially delete a child trie in case it is too large
|
||||
/// to delete in one go (block).
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Please note that keys that are residing in the overlay for that child trie when
|
||||
/// issuing this call are all deleted without counting towards the `limit`. Only keys
|
||||
/// written during the current block are part of the overlay. Deleting with a `limit`
|
||||
/// mostly makes sense with an empty overlay for that child trie.
|
||||
///
|
||||
/// Calling this function multiple times per block for the same `storage_key` does
|
||||
/// not make much sense because it is not cumulative when called inside the same block.
|
||||
/// Use this function to distribute the deletion of a single child trie across multiple
|
||||
/// blocks.
|
||||
#[deprecated = "Use `clear_storage` instead"]
|
||||
pub fn kill_storage(child_info: &ChildInfo, limit: Option<u32>) -> KillStorageResult {
|
||||
match child_info.child_type() {
|
||||
ChildType::ParentKeyId =>
|
||||
pezsp_io::default_child_storage::storage_kill(child_info.storage_key(), limit),
|
||||
}
|
||||
}
|
||||
|
||||
/// Partially clear the child storage of each key-value pair.
|
||||
///
|
||||
/// # Limit
|
||||
///
|
||||
/// A *limit* should always be provided through `maybe_limit`. This is one fewer than the
|
||||
/// maximum number of backend iterations which may be done by this operation and as such
|
||||
/// represents the maximum number of backend deletions which may happen. A *limit* of zero
|
||||
/// implies that no keys will be deleted, though there may be a single iteration done.
|
||||
///
|
||||
/// The limit can be used to partially delete storage items in case it is too large or costly
|
||||
/// to delete all in a single operation.
|
||||
///
|
||||
/// # Cursor
|
||||
///
|
||||
/// A *cursor* may be passed in to this operation with `maybe_cursor`. `None` should only be
|
||||
/// passed once (in the initial call) for any attempt to clear storage. In general, subsequent calls
|
||||
/// operating on the same prefix should pass `Some` and this value should be equal to the
|
||||
/// previous call result's `maybe_cursor` field. The only exception to this is when you can
|
||||
/// guarantee that the subsequent call is in a new block; in this case the previous call's result
|
||||
/// cursor need not be passed in and a `None` may be passed instead. This exception may be useful
|
||||
/// then making this call solely from a block-hook such as `on_initialize`.
|
||||
|
||||
/// Returns [`MultiRemovalResults`] to inform about the result. Once the resultant `maybe_cursor`
|
||||
/// field is `None`, then no further items remain to be deleted.
|
||||
///
|
||||
/// NOTE: After the initial call for any given child storage, it is important that no keys further
|
||||
/// keys are inserted. If so, then they may or may not be deleted by subsequent calls.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Please note that keys which are residing in the overlay for the child are deleted without
|
||||
/// counting towards the `limit`.
|
||||
pub fn clear_storage(
|
||||
child_info: &ChildInfo,
|
||||
maybe_limit: Option<u32>,
|
||||
_maybe_cursor: Option<&[u8]>,
|
||||
) -> MultiRemovalResults {
|
||||
// TODO: Once the network has upgraded to include the new host functions, this code can be
|
||||
// enabled.
|
||||
// pezsp_io::default_child_storage::storage_kill(prefix, maybe_limit, maybe_cursor)
|
||||
let r = match child_info.child_type() {
|
||||
ChildType::ParentKeyId =>
|
||||
pezsp_io::default_child_storage::storage_kill(child_info.storage_key(), maybe_limit),
|
||||
};
|
||||
use pezsp_io::KillStorageResult::*;
|
||||
let (maybe_cursor, backend) = match r {
|
||||
AllRemoved(db) => (None, db),
|
||||
SomeRemaining(db) => (Some(child_info.storage_key().to_vec()), db),
|
||||
};
|
||||
MultiRemovalResults { maybe_cursor, backend, unique: backend, loops: backend }
|
||||
}
|
||||
|
||||
/// Ensure `key` has no explicit entry in storage.
|
||||
pub fn kill(child_info: &ChildInfo, key: &[u8]) {
|
||||
match child_info.child_type() {
|
||||
ChildType::ParentKeyId => {
|
||||
pezsp_io::default_child_storage::clear(child_info.storage_key(), key);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a Vec of bytes from storage.
|
||||
pub fn get_raw(child_info: &ChildInfo, key: &[u8]) -> Option<Vec<u8>> {
|
||||
match child_info.child_type() {
|
||||
ChildType::ParentKeyId => pezsp_io::default_child_storage::get(child_info.storage_key(), key),
|
||||
}
|
||||
}
|
||||
|
||||
/// Put a raw byte slice into storage.
|
||||
pub fn put_raw(child_info: &ChildInfo, key: &[u8], value: &[u8]) {
|
||||
match child_info.child_type() {
|
||||
ChildType::ParentKeyId =>
|
||||
pezsp_io::default_child_storage::set(child_info.storage_key(), key, value),
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculate current child root value.
|
||||
pub fn root(child_info: &ChildInfo, version: StateVersion) -> Vec<u8> {
|
||||
match child_info.child_type() {
|
||||
ChildType::ParentKeyId =>
|
||||
pezsp_io::default_child_storage::root(child_info.storage_key(), version),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the length in bytes of the value without reading it. `None` if it does not exist.
|
||||
pub fn len(child_info: &ChildInfo, key: &[u8]) -> Option<u32> {
|
||||
match child_info.child_type() {
|
||||
ChildType::ParentKeyId => {
|
||||
let mut buffer = [0; 0];
|
||||
pezsp_io::default_child_storage::read(child_info.storage_key(), key, &mut buffer, 0)
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,656 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::{
|
||||
hash::{ReversibleStorageHasher, StorageHasher},
|
||||
storage::{self, storage_prefix, unhashed, KeyPrefixIterator, PrefixIterator, StorageAppend},
|
||||
Never,
|
||||
};
|
||||
use alloc::vec::Vec;
|
||||
use codec::{Decode, Encode, EncodeLike, FullCodec, FullEncode};
|
||||
|
||||
/// Generator for `StorageDoubleMap` used by `decl_storage`.
|
||||
///
|
||||
/// # 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 `key1_prefix` and `Key1`. And the second part
|
||||
/// is a hash of a `Key2`.
|
||||
///
|
||||
/// Thus value for (key1, key2) is stored at:
|
||||
/// ```nocompile
|
||||
/// Twox128(pezpallet_prefix) ++ Twox128(storage_prefix) ++ Hasher1(encode(key1)) ++ Hasher2(encode(key2))
|
||||
/// ```
|
||||
///
|
||||
/// # Warning
|
||||
///
|
||||
/// If the key1s are not trusted (e.g. can be set by a user), a cryptographic `hasher` such as
|
||||
/// `blake2_256` must be used for Hasher1. Otherwise, other values in storage can be compromised.
|
||||
/// If the key2s are not trusted (e.g. can be set by a user), a cryptographic `hasher` such as
|
||||
/// `blake2_256` must be used for Hasher2. Otherwise, other items in storage with the same first
|
||||
/// key can be compromised.
|
||||
pub trait StorageDoubleMap<K1: FullEncode, K2: FullEncode, V: FullCodec> {
|
||||
/// The type that get/take returns.
|
||||
type Query;
|
||||
|
||||
/// Hasher for the first key.
|
||||
type Hasher1: StorageHasher;
|
||||
|
||||
/// Hasher for the second key.
|
||||
type Hasher2: StorageHasher;
|
||||
|
||||
/// Pallet prefix. Used for generating final key.
|
||||
fn pezpallet_prefix() -> &'static [u8];
|
||||
|
||||
/// Storage prefix. Used for generating final key.
|
||||
fn storage_prefix() -> &'static [u8];
|
||||
|
||||
/// The full prefix; just the hash of `pezpallet_prefix` concatenated to the hash of
|
||||
/// `storage_prefix`.
|
||||
fn prefix_hash() -> [u8; 32];
|
||||
|
||||
/// Convert an optional value retrieved from storage to the type queried.
|
||||
fn from_optional_value_to_query(v: Option<V>) -> Self::Query;
|
||||
|
||||
/// Convert a query to an optional value into storage.
|
||||
fn from_query_to_optional_value(v: Self::Query) -> Option<V>;
|
||||
|
||||
/// Generate the first part of the key used in top storage.
|
||||
fn storage_double_map_final_key1<KArg1>(k1: KArg1) -> Vec<u8>
|
||||
where
|
||||
KArg1: EncodeLike<K1>,
|
||||
{
|
||||
let storage_prefix = storage_prefix(Self::pezpallet_prefix(), Self::storage_prefix());
|
||||
let key_hashed = k1.using_encoded(Self::Hasher1::hash);
|
||||
|
||||
let mut final_key = Vec::with_capacity(storage_prefix.len() + key_hashed.as_ref().len());
|
||||
|
||||
final_key.extend_from_slice(&storage_prefix);
|
||||
final_key.extend_from_slice(key_hashed.as_ref());
|
||||
|
||||
final_key
|
||||
}
|
||||
|
||||
/// Generate the full key used in top storage.
|
||||
fn storage_double_map_final_key<KArg1, KArg2>(k1: KArg1, k2: KArg2) -> Vec<u8>
|
||||
where
|
||||
KArg1: EncodeLike<K1>,
|
||||
KArg2: EncodeLike<K2>,
|
||||
{
|
||||
let storage_prefix = storage_prefix(Self::pezpallet_prefix(), Self::storage_prefix());
|
||||
let key1_hashed = k1.using_encoded(Self::Hasher1::hash);
|
||||
let key2_hashed = k2.using_encoded(Self::Hasher2::hash);
|
||||
|
||||
let mut final_key = Vec::with_capacity(
|
||||
storage_prefix.len() + key1_hashed.as_ref().len() + key2_hashed.as_ref().len(),
|
||||
);
|
||||
|
||||
final_key.extend_from_slice(&storage_prefix);
|
||||
final_key.extend_from_slice(key1_hashed.as_ref());
|
||||
final_key.extend_from_slice(key2_hashed.as_ref());
|
||||
|
||||
final_key
|
||||
}
|
||||
}
|
||||
|
||||
impl<K1, K2, V, G> storage::StorageDoubleMap<K1, K2, V> for G
|
||||
where
|
||||
K1: FullEncode,
|
||||
K2: FullEncode,
|
||||
V: FullCodec,
|
||||
G: StorageDoubleMap<K1, K2, V>,
|
||||
{
|
||||
type Query = G::Query;
|
||||
|
||||
fn hashed_key_for<KArg1, KArg2>(k1: KArg1, k2: KArg2) -> Vec<u8>
|
||||
where
|
||||
KArg1: EncodeLike<K1>,
|
||||
KArg2: EncodeLike<K2>,
|
||||
{
|
||||
Self::storage_double_map_final_key(k1, k2)
|
||||
}
|
||||
|
||||
fn contains_key<KArg1, KArg2>(k1: KArg1, k2: KArg2) -> bool
|
||||
where
|
||||
KArg1: EncodeLike<K1>,
|
||||
KArg2: EncodeLike<K2>,
|
||||
{
|
||||
unhashed::exists(&Self::storage_double_map_final_key(k1, k2))
|
||||
}
|
||||
|
||||
fn get<KArg1, KArg2>(k1: KArg1, k2: KArg2) -> Self::Query
|
||||
where
|
||||
KArg1: EncodeLike<K1>,
|
||||
KArg2: EncodeLike<K2>,
|
||||
{
|
||||
G::from_optional_value_to_query(unhashed::get(&Self::storage_double_map_final_key(k1, k2)))
|
||||
}
|
||||
|
||||
fn try_get<KArg1, KArg2>(k1: KArg1, k2: KArg2) -> Result<V, ()>
|
||||
where
|
||||
KArg1: EncodeLike<K1>,
|
||||
KArg2: EncodeLike<K2>,
|
||||
{
|
||||
unhashed::get(&Self::storage_double_map_final_key(k1, k2)).ok_or(())
|
||||
}
|
||||
|
||||
fn set<KArg1: EncodeLike<K1>, KArg2: EncodeLike<K2>>(k1: KArg1, k2: KArg2, q: Self::Query) {
|
||||
match G::from_query_to_optional_value(q) {
|
||||
Some(v) => Self::insert(k1, k2, v),
|
||||
None => Self::remove(k1, k2),
|
||||
}
|
||||
}
|
||||
|
||||
fn take<KArg1, KArg2>(k1: KArg1, k2: KArg2) -> Self::Query
|
||||
where
|
||||
KArg1: EncodeLike<K1>,
|
||||
KArg2: EncodeLike<K2>,
|
||||
{
|
||||
let final_key = Self::storage_double_map_final_key(k1, k2);
|
||||
|
||||
let value = unhashed::take(&final_key);
|
||||
G::from_optional_value_to_query(value)
|
||||
}
|
||||
|
||||
fn swap<XKArg1, XKArg2, YKArg1, YKArg2>(x_k1: XKArg1, x_k2: XKArg2, y_k1: YKArg1, y_k2: YKArg2)
|
||||
where
|
||||
XKArg1: EncodeLike<K1>,
|
||||
XKArg2: EncodeLike<K2>,
|
||||
YKArg1: EncodeLike<K1>,
|
||||
YKArg2: EncodeLike<K2>,
|
||||
{
|
||||
let final_x_key = Self::storage_double_map_final_key(x_k1, x_k2);
|
||||
let final_y_key = Self::storage_double_map_final_key(y_k1, y_k2);
|
||||
|
||||
let v1 = unhashed::get_raw(&final_x_key);
|
||||
if let Some(val) = unhashed::get_raw(&final_y_key) {
|
||||
unhashed::put_raw(&final_x_key, &val);
|
||||
} else {
|
||||
unhashed::kill(&final_x_key)
|
||||
}
|
||||
if let Some(val) = v1 {
|
||||
unhashed::put_raw(&final_y_key, &val);
|
||||
} else {
|
||||
unhashed::kill(&final_y_key)
|
||||
}
|
||||
}
|
||||
|
||||
fn insert<KArg1, KArg2, VArg>(k1: KArg1, k2: KArg2, val: VArg)
|
||||
where
|
||||
KArg1: EncodeLike<K1>,
|
||||
KArg2: EncodeLike<K2>,
|
||||
VArg: EncodeLike<V>,
|
||||
{
|
||||
unhashed::put(&Self::storage_double_map_final_key(k1, k2), &val)
|
||||
}
|
||||
|
||||
fn remove<KArg1, KArg2>(k1: KArg1, k2: KArg2)
|
||||
where
|
||||
KArg1: EncodeLike<K1>,
|
||||
KArg2: EncodeLike<K2>,
|
||||
{
|
||||
unhashed::kill(&Self::storage_double_map_final_key(k1, k2))
|
||||
}
|
||||
|
||||
fn remove_prefix<KArg1>(k1: KArg1, maybe_limit: Option<u32>) -> pezsp_io::KillStorageResult
|
||||
where
|
||||
KArg1: EncodeLike<K1>,
|
||||
{
|
||||
unhashed::clear_prefix(Self::storage_double_map_final_key1(k1).as_ref(), maybe_limit, None)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn clear_prefix<KArg1>(
|
||||
k1: KArg1,
|
||||
limit: u32,
|
||||
maybe_cursor: Option<&[u8]>,
|
||||
) -> pezsp_io::MultiRemovalResults
|
||||
where
|
||||
KArg1: EncodeLike<K1>,
|
||||
{
|
||||
unhashed::clear_prefix(
|
||||
Self::storage_double_map_final_key1(k1).as_ref(),
|
||||
Some(limit),
|
||||
maybe_cursor,
|
||||
)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn contains_prefix<KArg1>(k1: KArg1) -> bool
|
||||
where
|
||||
KArg1: EncodeLike<K1>,
|
||||
{
|
||||
unhashed::contains_prefixed_key(Self::storage_double_map_final_key1(k1).as_ref())
|
||||
}
|
||||
|
||||
fn iter_prefix_values<KArg1>(k1: KArg1) -> storage::PrefixIterator<V>
|
||||
where
|
||||
KArg1: ?Sized + EncodeLike<K1>,
|
||||
{
|
||||
let prefix = Self::storage_double_map_final_key1(k1);
|
||||
storage::PrefixIterator {
|
||||
prefix: prefix.clone(),
|
||||
previous_key: prefix,
|
||||
drain: false,
|
||||
closure: |_raw_key, mut raw_value| V::decode(&mut raw_value),
|
||||
phantom: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn mutate<KArg1, KArg2, R, F>(k1: KArg1, k2: KArg2, f: F) -> R
|
||||
where
|
||||
KArg1: EncodeLike<K1>,
|
||||
KArg2: EncodeLike<K2>,
|
||||
F: FnOnce(&mut Self::Query) -> R,
|
||||
{
|
||||
Self::try_mutate(k1, k2, |v| Ok::<R, Never>(f(v)))
|
||||
.expect("`Never` can not be constructed; qed")
|
||||
}
|
||||
|
||||
fn mutate_exists<KArg1, KArg2, R, F>(k1: KArg1, k2: KArg2, f: F) -> R
|
||||
where
|
||||
KArg1: EncodeLike<K1>,
|
||||
KArg2: EncodeLike<K2>,
|
||||
F: FnOnce(&mut Option<V>) -> R,
|
||||
{
|
||||
Self::try_mutate_exists(k1, k2, |v| Ok::<R, Never>(f(v)))
|
||||
.expect("`Never` can not be constructed; qed")
|
||||
}
|
||||
|
||||
fn try_mutate<KArg1, KArg2, R, E, F>(k1: KArg1, k2: KArg2, f: F) -> Result<R, E>
|
||||
where
|
||||
KArg1: EncodeLike<K1>,
|
||||
KArg2: EncodeLike<K2>,
|
||||
F: FnOnce(&mut Self::Query) -> Result<R, E>,
|
||||
{
|
||||
let final_key = Self::storage_double_map_final_key(k1, k2);
|
||||
let mut val = G::from_optional_value_to_query(unhashed::get(final_key.as_ref()));
|
||||
|
||||
let ret = f(&mut val);
|
||||
if ret.is_ok() {
|
||||
match G::from_query_to_optional_value(val) {
|
||||
Some(ref val) => unhashed::put(final_key.as_ref(), val),
|
||||
None => unhashed::kill(final_key.as_ref()),
|
||||
}
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
fn try_mutate_exists<KArg1, KArg2, R, E, F>(k1: KArg1, k2: KArg2, f: F) -> Result<R, E>
|
||||
where
|
||||
KArg1: EncodeLike<K1>,
|
||||
KArg2: EncodeLike<K2>,
|
||||
F: FnOnce(&mut Option<V>) -> Result<R, E>,
|
||||
{
|
||||
let final_key = Self::storage_double_map_final_key(k1, k2);
|
||||
let mut val = unhashed::get(final_key.as_ref());
|
||||
|
||||
let ret = f(&mut val);
|
||||
if ret.is_ok() {
|
||||
match val {
|
||||
Some(ref val) => unhashed::put(final_key.as_ref(), val),
|
||||
None => unhashed::kill(final_key.as_ref()),
|
||||
}
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
fn append<Item, EncodeLikeItem, KArg1, KArg2>(k1: KArg1, k2: KArg2, item: EncodeLikeItem)
|
||||
where
|
||||
KArg1: EncodeLike<K1>,
|
||||
KArg2: EncodeLike<K2>,
|
||||
Item: Encode,
|
||||
EncodeLikeItem: EncodeLike<Item>,
|
||||
V: StorageAppend<Item>,
|
||||
{
|
||||
let final_key = Self::storage_double_map_final_key(k1, k2);
|
||||
pezsp_io::storage::append(&final_key, item.encode());
|
||||
}
|
||||
|
||||
fn migrate_keys<
|
||||
OldHasher1: StorageHasher,
|
||||
OldHasher2: StorageHasher,
|
||||
KeyArg1: EncodeLike<K1>,
|
||||
KeyArg2: EncodeLike<K2>,
|
||||
>(
|
||||
key1: KeyArg1,
|
||||
key2: KeyArg2,
|
||||
) -> Option<V> {
|
||||
let old_key = {
|
||||
let storage_prefix = storage_prefix(Self::pezpallet_prefix(), Self::storage_prefix());
|
||||
|
||||
let key1_hashed = key1.using_encoded(OldHasher1::hash);
|
||||
let key2_hashed = key2.using_encoded(OldHasher2::hash);
|
||||
|
||||
let mut final_key = Vec::with_capacity(
|
||||
storage_prefix.len() + key1_hashed.as_ref().len() + key2_hashed.as_ref().len(),
|
||||
);
|
||||
|
||||
final_key.extend_from_slice(&storage_prefix);
|
||||
final_key.extend_from_slice(key1_hashed.as_ref());
|
||||
final_key.extend_from_slice(key2_hashed.as_ref());
|
||||
|
||||
final_key
|
||||
};
|
||||
unhashed::take(old_key.as_ref()).inspect(|value| {
|
||||
unhashed::put(Self::storage_double_map_final_key(key1, key2).as_ref(), &value);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<K1: FullCodec, K2: FullCodec, V: FullCodec, G: StorageDoubleMap<K1, K2, V>>
|
||||
storage::IterableStorageDoubleMap<K1, K2, V> for G
|
||||
where
|
||||
G::Hasher1: ReversibleStorageHasher,
|
||||
G::Hasher2: ReversibleStorageHasher,
|
||||
{
|
||||
type PartialKeyIterator = KeyPrefixIterator<K2>;
|
||||
type PrefixIterator = PrefixIterator<(K2, V)>;
|
||||
type FullKeyIterator = KeyPrefixIterator<(K1, K2)>;
|
||||
type Iterator = PrefixIterator<(K1, K2, V)>;
|
||||
|
||||
fn iter_prefix(k1: impl EncodeLike<K1>) -> Self::PrefixIterator {
|
||||
let prefix = G::storage_double_map_final_key1(k1);
|
||||
Self::PrefixIterator {
|
||||
prefix: prefix.clone(),
|
||||
previous_key: prefix,
|
||||
drain: false,
|
||||
closure: |raw_key_without_prefix, mut raw_value| {
|
||||
let mut key_material = G::Hasher2::reverse(raw_key_without_prefix);
|
||||
Ok((K2::decode(&mut key_material)?, V::decode(&mut raw_value)?))
|
||||
},
|
||||
phantom: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn iter_prefix_from(
|
||||
k1: impl EncodeLike<K1>,
|
||||
starting_raw_key: Vec<u8>,
|
||||
) -> Self::PrefixIterator {
|
||||
let mut iter = Self::iter_prefix(k1);
|
||||
iter.set_last_raw_key(starting_raw_key);
|
||||
iter
|
||||
}
|
||||
|
||||
fn iter_key_prefix(k1: impl EncodeLike<K1>) -> Self::PartialKeyIterator {
|
||||
let prefix = G::storage_double_map_final_key1(k1);
|
||||
Self::PartialKeyIterator {
|
||||
prefix: prefix.clone(),
|
||||
previous_key: prefix,
|
||||
drain: false,
|
||||
closure: |raw_key_without_prefix| {
|
||||
let mut key_material = G::Hasher2::reverse(raw_key_without_prefix);
|
||||
K2::decode(&mut key_material)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn iter_key_prefix_from(
|
||||
k1: impl EncodeLike<K1>,
|
||||
starting_raw_key: Vec<u8>,
|
||||
) -> Self::PartialKeyIterator {
|
||||
let mut iter = Self::iter_key_prefix(k1);
|
||||
iter.set_last_raw_key(starting_raw_key);
|
||||
iter
|
||||
}
|
||||
|
||||
fn drain_prefix(k1: impl EncodeLike<K1>) -> Self::PrefixIterator {
|
||||
let mut iterator = Self::iter_prefix(k1);
|
||||
iterator.drain = true;
|
||||
iterator
|
||||
}
|
||||
|
||||
fn iter() -> Self::Iterator {
|
||||
let prefix = G::prefix_hash().to_vec();
|
||||
Self::Iterator {
|
||||
prefix: prefix.clone(),
|
||||
previous_key: prefix,
|
||||
drain: false,
|
||||
closure: |raw_key_without_prefix, mut raw_value| {
|
||||
let mut k1_k2_material = G::Hasher1::reverse(raw_key_without_prefix);
|
||||
let k1 = K1::decode(&mut k1_k2_material)?;
|
||||
let mut k2_material = G::Hasher2::reverse(k1_k2_material);
|
||||
let k2 = K2::decode(&mut k2_material)?;
|
||||
Ok((k1, k2, V::decode(&mut raw_value)?))
|
||||
},
|
||||
phantom: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn iter_from(starting_raw_key: Vec<u8>) -> Self::Iterator {
|
||||
let mut iter = Self::iter();
|
||||
iter.set_last_raw_key(starting_raw_key);
|
||||
iter
|
||||
}
|
||||
|
||||
fn iter_keys() -> Self::FullKeyIterator {
|
||||
let prefix = G::prefix_hash().to_vec();
|
||||
Self::FullKeyIterator {
|
||||
prefix: prefix.clone(),
|
||||
previous_key: prefix,
|
||||
drain: false,
|
||||
closure: |raw_key_without_prefix| {
|
||||
let mut k1_k2_material = G::Hasher1::reverse(raw_key_without_prefix);
|
||||
let k1 = K1::decode(&mut k1_k2_material)?;
|
||||
let mut k2_material = G::Hasher2::reverse(k1_k2_material);
|
||||
let k2 = K2::decode(&mut k2_material)?;
|
||||
Ok((k1, k2))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn iter_keys_from(starting_raw_key: Vec<u8>) -> Self::FullKeyIterator {
|
||||
let mut iter = Self::iter_keys();
|
||||
iter.set_last_raw_key(starting_raw_key);
|
||||
iter
|
||||
}
|
||||
|
||||
fn drain() -> Self::Iterator {
|
||||
let mut iterator = Self::iter();
|
||||
iterator.drain = true;
|
||||
iterator
|
||||
}
|
||||
|
||||
fn translate<O: Decode, F: FnMut(K1, K2, O) -> Option<V>>(mut f: F) {
|
||||
let prefix = G::prefix_hash().to_vec();
|
||||
let mut previous_key = prefix.clone();
|
||||
while let Some(next) =
|
||||
pezsp_io::storage::next_key(&previous_key).filter(|n| n.starts_with(&prefix))
|
||||
{
|
||||
previous_key = next;
|
||||
let value = match unhashed::get::<O>(&previous_key) {
|
||||
Some(value) => value,
|
||||
None => {
|
||||
log::error!("Invalid translate: fail to decode old value");
|
||||
continue;
|
||||
},
|
||||
};
|
||||
let mut key_material = G::Hasher1::reverse(&previous_key[prefix.len()..]);
|
||||
let key1 = match K1::decode(&mut key_material) {
|
||||
Ok(key1) => key1,
|
||||
Err(_) => {
|
||||
log::error!("Invalid translate: fail to decode key1");
|
||||
continue;
|
||||
},
|
||||
};
|
||||
|
||||
let mut key2_material = G::Hasher2::reverse(key_material);
|
||||
let key2 = match K2::decode(&mut key2_material) {
|
||||
Ok(key2) => key2,
|
||||
Err(_) => {
|
||||
log::error!("Invalid translate: fail to decode key2");
|
||||
continue;
|
||||
},
|
||||
};
|
||||
|
||||
match f(key1, key2, value) {
|
||||
Some(new) => unhashed::put::<V>(&previous_key, &new),
|
||||
None => unhashed::kill(&previous_key),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Test iterators for StorageDoubleMap
|
||||
#[cfg(test)]
|
||||
mod test_iterators {
|
||||
use crate::{
|
||||
hash::StorageHasher,
|
||||
storage::{
|
||||
generator::{tests::*, StorageDoubleMap},
|
||||
unhashed,
|
||||
},
|
||||
};
|
||||
use alloc::vec;
|
||||
use codec::Encode;
|
||||
|
||||
#[test]
|
||||
fn double_map_iter_from() {
|
||||
pezsp_io::TestExternalities::default().execute_with(|| {
|
||||
use crate::hash::Identity;
|
||||
#[crate::storage_alias]
|
||||
type MyDoubleMap = StorageDoubleMap<MyModule, Identity, u64, Identity, u64, u64>;
|
||||
|
||||
MyDoubleMap::insert(1, 10, 100);
|
||||
MyDoubleMap::insert(1, 21, 201);
|
||||
MyDoubleMap::insert(1, 31, 301);
|
||||
MyDoubleMap::insert(1, 41, 401);
|
||||
MyDoubleMap::insert(2, 20, 200);
|
||||
MyDoubleMap::insert(3, 30, 300);
|
||||
MyDoubleMap::insert(4, 40, 400);
|
||||
MyDoubleMap::insert(5, 50, 500);
|
||||
|
||||
let starting_raw_key = MyDoubleMap::storage_double_map_final_key(1, 21);
|
||||
let iter = MyDoubleMap::iter_key_prefix_from(1, starting_raw_key);
|
||||
assert_eq!(iter.collect::<Vec<_>>(), vec![31, 41]);
|
||||
|
||||
let starting_raw_key = MyDoubleMap::storage_double_map_final_key(1, 31);
|
||||
let iter = MyDoubleMap::iter_prefix_from(1, starting_raw_key);
|
||||
assert_eq!(iter.collect::<Vec<_>>(), vec![(41, 401)]);
|
||||
|
||||
let starting_raw_key = MyDoubleMap::storage_double_map_final_key(2, 20);
|
||||
let iter = MyDoubleMap::iter_keys_from(starting_raw_key);
|
||||
assert_eq!(iter.collect::<Vec<_>>(), vec![(3, 30), (4, 40), (5, 50)]);
|
||||
|
||||
let starting_raw_key = MyDoubleMap::storage_double_map_final_key(3, 30);
|
||||
let iter = MyDoubleMap::iter_from(starting_raw_key);
|
||||
assert_eq!(iter.collect::<Vec<_>>(), vec![(4, 40, 400), (5, 50, 500)]);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn double_map_reversible_reversible_iteration() {
|
||||
pezsp_io::TestExternalities::default().execute_with(|| {
|
||||
type DoubleMap = self::pezframe_system::DoubleMap<Runtime>;
|
||||
|
||||
// All map iterator
|
||||
let prefix = DoubleMap::prefix_hash().to_vec();
|
||||
|
||||
unhashed::put(&key_before_prefix(prefix.clone()), &1u64);
|
||||
unhashed::put(&key_after_prefix(prefix.clone()), &1u64);
|
||||
|
||||
for i in 0..4 {
|
||||
DoubleMap::insert(i as u16, i as u32, i as u64);
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
DoubleMap::iter().collect::<Vec<_>>(),
|
||||
vec![(3, 3, 3), (0, 0, 0), (2, 2, 2), (1, 1, 1)],
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
DoubleMap::iter_keys().collect::<Vec<_>>(),
|
||||
vec![(3, 3), (0, 0), (2, 2), (1, 1)],
|
||||
);
|
||||
|
||||
assert_eq!(DoubleMap::iter_values().collect::<Vec<_>>(), vec![3, 0, 2, 1]);
|
||||
|
||||
assert_eq!(
|
||||
DoubleMap::drain().collect::<Vec<_>>(),
|
||||
vec![(3, 3, 3), (0, 0, 0), (2, 2, 2), (1, 1, 1)],
|
||||
);
|
||||
|
||||
assert_eq!(DoubleMap::iter().collect::<Vec<_>>(), vec![]);
|
||||
assert_eq!(unhashed::get(&key_before_prefix(prefix.clone())), Some(1u64));
|
||||
assert_eq!(unhashed::get(&key_after_prefix(prefix.clone())), Some(1u64));
|
||||
|
||||
// Prefix iterator
|
||||
let k1 = 3 << 8;
|
||||
let prefix = DoubleMap::storage_double_map_final_key1(k1);
|
||||
|
||||
unhashed::put(&key_before_prefix(prefix.clone()), &1u64);
|
||||
unhashed::put(&key_after_prefix(prefix.clone()), &1u64);
|
||||
|
||||
for i in 0..4 {
|
||||
DoubleMap::insert(k1, i as u32, i as u64);
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
DoubleMap::iter_prefix(k1).collect::<Vec<_>>(),
|
||||
vec![(1, 1), (2, 2), (0, 0), (3, 3)],
|
||||
);
|
||||
|
||||
assert_eq!(DoubleMap::iter_key_prefix(k1).collect::<Vec<_>>(), vec![1, 2, 0, 3]);
|
||||
|
||||
assert_eq!(DoubleMap::iter_prefix_values(k1).collect::<Vec<_>>(), vec![1, 2, 0, 3]);
|
||||
|
||||
assert_eq!(
|
||||
DoubleMap::drain_prefix(k1).collect::<Vec<_>>(),
|
||||
vec![(1, 1), (2, 2), (0, 0), (3, 3)],
|
||||
);
|
||||
|
||||
assert_eq!(DoubleMap::iter_prefix(k1).collect::<Vec<_>>(), vec![]);
|
||||
assert_eq!(unhashed::get(&key_before_prefix(prefix.clone())), Some(1u64));
|
||||
assert_eq!(unhashed::get(&key_after_prefix(prefix.clone())), Some(1u64));
|
||||
|
||||
// Translate
|
||||
let prefix = DoubleMap::prefix_hash().to_vec();
|
||||
|
||||
unhashed::put(&key_before_prefix(prefix.clone()), &1u64);
|
||||
unhashed::put(&key_after_prefix(prefix.clone()), &1u64);
|
||||
for i in 0..4 {
|
||||
DoubleMap::insert(i as u16, i as u32, i as u64);
|
||||
}
|
||||
|
||||
// Wrong key1
|
||||
unhashed::put(&[prefix.clone(), vec![1, 2, 3]].concat(), &3u64.encode());
|
||||
|
||||
// Wrong key2
|
||||
unhashed::put(
|
||||
&[prefix.clone(), crate::Blake2_128Concat::hash(&1u16.encode())].concat(),
|
||||
&3u64.encode(),
|
||||
);
|
||||
|
||||
// Wrong value
|
||||
unhashed::put(
|
||||
&[
|
||||
prefix.clone(),
|
||||
crate::Blake2_128Concat::hash(&1u16.encode()),
|
||||
crate::Twox64Concat::hash(&2u32.encode()),
|
||||
]
|
||||
.concat(),
|
||||
&vec![1],
|
||||
);
|
||||
|
||||
DoubleMap::translate(|_k1, _k2, v: u64| Some(v * 2));
|
||||
assert_eq!(
|
||||
DoubleMap::iter().collect::<Vec<_>>(),
|
||||
vec![(3, 3, 6), (0, 0, 0), (2, 2, 4), (1, 1, 2)],
|
||||
);
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,465 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::{
|
||||
hash::{ReversibleStorageHasher, StorageHasher},
|
||||
storage::{self, storage_prefix, unhashed, KeyPrefixIterator, PrefixIterator, StorageAppend},
|
||||
Never,
|
||||
};
|
||||
use alloc::vec::Vec;
|
||||
use codec::{Decode, Encode, EncodeLike, FullCodec, FullEncode};
|
||||
|
||||
/// Generator for `StorageMap` used by `decl_storage`.
|
||||
///
|
||||
/// By default each key value is stored at:
|
||||
/// ```nocompile
|
||||
/// Twox128(pezpallet_prefix) ++ Twox128(storage_prefix) ++ Hasher(encode(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.
|
||||
pub trait StorageMap<K: FullEncode, V: FullCodec> {
|
||||
/// The type that get/take returns.
|
||||
type Query;
|
||||
|
||||
/// Hasher. Used for generating final key.
|
||||
type Hasher: StorageHasher;
|
||||
|
||||
/// Pallet prefix. Used for generating final key.
|
||||
fn pezpallet_prefix() -> &'static [u8];
|
||||
|
||||
/// Storage prefix. Used for generating final key.
|
||||
fn storage_prefix() -> &'static [u8];
|
||||
|
||||
/// The full prefix; just the hash of `pezpallet_prefix` concatenated to the hash of
|
||||
/// `storage_prefix`.
|
||||
fn prefix_hash() -> [u8; 32];
|
||||
|
||||
/// Convert an optional value retrieved from storage to the type queried.
|
||||
fn from_optional_value_to_query(v: Option<V>) -> Self::Query;
|
||||
|
||||
/// Convert a query to an optional value into storage.
|
||||
fn from_query_to_optional_value(v: Self::Query) -> Option<V>;
|
||||
|
||||
/// Generate the full key used in top storage.
|
||||
fn storage_map_final_key<KeyArg>(key: KeyArg) -> Vec<u8>
|
||||
where
|
||||
KeyArg: EncodeLike<K>,
|
||||
{
|
||||
let storage_prefix = storage_prefix(Self::pezpallet_prefix(), Self::storage_prefix());
|
||||
let key_hashed = key.using_encoded(Self::Hasher::hash);
|
||||
|
||||
let mut final_key = Vec::with_capacity(storage_prefix.len() + key_hashed.as_ref().len());
|
||||
|
||||
final_key.extend_from_slice(&storage_prefix);
|
||||
final_key.extend_from_slice(key_hashed.as_ref());
|
||||
|
||||
final_key
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: FullCodec, V: FullCodec, G: StorageMap<K, V>> storage::IterableStorageMap<K, V> for G
|
||||
where
|
||||
G::Hasher: ReversibleStorageHasher,
|
||||
{
|
||||
type Iterator = PrefixIterator<(K, V)>;
|
||||
type KeyIterator = KeyPrefixIterator<K>;
|
||||
|
||||
/// Enumerate all elements in the map.
|
||||
fn iter() -> Self::Iterator {
|
||||
let prefix = G::prefix_hash().to_vec();
|
||||
PrefixIterator {
|
||||
prefix: prefix.clone(),
|
||||
previous_key: prefix,
|
||||
drain: false,
|
||||
closure: |raw_key_without_prefix, mut raw_value| {
|
||||
let mut key_material = G::Hasher::reverse(raw_key_without_prefix);
|
||||
Ok((K::decode(&mut key_material)?, V::decode(&mut raw_value)?))
|
||||
},
|
||||
phantom: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Enumerate all elements in the map after a given key.
|
||||
fn iter_from(starting_raw_key: Vec<u8>) -> Self::Iterator {
|
||||
let mut iter = Self::iter();
|
||||
iter.set_last_raw_key(starting_raw_key);
|
||||
iter
|
||||
}
|
||||
|
||||
/// Enumerate all keys in the map.
|
||||
fn iter_keys() -> Self::KeyIterator {
|
||||
let prefix = G::prefix_hash().to_vec();
|
||||
KeyPrefixIterator {
|
||||
prefix: prefix.clone(),
|
||||
previous_key: prefix,
|
||||
drain: false,
|
||||
closure: |raw_key_without_prefix| {
|
||||
let mut key_material = G::Hasher::reverse(raw_key_without_prefix);
|
||||
K::decode(&mut key_material)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Enumerate all keys in the map after a given key.
|
||||
fn iter_keys_from(starting_raw_key: Vec<u8>) -> Self::KeyIterator {
|
||||
let mut iter = Self::iter_keys();
|
||||
iter.set_last_raw_key(starting_raw_key);
|
||||
iter
|
||||
}
|
||||
|
||||
/// Enumerate all elements in the map.
|
||||
fn drain() -> Self::Iterator {
|
||||
let mut iterator = Self::iter();
|
||||
iterator.drain = true;
|
||||
iterator
|
||||
}
|
||||
|
||||
fn translate<O: Decode, F: FnMut(K, O) -> Option<V>>(mut f: F) {
|
||||
let mut previous_key = None;
|
||||
loop {
|
||||
previous_key = Self::translate_next(previous_key, &mut f);
|
||||
if previous_key.is_none() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn translate_next<O: Decode, F: FnMut(K, O) -> Option<V>>(
|
||||
previous_key: Option<Vec<u8>>,
|
||||
mut f: F,
|
||||
) -> Option<Vec<u8>> {
|
||||
let prefix = G::prefix_hash().to_vec();
|
||||
let previous_key = previous_key.unwrap_or_else(|| prefix.clone());
|
||||
|
||||
let current_key =
|
||||
pezsp_io::storage::next_key(&previous_key).filter(|n| n.starts_with(&prefix))?;
|
||||
|
||||
let value = match unhashed::get::<O>(¤t_key) {
|
||||
Some(value) => value,
|
||||
None => {
|
||||
crate::defensive!(
|
||||
"Invalid translation: failed to decode old value for key",
|
||||
array_bytes::bytes2hex("0x", ¤t_key)
|
||||
);
|
||||
return Some(current_key);
|
||||
},
|
||||
};
|
||||
|
||||
let mut key_material = G::Hasher::reverse(¤t_key[prefix.len()..]);
|
||||
let key = match K::decode(&mut key_material) {
|
||||
Ok(key) => key,
|
||||
Err(_) => {
|
||||
crate::defensive!(
|
||||
"Invalid translation: failed to decode key",
|
||||
array_bytes::bytes2hex("0x", ¤t_key)
|
||||
);
|
||||
return Some(current_key);
|
||||
},
|
||||
};
|
||||
|
||||
match f(key, value) {
|
||||
Some(new) => unhashed::put::<V>(¤t_key, &new),
|
||||
None => unhashed::kill(¤t_key),
|
||||
}
|
||||
|
||||
Some(current_key)
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: FullEncode, V: FullCodec, G: StorageMap<K, V>> storage::StorageMap<K, V> for G {
|
||||
type Query = G::Query;
|
||||
|
||||
fn hashed_key_for<KeyArg: EncodeLike<K>>(key: KeyArg) -> Vec<u8> {
|
||||
Self::storage_map_final_key(key)
|
||||
}
|
||||
|
||||
fn swap<KeyArg1: EncodeLike<K>, KeyArg2: EncodeLike<K>>(key1: KeyArg1, key2: KeyArg2) {
|
||||
let k1 = Self::storage_map_final_key(key1);
|
||||
let k2 = Self::storage_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 contains_key<KeyArg: EncodeLike<K>>(key: KeyArg) -> bool {
|
||||
unhashed::exists(Self::storage_map_final_key(key).as_ref())
|
||||
}
|
||||
|
||||
fn get<KeyArg: EncodeLike<K>>(key: KeyArg) -> Self::Query {
|
||||
G::from_optional_value_to_query(unhashed::get(Self::storage_map_final_key(key).as_ref()))
|
||||
}
|
||||
|
||||
fn try_get<KeyArg: EncodeLike<K>>(key: KeyArg) -> Result<V, ()> {
|
||||
unhashed::get(Self::storage_map_final_key(key).as_ref()).ok_or(())
|
||||
}
|
||||
|
||||
fn set<KeyArg: EncodeLike<K>>(key: KeyArg, q: Self::Query) {
|
||||
match G::from_query_to_optional_value(q) {
|
||||
Some(v) => Self::insert(key, v),
|
||||
None => Self::remove(key),
|
||||
}
|
||||
}
|
||||
|
||||
fn insert<KeyArg: EncodeLike<K>, ValArg: EncodeLike<V>>(key: KeyArg, val: ValArg) {
|
||||
unhashed::put(Self::storage_map_final_key(key).as_ref(), &val)
|
||||
}
|
||||
|
||||
fn remove<KeyArg: EncodeLike<K>>(key: KeyArg) {
|
||||
unhashed::kill(Self::storage_map_final_key(key).as_ref())
|
||||
}
|
||||
|
||||
fn mutate<KeyArg: EncodeLike<K>, R, F: FnOnce(&mut Self::Query) -> R>(key: KeyArg, f: F) -> R {
|
||||
Self::try_mutate(key, |v| Ok::<R, Never>(f(v)))
|
||||
.expect("`Never` can not be constructed; qed")
|
||||
}
|
||||
|
||||
fn mutate_exists<KeyArg: EncodeLike<K>, R, F: FnOnce(&mut Option<V>) -> R>(
|
||||
key: KeyArg,
|
||||
f: F,
|
||||
) -> R {
|
||||
Self::try_mutate_exists(key, |v| Ok::<R, Never>(f(v)))
|
||||
.expect("`Never` can not be constructed; qed")
|
||||
}
|
||||
|
||||
fn try_mutate<KeyArg: EncodeLike<K>, R, E, F: FnOnce(&mut Self::Query) -> Result<R, E>>(
|
||||
key: KeyArg,
|
||||
f: F,
|
||||
) -> Result<R, E> {
|
||||
let final_key = Self::storage_map_final_key(key);
|
||||
let mut val = G::from_optional_value_to_query(unhashed::get(final_key.as_ref()));
|
||||
|
||||
let ret = f(&mut val);
|
||||
if ret.is_ok() {
|
||||
match G::from_query_to_optional_value(val) {
|
||||
Some(ref val) => unhashed::put(final_key.as_ref(), &val),
|
||||
None => unhashed::kill(final_key.as_ref()),
|
||||
}
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
fn try_mutate_exists<KeyArg: EncodeLike<K>, R, E, F: FnOnce(&mut Option<V>) -> Result<R, E>>(
|
||||
key: KeyArg,
|
||||
f: F,
|
||||
) -> Result<R, E> {
|
||||
let final_key = Self::storage_map_final_key(key);
|
||||
let mut val = unhashed::get(final_key.as_ref());
|
||||
|
||||
let ret = f(&mut val);
|
||||
if ret.is_ok() {
|
||||
match val {
|
||||
Some(ref val) => unhashed::put(final_key.as_ref(), &val),
|
||||
None => unhashed::kill(final_key.as_ref()),
|
||||
}
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
fn take<KeyArg: EncodeLike<K>>(key: KeyArg) -> Self::Query {
|
||||
let key = Self::storage_map_final_key(key);
|
||||
let value = unhashed::take(key.as_ref());
|
||||
G::from_optional_value_to_query(value)
|
||||
}
|
||||
|
||||
fn append<Item, EncodeLikeItem, EncodeLikeKey>(key: EncodeLikeKey, item: EncodeLikeItem)
|
||||
where
|
||||
EncodeLikeKey: EncodeLike<K>,
|
||||
Item: Encode,
|
||||
EncodeLikeItem: EncodeLike<Item>,
|
||||
V: StorageAppend<Item>,
|
||||
{
|
||||
let key = Self::storage_map_final_key(key);
|
||||
pezsp_io::storage::append(&key, item.encode());
|
||||
}
|
||||
|
||||
fn migrate_key<OldHasher: StorageHasher, KeyArg: EncodeLike<K>>(key: KeyArg) -> Option<V> {
|
||||
let old_key = {
|
||||
let storage_prefix = storage_prefix(Self::pezpallet_prefix(), Self::storage_prefix());
|
||||
let key_hashed = key.using_encoded(OldHasher::hash);
|
||||
|
||||
let mut final_key =
|
||||
Vec::with_capacity(storage_prefix.len() + key_hashed.as_ref().len());
|
||||
|
||||
final_key.extend_from_slice(&storage_prefix);
|
||||
final_key.extend_from_slice(key_hashed.as_ref());
|
||||
|
||||
final_key
|
||||
};
|
||||
unhashed::take(old_key.as_ref()).inspect(|value| {
|
||||
unhashed::put(Self::storage_map_final_key(key).as_ref(), &value);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Test iterators for StorageMap
|
||||
#[cfg(test)]
|
||||
mod test_iterators {
|
||||
use crate::{
|
||||
hash::StorageHasher,
|
||||
storage::{
|
||||
generator::{tests::*, StorageMap},
|
||||
unhashed,
|
||||
},
|
||||
};
|
||||
use alloc::vec;
|
||||
use codec::Encode;
|
||||
|
||||
#[test]
|
||||
fn map_iter_from() {
|
||||
pezsp_io::TestExternalities::default().execute_with(|| {
|
||||
use crate::hash::Identity;
|
||||
#[crate::storage_alias]
|
||||
type MyMap = StorageMap<MyModule, Identity, u64, u64>;
|
||||
|
||||
MyMap::insert(1, 10);
|
||||
MyMap::insert(2, 20);
|
||||
MyMap::insert(3, 30);
|
||||
MyMap::insert(4, 40);
|
||||
MyMap::insert(5, 50);
|
||||
|
||||
let starting_raw_key = MyMap::storage_map_final_key(3);
|
||||
let iter = MyMap::iter_from(starting_raw_key);
|
||||
assert_eq!(iter.collect::<Vec<_>>(), vec![(4, 40), (5, 50)]);
|
||||
|
||||
let starting_raw_key = MyMap::storage_map_final_key(2);
|
||||
let iter = MyMap::iter_keys_from(starting_raw_key);
|
||||
assert_eq!(iter.collect::<Vec<_>>(), vec![3, 4, 5]);
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn map_translate_with_bad_key_in_debug_mode() {
|
||||
pezsp_io::TestExternalities::default().execute_with(|| {
|
||||
type Map = self::pezframe_system::Map<Runtime>;
|
||||
let prefix = Map::prefix_hash().to_vec();
|
||||
|
||||
// Wrong key
|
||||
unhashed::put(&[prefix.clone(), vec![1, 2, 3]].concat(), &3u64.encode());
|
||||
|
||||
// debug_assert should cause a
|
||||
Map::translate(|_k1, v: u64| Some(v * 2));
|
||||
assert_eq!(Map::iter().collect::<Vec<_>>(), vec![(3, 6), (0, 0), (2, 4), (1, 2)]);
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn map_translate_with_bad_value_in_debug_mode() {
|
||||
pezsp_io::TestExternalities::default().execute_with(|| {
|
||||
type Map = self::pezframe_system::Map<Runtime>;
|
||||
let prefix = Map::prefix_hash().to_vec();
|
||||
|
||||
// Wrong value
|
||||
unhashed::put(
|
||||
&[prefix.clone(), crate::Blake2_128Concat::hash(&6u16.encode())].concat(),
|
||||
&vec![1],
|
||||
);
|
||||
|
||||
Map::translate(|_k1, v: u64| Some(v * 2));
|
||||
assert_eq!(Map::iter().collect::<Vec<_>>(), vec![(3, 6), (0, 0), (2, 4), (1, 2)]);
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[test]
|
||||
fn map_translate_with_bad_key_in_production_mode() {
|
||||
pezsp_io::TestExternalities::default().execute_with(|| {
|
||||
type Map = self::pezframe_system::Map<Runtime>;
|
||||
let prefix = Map::prefix_hash().to_vec();
|
||||
|
||||
// Wrong key
|
||||
unhashed::put(&[prefix.clone(), vec![1, 2, 3]].concat(), &3u64.encode());
|
||||
|
||||
Map::translate(|_k1, v: u64| Some(v * 2));
|
||||
assert_eq!(Map::iter().collect::<Vec<_>>(), vec![]);
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[test]
|
||||
fn map_translate_with_bad_value_in_production_mode() {
|
||||
pezsp_io::TestExternalities::default().execute_with(|| {
|
||||
type Map = self::pezframe_system::Map<Runtime>;
|
||||
let prefix = Map::prefix_hash().to_vec();
|
||||
|
||||
// Wrong value
|
||||
unhashed::put(
|
||||
&[prefix.clone(), crate::Blake2_128Concat::hash(&6u16.encode())].concat(),
|
||||
&vec![1],
|
||||
);
|
||||
|
||||
Map::translate(|_k1, v: u64| Some(v * 2));
|
||||
assert_eq!(Map::iter().collect::<Vec<_>>(), vec![]);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn map_reversible_reversible_iteration() {
|
||||
pezsp_io::TestExternalities::default().execute_with(|| {
|
||||
type Map = self::pezframe_system::Map<Runtime>;
|
||||
|
||||
// All map iterator
|
||||
let prefix = Map::prefix_hash().to_vec();
|
||||
|
||||
unhashed::put(&key_before_prefix(prefix.clone()), &1u64);
|
||||
unhashed::put(&key_after_prefix(prefix.clone()), &1u64);
|
||||
|
||||
for i in 0..4 {
|
||||
Map::insert(i as u16, i as u64);
|
||||
}
|
||||
|
||||
assert_eq!(Map::iter().collect::<Vec<_>>(), vec![(3, 3), (0, 0), (2, 2), (1, 1)]);
|
||||
|
||||
assert_eq!(Map::iter_keys().collect::<Vec<_>>(), vec![3, 0, 2, 1]);
|
||||
|
||||
assert_eq!(Map::iter_values().collect::<Vec<_>>(), vec![3, 0, 2, 1]);
|
||||
|
||||
assert_eq!(Map::drain().collect::<Vec<_>>(), vec![(3, 3), (0, 0), (2, 2), (1, 1)]);
|
||||
|
||||
assert_eq!(Map::iter().collect::<Vec<_>>(), vec![]);
|
||||
assert_eq!(unhashed::get(&key_before_prefix(prefix.clone())), Some(1u64));
|
||||
assert_eq!(unhashed::get(&key_after_prefix(prefix.clone())), Some(1u64));
|
||||
|
||||
// Translate
|
||||
let prefix = Map::prefix_hash().to_vec();
|
||||
|
||||
unhashed::put(&key_before_prefix(prefix.clone()), &1u64);
|
||||
unhashed::put(&key_after_prefix(prefix.clone()), &1u64);
|
||||
for i in 0..4 {
|
||||
Map::insert(i as u16, i as u64);
|
||||
}
|
||||
|
||||
Map::translate(|_k1, v: u64| Some(v * 2));
|
||||
assert_eq!(Map::iter().collect::<Vec<_>>(), vec![(3, 6), (0, 0), (2, 4), (1, 2)]);
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Generators are a set of trait on which storage traits are implemented.
|
||||
//!
|
||||
//! (i.e. implementing the generator for StorageValue on a type will automatically derive the
|
||||
//! implementation of StorageValue for this type).
|
||||
//!
|
||||
//! They are used by `decl_storage`.
|
||||
//!
|
||||
//! This is internal api and is subject to change.
|
||||
|
||||
pub(crate) mod double_map;
|
||||
pub(crate) mod map;
|
||||
pub(crate) mod nmap;
|
||||
pub(crate) mod value;
|
||||
|
||||
pub use double_map::StorageDoubleMap;
|
||||
pub use map::StorageMap;
|
||||
pub use nmap::StorageNMap;
|
||||
pub use value::StorageValue;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alloc::vec::Vec;
|
||||
use codec::Encode;
|
||||
use pezsp_io::TestExternalities;
|
||||
use pezsp_runtime::{generic, traits::BlakeTwo256, BuildStorage};
|
||||
|
||||
use crate::{
|
||||
assert_noop, assert_ok,
|
||||
storage::{generator::StorageValue, unhashed},
|
||||
};
|
||||
|
||||
#[crate::pallet]
|
||||
pub mod pezframe_system {
|
||||
#[allow(unused)]
|
||||
use super::{pezframe_system, pezframe_system::pezpallet_prelude::*};
|
||||
pub use crate::dispatch::RawOrigin;
|
||||
use crate::pezpallet_prelude::*;
|
||||
|
||||
#[pallet::pallet]
|
||||
pub struct Pallet<T>(_);
|
||||
|
||||
#[pallet::config]
|
||||
#[pallet::disable_pezframe_system_supertrait_check]
|
||||
pub trait Config: 'static {
|
||||
type Block: pezsp_runtime::traits::Block;
|
||||
type AccountId;
|
||||
type BaseCallFilter: crate::traits::Contains<Self::RuntimeCall>;
|
||||
type RuntimeOrigin;
|
||||
type RuntimeCall;
|
||||
type RuntimeTask;
|
||||
type PalletInfo: crate::traits::PalletInfo;
|
||||
type DbWeight: Get<crate::weights::RuntimeDbWeight>;
|
||||
}
|
||||
|
||||
#[pallet::origin]
|
||||
pub type Origin<T> = RawOrigin<<T as Config>::AccountId>;
|
||||
|
||||
#[pallet::error]
|
||||
pub enum Error<T> {
|
||||
/// Required by construct_runtime
|
||||
CallFiltered,
|
||||
}
|
||||
|
||||
#[pallet::call]
|
||||
impl<T: Config> Pallet<T> {}
|
||||
|
||||
#[pallet::storage]
|
||||
pub type Value<T> = StorageValue<_, (u64, u64), ValueQuery>;
|
||||
|
||||
#[pallet::storage]
|
||||
pub type Map<T> = StorageMap<_, Blake2_128Concat, u16, u64, ValueQuery>;
|
||||
|
||||
#[pallet::storage]
|
||||
pub type NumberMap<T> = StorageMap<_, Identity, u32, u64, ValueQuery>;
|
||||
|
||||
#[pallet::storage]
|
||||
pub type DoubleMap<T> =
|
||||
StorageDoubleMap<_, Blake2_128Concat, u16, Twox64Concat, u32, u64, ValueQuery>;
|
||||
|
||||
#[pallet::storage]
|
||||
pub type NMap<T> = StorageNMap<
|
||||
_,
|
||||
(storage::Key<Blake2_128Concat, u16>, storage::Key<Twox64Concat, u32>),
|
||||
u64,
|
||||
ValueQuery,
|
||||
>;
|
||||
|
||||
pub mod pezpallet_prelude {
|
||||
pub type OriginFor<T> = <T as super::Config>::RuntimeOrigin;
|
||||
|
||||
pub type HeaderFor<T> =
|
||||
<<T as super::Config>::Block as pezsp_runtime::traits::HeaderProvider>::HeaderT;
|
||||
|
||||
pub type BlockNumberFor<T> = <HeaderFor<T> as pezsp_runtime::traits::Header>::Number;
|
||||
}
|
||||
}
|
||||
|
||||
type BlockNumber = u32;
|
||||
type AccountId = u32;
|
||||
type Header = generic::Header<BlockNumber, BlakeTwo256>;
|
||||
type UncheckedExtrinsic = generic::UncheckedExtrinsic<u32, RuntimeCall, (), ()>;
|
||||
type Block = generic::Block<Header, UncheckedExtrinsic>;
|
||||
|
||||
crate::construct_runtime!(
|
||||
pub enum Runtime
|
||||
{
|
||||
System: self::pezframe_system,
|
||||
}
|
||||
);
|
||||
|
||||
impl self::pezframe_system::Config for Runtime {
|
||||
type AccountId = AccountId;
|
||||
type Block = Block;
|
||||
type BaseCallFilter = crate::traits::Everything;
|
||||
type RuntimeOrigin = RuntimeOrigin;
|
||||
type RuntimeCall = RuntimeCall;
|
||||
type RuntimeTask = RuntimeTask;
|
||||
type PalletInfo = PalletInfo;
|
||||
type DbWeight = ();
|
||||
}
|
||||
|
||||
pub fn key_before_prefix(mut prefix: Vec<u8>) -> Vec<u8> {
|
||||
let last = prefix.iter_mut().last().unwrap();
|
||||
assert_ne!(*last, 0, "mock function not implemented for this prefix");
|
||||
*last -= 1;
|
||||
prefix
|
||||
}
|
||||
|
||||
pub fn key_after_prefix(mut prefix: Vec<u8>) -> Vec<u8> {
|
||||
let last = prefix.iter_mut().last().unwrap();
|
||||
assert_ne!(*last, 255, "mock function not implemented for this prefix");
|
||||
*last += 1;
|
||||
prefix
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn value_translate_works() {
|
||||
let t = RuntimeGenesisConfig::default().build_storage().unwrap();
|
||||
TestExternalities::new(t).execute_with(|| {
|
||||
type Value = self::pezframe_system::Value<Runtime>;
|
||||
|
||||
// put the old value `1111u32` in the storage.
|
||||
let key = Value::storage_value_final_key();
|
||||
unhashed::put_raw(&key, &1111u32.encode());
|
||||
|
||||
// translate
|
||||
let translate_fn = |old: Option<u32>| -> Option<(u64, u64)> {
|
||||
old.map(|o| (o.into(), (o * 2).into()))
|
||||
};
|
||||
let res = Value::translate(translate_fn);
|
||||
debug_assert!(res.is_ok());
|
||||
|
||||
// new storage should be `(1111, 1111 * 2)`
|
||||
assert_eq!(Value::get(), (1111, 2222));
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn map_translate_works() {
|
||||
let t = RuntimeGenesisConfig::default().build_storage().unwrap();
|
||||
TestExternalities::new(t).execute_with(|| {
|
||||
type NumberMap = self::pezframe_system::NumberMap<Runtime>;
|
||||
|
||||
// start with a map of u32 -> u64.
|
||||
for i in 0u32..100u32 {
|
||||
unhashed::put(&NumberMap::hashed_key_for(&i), &(i as u64));
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
NumberMap::iter().collect::<Vec<_>>(),
|
||||
(0..100).map(|x| (x as u32, x as u64)).collect::<Vec<_>>(),
|
||||
);
|
||||
|
||||
// do translation.
|
||||
NumberMap::translate(
|
||||
|k: u32, v: u64| if k % 2 == 0 { Some(((k as u64) << 32) | v) } else { None },
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
NumberMap::iter().collect::<Vec<_>>(),
|
||||
(0..50u32)
|
||||
.map(|x| x * 2)
|
||||
.map(|x| (x, ((x as u64) << 32) | x as u64))
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn try_mutate_works() {
|
||||
let t = RuntimeGenesisConfig::default().build_storage().unwrap();
|
||||
TestExternalities::new(t).execute_with(|| {
|
||||
type Value = self::pezframe_system::Value<Runtime>;
|
||||
type NumberMap = self::pezframe_system::NumberMap<Runtime>;
|
||||
type DoubleMap = self::pezframe_system::DoubleMap<Runtime>;
|
||||
|
||||
assert_eq!(Value::get(), (0, 0));
|
||||
assert_eq!(NumberMap::get(0), 0);
|
||||
assert_eq!(DoubleMap::get(0, 0), 0);
|
||||
|
||||
// `assert_noop` ensures that the state does not change
|
||||
assert_noop!(
|
||||
Value::try_mutate(|value| -> Result<(), &'static str> {
|
||||
*value = (2, 2);
|
||||
Err("don't change value")
|
||||
}),
|
||||
"don't change value"
|
||||
);
|
||||
|
||||
assert_noop!(
|
||||
NumberMap::try_mutate(0, |value| -> Result<(), &'static str> {
|
||||
*value = 4;
|
||||
Err("don't change value")
|
||||
}),
|
||||
"don't change value"
|
||||
);
|
||||
|
||||
assert_noop!(
|
||||
DoubleMap::try_mutate(0, 0, |value| -> Result<(), &'static str> {
|
||||
*value = 6;
|
||||
Err("don't change value")
|
||||
}),
|
||||
"don't change value"
|
||||
);
|
||||
|
||||
// Showing this explicitly for clarity
|
||||
assert_eq!(Value::get(), (0, 0));
|
||||
assert_eq!(NumberMap::get(0), 0);
|
||||
assert_eq!(DoubleMap::get(0, 0), 0);
|
||||
|
||||
assert_ok!(Value::try_mutate(|value| -> Result<(), &'static str> {
|
||||
*value = (2, 2);
|
||||
Ok(())
|
||||
}));
|
||||
|
||||
assert_ok!(NumberMap::try_mutate(0, |value| -> Result<(), &'static str> {
|
||||
*value = 4;
|
||||
Ok(())
|
||||
}));
|
||||
|
||||
assert_ok!(DoubleMap::try_mutate(0, 0, |value| -> Result<(), &'static str> {
|
||||
*value = 6;
|
||||
Ok(())
|
||||
}));
|
||||
|
||||
assert_eq!(Value::get(), (2, 2));
|
||||
assert_eq!(NumberMap::get(0), 4);
|
||||
assert_eq!(DoubleMap::get(0, 0), 6);
|
||||
});
|
||||
}
|
||||
}
|
||||
+628
@@ -0,0 +1,628 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Generator for `StorageNMap` used by `decl_storage` and storage types.
|
||||
//!
|
||||
//! By default each key value is stored at:
|
||||
//! ```nocompile
|
||||
//! Twox128(pezpallet_prefix) ++ Twox128(storage_prefix)
|
||||
//! ++ Hasher1(encode(key1)) ++ Hasher2(encode(key2)) ++ ... ++ HasherN(encode(keyN))
|
||||
//! ```
|
||||
//!
|
||||
//! # 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 with the same prefix can
|
||||
//! be compromised.
|
||||
|
||||
use crate::{
|
||||
storage::{
|
||||
self, storage_prefix,
|
||||
types::{
|
||||
EncodeLikeTuple, HasKeyPrefix, HasReversibleKeyPrefix, KeyGenerator,
|
||||
ReversibleKeyGenerator, TupleToEncodedIter,
|
||||
},
|
||||
unhashed, KeyPrefixIterator, PrefixIterator, StorageAppend,
|
||||
},
|
||||
Never,
|
||||
};
|
||||
use alloc::vec::Vec;
|
||||
use codec::{Decode, Encode, EncodeLike, FullCodec};
|
||||
|
||||
/// Generator for `StorageNMap` used by `decl_storage` and storage types.
|
||||
///
|
||||
/// By default each key value is stored at:
|
||||
/// ```nocompile
|
||||
/// Twox128(pezpallet_prefix) ++ Twox128(storage_prefix)
|
||||
/// ++ Hasher1(encode(key1)) ++ Hasher2(encode(key2)) ++ ... ++ HasherN(encode(keyN))
|
||||
/// ```
|
||||
///
|
||||
/// # 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 with the same prefix can
|
||||
/// be compromised.
|
||||
pub trait StorageNMap<K: KeyGenerator, V: FullCodec> {
|
||||
/// The type that get/take returns.
|
||||
type Query;
|
||||
|
||||
/// Pallet prefix. Used for generating final key.
|
||||
fn pezpallet_prefix() -> &'static [u8];
|
||||
|
||||
/// Storage prefix. Used for generating final key.
|
||||
fn storage_prefix() -> &'static [u8];
|
||||
|
||||
/// The full prefix; just the hash of `pezpallet_prefix` concatenated to the hash of
|
||||
/// `storage_prefix`.
|
||||
fn prefix_hash() -> [u8; 32];
|
||||
|
||||
/// Convert an optional value retrieved from storage to the type queried.
|
||||
fn from_optional_value_to_query(v: Option<V>) -> Self::Query;
|
||||
|
||||
/// Convert a query to an optional value into storage.
|
||||
fn from_query_to_optional_value(v: Self::Query) -> Option<V>;
|
||||
|
||||
/// Generate a partial key used in top storage.
|
||||
fn storage_n_map_partial_key<KP>(key: KP) -> Vec<u8>
|
||||
where
|
||||
K: HasKeyPrefix<KP>,
|
||||
{
|
||||
let storage_prefix = storage_prefix(Self::pezpallet_prefix(), Self::storage_prefix());
|
||||
let key_hashed = <K as HasKeyPrefix<KP>>::partial_key(key);
|
||||
|
||||
let mut final_key = Vec::with_capacity(storage_prefix.len() + key_hashed.len());
|
||||
|
||||
final_key.extend_from_slice(&storage_prefix);
|
||||
final_key.extend_from_slice(key_hashed.as_ref());
|
||||
|
||||
final_key
|
||||
}
|
||||
|
||||
/// Generate the full key used in top storage.
|
||||
fn storage_n_map_final_key<KG, KArg>(key: KArg) -> Vec<u8>
|
||||
where
|
||||
KG: KeyGenerator,
|
||||
KArg: EncodeLikeTuple<KG::KArg> + TupleToEncodedIter,
|
||||
{
|
||||
let storage_prefix = storage_prefix(Self::pezpallet_prefix(), Self::storage_prefix());
|
||||
let key_hashed = KG::final_key(key);
|
||||
|
||||
let mut final_key = Vec::with_capacity(storage_prefix.len() + key_hashed.len());
|
||||
|
||||
final_key.extend_from_slice(&storage_prefix);
|
||||
final_key.extend_from_slice(key_hashed.as_ref());
|
||||
|
||||
final_key
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V, G> storage::StorageNMap<K, V> for G
|
||||
where
|
||||
K: KeyGenerator,
|
||||
V: FullCodec,
|
||||
G: StorageNMap<K, V>,
|
||||
{
|
||||
type Query = G::Query;
|
||||
|
||||
fn hashed_key_for<KArg: EncodeLikeTuple<K::KArg> + TupleToEncodedIter>(key: KArg) -> Vec<u8> {
|
||||
Self::storage_n_map_final_key::<K, _>(key)
|
||||
}
|
||||
|
||||
fn contains_key<KArg: EncodeLikeTuple<K::KArg> + TupleToEncodedIter>(key: KArg) -> bool {
|
||||
unhashed::exists(&Self::storage_n_map_final_key::<K, _>(key))
|
||||
}
|
||||
|
||||
fn get<KArg: EncodeLikeTuple<K::KArg> + TupleToEncodedIter>(key: KArg) -> Self::Query {
|
||||
G::from_optional_value_to_query(unhashed::get(&Self::storage_n_map_final_key::<K, _>(key)))
|
||||
}
|
||||
|
||||
fn try_get<KArg: EncodeLikeTuple<K::KArg> + TupleToEncodedIter>(key: KArg) -> Result<V, ()> {
|
||||
unhashed::get(&Self::storage_n_map_final_key::<K, _>(key)).ok_or(())
|
||||
}
|
||||
|
||||
fn set<KArg: EncodeLikeTuple<K::KArg> + TupleToEncodedIter>(key: KArg, q: Self::Query) {
|
||||
match G::from_query_to_optional_value(q) {
|
||||
Some(v) => Self::insert(key, v),
|
||||
None => Self::remove(key),
|
||||
}
|
||||
}
|
||||
|
||||
fn take<KArg: EncodeLikeTuple<K::KArg> + TupleToEncodedIter>(key: KArg) -> Self::Query {
|
||||
let final_key = Self::storage_n_map_final_key::<K, _>(key);
|
||||
|
||||
let value = unhashed::take(&final_key);
|
||||
G::from_optional_value_to_query(value)
|
||||
}
|
||||
|
||||
fn swap<KOther, KArg1, KArg2>(key1: KArg1, key2: KArg2)
|
||||
where
|
||||
KOther: KeyGenerator,
|
||||
KArg1: EncodeLikeTuple<K::KArg> + TupleToEncodedIter,
|
||||
KArg2: EncodeLikeTuple<KOther::KArg> + TupleToEncodedIter,
|
||||
{
|
||||
let final_x_key = Self::storage_n_map_final_key::<K, _>(key1);
|
||||
let final_y_key = Self::storage_n_map_final_key::<KOther, _>(key2);
|
||||
|
||||
let v1 = unhashed::get_raw(&final_x_key);
|
||||
if let Some(val) = unhashed::get_raw(&final_y_key) {
|
||||
unhashed::put_raw(&final_x_key, &val);
|
||||
} else {
|
||||
unhashed::kill(&final_x_key);
|
||||
}
|
||||
if let Some(val) = v1 {
|
||||
unhashed::put_raw(&final_y_key, &val);
|
||||
} else {
|
||||
unhashed::kill(&final_y_key);
|
||||
}
|
||||
}
|
||||
|
||||
fn insert<KArg, VArg>(key: KArg, val: VArg)
|
||||
where
|
||||
KArg: EncodeLikeTuple<K::KArg> + TupleToEncodedIter,
|
||||
VArg: EncodeLike<V>,
|
||||
{
|
||||
unhashed::put(&Self::storage_n_map_final_key::<K, _>(key), &val);
|
||||
}
|
||||
|
||||
fn remove<KArg: EncodeLikeTuple<K::KArg> + TupleToEncodedIter>(key: KArg) {
|
||||
unhashed::kill(&Self::storage_n_map_final_key::<K, _>(key));
|
||||
}
|
||||
|
||||
fn remove_prefix<KP>(partial_key: KP, limit: Option<u32>) -> pezsp_io::KillStorageResult
|
||||
where
|
||||
K: HasKeyPrefix<KP>,
|
||||
{
|
||||
unhashed::clear_prefix(&Self::storage_n_map_partial_key(partial_key), limit, None).into()
|
||||
}
|
||||
|
||||
fn clear_prefix<KP>(
|
||||
partial_key: KP,
|
||||
limit: u32,
|
||||
maybe_cursor: Option<&[u8]>,
|
||||
) -> pezsp_io::MultiRemovalResults
|
||||
where
|
||||
K: HasKeyPrefix<KP>,
|
||||
{
|
||||
unhashed::clear_prefix(
|
||||
&Self::storage_n_map_partial_key(partial_key),
|
||||
Some(limit),
|
||||
maybe_cursor,
|
||||
)
|
||||
}
|
||||
|
||||
fn contains_prefix<KP>(partial_key: KP) -> bool
|
||||
where
|
||||
K: HasKeyPrefix<KP>,
|
||||
{
|
||||
unhashed::contains_prefixed_key(&Self::storage_n_map_partial_key(partial_key))
|
||||
}
|
||||
|
||||
fn iter_prefix_values<KP>(partial_key: KP) -> PrefixIterator<V>
|
||||
where
|
||||
K: HasKeyPrefix<KP>,
|
||||
{
|
||||
let prefix = Self::storage_n_map_partial_key(partial_key);
|
||||
PrefixIterator {
|
||||
prefix: prefix.clone(),
|
||||
previous_key: prefix,
|
||||
drain: false,
|
||||
closure: |_raw_key, mut raw_value| V::decode(&mut raw_value),
|
||||
phantom: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn mutate<KArg, R, F>(key: KArg, f: F) -> R
|
||||
where
|
||||
KArg: EncodeLikeTuple<K::KArg> + TupleToEncodedIter,
|
||||
F: FnOnce(&mut Self::Query) -> R,
|
||||
{
|
||||
Self::try_mutate(key, |v| Ok::<R, Never>(f(v)))
|
||||
.expect("`Never` can not be constructed; qed")
|
||||
}
|
||||
|
||||
fn try_mutate<KArg, R, E, F>(key: KArg, f: F) -> Result<R, E>
|
||||
where
|
||||
KArg: EncodeLikeTuple<K::KArg> + TupleToEncodedIter,
|
||||
F: FnOnce(&mut Self::Query) -> Result<R, E>,
|
||||
{
|
||||
let final_key = Self::storage_n_map_final_key::<K, _>(key);
|
||||
let mut val = G::from_optional_value_to_query(unhashed::get(final_key.as_ref()));
|
||||
|
||||
let ret = f(&mut val);
|
||||
if ret.is_ok() {
|
||||
match G::from_query_to_optional_value(val) {
|
||||
Some(ref val) => unhashed::put(final_key.as_ref(), val),
|
||||
None => unhashed::kill(final_key.as_ref()),
|
||||
}
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
fn mutate_exists<KArg, R, F>(key: KArg, f: F) -> R
|
||||
where
|
||||
KArg: EncodeLikeTuple<K::KArg> + TupleToEncodedIter,
|
||||
F: FnOnce(&mut Option<V>) -> R,
|
||||
{
|
||||
Self::try_mutate_exists(key, |v| Ok::<R, Never>(f(v)))
|
||||
.expect("`Never` can not be constructed; qed")
|
||||
}
|
||||
|
||||
fn try_mutate_exists<KArg, R, E, F>(key: KArg, f: F) -> Result<R, E>
|
||||
where
|
||||
KArg: EncodeLikeTuple<K::KArg> + TupleToEncodedIter,
|
||||
F: FnOnce(&mut Option<V>) -> Result<R, E>,
|
||||
{
|
||||
let final_key = Self::storage_n_map_final_key::<K, _>(key);
|
||||
let mut val = unhashed::get(final_key.as_ref());
|
||||
|
||||
let ret = f(&mut val);
|
||||
if ret.is_ok() {
|
||||
match val {
|
||||
Some(ref val) => unhashed::put(final_key.as_ref(), val),
|
||||
None => unhashed::kill(final_key.as_ref()),
|
||||
}
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
fn append<Item, EncodeLikeItem, KArg>(key: KArg, item: EncodeLikeItem)
|
||||
where
|
||||
KArg: EncodeLikeTuple<K::KArg> + TupleToEncodedIter,
|
||||
Item: Encode,
|
||||
EncodeLikeItem: EncodeLike<Item>,
|
||||
V: StorageAppend<Item>,
|
||||
{
|
||||
let final_key = Self::storage_n_map_final_key::<K, _>(key);
|
||||
pezsp_io::storage::append(&final_key, item.encode());
|
||||
}
|
||||
|
||||
fn migrate_keys<KArg>(key: KArg, hash_fns: K::HArg) -> Option<V>
|
||||
where
|
||||
KArg: EncodeLikeTuple<K::KArg> + TupleToEncodedIter,
|
||||
{
|
||||
let old_key = {
|
||||
let storage_prefix = storage_prefix(Self::pezpallet_prefix(), Self::storage_prefix());
|
||||
let key_hashed = K::migrate_key(&key, hash_fns);
|
||||
|
||||
let mut final_key = Vec::with_capacity(storage_prefix.len() + key_hashed.len());
|
||||
|
||||
final_key.extend_from_slice(&storage_prefix);
|
||||
final_key.extend_from_slice(key_hashed.as_ref());
|
||||
|
||||
final_key
|
||||
};
|
||||
unhashed::take(old_key.as_ref()).inspect(|value| {
|
||||
unhashed::put(Self::storage_n_map_final_key::<K, _>(key).as_ref(), &value);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: ReversibleKeyGenerator, V: FullCodec, G: StorageNMap<K, V>>
|
||||
storage::IterableStorageNMap<K, V> for G
|
||||
{
|
||||
type KeyIterator = KeyPrefixIterator<K::Key>;
|
||||
type Iterator = PrefixIterator<(K::Key, V)>;
|
||||
|
||||
fn iter_prefix<KP>(kp: KP) -> PrefixIterator<(<K as HasKeyPrefix<KP>>::Suffix, V)>
|
||||
where
|
||||
K: HasReversibleKeyPrefix<KP>,
|
||||
{
|
||||
let prefix = G::storage_n_map_partial_key(kp);
|
||||
PrefixIterator {
|
||||
prefix: prefix.clone(),
|
||||
previous_key: prefix,
|
||||
drain: false,
|
||||
closure: |raw_key_without_prefix, mut raw_value| {
|
||||
let partial_key = K::decode_partial_key(raw_key_without_prefix)?;
|
||||
Ok((partial_key, V::decode(&mut raw_value)?))
|
||||
},
|
||||
phantom: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn iter_prefix_from<KP>(
|
||||
kp: KP,
|
||||
starting_raw_key: Vec<u8>,
|
||||
) -> PrefixIterator<(<K as HasKeyPrefix<KP>>::Suffix, V)>
|
||||
where
|
||||
K: HasReversibleKeyPrefix<KP>,
|
||||
{
|
||||
let mut iter = Self::iter_prefix(kp);
|
||||
iter.set_last_raw_key(starting_raw_key);
|
||||
iter
|
||||
}
|
||||
|
||||
fn iter_key_prefix<KP>(kp: KP) -> KeyPrefixIterator<<K as HasKeyPrefix<KP>>::Suffix>
|
||||
where
|
||||
K: HasReversibleKeyPrefix<KP>,
|
||||
{
|
||||
let prefix = G::storage_n_map_partial_key(kp);
|
||||
KeyPrefixIterator {
|
||||
prefix: prefix.clone(),
|
||||
previous_key: prefix,
|
||||
drain: false,
|
||||
closure: K::decode_partial_key,
|
||||
}
|
||||
}
|
||||
|
||||
fn iter_key_prefix_from<KP>(
|
||||
kp: KP,
|
||||
starting_raw_key: Vec<u8>,
|
||||
) -> KeyPrefixIterator<<K as HasKeyPrefix<KP>>::Suffix>
|
||||
where
|
||||
K: HasReversibleKeyPrefix<KP>,
|
||||
{
|
||||
let mut iter = Self::iter_key_prefix(kp);
|
||||
iter.set_last_raw_key(starting_raw_key);
|
||||
iter
|
||||
}
|
||||
|
||||
fn drain_prefix<KP>(kp: KP) -> PrefixIterator<(<K as HasKeyPrefix<KP>>::Suffix, V)>
|
||||
where
|
||||
K: HasReversibleKeyPrefix<KP>,
|
||||
{
|
||||
let mut iter = Self::iter_prefix(kp);
|
||||
iter.drain = true;
|
||||
iter
|
||||
}
|
||||
|
||||
fn iter() -> Self::Iterator {
|
||||
Self::iter_from(G::prefix_hash().to_vec())
|
||||
}
|
||||
|
||||
fn iter_from(starting_raw_key: Vec<u8>) -> Self::Iterator {
|
||||
let prefix = G::prefix_hash().to_vec();
|
||||
Self::Iterator {
|
||||
prefix,
|
||||
previous_key: starting_raw_key,
|
||||
drain: false,
|
||||
closure: |raw_key_without_prefix, mut raw_value| {
|
||||
let (final_key, _) = K::decode_final_key(raw_key_without_prefix)?;
|
||||
Ok((final_key, V::decode(&mut raw_value)?))
|
||||
},
|
||||
phantom: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn iter_keys() -> Self::KeyIterator {
|
||||
Self::iter_keys_from(G::prefix_hash().to_vec())
|
||||
}
|
||||
|
||||
fn iter_keys_from(starting_raw_key: Vec<u8>) -> Self::KeyIterator {
|
||||
let prefix = G::prefix_hash().to_vec();
|
||||
Self::KeyIterator {
|
||||
prefix,
|
||||
previous_key: starting_raw_key,
|
||||
drain: false,
|
||||
closure: |raw_key_without_prefix| {
|
||||
let (final_key, _) = K::decode_final_key(raw_key_without_prefix)?;
|
||||
Ok(final_key)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn drain() -> Self::Iterator {
|
||||
let mut iterator = Self::iter();
|
||||
iterator.drain = true;
|
||||
iterator
|
||||
}
|
||||
|
||||
fn translate<O: Decode, F: FnMut(K::Key, O) -> Option<V>>(mut f: F) {
|
||||
let prefix = G::prefix_hash().to_vec();
|
||||
let mut previous_key = prefix.clone();
|
||||
while let Some(next) =
|
||||
pezsp_io::storage::next_key(&previous_key).filter(|n| n.starts_with(&prefix))
|
||||
{
|
||||
previous_key = next;
|
||||
let value = match unhashed::get::<O>(&previous_key) {
|
||||
Some(value) => value,
|
||||
None => {
|
||||
log::error!("Invalid translate: fail to decode old value");
|
||||
continue;
|
||||
},
|
||||
};
|
||||
|
||||
let final_key = match K::decode_final_key(&previous_key[prefix.len()..]) {
|
||||
Ok((final_key, _)) => final_key,
|
||||
Err(_) => {
|
||||
log::error!("Invalid translate: fail to decode key");
|
||||
continue;
|
||||
},
|
||||
};
|
||||
|
||||
match f(final_key, value) {
|
||||
Some(new) => unhashed::put::<V>(&previous_key, &new),
|
||||
None => unhashed::kill(&previous_key),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Test iterators for StorageNMap
|
||||
#[cfg(test)]
|
||||
mod test_iterators {
|
||||
use crate::{
|
||||
hash::StorageHasher,
|
||||
storage::{
|
||||
generator::{tests::*, StorageNMap},
|
||||
unhashed,
|
||||
},
|
||||
};
|
||||
use alloc::vec;
|
||||
use codec::Encode;
|
||||
|
||||
#[test]
|
||||
fn n_map_iter_from() {
|
||||
pezsp_io::TestExternalities::default().execute_with(|| {
|
||||
use crate::{hash::Identity, storage::Key as NMapKey};
|
||||
#[crate::storage_alias]
|
||||
type MyNMap = StorageNMap<
|
||||
MyModule,
|
||||
(NMapKey<Identity, u64>, NMapKey<Identity, u64>, NMapKey<Identity, u64>),
|
||||
u64,
|
||||
>;
|
||||
|
||||
MyNMap::insert((1, 1, 1), 11);
|
||||
MyNMap::insert((1, 1, 2), 21);
|
||||
MyNMap::insert((1, 1, 3), 31);
|
||||
MyNMap::insert((1, 2, 1), 12);
|
||||
MyNMap::insert((1, 2, 2), 22);
|
||||
MyNMap::insert((1, 2, 3), 32);
|
||||
MyNMap::insert((1, 3, 1), 13);
|
||||
MyNMap::insert((1, 3, 2), 23);
|
||||
MyNMap::insert((1, 3, 3), 33);
|
||||
MyNMap::insert((2, 0, 0), 200);
|
||||
|
||||
type Key = (NMapKey<Identity, u64>, NMapKey<Identity, u64>, NMapKey<Identity, u64>);
|
||||
|
||||
let starting_raw_key = MyNMap::storage_n_map_final_key::<Key, _>((1, 2, 2));
|
||||
let iter = MyNMap::iter_key_prefix_from((1,), starting_raw_key);
|
||||
assert_eq!(iter.collect::<Vec<_>>(), vec![(2, 3), (3, 1), (3, 2), (3, 3)]);
|
||||
|
||||
let starting_raw_key = MyNMap::storage_n_map_final_key::<Key, _>((1, 3, 1));
|
||||
let iter = MyNMap::iter_prefix_from((1, 3), starting_raw_key);
|
||||
assert_eq!(iter.collect::<Vec<_>>(), vec![(2, 23), (3, 33)]);
|
||||
|
||||
let starting_raw_key = MyNMap::storage_n_map_final_key::<Key, _>((1, 3, 2));
|
||||
let iter = MyNMap::iter_keys_from(starting_raw_key);
|
||||
assert_eq!(iter.collect::<Vec<_>>(), vec![(1, 3, 3), (2, 0, 0)]);
|
||||
|
||||
let starting_raw_key = MyNMap::storage_n_map_final_key::<Key, _>((1, 3, 3));
|
||||
let iter = MyNMap::iter_from(starting_raw_key);
|
||||
assert_eq!(iter.collect::<Vec<_>>(), vec![((2, 0, 0), 200)]);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn n_map_double_map_identical_key() {
|
||||
pezsp_io::TestExternalities::default().execute_with(|| {
|
||||
use crate::hash::{Blake2_128Concat, Twox64Concat};
|
||||
|
||||
type NMap = self::pezframe_system::NMap<Runtime>;
|
||||
|
||||
NMap::insert((1, 2), 50);
|
||||
let key_hash = NMap::hashed_key_for((1, 2));
|
||||
|
||||
{
|
||||
#[crate::storage_alias]
|
||||
type NMap = StorageDoubleMap<System, Blake2_128Concat, u16, Twox64Concat, u32, u64>;
|
||||
|
||||
assert_eq!(NMap::get(1, 2), Some(50));
|
||||
assert_eq!(NMap::hashed_key_for(1, 2), key_hash);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn n_map_reversible_reversible_iteration() {
|
||||
pezsp_io::TestExternalities::default().execute_with(|| {
|
||||
type NMap = self::pezframe_system::NMap<Runtime>;
|
||||
|
||||
// All map iterator
|
||||
let prefix = NMap::prefix_hash().to_vec();
|
||||
|
||||
unhashed::put(&key_before_prefix(prefix.clone()), &1u64);
|
||||
unhashed::put(&key_after_prefix(prefix.clone()), &1u64);
|
||||
|
||||
for i in 0..4 {
|
||||
NMap::insert((i as u16, i as u32), i as u64);
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
NMap::iter().collect::<Vec<_>>(),
|
||||
vec![((3, 3), 3), ((0, 0), 0), ((2, 2), 2), ((1, 1), 1)],
|
||||
);
|
||||
|
||||
assert_eq!(NMap::iter_keys().collect::<Vec<_>>(), vec![(3, 3), (0, 0), (2, 2), (1, 1)]);
|
||||
|
||||
assert_eq!(NMap::iter_values().collect::<Vec<_>>(), vec![3, 0, 2, 1]);
|
||||
|
||||
assert_eq!(
|
||||
NMap::drain().collect::<Vec<_>>(),
|
||||
vec![((3, 3), 3), ((0, 0), 0), ((2, 2), 2), ((1, 1), 1)],
|
||||
);
|
||||
|
||||
assert_eq!(NMap::iter().collect::<Vec<_>>(), vec![]);
|
||||
assert_eq!(unhashed::get(&key_before_prefix(prefix.clone())), Some(1u64));
|
||||
assert_eq!(unhashed::get(&key_after_prefix(prefix.clone())), Some(1u64));
|
||||
|
||||
// Prefix iterator
|
||||
let k1 = 3 << 8;
|
||||
let prefix = NMap::storage_n_map_partial_key((k1,));
|
||||
|
||||
unhashed::put(&key_before_prefix(prefix.clone()), &1u64);
|
||||
unhashed::put(&key_after_prefix(prefix.clone()), &1u64);
|
||||
|
||||
for i in 0..4 {
|
||||
NMap::insert((k1, i as u32), i as u64);
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
NMap::iter_prefix((k1,)).collect::<Vec<_>>(),
|
||||
vec![(1, 1), (2, 2), (0, 0), (3, 3)],
|
||||
);
|
||||
|
||||
assert_eq!(NMap::iter_key_prefix((k1,)).collect::<Vec<_>>(), vec![1, 2, 0, 3]);
|
||||
|
||||
assert_eq!(NMap::iter_prefix_values((k1,)).collect::<Vec<_>>(), vec![1, 2, 0, 3]);
|
||||
|
||||
assert_eq!(
|
||||
NMap::drain_prefix((k1,)).collect::<Vec<_>>(),
|
||||
vec![(1, 1), (2, 2), (0, 0), (3, 3)],
|
||||
);
|
||||
|
||||
assert_eq!(NMap::iter_prefix((k1,)).collect::<Vec<_>>(), vec![]);
|
||||
assert_eq!(unhashed::get(&key_before_prefix(prefix.clone())), Some(1u64));
|
||||
assert_eq!(unhashed::get(&key_after_prefix(prefix.clone())), Some(1u64));
|
||||
|
||||
// Translate
|
||||
let prefix = NMap::prefix_hash().to_vec();
|
||||
|
||||
unhashed::put(&key_before_prefix(prefix.clone()), &1u64);
|
||||
unhashed::put(&key_after_prefix(prefix.clone()), &1u64);
|
||||
for i in 0..4 {
|
||||
NMap::insert((i as u16, i as u32), i as u64);
|
||||
}
|
||||
|
||||
// Wrong key1
|
||||
unhashed::put(&[prefix.clone(), vec![1, 2, 3]].concat(), &3u64.encode());
|
||||
|
||||
// Wrong key2
|
||||
unhashed::put(
|
||||
&[prefix.clone(), crate::Blake2_128Concat::hash(&1u16.encode())].concat(),
|
||||
&3u64.encode(),
|
||||
);
|
||||
|
||||
// Wrong value
|
||||
unhashed::put(
|
||||
&[
|
||||
prefix.clone(),
|
||||
crate::Blake2_128Concat::hash(&1u16.encode()),
|
||||
crate::Twox64Concat::hash(&2u32.encode()),
|
||||
]
|
||||
.concat(),
|
||||
&vec![1],
|
||||
);
|
||||
|
||||
NMap::translate(|(_k1, _k2), v: u64| Some(v * 2));
|
||||
assert_eq!(
|
||||
NMap::iter().collect::<Vec<_>>(),
|
||||
vec![((3, 3), 6), ((0, 0), 0), ((2, 2), 4), ((1, 1), 2)],
|
||||
);
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::{
|
||||
storage::{self, unhashed, StorageAppend},
|
||||
Never,
|
||||
};
|
||||
use codec::{Decode, Encode, EncodeLike, FullCodec};
|
||||
|
||||
/// Generator for `StorageValue` used by `decl_storage`.
|
||||
///
|
||||
/// By default value is stored at:
|
||||
/// ```nocompile
|
||||
/// Twox128(pezpallet_prefix) ++ Twox128(storage_prefix)
|
||||
/// ```
|
||||
pub trait StorageValue<T: FullCodec> {
|
||||
/// The type that get/take returns.
|
||||
type Query;
|
||||
|
||||
/// Pallet prefix. Used for generating final key.
|
||||
fn pezpallet_prefix() -> &'static [u8];
|
||||
|
||||
/// Storage prefix. Used for generating final key.
|
||||
fn storage_prefix() -> &'static [u8];
|
||||
|
||||
/// Convert an optional value retrieved from storage to the type queried.
|
||||
fn from_optional_value_to_query(v: Option<T>) -> Self::Query;
|
||||
|
||||
/// Convert a query to an optional value into storage.
|
||||
fn from_query_to_optional_value(v: Self::Query) -> Option<T>;
|
||||
|
||||
/// Generate the full key used in top storage.
|
||||
fn storage_value_final_key() -> [u8; 32];
|
||||
}
|
||||
|
||||
impl<T: FullCodec, G: StorageValue<T>> storage::StorageValue<T> for G {
|
||||
type Query = G::Query;
|
||||
|
||||
fn hashed_key() -> [u8; 32] {
|
||||
Self::storage_value_final_key()
|
||||
}
|
||||
|
||||
fn exists() -> bool {
|
||||
unhashed::exists(&Self::storage_value_final_key())
|
||||
}
|
||||
|
||||
fn get() -> Self::Query {
|
||||
let value = unhashed::get(&Self::storage_value_final_key());
|
||||
G::from_optional_value_to_query(value)
|
||||
}
|
||||
|
||||
fn try_get() -> Result<T, ()> {
|
||||
unhashed::get(&Self::storage_value_final_key()).ok_or(())
|
||||
}
|
||||
|
||||
fn translate<O: Decode, F: FnOnce(Option<O>) -> Option<T>>(f: F) -> Result<Option<T>, ()> {
|
||||
let key = Self::storage_value_final_key();
|
||||
|
||||
// attempt to get the length directly.
|
||||
let maybe_old = unhashed::get_raw(&key)
|
||||
.map(|old_data| O::decode(&mut &old_data[..]).map_err(|_| ()))
|
||||
.transpose()?;
|
||||
let maybe_new = f(maybe_old);
|
||||
if let Some(new) = maybe_new.as_ref() {
|
||||
new.using_encoded(|d| unhashed::put_raw(&key, d));
|
||||
} else {
|
||||
unhashed::kill(&key);
|
||||
}
|
||||
Ok(maybe_new)
|
||||
}
|
||||
|
||||
fn put<Arg: EncodeLike<T>>(val: Arg) {
|
||||
unhashed::put(&Self::storage_value_final_key(), &val)
|
||||
}
|
||||
|
||||
fn set(maybe_val: Self::Query) {
|
||||
if let Some(val) = G::from_query_to_optional_value(maybe_val) {
|
||||
unhashed::put(&Self::storage_value_final_key(), &val)
|
||||
} else {
|
||||
unhashed::kill(&Self::storage_value_final_key())
|
||||
}
|
||||
}
|
||||
|
||||
fn mutate<R, F: FnOnce(&mut G::Query) -> R>(f: F) -> R {
|
||||
Self::try_mutate(|v| Ok::<R, Never>(f(v))).expect("`Never` can not be constructed; qed")
|
||||
}
|
||||
|
||||
fn try_mutate<R, E, F: FnOnce(&mut G::Query) -> Result<R, E>>(f: F) -> Result<R, E> {
|
||||
let mut val = G::get();
|
||||
|
||||
let ret = f(&mut val);
|
||||
if ret.is_ok() {
|
||||
match G::from_query_to_optional_value(val) {
|
||||
Some(ref val) => G::put(val),
|
||||
None => G::kill(),
|
||||
}
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
fn mutate_exists<R, F>(f: F) -> R
|
||||
where
|
||||
F: FnOnce(&mut Option<T>) -> R,
|
||||
{
|
||||
Self::try_mutate_exists(|v| Ok::<R, Never>(f(v)))
|
||||
.expect("`Never` can not be constructed; qed")
|
||||
}
|
||||
|
||||
fn try_mutate_exists<R, E, F>(f: F) -> Result<R, E>
|
||||
where
|
||||
F: FnOnce(&mut Option<T>) -> Result<R, E>,
|
||||
{
|
||||
let mut val = G::from_query_to_optional_value(Self::get());
|
||||
|
||||
let ret = f(&mut val);
|
||||
if ret.is_ok() {
|
||||
match val {
|
||||
Some(ref val) => Self::put(val),
|
||||
None => Self::kill(),
|
||||
}
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
fn kill() {
|
||||
unhashed::kill(&Self::storage_value_final_key())
|
||||
}
|
||||
|
||||
fn take() -> G::Query {
|
||||
let key = Self::storage_value_final_key();
|
||||
let value = unhashed::get(&key);
|
||||
if value.is_some() {
|
||||
unhashed::kill(&key)
|
||||
}
|
||||
G::from_optional_value_to_query(value)
|
||||
}
|
||||
|
||||
fn append<Item, EncodeLikeItem>(item: EncodeLikeItem)
|
||||
where
|
||||
Item: Encode,
|
||||
EncodeLikeItem: EncodeLike<Item>,
|
||||
T: StorageAppend<Item>,
|
||||
{
|
||||
let key = Self::storage_value_final_key();
|
||||
pezsp_io::storage::append(&key, item.encode());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Operation on runtime storage using hashed keys.
|
||||
|
||||
use super::unhashed;
|
||||
use alloc::vec::Vec;
|
||||
use codec::{Decode, Encode};
|
||||
|
||||
/// Return the value of the item in storage under `key`, or `None` if there is no explicit entry.
|
||||
pub fn get<T, HashFn, R>(hash: &HashFn, key: &[u8]) -> Option<T>
|
||||
where
|
||||
T: Decode + Sized,
|
||||
HashFn: Fn(&[u8]) -> R,
|
||||
R: AsRef<[u8]>,
|
||||
{
|
||||
unhashed::get(hash(key).as_ref())
|
||||
}
|
||||
|
||||
/// 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, HashFn, R>(hash: &HashFn, key: &[u8]) -> T
|
||||
where
|
||||
T: Decode + Sized + Default,
|
||||
HashFn: Fn(&[u8]) -> R,
|
||||
R: AsRef<[u8]>,
|
||||
{
|
||||
unhashed::get_or_default(hash(key).as_ref())
|
||||
}
|
||||
|
||||
/// Return the value of the item in storage under `key`, or `default_value` if there is no
|
||||
/// explicit entry.
|
||||
pub fn get_or<T, HashFn, R>(hash: &HashFn, key: &[u8], default_value: T) -> T
|
||||
where
|
||||
T: Decode + Sized,
|
||||
HashFn: Fn(&[u8]) -> R,
|
||||
R: AsRef<[u8]>,
|
||||
{
|
||||
unhashed::get_or(hash(key).as_ref(), 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, F, HashFn, R>(hash: &HashFn, key: &[u8], default_value: F) -> T
|
||||
where
|
||||
T: Decode + Sized,
|
||||
F: FnOnce() -> T,
|
||||
HashFn: Fn(&[u8]) -> R,
|
||||
R: AsRef<[u8]>,
|
||||
{
|
||||
unhashed::get_or_else(hash(key).as_ref(), default_value)
|
||||
}
|
||||
|
||||
/// Put `value` in storage under `key`.
|
||||
pub fn put<T, HashFn, R>(hash: &HashFn, key: &[u8], value: &T)
|
||||
where
|
||||
T: Encode,
|
||||
HashFn: Fn(&[u8]) -> R,
|
||||
R: AsRef<[u8]>,
|
||||
{
|
||||
unhashed::put(hash(key).as_ref(), value)
|
||||
}
|
||||
|
||||
/// Remove `key` from storage, returning its value if it had an explicit entry or `None` otherwise.
|
||||
pub fn take<T, HashFn, R>(hash: &HashFn, key: &[u8]) -> Option<T>
|
||||
where
|
||||
T: Decode + Sized,
|
||||
HashFn: Fn(&[u8]) -> R,
|
||||
R: AsRef<[u8]>,
|
||||
{
|
||||
unhashed::take(hash(key).as_ref())
|
||||
}
|
||||
|
||||
/// 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, HashFn, R>(hash: &HashFn, key: &[u8]) -> T
|
||||
where
|
||||
T: Decode + Sized + Default,
|
||||
HashFn: Fn(&[u8]) -> R,
|
||||
R: AsRef<[u8]>,
|
||||
{
|
||||
unhashed::take_or_default(hash(key).as_ref())
|
||||
}
|
||||
|
||||
/// 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, HashFn, R>(hash: &HashFn, key: &[u8], default_value: T) -> T
|
||||
where
|
||||
T: Decode + Sized,
|
||||
HashFn: Fn(&[u8]) -> R,
|
||||
R: AsRef<[u8]>,
|
||||
{
|
||||
unhashed::take_or(hash(key).as_ref(), 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, F, HashFn, R>(hash: &HashFn, key: &[u8], default_value: F) -> T
|
||||
where
|
||||
T: Decode + Sized,
|
||||
F: FnOnce() -> T,
|
||||
HashFn: Fn(&[u8]) -> R,
|
||||
R: AsRef<[u8]>,
|
||||
{
|
||||
unhashed::take_or_else(hash(key).as_ref(), default_value)
|
||||
}
|
||||
|
||||
/// Check to see if `key` has an explicit entry in storage.
|
||||
pub fn exists<HashFn, R>(hash: &HashFn, key: &[u8]) -> bool
|
||||
where
|
||||
HashFn: Fn(&[u8]) -> R,
|
||||
R: AsRef<[u8]>,
|
||||
{
|
||||
unhashed::exists(hash(key).as_ref())
|
||||
}
|
||||
|
||||
/// Ensure `key` has no explicit entry in storage.
|
||||
pub fn kill<HashFn, R>(hash: &HashFn, key: &[u8])
|
||||
where
|
||||
HashFn: Fn(&[u8]) -> R,
|
||||
R: AsRef<[u8]>,
|
||||
{
|
||||
unhashed::kill(hash(key).as_ref())
|
||||
}
|
||||
|
||||
/// Get a Vec of bytes from storage.
|
||||
pub fn get_raw<HashFn, R>(hash: &HashFn, key: &[u8]) -> Option<Vec<u8>>
|
||||
where
|
||||
HashFn: Fn(&[u8]) -> R,
|
||||
R: AsRef<[u8]>,
|
||||
{
|
||||
unhashed::get_raw(hash(key).as_ref())
|
||||
}
|
||||
|
||||
/// Put a raw byte slice into storage.
|
||||
pub fn put_raw<HashFn, R>(hash: &HashFn, key: &[u8], value: &[u8])
|
||||
where
|
||||
HashFn: Fn(&[u8]) -> R,
|
||||
R: AsRef<[u8]>,
|
||||
{
|
||||
unhashed::put_raw(hash(key).as_ref(), value)
|
||||
}
|
||||
@@ -0,0 +1,517 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Some utilities for helping access storage with arbitrary key types.
|
||||
|
||||
use crate::{
|
||||
hash::ReversibleStorageHasher,
|
||||
storage::{storage_prefix, unhashed},
|
||||
StorageHasher, Twox128,
|
||||
};
|
||||
use alloc::{vec, vec::Vec};
|
||||
use codec::{Decode, Encode};
|
||||
|
||||
use super::PrefixIterator;
|
||||
|
||||
/// Utility to iterate through raw items in storage.
|
||||
pub struct StorageIterator<T> {
|
||||
prefix: Vec<u8>,
|
||||
previous_key: Vec<u8>,
|
||||
drain: bool,
|
||||
_phantom: ::core::marker::PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T> StorageIterator<T> {
|
||||
/// Construct iterator to iterate over map items in `module` for the map called `item`.
|
||||
#[deprecated(note = "Will be removed after July 2023; Please use the storage_iter or \
|
||||
storage_iter_with_suffix functions instead")]
|
||||
pub fn new(module: &[u8], item: &[u8]) -> Self {
|
||||
#[allow(deprecated)]
|
||||
Self::with_suffix(module, item, &[][..])
|
||||
}
|
||||
|
||||
/// Construct iterator to iterate over map items in `module` for the map called `item`.
|
||||
#[deprecated(note = "Will be removed after July 2023; Please use the storage_iter or \
|
||||
storage_iter_with_suffix functions instead")]
|
||||
pub fn with_suffix(module: &[u8], item: &[u8], suffix: &[u8]) -> Self {
|
||||
let mut prefix = Vec::new();
|
||||
let storage_prefix = storage_prefix(module, item);
|
||||
prefix.extend_from_slice(&storage_prefix);
|
||||
prefix.extend_from_slice(suffix);
|
||||
let previous_key = prefix.clone();
|
||||
Self { prefix, previous_key, drain: false, _phantom: Default::default() }
|
||||
}
|
||||
|
||||
/// Mutate this iterator into a draining iterator; items iterated are removed from storage.
|
||||
pub fn drain(mut self) -> Self {
|
||||
self.drain = true;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Decode + Sized> Iterator for StorageIterator<T> {
|
||||
type Item = (Vec<u8>, T);
|
||||
|
||||
fn next(&mut self) -> Option<(Vec<u8>, T)> {
|
||||
loop {
|
||||
let maybe_next = pezsp_io::storage::next_key(&self.previous_key)
|
||||
.filter(|n| n.starts_with(&self.prefix));
|
||||
break match maybe_next {
|
||||
Some(next) => {
|
||||
self.previous_key = next.clone();
|
||||
let maybe_value = pezframe_support::storage::unhashed::get::<T>(&next);
|
||||
match maybe_value {
|
||||
Some(value) => {
|
||||
if self.drain {
|
||||
pezframe_support::storage::unhashed::kill(&next);
|
||||
}
|
||||
Some((self.previous_key[self.prefix.len()..].to_vec(), value))
|
||||
},
|
||||
None => continue,
|
||||
}
|
||||
},
|
||||
None => None,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Utility to iterate through raw items in storage.
|
||||
pub struct StorageKeyIterator<K, T, H: ReversibleStorageHasher> {
|
||||
prefix: Vec<u8>,
|
||||
previous_key: Vec<u8>,
|
||||
drain: bool,
|
||||
_phantom: ::core::marker::PhantomData<(K, T, H)>,
|
||||
}
|
||||
|
||||
impl<K, T, H: ReversibleStorageHasher> StorageKeyIterator<K, T, H> {
|
||||
/// Construct iterator to iterate over map items in `module` for the map called `item`.
|
||||
#[deprecated(note = "Will be removed after July 2023; Please use the storage_key_iter or \
|
||||
storage_key_iter_with_suffix functions instead")]
|
||||
pub fn new(module: &[u8], item: &[u8]) -> Self {
|
||||
#[allow(deprecated)]
|
||||
Self::with_suffix(module, item, &[][..])
|
||||
}
|
||||
|
||||
/// Construct iterator to iterate over map items in `module` for the map called `item`.
|
||||
#[deprecated(note = "Will be removed after July 2023; Please use the storage_key_iter or \
|
||||
storage_key_iter_with_suffix functions instead")]
|
||||
pub fn with_suffix(module: &[u8], item: &[u8], suffix: &[u8]) -> Self {
|
||||
let mut prefix = Vec::new();
|
||||
let storage_prefix = storage_prefix(module, item);
|
||||
prefix.extend_from_slice(&storage_prefix);
|
||||
prefix.extend_from_slice(suffix);
|
||||
let previous_key = prefix.clone();
|
||||
Self { prefix, previous_key, drain: false, _phantom: Default::default() }
|
||||
}
|
||||
|
||||
/// Mutate this iterator into a draining iterator; items iterated are removed from storage.
|
||||
pub fn drain(mut self) -> Self {
|
||||
self.drain = true;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: Decode + Sized, T: Decode + Sized, H: ReversibleStorageHasher> Iterator
|
||||
for StorageKeyIterator<K, T, H>
|
||||
{
|
||||
type Item = (K, T);
|
||||
|
||||
fn next(&mut self) -> Option<(K, T)> {
|
||||
loop {
|
||||
let maybe_next = pezsp_io::storage::next_key(&self.previous_key)
|
||||
.filter(|n| n.starts_with(&self.prefix));
|
||||
break match maybe_next {
|
||||
Some(next) => {
|
||||
self.previous_key = next.clone();
|
||||
let mut key_material = H::reverse(&next[self.prefix.len()..]);
|
||||
match K::decode(&mut key_material) {
|
||||
Ok(key) => {
|
||||
let maybe_value = pezframe_support::storage::unhashed::get::<T>(&next);
|
||||
match maybe_value {
|
||||
Some(value) => {
|
||||
if self.drain {
|
||||
pezframe_support::storage::unhashed::kill(&next);
|
||||
}
|
||||
Some((key, value))
|
||||
},
|
||||
None => continue,
|
||||
}
|
||||
},
|
||||
Err(_) => continue,
|
||||
}
|
||||
},
|
||||
None => None,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct iterator to iterate over map items in `module` for the map called `item`.
|
||||
pub fn storage_iter<T: Decode + Sized>(module: &[u8], item: &[u8]) -> PrefixIterator<(Vec<u8>, T)> {
|
||||
storage_iter_with_suffix(module, item, &[][..])
|
||||
}
|
||||
|
||||
/// Construct iterator to iterate over map items in `module` for the map called `item`.
|
||||
pub fn storage_iter_with_suffix<T: Decode + Sized>(
|
||||
module: &[u8],
|
||||
item: &[u8],
|
||||
suffix: &[u8],
|
||||
) -> PrefixIterator<(Vec<u8>, T)> {
|
||||
let mut prefix = Vec::new();
|
||||
let storage_prefix = storage_prefix(module, item);
|
||||
prefix.extend_from_slice(&storage_prefix);
|
||||
prefix.extend_from_slice(suffix);
|
||||
let previous_key = prefix.clone();
|
||||
let closure = |raw_key_without_prefix: &[u8], mut raw_value: &[u8]| {
|
||||
let value = T::decode(&mut raw_value)?;
|
||||
Ok((raw_key_without_prefix.to_vec(), value))
|
||||
};
|
||||
|
||||
PrefixIterator { prefix, previous_key, drain: false, closure, phantom: Default::default() }
|
||||
}
|
||||
|
||||
/// Construct iterator to iterate over map items in `module` for the map called `item`.
|
||||
pub fn storage_key_iter<K: Decode + Sized, T: Decode + Sized, H: ReversibleStorageHasher>(
|
||||
module: &[u8],
|
||||
item: &[u8],
|
||||
) -> PrefixIterator<(K, T)> {
|
||||
storage_key_iter_with_suffix::<K, T, H>(module, item, &[][..])
|
||||
}
|
||||
|
||||
/// Construct iterator to iterate over map items in `module` for the map called `item`.
|
||||
pub fn storage_key_iter_with_suffix<
|
||||
K: Decode + Sized,
|
||||
T: Decode + Sized,
|
||||
H: ReversibleStorageHasher,
|
||||
>(
|
||||
module: &[u8],
|
||||
item: &[u8],
|
||||
suffix: &[u8],
|
||||
) -> PrefixIterator<(K, T)> {
|
||||
let mut prefix = Vec::new();
|
||||
let storage_prefix = storage_prefix(module, item);
|
||||
|
||||
prefix.extend_from_slice(&storage_prefix);
|
||||
prefix.extend_from_slice(suffix);
|
||||
let previous_key = prefix.clone();
|
||||
let closure = |raw_key_without_prefix: &[u8], mut raw_value: &[u8]| {
|
||||
let mut key_material = H::reverse(raw_key_without_prefix);
|
||||
let key = K::decode(&mut key_material)?;
|
||||
let value = T::decode(&mut raw_value)?;
|
||||
Ok((key, value))
|
||||
};
|
||||
PrefixIterator { prefix, previous_key, drain: false, closure, phantom: Default::default() }
|
||||
}
|
||||
|
||||
/// Get a particular value in storage by the `module`, the map's `item` name and the key `hash`.
|
||||
pub fn have_storage_value(module: &[u8], item: &[u8], hash: &[u8]) -> bool {
|
||||
get_storage_value::<()>(module, item, hash).is_some()
|
||||
}
|
||||
|
||||
/// Get a particular value in storage by the `module`, the map's `item` name and the key `hash`.
|
||||
pub fn get_storage_value<T: Decode + Sized>(module: &[u8], item: &[u8], hash: &[u8]) -> Option<T> {
|
||||
let mut key = vec![0u8; 32 + hash.len()];
|
||||
let storage_prefix = storage_prefix(module, item);
|
||||
key[0..32].copy_from_slice(&storage_prefix);
|
||||
key[32..].copy_from_slice(hash);
|
||||
pezframe_support::storage::unhashed::get::<T>(&key)
|
||||
}
|
||||
|
||||
/// Take a particular value in storage by the `module`, the map's `item` name and the key `hash`.
|
||||
pub fn take_storage_value<T: Decode + Sized>(module: &[u8], item: &[u8], hash: &[u8]) -> Option<T> {
|
||||
let mut key = vec![0u8; 32 + hash.len()];
|
||||
let storage_prefix = storage_prefix(module, item);
|
||||
key[0..32].copy_from_slice(&storage_prefix);
|
||||
key[32..].copy_from_slice(hash);
|
||||
pezframe_support::storage::unhashed::take::<T>(&key)
|
||||
}
|
||||
|
||||
/// Put a particular value into storage by the `module`, the map's `item` name and the key `hash`.
|
||||
pub fn put_storage_value<T: Encode>(module: &[u8], item: &[u8], hash: &[u8], value: T) {
|
||||
let mut key = vec![0u8; 32 + hash.len()];
|
||||
let storage_prefix = storage_prefix(module, item);
|
||||
key[0..32].copy_from_slice(&storage_prefix);
|
||||
key[32..].copy_from_slice(hash);
|
||||
pezframe_support::storage::unhashed::put(&key, &value);
|
||||
}
|
||||
|
||||
/// Remove all items under a storage prefix by the `module`, the map's `item` name and the key
|
||||
/// `hash`.
|
||||
#[deprecated = "Use `clear_storage_prefix` instead"]
|
||||
pub fn remove_storage_prefix(module: &[u8], item: &[u8], hash: &[u8]) {
|
||||
let mut key = vec![0u8; 32 + hash.len()];
|
||||
let storage_prefix = storage_prefix(module, item);
|
||||
key[0..32].copy_from_slice(&storage_prefix);
|
||||
key[32..].copy_from_slice(hash);
|
||||
let _ = pezframe_support::storage::unhashed::clear_prefix(&key, None, None);
|
||||
}
|
||||
|
||||
/// Attempt to remove all values under a storage prefix by the `module`, the map's `item` name and
|
||||
/// the key `hash`.
|
||||
///
|
||||
/// All values in the client overlay will be deleted, if `maybe_limit` is `Some` then up to
|
||||
/// that number of values are deleted from the client backend by seeking and reading that number of
|
||||
/// storage values plus one. If `maybe_limit` is `None` then all values in the client backend are
|
||||
/// deleted. This is potentially unsafe since it's an unbounded operation.
|
||||
///
|
||||
/// ## Cursors
|
||||
///
|
||||
/// The `maybe_cursor` parameter should be `None` for the first call to initial removal.
|
||||
/// If the resultant `maybe_cursor` is `Some`, then another call is required to complete the
|
||||
/// removal operation. This value must be passed in as the subsequent call's `maybe_cursor`
|
||||
/// parameter. If the resultant `maybe_cursor` is `None`, then the operation is complete and no
|
||||
/// items remain in storage provided that no items were added between the first calls and the
|
||||
/// final call.
|
||||
pub fn clear_storage_prefix(
|
||||
module: &[u8],
|
||||
item: &[u8],
|
||||
hash: &[u8],
|
||||
maybe_limit: Option<u32>,
|
||||
maybe_cursor: Option<&[u8]>,
|
||||
) -> pezsp_io::MultiRemovalResults {
|
||||
let mut key = vec![0u8; 32 + hash.len()];
|
||||
let storage_prefix = storage_prefix(module, item);
|
||||
key[0..32].copy_from_slice(&storage_prefix);
|
||||
key[32..].copy_from_slice(hash);
|
||||
pezframe_support::storage::unhashed::clear_prefix(&key, maybe_limit, maybe_cursor)
|
||||
}
|
||||
|
||||
/// Take a particular item in storage by the `module`, the map's `item` name and the key `hash`.
|
||||
pub fn take_storage_item<K: Encode + Sized, T: Decode + Sized, H: StorageHasher>(
|
||||
module: &[u8],
|
||||
item: &[u8],
|
||||
key: K,
|
||||
) -> Option<T> {
|
||||
take_storage_value(module, item, key.using_encoded(H::hash).as_ref())
|
||||
}
|
||||
|
||||
/// Move a storage from a pallet prefix to another pallet prefix.
|
||||
///
|
||||
/// Keys used in pallet storages always start with:
|
||||
/// `concat(twox_128(pezpallet_name), twox_128(storage_name))`.
|
||||
///
|
||||
/// This function will remove all value for which the key start with
|
||||
/// `concat(twox_128(old_pallet_name), twox_128(storage_name))` and insert them at the key with
|
||||
/// the start replaced by `concat(twox_128(new_pallet_name), twox_128(storage_name))`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// If a pallet named "my_example" has 2 storages named "Foo" and "Bar" and the pallet is renamed
|
||||
/// "my_new_example_name", a migration can be:
|
||||
/// ```
|
||||
/// # use pezframe_support::storage::migration::move_storage_from_pallet;
|
||||
/// # pezsp_io::TestExternalities::new_empty().execute_with(|| {
|
||||
/// move_storage_from_pallet(b"Foo", b"my_example", b"my_new_example_name");
|
||||
/// move_storage_from_pallet(b"Bar", b"my_example", b"my_new_example_name");
|
||||
/// # })
|
||||
/// ```
|
||||
pub fn move_storage_from_pallet(
|
||||
storage_name: &[u8],
|
||||
old_pallet_name: &[u8],
|
||||
new_pallet_name: &[u8],
|
||||
) {
|
||||
let new_prefix = storage_prefix(new_pallet_name, storage_name);
|
||||
let old_prefix = storage_prefix(old_pallet_name, storage_name);
|
||||
|
||||
move_prefix(&old_prefix, &new_prefix);
|
||||
|
||||
if let Some(value) = unhashed::get_raw(&old_prefix) {
|
||||
unhashed::put_raw(&new_prefix, &value);
|
||||
unhashed::kill(&old_prefix);
|
||||
}
|
||||
}
|
||||
|
||||
/// Move all storages from a pallet prefix to another pallet prefix.
|
||||
///
|
||||
/// Keys used in pallet storages always start with:
|
||||
/// `concat(twox_128(pezpallet_name), twox_128(storage_name))`.
|
||||
///
|
||||
/// This function will remove all value for which the key start with `twox_128(old_pallet_name)`
|
||||
/// and insert them at the key with the start replaced by `twox_128(new_pallet_name)`.
|
||||
///
|
||||
/// NOTE: The value at the key `twox_128(old_pallet_name)` is not moved.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// If a pallet named "my_example" has some storages and the pallet is renamed
|
||||
/// "my_new_example_name", a migration can be:
|
||||
/// ```
|
||||
/// # use pezframe_support::storage::migration::move_pallet;
|
||||
/// # pezsp_io::TestExternalities::new_empty().execute_with(|| {
|
||||
/// move_pallet(b"my_example", b"my_new_example_name");
|
||||
/// # })
|
||||
/// ```
|
||||
pub fn move_pallet(old_pallet_name: &[u8], new_pallet_name: &[u8]) {
|
||||
move_prefix(&Twox128::hash(old_pallet_name), &Twox128::hash(new_pallet_name))
|
||||
}
|
||||
|
||||
/// Move all `(key, value)` after some prefix to the another prefix
|
||||
///
|
||||
/// This function will remove all value for which the key start with `from_prefix`
|
||||
/// and insert them at the key with the start replaced by `to_prefix`.
|
||||
///
|
||||
/// NOTE: The value at the key `from_prefix` is not moved.
|
||||
pub fn move_prefix(from_prefix: &[u8], to_prefix: &[u8]) {
|
||||
if from_prefix == to_prefix {
|
||||
return;
|
||||
}
|
||||
|
||||
let iter = PrefixIterator::<_> {
|
||||
prefix: from_prefix.to_vec(),
|
||||
previous_key: from_prefix.to_vec(),
|
||||
drain: true,
|
||||
closure: |key, value| Ok((key.to_vec(), value.to_vec())),
|
||||
phantom: Default::default(),
|
||||
};
|
||||
|
||||
for (key, value) in iter {
|
||||
let full_key = [to_prefix, &key].concat();
|
||||
unhashed::put_raw(&full_key, &value);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{
|
||||
move_pallet, move_prefix, move_storage_from_pallet, storage_iter, storage_key_iter,
|
||||
};
|
||||
use crate::{
|
||||
hash::StorageHasher,
|
||||
pezpallet_prelude::{StorageMap, StorageValue, Twox128, Twox64Concat},
|
||||
};
|
||||
use pezsp_io::TestExternalities;
|
||||
|
||||
struct OldPalletStorageValuePrefix;
|
||||
impl pezframe_support::traits::StorageInstance for OldPalletStorageValuePrefix {
|
||||
const STORAGE_PREFIX: &'static str = "foo_value";
|
||||
fn pezpallet_prefix() -> &'static str {
|
||||
"my_old_pallet"
|
||||
}
|
||||
}
|
||||
type OldStorageValue = StorageValue<OldPalletStorageValuePrefix, u32>;
|
||||
|
||||
struct OldPalletStorageMapPrefix;
|
||||
impl pezframe_support::traits::StorageInstance for OldPalletStorageMapPrefix {
|
||||
const STORAGE_PREFIX: &'static str = "foo_map";
|
||||
fn pezpallet_prefix() -> &'static str {
|
||||
"my_old_pallet"
|
||||
}
|
||||
}
|
||||
type OldStorageMap = StorageMap<OldPalletStorageMapPrefix, Twox64Concat, u32, u32>;
|
||||
|
||||
struct NewPalletStorageValuePrefix;
|
||||
impl pezframe_support::traits::StorageInstance for NewPalletStorageValuePrefix {
|
||||
const STORAGE_PREFIX: &'static str = "foo_value";
|
||||
fn pezpallet_prefix() -> &'static str {
|
||||
"my_new_pallet"
|
||||
}
|
||||
}
|
||||
type NewStorageValue = StorageValue<NewPalletStorageValuePrefix, u32>;
|
||||
|
||||
struct NewPalletStorageMapPrefix;
|
||||
impl pezframe_support::traits::StorageInstance for NewPalletStorageMapPrefix {
|
||||
const STORAGE_PREFIX: &'static str = "foo_map";
|
||||
fn pezpallet_prefix() -> &'static str {
|
||||
"my_new_pallet"
|
||||
}
|
||||
}
|
||||
type NewStorageMap = StorageMap<NewPalletStorageMapPrefix, Twox64Concat, u32, u32>;
|
||||
|
||||
#[test]
|
||||
fn test_move_prefix() {
|
||||
TestExternalities::new_empty().execute_with(|| {
|
||||
OldStorageValue::put(3);
|
||||
OldStorageMap::insert(1, 2);
|
||||
OldStorageMap::insert(3, 4);
|
||||
|
||||
move_prefix(&Twox128::hash(b"my_old_pallet"), &Twox128::hash(b"my_new_pallet"));
|
||||
|
||||
assert_eq!(OldStorageValue::get(), None);
|
||||
assert_eq!(OldStorageMap::iter().collect::<Vec<_>>(), vec![]);
|
||||
assert_eq!(NewStorageValue::get(), Some(3));
|
||||
assert_eq!(NewStorageMap::iter().collect::<Vec<_>>(), vec![(1, 2), (3, 4)]);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_move_storage() {
|
||||
TestExternalities::new_empty().execute_with(|| {
|
||||
OldStorageValue::put(3);
|
||||
OldStorageMap::insert(1, 2);
|
||||
OldStorageMap::insert(3, 4);
|
||||
|
||||
move_storage_from_pallet(b"foo_map", b"my_old_pallet", b"my_new_pallet");
|
||||
|
||||
assert_eq!(OldStorageValue::get(), Some(3));
|
||||
assert_eq!(OldStorageMap::iter().collect::<Vec<_>>(), vec![]);
|
||||
assert_eq!(NewStorageValue::get(), None);
|
||||
assert_eq!(NewStorageMap::iter().collect::<Vec<_>>(), vec![(1, 2), (3, 4)]);
|
||||
|
||||
move_storage_from_pallet(b"foo_value", b"my_old_pallet", b"my_new_pallet");
|
||||
|
||||
assert_eq!(OldStorageValue::get(), None);
|
||||
assert_eq!(OldStorageMap::iter().collect::<Vec<_>>(), vec![]);
|
||||
assert_eq!(NewStorageValue::get(), Some(3));
|
||||
assert_eq!(NewStorageMap::iter().collect::<Vec<_>>(), vec![(1, 2), (3, 4)]);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_move_pallet() {
|
||||
TestExternalities::new_empty().execute_with(|| {
|
||||
OldStorageValue::put(3);
|
||||
OldStorageMap::insert(1, 2);
|
||||
OldStorageMap::insert(3, 4);
|
||||
|
||||
move_pallet(b"my_old_pallet", b"my_new_pallet");
|
||||
|
||||
assert_eq!(OldStorageValue::get(), None);
|
||||
assert_eq!(OldStorageMap::iter().collect::<Vec<_>>(), vec![]);
|
||||
assert_eq!(NewStorageValue::get(), Some(3));
|
||||
assert_eq!(NewStorageMap::iter().collect::<Vec<_>>(), vec![(1, 2), (3, 4)]);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_storage_iter() {
|
||||
TestExternalities::new_empty().execute_with(|| {
|
||||
OldStorageValue::put(3);
|
||||
OldStorageMap::insert(1, 2);
|
||||
OldStorageMap::insert(3, 4);
|
||||
|
||||
assert_eq!(
|
||||
storage_key_iter::<i32, i32, Twox64Concat>(b"my_old_pallet", b"foo_map")
|
||||
.collect::<Vec<_>>(),
|
||||
vec![(1, 2), (3, 4)],
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
storage_iter(b"my_old_pallet", b"foo_map")
|
||||
.drain()
|
||||
.map(|t| t.1)
|
||||
.collect::<Vec<i32>>(),
|
||||
vec![2, 4],
|
||||
);
|
||||
assert_eq!(OldStorageMap::iter().collect::<Vec<_>>(), vec![]);
|
||||
|
||||
// Empty because storage iterator skips over the entry under the first key
|
||||
assert_eq!(storage_iter::<i32>(b"my_old_pallet", b"foo_value").drain().next(), None);
|
||||
assert_eq!(OldStorageValue::get(), Some(3));
|
||||
});
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,170 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Feature gated since it can panic.
|
||||
#![cfg(any(feature = "std", feature = "runtime-benchmarks", feature = "try-runtime", test))]
|
||||
|
||||
//! Contains the [`crate::StorageNoopGuard`] for conveniently asserting
|
||||
//! that no storage mutation has been made by a whole code block.
|
||||
|
||||
/// Asserts that no storage changes took place between con- and destruction of [`Self`].
|
||||
///
|
||||
/// This is easier than wrapping the whole code-block inside a `assert_storage_noop!`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```should_panic
|
||||
/// use pezframe_support::{StorageNoopGuard, storage::unhashed::put};
|
||||
///
|
||||
/// pezsp_io::TestExternalities::default().execute_with(|| {
|
||||
/// let _guard = pezframe_support::StorageNoopGuard::default();
|
||||
/// put(b"key", b"value");
|
||||
/// // Panics since there are storage changes.
|
||||
/// });
|
||||
/// ```
|
||||
#[must_use]
|
||||
pub struct StorageNoopGuard<'a> {
|
||||
storage_root: alloc::vec::Vec<u8>,
|
||||
error_message: &'a str,
|
||||
}
|
||||
|
||||
impl<'a> Default for StorageNoopGuard<'a> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
storage_root: pezsp_io::storage::root(pezsp_runtime::StateVersion::V1),
|
||||
error_message: "`StorageNoopGuard` detected an attempted storage change.",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> StorageNoopGuard<'a> {
|
||||
/// Alias to `default()`.
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Creates a new [`StorageNoopGuard`] with a custom error message.
|
||||
pub fn from_error_message(error_message: &'a str) -> Self {
|
||||
Self { storage_root: pezsp_io::storage::root(pezsp_runtime::StateVersion::V1), error_message }
|
||||
}
|
||||
|
||||
/// Sets a custom error message for a [`StorageNoopGuard`].
|
||||
pub fn set_error_message(&mut self, error_message: &'a str) {
|
||||
self.error_message = error_message;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Drop for StorageNoopGuard<'a> {
|
||||
fn drop(&mut self) {
|
||||
// No need to double panic, eg. inside a test assertion failure.
|
||||
#[cfg(feature = "std")]
|
||||
if std::thread::panicking() {
|
||||
return;
|
||||
}
|
||||
assert_eq!(
|
||||
pezsp_io::storage::root(pezsp_runtime::StateVersion::V1),
|
||||
self.storage_root,
|
||||
"{}",
|
||||
self.error_message,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use pezsp_io::TestExternalities;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "`StorageNoopGuard` detected an attempted storage change.")]
|
||||
fn storage_noop_guard_panics_on_changed() {
|
||||
TestExternalities::default().execute_with(|| {
|
||||
let _guard = StorageNoopGuard::default();
|
||||
pezframe_support::storage::unhashed::put(b"key", b"value");
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn storage_noop_guard_works_on_unchanged() {
|
||||
TestExternalities::default().execute_with(|| {
|
||||
let _guard = StorageNoopGuard::default();
|
||||
pezframe_support::storage::unhashed::put(b"key", b"value");
|
||||
pezframe_support::storage::unhashed::kill(b"key");
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "`StorageNoopGuard` detected an attempted storage change.")]
|
||||
fn storage_noop_guard_panics_on_early_drop() {
|
||||
TestExternalities::default().execute_with(|| {
|
||||
let guard = StorageNoopGuard::default();
|
||||
pezframe_support::storage::unhashed::put(b"key", b"value");
|
||||
std::mem::drop(guard);
|
||||
pezframe_support::storage::unhashed::kill(b"key");
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn storage_noop_guard_works_on_changed_forget() {
|
||||
TestExternalities::default().execute_with(|| {
|
||||
let guard = StorageNoopGuard::default();
|
||||
pezframe_support::storage::unhashed::put(b"key", b"value");
|
||||
std::mem::forget(guard);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "Something else")]
|
||||
fn storage_noop_guard_does_not_double_panic() {
|
||||
TestExternalities::default().execute_with(|| {
|
||||
let _guard = StorageNoopGuard::default();
|
||||
pezframe_support::storage::unhashed::put(b"key", b"value");
|
||||
panic!("Something else");
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "`StorageNoopGuard` found unexpected storage changes.")]
|
||||
fn storage_noop_guard_panics_created_from_error_message() {
|
||||
TestExternalities::default().execute_with(|| {
|
||||
let _guard = StorageNoopGuard::from_error_message(
|
||||
"`StorageNoopGuard` found unexpected storage changes.",
|
||||
);
|
||||
pezframe_support::storage::unhashed::put(b"key", b"value");
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "`StorageNoopGuard` found unexpected storage changes.")]
|
||||
fn storage_noop_guard_panics_with_set_error_message() {
|
||||
TestExternalities::default().execute_with(|| {
|
||||
let mut guard = StorageNoopGuard::default();
|
||||
guard.set_error_message("`StorageNoopGuard` found unexpected storage changes.");
|
||||
pezframe_support::storage::unhashed::put(b"key", b"value");
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "`StorageNoopGuard` detected an attempted storage change.")]
|
||||
fn storage_noop_guard_panics_new_alias() {
|
||||
TestExternalities::default().execute_with(|| {
|
||||
let _guard = StorageNoopGuard::new();
|
||||
pezframe_support::storage::unhashed::put(b"key", b"value");
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,666 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::{BoundedBTreeMap, BoundedBTreeSet, BoundedVec, WeakBoundedVec};
|
||||
use alloc::vec::Vec;
|
||||
use codec::Decode;
|
||||
|
||||
/// Provides the sealed trait `StreamIter`.
|
||||
mod private {
|
||||
use super::*;
|
||||
|
||||
/// Used as marker trait for types that support stream iteration.
|
||||
pub trait StreamIter {
|
||||
/// The actual iterator implementation.
|
||||
type Iterator: core::iter::Iterator;
|
||||
|
||||
/// Create the stream iterator for the value found at `key`.
|
||||
fn stream_iter(key: Vec<u8>) -> Self::Iterator;
|
||||
}
|
||||
|
||||
impl<T: codec::Decode> StreamIter for Vec<T> {
|
||||
type Iterator = ScaleContainerStreamIter<T>;
|
||||
|
||||
fn stream_iter(key: Vec<u8>) -> Self::Iterator {
|
||||
ScaleContainerStreamIter::new(key)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: codec::Decode> StreamIter for alloc::collections::btree_set::BTreeSet<T> {
|
||||
type Iterator = ScaleContainerStreamIter<T>;
|
||||
|
||||
fn stream_iter(key: Vec<u8>) -> Self::Iterator {
|
||||
ScaleContainerStreamIter::new(key)
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: codec::Decode, V: codec::Decode> StreamIter
|
||||
for alloc::collections::btree_map::BTreeMap<K, V>
|
||||
{
|
||||
type Iterator = ScaleContainerStreamIter<(K, V)>;
|
||||
|
||||
fn stream_iter(key: Vec<u8>) -> Self::Iterator {
|
||||
ScaleContainerStreamIter::new(key)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: codec::Decode, S> StreamIter for BoundedVec<T, S> {
|
||||
type Iterator = ScaleContainerStreamIter<T>;
|
||||
|
||||
fn stream_iter(key: Vec<u8>) -> Self::Iterator {
|
||||
ScaleContainerStreamIter::new(key)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: codec::Decode, S> StreamIter for WeakBoundedVec<T, S> {
|
||||
type Iterator = ScaleContainerStreamIter<T>;
|
||||
|
||||
fn stream_iter(key: Vec<u8>) -> Self::Iterator {
|
||||
ScaleContainerStreamIter::new(key)
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: codec::Decode, V: codec::Decode, S> StreamIter for BoundedBTreeMap<K, V, S> {
|
||||
type Iterator = ScaleContainerStreamIter<(K, V)>;
|
||||
|
||||
fn stream_iter(key: Vec<u8>) -> Self::Iterator {
|
||||
ScaleContainerStreamIter::new(key)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: codec::Decode, S> StreamIter for BoundedBTreeSet<T, S> {
|
||||
type Iterator = ScaleContainerStreamIter<T>;
|
||||
|
||||
fn stream_iter(key: Vec<u8>) -> Self::Iterator {
|
||||
ScaleContainerStreamIter::new(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An iterator that streams values directly from storage.
|
||||
///
|
||||
/// Requires that `T` implements the sealed trait `StreamIter`.
|
||||
///
|
||||
/// Instead of loading the entire `T` into memory, the iterator only loads a certain number of bytes
|
||||
/// into memory to decode the next `T::Item`. The iterator implementation is allowed to have some
|
||||
/// internal buffer to reduce the number of storage reads. The iterator should have an almost
|
||||
/// constant memory usage over its lifetime. If at some point there is a decoding error, the
|
||||
/// iterator will return `None` to signal that the iterator is finished.
|
||||
pub trait StorageStreamIter<T: private::StreamIter> {
|
||||
/// Create the streaming iterator.
|
||||
fn stream_iter() -> T::Iterator;
|
||||
}
|
||||
|
||||
impl<T: private::StreamIter + codec::FullCodec, StorageValue: super::StorageValue<T>>
|
||||
StorageStreamIter<T> for StorageValue
|
||||
{
|
||||
fn stream_iter() -> T::Iterator {
|
||||
T::stream_iter(Self::hashed_key().into())
|
||||
}
|
||||
}
|
||||
|
||||
/// A streaming iterator implementation for SCALE container types.
|
||||
///
|
||||
/// SCALE container types follow the same type of encoding `Compact<u32>(len) ++ data`.
|
||||
/// This type provides an [`Iterator`](core::iter::Iterator) implementation that decodes
|
||||
/// one item after another with each call to [`next`](Self::next). The bytes representing
|
||||
/// the container are also not read at once into memory and instead being read in chunks. As long
|
||||
/// as individual items are smaller than these chunks the memory usage of this iterator should
|
||||
/// be constant. On decoding errors [`next`](Self::next) will return `None` to signal that the
|
||||
/// iterator is finished.
|
||||
pub struct ScaleContainerStreamIter<T> {
|
||||
marker: core::marker::PhantomData<T>,
|
||||
input: StorageInput,
|
||||
length: u32,
|
||||
read: u32,
|
||||
}
|
||||
|
||||
impl<T> ScaleContainerStreamIter<T> {
|
||||
/// Creates a new instance of the stream iterator.
|
||||
///
|
||||
/// - `key`: Storage key of the container in the state.
|
||||
///
|
||||
/// Same as [`Self::new_try`], but logs a potential error and sets the length to `0`.
|
||||
pub fn new(key: Vec<u8>) -> Self {
|
||||
let mut input = StorageInput::new(key);
|
||||
let length = if input.exists() {
|
||||
match codec::Compact::<u32>::decode(&mut input) {
|
||||
Ok(length) => length.0,
|
||||
Err(e) => {
|
||||
// TODO #3700: error should be handleable.
|
||||
log::error!(
|
||||
target: "runtime::storage",
|
||||
"Corrupted state at `{:?}`: failed to decode element count: {:?}",
|
||||
input.key,
|
||||
e,
|
||||
);
|
||||
|
||||
0
|
||||
},
|
||||
}
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
Self { marker: core::marker::PhantomData, input, length, read: 0 }
|
||||
}
|
||||
|
||||
/// Creates a new instance of the stream iterator.
|
||||
///
|
||||
/// - `key`: Storage key of the container in the state.
|
||||
///
|
||||
/// Returns an error if the length of the container fails to decode.
|
||||
pub fn new_try(key: Vec<u8>) -> Result<Self, codec::Error> {
|
||||
let mut input = StorageInput::new(key);
|
||||
let length = if input.exists() { codec::Compact::<u32>::decode(&mut input)?.0 } else { 0 };
|
||||
|
||||
Ok(Self { marker: core::marker::PhantomData, input, length, read: 0 })
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: codec::Decode> core::iter::Iterator for ScaleContainerStreamIter<T> {
|
||||
type Item = T;
|
||||
|
||||
fn next(&mut self) -> Option<T> {
|
||||
if self.read >= self.length {
|
||||
return None;
|
||||
}
|
||||
|
||||
match codec::Decode::decode(&mut self.input) {
|
||||
Ok(r) => {
|
||||
self.read += 1;
|
||||
Some(r)
|
||||
},
|
||||
Err(e) => {
|
||||
log::error!(
|
||||
target: "runtime::storage",
|
||||
"Corrupted state at `{:?}`: failed to decode element {} (out of {} in total): {:?}",
|
||||
self.input.key,
|
||||
self.read,
|
||||
self.length,
|
||||
e,
|
||||
);
|
||||
|
||||
self.read = self.length;
|
||||
None
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
let left = (self.length - self.read) as usize;
|
||||
|
||||
(left, Some(left))
|
||||
}
|
||||
}
|
||||
|
||||
/// The size of the internal buffer used by [`StorageInput`].
|
||||
///
|
||||
/// This internal buffer is used to speed up implementation as reading from the
|
||||
/// state for every access is too slow.
|
||||
const STORAGE_INPUT_BUFFER_CAPACITY: usize = 2 * 1024;
|
||||
|
||||
/// Implementation of [`codec::Input`] using [`pezsp_io::storage::read`].
|
||||
///
|
||||
/// Keeps an internal buffer with a size of [`STORAGE_INPUT_BUFFER_CAPACITY`]. All read accesses
|
||||
/// are tried to be served by this buffer. If the buffer doesn't hold enough bytes to fulfill the
|
||||
/// current read access, the buffer is re-filled from the state. A read request that is bigger than
|
||||
/// the internal buffer is directly forwarded to the state to reduce the number of reads from the
|
||||
/// state.
|
||||
struct StorageInput {
|
||||
key: Vec<u8>,
|
||||
offset: u32,
|
||||
total_length: u32,
|
||||
exists: bool,
|
||||
buffer: Vec<u8>,
|
||||
buffer_pos: usize,
|
||||
}
|
||||
|
||||
impl StorageInput {
|
||||
/// Create a new instance of the input.
|
||||
///
|
||||
/// - `key`: The storage key of the storage item that this input will read.
|
||||
fn new(key: Vec<u8>) -> Self {
|
||||
let mut buffer = alloc::vec![0; STORAGE_INPUT_BUFFER_CAPACITY];
|
||||
unsafe {
|
||||
buffer.set_len(buffer.capacity());
|
||||
}
|
||||
|
||||
let (total_length, exists) =
|
||||
if let Some(total_length) = pezsp_io::storage::read(&key, &mut buffer, 0) {
|
||||
(total_length, true)
|
||||
} else {
|
||||
(0, false)
|
||||
};
|
||||
|
||||
if (total_length as usize) < buffer.len() {
|
||||
unsafe {
|
||||
buffer.set_len(total_length as usize);
|
||||
}
|
||||
}
|
||||
|
||||
Self { total_length, offset: buffer.len() as u32, key, exists, buffer, buffer_pos: 0 }
|
||||
}
|
||||
|
||||
/// Fill the internal buffer from the state.
|
||||
fn fill_buffer(&mut self) -> Result<(), codec::Error> {
|
||||
self.buffer.copy_within(self.buffer_pos.., 0);
|
||||
let present_bytes = self.buffer.len() - self.buffer_pos;
|
||||
self.buffer_pos = 0;
|
||||
|
||||
unsafe {
|
||||
self.buffer.set_len(self.buffer.capacity());
|
||||
}
|
||||
|
||||
if let Some(length_minus_offset) =
|
||||
pezsp_io::storage::read(&self.key, &mut self.buffer[present_bytes..], self.offset)
|
||||
{
|
||||
let bytes_read =
|
||||
core::cmp::min(length_minus_offset as usize, self.buffer.len() - present_bytes);
|
||||
let buffer_len = present_bytes + bytes_read;
|
||||
unsafe {
|
||||
self.buffer.set_len(buffer_len);
|
||||
}
|
||||
|
||||
self.ensure_total_length_did_not_change(length_minus_offset)?;
|
||||
|
||||
self.offset += bytes_read as u32;
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
// The value was deleted, let's ensure we don't read anymore.
|
||||
self.stop_reading();
|
||||
|
||||
Err("Value doesn't exist in the state?".into())
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns if the value to read exists in the state.
|
||||
fn exists(&self) -> bool {
|
||||
self.exists
|
||||
}
|
||||
|
||||
/// Reads directly into the given slice `into`.
|
||||
///
|
||||
/// Should be used when `into.len() > self.buffer.capacity()` to reduce the number of reads from
|
||||
/// the state.
|
||||
#[inline(never)]
|
||||
fn read_big_item(&mut self, into: &mut [u8]) -> Result<(), codec::Error> {
|
||||
let num_cached = self.buffer.len() - self.buffer_pos;
|
||||
|
||||
let (out_already_read, mut out_remaining) = into.split_at_mut(num_cached);
|
||||
out_already_read.copy_from_slice(&self.buffer[self.buffer_pos..]);
|
||||
|
||||
self.buffer_pos = 0;
|
||||
unsafe {
|
||||
self.buffer.set_len(0);
|
||||
}
|
||||
|
||||
if let Some(length_minus_offset) =
|
||||
pezsp_io::storage::read(&self.key, &mut out_remaining, self.offset)
|
||||
{
|
||||
if (length_minus_offset as usize) < out_remaining.len() {
|
||||
return Err("Not enough data to fill the buffer".into());
|
||||
}
|
||||
|
||||
self.ensure_total_length_did_not_change(length_minus_offset)?;
|
||||
|
||||
self.offset += out_remaining.len() as u32;
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
// The value was deleted, let's ensure we don't read anymore.
|
||||
self.stop_reading();
|
||||
|
||||
Err("Value doesn't exist in the state?".into())
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensures that the expected total length of the value did not change.
|
||||
///
|
||||
/// On error ensures that further reading is prohibited.
|
||||
fn ensure_total_length_did_not_change(
|
||||
&mut self,
|
||||
length_minus_offset: u32,
|
||||
) -> Result<(), codec::Error> {
|
||||
if self.total_length == self.offset + length_minus_offset {
|
||||
Ok(())
|
||||
} else {
|
||||
// The value total length changed, let's ensure we don't read anymore.
|
||||
self.stop_reading();
|
||||
|
||||
Err("Storage value changed while it is being read!".into())
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensure that we are stop reading from this value in the state.
|
||||
///
|
||||
/// Should be used when there happened an unrecoverable error while reading.
|
||||
fn stop_reading(&mut self) {
|
||||
self.offset = self.total_length;
|
||||
|
||||
self.buffer_pos = 0;
|
||||
unsafe {
|
||||
self.buffer.set_len(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl codec::Input for StorageInput {
|
||||
fn remaining_len(&mut self) -> Result<Option<usize>, codec::Error> {
|
||||
Ok(Some(self.total_length.saturating_sub(
|
||||
self.offset.saturating_sub((self.buffer.len() - self.buffer_pos) as u32),
|
||||
) as usize))
|
||||
}
|
||||
|
||||
fn read(&mut self, into: &mut [u8]) -> Result<(), codec::Error> {
|
||||
// If there is still data left to be read from the state.
|
||||
if self.offset < self.total_length {
|
||||
if into.len() > self.buffer.capacity() {
|
||||
return self.read_big_item(into);
|
||||
} else if self.buffer_pos + into.len() > self.buffer.len() {
|
||||
self.fill_buffer()?;
|
||||
}
|
||||
}
|
||||
|
||||
// Guard against `fill_buffer` not reading enough data or just not having enough data
|
||||
// anymore.
|
||||
if into.len() + self.buffer_pos > self.buffer.len() {
|
||||
return Err("Not enough data to fill the buffer".into());
|
||||
}
|
||||
|
||||
let end = self.buffer_pos + into.len();
|
||||
into.copy_from_slice(&self.buffer[self.buffer_pos..end]);
|
||||
self.buffer_pos = end;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use codec::{Compact, CompactLen, Encode, Input};
|
||||
|
||||
#[crate::storage_alias]
|
||||
pub type TestVecU32 = StorageValue<Test, Vec<u32>>;
|
||||
|
||||
#[crate::storage_alias]
|
||||
pub type TestVecVecU8 = StorageValue<Test, Vec<Vec<u8>>>;
|
||||
|
||||
#[test]
|
||||
fn remaining_len_works() {
|
||||
pezsp_io::TestExternalities::default().execute_with(|| {
|
||||
let data: Vec<u32> = vec![1, 2, 3, 4, 5];
|
||||
TestVecU32::put(&data);
|
||||
|
||||
let mut input = StorageInput::new(TestVecU32::hashed_key().into());
|
||||
assert_eq!(
|
||||
5 * std::mem::size_of::<u32>() + Compact::<u32>::compact_len(&5) as usize,
|
||||
input.remaining_len().ok().flatten().unwrap()
|
||||
);
|
||||
|
||||
assert_eq!(5, Compact::<u32>::decode(&mut input).unwrap().0);
|
||||
assert_eq!(
|
||||
5 * std::mem::size_of::<u32>(),
|
||||
input.remaining_len().ok().flatten().unwrap()
|
||||
);
|
||||
|
||||
for i in &data {
|
||||
assert_eq!(*i, u32::decode(&mut input).unwrap());
|
||||
assert_eq!(
|
||||
(5 - *i as usize) * std::mem::size_of::<u32>(),
|
||||
input.remaining_len().ok().flatten().unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
let data: Vec<Vec<u8>> = vec![
|
||||
vec![0; 20],
|
||||
vec![1; STORAGE_INPUT_BUFFER_CAPACITY * 2],
|
||||
vec![2; STORAGE_INPUT_BUFFER_CAPACITY * 2],
|
||||
vec![3; 30],
|
||||
vec![4; 30],
|
||||
vec![5; STORAGE_INPUT_BUFFER_CAPACITY * 2],
|
||||
vec![6; 30],
|
||||
];
|
||||
TestVecVecU8::put(&data);
|
||||
|
||||
let mut input = StorageInput::new(TestVecVecU8::hashed_key().into());
|
||||
let total_data_len = data
|
||||
.iter()
|
||||
.map(|v| v.len() + Compact::<u32>::compact_len(&(v.len() as u32)) as usize)
|
||||
.sum::<usize>();
|
||||
assert_eq!(
|
||||
total_data_len + Compact::<u32>::compact_len(&(data.len() as u32)) as usize,
|
||||
input.remaining_len().ok().flatten().unwrap()
|
||||
);
|
||||
|
||||
assert_eq!(data.len(), Compact::<u32>::decode(&mut input).unwrap().0 as usize);
|
||||
assert_eq!(total_data_len, input.remaining_len().ok().flatten().unwrap());
|
||||
|
||||
let mut remaining_len = total_data_len;
|
||||
for i in data {
|
||||
assert_eq!(i, Vec::<u8>::decode(&mut input).unwrap());
|
||||
|
||||
remaining_len -= i.len() + Compact::<u32>::compact_len(&(i.len() as u32)) as usize;
|
||||
|
||||
assert_eq!(remaining_len, input.remaining_len().ok().flatten().unwrap());
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detects_value_total_length_change() {
|
||||
pezsp_io::TestExternalities::default().execute_with(|| {
|
||||
let test_data: Vec<Vec<Vec<u8>>> = vec![
|
||||
vec![vec![0; 20], vec![1; STORAGE_INPUT_BUFFER_CAPACITY * 2]],
|
||||
vec![
|
||||
vec![0; STORAGE_INPUT_BUFFER_CAPACITY - 1],
|
||||
vec![1; STORAGE_INPUT_BUFFER_CAPACITY - 1],
|
||||
],
|
||||
];
|
||||
|
||||
for data in test_data {
|
||||
TestVecVecU8::put(&data);
|
||||
|
||||
let mut input = StorageInput::new(TestVecVecU8::hashed_key().into());
|
||||
|
||||
Compact::<u32>::decode(&mut input).unwrap();
|
||||
Vec::<u8>::decode(&mut input).unwrap();
|
||||
|
||||
TestVecVecU8::append(vec![1, 2, 3]);
|
||||
|
||||
assert!(Vec::<u8>::decode(&mut input)
|
||||
.unwrap_err()
|
||||
.to_string()
|
||||
.contains("Storage value changed while it is being read"));
|
||||
|
||||
// Reading a second time should now prevent reading at all.
|
||||
assert!(Vec::<u8>::decode(&mut input)
|
||||
.unwrap_err()
|
||||
.to_string()
|
||||
.contains("Not enough data to fill the buffer"));
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stream_read_test() {
|
||||
pezsp_io::TestExternalities::default().execute_with(|| {
|
||||
let data: Vec<u32> = vec![1, 2, 3, 4, 5];
|
||||
TestVecU32::put(&data);
|
||||
|
||||
assert_eq!(data, TestVecU32::stream_iter().collect::<Vec<_>>());
|
||||
|
||||
let data: Vec<Vec<u8>> = vec![vec![0; 3000], vec![1; 2500]];
|
||||
TestVecVecU8::put(&data);
|
||||
|
||||
assert_eq!(data, TestVecVecU8::stream_iter().collect::<Vec<_>>());
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reading_big_intermediate_value() {
|
||||
pezsp_io::TestExternalities::default().execute_with(|| {
|
||||
let data: Vec<Vec<u8>> =
|
||||
vec![vec![0; 20], vec![1; STORAGE_INPUT_BUFFER_CAPACITY * 2], vec![2; 30]];
|
||||
TestVecVecU8::put(&data);
|
||||
|
||||
assert_eq!(data, TestVecVecU8::stream_iter().collect::<Vec<_>>());
|
||||
|
||||
let data: Vec<Vec<u8>> = vec![
|
||||
vec![0; 20],
|
||||
vec![1; STORAGE_INPUT_BUFFER_CAPACITY * 2],
|
||||
vec![2; STORAGE_INPUT_BUFFER_CAPACITY * 2],
|
||||
vec![3; 30],
|
||||
vec![4; 30],
|
||||
vec![5; STORAGE_INPUT_BUFFER_CAPACITY * 2],
|
||||
vec![6; 30],
|
||||
];
|
||||
TestVecVecU8::put(&data);
|
||||
|
||||
assert_eq!(data, TestVecVecU8::stream_iter().collect::<Vec<_>>());
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reading_more_data_as_in_the_state_is_detected() {
|
||||
pezsp_io::TestExternalities::default().execute_with(|| {
|
||||
let data: Vec<Vec<u8>> = vec![vec![0; 20], vec![1; STORAGE_INPUT_BUFFER_CAPACITY * 2]];
|
||||
TestVecVecU8::put(&data);
|
||||
|
||||
let mut input = StorageInput::new(TestVecVecU8::hashed_key().into());
|
||||
|
||||
Compact::<u32>::decode(&mut input).unwrap();
|
||||
|
||||
Vec::<u8>::decode(&mut input).unwrap();
|
||||
|
||||
let mut buffer = vec![0; STORAGE_INPUT_BUFFER_CAPACITY * 4];
|
||||
assert!(input
|
||||
.read(&mut buffer)
|
||||
.unwrap_err()
|
||||
.to_string()
|
||||
.contains("Not enough data to fill the buffer"));
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reading_invalid_data_from_state() {
|
||||
pezsp_io::TestExternalities::default().execute_with(|| {
|
||||
let data: Vec<u32> = vec![1, 2, 3, 4, 5];
|
||||
|
||||
let mut data_encoded = data.encode();
|
||||
data_encoded.truncate(data_encoded.len() - 2);
|
||||
pezsp_io::storage::set(&TestVecU32::hashed_key(), &data_encoded);
|
||||
assert_eq!(
|
||||
data.iter().copied().take(data.len() - 1).collect::<Vec<_>>(),
|
||||
TestVecU32::stream_iter().collect::<Vec<_>>()
|
||||
);
|
||||
|
||||
let data_encoded = data.encode()[2..].to_vec();
|
||||
pezsp_io::storage::set(&TestVecU32::hashed_key(), &data_encoded);
|
||||
assert!(TestVecU32::stream_iter().collect::<Vec<_>>().is_empty());
|
||||
|
||||
let data: Vec<Vec<u8>> = vec![vec![0; 20], vec![1; STORAGE_INPUT_BUFFER_CAPACITY * 2]];
|
||||
let mut data_encoded = data.encode();
|
||||
data_encoded.truncate(data_encoded.len() - 100);
|
||||
pezsp_io::storage::set(&TestVecVecU8::hashed_key(), &data_encoded);
|
||||
|
||||
assert_eq!(
|
||||
data.iter().cloned().take(1).collect::<Vec<_>>(),
|
||||
TestVecVecU8::stream_iter().collect::<Vec<_>>()
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reading_with_fill_buffer() {
|
||||
pezsp_io::TestExternalities::default().execute_with(|| {
|
||||
const BUFFER_SIZE: usize = 300;
|
||||
// Ensure that the capacity isn't dividable by `300`.
|
||||
assert!(STORAGE_INPUT_BUFFER_CAPACITY % BUFFER_SIZE != 0, "Please update buffer size");
|
||||
// Create some items where the last item is partially in the inner buffer so that
|
||||
// we need to fill the buffer to read the entire item.
|
||||
let data: Vec<Vec<u8>> = (0..=(STORAGE_INPUT_BUFFER_CAPACITY / BUFFER_SIZE))
|
||||
.into_iter()
|
||||
.map(|i| vec![i as u8; BUFFER_SIZE])
|
||||
.collect::<Vec<Vec<u8>>>();
|
||||
TestVecVecU8::put(&data);
|
||||
|
||||
assert_eq!(data, TestVecVecU8::stream_iter().collect::<Vec<_>>());
|
||||
|
||||
let mut input = StorageInput::new(TestVecVecU8::hashed_key().into());
|
||||
|
||||
Compact::<u32>::decode(&mut input).unwrap();
|
||||
|
||||
(0..data.len() - 1).into_iter().for_each(|_| {
|
||||
Vec::<u8>::decode(&mut input).unwrap();
|
||||
});
|
||||
|
||||
// Try reading a more data than there should be left.
|
||||
let mut result_buffer = vec![0; BUFFER_SIZE * 2];
|
||||
assert!(input
|
||||
.read(&mut result_buffer)
|
||||
.unwrap_err()
|
||||
.to_string()
|
||||
.contains("Not enough data to fill the buffer"));
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detect_value_deleted_in_state() {
|
||||
pezsp_io::TestExternalities::default().execute_with(|| {
|
||||
let data: Vec<Vec<u8>> = vec![vec![0; 20], vec![1; STORAGE_INPUT_BUFFER_CAPACITY * 2]];
|
||||
TestVecVecU8::put(&data);
|
||||
|
||||
let mut input = StorageInput::new(TestVecVecU8::hashed_key().into());
|
||||
TestVecVecU8::kill();
|
||||
|
||||
Compact::<u32>::decode(&mut input).unwrap();
|
||||
Vec::<u8>::decode(&mut input).unwrap();
|
||||
|
||||
assert!(Vec::<u8>::decode(&mut input)
|
||||
.unwrap_err()
|
||||
.to_string()
|
||||
.contains("Value doesn't exist in the state?"));
|
||||
|
||||
const BUFFER_SIZE: usize = 300;
|
||||
// Ensure that the capacity isn't dividable by `300`.
|
||||
assert!(STORAGE_INPUT_BUFFER_CAPACITY % BUFFER_SIZE != 0, "Please update buffer size");
|
||||
// Create some items where the last item is partially in the inner buffer so that
|
||||
// we need to fill the buffer to read the entire item.
|
||||
let data: Vec<Vec<u8>> = (0..=(STORAGE_INPUT_BUFFER_CAPACITY / BUFFER_SIZE))
|
||||
.into_iter()
|
||||
.map(|i| vec![i as u8; BUFFER_SIZE])
|
||||
.collect::<Vec<Vec<u8>>>();
|
||||
TestVecVecU8::put(&data);
|
||||
|
||||
let mut input = StorageInput::new(TestVecVecU8::hashed_key().into());
|
||||
TestVecVecU8::kill();
|
||||
|
||||
Compact::<u32>::decode(&mut input).unwrap();
|
||||
(0..data.len() - 1).into_iter().for_each(|_| {
|
||||
Vec::<u8>::decode(&mut input).unwrap();
|
||||
});
|
||||
|
||||
assert!(Vec::<u8>::decode(&mut input)
|
||||
.unwrap_err()
|
||||
.to_string()
|
||||
.contains("Value doesn't exist in the state?"));
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,319 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Provides functionality around the transaction storage.
|
||||
//!
|
||||
//! Transactional storage provides functionality to run an entire code block
|
||||
//! in a storage transaction. This means that either the entire changes to the
|
||||
//! storage are committed or everything is thrown away. This simplifies the
|
||||
//! writing of functionality that may bail at any point of operation. Otherwise
|
||||
//! you would need to first verify all storage accesses and then do the storage
|
||||
//! modifications.
|
||||
//!
|
||||
//! [`with_transaction`] provides a way to run a given closure in a transactional context.
|
||||
|
||||
use pezsp_io::storage::{commit_transaction, rollback_transaction, start_transaction};
|
||||
use pezsp_runtime::{DispatchError, TransactionOutcome, TransactionalError};
|
||||
|
||||
/// The type that is being used to store the current number of active layers.
|
||||
pub type Layer = u32;
|
||||
/// The key that is holds the current number of active layers.
|
||||
///
|
||||
/// Encodes to `0x3a7472616e73616374696f6e5f6c6576656c3a`.
|
||||
pub const TRANSACTION_LEVEL_KEY: &[u8] = b":transaction_level:";
|
||||
/// The maximum number of nested layers.
|
||||
pub const TRANSACTIONAL_LIMIT: Layer = 255;
|
||||
|
||||
/// Returns the current number of nested transactional layers.
|
||||
fn get_transaction_level() -> Layer {
|
||||
crate::storage::unhashed::get_or_default::<Layer>(TRANSACTION_LEVEL_KEY)
|
||||
}
|
||||
|
||||
/// Set the current number of nested transactional layers.
|
||||
fn set_transaction_level(level: Layer) {
|
||||
crate::storage::unhashed::put::<Layer>(TRANSACTION_LEVEL_KEY, &level);
|
||||
}
|
||||
|
||||
/// Kill the transactional layers storage.
|
||||
fn kill_transaction_level() {
|
||||
crate::storage::unhashed::kill(TRANSACTION_LEVEL_KEY);
|
||||
}
|
||||
|
||||
/// Increments the transaction level. Returns an error if levels go past the limit.
|
||||
///
|
||||
/// Returns a guard that when dropped decrements the transaction level automatically.
|
||||
fn inc_transaction_level() -> Result<StorageLayerGuard, ()> {
|
||||
let existing_levels = get_transaction_level();
|
||||
if existing_levels >= TRANSACTIONAL_LIMIT {
|
||||
return Err(());
|
||||
}
|
||||
// Cannot overflow because of check above.
|
||||
set_transaction_level(existing_levels + 1);
|
||||
Ok(StorageLayerGuard)
|
||||
}
|
||||
|
||||
fn dec_transaction_level() {
|
||||
let existing_levels = get_transaction_level();
|
||||
if existing_levels == 0 {
|
||||
log::warn!(
|
||||
"We are underflowing with calculating transactional levels. Not great, but let's not panic...",
|
||||
);
|
||||
} else if existing_levels == 1 {
|
||||
// Don't leave any trace of this storage item.
|
||||
kill_transaction_level();
|
||||
} else {
|
||||
// Cannot underflow because of checks above.
|
||||
set_transaction_level(existing_levels - 1);
|
||||
}
|
||||
}
|
||||
|
||||
struct StorageLayerGuard;
|
||||
|
||||
impl Drop for StorageLayerGuard {
|
||||
fn drop(&mut self) {
|
||||
dec_transaction_level()
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if the current call is within a transactional layer.
|
||||
pub fn is_transactional() -> bool {
|
||||
get_transaction_level() > 0
|
||||
}
|
||||
|
||||
/// Execute the supplied function in a new storage transaction.
|
||||
///
|
||||
/// All changes to storage performed by the supplied function are discarded if the returned
|
||||
/// outcome is `TransactionOutcome::Rollback`.
|
||||
///
|
||||
/// Transactions can be nested up to `TRANSACTIONAL_LIMIT` times; more than that will result in an
|
||||
/// error.
|
||||
///
|
||||
/// Commits happen to the parent transaction.
|
||||
pub fn with_transaction<T, E, F>(f: F) -> Result<T, E>
|
||||
where
|
||||
E: From<DispatchError>,
|
||||
F: FnOnce() -> TransactionOutcome<Result<T, E>>,
|
||||
{
|
||||
// This needs to happen before `start_transaction` below.
|
||||
// Otherwise we may rollback the increase, then decrease as the guard goes out of scope
|
||||
// and then end in some bad state.
|
||||
let _guard = inc_transaction_level().map_err(|()| TransactionalError::LimitReached.into())?;
|
||||
|
||||
start_transaction();
|
||||
|
||||
match f() {
|
||||
TransactionOutcome::Commit(res) => {
|
||||
commit_transaction();
|
||||
res
|
||||
},
|
||||
TransactionOutcome::Rollback(res) => {
|
||||
rollback_transaction();
|
||||
res
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Same as [`with_transaction`] but casts any internal error to `()`.
|
||||
///
|
||||
/// This rids `E` of the `From<DispatchError>` bound that is required by `with_transaction`.
|
||||
pub fn with_transaction_opaque_err<T, E, F>(f: F) -> Result<Result<T, E>, ()>
|
||||
where
|
||||
F: FnOnce() -> TransactionOutcome<Result<T, E>>,
|
||||
{
|
||||
with_transaction(move || -> TransactionOutcome<Result<Result<T, E>, DispatchError>> {
|
||||
match f() {
|
||||
TransactionOutcome::Commit(res) => TransactionOutcome::Commit(Ok(res)),
|
||||
TransactionOutcome::Rollback(res) => TransactionOutcome::Rollback(Ok(res)),
|
||||
}
|
||||
})
|
||||
.map_err(|_| ())
|
||||
}
|
||||
|
||||
/// Same as [`with_transaction`] but without a limit check on nested transactional layers.
|
||||
///
|
||||
/// This is mostly for backwards compatibility before there was a transactional layer limit.
|
||||
/// It is recommended to only use [`with_transaction`] to avoid users from generating too many
|
||||
/// transactional layers.
|
||||
pub fn with_transaction_unchecked<R, F>(f: F) -> R
|
||||
where
|
||||
F: FnOnce() -> TransactionOutcome<R>,
|
||||
{
|
||||
// This needs to happen before `start_transaction` below.
|
||||
// Otherwise we may rollback the increase, then decrease as the guard goes out of scope
|
||||
// and then end in some bad state.
|
||||
let maybe_guard = inc_transaction_level();
|
||||
|
||||
if maybe_guard.is_err() {
|
||||
log::warn!(
|
||||
"The transactional layer limit has been reached, and new transactional layers are being
|
||||
spawned with `with_transaction_unchecked`. This could be caused by someone trying to
|
||||
attack your chain, and you should investigate usage of `with_transaction_unchecked` and
|
||||
potentially migrate to `with_transaction`, which enforces a transactional limit.",
|
||||
);
|
||||
}
|
||||
|
||||
start_transaction();
|
||||
|
||||
match f() {
|
||||
TransactionOutcome::Commit(res) => {
|
||||
commit_transaction();
|
||||
res
|
||||
},
|
||||
TransactionOutcome::Rollback(res) => {
|
||||
rollback_transaction();
|
||||
res
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Execute the supplied function, adding a new storage layer.
|
||||
///
|
||||
/// This is the same as `with_transaction`, but assuming that any function returning an `Err` should
|
||||
/// rollback, and any function returning `Ok` should commit. This provides a cleaner API to the
|
||||
/// developer who wants this behavior.
|
||||
pub fn with_storage_layer<T, E, F>(f: F) -> Result<T, E>
|
||||
where
|
||||
E: From<DispatchError>,
|
||||
F: FnOnce() -> Result<T, E>,
|
||||
{
|
||||
with_transaction(|| {
|
||||
let r = f();
|
||||
if r.is_ok() {
|
||||
TransactionOutcome::Commit(r)
|
||||
} else {
|
||||
TransactionOutcome::Rollback(r)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Execute the supplied function, ensuring we are at least in one storage layer.
|
||||
///
|
||||
/// If we are already in a storage layer, we just execute the provided closure.
|
||||
/// If we are not, we execute the closure within a [`with_storage_layer`].
|
||||
pub fn in_storage_layer<T, E, F>(f: F) -> Result<T, E>
|
||||
where
|
||||
E: From<DispatchError>,
|
||||
F: FnOnce() -> Result<T, E>,
|
||||
{
|
||||
if is_transactional() {
|
||||
f()
|
||||
} else {
|
||||
with_storage_layer(f)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{assert_noop, assert_ok};
|
||||
use pezsp_io::TestExternalities;
|
||||
use pezsp_runtime::DispatchResult;
|
||||
|
||||
#[test]
|
||||
fn is_transactional_should_return_false() {
|
||||
TestExternalities::default().execute_with(|| {
|
||||
assert!(!is_transactional());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_transactional_should_not_error_in_with_transaction() {
|
||||
TestExternalities::default().execute_with(|| {
|
||||
assert_ok!(with_transaction(|| -> TransactionOutcome<DispatchResult> {
|
||||
assert!(is_transactional());
|
||||
TransactionOutcome::Commit(Ok(()))
|
||||
}));
|
||||
|
||||
assert_noop!(
|
||||
with_transaction(|| -> TransactionOutcome<DispatchResult> {
|
||||
assert!(is_transactional());
|
||||
TransactionOutcome::Rollback(Err("revert".into()))
|
||||
}),
|
||||
"revert"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
fn recursive_transactional(num: u32) -> DispatchResult {
|
||||
if num == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
with_transaction(|| -> TransactionOutcome<DispatchResult> {
|
||||
let res = recursive_transactional(num - 1);
|
||||
TransactionOutcome::Commit(res)
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transaction_limit_should_work() {
|
||||
TestExternalities::default().execute_with(|| {
|
||||
assert_eq!(get_transaction_level(), 0);
|
||||
|
||||
assert_ok!(with_transaction(|| -> TransactionOutcome<DispatchResult> {
|
||||
assert_eq!(get_transaction_level(), 1);
|
||||
TransactionOutcome::Commit(Ok(()))
|
||||
}));
|
||||
|
||||
assert_ok!(with_transaction(|| -> TransactionOutcome<DispatchResult> {
|
||||
assert_eq!(get_transaction_level(), 1);
|
||||
let res = with_transaction(|| -> TransactionOutcome<DispatchResult> {
|
||||
assert_eq!(get_transaction_level(), 2);
|
||||
TransactionOutcome::Commit(Ok(()))
|
||||
});
|
||||
TransactionOutcome::Commit(res)
|
||||
}));
|
||||
|
||||
assert_ok!(recursive_transactional(255));
|
||||
assert_noop!(
|
||||
recursive_transactional(256),
|
||||
pezsp_runtime::TransactionalError::LimitReached
|
||||
);
|
||||
|
||||
assert_eq!(get_transaction_level(), 0);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn in_storage_layer_works() {
|
||||
TestExternalities::default().execute_with(|| {
|
||||
assert_eq!(get_transaction_level(), 0);
|
||||
|
||||
let res = in_storage_layer(|| -> DispatchResult {
|
||||
assert_eq!(get_transaction_level(), 1);
|
||||
in_storage_layer(|| -> DispatchResult {
|
||||
// We are still in the same layer :)
|
||||
assert_eq!(get_transaction_level(), 1);
|
||||
Ok(())
|
||||
})
|
||||
});
|
||||
|
||||
assert_ok!(res);
|
||||
|
||||
let res = in_storage_layer(|| -> DispatchResult {
|
||||
assert_eq!(get_transaction_level(), 1);
|
||||
in_storage_layer(|| -> DispatchResult {
|
||||
// We are still in the same layer :)
|
||||
assert_eq!(get_transaction_level(), 1);
|
||||
Err("epic fail".into())
|
||||
})
|
||||
});
|
||||
|
||||
assert_noop!(res, "epic fail");
|
||||
});
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+282
@@ -0,0 +1,282 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Storage key type.
|
||||
|
||||
use crate::hash::{ReversibleStorageHasher, StorageHasher};
|
||||
use alloc::{boxed::Box, vec::Vec};
|
||||
use codec::{Encode, EncodeLike, FullCodec, MaxEncodedLen};
|
||||
use paste::paste;
|
||||
use scale_info::StaticTypeInfo;
|
||||
|
||||
/// A type used exclusively by storage maps as their key type.
|
||||
///
|
||||
/// The final key generated has the following form:
|
||||
/// ```nocompile
|
||||
/// Hasher1(encode(key1))
|
||||
/// ++ Hasher2(encode(key2))
|
||||
/// ++ ...
|
||||
/// ++ HasherN(encode(keyN))
|
||||
/// ```
|
||||
pub struct Key<Hasher, KeyType>(core::marker::PhantomData<(Hasher, KeyType)>);
|
||||
|
||||
/// A trait that contains the current key as an associated type.
|
||||
pub trait KeyGenerator {
|
||||
type Key: EncodeLike<Self::Key> + StaticTypeInfo;
|
||||
type KArg: Encode + EncodeLike<Self::KArg>;
|
||||
type HashFn: FnOnce(&[u8]) -> Vec<u8>;
|
||||
type HArg;
|
||||
|
||||
const HASHER_METADATA: &'static [pezsp_metadata_ir::StorageHasherIR];
|
||||
|
||||
/// Given a `key` tuple, calculate the final key by encoding each element individually and
|
||||
/// hashing them using the corresponding hasher in the `KeyGenerator`.
|
||||
fn final_key<KArg: EncodeLikeTuple<Self::KArg> + TupleToEncodedIter>(key: KArg) -> Vec<u8>;
|
||||
/// Given a `key` tuple, migrate the keys from using the old hashers as given by `hash_fns`
|
||||
/// to using the newer hashers as specified by this `KeyGenerator`.
|
||||
fn migrate_key<KArg: EncodeLikeTuple<Self::KArg> + TupleToEncodedIter>(
|
||||
key: &KArg,
|
||||
hash_fns: Self::HArg,
|
||||
) -> Vec<u8>;
|
||||
}
|
||||
|
||||
/// The maximum length used by the key in storage.
|
||||
pub trait KeyGeneratorMaxEncodedLen: KeyGenerator {
|
||||
fn key_max_encoded_len() -> usize;
|
||||
}
|
||||
|
||||
/// A trait containing methods that are only implemented on the Key struct instead of the entire
|
||||
/// tuple.
|
||||
pub trait KeyGeneratorInner: KeyGenerator {
|
||||
type Hasher: StorageHasher;
|
||||
|
||||
/// Hash a given `encoded` byte slice using the `KeyGenerator`'s associated `StorageHasher`.
|
||||
fn final_hash(encoded: &[u8]) -> Vec<u8>;
|
||||
}
|
||||
|
||||
impl<H: StorageHasher, K: FullCodec + StaticTypeInfo> KeyGenerator for Key<H, K> {
|
||||
type Key = K;
|
||||
type KArg = (K,);
|
||||
type HashFn = Box<dyn FnOnce(&[u8]) -> Vec<u8>>;
|
||||
type HArg = (Self::HashFn,);
|
||||
|
||||
const HASHER_METADATA: &'static [pezsp_metadata_ir::StorageHasherIR] = &[H::METADATA];
|
||||
|
||||
fn final_key<KArg: EncodeLikeTuple<Self::KArg> + TupleToEncodedIter>(key: KArg) -> Vec<u8> {
|
||||
H::hash(&key.to_encoded_iter().next().expect("should have at least one element!"))
|
||||
.as_ref()
|
||||
.to_vec()
|
||||
}
|
||||
|
||||
fn migrate_key<KArg: EncodeLikeTuple<Self::KArg> + TupleToEncodedIter>(
|
||||
key: &KArg,
|
||||
hash_fns: Self::HArg,
|
||||
) -> Vec<u8> {
|
||||
(hash_fns.0)(&key.to_encoded_iter().next().expect("should have at least one element!"))
|
||||
}
|
||||
}
|
||||
|
||||
impl<H: StorageHasher, K: FullCodec + MaxEncodedLen + StaticTypeInfo> KeyGeneratorMaxEncodedLen
|
||||
for Key<H, K>
|
||||
{
|
||||
fn key_max_encoded_len() -> usize {
|
||||
H::max_len::<K>()
|
||||
}
|
||||
}
|
||||
|
||||
impl<H: StorageHasher, K: FullCodec + StaticTypeInfo> KeyGeneratorInner for Key<H, K> {
|
||||
type Hasher = H;
|
||||
|
||||
fn final_hash(encoded: &[u8]) -> Vec<u8> {
|
||||
H::hash(encoded).as_ref().to_vec()
|
||||
}
|
||||
}
|
||||
|
||||
#[impl_trait_for_tuples::impl_for_tuples(1, 18)]
|
||||
#[tuple_types_custom_trait_bound(KeyGeneratorInner)]
|
||||
impl KeyGenerator for Tuple {
|
||||
for_tuples!( type Key = ( #(Tuple::Key),* ); );
|
||||
for_tuples!( type KArg = ( #(Tuple::Key),* ); );
|
||||
for_tuples!( type HArg = ( #(Tuple::HashFn),* ); );
|
||||
type HashFn = Box<dyn FnOnce(&[u8]) -> Vec<u8>>;
|
||||
|
||||
const HASHER_METADATA: &'static [pezsp_metadata_ir::StorageHasherIR] =
|
||||
&[for_tuples!( #(Tuple::Hasher::METADATA),* )];
|
||||
|
||||
fn final_key<KArg: EncodeLikeTuple<Self::KArg> + TupleToEncodedIter>(key: KArg) -> Vec<u8> {
|
||||
let mut final_key = Vec::new();
|
||||
let mut iter = key.to_encoded_iter();
|
||||
for_tuples!(
|
||||
#(
|
||||
let next_encoded = iter.next().expect("KArg number should be equal to Key number");
|
||||
final_key.extend_from_slice(&Tuple::final_hash(&next_encoded));
|
||||
)*
|
||||
);
|
||||
final_key
|
||||
}
|
||||
|
||||
fn migrate_key<KArg: EncodeLikeTuple<Self::KArg> + TupleToEncodedIter>(
|
||||
key: &KArg,
|
||||
hash_fns: Self::HArg,
|
||||
) -> Vec<u8> {
|
||||
let mut migrated_key = Vec::new();
|
||||
let mut iter = key.to_encoded_iter();
|
||||
for_tuples!(
|
||||
#(
|
||||
let next_encoded = iter.next().expect("KArg number should be equal to Key number");
|
||||
migrated_key.extend_from_slice(&(hash_fns.Tuple)(&next_encoded));
|
||||
)*
|
||||
);
|
||||
migrated_key
|
||||
}
|
||||
}
|
||||
|
||||
#[impl_trait_for_tuples::impl_for_tuples(1, 18)]
|
||||
#[tuple_types_custom_trait_bound(KeyGeneratorInner + KeyGeneratorMaxEncodedLen)]
|
||||
impl KeyGeneratorMaxEncodedLen for Tuple {
|
||||
fn key_max_encoded_len() -> usize {
|
||||
let mut len = 0usize;
|
||||
for_tuples!(
|
||||
#(
|
||||
len = len.saturating_add(Tuple::key_max_encoded_len());
|
||||
)*
|
||||
);
|
||||
len
|
||||
}
|
||||
}
|
||||
|
||||
/// Marker trait to indicate that each element in the tuple encodes like the corresponding element
|
||||
/// in another tuple.
|
||||
///
|
||||
/// This trait is sealed.
|
||||
pub trait EncodeLikeTuple<T>: crate::storage::private::Sealed {}
|
||||
|
||||
macro_rules! impl_encode_like_tuples {
|
||||
($($elem:ident),+) => {
|
||||
paste! {
|
||||
impl<$($elem: Encode,)+ $([<$elem $elem>]: Encode + EncodeLike<$elem>,)+>
|
||||
EncodeLikeTuple<($($elem,)+)> for
|
||||
($([<$elem $elem>],)+) {}
|
||||
impl<$($elem: Encode,)+ $([<$elem $elem>]: Encode + EncodeLike<$elem>,)+>
|
||||
EncodeLikeTuple<($($elem,)+)> for
|
||||
&($([<$elem $elem>],)+) {}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_encode_like_tuples!(A);
|
||||
impl_encode_like_tuples!(A, B);
|
||||
impl_encode_like_tuples!(A, B, C);
|
||||
impl_encode_like_tuples!(A, B, C, D);
|
||||
impl_encode_like_tuples!(A, B, C, D, E);
|
||||
impl_encode_like_tuples!(A, B, C, D, E, F);
|
||||
impl_encode_like_tuples!(A, B, C, D, E, F, G);
|
||||
impl_encode_like_tuples!(A, B, C, D, E, F, G, H);
|
||||
impl_encode_like_tuples!(A, B, C, D, E, F, G, H, I);
|
||||
impl_encode_like_tuples!(A, B, C, D, E, F, G, H, I, J);
|
||||
impl_encode_like_tuples!(A, B, C, D, E, F, G, H, I, J, K);
|
||||
impl_encode_like_tuples!(A, B, C, D, E, F, G, H, I, J, K, L);
|
||||
impl_encode_like_tuples!(A, B, C, D, E, F, G, H, I, J, K, L, M);
|
||||
impl_encode_like_tuples!(A, B, C, D, E, F, G, H, I, J, K, L, M, O);
|
||||
impl_encode_like_tuples!(A, B, C, D, E, F, G, H, I, J, K, L, M, O, P);
|
||||
impl_encode_like_tuples!(A, B, C, D, E, F, G, H, I, J, K, L, M, O, P, Q);
|
||||
impl_encode_like_tuples!(A, B, C, D, E, F, G, H, I, J, K, L, M, O, P, Q, R);
|
||||
|
||||
impl<'a, T: EncodeLike<U> + EncodeLikeTuple<U>, U: Encode> EncodeLikeTuple<U>
|
||||
for codec::Ref<'a, T, U>
|
||||
{
|
||||
}
|
||||
|
||||
/// Trait to indicate that a tuple can be converted into an iterator of a vector of encoded bytes.
|
||||
pub trait TupleToEncodedIter {
|
||||
fn to_encoded_iter(&self) -> alloc::vec::IntoIter<Vec<u8>>;
|
||||
}
|
||||
|
||||
#[impl_trait_for_tuples::impl_for_tuples(1, 18)]
|
||||
#[tuple_types_custom_trait_bound(Encode)]
|
||||
impl TupleToEncodedIter for Tuple {
|
||||
fn to_encoded_iter(&self) -> alloc::vec::IntoIter<Vec<u8>> {
|
||||
[for_tuples!( #(self.Tuple.encode()),* )].to_vec().into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: TupleToEncodedIter> TupleToEncodedIter for &T {
|
||||
fn to_encoded_iter(&self) -> alloc::vec::IntoIter<Vec<u8>> {
|
||||
(*self).to_encoded_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: EncodeLike<U> + TupleToEncodedIter, U: Encode> TupleToEncodedIter
|
||||
for codec::Ref<'a, T, U>
|
||||
{
|
||||
fn to_encoded_iter(&self) -> alloc::vec::IntoIter<Vec<u8>> {
|
||||
use core::ops::Deref as _;
|
||||
self.deref().to_encoded_iter()
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait that indicates the hashers for the keys generated are all reversible.
|
||||
pub trait ReversibleKeyGenerator: KeyGenerator {
|
||||
type ReversibleHasher;
|
||||
fn decode_final_key(key_material: &[u8]) -> Result<(Self::Key, &[u8]), codec::Error>;
|
||||
}
|
||||
|
||||
impl<H: ReversibleStorageHasher, K: FullCodec + StaticTypeInfo> ReversibleKeyGenerator
|
||||
for Key<H, K>
|
||||
{
|
||||
type ReversibleHasher = H;
|
||||
|
||||
fn decode_final_key(key_material: &[u8]) -> Result<(Self::Key, &[u8]), codec::Error> {
|
||||
let mut current_key_material = Self::ReversibleHasher::reverse(key_material);
|
||||
let key = K::decode(&mut current_key_material)?;
|
||||
Ok((key, current_key_material))
|
||||
}
|
||||
}
|
||||
|
||||
#[impl_trait_for_tuples::impl_for_tuples(2, 18)]
|
||||
#[tuple_types_custom_trait_bound(ReversibleKeyGenerator + KeyGeneratorInner)]
|
||||
impl ReversibleKeyGenerator for Tuple {
|
||||
for_tuples!( type ReversibleHasher = ( #(Tuple::ReversibleHasher),* ); );
|
||||
|
||||
fn decode_final_key(key_material: &[u8]) -> Result<(Self::Key, &[u8]), codec::Error> {
|
||||
let mut current_key_material = key_material;
|
||||
Ok((
|
||||
(for_tuples! {
|
||||
#({
|
||||
let (key, material) = Tuple::decode_final_key(current_key_material)?;
|
||||
current_key_material = material;
|
||||
key
|
||||
}),*
|
||||
}),
|
||||
current_key_material,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait indicating whether a KeyGenerator has the prefix P.
|
||||
pub trait HasKeyPrefix<P>: KeyGenerator {
|
||||
type Suffix;
|
||||
|
||||
fn partial_key(prefix: P) -> Vec<u8>;
|
||||
}
|
||||
|
||||
/// Trait indicating whether a ReversibleKeyGenerator has the prefix P.
|
||||
pub trait HasReversibleKeyPrefix<P>: ReversibleKeyGenerator + HasKeyPrefix<P> {
|
||||
fn decode_partial_key(key_material: &[u8]) -> Result<Self::Suffix, codec::Error>;
|
||||
}
|
||||
|
||||
pezframe_support_procedural::impl_key_prefix_for_tuples!();
|
||||
@@ -0,0 +1,845 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Storage map type. Implements StorageMap, StorageIterableMap, StoragePrefixedMap traits and their
|
||||
//! methods directly.
|
||||
|
||||
use crate::{
|
||||
storage::{
|
||||
types::{OptionQuery, QueryKindTrait, StorageEntryMetadataBuilder},
|
||||
KeyLenOf, StorageAppend, StorageDecodeLength, StoragePrefixedMap, StorageTryAppend,
|
||||
},
|
||||
traits::{Get, GetDefault, StorageInfo, StorageInstance},
|
||||
StorageHasher, Twox128,
|
||||
};
|
||||
use alloc::{vec, vec::Vec};
|
||||
use codec::{Decode, Encode, EncodeLike, FullCodec, MaxEncodedLen};
|
||||
use pezframe_support::storage::StorageDecodeNonDedupLength;
|
||||
use pezsp_arithmetic::traits::SaturatedConversion;
|
||||
use pezsp_metadata_ir::{StorageEntryMetadataIR, StorageEntryTypeIR};
|
||||
|
||||
/// A type representing a *map* in storage. A *storage map* is a mapping of keys to values of a
|
||||
/// given type stored on-chain.
|
||||
///
|
||||
/// For general information regarding the `#[pallet::storage]` attribute, refer to
|
||||
/// [`crate::pezpallet_macros::storage`].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// #[pezframe_support::pallet]
|
||||
/// mod pallet {
|
||||
/// # use pezframe_support::pezpallet_prelude::*;
|
||||
/// # #[pallet::config]
|
||||
/// # pub trait Config: pezframe_system::Config {}
|
||||
/// # #[pallet::pallet]
|
||||
/// # pub struct Pallet<T>(_);
|
||||
/// /// A kitchen-sink StorageMap, with all possible additional attributes.
|
||||
/// #[pallet::storage]
|
||||
/// #[pallet::getter(fn foo)]
|
||||
/// #[pallet::storage_prefix = "OtherFoo"]
|
||||
/// #[pallet::unbounded]
|
||||
/// pub type Foo<T> = StorageMap<
|
||||
/// _,
|
||||
/// Blake2_128Concat,
|
||||
/// u32,
|
||||
/// u32,
|
||||
/// ValueQuery
|
||||
/// >;
|
||||
///
|
||||
/// /// Alternative named syntax.
|
||||
/// #[pallet::storage]
|
||||
/// pub type Bar<T> = StorageMap<
|
||||
/// Hasher = Blake2_128Concat,
|
||||
/// Key = u32,
|
||||
/// Value = u32,
|
||||
/// QueryKind = ValueQuery
|
||||
/// >;
|
||||
/// }
|
||||
/// ```
|
||||
pub struct StorageMap<
|
||||
Prefix,
|
||||
Hasher,
|
||||
Key,
|
||||
Value,
|
||||
QueryKind = OptionQuery,
|
||||
OnEmpty = GetDefault,
|
||||
MaxValues = GetDefault,
|
||||
>(core::marker::PhantomData<(Prefix, Hasher, Key, Value, QueryKind, OnEmpty, MaxValues)>);
|
||||
|
||||
impl<Prefix, Hasher, Key, Value, QueryKind, OnEmpty, MaxValues> Get<u32>
|
||||
for KeyLenOf<StorageMap<Prefix, Hasher, Key, Value, QueryKind, OnEmpty, MaxValues>>
|
||||
where
|
||||
Prefix: StorageInstance,
|
||||
Hasher: crate::hash::StorageHasher,
|
||||
Key: FullCodec + MaxEncodedLen,
|
||||
{
|
||||
fn get() -> u32 {
|
||||
// The `max_len` of the key hash plus the pallet prefix and storage prefix (which both are
|
||||
// hashed with `Twox128`).
|
||||
let z = Hasher::max_len::<Key>() + Twox128::max_len::<()>() * 2;
|
||||
z as u32
|
||||
}
|
||||
}
|
||||
|
||||
impl<Prefix, Hasher, Key, Value, QueryKind, OnEmpty, MaxValues>
|
||||
crate::storage::generator::StorageMap<Key, Value>
|
||||
for StorageMap<Prefix, Hasher, Key, Value, QueryKind, OnEmpty, MaxValues>
|
||||
where
|
||||
Prefix: StorageInstance,
|
||||
Hasher: crate::hash::StorageHasher,
|
||||
Key: FullCodec,
|
||||
Value: FullCodec,
|
||||
QueryKind: QueryKindTrait<Value, OnEmpty>,
|
||||
OnEmpty: Get<QueryKind::Query> + 'static,
|
||||
MaxValues: Get<Option<u32>>,
|
||||
{
|
||||
type Query = QueryKind::Query;
|
||||
type Hasher = Hasher;
|
||||
fn pezpallet_prefix() -> &'static [u8] {
|
||||
Prefix::pezpallet_prefix().as_bytes()
|
||||
}
|
||||
fn storage_prefix() -> &'static [u8] {
|
||||
Prefix::STORAGE_PREFIX.as_bytes()
|
||||
}
|
||||
fn prefix_hash() -> [u8; 32] {
|
||||
Prefix::prefix_hash()
|
||||
}
|
||||
fn from_optional_value_to_query(v: Option<Value>) -> Self::Query {
|
||||
QueryKind::from_optional_value_to_query(v)
|
||||
}
|
||||
fn from_query_to_optional_value(v: Self::Query) -> Option<Value> {
|
||||
QueryKind::from_query_to_optional_value(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Prefix, Hasher, Key, Value, QueryKind, OnEmpty, MaxValues> StoragePrefixedMap<Value>
|
||||
for StorageMap<Prefix, Hasher, Key, Value, QueryKind, OnEmpty, MaxValues>
|
||||
where
|
||||
Prefix: StorageInstance,
|
||||
Hasher: crate::hash::StorageHasher,
|
||||
Key: FullCodec,
|
||||
Value: FullCodec,
|
||||
QueryKind: QueryKindTrait<Value, OnEmpty>,
|
||||
OnEmpty: Get<QueryKind::Query> + 'static,
|
||||
MaxValues: Get<Option<u32>>,
|
||||
{
|
||||
fn pezpallet_prefix() -> &'static [u8] {
|
||||
<Self as crate::storage::generator::StorageMap<Key, Value>>::pezpallet_prefix()
|
||||
}
|
||||
fn storage_prefix() -> &'static [u8] {
|
||||
<Self as crate::storage::generator::StorageMap<Key, Value>>::storage_prefix()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Prefix, Hasher, Key, Value, QueryKind, OnEmpty, MaxValues>
|
||||
StorageMap<Prefix, Hasher, Key, Value, QueryKind, OnEmpty, MaxValues>
|
||||
where
|
||||
Prefix: StorageInstance,
|
||||
Hasher: crate::hash::StorageHasher,
|
||||
Key: FullCodec,
|
||||
Value: FullCodec,
|
||||
QueryKind: QueryKindTrait<Value, OnEmpty>,
|
||||
OnEmpty: Get<QueryKind::Query> + 'static,
|
||||
MaxValues: Get<Option<u32>>,
|
||||
{
|
||||
/// Get the storage key used to fetch a value corresponding to a specific key.
|
||||
pub fn hashed_key_for<KeyArg: EncodeLike<Key>>(key: KeyArg) -> Vec<u8> {
|
||||
<Self as crate::storage::StorageMap<Key, Value>>::hashed_key_for(key)
|
||||
}
|
||||
|
||||
/// Does the value (explicitly) exist in storage?
|
||||
pub fn contains_key<KeyArg: EncodeLike<Key>>(key: KeyArg) -> bool {
|
||||
<Self as crate::storage::StorageMap<Key, Value>>::contains_key(key)
|
||||
}
|
||||
|
||||
/// Load the value associated with the given key from the map.
|
||||
pub fn get<KeyArg: EncodeLike<Key>>(key: KeyArg) -> QueryKind::Query {
|
||||
<Self as crate::storage::StorageMap<Key, Value>>::get(key)
|
||||
}
|
||||
|
||||
/// Try to get the value for the given key from the map.
|
||||
///
|
||||
/// Returns `Ok` if it exists, `Err` if not.
|
||||
pub fn try_get<KeyArg: EncodeLike<Key>>(key: KeyArg) -> Result<Value, ()> {
|
||||
<Self as crate::storage::StorageMap<Key, Value>>::try_get(key)
|
||||
}
|
||||
|
||||
/// Swap the values of two keys.
|
||||
pub fn swap<KeyArg1: EncodeLike<Key>, KeyArg2: EncodeLike<Key>>(key1: KeyArg1, key2: KeyArg2) {
|
||||
<Self as crate::storage::StorageMap<Key, Value>>::swap(key1, key2)
|
||||
}
|
||||
|
||||
/// Store or remove the value to be associated with `key` so that `get` returns the `query`.
|
||||
pub fn set<KeyArg: EncodeLike<Key>>(key: KeyArg, q: QueryKind::Query) {
|
||||
<Self as crate::storage::StorageMap<Key, Value>>::set(key, q)
|
||||
}
|
||||
|
||||
/// Store a value to be associated with the given key from the map.
|
||||
pub fn insert<KeyArg: EncodeLike<Key>, ValArg: EncodeLike<Value>>(key: KeyArg, val: ValArg) {
|
||||
<Self as crate::storage::StorageMap<Key, Value>>::insert(key, val)
|
||||
}
|
||||
|
||||
/// Remove the value under a key.
|
||||
pub fn remove<KeyArg: EncodeLike<Key>>(key: KeyArg) {
|
||||
<Self as crate::storage::StorageMap<Key, Value>>::remove(key)
|
||||
}
|
||||
|
||||
/// Mutate the value under a key.
|
||||
pub fn mutate<KeyArg: EncodeLike<Key>, R, F: FnOnce(&mut QueryKind::Query) -> R>(
|
||||
key: KeyArg,
|
||||
f: F,
|
||||
) -> R {
|
||||
<Self as crate::storage::StorageMap<Key, Value>>::mutate(key, f)
|
||||
}
|
||||
|
||||
/// Mutate the item, only if an `Ok` value is returned.
|
||||
pub fn try_mutate<KeyArg, R, E, F>(key: KeyArg, f: F) -> Result<R, E>
|
||||
where
|
||||
KeyArg: EncodeLike<Key>,
|
||||
F: FnOnce(&mut QueryKind::Query) -> Result<R, E>,
|
||||
{
|
||||
<Self as crate::storage::StorageMap<Key, Value>>::try_mutate(key, f)
|
||||
}
|
||||
|
||||
/// Mutate the value under a key iff it exists. Do nothing and return the default value if not.
|
||||
pub fn mutate_extant<KeyArg: EncodeLike<Key>, R: Default, F: FnOnce(&mut Value) -> R>(
|
||||
key: KeyArg,
|
||||
f: F,
|
||||
) -> R {
|
||||
<Self as crate::storage::StorageMap<Key, Value>>::mutate_extant(key, f)
|
||||
}
|
||||
|
||||
/// Mutate the value under a key. Deletes the item if mutated to a `None`.
|
||||
pub fn mutate_exists<KeyArg: EncodeLike<Key>, R, F: FnOnce(&mut Option<Value>) -> R>(
|
||||
key: KeyArg,
|
||||
f: F,
|
||||
) -> R {
|
||||
<Self as crate::storage::StorageMap<Key, Value>>::mutate_exists(key, f)
|
||||
}
|
||||
|
||||
/// Mutate the item, only if an `Ok` value is returned. Deletes the item if mutated to a `None`.
|
||||
/// `f` will always be called with an option representing if the storage item exists (`Some<V>`)
|
||||
/// or if the storage item does not exist (`None`), independent of the `QueryType`.
|
||||
pub fn try_mutate_exists<KeyArg, R, E, F>(key: KeyArg, f: F) -> Result<R, E>
|
||||
where
|
||||
KeyArg: EncodeLike<Key>,
|
||||
F: FnOnce(&mut Option<Value>) -> Result<R, E>,
|
||||
{
|
||||
<Self as crate::storage::StorageMap<Key, Value>>::try_mutate_exists(key, f)
|
||||
}
|
||||
|
||||
/// Take the value under a key.
|
||||
pub fn take<KeyArg: EncodeLike<Key>>(key: KeyArg) -> QueryKind::Query {
|
||||
<Self as crate::storage::StorageMap<Key, Value>>::take(key)
|
||||
}
|
||||
|
||||
/// Append the given items to the value in the storage.
|
||||
///
|
||||
/// `Value` is required to implement `codec::EncodeAppend`.
|
||||
///
|
||||
/// # Warning
|
||||
///
|
||||
/// If the storage item is not encoded properly, the storage will be overwritten
|
||||
/// and set to `[item]`. Any default value set for the storage item will be ignored
|
||||
/// on overwrite.
|
||||
pub fn append<Item, EncodeLikeItem, EncodeLikeKey>(key: EncodeLikeKey, item: EncodeLikeItem)
|
||||
where
|
||||
EncodeLikeKey: EncodeLike<Key>,
|
||||
Item: Encode,
|
||||
EncodeLikeItem: EncodeLike<Item>,
|
||||
Value: StorageAppend<Item>,
|
||||
{
|
||||
<Self as crate::storage::StorageMap<Key, Value>>::append(key, item)
|
||||
}
|
||||
|
||||
/// Read the length of the storage value without decoding the entire value under the
|
||||
/// given `key`.
|
||||
///
|
||||
/// `Value` is required to implement [`StorageDecodeLength`].
|
||||
///
|
||||
/// If the value does not exists or it fails to decode the length, `None` is returned.
|
||||
/// Otherwise `Some(len)` is returned.
|
||||
///
|
||||
/// # Warning
|
||||
///
|
||||
/// `None` does not mean that `get()` does not return a value. The default value is completely
|
||||
/// ignored by this function.
|
||||
pub fn decode_len<KeyArg: EncodeLike<Key>>(key: KeyArg) -> Option<usize>
|
||||
where
|
||||
Value: StorageDecodeLength,
|
||||
{
|
||||
<Self as crate::storage::StorageMap<Key, Value>>::decode_len(key)
|
||||
}
|
||||
|
||||
/// Read the length of the storage value without decoding the entire value.
|
||||
///
|
||||
/// `Value` is required to implement [`StorageDecodeNonDedupLength`].
|
||||
///
|
||||
/// If the value does not exists or it fails to decode the length, `None` is returned.
|
||||
/// Otherwise `Some(len)` is returned.
|
||||
///
|
||||
/// # Warning
|
||||
///
|
||||
/// - `None` does not mean that `get()` does not return a value. The default value is
|
||||
/// completely
|
||||
/// ignored by this function.
|
||||
///
|
||||
/// - The value returned is the non-deduplicated length of the underlying Vector in storage.This
|
||||
/// means that any duplicate items are included.
|
||||
pub fn decode_non_dedup_len<KeyArg: EncodeLike<Key>>(key: KeyArg) -> Option<usize>
|
||||
where
|
||||
Value: StorageDecodeNonDedupLength,
|
||||
{
|
||||
<Self as crate::storage::StorageMap<Key, Value>>::decode_non_dedup_len(key)
|
||||
}
|
||||
|
||||
/// Migrate an item with the given `key` from a defunct `OldHasher` to the current hasher.
|
||||
///
|
||||
/// If the key doesn't exist, then it's a no-op. If it does, then it returns its value.
|
||||
pub fn migrate_key<OldHasher: crate::hash::StorageHasher, KeyArg: EncodeLike<Key>>(
|
||||
key: KeyArg,
|
||||
) -> Option<Value> {
|
||||
<Self as crate::storage::StorageMap<Key, Value>>::migrate_key::<OldHasher, _>(key)
|
||||
}
|
||||
|
||||
/// Remove all values of the storage in the overlay and up to `limit` in the backend.
|
||||
///
|
||||
/// All values in the client overlay will be deleted, if there is some `limit` then up to
|
||||
/// `limit` values are deleted from the client backend, if `limit` is none then all values in
|
||||
/// the client backend are deleted.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Calling this multiple times per block with a `limit` set leads always to the same keys being
|
||||
/// removed and the same result being returned. This happens because the keys to delete in the
|
||||
/// overlay are not taken into account when deleting keys in the backend.
|
||||
#[deprecated = "Use `clear` instead"]
|
||||
pub fn remove_all(limit: Option<u32>) -> pezsp_io::KillStorageResult {
|
||||
#[allow(deprecated)]
|
||||
<Self as crate::storage::StoragePrefixedMap<Value>>::remove_all(limit)
|
||||
}
|
||||
|
||||
/// Attempt to remove all items from the map.
|
||||
///
|
||||
/// Returns [`MultiRemovalResults`](pezsp_io::MultiRemovalResults) to inform about the result. Once
|
||||
/// the resultant `maybe_cursor` field is `None`, then no further items remain to be deleted.
|
||||
///
|
||||
/// NOTE: After the initial call for any given map, it is important that no further items
|
||||
/// are inserted into the map. If so, then the map may not be empty when the resultant
|
||||
/// `maybe_cursor` is `None`.
|
||||
///
|
||||
/// # Limit
|
||||
///
|
||||
/// A `limit` must always be provided through in order to cap the maximum
|
||||
/// amount of deletions done in a single call. This is one fewer than the
|
||||
/// maximum number of backend iterations which may be done by this operation and as such
|
||||
/// represents the maximum number of backend deletions which may happen. A `limit` of zero
|
||||
/// implies that no keys will be deleted, though there may be a single iteration done.
|
||||
///
|
||||
/// # Cursor
|
||||
///
|
||||
/// A *cursor* may be passed in to this operation with `maybe_cursor`. `None` should only be
|
||||
/// passed once (in the initial call) for any given storage map. Subsequent calls
|
||||
/// operating on the same map should always pass `Some`, and this should be equal to the
|
||||
/// previous call result's `maybe_cursor` field.
|
||||
pub fn clear(limit: u32, maybe_cursor: Option<&[u8]>) -> pezsp_io::MultiRemovalResults {
|
||||
<Self as crate::storage::StoragePrefixedMap<Value>>::clear(limit, maybe_cursor)
|
||||
}
|
||||
|
||||
/// Iter over all value of the storage.
|
||||
///
|
||||
/// NOTE: If a value failed to decode because storage is corrupted then it is skipped.
|
||||
pub fn iter_values() -> crate::storage::PrefixIterator<Value> {
|
||||
<Self as crate::storage::StoragePrefixedMap<Value>>::iter_values()
|
||||
}
|
||||
|
||||
/// Translate the values of all elements by a function `f`, in the map in no particular order.
|
||||
///
|
||||
/// By returning `None` from `f` for an element, you'll remove it from the map.
|
||||
///
|
||||
/// NOTE: If a value fail to decode because storage is corrupted then it is skipped.
|
||||
///
|
||||
/// # Warning
|
||||
///
|
||||
/// This function must be used with care, before being updated the storage still contains the
|
||||
/// old type, thus other calls (such as `get`) will fail at decoding it.
|
||||
///
|
||||
/// # Usage
|
||||
///
|
||||
/// This would typically be called inside the module implementation of on_runtime_upgrade.
|
||||
pub fn translate_values<OldValue: Decode, F: FnMut(OldValue) -> Option<Value>>(f: F) {
|
||||
<Self as crate::storage::StoragePrefixedMap<Value>>::translate_values(f)
|
||||
}
|
||||
|
||||
/// Try and append the given item to the value in the storage.
|
||||
///
|
||||
/// Is only available if `Value` of the storage implements [`StorageTryAppend`].
|
||||
pub fn try_append<KArg, Item, EncodeLikeItem>(key: KArg, item: EncodeLikeItem) -> Result<(), ()>
|
||||
where
|
||||
KArg: EncodeLike<Key> + Clone,
|
||||
Item: Encode,
|
||||
EncodeLikeItem: EncodeLike<Item>,
|
||||
Value: StorageTryAppend<Item>,
|
||||
{
|
||||
<Self as crate::storage::TryAppendMap<Key, Value, Item>>::try_append(key, item)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Prefix, Hasher, Key, Value, QueryKind, OnEmpty, MaxValues>
|
||||
StorageMap<Prefix, Hasher, Key, Value, QueryKind, OnEmpty, MaxValues>
|
||||
where
|
||||
Prefix: StorageInstance,
|
||||
Hasher: crate::hash::StorageHasher + crate::ReversibleStorageHasher,
|
||||
Key: FullCodec,
|
||||
Value: FullCodec,
|
||||
QueryKind: QueryKindTrait<Value, OnEmpty>,
|
||||
OnEmpty: Get<QueryKind::Query> + 'static,
|
||||
MaxValues: Get<Option<u32>>,
|
||||
{
|
||||
/// Enumerate all elements in the map in no particular order.
|
||||
///
|
||||
/// If you alter the map while doing this, you'll get undefined results.
|
||||
pub fn iter() -> crate::storage::PrefixIterator<(Key, Value)> {
|
||||
<Self as crate::storage::IterableStorageMap<Key, Value>>::iter()
|
||||
}
|
||||
|
||||
/// Enumerate all elements in the map after a specified `starting_raw_key` in no
|
||||
/// particular order.
|
||||
///
|
||||
/// If you alter the map while doing this, you'll get undefined results.
|
||||
pub fn iter_from(starting_raw_key: Vec<u8>) -> crate::storage::PrefixIterator<(Key, Value)> {
|
||||
<Self as crate::storage::IterableStorageMap<Key, Value>>::iter_from(starting_raw_key)
|
||||
}
|
||||
|
||||
/// Enumerate all elements in the map after a specified `starting_key` in no
|
||||
/// particular order.
|
||||
///
|
||||
/// If you alter the map while doing this, you'll get undefined results.
|
||||
pub fn iter_from_key(
|
||||
starting_key: impl EncodeLike<Key>,
|
||||
) -> crate::storage::PrefixIterator<(Key, Value)> {
|
||||
Self::iter_from(Self::hashed_key_for(starting_key))
|
||||
}
|
||||
|
||||
/// Enumerate all keys in the map in no particular order.
|
||||
///
|
||||
/// If you alter the map while doing this, you'll get undefined results.
|
||||
pub fn iter_keys() -> crate::storage::KeyPrefixIterator<Key> {
|
||||
<Self as crate::storage::IterableStorageMap<Key, Value>>::iter_keys()
|
||||
}
|
||||
|
||||
/// Enumerate all keys in the map after a specified `starting_raw_key` in no particular
|
||||
/// order.
|
||||
///
|
||||
/// If you alter the map while doing this, you'll get undefined results.
|
||||
pub fn iter_keys_from(starting_raw_key: Vec<u8>) -> crate::storage::KeyPrefixIterator<Key> {
|
||||
<Self as crate::storage::IterableStorageMap<Key, Value>>::iter_keys_from(starting_raw_key)
|
||||
}
|
||||
|
||||
/// Enumerate all keys in the map after a specified `starting_key` in no particular
|
||||
/// order.
|
||||
///
|
||||
/// If you alter the map while doing this, you'll get undefined results.
|
||||
pub fn iter_keys_from_key(
|
||||
starting_key: impl EncodeLike<Key>,
|
||||
) -> crate::storage::KeyPrefixIterator<Key> {
|
||||
Self::iter_keys_from(Self::hashed_key_for(starting_key))
|
||||
}
|
||||
|
||||
/// Remove all elements from the map and iterate through them in no particular order.
|
||||
///
|
||||
/// If you add elements to the map while doing this, you'll get undefined results.
|
||||
pub fn drain() -> crate::storage::PrefixIterator<(Key, Value)> {
|
||||
<Self as crate::storage::IterableStorageMap<Key, Value>>::drain()
|
||||
}
|
||||
|
||||
/// Translate the values of all elements by a function `f`, in the map in no particular order.
|
||||
///
|
||||
/// By returning `None` from `f` for an element, you'll remove it from the map.
|
||||
///
|
||||
/// NOTE: If a value fails to decode because storage is corrupted, then it will log an error and
|
||||
/// be skipped in production, or panic in development.
|
||||
pub fn translate<O: Decode, F: FnMut(Key, O) -> Option<Value>>(f: F) {
|
||||
<Self as crate::storage::IterableStorageMap<Key, Value>>::translate(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Prefix, Hasher, Key, Value, QueryKind, OnEmpty, MaxValues> StorageEntryMetadataBuilder
|
||||
for StorageMap<Prefix, Hasher, Key, Value, QueryKind, OnEmpty, MaxValues>
|
||||
where
|
||||
Prefix: StorageInstance,
|
||||
Hasher: crate::hash::StorageHasher,
|
||||
Key: FullCodec + scale_info::StaticTypeInfo,
|
||||
Value: FullCodec + scale_info::StaticTypeInfo,
|
||||
QueryKind: QueryKindTrait<Value, OnEmpty>,
|
||||
OnEmpty: Get<QueryKind::Query> + 'static,
|
||||
MaxValues: Get<Option<u32>>,
|
||||
{
|
||||
fn build_metadata(
|
||||
deprecation_status: pezsp_metadata_ir::ItemDeprecationInfoIR,
|
||||
docs: Vec<&'static str>,
|
||||
entries: &mut Vec<StorageEntryMetadataIR>,
|
||||
) {
|
||||
let docs = if cfg!(feature = "no-metadata-docs") { vec![] } else { docs };
|
||||
|
||||
let entry = StorageEntryMetadataIR {
|
||||
name: Prefix::STORAGE_PREFIX,
|
||||
modifier: QueryKind::METADATA,
|
||||
ty: StorageEntryTypeIR::Map {
|
||||
hashers: vec![Hasher::METADATA],
|
||||
key: scale_info::meta_type::<Key>(),
|
||||
value: scale_info::meta_type::<Value>(),
|
||||
},
|
||||
default: OnEmpty::get().encode(),
|
||||
docs,
|
||||
deprecation_info: deprecation_status,
|
||||
};
|
||||
|
||||
entries.push(entry);
|
||||
}
|
||||
}
|
||||
|
||||
impl<Prefix, Hasher, Key, Value, QueryKind, OnEmpty, MaxValues> crate::traits::StorageInfoTrait
|
||||
for StorageMap<Prefix, Hasher, Key, Value, QueryKind, OnEmpty, MaxValues>
|
||||
where
|
||||
Prefix: StorageInstance,
|
||||
Hasher: crate::hash::StorageHasher,
|
||||
Key: FullCodec + MaxEncodedLen,
|
||||
Value: FullCodec + MaxEncodedLen,
|
||||
QueryKind: QueryKindTrait<Value, OnEmpty>,
|
||||
OnEmpty: Get<QueryKind::Query> + 'static,
|
||||
MaxValues: Get<Option<u32>>,
|
||||
{
|
||||
fn storage_info() -> Vec<StorageInfo> {
|
||||
vec![StorageInfo {
|
||||
pezpallet_name: Self::pezpallet_prefix().to_vec(),
|
||||
storage_name: Self::storage_prefix().to_vec(),
|
||||
prefix: Self::final_prefix().to_vec(),
|
||||
max_values: MaxValues::get(),
|
||||
max_size: Some(
|
||||
Hasher::max_len::<Key>()
|
||||
.saturating_add(Value::max_encoded_len())
|
||||
.saturated_into(),
|
||||
),
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
/// It doesn't require to implement `MaxEncodedLen` and give no information for `max_size`.
|
||||
impl<Prefix, Hasher, Key, Value, QueryKind, OnEmpty, MaxValues>
|
||||
crate::traits::PartialStorageInfoTrait
|
||||
for StorageMap<Prefix, Hasher, Key, Value, QueryKind, OnEmpty, MaxValues>
|
||||
where
|
||||
Prefix: StorageInstance,
|
||||
Hasher: crate::hash::StorageHasher,
|
||||
Key: FullCodec,
|
||||
Value: FullCodec,
|
||||
QueryKind: QueryKindTrait<Value, OnEmpty>,
|
||||
OnEmpty: Get<QueryKind::Query> + 'static,
|
||||
MaxValues: Get<Option<u32>>,
|
||||
{
|
||||
fn partial_storage_info() -> Vec<StorageInfo> {
|
||||
vec![StorageInfo {
|
||||
pezpallet_name: Self::pezpallet_prefix().to_vec(),
|
||||
storage_name: Self::storage_prefix().to_vec(),
|
||||
prefix: Self::final_prefix().to_vec(),
|
||||
max_values: MaxValues::get(),
|
||||
max_size: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::{
|
||||
hash::*,
|
||||
storage::{types::ValueQuery, IterableStorageMap},
|
||||
};
|
||||
use pezsp_io::{hashing::twox_128, TestExternalities};
|
||||
use pezsp_metadata_ir::{StorageEntryModifierIR, StorageEntryTypeIR, StorageHasherIR};
|
||||
|
||||
struct Prefix;
|
||||
impl StorageInstance for Prefix {
|
||||
fn pezpallet_prefix() -> &'static str {
|
||||
"test"
|
||||
}
|
||||
const STORAGE_PREFIX: &'static str = "foo";
|
||||
}
|
||||
|
||||
struct ADefault;
|
||||
impl crate::traits::Get<u32> for ADefault {
|
||||
fn get() -> u32 {
|
||||
97
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keylenof_works() {
|
||||
// Works with Blake2_128Concat.
|
||||
type A = StorageMap<Prefix, Blake2_128Concat, u32, u32>;
|
||||
let size = 16 * 2 // Two Twox128
|
||||
+ 16 + 4; // Blake2_128Concat = hash + key
|
||||
assert_eq!(KeyLenOf::<A>::get(), size);
|
||||
|
||||
// Works with Blake2_256.
|
||||
type B = StorageMap<Prefix, Blake2_256, u32, u32>;
|
||||
let size = 16 * 2 // Two Twox128
|
||||
+ 32; // Blake2_256
|
||||
assert_eq!(KeyLenOf::<B>::get(), size);
|
||||
|
||||
// Works with Twox64Concat.
|
||||
type C = StorageMap<Prefix, Twox64Concat, u32, u32>;
|
||||
let size = 16 * 2 // Two Twox128
|
||||
+ 8 + 4; // Twox64Concat = hash + key
|
||||
assert_eq!(KeyLenOf::<C>::get(), size);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
type A = StorageMap<Prefix, Blake2_128Concat, u16, u32, OptionQuery>;
|
||||
type AValueQueryWithAnOnEmpty =
|
||||
StorageMap<Prefix, Blake2_128Concat, u16, u32, ValueQuery, ADefault>;
|
||||
type B = StorageMap<Prefix, Blake2_256, u16, u32, ValueQuery>;
|
||||
type C = StorageMap<Prefix, Blake2_128Concat, u16, u8, ValueQuery>;
|
||||
type WithLen = StorageMap<Prefix, Blake2_128Concat, u16, Vec<u32>>;
|
||||
|
||||
TestExternalities::default().execute_with(|| {
|
||||
let mut k: Vec<u8> = vec![];
|
||||
k.extend(&twox_128(b"test"));
|
||||
k.extend(&twox_128(b"foo"));
|
||||
k.extend(&3u16.blake2_128_concat());
|
||||
assert_eq!(A::hashed_key_for(3).to_vec(), k);
|
||||
|
||||
assert_eq!(A::contains_key(3), false);
|
||||
assert_eq!(A::get(3), None);
|
||||
assert_eq!(AValueQueryWithAnOnEmpty::get(3), 97);
|
||||
|
||||
A::insert(3, 10);
|
||||
assert_eq!(A::contains_key(3), true);
|
||||
assert_eq!(A::get(3), Some(10));
|
||||
assert_eq!(A::try_get(3), Ok(10));
|
||||
assert_eq!(AValueQueryWithAnOnEmpty::get(3), 10);
|
||||
|
||||
A::swap(3, 2);
|
||||
assert_eq!(A::contains_key(3), false);
|
||||
assert_eq!(A::contains_key(2), true);
|
||||
assert_eq!(A::get(3), None);
|
||||
assert_eq!(A::try_get(3), Err(()));
|
||||
assert_eq!(AValueQueryWithAnOnEmpty::get(3), 97);
|
||||
assert_eq!(A::get(2), Some(10));
|
||||
assert_eq!(AValueQueryWithAnOnEmpty::get(2), 10);
|
||||
|
||||
A::remove(2);
|
||||
assert_eq!(A::contains_key(2), false);
|
||||
assert_eq!(A::get(2), None);
|
||||
|
||||
AValueQueryWithAnOnEmpty::mutate(2, |v| *v = *v * 2);
|
||||
AValueQueryWithAnOnEmpty::mutate(2, |v| *v = *v * 2);
|
||||
assert_eq!(AValueQueryWithAnOnEmpty::contains_key(2), true);
|
||||
assert_eq!(AValueQueryWithAnOnEmpty::get(2), 97 * 4);
|
||||
|
||||
A::remove(2);
|
||||
let _: Result<(), ()> = AValueQueryWithAnOnEmpty::try_mutate(2, |v| {
|
||||
*v = *v * 2;
|
||||
Ok(())
|
||||
});
|
||||
let _: Result<(), ()> = AValueQueryWithAnOnEmpty::try_mutate(2, |v| {
|
||||
*v = *v * 2;
|
||||
Ok(())
|
||||
});
|
||||
assert_eq!(A::contains_key(2), true);
|
||||
assert_eq!(A::get(2), Some(97 * 4));
|
||||
|
||||
A::remove(2);
|
||||
let _: Result<(), ()> = AValueQueryWithAnOnEmpty::try_mutate(2, |v| {
|
||||
*v = *v * 2;
|
||||
Err(())
|
||||
});
|
||||
assert_eq!(A::contains_key(2), false);
|
||||
|
||||
A::remove(2);
|
||||
AValueQueryWithAnOnEmpty::mutate_exists(2, |v| {
|
||||
assert!(v.is_none());
|
||||
*v = Some(10);
|
||||
});
|
||||
assert_eq!(A::contains_key(2), true);
|
||||
assert_eq!(A::get(2), Some(10));
|
||||
AValueQueryWithAnOnEmpty::mutate_exists(2, |v| {
|
||||
*v = Some(v.unwrap() * 10);
|
||||
});
|
||||
assert_eq!(A::contains_key(2), true);
|
||||
assert_eq!(A::get(2), Some(100));
|
||||
|
||||
A::remove(2);
|
||||
let _: Result<(), ()> = AValueQueryWithAnOnEmpty::try_mutate_exists(2, |v| {
|
||||
assert!(v.is_none());
|
||||
*v = Some(10);
|
||||
Ok(())
|
||||
});
|
||||
assert_eq!(A::contains_key(2), true);
|
||||
assert_eq!(A::get(2), Some(10));
|
||||
let _: Result<(), ()> = AValueQueryWithAnOnEmpty::try_mutate_exists(2, |v| {
|
||||
*v = Some(v.unwrap() * 10);
|
||||
Ok(())
|
||||
});
|
||||
assert_eq!(A::contains_key(2), true);
|
||||
assert_eq!(A::get(2), Some(100));
|
||||
let _: Result<(), ()> = AValueQueryWithAnOnEmpty::try_mutate_exists(2, |v| {
|
||||
*v = Some(v.unwrap() * 10);
|
||||
Err(())
|
||||
});
|
||||
assert_eq!(A::contains_key(2), true);
|
||||
assert_eq!(A::get(2), Some(100));
|
||||
|
||||
A::insert(2, 10);
|
||||
assert_eq!(A::take(2), Some(10));
|
||||
assert_eq!(A::contains_key(2), false);
|
||||
assert_eq!(AValueQueryWithAnOnEmpty::take(2), 97);
|
||||
assert_eq!(A::contains_key(2), false);
|
||||
|
||||
// Set non-existing.
|
||||
B::set(30, 100);
|
||||
|
||||
assert_eq!(B::contains_key(30), true);
|
||||
assert_eq!(B::get(30), 100);
|
||||
assert_eq!(B::try_get(30), Ok(100));
|
||||
|
||||
// Set existing.
|
||||
B::set(30, 101);
|
||||
|
||||
assert_eq!(B::contains_key(30), true);
|
||||
assert_eq!(B::get(30), 101);
|
||||
assert_eq!(B::try_get(30), Ok(101));
|
||||
|
||||
// Set non-existing.
|
||||
A::set(30, Some(100));
|
||||
|
||||
assert_eq!(A::contains_key(30), true);
|
||||
assert_eq!(A::get(30), Some(100));
|
||||
assert_eq!(A::try_get(30), Ok(100));
|
||||
|
||||
// Set existing.
|
||||
A::set(30, Some(101));
|
||||
|
||||
assert_eq!(A::contains_key(30), true);
|
||||
assert_eq!(A::get(30), Some(101));
|
||||
assert_eq!(A::try_get(30), Ok(101));
|
||||
|
||||
// Unset existing.
|
||||
A::set(30, None);
|
||||
|
||||
assert_eq!(A::contains_key(30), false);
|
||||
assert_eq!(A::get(30), None);
|
||||
assert_eq!(A::try_get(30), Err(()));
|
||||
|
||||
// Unset non-existing.
|
||||
A::set(31, None);
|
||||
|
||||
assert_eq!(A::contains_key(31), false);
|
||||
assert_eq!(A::get(31), None);
|
||||
assert_eq!(A::try_get(31), Err(()));
|
||||
|
||||
B::insert(2, 10);
|
||||
assert_eq!(A::migrate_key::<Blake2_256, _>(2), Some(10));
|
||||
assert_eq!(A::contains_key(2), true);
|
||||
assert_eq!(A::get(2), Some(10));
|
||||
|
||||
A::insert(3, 10);
|
||||
A::insert(4, 10);
|
||||
let _ = A::clear(u32::max_value(), None);
|
||||
assert_eq!(A::contains_key(3), false);
|
||||
assert_eq!(A::contains_key(4), false);
|
||||
|
||||
A::insert(3, 10);
|
||||
A::insert(4, 10);
|
||||
assert_eq!(A::iter_values().collect::<Vec<_>>(), vec![10, 10]);
|
||||
|
||||
C::insert(3, 10);
|
||||
C::insert(4, 10);
|
||||
A::translate_values::<u8, _>(|v| Some((v * 2).into()));
|
||||
assert_eq!(A::iter().collect::<Vec<_>>(), vec![(4, 20), (3, 20)]);
|
||||
|
||||
A::insert(3, 10);
|
||||
A::insert(4, 10);
|
||||
assert_eq!(A::iter().collect::<Vec<_>>(), vec![(4, 10), (3, 10)]);
|
||||
assert_eq!(A::drain().collect::<Vec<_>>(), vec![(4, 10), (3, 10)]);
|
||||
assert_eq!(A::iter().collect::<Vec<_>>(), vec![]);
|
||||
|
||||
C::insert(3, 10);
|
||||
C::insert(4, 10);
|
||||
A::translate::<u8, _>(|k, v| Some((k * v as u16).into()));
|
||||
assert_eq!(A::iter().collect::<Vec<_>>(), vec![(4, 40), (3, 30)]);
|
||||
|
||||
let translate_next = |k: u16, v: u8| Some((v as u16 / k).into());
|
||||
let k = A::translate_next::<u8, _>(None, translate_next);
|
||||
let k = A::translate_next::<u8, _>(k, translate_next);
|
||||
assert_eq!(None, A::translate_next::<u8, _>(k, translate_next));
|
||||
assert_eq!(A::iter().collect::<Vec<_>>(), vec![(4, 10), (3, 10)]);
|
||||
|
||||
let _ = A::translate_next::<u8, _>(None, |_, _| None);
|
||||
assert_eq!(A::iter().collect::<Vec<_>>(), vec![(3, 10)]);
|
||||
|
||||
let mut entries = vec![];
|
||||
A::build_metadata(
|
||||
pezsp_metadata_ir::ItemDeprecationInfoIR::NotDeprecated,
|
||||
vec![],
|
||||
&mut entries,
|
||||
);
|
||||
AValueQueryWithAnOnEmpty::build_metadata(
|
||||
pezsp_metadata_ir::ItemDeprecationInfoIR::NotDeprecated,
|
||||
vec![],
|
||||
&mut entries,
|
||||
);
|
||||
assert_eq!(
|
||||
entries,
|
||||
vec![
|
||||
StorageEntryMetadataIR {
|
||||
name: "foo",
|
||||
modifier: StorageEntryModifierIR::Optional,
|
||||
ty: StorageEntryTypeIR::Map {
|
||||
hashers: vec![StorageHasherIR::Blake2_128Concat],
|
||||
key: scale_info::meta_type::<u16>(),
|
||||
value: scale_info::meta_type::<u32>(),
|
||||
},
|
||||
default: Option::<u32>::None.encode(),
|
||||
docs: vec![],
|
||||
deprecation_info: pezsp_metadata_ir::ItemDeprecationInfoIR::NotDeprecated
|
||||
},
|
||||
StorageEntryMetadataIR {
|
||||
name: "foo",
|
||||
modifier: StorageEntryModifierIR::Default,
|
||||
ty: StorageEntryTypeIR::Map {
|
||||
hashers: vec![StorageHasherIR::Blake2_128Concat],
|
||||
key: scale_info::meta_type::<u16>(),
|
||||
value: scale_info::meta_type::<u32>(),
|
||||
},
|
||||
default: 97u32.encode(),
|
||||
docs: vec![],
|
||||
deprecation_info: pezsp_metadata_ir::ItemDeprecationInfoIR::NotDeprecated
|
||||
}
|
||||
]
|
||||
);
|
||||
|
||||
let _ = WithLen::clear(u32::max_value(), None);
|
||||
assert_eq!(WithLen::decode_len(3), None);
|
||||
WithLen::append(0, 10);
|
||||
assert_eq!(WithLen::decode_len(0), Some(1));
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Storage types to build abstraction on storage, they implements storage traits such as
|
||||
//! StorageMap and others.
|
||||
|
||||
use alloc::vec::Vec;
|
||||
use codec::FullCodec;
|
||||
use pezsp_metadata_ir::{StorageEntryMetadataIR, StorageEntryModifierIR};
|
||||
|
||||
mod counted_map;
|
||||
mod counted_nmap;
|
||||
mod double_map;
|
||||
mod key;
|
||||
mod map;
|
||||
mod nmap;
|
||||
mod value;
|
||||
|
||||
pub use counted_map::{CountedStorageMap, CountedStorageMapInstance, Counter};
|
||||
pub use counted_nmap::{CountedStorageNMap, CountedStorageNMapInstance};
|
||||
pub use double_map::StorageDoubleMap;
|
||||
pub use key::{
|
||||
EncodeLikeTuple, HasKeyPrefix, HasReversibleKeyPrefix, Key, KeyGenerator,
|
||||
KeyGeneratorMaxEncodedLen, ReversibleKeyGenerator, TupleToEncodedIter,
|
||||
};
|
||||
pub use map::StorageMap;
|
||||
pub use nmap::StorageNMap;
|
||||
pub use value::StorageValue;
|
||||
|
||||
/// Trait implementing how the storage optional value is converted into the queried type.
|
||||
///
|
||||
/// It is implemented most notable by:
|
||||
///
|
||||
/// * [`OptionQuery`] which converts an optional value to an optional value, used when querying
|
||||
/// storage returns an optional value.
|
||||
/// * [`ResultQuery`] which converts an optional value to a result value, used when querying storage
|
||||
/// returns a result value.
|
||||
/// * [`ValueQuery`] which converts an optional value to a value, used when querying storage returns
|
||||
/// a value.
|
||||
///
|
||||
/// ## Example
|
||||
#[doc = docify::embed!("src/storage/types/mod.rs", value_query_examples)]
|
||||
pub trait QueryKindTrait<Value, OnEmpty> {
|
||||
/// Metadata for the storage kind.
|
||||
const METADATA: StorageEntryModifierIR;
|
||||
|
||||
/// Type returned on query
|
||||
type Query: FullCodec + 'static;
|
||||
|
||||
/// Convert an optional value (i.e. some if trie contains the value or none otherwise) to the
|
||||
/// query.
|
||||
fn from_optional_value_to_query(v: Option<Value>) -> Self::Query;
|
||||
|
||||
/// Convert a query to an optional value.
|
||||
fn from_query_to_optional_value(v: Self::Query) -> Option<Value>;
|
||||
}
|
||||
|
||||
/// Implements [`QueryKindTrait`] with `Query` type being `Option<_>`.
|
||||
///
|
||||
/// NOTE: it doesn't support a generic `OnEmpty`. This means only `None` can be returned when no
|
||||
/// value is found. To use another `OnEmpty` implementation, `ValueQuery` can be used instead.
|
||||
pub struct OptionQuery;
|
||||
impl<Value> QueryKindTrait<Value, crate::traits::GetDefault> for OptionQuery
|
||||
where
|
||||
Value: FullCodec + 'static,
|
||||
{
|
||||
const METADATA: StorageEntryModifierIR = StorageEntryModifierIR::Optional;
|
||||
|
||||
type Query = Option<Value>;
|
||||
|
||||
fn from_optional_value_to_query(v: Option<Value>) -> Self::Query {
|
||||
// NOTE: OnEmpty is fixed to GetDefault, thus it returns `None` on no value.
|
||||
v
|
||||
}
|
||||
|
||||
fn from_query_to_optional_value(v: Self::Query) -> Option<Value> {
|
||||
v
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements [`QueryKindTrait`] with `Query` type being `Result<Value, PalletError>`.
|
||||
pub struct ResultQuery<Error>(core::marker::PhantomData<Error>);
|
||||
impl<Value, Error, OnEmpty> QueryKindTrait<Value, OnEmpty> for ResultQuery<Error>
|
||||
where
|
||||
Value: FullCodec + 'static,
|
||||
Error: FullCodec + 'static,
|
||||
OnEmpty: crate::traits::Get<Result<Value, Error>>,
|
||||
{
|
||||
const METADATA: StorageEntryModifierIR = StorageEntryModifierIR::Optional;
|
||||
|
||||
type Query = Result<Value, Error>;
|
||||
|
||||
fn from_optional_value_to_query(v: Option<Value>) -> Self::Query {
|
||||
match v {
|
||||
Some(v) => Ok(v),
|
||||
None => OnEmpty::get(),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_query_to_optional_value(v: Self::Query) -> Option<Value> {
|
||||
v.ok()
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements [`QueryKindTrait`] with `Query` type being `Value`.
|
||||
pub struct ValueQuery;
|
||||
impl<Value, OnEmpty> QueryKindTrait<Value, OnEmpty> for ValueQuery
|
||||
where
|
||||
Value: FullCodec + 'static,
|
||||
OnEmpty: crate::traits::Get<Value>,
|
||||
{
|
||||
const METADATA: StorageEntryModifierIR = StorageEntryModifierIR::Default;
|
||||
|
||||
type Query = Value;
|
||||
|
||||
fn from_optional_value_to_query(v: Option<Value>) -> Self::Query {
|
||||
v.unwrap_or_else(|| OnEmpty::get())
|
||||
}
|
||||
|
||||
fn from_query_to_optional_value(v: Self::Query) -> Option<Value> {
|
||||
Some(v)
|
||||
}
|
||||
}
|
||||
|
||||
/// Build the metadata of a storage.
|
||||
///
|
||||
/// Implemented by each of the storage types: value, map, countedmap, doublemap and nmap.
|
||||
pub trait StorageEntryMetadataBuilder {
|
||||
/// Build into `entries` the storage metadata entries of a storage given some `docs`.
|
||||
fn build_metadata(
|
||||
deprecation_status: pezsp_metadata_ir::ItemDeprecationInfoIR,
|
||||
doc: Vec<&'static str>,
|
||||
entries: &mut Vec<StorageEntryMetadataIR>,
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::{
|
||||
storage::types::ValueQuery,
|
||||
traits::{Get, StorageInstance},
|
||||
};
|
||||
use pezsp_io::TestExternalities;
|
||||
|
||||
struct Prefix;
|
||||
impl StorageInstance for Prefix {
|
||||
fn pezpallet_prefix() -> &'static str {
|
||||
"test"
|
||||
}
|
||||
const STORAGE_PREFIX: &'static str = "foo";
|
||||
}
|
||||
|
||||
#[docify::export]
|
||||
#[test]
|
||||
pub fn value_query_examples() {
|
||||
/// Custom default impl to be used with `ValueQuery`.
|
||||
struct UniverseSecret;
|
||||
impl Get<u32> for UniverseSecret {
|
||||
fn get() -> u32 {
|
||||
42
|
||||
}
|
||||
}
|
||||
|
||||
/// Custom default impl to be used with `ResultQuery`.
|
||||
struct GetDefaultForResult;
|
||||
impl Get<Result<u32, ()>> for GetDefaultForResult {
|
||||
fn get() -> Result<u32, ()> {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
type A = StorageValue<Prefix, u32, ValueQuery>;
|
||||
type B = StorageValue<Prefix, u32, OptionQuery>;
|
||||
type C = StorageValue<Prefix, u32, ResultQuery<()>, GetDefaultForResult>;
|
||||
type D = StorageValue<Prefix, u32, ValueQuery, UniverseSecret>;
|
||||
|
||||
TestExternalities::default().execute_with(|| {
|
||||
// normal value query returns default
|
||||
assert_eq!(A::get(), 0);
|
||||
|
||||
// option query returns none
|
||||
assert_eq!(B::get(), None);
|
||||
|
||||
// result query returns error
|
||||
assert_eq!(C::get(), Err(()));
|
||||
|
||||
// value query with custom on empty returns 42
|
||||
assert_eq!(D::get(), 42);
|
||||
});
|
||||
}
|
||||
}
|
||||
+1366
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,461 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Storage value type. Implements StorageValue trait and its method directly.
|
||||
|
||||
use crate::{
|
||||
storage::{
|
||||
generator::StorageValue as StorageValueT,
|
||||
types::{OptionQuery, QueryKindTrait, StorageEntryMetadataBuilder},
|
||||
StorageAppend, StorageDecodeLength, StorageTryAppend,
|
||||
},
|
||||
traits::{Get, GetDefault, StorageInfo, StorageInstance},
|
||||
};
|
||||
use alloc::{vec, vec::Vec};
|
||||
use codec::{Decode, Encode, EncodeLike, FullCodec, MaxEncodedLen};
|
||||
use pezframe_support::storage::StorageDecodeNonDedupLength;
|
||||
use pezsp_arithmetic::traits::SaturatedConversion;
|
||||
use pezsp_metadata_ir::{StorageEntryMetadataIR, StorageEntryTypeIR};
|
||||
|
||||
/// A type representing a *value* in storage. A *storage value* is a single value of a given type
|
||||
/// stored on-chain.
|
||||
///
|
||||
/// For general information regarding the `#[pallet::storage]` attribute, refer to
|
||||
/// [`crate::pezpallet_macros::storage`].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// #[pezframe_support::pallet]
|
||||
/// mod pallet {
|
||||
/// # use pezframe_support::pezpallet_prelude::*;
|
||||
/// # #[pallet::config]
|
||||
/// # pub trait Config: pezframe_system::Config {}
|
||||
/// # #[pallet::pallet]
|
||||
/// # pub struct Pallet<T>(_);
|
||||
/// /// A kitchen-sink StorageValue, with all possible additional attributes.
|
||||
/// #[pallet::storage]
|
||||
/// #[pallet::getter(fn foo)]
|
||||
/// #[pallet::storage_prefix = "OtherFoo"]
|
||||
/// #[pallet::unbounded]
|
||||
/// pub type Foo<T> = StorageValue<_, u32,ValueQuery>;
|
||||
///
|
||||
/// /// Named alternative syntax.
|
||||
/// #[pallet::storage]
|
||||
/// pub type Bar<T> = StorageValue<
|
||||
/// Value = u32,
|
||||
/// QueryKind = ValueQuery
|
||||
/// >;
|
||||
/// }
|
||||
/// ```
|
||||
pub struct StorageValue<Prefix, Value, QueryKind = OptionQuery, OnEmpty = GetDefault>(
|
||||
core::marker::PhantomData<(Prefix, Value, QueryKind, OnEmpty)>,
|
||||
);
|
||||
|
||||
impl<Prefix, Value, QueryKind, OnEmpty> crate::storage::generator::StorageValue<Value>
|
||||
for StorageValue<Prefix, Value, QueryKind, OnEmpty>
|
||||
where
|
||||
Prefix: StorageInstance,
|
||||
Value: FullCodec,
|
||||
QueryKind: QueryKindTrait<Value, OnEmpty>,
|
||||
OnEmpty: Get<QueryKind::Query> + 'static,
|
||||
{
|
||||
type Query = QueryKind::Query;
|
||||
fn pezpallet_prefix() -> &'static [u8] {
|
||||
Prefix::pezpallet_prefix().as_bytes()
|
||||
}
|
||||
fn storage_prefix() -> &'static [u8] {
|
||||
Prefix::STORAGE_PREFIX.as_bytes()
|
||||
}
|
||||
fn from_optional_value_to_query(v: Option<Value>) -> Self::Query {
|
||||
QueryKind::from_optional_value_to_query(v)
|
||||
}
|
||||
fn from_query_to_optional_value(v: Self::Query) -> Option<Value> {
|
||||
QueryKind::from_query_to_optional_value(v)
|
||||
}
|
||||
fn storage_value_final_key() -> [u8; 32] {
|
||||
Prefix::prefix_hash()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Prefix, Value, QueryKind, OnEmpty> StorageValue<Prefix, Value, QueryKind, OnEmpty>
|
||||
where
|
||||
Prefix: StorageInstance,
|
||||
Value: FullCodec,
|
||||
QueryKind: QueryKindTrait<Value, OnEmpty>,
|
||||
OnEmpty: crate::traits::Get<QueryKind::Query> + 'static,
|
||||
{
|
||||
/// Get the storage key.
|
||||
pub fn hashed_key() -> [u8; 32] {
|
||||
<Self as crate::storage::StorageValue<Value>>::hashed_key()
|
||||
}
|
||||
|
||||
/// Does the value (explicitly) exist in storage?
|
||||
pub fn exists() -> bool {
|
||||
<Self as crate::storage::StorageValue<Value>>::exists()
|
||||
}
|
||||
|
||||
/// Load the value from the provided storage instance.
|
||||
pub fn get() -> QueryKind::Query {
|
||||
<Self as crate::storage::StorageValue<Value>>::get()
|
||||
}
|
||||
|
||||
/// Try to get the underlying value from the provided storage instance; `Ok` if it exists,
|
||||
/// `Err` if not.
|
||||
pub fn try_get() -> Result<Value, ()> {
|
||||
<Self as crate::storage::StorageValue<Value>>::try_get()
|
||||
}
|
||||
|
||||
/// Translate a value from some previous type (`O`) to the current type.
|
||||
///
|
||||
/// `f: F` is the translation function.
|
||||
///
|
||||
/// Returns `Err` if the storage item could not be interpreted as the old type, and Ok, along
|
||||
/// with the new value if it could.
|
||||
///
|
||||
/// NOTE: This operates from and to `Option<_>` types; no effort is made to respect the default
|
||||
/// value of the original type.
|
||||
///
|
||||
/// # Warning
|
||||
///
|
||||
/// This function must be used with care, before being updated the storage still contains the
|
||||
/// old type, thus other calls (such as `get`) will fail at decoding it.
|
||||
///
|
||||
/// # Usage
|
||||
///
|
||||
/// This would typically be called inside the module implementation of on_runtime_upgrade,
|
||||
/// while ensuring **no usage of this storage are made before the call to
|
||||
/// `on_runtime_upgrade`**. (More precisely prior initialized modules doesn't make use of this
|
||||
/// storage).
|
||||
pub fn translate<O: Decode, F: FnOnce(Option<O>) -> Option<Value>>(
|
||||
f: F,
|
||||
) -> Result<Option<Value>, ()> {
|
||||
<Self as crate::storage::StorageValue<Value>>::translate(f)
|
||||
}
|
||||
|
||||
/// Store a value under this key into the provided storage instance.
|
||||
pub fn put<Arg: EncodeLike<Value>>(val: Arg) {
|
||||
<Self as crate::storage::StorageValue<Value>>::put(val)
|
||||
}
|
||||
|
||||
/// Store a value under this key into the provided storage instance.
|
||||
///
|
||||
/// this uses the query type rather than the underlying value.
|
||||
pub fn set(val: QueryKind::Query) {
|
||||
<Self as crate::storage::StorageValue<Value>>::set(val)
|
||||
}
|
||||
|
||||
/// Mutate the value
|
||||
pub fn mutate<R, F: FnOnce(&mut QueryKind::Query) -> R>(f: F) -> R {
|
||||
<Self as crate::storage::StorageValue<Value>>::mutate(f)
|
||||
}
|
||||
|
||||
/// Mutate the value under a key iff it exists. Do nothing and return the default value if not.
|
||||
pub fn mutate_extant<R: Default, F: FnOnce(&mut Value) -> R>(f: F) -> R {
|
||||
<Self as crate::storage::StorageValue<Value>>::mutate_extant(f)
|
||||
}
|
||||
|
||||
/// Mutate the value if closure returns `Ok`
|
||||
pub fn try_mutate<R, E, F: FnOnce(&mut QueryKind::Query) -> Result<R, E>>(
|
||||
f: F,
|
||||
) -> Result<R, E> {
|
||||
<Self as crate::storage::StorageValue<Value>>::try_mutate(f)
|
||||
}
|
||||
|
||||
/// Mutate the value. Deletes the item if mutated to a `None`.
|
||||
pub fn mutate_exists<R, F: FnOnce(&mut Option<Value>) -> R>(f: F) -> R {
|
||||
<Self as crate::storage::StorageValue<Value>>::mutate_exists(f)
|
||||
}
|
||||
|
||||
/// Mutate the value if closure returns `Ok`. Deletes the item if mutated to a `None`.
|
||||
pub fn try_mutate_exists<R, E, F: FnOnce(&mut Option<Value>) -> Result<R, E>>(
|
||||
f: F,
|
||||
) -> Result<R, E> {
|
||||
<Self as crate::storage::StorageValue<Value>>::try_mutate_exists(f)
|
||||
}
|
||||
|
||||
/// Clear the storage value.
|
||||
pub fn kill() {
|
||||
<Self as crate::storage::StorageValue<Value>>::kill()
|
||||
}
|
||||
|
||||
/// Take a value from storage, removing it afterwards.
|
||||
pub fn take() -> QueryKind::Query {
|
||||
<Self as crate::storage::StorageValue<Value>>::take()
|
||||
}
|
||||
|
||||
/// Append the given item to the value in the storage.
|
||||
///
|
||||
/// `Value` is required to implement [`StorageAppend`].
|
||||
///
|
||||
/// # Warning
|
||||
///
|
||||
/// If the storage item is not encoded properly, the storage item will be overwritten
|
||||
/// and set to `[item]`. Any default value set for the storage item will be ignored
|
||||
/// on overwrite.
|
||||
pub fn append<Item, EncodeLikeItem>(item: EncodeLikeItem)
|
||||
where
|
||||
Item: Encode,
|
||||
EncodeLikeItem: EncodeLike<Item>,
|
||||
Value: StorageAppend<Item>,
|
||||
{
|
||||
<Self as crate::storage::StorageValue<Value>>::append(item)
|
||||
}
|
||||
|
||||
/// Read the length of the storage value without decoding the entire value.
|
||||
///
|
||||
/// `Value` is required to implement [`StorageDecodeLength`].
|
||||
///
|
||||
/// If the value does not exists or it fails to decode the length, `None` is returned.
|
||||
/// Otherwise `Some(len)` is returned.
|
||||
///
|
||||
/// # Warning
|
||||
///
|
||||
/// `None` does not mean that `get()` does not return a value. The default value is completely
|
||||
/// ignored by this function.
|
||||
pub fn decode_len() -> Option<usize>
|
||||
where
|
||||
Value: StorageDecodeLength,
|
||||
{
|
||||
<Self as crate::storage::StorageValue<Value>>::decode_len()
|
||||
}
|
||||
|
||||
/// Read the length of the storage value without decoding the entire value.
|
||||
///
|
||||
/// `Value` is required to implement [`StorageDecodeNonDedupLength`].
|
||||
///
|
||||
/// If the value does not exists or it fails to decode the length, `None` is returned.
|
||||
/// Otherwise `Some(len)` is returned.
|
||||
///
|
||||
/// # Warning
|
||||
///
|
||||
/// - `None` does not mean that `get()` does not return a value. The default value is
|
||||
/// completely
|
||||
/// ignored by this function.
|
||||
///
|
||||
/// - The value returned is the non-deduplicated length of the underlying Vector in storage.This
|
||||
/// means that any duplicate items are included.
|
||||
pub fn decode_non_dedup_len() -> Option<usize>
|
||||
where
|
||||
Value: StorageDecodeNonDedupLength,
|
||||
{
|
||||
<Self as crate::storage::StorageValue<Value>>::decode_non_dedup_len()
|
||||
}
|
||||
|
||||
/// Try and append the given item to the value in the storage.
|
||||
///
|
||||
/// Is only available if `Value` of the storage implements [`StorageTryAppend`].
|
||||
pub fn try_append<Item, EncodeLikeItem>(item: EncodeLikeItem) -> Result<(), ()>
|
||||
where
|
||||
Item: Encode,
|
||||
EncodeLikeItem: EncodeLike<Item>,
|
||||
Value: StorageTryAppend<Item>,
|
||||
{
|
||||
<Self as crate::storage::TryAppendValue<Value, Item>>::try_append(item)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Prefix, Value, QueryKind, OnEmpty> StorageEntryMetadataBuilder
|
||||
for StorageValue<Prefix, Value, QueryKind, OnEmpty>
|
||||
where
|
||||
Prefix: StorageInstance,
|
||||
Value: FullCodec + scale_info::StaticTypeInfo,
|
||||
QueryKind: QueryKindTrait<Value, OnEmpty>,
|
||||
OnEmpty: crate::traits::Get<QueryKind::Query> + 'static,
|
||||
{
|
||||
fn build_metadata(
|
||||
deprecation_status: pezsp_metadata_ir::ItemDeprecationInfoIR,
|
||||
docs: Vec<&'static str>,
|
||||
entries: &mut Vec<StorageEntryMetadataIR>,
|
||||
) {
|
||||
let docs = if cfg!(feature = "no-metadata-docs") { vec![] } else { docs };
|
||||
|
||||
let entry = StorageEntryMetadataIR {
|
||||
name: Prefix::STORAGE_PREFIX,
|
||||
modifier: QueryKind::METADATA,
|
||||
ty: StorageEntryTypeIR::Plain(scale_info::meta_type::<Value>()),
|
||||
default: OnEmpty::get().encode(),
|
||||
docs,
|
||||
deprecation_info: deprecation_status,
|
||||
};
|
||||
|
||||
entries.push(entry);
|
||||
}
|
||||
}
|
||||
|
||||
impl<Prefix, Value, QueryKind, OnEmpty> crate::traits::StorageInfoTrait
|
||||
for StorageValue<Prefix, Value, QueryKind, OnEmpty>
|
||||
where
|
||||
Prefix: StorageInstance,
|
||||
Value: FullCodec + MaxEncodedLen,
|
||||
QueryKind: QueryKindTrait<Value, OnEmpty>,
|
||||
OnEmpty: crate::traits::Get<QueryKind::Query> + 'static,
|
||||
{
|
||||
fn storage_info() -> Vec<StorageInfo> {
|
||||
vec![StorageInfo {
|
||||
pezpallet_name: Self::pezpallet_prefix().to_vec(),
|
||||
storage_name: Self::storage_prefix().to_vec(),
|
||||
prefix: Self::hashed_key().to_vec(),
|
||||
max_values: Some(1),
|
||||
max_size: Some(Value::max_encoded_len().saturated_into()),
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
/// It doesn't require to implement `MaxEncodedLen` and give no information for `max_size`.
|
||||
impl<Prefix, Value, QueryKind, OnEmpty> crate::traits::PartialStorageInfoTrait
|
||||
for StorageValue<Prefix, Value, QueryKind, OnEmpty>
|
||||
where
|
||||
Prefix: StorageInstance,
|
||||
Value: FullCodec,
|
||||
QueryKind: QueryKindTrait<Value, OnEmpty>,
|
||||
OnEmpty: crate::traits::Get<QueryKind::Query> + 'static,
|
||||
{
|
||||
fn partial_storage_info() -> Vec<StorageInfo> {
|
||||
vec![StorageInfo {
|
||||
pezpallet_name: Self::pezpallet_prefix().to_vec(),
|
||||
storage_name: Self::storage_prefix().to_vec(),
|
||||
prefix: Self::hashed_key().to_vec(),
|
||||
max_values: Some(1),
|
||||
max_size: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::storage::types::ValueQuery;
|
||||
use pezsp_io::{hashing::twox_128, TestExternalities};
|
||||
use pezsp_metadata_ir::StorageEntryModifierIR;
|
||||
|
||||
struct Prefix;
|
||||
impl StorageInstance for Prefix {
|
||||
fn pezpallet_prefix() -> &'static str {
|
||||
"test"
|
||||
}
|
||||
const STORAGE_PREFIX: &'static str = "foo";
|
||||
}
|
||||
|
||||
struct ADefault;
|
||||
impl crate::traits::Get<u32> for ADefault {
|
||||
fn get() -> u32 {
|
||||
97
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
type A = StorageValue<Prefix, u32, OptionQuery>;
|
||||
type AValueQueryWithAnOnEmpty = StorageValue<Prefix, u32, ValueQuery, ADefault>;
|
||||
type B = StorageValue<Prefix, u16, ValueQuery>;
|
||||
type WithLen = StorageValue<Prefix, Vec<u32>>;
|
||||
|
||||
TestExternalities::default().execute_with(|| {
|
||||
assert_eq!(A::hashed_key().to_vec(), [twox_128(b"test"), twox_128(b"foo")].concat());
|
||||
assert_eq!(A::exists(), false);
|
||||
assert_eq!(A::get(), None);
|
||||
assert_eq!(AValueQueryWithAnOnEmpty::get(), 97);
|
||||
assert_eq!(A::try_get(), Err(()));
|
||||
|
||||
A::put(2);
|
||||
assert_eq!(A::exists(), true);
|
||||
assert_eq!(A::get(), Some(2));
|
||||
assert_eq!(AValueQueryWithAnOnEmpty::get(), 2);
|
||||
assert_eq!(A::try_get(), Ok(2));
|
||||
assert_eq!(A::try_get(), Ok(2));
|
||||
|
||||
B::put(4);
|
||||
A::translate::<u16, _>(|v| v.map(Into::into)).unwrap();
|
||||
assert_eq!(A::try_get(), Ok(4));
|
||||
|
||||
A::set(None);
|
||||
assert_eq!(A::try_get(), Err(()));
|
||||
|
||||
A::set(Some(2));
|
||||
assert_eq!(A::try_get(), Ok(2));
|
||||
|
||||
A::mutate(|v| *v = Some(v.unwrap() * 2));
|
||||
assert_eq!(A::try_get(), Ok(4));
|
||||
|
||||
A::set(Some(4));
|
||||
let _: Result<(), ()> = A::try_mutate(|v| {
|
||||
*v = Some(v.unwrap() * 2);
|
||||
Ok(())
|
||||
});
|
||||
assert_eq!(A::try_get(), Ok(8));
|
||||
|
||||
let _: Result<(), ()> = A::try_mutate(|v| {
|
||||
*v = Some(v.unwrap() * 2);
|
||||
Err(())
|
||||
});
|
||||
assert_eq!(A::try_get(), Ok(8));
|
||||
|
||||
A::kill();
|
||||
AValueQueryWithAnOnEmpty::mutate(|v| *v = *v * 2);
|
||||
assert_eq!(AValueQueryWithAnOnEmpty::try_get(), Ok(97 * 2));
|
||||
|
||||
AValueQueryWithAnOnEmpty::kill();
|
||||
let _: Result<(), ()> = AValueQueryWithAnOnEmpty::try_mutate(|v| {
|
||||
*v = *v * 2;
|
||||
Ok(())
|
||||
});
|
||||
assert_eq!(AValueQueryWithAnOnEmpty::try_get(), Ok(97 * 2));
|
||||
|
||||
A::kill();
|
||||
assert_eq!(A::try_get(), Err(()));
|
||||
|
||||
let mut entries = vec![];
|
||||
A::build_metadata(
|
||||
pezsp_metadata_ir::ItemDeprecationInfoIR::NotDeprecated,
|
||||
vec![],
|
||||
&mut entries,
|
||||
);
|
||||
AValueQueryWithAnOnEmpty::build_metadata(
|
||||
pezsp_metadata_ir::ItemDeprecationInfoIR::NotDeprecated,
|
||||
vec![],
|
||||
&mut entries,
|
||||
);
|
||||
assert_eq!(
|
||||
entries,
|
||||
vec![
|
||||
StorageEntryMetadataIR {
|
||||
name: "foo",
|
||||
modifier: StorageEntryModifierIR::Optional,
|
||||
ty: StorageEntryTypeIR::Plain(scale_info::meta_type::<u32>()),
|
||||
default: Option::<u32>::None.encode(),
|
||||
docs: vec![],
|
||||
deprecation_info: pezsp_metadata_ir::ItemDeprecationInfoIR::NotDeprecated
|
||||
},
|
||||
StorageEntryMetadataIR {
|
||||
name: "foo",
|
||||
modifier: StorageEntryModifierIR::Default,
|
||||
ty: StorageEntryTypeIR::Plain(scale_info::meta_type::<u32>()),
|
||||
default: 97u32.encode(),
|
||||
docs: vec![],
|
||||
deprecation_info: pezsp_metadata_ir::ItemDeprecationInfoIR::NotDeprecated
|
||||
}
|
||||
]
|
||||
);
|
||||
|
||||
WithLen::kill();
|
||||
assert_eq!(WithLen::decode_len(), None);
|
||||
WithLen::append(3);
|
||||
assert_eq!(WithLen::decode_len(), Some(1));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Operation on unhashed runtime storage.
|
||||
|
||||
use alloc::vec::Vec;
|
||||
use codec::{Decode, Encode};
|
||||
|
||||
/// 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> {
|
||||
pezsp_io::storage::get(key).and_then(|val| {
|
||||
Decode::decode(&mut &val[..]).map(Some).unwrap_or_else(|e| {
|
||||
// TODO #3700: error should be handleable.
|
||||
log::error!(
|
||||
target: "runtime::storage",
|
||||
"Corrupted state at `{}`: {:?}",
|
||||
array_bytes::bytes2hex("0x", key),
|
||||
e,
|
||||
);
|
||||
None
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// 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_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 + ?Sized>(key: &[u8], value: &T) {
|
||||
value.using_encoded(|slice| pezsp_io::storage::set(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_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 {
|
||||
pezsp_io::storage::exists(key)
|
||||
}
|
||||
|
||||
/// Ensure `key` has no explicit entry in storage.
|
||||
pub fn kill(key: &[u8]) {
|
||||
pezsp_io::storage::clear(key);
|
||||
}
|
||||
|
||||
/// Ensure keys with the given `prefix` have no entries in storage.
|
||||
#[deprecated = "Use `clear_prefix` instead"]
|
||||
pub fn kill_prefix(prefix: &[u8], limit: Option<u32>) -> pezsp_io::KillStorageResult {
|
||||
// TODO: Once the network has upgraded to include the new host functions, this code can be
|
||||
// enabled.
|
||||
// clear_prefix(prefix, limit).into()
|
||||
pezsp_io::storage::clear_prefix(prefix, limit)
|
||||
}
|
||||
|
||||
/// Partially clear the storage of all keys under a common `prefix`.
|
||||
///
|
||||
/// # Limit
|
||||
///
|
||||
/// A *limit* should always be provided through `maybe_limit`. This is one fewer than the
|
||||
/// maximum number of backend iterations which may be done by this operation and as such
|
||||
/// represents the maximum number of backend deletions which may happen. A *limit* of zero
|
||||
/// implies that no keys will be deleted, though there may be a single iteration done.
|
||||
///
|
||||
/// The limit can be used to partially delete storage items in case it is too large or costly
|
||||
/// to delete all in a single operation.
|
||||
///
|
||||
/// # Cursor
|
||||
///
|
||||
/// A *cursor* may be passed in to this operation with `maybe_cursor`. `None` should only be
|
||||
/// passed once (in the initial call) for any attempt to clear storage. In general, subsequent calls
|
||||
/// operating on the same prefix should pass `Some` and this value should be equal to the
|
||||
/// previous call result's `maybe_cursor` field. The only exception to this is when you can
|
||||
/// guarantee that the subsequent call is in a new block; in this case the previous call's result
|
||||
/// cursor need not be passed in and a `None` may be passed instead. This exception may be useful
|
||||
/// then making this call solely from a block-hook such as `on_initialize`.
|
||||
///
|
||||
/// Returns [`MultiRemovalResults`](pezsp_io::MultiRemovalResults) to inform about the result. Once the
|
||||
/// resultant `maybe_cursor` field is `None`, then no further items remain to be deleted.
|
||||
///
|
||||
/// NOTE: After the initial call for any given child storage, it is important that no keys further
|
||||
/// keys are inserted. If so, then they may or may not be deleted by subsequent calls.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Please note that keys which are residing in the overlay for the child are deleted without
|
||||
/// counting towards the `limit`.
|
||||
pub fn clear_prefix(
|
||||
prefix: &[u8],
|
||||
maybe_limit: Option<u32>,
|
||||
_maybe_cursor: Option<&[u8]>,
|
||||
) -> pezsp_io::MultiRemovalResults {
|
||||
// TODO: Once the network has upgraded to include the new host functions, this code can be
|
||||
// enabled.
|
||||
// pezsp_io::storage::clear_prefix(prefix, maybe_limit, maybe_cursor)
|
||||
use pezsp_io::{KillStorageResult::*, MultiRemovalResults};
|
||||
#[allow(deprecated)]
|
||||
let (maybe_cursor, i) = match kill_prefix(prefix, maybe_limit) {
|
||||
AllRemoved(i) => (None, i),
|
||||
SomeRemaining(i) => (Some(prefix.to_vec()), i),
|
||||
};
|
||||
MultiRemovalResults { maybe_cursor, backend: i, unique: i, loops: i }
|
||||
}
|
||||
|
||||
/// Returns `true` if the storage contains any key, which starts with a certain prefix,
|
||||
/// and is longer than said prefix.
|
||||
/// This means that a key which equals the prefix will not be counted.
|
||||
pub fn contains_prefixed_key(prefix: &[u8]) -> bool {
|
||||
match pezsp_io::storage::next_key(prefix) {
|
||||
Some(key) => key.starts_with(prefix),
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a Vec of bytes from storage.
|
||||
pub fn get_raw(key: &[u8]) -> Option<Vec<u8>> {
|
||||
pezsp_io::storage::get(key).map(|value| value.to_vec())
|
||||
}
|
||||
|
||||
/// Put a raw byte slice into storage.
|
||||
///
|
||||
/// **WARNING**: If you set the storage of the Bizinikiwi Wasm (`well_known_keys::CODE`),
|
||||
/// you should also call `pezframe_system::RuntimeUpgraded::put(true)` to trigger the
|
||||
/// `on_runtime_upgrade` logic.
|
||||
pub fn put_raw(key: &[u8], value: &[u8]) {
|
||||
pezsp_io::storage::set(key, value)
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Traits, types and structs to support putting a bounded vector into storage, as a raw value, map
|
||||
//! or a double map.
|
||||
|
||||
use crate::{
|
||||
storage::{StorageDecodeLength, StorageTryAppend},
|
||||
traits::Get,
|
||||
};
|
||||
pub use pezsp_runtime::WeakBoundedVec;
|
||||
|
||||
impl<T, S> StorageDecodeLength for WeakBoundedVec<T, S> {}
|
||||
|
||||
impl<T, S: Get<u32>> StorageTryAppend<T> for WeakBoundedVec<T, S> {
|
||||
fn bound() -> usize {
|
||||
S::get() as usize
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod test {
|
||||
use super::*;
|
||||
use crate::Twox128;
|
||||
use pezframe_support::traits::ConstU32;
|
||||
use pezsp_io::TestExternalities;
|
||||
|
||||
#[crate::storage_alias]
|
||||
type Foo = StorageValue<Prefix, WeakBoundedVec<u32, ConstU32<7>>>;
|
||||
#[crate::storage_alias]
|
||||
type FooMap = StorageMap<Prefix, Twox128, u32, WeakBoundedVec<u32, ConstU32<7>>>;
|
||||
#[crate::storage_alias]
|
||||
type FooDoubleMap =
|
||||
StorageDoubleMap<Prefix, Twox128, u32, Twox128, u32, WeakBoundedVec<u32, ConstU32<7>>>;
|
||||
|
||||
#[test]
|
||||
fn decode_len_works() {
|
||||
TestExternalities::default().execute_with(|| {
|
||||
let bounded: WeakBoundedVec<u32, ConstU32<7>> = vec![1, 2, 3].try_into().unwrap();
|
||||
Foo::put(bounded);
|
||||
assert_eq!(Foo::decode_len().unwrap(), 3);
|
||||
});
|
||||
|
||||
TestExternalities::default().execute_with(|| {
|
||||
let bounded: WeakBoundedVec<u32, ConstU32<7>> = vec![1, 2, 3].try_into().unwrap();
|
||||
FooMap::insert(1, bounded);
|
||||
assert_eq!(FooMap::decode_len(1).unwrap(), 3);
|
||||
assert!(FooMap::decode_len(0).is_none());
|
||||
assert!(FooMap::decode_len(2).is_none());
|
||||
});
|
||||
|
||||
TestExternalities::default().execute_with(|| {
|
||||
let bounded: WeakBoundedVec<u32, ConstU32<7>> = vec![1, 2, 3].try_into().unwrap();
|
||||
FooDoubleMap::insert(1, 1, bounded);
|
||||
assert_eq!(FooDoubleMap::decode_len(1, 1).unwrap(), 3);
|
||||
assert!(FooDoubleMap::decode_len(2, 1).is_none());
|
||||
assert!(FooDoubleMap::decode_len(1, 2).is_none());
|
||||
assert!(FooDoubleMap::decode_len(2, 2).is_none());
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user