// This file is part of Substrate. // Copyright (C) 2017-2022 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 a bounded BTreeMap. use crate::traits::{Get, TryCollect}; use codec::{Decode, Encode, MaxEncodedLen}; use sp_std::{borrow::Borrow, collections::btree_map::BTreeMap, marker::PhantomData, ops::Deref}; /// A bounded map based on a B-Tree. /// /// B-Trees represent a fundamental compromise between cache-efficiency and actually minimizing /// the amount of work performed in a search. See [`BTreeMap`] for more details. /// /// Unlike a standard `BTreeMap`, there is an enforced upper limit to the number of items in the /// map. All internal operations ensure this bound is respected. #[derive(Encode, scale_info::TypeInfo)] #[scale_info(skip_type_params(S))] pub struct BoundedBTreeMap(BTreeMap, PhantomData); impl Decode for BoundedBTreeMap where K: Decode + Ord, V: Decode, S: Get, { fn decode(input: &mut I) -> Result { let inner = BTreeMap::::decode(input)?; if inner.len() > S::get() as usize { return Err("BoundedBTreeMap exceeds its limit".into()) } Ok(Self(inner, PhantomData)) } fn skip(input: &mut I) -> Result<(), codec::Error> { BTreeMap::::skip(input) } } impl BoundedBTreeMap where S: Get, { /// Get the bound of the type in `usize`. pub fn bound() -> usize { S::get() as usize } } impl BoundedBTreeMap where K: Ord, S: Get, { /// Create `Self` from `t` without any checks. fn unchecked_from(t: BTreeMap) -> Self { Self(t, Default::default()) } /// Exactly the same semantics as `BTreeMap::retain`. /// /// The is a safe `&mut self` borrow because `retain` can only ever decrease the length of the /// inner map. pub fn retain bool>(&mut self, f: F) { self.0.retain(f) } /// Create a new `BoundedBTreeMap`. /// /// Does not allocate. pub fn new() -> Self { BoundedBTreeMap(BTreeMap::new(), PhantomData) } /// Consume self, and return the inner `BTreeMap`. /// /// This is useful when a mutating API of the inner type is desired, and closure-based mutation /// such as provided by [`try_mutate`][Self::try_mutate] is inconvenient. pub fn into_inner(self) -> BTreeMap { 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 BTreeMap)) -> Option { mutate(&mut self.0); (self.0.len() <= Self::bound()).then(move || self) } /// Clears the map, removing all elements. pub fn clear(&mut self) { self.0.clear() } /// Return a mutable reference to the value corresponding to the key. /// /// The key may be any borrowed form of the map's key type, but the ordering on the borrowed /// form _must_ match the ordering on the key type. pub fn get_mut(&mut self, key: &Q) -> Option<&mut V> where K: Borrow, Q: Ord + ?Sized, { self.0.get_mut(key) } /// Exactly the same semantics as [`BTreeMap::insert`], but returns an `Err` (and is a noop) if /// the new length of the map exceeds `S`. /// /// In the `Err` case, returns the inserted pair so it can be further used without cloning. pub fn try_insert(&mut self, key: K, value: V) -> Result, (K, V)> { if self.len() < Self::bound() || self.0.contains_key(&key) { Ok(self.0.insert(key, value)) } else { Err((key, value)) } } /// Remove a key from the map, returning the value at the key if the key was previously in the /// map. /// /// The key may be any borrowed form of the map's key type, but the ordering on the borrowed /// form _must_ match the ordering on the key type. pub fn remove(&mut self, key: &Q) -> Option where K: Borrow, Q: Ord + ?Sized, { self.0.remove(key) } /// Remove a key from the map, returning the value at the key if the key was previously in the /// map. /// /// The key may be any borrowed form of the map's key type, but the ordering on the borrowed /// form _must_ match the ordering on the key type. pub fn remove_entry(&mut self, key: &Q) -> Option<(K, V)> where K: Borrow, Q: Ord + ?Sized, { self.0.remove_entry(key) } } impl Default for BoundedBTreeMap where K: Ord, S: Get, { fn default() -> Self { Self::new() } } impl Clone for BoundedBTreeMap where BTreeMap: Clone, { fn clone(&self) -> Self { BoundedBTreeMap(self.0.clone(), PhantomData) } } impl sp_std::fmt::Debug for BoundedBTreeMap where BTreeMap: sp_std::fmt::Debug, S: Get, { fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result { f.debug_tuple("BoundedBTreeMap").field(&self.0).field(&Self::bound()).finish() } } impl PartialEq> for BoundedBTreeMap where BTreeMap: PartialEq, S1: Get, S2: Get, { fn eq(&self, other: &BoundedBTreeMap) -> bool { S1::get() == S2::get() && self.0 == other.0 } } impl Eq for BoundedBTreeMap where BTreeMap: Eq, S: Get, { } impl PartialEq> for BoundedBTreeMap where BTreeMap: PartialEq, { fn eq(&self, other: &BTreeMap) -> bool { self.0 == *other } } impl PartialOrd for BoundedBTreeMap where BTreeMap: PartialOrd, S: Get, { fn partial_cmp(&self, other: &Self) -> Option { self.0.partial_cmp(&other.0) } } impl Ord for BoundedBTreeMap where BTreeMap: Ord, S: Get, { fn cmp(&self, other: &Self) -> sp_std::cmp::Ordering { self.0.cmp(&other.0) } } impl IntoIterator for BoundedBTreeMap { type Item = (K, V); type IntoIter = sp_std::collections::btree_map::IntoIter; fn into_iter(self) -> Self::IntoIter { self.0.into_iter() } } impl<'a, K, V, S> IntoIterator for &'a BoundedBTreeMap { type Item = (&'a K, &'a V); type IntoIter = sp_std::collections::btree_map::Iter<'a, K, V>; fn into_iter(self) -> Self::IntoIter { self.0.iter() } } impl<'a, K, V, S> IntoIterator for &'a mut BoundedBTreeMap { type Item = (&'a K, &'a mut V); type IntoIter = sp_std::collections::btree_map::IterMut<'a, K, V>; fn into_iter(self) -> Self::IntoIter { self.0.iter_mut() } } impl MaxEncodedLen for BoundedBTreeMap where K: MaxEncodedLen, V: MaxEncodedLen, S: Get, { fn max_encoded_len() -> usize { Self::bound() .saturating_mul(K::max_encoded_len().saturating_add(V::max_encoded_len())) .saturating_add(codec::Compact(S::get()).encoded_size()) } } impl Deref for BoundedBTreeMap where K: Ord, { type Target = BTreeMap; fn deref(&self) -> &Self::Target { &self.0 } } impl AsRef> for BoundedBTreeMap where K: Ord, { fn as_ref(&self) -> &BTreeMap { &self.0 } } impl From> for BTreeMap where K: Ord, { fn from(map: BoundedBTreeMap) -> Self { map.0 } } impl TryFrom> for BoundedBTreeMap where K: Ord, S: Get, { type Error = (); fn try_from(value: BTreeMap) -> Result { (value.len() <= Self::bound()) .then(move || BoundedBTreeMap(value, PhantomData)) .ok_or(()) } } impl codec::DecodeLength for BoundedBTreeMap { fn len(self_encoded: &[u8]) -> Result { // `BoundedBTreeMap` is stored just a `BTreeMap`, which is stored as a // `Compact` with its length followed by an iteration of its items. We can just use // the underlying implementation. as codec::DecodeLength>::len(self_encoded) } } impl codec::EncodeLike> for BoundedBTreeMap where BTreeMap: Encode { } impl TryCollect> for I where K: Ord, I: ExactSizeIterator + Iterator, Bound: Get, { type Error = &'static str; fn try_collect(self) -> Result, Self::Error> { if self.len() > Bound::get() as usize { Err("iterator length too big") } else { Ok(BoundedBTreeMap::::unchecked_from(self.collect::>())) } } } #[cfg(test)] pub mod test { use super::*; use crate::traits::ConstU32; fn map_from_keys(keys: &[K]) -> BTreeMap where K: Ord + Copy, { keys.iter().copied().zip(std::iter::repeat(())).collect() } fn boundedmap_from_keys(keys: &[K]) -> BoundedBTreeMap where K: Ord + Copy, S: Get, { map_from_keys(keys).try_into().unwrap() } #[test] fn try_insert_works() { let mut bounded = boundedmap_from_keys::>(&[1, 2, 3]); bounded.try_insert(0, ()).unwrap(); assert_eq!(*bounded, map_from_keys(&[1, 0, 2, 3])); assert!(bounded.try_insert(9, ()).is_err()); assert_eq!(*bounded, map_from_keys(&[1, 0, 2, 3])); } #[test] fn deref_coercion_works() { let bounded = boundedmap_from_keys::>(&[1, 2, 3]); // 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 = boundedmap_from_keys::>(&[1, 2, 3, 4, 5, 6]); let bounded = bounded .try_mutate(|v| { v.insert(7, ()); }) .unwrap(); assert_eq!(bounded.len(), 7); assert!(bounded .try_mutate(|v| { v.insert(8, ()); }) .is_none()); } #[test] fn btree_map_eq_works() { let bounded = boundedmap_from_keys::>(&[1, 2, 3, 4, 5, 6]); assert_eq!(bounded, map_from_keys(&[1, 2, 3, 4, 5, 6])); } #[test] fn too_big_fail_to_decode() { let v: Vec<(u32, u32)> = vec![(1, 1), (2, 2), (3, 3), (4, 4), (5, 5)]; assert_eq!( BoundedBTreeMap::>::decode(&mut &v.encode()[..]), Err("BoundedBTreeMap exceeds its limit".into()), ); } #[test] fn unequal_eq_impl_insert_works() { // given a struct with a strange notion of equality #[derive(Debug)] struct Unequal(u32, bool); impl PartialEq for Unequal { fn eq(&self, other: &Self) -> bool { self.0 == other.0 } } impl Eq for Unequal {} impl Ord for Unequal { fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.0.cmp(&other.0) } } impl PartialOrd for Unequal { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } let mut map = BoundedBTreeMap::>::new(); // when the set is full for i in 0..4 { map.try_insert(Unequal(i, false), i).unwrap(); } // can't insert a new distinct member map.try_insert(Unequal(5, false), 5).unwrap_err(); // but _can_ insert a distinct member which compares equal, though per the documentation, // neither the set length nor the actual member are changed, but the value is map.try_insert(Unequal(0, true), 6).unwrap(); assert_eq!(map.len(), 4); let (zero_key, zero_value) = map.get_key_value(&Unequal(0, true)).unwrap(); assert_eq!(zero_key.0, 0); assert_eq!(zero_key.1, false); assert_eq!(*zero_value, 6); } #[test] fn eq_works() { // of same type let b1 = boundedmap_from_keys::>(&[1, 2]); let b2 = boundedmap_from_keys::>(&[1, 2]); assert_eq!(b1, b2); // of different type, but same value and bound. crate::parameter_types! { B1: u32 = 7; B2: u32 = 7; } let b1 = boundedmap_from_keys::(&[1, 2]); let b2 = boundedmap_from_keys::(&[1, 2]); assert_eq!(b1, b2); } #[test] fn can_be_collected() { let b1 = boundedmap_from_keys::>(&[1, 2, 3, 4]); let b2: BoundedBTreeMap> = b1.iter().map(|(k, v)| (k + 1, *v)).try_collect().unwrap(); assert_eq!(b2.into_iter().map(|(k, _)| k).collect::>(), vec![2, 3, 4, 5]); // can also be collected into a collection of length 4. let b2: BoundedBTreeMap> = b1.iter().map(|(k, v)| (k + 1, *v)).try_collect().unwrap(); assert_eq!(b2.into_iter().map(|(k, _)| k).collect::>(), vec![2, 3, 4, 5]); // can be mutated further into iterators that are `ExactSizedIterator`. let b2: BoundedBTreeMap> = b1.iter().map(|(k, v)| (k + 1, *v)).rev().skip(2).try_collect().unwrap(); // note that the binary tree will re-sort this, so rev() is not really seen assert_eq!(b2.into_iter().map(|(k, _)| k).collect::>(), vec![2, 3]); let b2: BoundedBTreeMap> = b1.iter().map(|(k, v)| (k + 1, *v)).take(2).try_collect().unwrap(); assert_eq!(b2.into_iter().map(|(k, _)| k).collect::>(), vec![2, 3]); // but these worn't work let b2: Result>, _> = b1.iter().map(|(k, v)| (k + 1, *v)).try_collect(); assert!(b2.is_err()); let b2: Result>, _> = b1.iter().map(|(k, v)| (k + 1, *v)).skip(2).try_collect(); assert!(b2.is_err()); } }