diff --git a/crates/ink/src/lib.rs b/crates/ink/src/lib.rs index 4c00a39d9aa..6cd7d2c500a 100644 --- a/crates/ink/src/lib.rs +++ b/crates/ink/src/lib.rs @@ -57,6 +57,7 @@ pub mod storage { pub use ink_storage::{ Lazy, Mapping, + StorageVec, }; } diff --git a/crates/storage/src/collections/mod.rs b/crates/storage/src/collections/mod.rs new file mode 100644 index 00000000000..b1cff3c7723 --- /dev/null +++ b/crates/storage/src/collections/mod.rs @@ -0,0 +1,3 @@ +mod storage_vec; + +pub use self::storage_vec::Vec; diff --git a/crates/storage/src/collections/storage_vec.rs b/crates/storage/src/collections/storage_vec.rs new file mode 100644 index 00000000000..be4b556c004 --- /dev/null +++ b/crates/storage/src/collections/storage_vec.rs @@ -0,0 +1,1584 @@ +// Copyright 2018-2022 Parity Technologies (UK) Ltd. +// +// 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. + +//! A storage vector used to store elements in a contiguous sequenced order. +//! +//! This is by default the go-to collection for most smart contracts if there +//! are no special requirements to the storage data structure. + +// mod impls; +// mod iter; +// mod storage; +// +//#[cfg(test)] +// mod tests; +// +//#[cfg(all(test, feature = "ink-fuzz-tests"))] +// mod fuzz_tests; + +use core::iter::{ + Extend, + FromIterator, +}; +use ink_primitives::Key; +use ink_storage_traits::{ + AutoKey, + Packed, + Storable, + StorableHint, + StorageKey, +}; + +// pub use self::iter::{Iter, IterMut}; +use crate::{ + extend_lifetime, + lazy::LazyIndexMap, +}; + +/// A contiguous growable array type, written `Vec` but pronounced "vector". +/// +/// # Note +/// +/// Despite the similarity to Rust's `Vec` type this storage `Vec` has many +/// differences in its internal data layout. While it stores its data in contiguous +/// storage slots this does not mean that the data is actually densely stored +/// in memory. +/// +/// Also its technical performance characteristics may be different from Rust's +/// `Vec` due to the differences stated above. +/// +/// Allows to store up to `2^32` elements and is guaranteed to not reallocate +/// upon pushing new elements to it. +#[derive(Debug)] +pub struct Vec +where + T: Packed, +{ + /// The length of the vector. + len: Option, + /// The synchronized cells to operate on the contract storage. + elems: LazyIndexMap, +} + +#[cfg(feature = "std")] +impl scale_info::TypeInfo for Vec +where + T: Packed + scale_info::TypeInfo + 'static, + KeyType: StorageKey, +{ + type Identity = ink_prelude::vec::Vec; + + fn type_info() -> scale_info::Type { + ::type_info() + } +} + +impl Storable for Vec +where + V: Packed, + KeyType: StorageKey, +{ + #[inline] + fn encode(&self, _dest: &mut T) {} + + #[inline] + fn decode(_input: &mut I) -> Result { + Ok(Default::default()) + } + + #[inline] + fn encoded_size(&self) -> usize { + 0 + } +} + +impl StorableHint for Vec +where + V: Packed, + Key: StorageKey, + InnerKey: StorageKey, +{ + type Type = Vec; + type PreferredKey = InnerKey; +} + +impl StorageKey for Vec +where + V: Packed, + KeyType: StorageKey, +{ + const KEY: Key = KeyType::KEY; +} + +#[cfg(feature = "std")] +const _: () = { + use crate::traits::StorageLayout; + use ink_metadata::layout::{ + Layout, + LayoutKey, + RootLayout, + }; + + impl StorageLayout for Vec + where + V: Packed + StorageLayout + scale_info::TypeInfo + 'static, + KeyType: StorageKey + scale_info::TypeInfo + 'static, + { + fn layout(_: &Key) -> Layout { + Layout::Root(RootLayout::new( + LayoutKey::from(&KeyType::KEY), + ::layout(&KeyType::KEY), + scale_info::meta_type::(), + )) + } + } +}; + +/// The index is out of the bounds of this vector. +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct IndexOutOfBounds; + +impl Default for Vec +where + KeyType: StorageKey, + T: Packed, +{ + fn default() -> Self { + Self::new() + } +} + +impl Vec +where + KeyType: StorageKey, + T: Packed, +{ + /// Creates a new empty storage vector. + pub fn new() -> Self { + Self { + len: None, + elems: LazyIndexMap::new(), + } + } + + /// Returns the number of elements in the vector, also referred to as its length. + pub fn len(&self) -> u32 { + self.len.unwrap_or_else(|| { + ink_env::get_contract_storage(&KeyType::KEY) + .expect("u32 must always fit into the buffer") + .unwrap_or(u32::MIN) + }) + } + + /// Returns `true` if the vector contains no elements. + pub fn is_empty(&self) -> bool { + self.len() == 0 + } +} + +impl Vec +where + KeyType: StorageKey, + T: Packed, +{ + /// Clears the underlying storage cells of the storage vector. + /// + /// # Note + /// + /// This completely invalidates the storage vector's invariants about + /// the contents of its associated storage region. + /// + /// This API is used for the `Drop` implementation of [`Vec`] as well as + /// for the [`SpreadLayout::clear_spread`][`crate::traits::SpreadLayout::clear_spread`] + /// trait implementation. + fn clear_cells(&self) { + let len = self.len(); + let _ = ink_env::clear_contract_storage(&KeyType::KEY); + + for index in 0..len { + self.elems.clear_packed_at(index); + } + } +} + +impl Vec +where + KeyType: StorageKey, + T: Packed, +{ + /// Returns an iterator yielding shared references to all elements of the vector. + /// + /// # Note + /// + /// Avoid unbounded iteration over big storage vectors. + /// Prefer using methods like `Iterator::take` in order to limit the number + /// of yielded elements. + pub fn iter(&self) -> Iter { + Iter::new(self) + } + + /// Returns an iterator yielding exclusive references to all elements of the vector. + /// + /// # Note + /// + /// Avoid unbounded iteration over big storage vectors. + /// Prefer using methods like `Iterator::take` in order to limit the number + /// of yielded elements. + pub fn iter_mut(&mut self) -> IterMut { + IterMut::new(self) + } + + /// Returns the index if it is within bounds or `None` otherwise. + fn within_bounds(&self, index: u32) -> Option { + if index < self.len() { + return Some(index); + } + None + } + + /// Returns a shared reference to the first element if any. + pub fn first(&self) -> Option<&T> { + if self.is_empty() { + return None; + } + self.get(0) + } + + /// Returns a shared reference to the last element if any. + pub fn last(&self) -> Option<&T> { + if self.is_empty() { + return None; + } + let last_index = self.len() - 1; + self.get(last_index) + } + + /// Returns a shared reference to the indexed element. + /// + /// Returns `None` if `index` is out of bounds. + pub fn get(&self, index: u32) -> Option<&T> { + self.within_bounds(index) + .and_then(|index| self.elems.get(index)) + } +} + +impl Vec +where + KeyType: StorageKey, + T: Packed, +{ + /// Appends an element to the back of the vector. + pub fn push(&mut self, value: T) { + let last_index = self.len(); + assert!( + last_index < core::u32::MAX, + "cannot push more elements into the storage vector" + ); + self.len = Some(last_index.checked_add(1).unwrap()); + self.elems.put(last_index, Some(value)); + } + + /// Binary searches this sorted vector for a given element. + /// + /// If the value is found then [`Result::Ok`] is returned, containing the + /// index of the matching element. If there are multiple matches, then any + /// one of the matches could be returned. If the value is not found then + /// [`Result::Err`] is returned, containing the index where a matching + /// element could be inserted while maintaining sorted order. + /// + /// See also [`binary_search_by`], [`binary_search_by_key`]. + /// + /// [`binary_search_by`]: Vec::binary_search_by + /// [`binary_search_by_key`]: Vec::binary_search_by_key + /// + /// # Examples + /// + /// Looks up a series of four elements. The first is found, with a + /// uniquely determined position; the second and third are not + /// found; the fourth could match any position in `[1, 4]`. + /// + /// ```ignore + /// # // Tracking issue [#1119]: We currently ignore this test since we stopped exposing + /// # // `StorageVec` publicly. + /// use ink_storage::Vec as StorageVec; + /// + /// let s: StorageVec = [0, 1, 1, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55] + /// .into_iter() + /// .collect(); + /// + /// assert_eq!(s.binary_search(&13), Ok(9)); + /// assert_eq!(s.binary_search(&4), Err(7)); + /// assert_eq!(s.binary_search(&100), Err(13)); + /// let r = s.binary_search(&1); + /// assert!(match r { Ok(1..=4) => true, _ => false, }); + /// ``` + #[inline] + pub fn binary_search(&self, x: &T) -> Result + where + T: Ord, + { + self.binary_search_by(|p| p.cmp(x)) + } + + /// Binary searches this sorted vector with a comparator function. + /// + /// The comparator function should implement an order consistent + /// with the sort order of the underlying vector, returning an + /// order code that indicates whether its argument is `Less`, + /// `Equal` or `Greater` the desired target. + /// + /// If the value is found then [`Result::Ok`] is returned, containing the + /// index of the matching element. If there are multiple matches, then any + /// one of the matches could be returned. If the value is not found then + /// [`Result::Err`] is returned, containing the index where a matching + /// element could be inserted while maintaining sorted order. + /// + /// See also [`binary_search`], [`binary_search_by_key`]. + /// + /// [`binary_search`]: Vec::binary_search + /// [`binary_search_by_key`]: Vec::binary_search_by_key + /// + /// # Examples + /// + /// Looks up a series of four elements. The first is found, with a + /// uniquely determined position; the second and third are not + /// found; the fourth could match any position in `[1, 4]`. + /// + /// ```ignore + /// # // Tracking issue [#1119]: We currently ignore this test since we stopped exposing + /// # // `StorageVec` publicly. + /// use ink_storage::Vec as StorageVec; + /// + /// let s: StorageVec = [0, 1, 1, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55] + /// .into_iter() + /// .collect(); + /// + /// let seek = 13; + /// assert_eq!(s.binary_search_by(|probe| probe.cmp(&seek)), Ok(9)); + /// let seek = 4; + /// assert_eq!(s.binary_search_by(|probe| probe.cmp(&seek)), Err(7)); + /// let seek = 100; + /// assert_eq!(s.binary_search_by(|probe| probe.cmp(&seek)), Err(13)); + /// let seek = 1; + /// let r = s.binary_search_by(|probe| probe.cmp(&seek)); + /// assert!(match r { Ok(1..=4) => true, _ => false, }); + /// ``` + // The binary_search implementation is ported from + // https://github.com/rust-lang/rust/blob/c5e344f7747dbd7e7d4b209e3c480deb5979a56f/library/core/src/slice/mod.rs#L2191 + // and attempts to remain as close to the source as possible. + #[inline] + pub fn binary_search_by<'a, F>(&'a self, mut f: F) -> Result + where + F: FnMut(&'a T) -> core::cmp::Ordering, + { + use core::cmp::Ordering::*; + + let mut size = self.len(); + let mut left = 0; + let mut right = size; + while left < right { + let mid = left + size / 2; + + // the call is made safe by the following invariants: + // - `mid >= 0` + // - `mid < size`: `mid` is limited by `[left; right)` bound. + let cmp = f(&self[mid]); + + // The reason why we use if/else control flow rather than match + // is because match reorders comparison operations, which is perf sensitive. + if cmp == Less { + left = mid + 1; + } else if cmp == Greater { + right = mid; + } else { + return Ok(mid); + } + + size = right - left; + } + Err(left) + } + + /// Binary searches this sorted vector with a key extraction function. + /// + /// If the value is found then [`Result::Ok`] is returned, containing the + /// index of the matching element. If there are multiple matches, then any + /// one of the matches could be returned. If the value is not found then + /// [`Result::Err`] is returned, containing the index where a matching + /// element could be inserted while maintaining sorted order. + /// + /// See also [`binary_search`], [`binary_search_by`]. + /// + /// [`binary_search`]: Vec::binary_search + /// [`binary_search_by`]: Vec::binary_search_by + /// + /// # Examples + /// + /// Looks up a series of four elements in a vector of pairs sorted by + /// their second elements. The first is found, with a uniquely + /// determined position; the second and third are not found; the + /// fourth could match any position in `[1, 4]`. + /// + /// ```ignore + /// # // Tracking issue [#1119]: We currently ignore this test since we stopped exposing + /// # // `StorageVec` publicly. + /// use ink_storage::Vec as StorageVec; + /// + /// let s: StorageVec<(i32, i32)> = [ + /// (0, 0), + /// (2, 1), + /// (4, 1), + /// (5, 1), + /// (3, 1), + /// (1, 2), + /// (2, 3), + /// (4, 5), + /// (5, 8), + /// (3, 13), + /// (1, 21), + /// (2, 34), + /// (4, 55), + /// ] + /// .into_iter() + /// .collect(); + /// + /// assert_eq!(s.binary_search_by_key(&13, |&(a, b)| b), Ok(9)); + /// assert_eq!(s.binary_search_by_key(&4, |&(a, b)| b), Err(7)); + /// assert_eq!(s.binary_search_by_key(&100, |&(a, b)| b), Err(13)); + /// let r = s.binary_search_by_key(&1, |&(a, b)| b); + /// assert!(match r { Ok(1..=4) => true, _ => false, }); + /// ``` + #[inline] + pub fn binary_search_by_key<'a, B, F>(&'a self, b: &B, mut f: F) -> Result + where + F: FnMut(&'a T) -> B, + B: Ord, + { + self.binary_search_by(|k| f(k).cmp(b)) + } +} + +impl Vec +where + KeyType: StorageKey, + T: Packed, +{ + /// Pops the last element from the vector and returns it. + // + /// Returns `None` if the vector is empty. + pub fn pop(&mut self) -> Option { + if self.is_empty() { + return None; + } + let last_index = self.len() - 1; + self.len = Some(last_index); + self.elems.put_get(last_index, None) + } + + /// Pops the last element from the vector and immediately drops it. + /// + /// Returns `Some(())` if an element has been removed and `None` otherwise. + /// + /// # Note + /// + /// This operation is a bit more efficient than [`Vec::pop`] + /// since it avoids reading from contract storage in some use cases. + pub fn pop_drop(&mut self) -> Option<()> { + if self.is_empty() { + return None; + } + let last_index = self.len() - 1; + self.len = Some(last_index); + self.elems.put(last_index, None); + Some(()) + } + + /// Returns an exclusive reference to the first element if any. + pub fn first_mut(&mut self) -> Option<&mut T> { + if self.is_empty() { + return None; + } + self.get_mut(0) + } + + /// Returns an exclusive reference to the last element if any. + pub fn last_mut(&mut self) -> Option<&mut T> { + if self.is_empty() { + return None; + } + let last_index = self.len() - 1; + self.get_mut(last_index) + } + + /// Returns an exclusive reference to the indexed element. + /// + /// Returns `None` if `index` is out of bounds. + pub fn get_mut(&mut self, index: u32) -> Option<&mut T> { + self.within_bounds(index) + .and_then(move |index| self.elems.get_mut(index)) + } + + /// Swaps the elements at the given indices. + /// + /// # Panics + /// + /// If one or both indices are out of bounds. + pub fn swap(&mut self, a: u32, b: u32) { + assert!( + a < self.len() && b < self.len(), + "indices are out of bounds" + ); + self.elems.swap(a, b) + } + + /// Removes the indexed element from the vector and returns it. + /// + /// The last element of the vector is put into the indexed slot. + /// Returns `None` and does not mutate the vector if the index is out of bounds. + /// + /// # Note + /// + /// This operation does not preserve ordering but is constant time. + pub fn swap_remove(&mut self, n: u32) -> Option { + if self.is_empty() { + return None; + } + self.elems.swap(n, self.len() - 1); + self.pop() + } + + /// Removes the indexed element from the vector. + /// + /// The last element of the vector is put into the indexed slot. + /// Returns `Some(())` if an element has been removed and `None` otherwise. + /// + /// # Note + /// + /// This operation should be preferred over [`Vec::swap_remove`] if there is + /// no need to return the removed element since it avoids a contract storage + /// read for some use cases. + pub fn swap_remove_drop(&mut self, n: u32) -> Option<()> { + if self.is_empty() { + return None; + } + self.elems.put(n, None); + let last_index = self.len() - 1; + let last = self.elems.put_get(last_index, None); + self.elems.put(n, last); + self.len = Some(last_index); + Some(()) + } + + /// Sets the elements at the given index to the new value. + /// + /// Won't return the old element back to the caller. + /// Prefer this operation over other method of overriding an element + /// in the storage vector since this is more efficient. + #[inline] + pub fn set(&mut self, index: u32, new_value: T) -> Result<(), IndexOutOfBounds> { + if self.within_bounds(index).is_none() { + return Err(IndexOutOfBounds); + } + self.elems.put(index, Some(new_value)); + Ok(()) + } + + /// Removes all elements from this vector. + /// + /// # Note + /// + /// Use this method to clear the vector instead of e.g. iterative `pop()`. + /// This method performs significantly better and does not actually read + /// any of the elements (whereas `pop()` does). + pub fn clear(&mut self) { + if self.is_empty() { + return; + } + for index in 0..self.len() { + self.elems.put(index, None); + } + self.len = Some(0); + } + + /// Write the cached in-memory data back to storage. + /// + /// This does only write elements that were modified. + pub fn write(&mut self) { + ink_env::set_contract_storage(&KeyType::KEY, &self.len()); + self.elems.write(); + } +} + +impl Drop for Vec +where + KeyType: StorageKey, + T: Packed, +{ + fn drop(&mut self) { + self.clear_cells(); + } +} + +impl core::ops::Index for Vec +where + KeyType: StorageKey, + T: Packed, +{ + type Output = T; + + fn index(&self, index: u32) -> &Self::Output { + match self.get(index) { + Some(value) => value, + None => { + panic!( + "index out of bounds: the len is {} but the index is {}", + self.len(), + index + ) + } + } + } +} + +impl core::ops::IndexMut for Vec +where + T: Packed, + KeyType: StorageKey, +{ + fn index_mut(&mut self, index: u32) -> &mut Self::Output { + let len = self.len(); + match self.get_mut(index) { + Some(value) => value, + None => { + panic!( + "index out of bounds: the len is {} but the index is {}", + len, index + ) + } + } + } +} + +impl<'a, T: 'a, KeyType> IntoIterator for &'a Vec +where + T: Packed, + KeyType: StorageKey, +{ + type Item = &'a T; + type IntoIter = Iter<'a, T, KeyType>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl<'a, T: 'a, KeyType> IntoIterator for &'a mut Vec +where + T: Packed, + KeyType: StorageKey, +{ + type Item = &'a mut T; + type IntoIter = IterMut<'a, T, KeyType>; + + fn into_iter(self) -> Self::IntoIter { + self.iter_mut() + } +} + +impl Extend for Vec +where + T: Packed, + KeyType: StorageKey, +{ + fn extend(&mut self, iter: I) + where + I: IntoIterator, + { + for item in iter { + self.push(item) + } + } +} + +impl FromIterator for Vec +where + T: Packed, + KeyType: StorageKey, +{ + fn from_iter(iter: I) -> Self + where + I: IntoIterator, + { + let mut vec = Vec::new(); + vec.extend(iter); + vec + } +} + +impl core::cmp::PartialEq for Vec +where + T: PartialEq + Packed, + KeyType: StorageKey, +{ + fn eq(&self, other: &Self) -> bool { + if self.len() != other.len() { + return false; + } + self.iter().zip(other.iter()).all(|(lhs, rhs)| lhs == rhs) + } +} + +impl core::cmp::Eq for Vec where T: Eq + Packed {} + +/// An iterator over shared references to the elements of a storage vector. +#[derive(Debug, Clone, Copy)] +pub struct Iter<'a, T, KeyType> +where + T: Packed, + KeyType: StorageKey, +{ + /// The storage vector to iterate over. + vec: &'a Vec, + /// The current begin of the iteration. + begin: u32, + /// The current end of the iteration. + end: u32, +} + +impl<'a, T, KeyType> Iter<'a, T, KeyType> +where + T: Packed, + KeyType: StorageKey, +{ + /// Creates a new iterator for the given storage vector. + pub(crate) fn new(vec: &'a Vec) -> Self { + Self { + vec, + begin: 0, + end: vec.len(), + } + } + + /// Returns the amount of remaining elements to yield by the iterator. + fn remaining(&self) -> u32 { + self.end - self.begin + } +} + +impl<'a, T, KeyType> Iterator for Iter<'a, T, KeyType> +where + T: Packed, + KeyType: StorageKey, +{ + type Item = &'a T; + + fn next(&mut self) -> Option { + ::nth(self, 0) + } + + fn size_hint(&self) -> (usize, Option) { + let remaining = self.remaining() as usize; + (remaining, Some(remaining)) + } + + fn count(self) -> usize { + self.remaining() as usize + } + + fn nth(&mut self, n: usize) -> Option { + debug_assert!(self.begin <= self.end); + let n = n as u32; + if self.begin + n >= self.end { + return None; + } + let cur = self.begin + n; + self.begin += 1 + n; + self.vec.get(cur).expect("access is within bounds").into() + } +} + +impl<'a, T, KeyType> ExactSizeIterator for Iter<'a, T, KeyType> +where + T: Packed, + KeyType: StorageKey, +{ +} + +impl<'a, T, KeyType> DoubleEndedIterator for Iter<'a, T, KeyType> +where + T: Packed, + KeyType: StorageKey, +{ + fn next_back(&mut self) -> Option { + ::nth_back(self, 0) + } + + fn nth_back(&mut self, n: usize) -> Option { + debug_assert!(self.begin <= self.end); + let n = n as u32; + if self.begin >= self.end.saturating_sub(n) { + return None; + } + self.end -= 1 + n; + self.vec + .get(self.end) + .expect("access is within bounds") + .into() + } +} + +/// An iterator over exclusive references to the elements of a storage vector. +#[derive(Debug)] +pub struct IterMut<'a, T, KeyType> +where + T: Packed, + KeyType: StorageKey, +{ + /// The storage vector to iterate over. + vec: &'a mut Vec, + /// The current begin of the iteration. + begin: u32, + /// The current end of the iteration. + end: u32, +} + +impl<'a, T, KeyType> IterMut<'a, T, KeyType> +where + T: Packed, + KeyType: StorageKey, +{ + /// Creates a new iterator for the given storage vector. + pub(crate) fn new(vec: &'a mut Vec) -> Self { + let len = vec.len(); + Self { + vec, + begin: 0, + end: len, + } + } + + /// Returns the amount of remaining elements to yield by the iterator. + fn remaining(&self) -> u32 { + self.end - self.begin + } +} + +impl<'a, T, KeyType> IterMut<'a, T, KeyType> +where + T: Packed, + KeyType: StorageKey, +{ + fn get_mut<'b>(&'b mut self, at: u32) -> Option<&'a mut T> { + self.vec.get_mut(at).map(|value| { + // SAFETY: We extend the lifetime of the reference here. + // + // This is safe because the iterator yields an exclusive + // reference to every element in the iterated vector + // just once and also there can be only one such iterator + // for the same vector at the same time which is + // guaranteed by the constructor of the iterator. + unsafe { extend_lifetime::<'b, 'a, T>(value) } + }) + } +} + +impl<'a, T, KeyType> Iterator for IterMut<'a, T, KeyType> +where + T: Packed, + KeyType: StorageKey, +{ + type Item = &'a mut T; + + fn next(&mut self) -> Option { + ::nth(self, 0) + } + + fn size_hint(&self) -> (usize, Option) { + let remaining = self.remaining() as usize; + (remaining, Some(remaining)) + } + + fn count(self) -> usize { + self.remaining() as usize + } + + fn nth(&mut self, n: usize) -> Option { + debug_assert!(self.begin <= self.end); + let n = n as u32; + if self.begin + n >= self.end { + return None; + } + let cur = self.begin + n; + self.begin += 1 + n; + self.get_mut(cur).expect("access is within bounds").into() + } +} + +impl<'a, T, KeyType> ExactSizeIterator for IterMut<'a, T, KeyType> +where + T: Packed, + KeyType: StorageKey, +{ +} + +impl<'a, T, KeyType> DoubleEndedIterator for IterMut<'a, T, KeyType> +where + T: Packed, + KeyType: StorageKey, +{ + fn next_back(&mut self) -> Option { + ::nth_back(self, 0) + } + + fn nth_back(&mut self, n: usize) -> Option { + debug_assert!(self.begin <= self.end); + let n = n as u32; + if self.begin >= self.end.saturating_sub(n) { + return None; + } + self.end -= 1 + n; + self.get_mut(self.end) + .expect("access is within bounds") + .into() + } +} + +#[cfg(test)] +mod tests { + + use crate::collections::storage_vec::IndexOutOfBounds; + + use super::Vec as StorageVec; + use core::cmp::Ordering; + use ink_storage_traits::{ + ManualKey, + Packed, + }; + + #[test] + fn new_vec_works() { + ink_env::test::run_test::(|_| { + // `StorageVec::new` + let vec = >::new(); + assert!(vec.is_empty()); + assert_eq!(vec.len(), 0); + assert_eq!(vec.get(0), None); + assert!(vec.iter().next().is_none()); + // `StorageVec::default` + let default = as Default>::default(); + assert!(default.is_empty()); + assert_eq!(default.len(), 0); + assert_eq!(vec.get(0), None); + assert!(default.iter().next().is_none()); + // `StorageVec::new` and `StorageVec::default` should be equal. + assert_eq!(vec, default); + Ok(()) + }) + .unwrap() + } + + #[test] + fn from_iterator_works() { + ink_env::test::run_test::(|_| { + let some_primes = [1, 2, 3, 5, 7, 11, 13]; + assert_eq!(some_primes.iter().copied().collect::>(), { + let mut vec = StorageVec::new(); + for prime in &some_primes { + vec.push(*prime) + } + vec + }); + Ok(()) + }) + .unwrap() + } + + #[test] + fn from_empty_iterator_works() { + ink_env::test::run_test::(|_| { + assert_eq!( + [].iter().copied().collect::>(), + StorageVec::new(), + ); + Ok(()) + }) + .unwrap() + } + + #[test] + fn first_last_of_empty() { + ink_env::test::run_test::(|_| { + let mut vec = >::new(); + assert_eq!(vec.first(), None); + assert_eq!(vec.first_mut(), None); + assert_eq!(vec.last(), None); + assert_eq!(vec.last_mut(), None); + Ok(()) + }) + .unwrap() + } + + #[test] + fn push_pop_first_last_works() { + ink_env::test::run_test::(|_| { + /// Asserts conditions are met for the given storage vector. + fn assert_vec(vec: &StorageVec, len: u32, first: F, last: L) + where + F: Into>, + L: Into>, + { + assert_eq!(vec.is_empty(), len == 0); + assert_eq!(vec.len(), len); + assert_eq!(vec.first().copied(), first.into()); + assert_eq!(vec.last().copied(), last.into()); + } + + let mut vec = StorageVec::new(); + assert_vec(&vec, 0, None, None); + + // Sequence of `push` + vec.push(b'a'); + assert_vec(&vec, 1, b'a', b'a'); + vec.push(b'b'); + assert_vec(&vec, 2, b'a', b'b'); + vec.push(b'c'); + assert_vec(&vec, 3, b'a', b'c'); + vec.push(b'd'); + assert_vec(&vec, 4, b'a', b'd'); + + // Sequence of `pop` + assert_eq!(vec.pop(), Some(b'd')); + assert_vec(&vec, 3, b'a', b'c'); + assert_eq!(vec.pop(), Some(b'c')); + assert_vec(&vec, 2, b'a', b'b'); + assert_eq!(vec.pop(), Some(b'b')); + assert_vec(&vec, 1, b'a', b'a'); + assert_eq!(vec.pop(), Some(b'a')); + assert_vec(&vec, 0, None, None); + + // Pop from empty vector. + assert_eq!(vec.pop(), None); + assert_vec(&vec, 0, None, None); + Ok(()) + }) + .unwrap() + } + + #[test] + fn pop_drop_works() { + ink_env::test::run_test::(|_| { + let elems = [b'a', b'b', b'c', b'd']; + let mut vec = vec_from_slice(&elems); + assert_eq!(vec.pop_drop(), Some(())); + assert_eq_slice(&vec, &elems[0..3]); + assert_eq!(vec.pop_drop(), Some(())); + assert_eq_slice(&vec, &elems[0..2]); + assert_eq!(vec.pop_drop(), Some(())); + assert_eq_slice(&vec, &elems[0..1]); + assert_eq!(vec.pop_drop(), Some(())); + assert_eq_slice(&vec, &[]); + assert_eq!(vec.pop_drop(), None); + assert_eq_slice(&vec, &[]); + Ok(()) + }) + .unwrap() + } + + #[test] + fn get_works() { + ink_env::test::run_test::(|_| { + let elems = [b'a', b'b', b'c', b'd']; + let mut vec = vec_from_slice(&elems); + for (n, mut expected) in elems.iter().copied().enumerate() { + let n = n as u32; + assert_eq!(vec.get(n), Some(&expected)); + assert_eq!(vec.get_mut(n), Some(&mut expected)); + assert_eq!(&vec[n], &expected); + assert_eq!(&mut vec[n], &mut expected); + } + let len = vec.len(); + assert_eq!(vec.get(len), None); + assert_eq!(vec.get_mut(len), None); + Ok(()) + }) + .unwrap() + } + + #[test] + #[should_panic(expected = "index out of bounds: the len is 3 but the index is 3")] + fn index_out_of_bounds_works() { + ink_env::test::run_test::(|_| { + let test_values = [b'a', b'b', b'c']; + let vec = vec_from_slice(&test_values); + let _ = &vec[test_values.len() as u32]; + Ok(()) + }) + .unwrap() + } + + #[test] + #[should_panic(expected = "index out of bounds: the len is 3 but the index is 3")] + fn index_mut_out_of_bounds_works() { + ink_env::test::run_test::(|_| { + let test_values = [b'a', b'b', b'c']; + let mut vec = vec_from_slice(&test_values); + let _ = &mut vec[test_values.len() as u32]; + Ok(()) + }) + .unwrap() + } + + #[test] + fn iter_next_works() { + ink_env::test::run_test::(|_| { + let elems = [b'a', b'b', b'c', b'd']; + let vec = vec_from_slice(&elems); + // Test iterator over `&T`: + let mut iter = vec.iter(); + assert_eq!(iter.count(), 4); + assert_eq!(iter.size_hint(), (4, Some(4))); + assert_eq!(iter.next(), Some(&b'a')); + assert_eq!(iter.size_hint(), (3, Some(3))); + assert_eq!(iter.next(), Some(&b'b')); + assert_eq!(iter.size_hint(), (2, Some(2))); + assert_eq!(iter.count(), 2); + assert_eq!(iter.next(), Some(&b'c')); + assert_eq!(iter.size_hint(), (1, Some(1))); + assert_eq!(iter.next(), Some(&b'd')); + assert_eq!(iter.size_hint(), (0, Some(0))); + assert_eq!(iter.count(), 0); + assert_eq!(iter.next(), None); + // Test iterator over `&mut T`: + let mut vec = vec; + let mut iter = vec.iter_mut(); + assert_eq!(iter.size_hint(), (4, Some(4))); + assert_eq!(iter.next(), Some(&mut b'a')); + assert_eq!(iter.size_hint(), (3, Some(3))); + assert_eq!(iter.next(), Some(&mut b'b')); + assert_eq!(iter.size_hint(), (2, Some(2))); + assert_eq!(iter.next(), Some(&mut b'c')); + assert_eq!(iter.size_hint(), (1, Some(1))); + assert_eq!(iter.next(), Some(&mut b'd')); + assert_eq!(iter.size_hint(), (0, Some(0))); + assert_eq!(iter.next(), None); + assert_eq!(iter.count(), 0); + Ok(()) + }) + .unwrap() + } + + #[test] + fn iter_nth_works() { + ink_env::test::run_test::(|_| { + let elems = [b'a', b'b', b'c', b'd']; + let vec = vec_from_slice(&elems); + // Test iterator over `&T`: + let mut iter = vec.iter(); + assert_eq!(iter.count(), 4); + assert_eq!(iter.size_hint(), (4, Some(4))); + assert_eq!(iter.nth(1), Some(&b'b')); + assert_eq!(iter.count(), 2); + assert_eq!(iter.size_hint(), (2, Some(2))); + assert_eq!(iter.nth(1), Some(&b'd')); + assert_eq!(iter.size_hint(), (0, Some(0))); + assert_eq!(iter.count(), 0); + assert_eq!(iter.nth(1), None); + // Test iterator over `&mut T`: + let mut vec = vec; + let mut iter = vec.iter_mut(); + assert_eq!(iter.size_hint(), (4, Some(4))); + assert_eq!(iter.nth(1), Some(&mut b'b')); + assert_eq!(iter.size_hint(), (2, Some(2))); + assert_eq!(iter.nth(1), Some(&mut b'd')); + assert_eq!(iter.size_hint(), (0, Some(0))); + assert_eq!(iter.nth(1), None); + assert_eq!(iter.count(), 0); + Ok(()) + }) + .unwrap() + } + + #[test] + fn iter_next_back_works() { + ink_env::test::run_test::(|_| { + let elems = [b'a', b'b', b'c', b'd']; + let vec = vec_from_slice(&elems); + // Test iterator over `&T`: + let mut iter = vec.iter().rev(); + assert_eq!(iter.clone().count(), 4); + assert_eq!(iter.next(), Some(&b'd')); + assert_eq!(iter.next(), Some(&b'c')); + assert_eq!(iter.clone().count(), 2); + assert_eq!(iter.next(), Some(&b'b')); + assert_eq!(iter.next(), Some(&b'a')); + assert_eq!(iter.clone().count(), 0); + assert_eq!(iter.next(), None); + // Test iterator over `&mut T`: + let mut vec = vec; + let mut iter = vec.iter_mut().rev(); + assert_eq!(iter.next(), Some(&mut b'd')); + assert_eq!(iter.next(), Some(&mut b'c')); + assert_eq!(iter.next(), Some(&mut b'b')); + assert_eq!(iter.next(), Some(&mut b'a')); + assert_eq!(iter.next(), None); + assert_eq!(iter.count(), 0); + Ok(()) + }) + .unwrap() + } + + #[test] + fn iter_nth_back_works() { + ink_env::test::run_test::(|_| { + let elems = [b'a', b'b', b'c', b'd']; + let vec = vec_from_slice(&elems); + // Test iterator over `&T`: + let mut iter = vec.iter().rev(); + assert_eq!(iter.clone().count(), 4); + assert_eq!(iter.nth(1), Some(&b'c')); + assert_eq!(iter.clone().count(), 2); + assert_eq!(iter.nth(1), Some(&b'a')); + assert_eq!(iter.clone().count(), 0); + assert_eq!(iter.nth(1), None); + // Test iterator over `&mut T`: + let mut vec = vec; + let mut iter = vec.iter_mut().rev(); + assert_eq!(iter.nth(1), Some(&mut b'c')); + assert_eq!(iter.nth(1), Some(&mut b'a')); + assert_eq!(iter.nth(1), None); + assert_eq!(iter.count(), 0); + Ok(()) + }) + .unwrap() + } + + /// Asserts that the given ordered storage vector elements are equal to the + /// ordered elements of the given slice. + fn assert_eq_slice(vec: &StorageVec, slice: &[u8]) { + assert!(vec.iter().zip(slice.iter()).all(|(lhs, rhs)| *lhs == *rhs)) + } + + /// Creates a storage vector from the given slice. + fn vec_from_slice(slice: &[T]) -> StorageVec { + slice.iter().copied().collect::>() + } + + #[test] + fn swap_works() { + ink_env::test::run_test::(|_| { + let elems = [b'a', b'b', b'c', b'd']; + let mut vec = vec_from_slice(&elems); + + // Swap at same position is a no-op. + for index in 0..elems.len() as u32 { + vec.swap(index, index); + assert_eq_slice(&vec, &elems); + } + + // Swap first and second + vec.swap(0, 1); + assert_eq_slice(&vec, &[b'b', b'a', b'c', b'd']); + // Swap third and last + vec.swap(2, 3); + assert_eq_slice(&vec, &[b'b', b'a', b'd', b'c']); + // Swap first and last + vec.swap(0, 3); + assert_eq_slice(&vec, &[b'c', b'a', b'd', b'b']); + Ok(()) + }) + .unwrap() + } + + #[test] + #[should_panic] + fn swap_one_invalid_index() { + ink_env::test::run_test::(|_| { + let mut vec = vec_from_slice(&[b'a', b'b', b'c', b'd']); + vec.swap(0, vec.len()); + Ok(()) + }) + .unwrap() + } + + #[test] + #[should_panic] + fn swap_both_invalid_indices() { + ink_env::test::run_test::(|_| { + let mut vec = vec_from_slice(&[b'a', b'b', b'c', b'd']); + vec.swap(vec.len(), vec.len()); + Ok(()) + }) + .unwrap() + } + + #[test] + fn swap_remove_works() { + ink_env::test::run_test::(|_| { + let mut vec = vec_from_slice(&[b'a', b'b', b'c', b'd']); + + // Swap remove first element. + assert_eq!(vec.swap_remove(0), Some(b'a')); + assert_eq_slice(&vec, &[b'd', b'b', b'c']); + // Swap remove middle element. + assert_eq!(vec.swap_remove(1), Some(b'b')); + assert_eq_slice(&vec, &[b'd', b'c']); + // Swap remove last element. + assert_eq!(vec.swap_remove(1), Some(b'c')); + assert_eq_slice(&vec, &[b'd']); + // Swap remove only element. + assert_eq!(vec.swap_remove(0), Some(b'd')); + assert_eq_slice(&vec, &[]); + // Swap remove from empty vector. + assert_eq!(vec.swap_remove(0), None); + assert_eq_slice(&vec, &[]); + Ok(()) + }) + .unwrap() + } + + #[test] + fn swap_remove_drop_works() { + ink_env::test::run_test::(|_| { + let mut vec = vec_from_slice(&[b'a', b'b', b'c', b'd']); + + // Swap remove first element. + assert_eq!(vec.swap_remove_drop(0), Some(())); + assert_eq_slice(&vec, &[b'd', b'b', b'c']); + // Swap remove middle element. + assert_eq!(vec.swap_remove_drop(1), Some(())); + assert_eq_slice(&vec, &[b'd', b'c']); + // Swap remove last element. + assert_eq!(vec.swap_remove_drop(1), Some(())); + assert_eq_slice(&vec, &[b'd']); + // Swap remove only element. + assert_eq!(vec.swap_remove_drop(0), Some(())); + assert_eq_slice(&vec, &[]); + // Swap remove from empty vector. + assert_eq!(vec.swap_remove_drop(0), None); + assert_eq_slice(&vec, &[]); + Ok(()) + }) + .unwrap(); + } + + #[test] + fn clear_works() { + ink_env::test::run_test::(|_| { + let mut vec1 = vec_from_slice(&[b'a', b'b', b'c', b'd']); + vec1.write(); + + vec1.clear(); + vec1.write(); + + assert_eq!(vec1.len(), 0); + assert_eq!(vec1.get(0), None); + Ok(()) + }) + .unwrap() + } + + #[test] + fn set_works() { + ink_env::test::run_test::(|_| { + let mut vec = vec_from_slice(&[b'a', b'b', b'c', b'd']); + vec.set(0, b'x').unwrap(); + let expected = vec_from_slice(&[b'x', b'b', b'c', b'd']); + assert_eq!(vec, expected); + Ok(()) + }) + .unwrap() + } + + #[test] + fn set_fails_when_index_oob() { + ink_env::test::run_test::(|_| { + let mut vec = vec_from_slice(&[b'a']); + let res = vec.set(1, b'x'); + assert_eq!(res, Err(IndexOutOfBounds)); + Ok(()) + }) + .unwrap() + } + + #[test] + fn clear_works_on_filled_vec() { + ink_env::test::run_test::(|_| { + let mut vec = vec_from_slice(&[b'a', b'b', b'c', b'd']); + vec.clear(); + assert!(vec.is_empty()); + Ok(()) + }) + .unwrap() + } + + #[test] + fn clear_works_on_empty_vec() { + ink_env::test::run_test::(|_| { + let mut vec: StorageVec<()> = vec_from_slice(&[]); + vec.clear(); + assert!(vec.is_empty()); + Ok(()) + }) + .unwrap() + } + + #[test] + fn test_binary_search() { + ink_env::test::run_test::(|_| { + let b: StorageVec = StorageVec::new(); + assert_eq!(b.binary_search(&5), Err(0)); + + let b = vec_from_slice(&[4]); + assert_eq!(b.binary_search(&3), Err(0)); + assert_eq!(b.binary_search(&4), Ok(0)); + assert_eq!(b.binary_search(&5), Err(1)); + + let b = vec_from_slice(&[1, 2, 4, 6, 8, 9]); + dbg!(b.len()); + dbg!(&b); + assert_eq!(b.binary_search(&5), Err(3)); + assert_eq!(b.binary_search(&6), Ok(3)); + assert_eq!(b.binary_search(&7), Err(4)); + assert_eq!(b.binary_search(&8), Ok(4)); + + let b = vec_from_slice(&[1, 2, 4, 5, 6, 8]); + assert_eq!(b.binary_search(&9), Err(6)); + + let b = vec_from_slice(&[1, 2, 4, 6, 7, 8, 9]); + assert_eq!(b.binary_search(&6), Ok(3)); + assert_eq!(b.binary_search(&5), Err(3)); + assert_eq!(b.binary_search(&8), Ok(5)); + + let b = vec_from_slice(&[1, 2, 4, 5, 6, 8, 9]); + assert_eq!(b.binary_search(&7), Err(5)); + assert_eq!(b.binary_search(&0), Err(0)); + + let b = vec_from_slice(&[1, 3, 3, 3, 7]); + assert_eq!(b.binary_search(&0), Err(0)); + assert_eq!(b.binary_search(&1), Ok(0)); + assert_eq!(b.binary_search(&2), Err(1)); + matches!(b.binary_search(&3), Ok(1..=3)); + assert_eq!(b.binary_search(&4), Err(4)); + assert_eq!(b.binary_search(&5), Err(4)); + assert_eq!(b.binary_search(&6), Err(4)); + assert_eq!(b.binary_search(&7), Ok(4)); + assert_eq!(b.binary_search(&8), Err(5)); + + let b = vec_from_slice(&[(); u8::MAX as usize]); + assert_eq!(b.binary_search(&()), Ok(u8::MAX as u32 / 2)); + Ok(()) + }) + .unwrap() + } + + #[test] + fn test_binary_search_by_overflow() { + ink_env::test::run_test::(|_| { + let b = vec_from_slice(&[(); u8::MAX as usize]); + assert_eq!( + b.binary_search_by(|_| Ordering::Equal), + Ok(u8::MAX as u32 / 2) + ); + assert_eq!(b.binary_search_by(|_| Ordering::Greater), Err(0)); + assert_eq!(b.binary_search_by(|_| Ordering::Less), Err(u8::MAX as u32)); + Ok(()) + }) + .unwrap() + } + + #[test] + // Test implementation specific behavior when finding equivalent elements. + fn test_binary_search_implementation_details() { + ink_env::test::run_test::(|_| { + let b = vec_from_slice(&[1, 1, 2, 2, 3, 3, 3]); + assert_eq!(b.binary_search(&1), Ok(1)); + assert_eq!(b.binary_search(&2), Ok(3)); + assert_eq!(b.binary_search(&3), Ok(5)); + let b = vec_from_slice(&[1, 1, 1, 1, 1, 3, 3, 3, 3]); + assert_eq!(b.binary_search(&1), Ok(4)); + assert_eq!(b.binary_search(&3), Ok(7)); + let b = vec_from_slice(&[1, 1, 1, 1, 3, 3, 3, 3, 3]); + assert_eq!(b.binary_search(&1), Ok(2)); + assert_eq!(b.binary_search(&3), Ok(4)); + Ok(()) + }) + .unwrap() + } + + #[test] + //#[should_panic(expected = "encountered empty storage cell")] + fn drop_works() { + ink_env::test::run_test::(|_| { + // if the setup panics it should not cause the test to pass + let setup_result = std::panic::catch_unwind(|| { + vec_from_slice(&[b'a', b'b', b'c', b'd']).write(); + }); + assert!(setup_result.is_ok(), "setup should not panic"); + + let contract_id = ink_env::test::callee::(); + let used_cells = ink_env::test::count_used_storage_cells::< + ink_env::DefaultEnvironment, + >(&contract_id) + .expect("used cells must be returned"); + assert_eq!(used_cells, 0); + + Ok(()) + }) + .unwrap() + } + + #[test] + fn write_work() { + ink_env::test::run_test::(|_| { + let mut a = [1, 1, 2, 2, 3, 3, 3] + .iter() + .copied() + .collect::>>(); + let b = StorageVec::>::new(); + + a.write(); + + assert_eq!(b.len(), 7); + assert_eq!(a, b); + + Ok(()) + }) + .unwrap() + } + + #[test] + fn write_on_empty_vec_work() { + ink_env::test::run_test::(|_| { + let mut a = [0, 1] + .iter() + .copied() + .collect::>>(); + + a.write(); + + assert_eq!(a.pop(), Some(1)); + assert_eq!(a.pop(), Some(0)); + assert_eq!(a.pop(), None); + assert_eq!(a.len(), 0); + + a.write(); + + assert_eq!(a.pop(), None); + assert_eq!(a.len(), 0); + + Ok(()) + }) + .unwrap() + } +} diff --git a/crates/storage/src/lazy/cache_cell.rs b/crates/storage/src/lazy/cache_cell.rs new file mode 100644 index 00000000000..50688bca054 --- /dev/null +++ b/crates/storage/src/lazy/cache_cell.rs @@ -0,0 +1,105 @@ +// Copyright 2018-2022 Parity Technologies (UK) Ltd. +// +// 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. + +use core::{ + cell::UnsafeCell, + fmt, + fmt::Debug, + ptr::NonNull, +}; + +/// A cache for a `T` that allow to mutate the inner `T` through `&self`. +/// +/// Internally this is a thin wrapper around an `UnsafeCell`. +/// The main difference to `UnsafeCell` is that this type provides an out-of-the-box +/// API to safely access the inner `T` as well for single threaded contexts. +pub struct CacheCell { + /// The inner value that is allowed to be mutated in shared contexts. + inner: UnsafeCell, +} + +impl CacheCell { + /// Creates a new cache cell from the given value. + #[inline] + pub fn new(value: T) -> Self { + Self { + inner: UnsafeCell::new(value), + } + } + + /// Returns the inner value. + #[allow(dead_code)] + pub fn into_inner(self) -> T { + self.inner.into_inner() + } +} + +impl Debug for CacheCell +where + T: ?Sized + Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + ::fmt(self.as_inner(), f) + } +} + +impl From for CacheCell { + #[inline] + fn from(value: T) -> Self { + Self::new(value) + } +} + +impl Default for CacheCell +where + T: Default, +{ + #[inline] + fn default() -> Self { + Self::new(::default()) + } +} + +impl CacheCell +where + T: ?Sized, +{ + /// Returns a shared reference to the inner value. + #[inline] + pub fn as_inner(&self) -> &T { + // SAFETY: This is safe since we are returning a shared reference back + // to the caller while this method itself accesses `self` as + // shared reference. + unsafe { &*self.inner.get() } + } + + /// Returns an exclusive reference to the inner value. + #[inline] + pub fn as_inner_mut(&mut self) -> &mut T { + // SAFETY: This is safe since we are returning the exclusive reference + // of the inner value through the `get_mut` API which itself + // requires exclusive reference access to the wrapping `self` + // disallowing aliasing exclusive references. + unsafe { &mut *self.inner.get() } + } + + /// Returns a mutable pointer to the inner value. + #[inline] + pub fn get_ptr(&self) -> NonNull { + // SAFETY: The inner `T` of the internal `UnsafeCell` exists and thus + // the pointer that we get returned to it via `UnsafeCell::get` + // is never going to be `null`. + unsafe { NonNull::new_unchecked(self.inner.get()) } + } +} diff --git a/crates/storage/src/lazy/entry.rs b/crates/storage/src/lazy/entry.rs new file mode 100644 index 00000000000..97f9738d9eb --- /dev/null +++ b/crates/storage/src/lazy/entry.rs @@ -0,0 +1,191 @@ +// Copyright 2018-2022 Parity Technologies (UK) Ltd. +// +// 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. + +use core::{ + cell::Cell, + fmt, + fmt::Debug, +}; +use ink_prelude::vec::Vec; +use ink_storage_traits::Packed; + +/// The entry of a single cached value of a lazy storage data structure. +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct StorageEntry { + /// The value or `None` if the value has been removed. + value: Option, + /// This is [`EntryState::Mutated`] if the value has been mutated and is in + /// need to be synchronized with the contract storage. If it is + /// [`EntryState::Preserved`] the value from the contract storage has been + /// preserved and does not need to be synchronized. + state: Cell, +} + +impl Debug for StorageEntry +where + T: Packed + Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("Entry") + .field("value", &self.value) + .field("state", &self.state.get()) + .finish() + } +} + +#[test] +fn debug_impl_works() { + let e1 = >::new(None, EntryState::Preserved); + assert_eq!( + format!("{:?}", &e1), + "Entry { value: None, state: Preserved }", + ); + let e2 = StorageEntry::new(Some(42), EntryState::Mutated); + assert_eq!( + format!("{:?}", &e2), + "Entry { value: Some(42), state: Mutated }", + ); +} + +/// The state of the entry. +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] +pub enum EntryState { + /// The entry's value must be synchronized with the contract storage. + Mutated, + /// The entry's value preserved the value from the contract storage. + Preserved, +} + +impl EntryState { + /// Returns `true` if the entry state is mutated. + pub fn is_mutated(self) -> bool { + match self { + EntryState::Mutated => true, + EntryState::Preserved => false, + } + } + + /// Returns `true` if the entry state is preserved. + pub fn is_preserved(self) -> bool { + !self.is_mutated() + } +} + +impl scale::Encode for StorageEntry +where + T: Packed, +{ + #[inline] + fn size_hint(&self) -> usize { + as scale::Encode>::size_hint(&self.value) + } + + #[inline] + fn encode_to(&self, dest: &mut O) { + as scale::Encode>::encode_to(&self.value, dest) + } + + #[inline] + fn encode(&self) -> Vec { + as scale::Encode>::encode(&self.value) + } + + #[inline] + fn using_encoded R>(&self, f: F) -> R { + as scale::Encode>::using_encoded(&self.value, f) + } +} + +impl scale::Decode for StorageEntry +where + T: Packed, +{ + fn decode(input: &mut I) -> Result { + Ok(Self::new( + as scale::Decode>::decode(input)?, + EntryState::Preserved, + )) + } +} + +impl StorageEntry +where + T: Packed, +{ + /// Creates a new entry with the value and state. + pub fn new(value: Option, state: EntryState) -> Self { + Self { + value, + state: Cell::new(state), + } + } + + /// Replaces the current entry state with the new state and returns it. + pub fn replace_state(&self, new_state: EntryState) -> EntryState { + // The implementation of `Cell::set` uses `Cell::replace` so instead + // of offering both APIs we simply opted to offer just the more general + // replace API for `Entry`. + self.state.replace(new_state) + } + + /// Returns a shared reference to the value of the entry. + pub fn value(&self) -> &Option { + &self.value + } + + /// Returns an exclusive reference to the entry value. + /// + /// # Note + /// + /// This changes the `mutate` state of the entry if the entry was occupied + /// since the caller could potentially change the returned value. + pub fn value_mut(&mut self) -> &mut Option { + if self.value.is_some() { + self.state.set(EntryState::Mutated); + } + &mut self.value + } + + /// Converts the entry into its value. + pub fn into_value(self) -> Option { + self.value + } + + /// Puts the new value into the entry and returns the old value. + /// + /// # Note + /// + /// This changes the `mutate` state of the entry to `true` as long as at + /// least one of `old_value` and `new_value` is `Some`. + pub fn put(&mut self, new_value: Option) -> Option { + let new_value_is_some = new_value.is_some(); + let old_value = core::mem::replace(&mut self.value, new_value); + if old_value.is_some() || new_value_is_some { + self.state.set(EntryState::Mutated); + } + old_value + } + + pub fn write(&self, at: &(u32, u32)) { + let old_state = self.replace_state(EntryState::Preserved); + + if old_state.is_mutated() { + match self.value() { + Some(value) => ink_env::set_contract_storage(at, value), + None => ink_env::clear_contract_storage(at), + }; + } + } +} diff --git a/crates/storage/src/lazy/lazy_imap.rs b/crates/storage/src/lazy/lazy_imap.rs new file mode 100644 index 00000000000..c9660542261 --- /dev/null +++ b/crates/storage/src/lazy/lazy_imap.rs @@ -0,0 +1,668 @@ +// Copyright 2018-2022 Parity Technologies (UK) Ltd. +// +// 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. + +use super::{ + cache_cell::CacheCell, + entry::{ + EntryState, + StorageEntry, + }, +}; +use core::{ + fmt, + fmt::Debug, + marker::PhantomData, + ptr::NonNull, +}; +use ink_prelude::{ + boxed::Box, + collections::BTreeMap, +}; +use ink_primitives::Key; +use ink_storage_traits::{ + Packed, + StorageKey, +}; + +/// The index type used in the lazy storage chunk. +pub type Index = u32; + +/// A lazy storage chunk that spans over a whole chunk of storage cells. +/// +/// # Note +/// +/// This is mainly used as low-level storage primitives by other high-level +/// storage primitives in order to manage the contract storage for a whole +/// chunk of storage cells. +/// +/// A chunk of storage cells is a contiguous range of `2^32` storage cells. +pub struct LazyIndexMap { + /// The subset of currently cached entries of the lazy storage chunk. + /// + /// An entry is cached as soon as it is loaded or written. + cached_entries: CacheCell>, + #[allow(clippy::type_complexity)] + _marker: PhantomData (V, KeyType)>, +} + +struct DebugEntryMap<'a, V: Packed>(&'a CacheCell>); + +impl<'a, V> Debug for DebugEntryMap<'a, V> +where + V: Packed + Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_map().entries(self.0.as_inner().iter()).finish() + } +} + +impl Debug for LazyIndexMap +where + V: Packed + Debug, + KeyType: StorageKey, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("LazyIndexMap") + .field("key", &KeyType::KEY) + .field("cached_entries", &DebugEntryMap(&self.cached_entries)) + .finish() + } +} + +#[test] +fn debug_impl_works() { + ink_env::test::run_test::(|_| { + let mut imap = >::new(); + // Empty imap. + assert_eq!( + format!("{:?}", &imap), + "LazyIndexMap { key: 0, cached_entries: {} }", + ); + // Filled imap. + imap.put(0, Some(1)); + imap.put(42, Some(2)); + imap.put(999, None); + assert_eq!( + format!("{:?}", &imap), + "LazyIndexMap { \ + key: 0, \ + cached_entries: {\ + 0: Entry { \ + value: Some(1), \ + state: Mutated \ + }, \ + 42: Entry { \ + value: Some(2), \ + state: Mutated \ + }, \ + 999: Entry { \ + value: None, \ + state: Mutated \ + }\ + } \ + }", + ); + Ok(()) + }) + .unwrap() +} + +impl Default for LazyIndexMap +where + V: Packed, + KeyType: StorageKey, +{ + fn default() -> Self { + Self::new() + } +} + +/// The map for the contract storage entries. +/// +/// # Note +/// +/// We keep the whole entry in a `Box` in order to prevent pointer +/// invalidation upon updating the cache through `&self` methods as in +/// [`LazyIndexMap::get`]. +pub type EntryMap = BTreeMap>>; + +impl LazyIndexMap +where + V: Packed, + KeyType: StorageKey, +{ + /// Creates a new empty lazy map. + /// + /// # Note + /// + /// A lazy map created this way cannot be used to load from the contract storage. + /// All operations that directly or indirectly load from storage will panic. + pub fn new() -> Self { + Self { + cached_entries: CacheCell::new(EntryMap::new()), + _marker: Default::default(), + } + } + + /// Returns a shared reference to the underlying entries. + fn entries(&self) -> &EntryMap { + self.cached_entries.as_inner() + } + + /// Returns an exclusive reference to the underlying entries. + fn entries_mut(&mut self) -> &mut EntryMap { + self.cached_entries.as_inner_mut() + } + + /// Puts the new value at the given index. + /// + /// # Note + /// + /// - Use [`LazyIndexMap::put`]`(None)` in order to remove an element. + /// - Prefer this method over [`LazyIndexMap::put_get`] if you are not interested in + /// the old value of the same cell index. + /// + /// # Panics + /// + /// - If the lazy chunk is in an invalid state that forbids interaction. + /// - If the decoding of the old element at the given index failed. + pub fn put(&mut self, index: Index, new_value: Option) { + use ink_prelude::collections::btree_map::Entry as BTreeMapEntry; + match self.entries_mut().entry(index) { + BTreeMapEntry::Occupied(mut occupied) => { + // We can re-use the already existing boxed `Entry` and simply + // swap the underlying values. + occupied.get_mut().put(new_value); + } + BTreeMapEntry::Vacant(vacant) => { + vacant + .insert(Box::new(StorageEntry::new(new_value, EntryState::Mutated))); + } + } + } +} + +//#[cfg(feature = "std")] +// const _: () = { +// use crate::traits::StorageLayout; +// use ink_metadata::layout::{ArrayLayout, CellLayout, Layout, LayoutKey}; +// use scale_info::TypeInfo; +// +// impl StorageLayout for LazyIndexMap +// where +// T: TypeInfo + 'static, +// { +// fn layout(key_ptr: &mut KeyPtr) -> Layout { +// let capacity = u32::MAX; +// Layout::Array(ArrayLayout::new( +// LayoutKey::from(key_ptr.advance_by(capacity as u64)), +// capacity, +// 1, +// Layout::Cell(CellLayout::new::(LayoutKey::from( +// key_ptr.advance_by(0), +// ))), +// )) +// } +// } +//}; + +impl LazyIndexMap +where + V: Packed, + KeyType: StorageKey, +{ + /// Clears the underlying storage of the entry at the given index. + /// + /// # Safety + /// + /// For performance reasons this does not synchronize the lazy index map's + /// memory-side cache which invalidates future accesses the cleared entry. + /// Care should be taken when using this API. + /// + /// The general use of this API is to streamline `Drop` implementations of + /// high-level abstractions that build upon this low-level data structure. + pub fn clear_packed_at(&self, index: Index) { + let root_key = self.key_at(index); + + ink_env::clear_contract_storage(&root_key); + } + + pub fn write(&self) { + for (&index, entry) in self.entries().iter() { + entry.write(&(KeyType::KEY, index)); + } + } +} + +impl LazyIndexMap +where + V: Packed, + KeyType: StorageKey, +{ + /// Returns an offset key for the given index. + pub fn key_at(&self, index: Index) -> (Key, Index) { + (KeyType::KEY, index) + } + + /// Lazily loads the value at the given index. + /// + /// # Note + /// + /// Only loads a value if `key` is set and if the value has not been loaded yet. + /// Returns the freshly loaded or already loaded entry of the value. + /// + /// # Safety + /// + /// This function has a `&self` receiver while returning an `Option<*mut T>` + /// which is unsafe in isolation. The caller has to determine how to forward + /// the returned `*mut T`. + /// + /// # Safety + /// + /// This is an `unsafe` operation because it has a `&self` receiver but returns + /// a `*mut Entry` pointer that allows for exclusive access. This is safe + /// within internal use only and should never be given outside the lazy entity + /// for public `&self` methods. + unsafe fn lazily_load(&self, index: Index) -> NonNull> { + // SAFETY: We have put the whole `cached_entries` mapping into an + // `UnsafeCell` because of this caching functionality. The + // trick here is that due to using `Box` internally + // we are able to return references to the cached entries + // while maintaining the invariant that mutating the caching + // `BTreeMap` will never invalidate those references. + // By returning a raw pointer we enforce an `unsafe` block at + // the caller site to underline that guarantees are given by the + // caller. + let cached_entries = &mut *self.cached_entries.get_ptr().as_ptr(); + use ink_prelude::collections::btree_map::Entry as BTreeMapEntry; + match cached_entries.entry(index) { + BTreeMapEntry::Occupied(occupied) => { + NonNull::from(&mut **occupied.into_mut()) + } + BTreeMapEntry::Vacant(vacant) => { + let value = ink_env::get_contract_storage(&self.key_at(index)).unwrap(); + NonNull::from( + &mut **vacant.insert(Box::new(StorageEntry::new( + value, + EntryState::Preserved, + ))), + ) + } + } + } + + /// Lazily loads the value at the given index. + /// + /// # Note + /// + /// Only loads a value if `key` is set and if the value has not been loaded yet. + /// Returns the freshly loaded or already loaded entry of the value. + /// + /// # Panics + /// + /// - If the lazy chunk is in an invalid state that forbids interaction. + /// - If the lazy chunk is not in a state that allows lazy loading. + fn lazily_load_mut(&mut self, index: Index) -> &mut StorageEntry { + // SAFETY: + // - Returning a `&mut Entry` is safe because entities inside the cache are + // stored within a `Box` to not invalidate references into them upon operating + // on the outer cache. + unsafe { &mut *self.lazily_load(index).as_ptr() } + } + + /// Returns a shared reference to the element at the given index if any. + /// + /// # Panics + /// + /// - If the lazy chunk is in an invalid state that forbids interaction. + /// - If the decoding of the element at the given index failed. + pub fn get(&self, index: Index) -> Option<&V> { + // SAFETY: Dereferencing the `*mut T` pointer into a `&T` is safe + // since this method's receiver is `&self` so we do not + // leak non-shared references to the outside. + unsafe { &*self.lazily_load(index).as_ptr() }.value().into() + } + + /// Returns an exclusive reference to the element at the given index if any. + /// + /// # Panics + /// + /// - If the lazy chunk is in an invalid state that forbids interaction. + /// - If the decoding of the element at the given index failed. + pub fn get_mut(&mut self, index: Index) -> Option<&mut V> { + self.lazily_load_mut(index).value_mut().into() + } + + /// Puts the new value at the given index and returns the old value if any. + /// + /// # Note + /// + /// - Use [`LazyIndexMap::put_get`]`(None)` in order to remove an element and retrieve + /// the old element back. + /// + /// # Panics + /// + /// - If the lazy chunk is in an invalid state that forbids interaction. + /// - If the decoding of the old element at the given index failed. + pub fn put_get(&mut self, index: Index, new_value: Option) -> Option { + self.lazily_load_mut(index).put(new_value) + } + + /// Swaps the values at indices `x` and `y`. + /// + /// This operation tries to be as efficient as possible and reuse allocations. + /// + /// # Panics + /// + /// - If the lazy chunk is in an invalid state that forbids interaction. + /// - If the decoding of one of the elements failed. + pub fn swap(&mut self, x: Index, y: Index) { + if x == y { + // Bail out early if both indices are the same. + return + } + let (loaded_x, loaded_y) = + // SAFETY: The loaded `x` and `y` entries are distinct from each + // other guaranteed by the previous check. Also `lazily_load` + // guarantees to return a pointer to a pinned entity + // so that the returned references do not conflict with + // each other. + unsafe { ( + &mut *self.lazily_load(x).as_ptr(), + &mut *self.lazily_load(y).as_ptr(), + ) }; + if loaded_x.value().is_none() && loaded_y.value().is_none() { + // Bail out since nothing has to be swapped if both values are `None`. + return + } + // Set the `mutate` flag since at this point at least one of the loaded + // values is guaranteed to be `Some`. + loaded_x.replace_state(EntryState::Mutated); + loaded_y.replace_state(EntryState::Mutated); + core::mem::swap(loaded_x.value_mut(), loaded_y.value_mut()); + } +} + +#[cfg(test)] +mod tests { + use super::{ + super::entry::{ + EntryState, + StorageEntry, + }, + Index, + LazyIndexMap, + }; + use ink_storage_traits::{ + AutoKey, + StorageKey, + }; + + /// Asserts that the cached entries of the given `imap` is equal to the `expected` + /// slice. + fn assert_cached_entries( + imap: &LazyIndexMap, + expected: &[(Index, StorageEntry)], + ) { + assert_eq!(imap.entries().len(), expected.len()); + for (given, expected) in imap + .entries() + .iter() + .map(|(index, boxed_entry)| (*index, &**boxed_entry)) + .zip(expected.iter().map(|(index, entry)| (*index, entry))) + { + assert_eq!(given, expected); + } + } + + //#[test] + // fn new_works() { + // let imap = >::new(); + // // Key must be none. + // assert_eq!(imap.key(), None); + // assert_eq!(imap.key_at(0), None); + // // Cached elements must be empty. + // assert_cached_entries(&imap, &[]); + // // Same as default: + // let default_imap = >::default(); + // assert_eq!(imap.key(), default_imap.key()); + // assert_eq!(imap.entries(), default_imap.entries()); + //} + + // fn add_key(key: &Key, offset: u32) -> Key { + // let mut result = *key; + // result += offset; + // result + //} + + //#[test] + // fn lazy_works() { + // let key = Key::default(); + // let imap = >::lazy(key); + // // Key must be none. + // assert_eq!(imap.key(), Some(&key)); + // assert_eq!(imap.key_at(0), Some((key, 0))); + // assert_eq!(imap.key_at(1), Some((key, 1))); + // // Cached elements must be empty. + // assert_cached_entries(&imap, &[]); + //} + #[test] + fn put_get_works() { + ink_env::test::run_test::(|_| { + let mut imap = >::new(); + // Put some values. + assert_eq!(imap.put_get(1, Some(b'A')), None); + assert_eq!(imap.put_get(2, Some(b'B')), None); + assert_eq!(imap.put_get(4, Some(b'C')), None); + assert_cached_entries( + &imap, + &[ + (1, StorageEntry::new(Some(b'A'), EntryState::Mutated)), + (2, StorageEntry::new(Some(b'B'), EntryState::Mutated)), + (4, StorageEntry::new(Some(b'C'), EntryState::Mutated)), + ], + ); + // Put none values. + assert_eq!(imap.put_get(3, None), None); + assert_eq!(imap.put_get(5, None), None); + assert_cached_entries( + &imap, + &[ + (1, StorageEntry::new(Some(b'A'), EntryState::Mutated)), + (2, StorageEntry::new(Some(b'B'), EntryState::Mutated)), + (3, StorageEntry::new(None, EntryState::Preserved)), + (4, StorageEntry::new(Some(b'C'), EntryState::Mutated)), + (5, StorageEntry::new(None, EntryState::Preserved)), + ], + ); + // Override some values with none. + assert_eq!(imap.put_get(2, None), Some(b'B')); + assert_eq!(imap.put_get(4, None), Some(b'C')); + assert_cached_entries( + &imap, + &[ + (1, StorageEntry::new(Some(b'A'), EntryState::Mutated)), + (2, StorageEntry::new(None, EntryState::Mutated)), + (3, StorageEntry::new(None, EntryState::Preserved)), + (4, StorageEntry::new(None, EntryState::Mutated)), + (5, StorageEntry::new(None, EntryState::Preserved)), + ], + ); + // Override none values with some. + assert_eq!(imap.put_get(3, Some(b'X')), None); + assert_eq!(imap.put_get(5, Some(b'Y')), None); + assert_cached_entries( + &imap, + &[ + (1, StorageEntry::new(Some(b'A'), EntryState::Mutated)), + (2, StorageEntry::new(None, EntryState::Mutated)), + (3, StorageEntry::new(Some(b'X'), EntryState::Mutated)), + (4, StorageEntry::new(None, EntryState::Mutated)), + (5, StorageEntry::new(Some(b'Y'), EntryState::Mutated)), + ], + ); + Ok(()) + }) + .unwrap() + } + + #[test] + fn get_works() { + ink_env::test::run_test::(|_| { + let mut imap = >::new(); + let nothing_changed = &[ + (1, StorageEntry::new(None, EntryState::Preserved)), + (2, StorageEntry::new(Some(b'B'), EntryState::Mutated)), + (3, StorageEntry::new(None, EntryState::Preserved)), + (4, StorageEntry::new(Some(b'D'), EntryState::Mutated)), + ]; + // Put some values. + assert_eq!(imap.put_get(1, None), None); + assert_eq!(imap.put_get(2, Some(b'B')), None); + assert_eq!(imap.put_get(3, None), None); + assert_eq!(imap.put_get(4, Some(b'D')), None); + assert_cached_entries(&imap, nothing_changed); + // `get` works: + assert_eq!(imap.get(1), None); + assert_eq!(imap.get(2), Some(&b'B')); + assert_eq!(imap.get(3), None); + assert_eq!(imap.get(4), Some(&b'D')); + assert_cached_entries(&imap, nothing_changed); + // `get_mut` works: + assert_eq!(imap.get_mut(1), None); + assert_eq!(imap.get_mut(2), Some(&mut b'B')); + assert_eq!(imap.get_mut(3), None); + assert_eq!(imap.get_mut(4), Some(&mut b'D')); + assert_cached_entries(&imap, nothing_changed); + // `get` or `get_mut` without cache: + assert_eq!(imap.get(5), None); + assert_eq!(imap.get_mut(5), None); + Ok(()) + }) + .unwrap() + } + + #[test] + fn put_works() { + ink_env::test::run_test::(|_| { + let mut imap = >::new(); + // Put some values. + imap.put(1, None); + imap.put(2, Some(b'B')); + imap.put(4, None); + // The main difference between `put` and `put_get` is that `put` never + // loads from storage which also has one drawback: Putting a `None` + // value always ends-up in `Mutated` state for the entry even if the + // entry is already `None`. + assert_cached_entries( + &imap, + &[ + (1, StorageEntry::new(None, EntryState::Mutated)), + (2, StorageEntry::new(Some(b'B'), EntryState::Mutated)), + (4, StorageEntry::new(None, EntryState::Mutated)), + ], + ); + // Overwrite entries: + imap.put(1, Some(b'A')); + imap.put(2, None); + assert_cached_entries( + &imap, + &[ + (1, StorageEntry::new(Some(b'A'), EntryState::Mutated)), + (2, StorageEntry::new(None, EntryState::Mutated)), + (4, StorageEntry::new(None, EntryState::Mutated)), + ], + ); + Ok(()) + }) + .unwrap() + } + + #[test] + fn swap_works() { + ink_env::test::run_test::(|_| { + let mut imap = >::new(); + let nothing_changed = &[ + (1, StorageEntry::new(Some(b'A'), EntryState::Mutated)), + (2, StorageEntry::new(Some(b'B'), EntryState::Mutated)), + (3, StorageEntry::new(None, EntryState::Preserved)), + (4, StorageEntry::new(None, EntryState::Preserved)), + ]; + // Put some values. + assert_eq!(imap.put_get(1, Some(b'A')), None); + assert_eq!(imap.put_get(2, Some(b'B')), None); + assert_eq!(imap.put_get(3, None), None); + assert_eq!(imap.put_get(4, None), None); + assert_cached_entries(&imap, nothing_changed); + // Swap same indices: Check that nothing has changed. + for i in 0..4 { + imap.swap(i, i); + } + assert_cached_entries(&imap, nothing_changed); + // Swap `None` values: Check that nothing has changed. + imap.swap(3, 4); + imap.swap(4, 3); + assert_cached_entries(&imap, nothing_changed); + // Swap `Some` and `None`: + imap.swap(1, 3); + assert_cached_entries( + &imap, + &[ + (1, StorageEntry::new(None, EntryState::Mutated)), + (2, StorageEntry::new(Some(b'B'), EntryState::Mutated)), + (3, StorageEntry::new(Some(b'A'), EntryState::Mutated)), + (4, StorageEntry::new(None, EntryState::Preserved)), + ], + ); + // Swap `Some` and `Some`: + imap.swap(2, 3); + assert_cached_entries( + &imap, + &[ + (1, StorageEntry::new(None, EntryState::Mutated)), + (2, StorageEntry::new(Some(b'A'), EntryState::Mutated)), + (3, StorageEntry::new(Some(b'B'), EntryState::Mutated)), + (4, StorageEntry::new(None, EntryState::Preserved)), + ], + ); + // Swap out of bounds: `None` and `None` + imap.swap(4, 5); + assert_cached_entries( + &imap, + &[ + (1, StorageEntry::new(None, EntryState::Mutated)), + (2, StorageEntry::new(Some(b'A'), EntryState::Mutated)), + (3, StorageEntry::new(Some(b'B'), EntryState::Mutated)), + (4, StorageEntry::new(None, EntryState::Preserved)), + (5, StorageEntry::new(None, EntryState::Preserved)), + ], + ); + // Swap out of bounds: `Some` and `None` + imap.swap(3, 6); + assert_cached_entries( + &imap, + &[ + (1, StorageEntry::new(None, EntryState::Mutated)), + (2, StorageEntry::new(Some(b'A'), EntryState::Mutated)), + (3, StorageEntry::new(None, EntryState::Mutated)), + (4, StorageEntry::new(None, EntryState::Preserved)), + (5, StorageEntry::new(None, EntryState::Preserved)), + (6, StorageEntry::new(Some(b'B'), EntryState::Mutated)), + ], + ); + Ok(()) + }) + .unwrap() + } +} diff --git a/crates/storage/src/lazy/mod.rs b/crates/storage/src/lazy/mod.rs index ef0fed60268..1f12c5ec02f 100644 --- a/crates/storage/src/lazy/mod.rs +++ b/crates/storage/src/lazy/mod.rs @@ -18,8 +18,12 @@ //! These low-level collections are not aware of the elements they manage thus //! extra care has to be taken when operating directly on them. +mod cache_cell; +mod entry; +mod lazy_imap; mod mapping; +pub use self::lazy_imap::LazyIndexMap; #[doc(inline)] pub use self::mapping::Mapping; diff --git a/crates/storage/src/lib.rs b/crates/storage/src/lib.rs index 266a31c2f26..ce16c667971 100644 --- a/crates/storage/src/lib.rs +++ b/crates/storage/src/lib.rs @@ -48,11 +48,30 @@ pub use ink_storage_traits as traits; +pub(crate) mod collections; #[allow(dead_code)] pub(crate) mod lazy; +pub use self::collections::Vec as StorageVec; #[doc(inline)] pub use self::lazy::{ Lazy, Mapping, }; + +/// Extends the lifetime `'a` to the outliving lifetime `'b` for the given reference. +/// +/// # Note +/// +/// This interface is a bit more constraint than a simple +/// [transmute](`core::mem::transmute`) and therefore preferred +/// for extending lifetimes only. +/// +/// # Safety +/// +/// This function is `unsafe` because lifetimes can be extended beyond the +/// lifetimes of the objects they are referencing and thus potentially create +/// dangling references if not used carefully. +pub(crate) unsafe fn extend_lifetime<'a, 'b: 'a, T>(reference: &'a mut T) -> &'b mut T { + core::mem::transmute::<&'a mut T, &'b mut T>(reference) +} diff --git a/integration-tests/storagevec-integration-tests/.gitignore b/integration-tests/storagevec-integration-tests/.gitignore new file mode 100755 index 00000000000..8de8f877e47 --- /dev/null +++ b/integration-tests/storagevec-integration-tests/.gitignore @@ -0,0 +1,9 @@ +# Ignore build artifacts from the local tests sub-crate. +/target/ + +# Ignore backup files creates by cargo fmt. +**/*.rs.bk + +# Remove Cargo.lock when creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +Cargo.lock diff --git a/integration-tests/storagevec-integration-tests/Cargo.toml b/integration-tests/storagevec-integration-tests/Cargo.toml new file mode 100755 index 00000000000..aa014a6a69a --- /dev/null +++ b/integration-tests/storagevec-integration-tests/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "storagevec-integration-tests" +version = "5.0.0-alpha" +authors = ["Parity Technologies "] +edition = "2021" +publish = false + +[dependencies] +ink = { path = "../../crates/ink", default-features = false } + +[dev-dependencies] +ink_e2e = { path = "../../crates/e2e" } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", +] +ink-as-dependency = [] +e2e-tests = [] diff --git a/integration-tests/storagevec-integration-tests/lib.rs b/integration-tests/storagevec-integration-tests/lib.rs new file mode 100755 index 00000000000..c7b3a805d6a --- /dev/null +++ b/integration-tests/storagevec-integration-tests/lib.rs @@ -0,0 +1,158 @@ +//! A smart contract which demonstrates functionality of `StorageVec` functions. + +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +#[ink::contract] +mod storagevec_integration_tests { + use ink::{ + prelude::vec::Vec, + storage::StorageVec, + }; + + #[cfg_attr(feature = "std", derive(ink::storage::traits::StorageLayout))] + #[ink::scale_derive(Encode, Decode, TypeInfo)] + #[derive(Clone, Debug)] + pub struct Proposal { + data: Vec, + until: BlockNumber, + approvals: u32, + min_approvals: u32, + } + + impl Proposal { + fn is_finished(&self) -> bool { + self.until < ink::env::block_number::() + } + } + + #[ink(storage)] + pub struct StorageVector { + proposals: StorageVec, + } + + impl StorageVector { + #[ink(constructor, payable)] + pub fn default() -> Self { + Self { + proposals: Default::default(), + } + } + + /// Checks whether given account is allowed to vote and didn't already + /// participate. + fn is_eligible(&self, _voter: AccountId) -> bool { + // todo + true + } + + /// Vote to approve the current proposal. + #[ink(message)] + pub fn approve(&mut self) { + assert!(self.is_eligible(self.env().caller())); + + if let Some(mut proposal) = self.proposals.pop() { + assert!(!proposal.is_finished()); + + proposal.approvals = proposal.approvals.saturating_add(1); + self.proposals.push(proposal); + self.proposals.write(); + } + } + + /// Create a new proposal. + /// + /// Returns `None` if the current proposal is not yet finished. + #[ink(message)] + pub fn create_proposal( + &mut self, + data: Vec, + duration: BlockNumber, + min_approvals: u32, + ) -> Option { + let proposal_number = match self.proposals.last() { + Some(last) if !last.is_finished() => return None, + _ => self.proposals.len(), + }; + + self.proposals.push(Proposal { + data, + until: self.env().block_number().saturating_add(duration.min(6000)), + min_approvals, + approvals: 0, + }); + self.proposals.write(); + + Some(proposal_number) + } + + #[ink(message)] + pub fn get(&self, at: u32) -> Option { + self.proposals.get(at).cloned() + } + } + + #[cfg(all(test, feature = "e2e-tests"))] + mod e2e_tests { + use super::*; + use ink_e2e::ContractsBackend; + + type E2EResult = std::result::Result>; + + #[ink_e2e::test] + async fn create_and_vote( + mut client: Client, + ) -> E2EResult<()> { + // given + let mut constructor = StorageVectorRef::default(); + let contract = client + .instantiate( + "storagevec-integration-tests", + &ink_e2e::alice(), + &mut constructor, + ) + .submit() + .await + .expect("instantiate failed"); + let mut call = contract.call::(); + + // when + let create = call.create_proposal(vec![0x41], 5, 1); + let _ = client + .call(&ink_e2e::alice(), &create) + .submit() + .await + .expect("Calling `create_proposal` failed"); + + let approve = call.approve(); + let _ = client + .call(&ink_e2e::alice(), &approve) + .submit() + .await + .expect("Voting failed"); + let _ = client + .call(&ink_e2e::bob(), &approve) + .submit() + .await + .expect("Voting failed"); + + // then + let value = client + .call(&ink_e2e::alice(), &create) + .dry_run() + .await + .expect("create trapped when it shouldn't") + .return_value(); + assert_eq!(value, None); + + let value = client + .call(&ink_e2e::alice(), &call.get(0)) + .dry_run() + .await + .expect("get trapped when it shouldn't") + .return_value(); + assert_eq!(value.unwrap().approvals, 2); + + Ok(()) + } + } +}