Skip to content

Commit

Permalink
Add BoundedSortedVec and SortedVec (#532)
Browse files Browse the repository at this point in the history
* Add BoundedSortedVec and SortedVec

* fail on unsorted decodes
  • Loading branch information
KaiserKarel authored Jan 21, 2022
1 parent aa8dfec commit 9c5bd28
Show file tree
Hide file tree
Showing 7 changed files with 845 additions and 1 deletion.
7 changes: 6 additions & 1 deletion frame/composable-support/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ targets = ["x86_64-unknown-linux-gnu"]

[dev-dependencies]
proptest = { version = "1.0" }
serde_json = "1.*"

[dependencies]
frame-support = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13" }
Expand All @@ -19,6 +20,10 @@ sp-arithmetic = { default-features = false, git = "https://github.com/paritytech
sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13" }
sp-std = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13" }
scale-info = { version = "1.0", default-features = false, features = ["derive"] }
sorted-vec = "0.7.0"
serde = { version = "1.*", features = [ "derive" ], optional = true }
is_sorted = "0.1.1"


[dependencies.codec]
default-features = false
Expand All @@ -35,4 +40,4 @@ std = [
"sp-runtime/std",
"sp-std/std",
"scale-info/std",
]
]
3 changes: 3 additions & 0 deletions frame/composable-support/src/collections/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub mod vec;

// pub use vec::BoundedSortedVec;
3 changes: 3 additions & 0 deletions frame/composable-support/src/collections/vec/bounded/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub mod sorted_vec;

pub use self::sorted_vec::BoundedSortedVec;
360 changes: 360 additions & 0 deletions frame/composable-support/src/collections/vec/bounded/sorted_vec.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,360 @@
use crate::collections::vec::sorted::SortedVec;
use codec::{Decode, Encode, EncodeLike, MaxEncodedLen};
use core::{
ops::{Deref, Index},
slice::SliceIndex,
};
use frame_support::{traits::Get, BoundedVec};
use sp_std::{convert::TryFrom, fmt, marker::PhantomData, prelude::*};

/// A bounded, sorted 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 and sorted. All internal
/// operations ensure this bound is respected and order is maintained.
#[derive(Encode)]
pub struct BoundedSortedVec<T: Ord, S>(SortedVec<T>, PhantomData<S>);

impl<T: Decode + Ord, S: Get<u32>> Decode for BoundedSortedVec<T, S> {
#[inline]
fn decode<I: codec::Input>(input: &mut I) -> Result<Self, codec::Error> {
use is_sorted::IsSorted;

let inner = Vec::<T>::decode(input)?;
if inner.len() > S::get() as usize {
return Err("BoundedSortedVec exceeds its limit".into())
}

if !IsSorted::is_sorted(&mut inner.iter()) {
return Err("input is not sorted".into())
}
// checked above
Ok(Self(SortedVec::unchecked_from(inner), PhantomData))
}

#[inline]
fn skip<I: codec::Input>(input: &mut I) -> Result<(), codec::Error> {
Vec::<T>::skip(input)
}
}

// `BoundedSortedVec`s encode to something which will always decode as a `Vec`.
impl<T: Encode + Decode + Ord, S: Get<u32>> EncodeLike<Vec<T>> for BoundedSortedVec<T, S> {}

impl<T: Ord, S> BoundedSortedVec<T, S> {
/// Create `Self` from `t` without any checks.
fn unchecked_from(t: Vec<T>) -> Self {
Self(SortedVec::unchecked_from(t), Default::default())
}

/// 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<Vec<_>>` 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`.
#[inline]
pub fn into_inner(self) -> SortedVec<T> {
self.0
}

/// Exactly the same semantics as [`Vec::remove`].
///
/// # Panics
///
/// Panics if `index` is out of bounds.
#[inline]
pub fn remove(&mut self, index: usize) -> T {
self.0.remove(index)
}

/// Exactly the same semantics as [`Vec::retain`].
#[inline]
pub fn retain<F: FnMut(&T) -> bool>(&mut self, f: F) {
self.0.retain(f)
}
}

impl<T: Ord, S: Get<u32>> From<BoundedSortedVec<T, S>> for Vec<T> {
#[inline]
fn from(x: BoundedSortedVec<T, S>) -> Vec<T> {
x.0.into_inner()
}
}

impl<T: Ord, S: Get<u32>> TryFrom<BoundedSortedVec<T, S>> for BoundedVec<T, S> {
type Error = ();

#[inline]
fn try_from(x: BoundedSortedVec<T, S>) -> Result<Self, Self::Error> {
BoundedVec::try_from(x.0.into_inner())
}
}

impl<T: Ord, S: Get<u32>> BoundedSortedVec<T, S> {
/// Get the bound of the type in `usize`.
#[inline]
pub fn bound() -> usize {
S::get() as usize
}

/// Exactly the same semantics as [`Vec::insert`], but returns an `Err` (and is a noop) if the
/// new length of the vector exceeds `S`.
#[inline]
pub fn try_insert(&mut self, element: T) -> Result<(), T> {
if self.len() < Self::bound() {
self.0.insert(element);
Ok(())
} else {
Err(element)
}
}

/// 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.
#[inline]
pub fn try_push(&mut self, element: T) -> Result<(), T> {
if self.len() < Self::bound() {
self.0.insert(element);
Ok(())
} else {
Err(element)
}
}
}

impl<T: Ord, S> Default for BoundedSortedVec<T, S> {
fn default() -> Self {
// the bound cannot be below 0, which is satisfied by an empty vector
Self::unchecked_from(Vec::default())
}
}

#[cfg(feature = "std")]
impl<T: Ord, S> fmt::Debug for BoundedSortedVec<T, S>
where
T: fmt::Debug,
S: Get<u32>,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("BoundedVec").field(&self.0).field(&Self::bound()).finish()
}
}

impl<T: Ord, S> Clone for BoundedSortedVec<T, S>
where
T: Clone,
{
#[inline]
fn clone(&self) -> Self {
// bound is retained
Self::unchecked_from(self.0.as_inner().to_vec())
}
}

impl<T: Ord, S: Get<u32>> TryFrom<Vec<T>> for BoundedSortedVec<T, S> {
type Error = ();

#[inline]
fn try_from(t: Vec<T>) -> Result<Self, Self::Error> {
if t.len() <= Self::bound() {
// explicit check just above
Ok(Self::unchecked_from(t))
} else {
Err(())
}
}
}

// It is okay to give a non-mutable reference of the inner vec to anyone.
impl<T: Ord, S> AsRef<Vec<T>> for BoundedSortedVec<T, S> {
#[inline]
fn as_ref(&self) -> &Vec<T> {
&self.0
}
}

impl<T: Ord, S> AsRef<[T]> for BoundedSortedVec<T, S> {
#[inline]
fn as_ref(&self) -> &[T] {
&self.0
}
}

// will allow for immutable all operations of `Vec<T>` on `BoundedVec<T>`.
impl<T: Ord, S> Deref for BoundedSortedVec<T, S> {
type Target = Vec<T>;

#[inline]
fn deref(&self) -> &Self::Target {
&self.0
}
}

// Allows for indexing similar to a normal `Vec`. Can panic if out of bound.
impl<T: Ord, S, I> Index<I> for BoundedSortedVec<T, S>
where
I: SliceIndex<[T]>,
{
type Output = I::Output;

#[inline]
fn index(&self, index: I) -> &Self::Output {
self.0.index(index)
}
}

impl<T: Ord, S> sp_std::iter::IntoIterator for BoundedSortedVec<T, S> {
type Item = T;
type IntoIter = sp_std::vec::IntoIter<T>;

#[inline]
fn into_iter(self) -> Self::IntoIter {
self.0.into_inner().into_iter()
}
}

impl<T: Ord, S> codec::DecodeLength for BoundedSortedVec<T, S> {
#[inline]
fn len(self_encoded: &[u8]) -> Result<usize, codec::Error> {
// `BoundedSortedVec<T, _>` stored just a `Vec<T>`, thus the length is at the beginning in
// `Compact` form, and same implementation as `Vec<T>` can be used.
<Vec<T> as codec::DecodeLength>::len(self_encoded)
}
}

/// Allows for comparing vectors with different bounds.
impl<T, S1: Get<u32>, S2: Get<u32>> PartialEq<BoundedSortedVec<T, S2>> for BoundedSortedVec<T, S1>
where
T: PartialEq + Ord,
{
#[inline]
fn eq(&self, rhs: &BoundedSortedVec<T, S2>) -> bool {
self.0 == rhs.0
}
}

impl<T: PartialEq + Ord, S: Get<u32>> PartialEq<Vec<T>> for BoundedSortedVec<T, S> {
#[inline]
fn eq(&self, other: &Vec<T>) -> bool {
self.0.as_inner() == other
}
}

impl<T: PartialEq + Ord, S: Get<u32>> Eq for BoundedSortedVec<T, S> where T: Eq {}

impl<T: Ord, S> MaxEncodedLen for BoundedSortedVec<T, S>
where
T: MaxEncodedLen,
S: Get<u32>,
BoundedSortedVec<T, S>: Encode,
{
#[inline]
fn max_encoded_len() -> usize {
// BoundedSortedVec<T, S> encodes like Vec<T> which encodes like [T], which is a compact u32
// plus each item in the slice:
// https://substrate.dev/rustdocs/v3.0.0/src/parity_scale_codec/codec.rs.html#798-808
codec::Compact(S::get())
.encoded_size()
.saturating_add(Self::bound().saturating_mul(T::max_encoded_len()))
}
}

#[cfg(test)]
pub mod test {
use super::*;
use frame_support::sp_io::TestExternalities;
use sp_std::convert::TryInto;

frame_support::parameter_types! {
pub const Seven: u32 = 7;
pub const Four: u32 = 4;
}

frame_support::generate_storage_alias! { Prefix, Foo => Value<BoundedSortedVec<u32, Seven>> }

#[test]
fn store_works() {
TestExternalities::default().execute_with(|| {
let bounded: BoundedSortedVec<u32, Seven> = vec![1, 2, 3].try_into().unwrap();
Foo::put(bounded.clone());
assert_eq!(Foo::get().unwrap(), bounded);
});
}

#[test]
fn try_append_is_correct() {
assert_eq!(BoundedSortedVec::<u32, Seven>::bound(), 7);
}

#[test]
fn try_insert_works() {
let mut bounded: BoundedSortedVec<u32, Four> = vec![1, 2, 3].try_into().unwrap();
bounded.try_insert(1).unwrap();
assert_eq!(*bounded, vec![1, 1, 2, 3]);

assert!(bounded.try_insert(0).is_err());
assert_eq!(*bounded, vec![1, 1, 2, 3]);
}

#[test]
#[should_panic]
fn try_insert_panics_if_oob() {
let mut bounded: BoundedSortedVec<u32, Four> = vec![1, 2, 3, 4].try_into().unwrap();
bounded.try_insert(9).unwrap();
}

#[test]
fn try_push_works() {
let mut bounded: BoundedSortedVec<u32, Four> = vec![1, 2, 3].try_into().unwrap();
bounded.try_push(0).unwrap();
assert_eq!(*bounded, vec![0, 1, 2, 3]);

assert!(bounded.try_push(9).is_err());
}

#[test]
fn deref_coercion_works() {
let bounded: BoundedSortedVec<u32, Seven> = 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 slice_indexing_works() {
let bounded: BoundedSortedVec<u32, Seven> = vec![1, 2, 3, 4, 5, 6].try_into().unwrap();
assert_eq!(&bounded[0..=2], &[1, 2, 3]);
}

#[test]
fn vec_eq_works() {
let bounded: BoundedSortedVec<u32, Seven> = vec![1, 2, 3, 4, 5, 6].try_into().unwrap();
assert_eq!(bounded, vec![1, 2, 3, 4, 5, 6]);
}

#[test]
fn too_big_vec_fail_to_decode() {
let v: Vec<u32> = vec![1, 2, 3, 4, 5];
assert_eq!(
BoundedSortedVec::<u32, Four>::decode(&mut &v.encode()[..]),
Err("BoundedSortedVec exceeds its limit".into()),
);
}

#[test]
fn unsorted_fail_to_decode() {
let v: Vec<u32> = vec![1, 2, 5, 4];
assert_eq!(
BoundedSortedVec::<u32, Four>::decode(&mut &v.encode()[..]),
Err("input is not sorted".into()),
);
}
}
Loading

0 comments on commit 9c5bd28

Please sign in to comment.