diff --git a/frame/collective/src/lib.rs b/frame/collective/src/lib.rs index 5c33bff3006b7..6284617e89bd9 100644 --- a/frame/collective/src/lib.rs +++ b/frame/collective/src/lib.rs @@ -48,13 +48,12 @@ use sp_io::storage; use sp_runtime::{RuntimeDebug, traits::Hash}; use frame_support::{ + decl_error, decl_event, decl_module, decl_storage, ensure, BoundedVec, codec::{Decode, Encode}, - decl_error, decl_event, decl_module, decl_storage, dispatch::{ DispatchError, DispatchResult, DispatchResultWithPostInfo, Dispatchable, Parameter, PostDispatchInfo, }, - ensure, traits::{ChangeMembers, EnsureOrigin, Get, InitializeMembers, GetBacking, Backing}, weights::{DispatchClass, GetDispatchInfo, Weight, Pays}, }; @@ -195,7 +194,7 @@ pub struct Votes { decl_storage! { trait Store for Module, I: Instance=DefaultInstance> as Collective { /// The hashes of the active proposals. - pub Proposals get(fn proposals): Vec; + pub Proposals get(fn proposals): BoundedVec; /// Actual proposal for a given hash, if it's current. pub ProposalOf get(fn proposal_of): map hasher(identity) T::Hash => Option<>::Proposal>; @@ -471,11 +470,7 @@ decl_module! { } else { let active_proposals = >::try_mutate(|proposals| -> Result { - proposals.push(proposal_hash); - ensure!( - proposals.len() <= T::MaxProposals::get() as usize, - Error::::TooManyProposals - ); + proposals.try_push(proposal_hash).map_err(|_| Error::::TooManyProposals)?; Ok(proposals.len()) })?; let index = Self::proposal_count(); @@ -1086,7 +1081,7 @@ mod tests { fn motions_basic_environment_works() { new_test_ext().execute_with(|| { assert_eq!(Collective::members(), vec![1, 2, 3]); - assert_eq!(Collective::proposals(), Vec::::new()); + assert_eq!(*Collective::proposals(), Vec::::new()); }); } @@ -1316,7 +1311,7 @@ mod tests { let hash = proposal.blake2_256().into(); let end = 4; assert_ok!(Collective::propose(Origin::signed(1), 3, Box::new(proposal.clone()), proposal_len)); - assert_eq!(Collective::proposals(), vec![hash]); + assert_eq!(*Collective::proposals(), vec![hash]); assert_eq!(Collective::proposal_of(&hash), Some(proposal)); assert_eq!( Collective::voting(&hash), @@ -1577,9 +1572,9 @@ mod tests { assert_ok!(Collective::propose(Origin::signed(1), 3, Box::new(proposal.clone()), proposal_len)); assert_ok!(Collective::vote(Origin::signed(2), hash.clone(), 0, false)); assert_ok!(Collective::close(Origin::signed(2), hash.clone(), 0, proposal_weight, proposal_len)); - assert_eq!(Collective::proposals(), vec![]); + assert_eq!(*Collective::proposals(), vec![]); assert_ok!(Collective::propose(Origin::signed(1), 2, Box::new(proposal.clone()), proposal_len)); - assert_eq!(Collective::proposals(), vec![hash]); + assert_eq!(*Collective::proposals(), vec![hash]); }); } diff --git a/frame/democracy/src/tests.rs b/frame/democracy/src/tests.rs index 9df594ed07877..73bbb5481dade 100644 --- a/frame/democracy/src/tests.rs +++ b/frame/democracy/src/tests.rs @@ -120,9 +120,10 @@ impl pallet_scheduler::Config for Test { } parameter_types! { pub const ExistentialDeposit: u64 = 1; + pub const MaxLocks: u32 = 10; } impl pallet_balances::Config for Test { - type MaxLocks = (); + type MaxLocks = MaxLocks; type Balance = u64; type Event = Event; type DustRemoval = (); diff --git a/frame/support/src/lib.rs b/frame/support/src/lib.rs index ef5f64cfc24af..6740e0db5a0ef 100644 --- a/frame/support/src/lib.rs +++ b/frame/support/src/lib.rs @@ -75,7 +75,8 @@ pub use self::hash::{ }; pub use self::storage::{ StorageValue, StorageMap, StorageDoubleMap, StoragePrefixedMap, IterableStorageMap, - IterableStorageDoubleMap, migration + IterableStorageDoubleMap, migration, + bounded_vec::{self, BoundedVec}, }; pub use self::dispatch::{Parameter, Callable}; pub use sp_runtime::{self, ConsensusEngineId, print, traits::Printable}; @@ -112,20 +113,20 @@ impl TypeId for PalletId { /// /// // generate a double map from `(u32, u32)` (with hasher `Twox64Concat`) to `Vec` /// generate_storage_alias!( -/// OtherPrefix, OtherStorageName => DoubleMap< +/// OtherPrefix, OtherStorageName => DoubleMap< /// (u32, u32), /// (u32, u32), /// Vec -/// > +/// > /// ); /// /// // generate a map from `Config::AccountId` (with hasher `Twox64Concat`) to `Vec` /// trait Config { type AccountId: codec::FullCodec; } /// generate_storage_alias!( -/// Prefix, GenericStorage => Map<(Twox64Concat, T::AccountId), Vec> +/// Prefix, GenericStorage => Map<(Twox64Concat, T::AccountId), Vec> /// ); /// # fn main() {} -///``` +/// ``` #[macro_export] macro_rules! generate_storage_alias { // without generic for $name. @@ -143,7 +144,7 @@ macro_rules! generate_storage_alias { ($pallet:ident, $name:ident => DoubleMap<($key1:ty, $hasher1:ty), ($key2:ty, $hasher2:ty), $value:ty>) => { $crate::paste::paste! { $crate::generate_storage_alias!(@GENERATE_INSTANCE_STRUCT $pallet, $name); - type $name = $crate::storage::types::StorageMap< + type $name = $crate::storage::types::StorageDoubleMap< [<$name Instance>], $hasher1, $key1, @@ -178,12 +179,11 @@ macro_rules! generate_storage_alias { ( $pallet:ident, $name:ident<$t:ident : $bounds:tt> - => DoubleMap<($key1:ty, $hasher1:ty), ($key2:ty, $hasher2:ty), $value:ty>) - => { + => DoubleMap<($key1:ty, $hasher1:ty), ($key2:ty, $hasher2:ty), $value:ty>) => { $crate::paste::paste! { $crate::generate_storage_alias!(@GENERATE_INSTANCE_STRUCT $pallet, $name); #[allow(type_alias_bounds)] - type $name<$t : $bounds> = $crate::storage::types::StorageMap< + type $name<$t : $bounds> = $crate::storage::types::StorageDoubleMap< [<$name Instance>], $key1, $hasher1, @@ -213,7 +213,7 @@ macro_rules! generate_storage_alias { const STORAGE_PREFIX: &'static str = stringify!($name); } } - } + }; } /// Create new implementations of the [`Get`](crate::traits::Get) trait. diff --git a/frame/support/src/storage/bounded_vec.rs b/frame/support/src/storage/bounded_vec.rs new file mode 100644 index 0000000000000..44e3f30a7b31c --- /dev/null +++ b/frame/support/src/storage/bounded_vec.rs @@ -0,0 +1,470 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2021 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 sp_std::prelude::*; +use sp_std::{convert::TryFrom, marker::PhantomData}; +use codec::{FullCodec, Encode, EncodeLike, Decode}; +use crate::{ + traits::Get, + storage::{generator, StorageDecodeLength, StorageValue, StorageMap, StorageDoubleMap}, +}; + +/// Marker trait for types `T` that can be stored in storage as `BoundedVec`. +pub trait BoundedVecValue: FullCodec + Clone + sp_std::fmt::Debug {} +impl BoundedVecValue for T {} + +/// A bounded vector. +/// +/// It has implementations for efficient append and length decoding, as with a normal `Vec<_>`, once +/// put into storage as a raw value, map or double-map. +/// +/// As the name suggests, the length of the queue is always bounded. All internal operations ensure +/// this bound is respected. +#[derive(Encode, Decode, crate::DefaultNoBound, crate::CloneNoBound, crate::DebugNoBound)] +pub struct BoundedVec>(Vec, PhantomData); + +// NOTE: we could also implement this as: +// impl, S2: Get> PartialEq> for BoundedVec +// to allow comparison of bounded vectors with different bounds. +impl> PartialEq for BoundedVec { + fn eq(&self, rhs: &Self) -> bool { + self.0 == rhs.0 + } +} +impl> Eq for BoundedVec {} + +impl> BoundedVec { + /// Get the bound of the type in `usize`. + pub fn bound() -> usize { + S::get() as usize + } + + /// Create `Self` from `t` without any checks. + /// + /// # WARNING + /// + /// Only use when you are sure you know what you are doing. + fn unchecked_from(t: Vec) -> Self { + Self(t, Default::default()) + } + + /// Create `Self` from `t` without any checks. Logs warnings if the bound is not being + /// respected. The additional scope can be used to indicate where a potential overflow is + /// happening. + /// + /// # WARNING + /// + /// Only use when you are sure you know what you are doing. + pub fn force_from(t: Vec, scope: Option<&'static str>) -> Self { + if t.len() > Self::bound() { + log::warn!( + target: crate::LOG_TARGET, + "length of a bounded vector in scope {} is not respected.", + scope.unwrap_or("UNKNOWN"), + ); + } + + Self::unchecked_from(t) + } + + /// Consume self, and return the inner `Vec`. Henceforth, the `Vec<_>` can be altered in an + /// arbitrary way. At some point, if the reverse conversion is required, `TryFrom>` can + /// be used. + /// + /// This is useful for cases if you need access to an internal API of the inner `Vec<_>` which + /// is not provided by the wrapper `BoundedVec`. + pub fn into_inner(self) -> Vec { + debug_assert!(self.0.len() <= Self::bound()); + self.0 + } + + /// Consumes self and mutates self via the given `mutate` function. + /// + /// If the outcome of mutation is within bounds, `Some(Self)` is returned. Else, `None` is + /// returned. + /// + /// This is essentially a *consuming* shorthand [`Self::into_inner`] -> `...` -> + /// [`Self::try_from`]. + pub fn try_mutate(mut self, mut mutate: impl FnMut(&mut Vec)) -> Option { + mutate(&mut self.0); + (self.0.len() <= Self::bound()).then(move || self) + } + + /// Exactly the same semantics as [`Vec::insert`], but returns an `Err` (and is a noop) if the + /// new length of the vector exceeds `S`. + /// + /// # Panics + /// + /// Panics if `index > len`. + pub fn try_insert(&mut self, index: usize, element: T) -> Result<(), ()> { + if self.len() < Self::bound() { + self.0.insert(index, element); + Ok(()) + } else { + Err(()) + } + } + + /// Exactly the same semantics as [`Vec::push`], but returns an `Err` (and is a noop) if the + /// new length of the vector exceeds `S`. + /// + /// # Panics + /// + /// Panics if the new capacity exceeds isize::MAX bytes. + pub fn try_push(&mut self, element: T) -> Result<(), ()> { + if self.len() < Self::bound() { + self.0.push(element); + Ok(()) + } else { + Err(()) + } + } + + /// Exactly the same semantics as [`Vec::remove`]. + /// + /// # Panics + /// + /// Panics if `index` is out of bounds. + pub fn remove(&mut self, index: usize) { + self.0.remove(index); + } + + /// Exactly the same semantics as [`Vec::swap_remove`]. + /// + /// # Panics + /// + /// Panics if `index` is out of bounds. + pub fn swap_remove(&mut self, index: usize) { + self.0.swap_remove(index); + } + + /// Exactly the same semantics as [`Vec::retain`]. + pub fn retain bool>(&mut self, f: F) { + self.0.retain(f) + } +} + +impl> TryFrom> for BoundedVec { + type Error = (); + fn try_from(t: Vec) -> Result { + if t.len() <= Self::bound() { + Ok(Self::unchecked_from(t)) + } else { + Err(()) + } + } +} + +// It is okay to give a non-mutable reference of the inner vec to anyone. +impl> AsRef> for BoundedVec { + fn as_ref(&self) -> &Vec { + &self.0 + } +} + +// will allow for immutable all operations of `Vec` on `BoundedVec`. +impl> sp_std::ops::Deref for BoundedVec { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +// Allows for indexing similar to a normal `Vec`. Can panic if out of bound. +impl> sp_std::ops::Index for BoundedVec { + type Output = T; + fn index(&self, index: usize) -> &Self::Output { + self.get(index).expect("index out of bound") + } +} + +impl> sp_std::iter::IntoIterator for BoundedVec { + type Item = T; + type IntoIter = sp_std::vec::IntoIter; + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl> codec::DecodeLength for BoundedVec { + fn len(self_encoded: &[u8]) -> Result { + // `BoundedVec` stored just a `Vec`, thus the length is at the beginning in + // `Compact` form, and same implementation as `Vec` can be used. + as codec::DecodeLength>::len(self_encoded) + } +} + +impl> StorageDecodeLength for BoundedVec {} + +/// Storage value that is *maybe* capable of [`StorageAppend`]. +pub trait TryAppendValue> { + /// Try and append the `item` into the storage item. + /// + /// This might fail if bounds are not respected. + fn try_append>(item: LikeT) -> Result<(), ()>; +} + +/// Storage map that is *maybe* capable of [`StorageAppend`]. +pub trait TryAppendMap> { + /// Try and append the `item` into the storage map at the given `key`. + /// + /// This might fail if bounds are not respected. + fn try_append + Clone, LikeT: EncodeLike>( + key: LikeK, + item: LikeT, + ) -> Result<(), ()>; +} + +/// Storage double map that is *maybe* capable of [`StorageAppend`]. +pub trait TryAppendDoubleMap> { + /// Try and append the `item` into the storage double map at the given `key`. + /// + /// This might fail if bounds are not respected. + fn try_append< + LikeK1: EncodeLike + Clone, + LikeK2: EncodeLike + Clone, + LikeT: EncodeLike, + >( + key1: LikeK1, + key2: LikeK2, + item: LikeT, + ) -> Result<(), ()>; +} + +impl, StorageValueT: generator::StorageValue>> + TryAppendValue for StorageValueT +{ + fn try_append>(item: LikeT) -> Result<(), ()> { + let bound = BoundedVec::::bound(); + let current = Self::decode_len().unwrap_or_default(); + if current < bound { + // NOTE: we cannot reuse the implementation for `Vec` here because we never want to + // mark `BoundedVec` as `StorageAppend`. + let key = Self::storage_value_final_key(); + sp_io::storage::append(&key, item.encode()); + Ok(()) + } else { + Err(()) + } + } +} + +impl< + K: FullCodec, + T: BoundedVecValue, + S: Get, + StorageMapT: generator::StorageMap>, + > TryAppendMap for StorageMapT +{ + fn try_append + Clone, LikeT: EncodeLike>( + key: LikeK, + item: LikeT, + ) -> Result<(), ()> { + let bound = BoundedVec::::bound(); + let current = Self::decode_len(key.clone()).unwrap_or_default(); + if current < bound { + let key = Self::storage_map_final_key(key); + sp_io::storage::append(&key, item.encode()); + Ok(()) + } else { + Err(()) + } + } +} + +impl< + K1: FullCodec, + K2: FullCodec, + T: BoundedVecValue, + S: Get, + StorageDoubleMapT: generator::StorageDoubleMap>, + > TryAppendDoubleMap for StorageDoubleMapT +{ + fn try_append< + LikeK1: EncodeLike + Clone, + LikeK2: EncodeLike + Clone, + LikeT: EncodeLike, + >( + key1: LikeK1, + key2: LikeK2, + item: LikeT, + ) -> Result<(), ()> { + let bound = BoundedVec::::bound(); + let current = Self::decode_len(key1.clone(), key2.clone()).unwrap_or_default(); + if current < bound { + let double_map_key = Self::storage_double_map_final_key(key1, key2); + sp_io::storage::append(&double_map_key, item.encode()); + Ok(()) + } else { + Err(()) + } + } +} + +#[cfg(test)] +pub mod test { + use super::*; + use sp_io::TestExternalities; + use sp_std::convert::TryInto; + use crate::{assert_ok, Twox128}; + + crate::parameter_types! { + pub const Seven: u32 = 7; + pub const Four: u32 = 4; + } + + crate::generate_storage_alias! { Prefix, Foo => Value> } + crate::generate_storage_alias! { Prefix, FooMap => Map<(u32, Twox128), BoundedVec> } + crate::generate_storage_alias! { + Prefix, + FooDoubleMap => DoubleMap<(u32, Twox128), (u32, Twox128), BoundedVec> + } + + #[test] + fn decode_len_works() { + TestExternalities::default().execute_with(|| { + let bounded: BoundedVec = vec![1, 2, 3].try_into().unwrap(); + Foo::put(bounded); + assert_eq!(Foo::decode_len().unwrap(), 3); + }); + + TestExternalities::default().execute_with(|| { + let bounded: BoundedVec = 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: BoundedVec = 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()); + }); + } + + #[test] + fn try_append_works() { + TestExternalities::default().execute_with(|| { + let bounded: BoundedVec = vec![1, 2, 3].try_into().unwrap(); + Foo::put(bounded); + assert_ok!(Foo::try_append(4)); + assert_ok!(Foo::try_append(5)); + assert_ok!(Foo::try_append(6)); + assert_ok!(Foo::try_append(7)); + assert_eq!(Foo::decode_len().unwrap(), 7); + assert!(Foo::try_append(8).is_err()); + }); + + TestExternalities::default().execute_with(|| { + let bounded: BoundedVec = vec![1, 2, 3].try_into().unwrap(); + FooMap::insert(1, bounded); + + assert_ok!(FooMap::try_append(1, 4)); + assert_ok!(FooMap::try_append(1, 5)); + assert_ok!(FooMap::try_append(1, 6)); + assert_ok!(FooMap::try_append(1, 7)); + assert_eq!(FooMap::decode_len(1).unwrap(), 7); + assert!(FooMap::try_append(1, 8).is_err()); + + // append to a non-existing + assert!(FooMap::get(2).is_none()); + assert_ok!(FooMap::try_append(2, 4)); + assert_eq!(FooMap::get(2).unwrap(), BoundedVec::::unchecked_from(vec![4])); + assert_ok!(FooMap::try_append(2, 5)); + assert_eq!( + FooMap::get(2).unwrap(), + BoundedVec::::unchecked_from(vec![4, 5]) + ); + }); + + TestExternalities::default().execute_with(|| { + let bounded: BoundedVec = vec![1, 2, 3].try_into().unwrap(); + FooDoubleMap::insert(1, 1, bounded); + + assert_ok!(FooDoubleMap::try_append(1, 1, 4)); + assert_ok!(FooDoubleMap::try_append(1, 1, 5)); + assert_ok!(FooDoubleMap::try_append(1, 1, 6)); + assert_ok!(FooDoubleMap::try_append(1, 1, 7)); + assert_eq!(FooDoubleMap::decode_len(1, 1).unwrap(), 7); + assert!(FooDoubleMap::try_append(1, 1, 8).is_err()); + + // append to a non-existing + assert!(FooDoubleMap::get(2, 1).is_none()); + assert_ok!(FooDoubleMap::try_append(2, 1, 4)); + assert_eq!( + FooDoubleMap::get(2, 1).unwrap(), + BoundedVec::::unchecked_from(vec![4]) + ); + assert_ok!(FooDoubleMap::try_append(2, 1, 5)); + assert_eq!( + FooDoubleMap::get(2, 1).unwrap(), + BoundedVec::::unchecked_from(vec![4, 5]) + ); + }); + } + + #[test] + fn try_insert_works() { + let mut bounded: BoundedVec = vec![1, 2, 3].try_into().unwrap(); + bounded.try_insert(1, 0).unwrap(); + assert_eq!(*bounded, vec![1, 0, 2, 3]); + + assert!(bounded.try_insert(0, 9).is_err()); + assert_eq!(*bounded, vec![1, 0, 2, 3]); + } + + #[test] + #[should_panic(expected = "insertion index (is 9) should be <= len (is 3)")] + fn try_inert_panics_if_oob() { + let mut bounded: BoundedVec = vec![1, 2, 3].try_into().unwrap(); + bounded.try_insert(9, 0).unwrap(); + } + + #[test] + fn try_push_works() { + let mut bounded: BoundedVec = vec![1, 2, 3].try_into().unwrap(); + bounded.try_push(0).unwrap(); + assert_eq!(*bounded, vec![1, 2, 3, 0]); + + assert!(bounded.try_push(9).is_err()); + } + + #[test] + fn deref_coercion_works() { + let bounded: BoundedVec = vec![1, 2, 3].try_into().unwrap(); + // these methods come from deref-ed vec. + assert_eq!(bounded.len(), 3); + assert!(bounded.iter().next().is_some()); + assert!(!bounded.is_empty()); + } + + #[test] + fn try_mutate_works() { + let bounded: BoundedVec = vec![1, 2, 3, 4, 5, 6].try_into().unwrap(); + let bounded = bounded.try_mutate(|v| v.push(7)).unwrap(); + assert_eq!(bounded.len(), 7); + assert!(bounded.try_mutate(|v| v.push(8)).is_none()); + } +} diff --git a/frame/support/src/storage/mod.rs b/frame/support/src/storage/mod.rs index e00a3fe831829..adcf44a64620e 100644 --- a/frame/support/src/storage/mod.rs +++ b/frame/support/src/storage/mod.rs @@ -20,12 +20,16 @@ use sp_core::storage::ChildInfo; use sp_std::prelude::*; use codec::{FullCodec, FullEncode, Encode, EncodeLike, Decode}; -use crate::hash::{Twox128, StorageHasher, ReversibleStorageHasher}; +use crate::{ + hash::{Twox128, StorageHasher, ReversibleStorageHasher}, + traits::Get, +}; use sp_runtime::generic::{Digest, DigestItem}; pub use sp_runtime::TransactionOutcome; pub mod unhashed; pub mod hashed; +pub mod bounded_vec; pub mod child; #[doc(hidden)] pub mod generator; @@ -806,19 +810,21 @@ pub trait StorageDecodeLength: private::Sealed + codec::DecodeLength { /// outside of this crate. mod private { use super::*; + use bounded_vec::{BoundedVecValue, BoundedVec}; pub trait Sealed {} impl Sealed for Vec {} impl Sealed for Digest {} + impl> Sealed for BoundedVec {} } impl StorageAppend for Vec {} impl StorageDecodeLength for Vec {} -/// We abuse the fact that SCALE does not put any marker into the encoding, i.e. -/// we only encode the internal vec and we can append to this vec. We have a test that ensures -/// that if the `Digest` format ever changes, we need to remove this here. +/// We abuse the fact that SCALE does not put any marker into the encoding, i.e. we only encode the +/// internal vec and we can append to this vec. We have a test that ensures that if the `Digest` +/// format ever changes, we need to remove this here. impl StorageAppend> for Digest {} #[cfg(test)] diff --git a/frame/support/src/storage/types/double_map.rs b/frame/support/src/storage/types/double_map.rs index f0b5f66eff058..184d96b3a54f9 100644 --- a/frame/support/src/storage/types/double_map.rs +++ b/frame/support/src/storage/types/double_map.rs @@ -22,9 +22,10 @@ use codec::{FullCodec, Decode, EncodeLike, Encode}; use crate::{ storage::{ StorageAppend, StorageDecodeLength, + bounded_vec::{BoundedVec, BoundedVecValue}, types::{OptionQuery, QueryKindTrait, OnEmptyGetter}, }, - traits::{GetDefault, StorageInstance}, + traits::{GetDefault, StorageInstance, Get}, }; use frame_metadata::{DefaultByteGetter, StorageEntryModifier}; use sp_std::vec::Vec; @@ -102,6 +103,50 @@ where } } +impl + StorageDoubleMap< + Prefix, + Hasher1, + Key1, + Hasher2, + Key2, + BoundedVec, + QueryKind, + OnEmpty, + > where + Prefix: StorageInstance, + Hasher1: crate::hash::StorageHasher, + Hasher2: crate::hash::StorageHasher, + Key1: FullCodec, + Key2: FullCodec, + QueryKind: QueryKindTrait, OnEmpty>, + OnEmpty: crate::traits::Get + 'static, + VecValue: BoundedVecValue, + VecBound: Get, +{ + /// Try and append the given item to the double map in the storage. + /// + /// Is only available if `Value` of the map is [`BoundedVec`]. + pub fn try_append( + key1: EncodeLikeKey1, + key2: EncodeLikeKey2, + item: EncodeLikeItem, + ) -> Result<(), ()> + where + EncodeLikeKey1: EncodeLike + Clone, + EncodeLikeKey2: EncodeLike + Clone, + EncodeLikeItem: EncodeLike, + { + < + Self + as + crate::storage::bounded_vec::TryAppendDoubleMap + >::try_append( + key1, key2, item, + ) + } +} + impl StorageDoubleMap where diff --git a/frame/support/src/storage/types/map.rs b/frame/support/src/storage/types/map.rs index 4af28a77cf2b6..187323b4ad1ee 100644 --- a/frame/support/src/storage/types/map.rs +++ b/frame/support/src/storage/types/map.rs @@ -22,9 +22,10 @@ use codec::{FullCodec, Decode, EncodeLike, Encode}; use crate::{ storage::{ StorageAppend, StorageDecodeLength, + bounded_vec::{BoundedVec, BoundedVecValue}, types::{OptionQuery, QueryKindTrait, OnEmptyGetter}, }, - traits::{GetDefault, StorageInstance}, + traits::{GetDefault, StorageInstance, Get}, }; use frame_metadata::{DefaultByteGetter, StorageEntryModifier}; use sp_std::prelude::*; @@ -91,6 +92,34 @@ where } } +impl + StorageMap, QueryKind, OnEmpty> +where + Prefix: StorageInstance, + Hasher: crate::hash::StorageHasher, + Key: FullCodec, + QueryKind: QueryKindTrait, OnEmpty>, + OnEmpty: crate::traits::Get + 'static, + VecValue: BoundedVecValue, + VecBound: Get, +{ + /// Try and append the given item to the map in the storage. + /// + /// Is only available if `Value` of the map is [`BoundedVec`]. + pub fn try_append( + key: EncodeLikeKey, + item: EncodeLikeItem, + ) -> Result<(), ()> + where + EncodeLikeKey: EncodeLike + Clone, + EncodeLikeItem: EncodeLike, + { + >::try_append( + key, item, + ) + } +} + impl StorageMap where diff --git a/frame/support/src/storage/types/value.rs b/frame/support/src/storage/types/value.rs index 39f718956eb64..d536d76d76b8e 100644 --- a/frame/support/src/storage/types/value.rs +++ b/frame/support/src/storage/types/value.rs @@ -21,9 +21,10 @@ use codec::{FullCodec, Decode, EncodeLike, Encode}; use crate::{ storage::{ StorageAppend, StorageDecodeLength, + bounded_vec::{BoundedVec, BoundedVecValue}, types::{OptionQuery, QueryKindTrait, OnEmptyGetter}, }, - traits::{GetDefault, StorageInstance}, + traits::{GetDefault, StorageInstance, Get}, }; use frame_metadata::{DefaultByteGetter, StorageEntryModifier}; @@ -60,6 +61,26 @@ where } } +impl + StorageValue, QueryKind, OnEmpty> +where + Prefix: StorageInstance, + QueryKind: QueryKindTrait, OnEmpty>, + OnEmpty: crate::traits::Get + 'static, + VecValue: BoundedVecValue, + VecBound: Get, +{ + /// Try and append the given item to the value in the storage. + /// + /// Is only available if `Value` of the storage is [`BoundedVec`]. + pub fn try_append(item: EncodeLikeItem) -> Result<(), ()> + where + EncodeLikeItem: EncodeLike, + { + >::try_append(item) + } +} + impl StorageValue where Prefix: StorageInstance,