From 52f35c815313a022c3f8d1b4a59b24708dc9fb44 Mon Sep 17 00:00:00 2001 From: Shawn Tabrizi Date: Mon, 17 Jan 2022 05:28:11 -0400 Subject: [PATCH] Improve BoundedVec API (extracted from #10195) (#10656) * Gav wrote this code in pull #10195. Extracting to simplify that PR. * fix potential panics * prevent panics in slide * update doc * fmt * Update frame/support/src/storage/bounded_vec.rs Co-authored-by: Keith Yeung Co-authored-by: Gav Wood Co-authored-by: Keith Yeung --- frame/support/src/storage/bounded_vec.rs | 218 ++++++++++++++++++++++- 1 file changed, 216 insertions(+), 2 deletions(-) diff --git a/frame/support/src/storage/bounded_vec.rs b/frame/support/src/storage/bounded_vec.rs index 9f43d37a2d7f1..6d7206df6db10 100644 --- a/frame/support/src/storage/bounded_vec.rs +++ b/frame/support/src/storage/bounded_vec.rs @@ -149,6 +149,16 @@ impl BoundedVec { ) -> Option<&mut >::Output> { self.0.get_mut(index) } + + /// Exactly the same semantics as [`Vec::truncate`]. + pub fn truncate(&mut self, s: usize) { + self.0.truncate(s); + } + + /// Exactly the same semantics as [`Vec::pop`]. + pub fn pop(&mut self) -> Option { + self.0.pop() + } } impl> From> for Vec { @@ -176,6 +186,115 @@ impl> BoundedVec { S::get() as usize } + /// Forces the insertion of `s` into `self` retaining all items with index at least `index`. + /// + /// If `index == 0` and `self.len() == Self::bound()`, then this is a no-op. + /// + /// If `Self::bound() < index` or `self.len() < index`, then this is also a no-op. + /// + /// Returns `true` if the item was inserted. + pub fn force_insert_keep_right(&mut self, index: usize, element: T) -> bool { + // Check against panics. + if Self::bound() < index || self.len() < index { + return false + } + if self.len() < Self::bound() { + // Cannot panic since self.len() >= index; + self.0.insert(index, element); + } else { + if index == 0 { + return false + } + self[0] = element; + // `[0..index] cannot panic since self.len() >= index. + // `rotate_left(1)` cannot panic because there is at least 1 element. + self[0..index].rotate_left(1); + } + true + } + + /// Forces the insertion of `s` into `self` retaining all items with index at most `index`. + /// + /// If `index == Self::bound()` and `self.len() == Self::bound()`, then this is a no-op. + /// + /// If `Self::bound() < index` or `self.len() < index`, then this is also a no-op. + /// + /// Returns `true` if the item was inserted. + pub fn force_insert_keep_left(&mut self, index: usize, element: T) -> bool { + // Check against panics. + if Self::bound() < index || self.len() < index { + return false + } + // Noop condition. + if Self::bound() == index && self.len() <= Self::bound() { + return false + } + // Cannot panic since self.len() >= index; + self.0.insert(index, element); + self.0.truncate(Self::bound()); + true + } + + /// Move the position of an item from one location to another in the slice. + /// + /// Except for the item being moved, the order of the slice remains the same. + /// + /// - `index` is the location of the item to be moved. + /// - `insert_position` is the index of the item in the slice which should *immediately follow* + /// the item which is being moved. + /// + /// Returns `true` of the operation was successful, otherwise `false` if a noop. + pub fn slide(&mut self, index: usize, insert_position: usize) -> bool { + // Check against panics. + if self.len() <= index || self.len() < insert_position || index == usize::MAX { + return false + } + // Noop conditions. + if index == insert_position || index + 1 == insert_position { + return false + } + if insert_position < index && index < self.len() { + // --- --- --- === === === === @@@ --- --- --- + // ^-- N ^O^ + // ... + // /-----<<<-----\ + // --- --- --- === === === === @@@ --- --- --- + // >>> >>> >>> >>> + // ... + // --- --- --- @@@ === === === === --- --- --- + // ^N^ + self[insert_position..index + 1].rotate_right(1); + return true + } else if insert_position > 0 && index + 1 < insert_position { + // Note that the apparent asymmetry of these two branches is due to the + // fact that the "new" position is the position to be inserted *before*. + // --- --- --- @@@ === === === === --- --- --- + // ^O^ ^-- N + // ... + // /----->>>-----\ + // --- --- --- @@@ === === === === --- --- --- + // <<< <<< <<< <<< + // ... + // --- --- --- === === === === @@@ --- --- --- + // ^N^ + self[index..insert_position].rotate_left(1); + return true + } + + debug_assert!(false, "all noop conditions should have been covered above"); + false + } + + /// Forces the insertion of `s` into `self` truncating first if necessary. + /// + /// Infallible, but if the bound is zero, then it's a no-op. + pub fn force_push(&mut self, element: T) { + if Self::bound() > 0 { + self.0.truncate(Self::bound() as usize - 1); + self.0.push(element); + } + } + /// Same as `Vec::resize`, but if `size` is more than [`Self::bound`], then [`Self::bound`] is /// used. pub fn bounded_resize(&mut self, size: usize, value: T) @@ -397,8 +516,7 @@ where #[cfg(test)] pub mod test { use super::*; - use crate::Twox128; - use frame_support::traits::ConstU32; + use crate::{traits::ConstU32, Twox128}; use sp_io::TestExternalities; crate::generate_storage_alias! { Prefix, Foo => Value>> } @@ -408,6 +526,102 @@ pub mod test { FooDoubleMap => DoubleMap<(u32, Twox128), (u32, Twox128), BoundedVec>> } + #[test] + fn slide_works() { + let mut b: BoundedVec> = vec![0, 1, 2, 3, 4, 5].try_into().unwrap(); + assert!(b.slide(1, 5)); + assert_eq!(*b, vec![0, 2, 3, 4, 1, 5]); + assert!(b.slide(4, 0)); + assert_eq!(*b, vec![1, 0, 2, 3, 4, 5]); + assert!(b.slide(0, 2)); + assert_eq!(*b, vec![0, 1, 2, 3, 4, 5]); + assert!(b.slide(1, 6)); + assert_eq!(*b, vec![0, 2, 3, 4, 5, 1]); + assert!(b.slide(0, 6)); + assert_eq!(*b, vec![2, 3, 4, 5, 1, 0]); + assert!(b.slide(5, 0)); + assert_eq!(*b, vec![0, 2, 3, 4, 5, 1]); + assert!(!b.slide(6, 0)); + assert!(!b.slide(7, 0)); + assert_eq!(*b, vec![0, 2, 3, 4, 5, 1]); + + let mut c: BoundedVec> = vec![0, 1, 2].try_into().unwrap(); + assert!(!c.slide(1, 5)); + assert_eq!(*c, vec![0, 1, 2]); + assert!(!c.slide(4, 0)); + assert_eq!(*c, vec![0, 1, 2]); + assert!(!c.slide(3, 0)); + assert_eq!(*c, vec![0, 1, 2]); + assert!(c.slide(2, 0)); + assert_eq!(*c, vec![2, 0, 1]); + } + + #[test] + fn slide_noops_work() { + let mut b: BoundedVec> = vec![0, 1, 2, 3, 4, 5].try_into().unwrap(); + assert!(!b.slide(3, 3)); + assert_eq!(*b, vec![0, 1, 2, 3, 4, 5]); + assert!(!b.slide(3, 4)); + assert_eq!(*b, vec![0, 1, 2, 3, 4, 5]); + } + + #[test] + fn force_insert_keep_left_works() { + let mut b: BoundedVec> = vec![].try_into().unwrap(); + assert!(!b.force_insert_keep_left(1, 10)); + assert!(b.is_empty()); + + assert!(b.force_insert_keep_left(0, 30)); + assert!(b.force_insert_keep_left(0, 10)); + assert!(b.force_insert_keep_left(1, 20)); + assert!(b.force_insert_keep_left(3, 40)); + assert_eq!(*b, vec![10, 20, 30, 40]); + // at capacity. + assert!(!b.force_insert_keep_left(4, 41)); + assert_eq!(*b, vec![10, 20, 30, 40]); + assert!(b.force_insert_keep_left(3, 31)); + assert_eq!(*b, vec![10, 20, 30, 31]); + assert!(b.force_insert_keep_left(1, 11)); + assert_eq!(*b, vec![10, 11, 20, 30]); + assert!(b.force_insert_keep_left(0, 1)); + assert_eq!(*b, vec![1, 10, 11, 20]); + + let mut z: BoundedVec> = vec![].try_into().unwrap(); + assert!(z.is_empty()); + assert!(!z.force_insert_keep_left(0, 10)); + assert!(z.is_empty()); + } + + #[test] + fn force_insert_keep_right_works() { + let mut b: BoundedVec> = vec![].try_into().unwrap(); + assert!(!b.force_insert_keep_right(1, 10)); + assert!(b.is_empty()); + + assert!(b.force_insert_keep_right(0, 30)); + assert!(b.force_insert_keep_right(0, 10)); + assert!(b.force_insert_keep_right(1, 20)); + assert!(b.force_insert_keep_right(3, 40)); + assert_eq!(*b, vec![10, 20, 30, 40]); + // at capacity. + assert!(!b.force_insert_keep_right(0, 0)); + assert_eq!(*b, vec![10, 20, 30, 40]); + assert!(b.force_insert_keep_right(1, 11)); + assert_eq!(*b, vec![11, 20, 30, 40]); + assert!(b.force_insert_keep_right(3, 31)); + assert_eq!(*b, vec![20, 30, 31, 40]); + assert!(b.force_insert_keep_right(4, 41)); + assert_eq!(*b, vec![30, 31, 40, 41]); + + assert!(!b.force_insert_keep_right(5, 69)); + assert_eq!(*b, vec![30, 31, 40, 41]); + + let mut z: BoundedVec> = vec![].try_into().unwrap(); + assert!(z.is_empty()); + assert!(!z.force_insert_keep_right(0, 10)); + assert!(z.is_empty()); + } + #[test] fn try_append_is_correct() { assert_eq!(BoundedVec::>::bound(), 7);