From 073040a053eb03f56c0393dfc2731106095e9da3 Mon Sep 17 00:00:00 2001 From: Gavin Wood <gavin@parity.io> Date: Thu, 31 Oct 2019 09:24:23 +0100 Subject: [PATCH] Add translate API for storage values (#3947) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add translate item. * fix * doc * fix doc * A test added. * Apply suggestions from code review Co-Authored-By: Bastian Köcher <bkchr@users.noreply.github.com> * address suggestion --- .../srml/support/src/storage/generator/mod.rs | 49 +++++++++++++++++++ .../support/src/storage/generator/value.rs | 19 ++++++- substrate/srml/support/src/storage/mod.rs | 24 ++++++++- 3 files changed, 90 insertions(+), 2 deletions(-) diff --git a/substrate/srml/support/src/storage/generator/mod.rs b/substrate/srml/support/src/storage/generator/mod.rs index ab7616158af..1bda7910237 100644 --- a/substrate/srml/support/src/storage/generator/mod.rs +++ b/substrate/srml/support/src/storage/generator/mod.rs @@ -30,3 +30,52 @@ pub use linked_map::{StorageLinkedMap, Enumerator, Linkage}; pub use map::StorageMap; pub use double_map::StorageDoubleMap; pub use value::StorageValue; + + +#[cfg(test)] +#[allow(dead_code)] +mod tests { + use runtime_io::TestExternalities; + use codec::Encode; + use crate::storage::{unhashed, generator::StorageValue}; + + struct Runtime {} + pub trait Trait { + type Origin; + type BlockNumber; + } + + impl Trait for Runtime { + type Origin = u32; + type BlockNumber = u32; + } + + decl_module! { + pub struct Module<T: Trait> for enum Call where origin: T::Origin {} + } + + crate::decl_storage! { + trait Store for Module<T: Trait> as Runtime { + Value get(fn value) config(): (u64, u64); + } + } + + #[test] + fn value_translate_works() { + let t = GenesisConfig::default().build_storage().unwrap(); + TestExternalities::new(t).execute_with(|| { + // 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 _ = Value::translate(translate_fn); + + // new storage should be `(1111, 1111 * 2)` + assert_eq!(Value::get(), (1111, 2222)); + }) + } +} diff --git a/substrate/srml/support/src/storage/generator/value.rs b/substrate/srml/support/src/storage/generator/value.rs index 8423503dde2..5ebc25a70af 100644 --- a/substrate/srml/support/src/storage/generator/value.rs +++ b/substrate/srml/support/src/storage/generator/value.rs @@ -16,7 +16,7 @@ #[cfg(not(feature = "std"))] use rstd::prelude::*; -use codec::{FullCodec, Encode, EncodeAppend, EncodeLike}; +use codec::{FullCodec, Encode, EncodeAppend, EncodeLike, Decode}; use crate::{storage::{self, unhashed}, hash::{Twox128, StorageHasher}, traits::Len}; /// Generator for `StorageValue` used by `decl_storage`. @@ -60,6 +60,23 @@ impl<T: FullCodec, G: StorageValue<T>> storage::StorageValue<T> for G { G::from_optional_value_to_query(value) } + 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 = match unhashed::get_raw(&key) { + Some(old_data) => Some(O::decode(&mut &old_data[..]).map_err(|_| ())?), + None => None, + }; + 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) } diff --git a/substrate/srml/support/src/storage/mod.rs b/substrate/srml/support/src/storage/mod.rs index 648009b470e..5636d211990 100644 --- a/substrate/srml/support/src/storage/mod.rs +++ b/substrate/srml/support/src/storage/mod.rs @@ -17,7 +17,7 @@ //! Stuff to do with the runtime's storage. use rstd::prelude::*; -use codec::{FullCodec, FullEncode, Encode, EncodeAppend, EncodeLike}; +use codec::{FullCodec, FullEncode, Encode, EncodeAppend, EncodeLike, Decode}; use crate::traits::Len; #[macro_use] @@ -41,6 +41,28 @@ pub trait StorageValue<T: FullCodec> { /// Load the value from the provided storage instance. fn get() -> Self::Query; + /// 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_initialize, while + /// ensuring **no usage of this storage are made before the call to `on_initialize`**. (More + /// precisely prior initialized modules doesn't make use of this storage). + fn translate<O: Decode, F: FnOnce(Option<O>) -> Option<T>>(f: F) -> Result<Option<T>, ()>; + /// Store a value under this key into the provided storage instance. fn put<Arg: EncodeLike<T>>(val: Arg); -- GitLab