Implement try_mutate for storage value and storage double map (#5699)

* impl try_mutate for storage value and storage double map

* Docs + Reuse `try_mutate` in `mutate`
This commit is contained in:
Shawn Tabrizi
2020-04-20 15:05:20 +02:00
committed by GitHub
parent 319a00fb1c
commit 7d9aa81bfc
8 changed files with 116 additions and 33 deletions
@@ -17,7 +17,7 @@
use sp_std::prelude::*;
use sp_std::borrow::Borrow;
use codec::{Ref, FullCodec, FullEncode, Decode, Encode, EncodeLike, EncodeAppend};
use crate::{storage::{self, unhashed}, traits::Len};
use crate::{storage::{self, unhashed}, traits::Len, Never};
use crate::hash::{StorageHasher, Twox128, ReversibleStorageHasher};
/// Generator for `StorageDoubleMap` used by `decl_storage`.
@@ -223,14 +223,24 @@ impl<K1, K2, V, G> storage::StorageDoubleMap<K1, K2, V> for G 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 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);
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()),
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
}
@@ -18,7 +18,7 @@
use sp_std::prelude::*;
use sp_std::borrow::Borrow;
use codec::{FullCodec, FullEncode, Decode, Encode, EncodeLike, Ref, EncodeAppend};
use crate::{storage::{self, unhashed}, traits::Len};
use crate::{storage::{self, unhashed}, traits::Len, Never};
use crate::hash::{StorageHasher, Twox128, ReversibleStorageHasher};
/// Generator for `StorageMap` used by `decl_storage`.
@@ -229,27 +229,11 @@ impl<K: FullEncode, V: FullCodec, G: StorageMap<K, V>> storage::StorageMap<K, V>
}
fn mutate<KeyArg: EncodeLike<K>, R, F: FnOnce(&mut Self::Query) -> R>(key: KeyArg, f: F) -> R {
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);
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
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 {
let final_key = Self::storage_map_final_key(key);
let mut val = unhashed::get(final_key.as_ref());
let ret = f(&mut val);
match val {
Some(ref val) => unhashed::put(final_key.as_ref(), &val),
None => unhashed::kill(final_key.as_ref()),
}
ret
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>>(
@@ -37,6 +37,7 @@ mod tests {
use sp_io::TestExternalities;
use codec::Encode;
use crate::storage::{unhashed, generator::StorageValue, IterableStorageMap};
use crate::{assert_noop, assert_ok};
struct Runtime {}
pub trait Trait {
@@ -57,6 +58,7 @@ mod tests {
trait Store for Module<T: Trait> as Runtime {
Value get(fn value) config(): (u64, u64);
NumberMap: map hasher(identity) u32 => u64;
DoubleMap: double_map hasher(identity) u32, hasher(identity) u32 => u64;
}
}
@@ -102,4 +104,54 @@ mod tests {
);
})
}
#[test]
fn try_mutate_works() {
let t = GenesisConfig::default().build_storage().unwrap();
TestExternalities::new(t).execute_with(|| {
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);
});
}
}
@@ -17,7 +17,12 @@
#[cfg(not(feature = "std"))]
use sp_std::prelude::*;
use codec::{FullCodec, Encode, EncodeAppend, EncodeLike, Decode};
use crate::{storage::{self, unhashed}, hash::{Twox128, StorageHasher}, traits::Len};
use crate::{
Never,
storage::{self, unhashed},
hash::{Twox128, StorageHasher},
traits::Len
};
/// Generator for `StorageValue` used by `decl_storage`.
///
@@ -104,12 +109,18 @@ impl<T: FullCodec, G: StorageValue<T>> storage::StorageValue<T> for G {
}
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);
match G::from_query_to_optional_value(val) {
Some(ref val) => G::put(val),
None => G::kill(),
if ret.is_ok() {
match G::from_query_to_optional_value(val) {
Some(ref val) => G::put(val),
None => G::kill(),
}
}
ret
}