From e0766bb97dd33e2b877625737f78b0f41dce222a Mon Sep 17 00:00:00 2001 From: CJ13th <48095175+CJ13th@users.noreply.github.com> Date: Mon, 23 Sep 2024 22:42:17 +0200 Subject: [PATCH] Implement try_append for StorageNMap (#5745) # Description Closes #5722 Added an implementation of the `try_append` functionality which is present on the other storage map types but currently missing from StorageNMap. --------- Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com> --- prdoc/pr_5745.prdoc | 14 ++++ substrate/frame/support/src/storage/mod.rs | 76 +++++++++++++++++++ .../frame/support/src/storage/types/nmap.rs | 14 ++++ 3 files changed, 104 insertions(+) create mode 100644 prdoc/pr_5745.prdoc diff --git a/prdoc/pr_5745.prdoc b/prdoc/pr_5745.prdoc new file mode 100644 index 00000000000..7463589378a --- /dev/null +++ b/prdoc/pr_5745.prdoc @@ -0,0 +1,14 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Implement `try_append` for `StorageNMap` + +doc: + - audience: Runtime Dev + description: | + This PR introduces the `try_append` api which is available on other storage map types, + but missing on `StorageNMap`. + +crates: + - name: frame-support + bump: minor diff --git a/substrate/frame/support/src/storage/mod.rs b/substrate/frame/support/src/storage/mod.rs index 7fb991d3779..61939256303 100644 --- a/substrate/frame/support/src/storage/mod.rs +++ b/substrate/frame/support/src/storage/mod.rs @@ -1693,6 +1693,46 @@ where } } +/// Storage N map that is capable of [`StorageTryAppend`]. +pub trait TryAppendNMap<K: KeyGenerator, T: StorageTryAppend<I>, I: Encode> { + /// Try and append the `item` into the storage N map at the given `key`. + /// + /// This might fail if bounds are not respected. + fn try_append< + LikeK: EncodeLikeTuple<K::KArg> + TupleToEncodedIter + Clone, + LikeI: EncodeLike<I>, + >( + key: LikeK, + item: LikeI, + ) -> Result<(), ()>; +} + +impl<K, T, I, StorageNMapT> TryAppendNMap<K, T, I> for StorageNMapT +where + K: KeyGenerator, + T: FullCodec + StorageTryAppend<I>, + I: Encode, + StorageNMapT: generator::StorageNMap<K, T>, +{ + fn try_append< + LikeK: EncodeLikeTuple<K::KArg> + TupleToEncodedIter + Clone, + LikeI: EncodeLike<I>, + >( + key: LikeK, + item: LikeI, + ) -> Result<(), ()> { + let bound = T::bound(); + let current = Self::decode_len(key.clone()).unwrap_or_default(); + if current < bound { + let key = Self::storage_n_map_final_key::<K, _>(key); + sp_io::storage::append(&key, item.encode()); + Ok(()) + } else { + Err(()) + } + } +} + /// Returns the storage prefix for a specific pallet name and storage name. /// /// The storage prefix is `concat(twox_128(pallet_name), twox_128(storage_name))`. @@ -2019,6 +2059,17 @@ mod test { (NMapKey<Twox128, u32>, NMapKey<Twox128, u32>, NMapKey<Twox128, u32>), u64, >; + #[crate::storage_alias] + type FooQuadMap = StorageNMap< + Prefix, + ( + NMapKey<Twox128, u32>, + NMapKey<Twox128, u32>, + NMapKey<Twox128, u32>, + NMapKey<Twox128, u32>, + ), + BoundedVec<u32, ConstU32<7>>, + >; #[test] fn contains_prefix_works() { @@ -2109,6 +2160,31 @@ mod test { BoundedVec::<u32, ConstU32<7>>::try_from(vec![4, 5]).unwrap(), ); }); + + TestExternalities::default().execute_with(|| { + let bounded: BoundedVec<u32, ConstU32<7>> = vec![1, 2, 3].try_into().unwrap(); + FooQuadMap::insert((1, 1, 1, 1), bounded); + + assert_ok!(FooQuadMap::try_append((1, 1, 1, 1), 4)); + assert_ok!(FooQuadMap::try_append((1, 1, 1, 1), 5)); + assert_ok!(FooQuadMap::try_append((1, 1, 1, 1), 6)); + assert_ok!(FooQuadMap::try_append((1, 1, 1, 1), 7)); + assert_eq!(FooQuadMap::decode_len((1, 1, 1, 1)).unwrap(), 7); + assert!(FooQuadMap::try_append((1, 1, 1, 1), 8).is_err()); + + // append to a non-existing + assert!(FooQuadMap::get((2, 1, 1, 1)).is_none()); + assert_ok!(FooQuadMap::try_append((2, 1, 1, 1), 4)); + assert_eq!( + FooQuadMap::get((2, 1, 1, 1)).unwrap(), + BoundedVec::<u32, ConstU32<7>>::try_from(vec![4]).unwrap(), + ); + assert_ok!(FooQuadMap::try_append((2, 1, 1, 1), 5)); + assert_eq!( + FooQuadMap::get((2, 1, 1, 1)).unwrap(), + BoundedVec::<u32, ConstU32<7>>::try_from(vec![4, 5]).unwrap(), + ); + }); } #[crate::storage_alias] diff --git a/substrate/frame/support/src/storage/types/nmap.rs b/substrate/frame/support/src/storage/types/nmap.rs index 9ee012f8628..0fc22b35352 100755 --- a/substrate/frame/support/src/storage/types/nmap.rs +++ b/substrate/frame/support/src/storage/types/nmap.rs @@ -25,6 +25,7 @@ use crate::{ StorageEntryMetadataBuilder, TupleToEncodedIter, }, KeyGenerator, PrefixIterator, StorageAppend, StorageDecodeLength, StoragePrefixedMap, + StorageTryAppend, }, traits::{Get, GetDefault, StorageInfo, StorageInstance}, }; @@ -338,6 +339,19 @@ where <Self as crate::storage::StorageNMap<Key, Value>>::append(key, item) } + /// 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: EncodeLikeTuple<Key::KArg> + TupleToEncodedIter + Clone, + Item: Encode, + EncodeLikeItem: EncodeLike<Item>, + Value: StorageTryAppend<Item>, + { + <Self as crate::storage::TryAppendNMap<Key, Value, Item>>::try_append(key, item) + } + /// Read the length of the storage value without decoding the entire value under the /// given `key1` and `key2`. /// -- GitLab