Skip to content

Commit

Permalink
Implement try_append for StorageNMap (#5745)
Browse files Browse the repository at this point in the history
# 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>
  • Loading branch information
CJ13th and shawntabrizi committed Sep 23, 2024
1 parent 71c768a commit e0766bb
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 0 deletions.
14 changes: 14 additions & 0 deletions prdoc/pr_5745.prdoc
Original file line number Diff line number Diff line change
@@ -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
76 changes: 76 additions & 0 deletions substrate/frame/support/src/storage/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))`.
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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]
Expand Down
14 changes: 14 additions & 0 deletions substrate/frame/support/src/storage/types/nmap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ use crate::{
StorageEntryMetadataBuilder, TupleToEncodedIter,
},
KeyGenerator, PrefixIterator, StorageAppend, StorageDecodeLength, StoragePrefixedMap,
StorageTryAppend,
},
traits::{Get, GetDefault, StorageInfo, StorageInstance},
};
Expand Down Expand Up @@ -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`.
///
Expand Down

0 comments on commit e0766bb

Please sign in to comment.