From 7b0b5ca7473ac59c468d3f589e82c530919882a6 Mon Sep 17 00:00:00 2001 From: Cyrill Leutwiler Date: Thu, 2 Nov 2023 11:56:28 +0100 Subject: [PATCH 01/15] design draft Signed-off-by: Cyrill Leutwiler --- crates/storage/src/lazy/mod.rs | 2 + crates/storage/src/lazy/vec.rs | 153 +++++++++++++++++++++++++++++++++ 2 files changed, 155 insertions(+) create mode 100644 crates/storage/src/lazy/vec.rs diff --git a/crates/storage/src/lazy/mod.rs b/crates/storage/src/lazy/mod.rs index 058c975c0ec..9e8fa3a76a1 100644 --- a/crates/storage/src/lazy/mod.rs +++ b/crates/storage/src/lazy/mod.rs @@ -19,9 +19,11 @@ //! extra care has to be taken when operating directly on them. mod mapping; +mod vec; #[doc(inline)] pub use self::mapping::Mapping; +pub use self::vec::StorageVec; use crate::traits::{ AutoKey, diff --git a/crates/storage/src/lazy/vec.rs b/crates/storage/src/lazy/vec.rs new file mode 100644 index 00000000000..2da4fe18d5d --- /dev/null +++ b/crates/storage/src/lazy/vec.rs @@ -0,0 +1,153 @@ +// Copyright (C) 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 simple storage vector implementation built on top of [Mapping]. +//! +//! # Note +//! +//! This vector doesn't actually "own" any data. +//! Instead it is just a simple wrapper around the contract storage facilities. + +use ink_storage_traits::{AutoKey, Packed, Storable, StorageKey}; + +use crate::{Lazy, Mapping}; + +/// A vector of values (elements) directly on contract storage. +/// +/// # Difference between `ink::prelude::vec::Vec` and [StorageVec] +/// +/// Any `Vec` will exhibit [Packed] storage layout in its entirety; +/// where [StorageVec] stores each value under it's own storage key. +/// +/// Hence, reading and writing a `Vec` from and to storage will load +/// and decode _all_ of its elements. +/// +/// This can be undesirable: +/// The cost of read and write operations grows linearly, corresponding to +/// the number of elements in the vector (its length). Additionally, the +/// maximum capacity of the _whole_ vector is limited by the size of +/// the static buffer used during ABI encoding and decoding (default 16KiB). +/// +/// [StorageVec] on the other hand can theoretically grow to infinite size. +/// However, we currently limit the length at 2 ^ 32 elements: In practice, +/// even if the vector elements are single bytes, it'll allow to store +/// more than 4GB data in blockchain storage, or more than enoug. +/// +/// # Caveats +/// +/// Iterating over [StorageVec] elements will cause a storage read for +/// _each_ iteration (additionally a storage write in case of mutable +/// iterations with assignements). +/// +/// The decision whether to use `Vec` or [StorageVec] can be seen as an +/// optimization problem with several factors: +/// * How large you expect the vector to grow +/// * The size of individual elements being stored +/// * How frequentely reads, writes and iterations happen +/// +/// For example, if a vector is expected to stay small but is frequently +/// iteratet over. Chooosing a `Vec` instead of [StorageVec] will be +/// preferred as indiviudal storage reads are much more expensive as +/// opposed to retrieving and decoding the whole collections with a single +/// storage read. +/// +/// # Storage Layout +/// +/// At given [StorageKey] `K`, the length of the [StorageVec] is hold. +/// Each element `E` is then stored under a combination of the [StorageVec] +/// key `K` and the elements index. +/// +/// Given [StorageVec] under key `K`, the storage key `E` of the `N`th +/// element is calcualted as follows: +/// +/// `E = scale::Encode((K, N))` +/// +#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] +pub struct StorageVec { + len: Lazy, + elements: Mapping, +} + +impl Default for StorageVec +where + V: Packed, + KeyType: StorageKey, +{ + fn default() -> Self { + Self::new() + } +} + +impl StorageVec +where + V: Packed, + KeyType: StorageKey, +{ + /// Creates a new empty `Mapping`. + pub const fn new() -> Self { + Self { + len: Lazy::new(), + elements: Mapping::new(), + } + } + + /// Returns the number of elements in the vector, also referred to as its length. + #[inline] + pub fn len(&self) -> u32 { + self.len.get().unwrap_or(u32::MIN) + } + + /// Returns `true` if the vector contains no elements. + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Appends an element to the back of the vector. + /// + /// # Panics + /// + /// If the vector is at capacity (max. of 2 ^ 32 elements). + pub fn push(&mut self, value: &T) + where + T: Storable + scale::EncodeLike, + { + let slot = self.len(); + let _ = self.elements.insert(slot, value); + + self.len.set(&slot.checked_add(1).unwrap()); + } + + /// Pops the last element from the vector and returns it. + // + /// Returns `None` if the vector is empty. + pub fn pop(&mut self) -> Option { + let slot = self.len().checked_sub(1)?; + self.len.set(&slot); + + Some(self.elements.take(slot).unwrap()) + } +} + +impl ::core::fmt::Debug for StorageVec +where + V: Packed, + KeyType: StorageKey, +{ + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + f.debug_struct("StorageVec") + .field("key", &KeyType::KEY) + .field("len", &self.len) + .finish() + } +} From e05f98aaba08503951eed5392d06fc2f36913c2d Mon Sep 17 00:00:00 2001 From: Cyrill Leutwiler Date: Thu, 2 Nov 2023 13:53:04 +0100 Subject: [PATCH 02/15] basic tests Signed-off-by: Cyrill Leutwiler --- crates/storage/src/lazy/vec.rs | 93 ++++++++++++++++++++++++++++++---- 1 file changed, 83 insertions(+), 10 deletions(-) diff --git a/crates/storage/src/lazy/vec.rs b/crates/storage/src/lazy/vec.rs index 2da4fe18d5d..21062e63128 100644 --- a/crates/storage/src/lazy/vec.rs +++ b/crates/storage/src/lazy/vec.rs @@ -27,22 +27,23 @@ use crate::{Lazy, Mapping}; /// /// # Difference between `ink::prelude::vec::Vec` and [StorageVec] /// -/// Any `Vec` will exhibit [Packed] storage layout in its entirety; -/// where [StorageVec] stores each value under it's own storage key. +/// Any `Vec` will exhibit [Packed] storage layout; where +/// [StorageVec] stores each value under it's own storage key. /// -/// Hence, reading and writing a `Vec` from and to storage will load -/// and decode _all_ of its elements. +/// Hence, any read or write from or to a `Vec` on storage will load +/// or store _all_ of its elements. /// /// This can be undesirable: -/// The cost of read and write operations grows linearly, corresponding to -/// the number of elements in the vector (its length). Additionally, the -/// maximum capacity of the _whole_ vector is limited by the size of -/// the static buffer used during ABI encoding and decoding (default 16KiB). +/// The cost of reading or writing a _single_ element grows linearly +/// corresponding to the number of elements in the vector (its length). +/// Additionally, the maximum capacity of the _whole_ vector is limited by +/// the size of the static buffer used during ABI encoding and decoding +/// (default 16KiB). /// /// [StorageVec] on the other hand can theoretically grow to infinite size. -/// However, we currently limit the length at 2 ^ 32 elements: In practice, +/// However, we currently limit the length at 2 ^ 32 elements. In practice, /// even if the vector elements are single bytes, it'll allow to store -/// more than 4GB data in blockchain storage, or more than enoug. +/// more than 4GB data in blockchain storage, much more than enough. /// /// # Caveats /// @@ -151,3 +152,75 @@ where .finish() } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::traits::ManualKey; + + #[test] + fn default_values() { + ink_env::test::run_test::(|_| { + let mut array: StorageVec = StorageVec::new(); + + assert_eq!(array.pop(), None); + assert_eq!(array.len(), 0); + + Ok(()) + }) + .unwrap() + } + #[test] + fn push_and_pop_work() { + ink_env::test::run_test::(|_| { + let mut array: StorageVec = StorageVec::new(); + + let value = "test".to_string(); + array.push(&value); + assert_eq!(array.len(), 1); + assert_eq!(array.pop(), Some(value)); + + assert_eq!(array.len(), 0); + assert_eq!(array.pop(), None); + + Ok(()) + }) + .unwrap() + } + + #[test] + fn storage_keys_are_correct() { + ink_env::test::run_test::(|_| { + const BASE: u32 = 123; + let mut array: StorageVec> = StorageVec::new(); + + let expected_value = 127; + array.push(&expected_value); + + let actual_length = ink_env::get_contract_storage::<_, u32>(&BASE); + assert_eq!(actual_length, Ok(Some(1))); + + let actual_value = ink_env::get_contract_storage::<_, u8>(&(BASE, 0u32)); + assert_eq!(actual_value, Ok(Some(expected_value))); + + Ok(()) + }) + .unwrap() + } + + #[test] + fn push_and_pop_work_for_two_vecs_with_same_manual_key() { + ink_env::test::run_test::(|_| { + let expected_value = 255; + + let mut array: StorageVec> = StorageVec::new(); + array.push(&expected_value); + + let mut array2: StorageVec> = StorageVec::new(); + assert_eq!(array2.pop(), Some(expected_value)); + + Ok(()) + }) + .unwrap() + } +} From be69ee782d300ea776f4c90e5e0c5cf27ab2a4ee Mon Sep 17 00:00:00 2001 From: xermicus Date: Mon, 6 Nov 2023 16:38:54 +0100 Subject: [PATCH 03/15] cache the length Signed-off-by: xermicus --- crates/storage/src/lazy/vec.rs | 100 ++++++++++++++++++++++++++++----- 1 file changed, 86 insertions(+), 14 deletions(-) diff --git a/crates/storage/src/lazy/vec.rs b/crates/storage/src/lazy/vec.rs index 21062e63128..a6c5b508ee3 100644 --- a/crates/storage/src/lazy/vec.rs +++ b/crates/storage/src/lazy/vec.rs @@ -19,7 +19,8 @@ //! This vector doesn't actually "own" any data. //! Instead it is just a simple wrapper around the contract storage facilities. -use ink_storage_traits::{AutoKey, Packed, Storable, StorageKey}; +use ink_primitives::Key; +use ink_storage_traits::{AutoKey, Packed, Storable, StorableHint, StorageKey}; use crate::{Lazy, Mapping}; @@ -40,17 +41,14 @@ use crate::{Lazy, Mapping}; /// the size of the static buffer used during ABI encoding and decoding /// (default 16KiB). /// -/// [StorageVec] on the other hand can theoretically grow to infinite size. +/// [StorageVec] on the other hand allows to access each element individually. +/// Thus, it can theoretically grow to infinite size. /// However, we currently limit the length at 2 ^ 32 elements. In practice, /// even if the vector elements are single bytes, it'll allow to store -/// more than 4GB data in blockchain storage, much more than enough. +/// more than 4GB data in blockchain storage. /// /// # Caveats /// -/// Iterating over [StorageVec] elements will cause a storage read for -/// _each_ iteration (additionally a storage write in case of mutable -/// iterations with assignements). -/// /// The decision whether to use `Vec` or [StorageVec] can be seen as an /// optimization problem with several factors: /// * How large you expect the vector to grow @@ -77,6 +75,7 @@ use crate::{Lazy, Mapping}; #[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] pub struct StorageVec { len: Lazy, + len_cached: Option, elements: Mapping, } @@ -90,23 +89,89 @@ where } } +impl Storable for StorageVec +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 StorageVec +where + V: Packed, + Key: StorageKey, + InnerKey: StorageKey, +{ + type Type = StorageVec; + type PreferredKey = InnerKey; +} + +impl StorageKey for StorageVec +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 StorageVec + 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), + )) + } + } +}; + impl StorageVec where V: Packed, KeyType: StorageKey, { - /// Creates a new empty `Mapping`. + /// Creates a new empty `StorageVec`. pub const fn new() -> Self { Self { len: Lazy::new(), + len_cached: None, elements: Mapping::new(), } } /// Returns the number of elements in the vector, also referred to as its length. + /// + /// The length is cached; subsequent calls (without writing to the vector) won't + /// trigger additional storage reads. #[inline] pub fn len(&self) -> u32 { - self.len.get().unwrap_or(u32::MIN) + self.len_cached + .unwrap_or_else(|| self.len.get().unwrap_or(u32::MIN)) + } + + fn set_len(&mut self, new_len: u32) { + self.len.set(&new_len); + self.len_cached = Some(new_len); } /// Returns `true` if the vector contains no elements. @@ -118,25 +183,32 @@ where /// /// # Panics /// - /// If the vector is at capacity (max. of 2 ^ 32 elements). + /// * If the vector is at capacity (max. of 2 ^ 32 elements). + /// * If the value overgrows the static buffer size. + /// * If there was already a value at the current index. pub fn push(&mut self, value: &T) where T: Storable + scale::EncodeLike, { let slot = self.len(); - let _ = self.elements.insert(slot, value); + self.set_len(slot.checked_add(1).unwrap()); - self.len.set(&slot.checked_add(1).unwrap()); + assert!(self.elements.insert(slot, value).is_none()); } /// Pops the last element from the vector and returns it. // /// Returns `None` if the vector is empty. + /// + /// # Panics + /// + /// * If the value overgrows the static buffer size. + /// * If there is no value at the current index. pub fn pop(&mut self) -> Option { let slot = self.len().checked_sub(1)?; - self.len.set(&slot); - Some(self.elements.take(slot).unwrap()) + self.set_len(slot); + self.elements.take(slot).unwrap().into() } } From 65d9ea6b7ca6e734109b39598d7042abf440fab4 Mon Sep 17 00:00:00 2001 From: xermicus Date: Mon, 6 Nov 2023 17:05:44 +0100 Subject: [PATCH 04/15] get and set Signed-off-by: xermicus --- crates/storage/src/lazy/vec.rs | 70 +++++++++++++++++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/crates/storage/src/lazy/vec.rs b/crates/storage/src/lazy/vec.rs index a6c5b508ee3..e7bcb5b4d2c 100644 --- a/crates/storage/src/lazy/vec.rs +++ b/crates/storage/src/lazy/vec.rs @@ -21,12 +21,22 @@ use ink_primitives::Key; use ink_storage_traits::{AutoKey, Packed, Storable, StorableHint, StorageKey}; +use scale::EncodeLike; use crate::{Lazy, Mapping}; /// A vector of values (elements) directly on contract storage. + +/// # Important +/// +/// [StorageVec] requires its own pre-defined storage key where to store values. By +/// default, the is automatically calculated using [`AutoKey`](crate::traits::AutoKey) +/// during compilation. However, anyone can specify a storage key using +/// [`ManualKey`](crate::traits::ManualKey). Specifying the storage key can be helpful for +/// upgradeable contracts or you want to be resistant to future changes of storage key +/// calculation strategy. /// -/// # Difference between `ink::prelude::vec::Vec` and [StorageVec] +/// # Differences between `ink::prelude::vec::Vec` and [StorageVec] /// /// Any `Vec` will exhibit [Packed] storage layout; where /// [StorageVec] stores each value under it's own storage key. @@ -49,6 +59,10 @@ use crate::{Lazy, Mapping}; /// /// # Caveats /// +/// Iteration is not providided. [StorageVec] is expected to be used to +/// store a lot or large values where iterating through elements would be +/// rather inefficient anyways. +/// /// The decision whether to use `Vec` or [StorageVec] can be seen as an /// optimization problem with several factors: /// * How large you expect the vector to grow @@ -210,6 +224,30 @@ where self.set_len(slot); self.elements.take(slot).unwrap().into() } + + /// Access an element at given `index`. + /// + /// # Panics + /// + /// * If encoding the element exceeds the static buffer size. + pub fn get(&self, index: u32) -> Option { + self.elements.get(index) + } + + /// Set the `value` at given `index`. + /// + /// # Panics + /// + /// * If the index is out of bounds. + /// * If decoding the element exceeds the static buffer size. + pub fn set(&mut self, index: u32, value: &T) + where + T: Storable + EncodeLike, + { + assert!(index < self.len()); + + let _ = self.elements.insert(index, value); + } } impl ::core::fmt::Debug for StorageVec @@ -295,4 +333,34 @@ mod tests { }) .unwrap() } + + #[test] + fn set_and_get_work() { + ink_env::test::run_test::(|_| { + let mut array: StorageVec = StorageVec::new(); + + let value = "test".to_string(); + array.push(&value); + assert_eq!(array.get(0), Some(value)); + assert_eq!(array.len(), 1); + + let replaced_value = "foo".to_string(); + array.set(0, &replaced_value); + assert_eq!(array.get(0), Some(replaced_value)); + + Ok(()) + }) + .unwrap() + } + + #[test] + #[should_panic] + fn set_panics_on_oob() { + ink_env::test::run_test::(|_| { + StorageVec::::new().set(0, &0); + + Ok(()) + }) + .unwrap() + } } From 70c234c3f53a7dbf7ab1e984961a28d98fc1a22f Mon Sep 17 00:00:00 2001 From: xermicus Date: Wed, 8 Nov 2023 10:43:28 +0100 Subject: [PATCH 05/15] experiment Signed-off-by: xermicus --- crates/storage/src/lazy/cache_cell.rs | 100 ++ crates/storage/src/lazy/entry.rs | 171 ++++ crates/storage/src/lazy/lazy_imap.rs | 636 +++++++++++++ crates/storage/src/lazy/mod.rs | 27 +- crates/storage/src/lazy/storage_vec.rs | 1219 ++++++++++++++++++++++++ crates/storage/src/lazy/vec.rs | 2 +- 6 files changed, 2137 insertions(+), 18 deletions(-) create mode 100644 crates/storage/src/lazy/cache_cell.rs create mode 100644 crates/storage/src/lazy/entry.rs create mode 100644 crates/storage/src/lazy/lazy_imap.rs create mode 100644 crates/storage/src/lazy/storage_vec.rs diff --git a/crates/storage/src/lazy/cache_cell.rs b/crates/storage/src/lazy/cache_cell.rs new file mode 100644 index 00000000000..ddbb700f0c0 --- /dev/null +++ b/crates/storage/src/lazy/cache_cell.rs @@ -0,0 +1,100 @@ +// 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..7d41f7a8759 --- /dev/null +++ b/crates/storage/src/lazy/entry.rs @@ -0,0 +1,171 @@ +// 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; + +/// 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: 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)] +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: scale::Encode, +{ + #[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: scale::Decode, +{ + fn decode(input: &mut I) -> Result { + Ok(Self::new( + as scale::Decode>::decode(input)?, + EntryState::Preserved, + )) + } +} + +impl StorageEntry { + /// 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 + } +} diff --git a/crates/storage/src/lazy/lazy_imap.rs b/crates/storage/src/lazy/lazy_imap.rs new file mode 100644 index 00000000000..706f4a7205e --- /dev/null +++ b/crates/storage/src/lazy/lazy_imap.rs @@ -0,0 +1,636 @@ +// 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, entry::StorageEntry}; +use core::{fmt, fmt::Debug, ptr::NonNull}; +use ink_prelude::{boxed::Box, collections::BTreeMap}; +use ink_primitives::Key; +use ink_storage_traits::Packed; + +/// 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 offset key for the chunk of cells. + /// + /// If the lazy chunk has been initialized during contract initialization + /// the key will be `None` since there won't be a storage region associated + /// to the lazy chunk which prevents it from lazily loading elements. This, + /// however, is only checked at contract runtime. We might incorporate + /// compile-time checks for this particular use case later on. + key: Option, + /// 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>, +} + +struct DebugEntryMap<'a, V>(&'a CacheCell>); + +impl<'a, V> Debug for DebugEntryMap<'a, V> +where + V: 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: Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("LazyIndexMap") + .field("key", &self.key) + .field("cached_entries", &DebugEntryMap(&self.cached_entries)) + .finish() + } +} + +#[test] +fn debug_impl_works() { + let mut imap = >::new(); + // Empty imap. + assert_eq!( + format!("{:?}", &imap), + "LazyIndexMap { key: None, cached_entries: {} }", + ); + // Filled imap. + imap.put(0, Some(1)); + imap.put(42, Some(2)); + imap.put(999, None); + assert_eq!( + format!("{:?}", &imap), + "LazyIndexMap { \ + key: None, \ + cached_entries: {\ + 0: Entry { \ + value: Some(1), \ + state: Mutated \ + }, \ + 42: Entry { \ + value: Some(2), \ + state: Mutated \ + }, \ + 999: Entry { \ + value: None, \ + state: Mutated \ + }\ + } \ + }", + ); +} + +impl Default for LazyIndexMap { + 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 { + /// 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 { + key: None, + cached_entries: CacheCell::new(EntryMap::new()), + } + } + + /// Creates a new empty lazy map positioned at the given key. + /// + /// # Note + /// + /// This constructor is private and should never need to be called from + /// outside this module. It is used to construct a lazy index map from a + /// key that is only useful upon a contract call. Use [`LazyIndexMap::new`] + /// for construction during contract initialization. + fn lazy(key: Key) -> Self { + Self { + key: Some(key), + cached_entries: CacheCell::new(EntryMap::new()), + } + } + + /// Returns the offset key of the lazy map if any. + pub fn key(&self) -> Option<&Key> { + self.key.as_ref() + } + + /// 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, +{ + /// 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).expect("cannot clear in lazy state"); + + ink_env::clear_contract_storage(&root_key); + } +} + +impl LazyIndexMap +where + V: Packed, +{ + /// Returns an offset key for the given index. + pub fn key_at(&self, index: Index) -> Option<(Key, Index)> { + self.key.map(|key| (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 = self + .key_at(index) + .map(|key| ink_env::get_contract_storage(&key).unwrap()) + .unwrap_or(None); + 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, entry::StorageEntry}, + Index, LazyIndexMap, + }; + use ink_primitives::Key; + + /// 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() { + 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)), + ], + ); + } + + #[test] + fn get_works() { + 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); + } + + #[test] + fn put_works() { + 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)), + ], + ); + } + + #[test] + fn swap_works() { + 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)), + ], + ); + } +} diff --git a/crates/storage/src/lazy/mod.rs b/crates/storage/src/lazy/mod.rs index 9e8fa3a76a1..241e5c9d8c9 100644 --- a/crates/storage/src/lazy/mod.rs +++ b/crates/storage/src/lazy/mod.rs @@ -18,26 +18,23 @@ //! 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; +mod storage_vec; mod vec; +pub use self::lazy_imap::LazyIndexMap; #[doc(inline)] pub use self::mapping::Mapping; pub use self::vec::StorageVec; -use crate::traits::{ - AutoKey, - StorableHint, - StorageKey, -}; +use crate::traits::{AutoKey, StorableHint, StorageKey}; use core::marker::PhantomData; use ink_primitives::Key; use ink_storage_traits::Storable; -use scale::{ - Error, - Input, - Output, -}; +use scale::{Error, Input, Output}; /// A simple wrapper around a type to store it in a separate storage cell under its own /// storage key. If you want to update the value, first you need to @@ -160,7 +157,7 @@ where .expect("targets of less than 32bit pointer size are not supported; qed"); if encoded_length > ink_env::BUFFER_SIZE { - return Some(Err(ink_env::Error::BufferTooSmall)) + return Some(Err(ink_env::Error::BufferTooSmall)); } self.get().map(Ok) @@ -180,7 +177,7 @@ where /// Fails if `value` exceeds the static buffer size. pub fn try_set(&mut self, value: &V) -> ink_env::Result<()> { if value.encoded_size() > ink_env::BUFFER_SIZE { - return Err(ink_env::Error::BufferTooSmall) + return Err(ink_env::Error::BufferTooSmall); }; self.set(value); @@ -243,11 +240,7 @@ where #[cfg(feature = "std")] const _: () = { use crate::traits::StorageLayout; - use ink_metadata::layout::{ - Layout, - LayoutKey, - RootLayout, - }; + use ink_metadata::layout::{Layout, LayoutKey, RootLayout}; impl StorageLayout for Lazy where diff --git a/crates/storage/src/lazy/storage_vec.rs b/crates/storage/src/lazy/storage_vec.rs new file mode 100644 index 00000000000..e944ae7ccf1 --- /dev/null +++ b/crates/storage/src/lazy/storage_vec.rs @@ -0,0 +1,1219 @@ +// 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 ink_storage_traits::{AutoKey, Packed, StorageKey}; + +//pub use self::iter::{Iter, IterMut}; +use crate::lazy::{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: Lazy, + /// The synchronized cells to operate on the contract storage. + elems: LazyIndexMap, +} + +/// 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: Lazy::new(), + elems: LazyIndexMap::new(), + } + } + + /// Returns the number of elements in the vector, also referred to as its length. + pub fn len(&self) -> u32 { + self.len.get().unwrap_or(0) + } + + /// 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(); + ink_env::clear_contract_storage(&self.len.key()); + + if self.elems.key().is_none() { + // We won't clear any storage if we are in lazy state since there + // probably has not been any state written to storage, yet. + return; + } + 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.set(&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.set(&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.set(&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.set(&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.set(&0); + } +} + +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 + ) + } + } + } +} + +#[cfg(test)] +mod tests { + + use super::Vec as StorageVec; + use core::cmp::Ordering; + use ink_storage_traits::Packed; + + /* + #[test] + fn new_vec_works() { + // `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); + } + + #[test] + fn from_iterator_works() { + 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 + }); + } + + #[test] + fn from_empty_iterator_works() { + assert_eq!( + [].iter().copied().collect::>(), + StorageVec::new(), + ); + } + */ + + #[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() { + 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); + } + + #[test] + fn iter_nth_works() { + 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); + } + */ + + /* + #[test] + fn iter_next_back_works() { + 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); + } + */ + + /* + #[test] + fn iter_nth_back_works() { + 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); + } + + */ + /// 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_eq!(vec.len() as usize, slice.len()); + for i in 0..slice.len() { + assert_eq!(slice[i], *vec.get(i as u32).unwrap()); + } + //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::>() + let mut result: StorageVec = Default::default(); + result.clear(); + + for element in slice.iter() { + result.push(*element); + } + + result + } + + #[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 spread_layout_push_pull_works() -> ink_env::Result<()> { + ink_env::test::run_test::(|_| { + let vec1 = vec_from_slice(&[b'a', b'b', b'c', b'd']); + let root_key = Key::from([0x42; 32]); + SpreadLayout::push_spread(&vec1, &mut KeyPtr::from(root_key)); + // Load the pushed storage vector into another instance and check that + // both instances are equal: + let vec2 = as SpreadLayout>::pull_spread(&mut KeyPtr::from( + root_key, + )); + assert_eq!(vec1, vec2); + Ok(()) + }) + } + */ + + /* + #[test] + #[should_panic(expected = "encountered empty storage cell")] + fn spread_layout_clear_works() { + ink_env::test::run_test::(|_| { + let vec1 = vec_from_slice(&[b'a', b'b', b'c', b'd']); + let root_key = Key::from([0x42; 32]); + SpreadLayout::push_spread(&vec1, &mut KeyPtr::from(root_key)); + // It has already been asserted that a valid instance can be pulled + // from contract storage after a push to the same storage region. + // + // Now clear the associated storage from `vec1` and check whether + // loading another instance from this storage will panic since the + // vector's length property cannot read a value: + SpreadLayout::clear_spread(&vec1, &mut KeyPtr::from(root_key)); + let _ = as SpreadLayout>::pull_spread(&mut KeyPtr::from( + root_key, + )); + 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() { + let mut vec = vec_from_slice(&[b'a']); + let res = vec.set(1, b'x'); + assert_eq!(res, Err(IndexOutOfBounds)); + } + */ + + #[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 storage_is_cleared_completely_after_pull_lazy() { + ink_env::test::run_test::(|_| { + // given + let root_key = Key::from([0x42; 32]); + let mut lazy_vec: Lazy> = Lazy::new(StorageVec::new()); + lazy_vec.push(13u32); + lazy_vec.push(13u32); + SpreadLayout::push_spread(&lazy_vec, &mut KeyPtr::from(root_key)); + let pulled_vec = > as SpreadLayout>::pull_spread( + &mut KeyPtr::from(root_key), + ); + + // when + SpreadLayout::clear_spread(&pulled_vec, &mut KeyPtr::from(root_key)); + + // then + 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); + let _ = *> as SpreadLayout>::pull_spread(&mut KeyPtr::from( + root_key, + )); + + Ok(()) + }) + .unwrap() + } + */ + + #[test] + //#[should_panic(expected = "encountered empty storage cell")] + fn drop_works() { + ink_env::test::run_test::(|_| { + //let root_key = Key::from([0x42; 32]); + + // if the setup panics it should not cause the test to pass + let setup_result = std::panic::catch_unwind(|| { + let vec = vec_from_slice(&[b'a', b'b', b'c', b'd']); + //SpreadLayout::push_spread(&vec, &mut KeyPtr::from(root_key)); + //let _ = as SpreadLayout>::pull_spread(&mut KeyPtr::from( + // root_key, + //)); + // vec is dropped which should clear the cells + }); + 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); + + //let _ = as SpreadLayout>::pull_spread(&mut KeyPtr::from( + // root_key, + //)); + Ok(()) + }) + .unwrap() + } +} diff --git a/crates/storage/src/lazy/vec.rs b/crates/storage/src/lazy/vec.rs index e7bcb5b4d2c..8bbd41e6f7a 100644 --- a/crates/storage/src/lazy/vec.rs +++ b/crates/storage/src/lazy/vec.rs @@ -273,8 +273,8 @@ mod tests { ink_env::test::run_test::(|_| { let mut array: StorageVec = StorageVec::new(); + assert!(array.is_empty()); assert_eq!(array.pop(), None); - assert_eq!(array.len(), 0); Ok(()) }) From 834756ff9e422e49b89ca742d09d450aa65c3699 Mon Sep 17 00:00:00 2001 From: xermicus Date: Thu, 9 Nov 2023 10:57:22 +0100 Subject: [PATCH 06/15] vector length Signed-off-by: xermicus --- crates/storage/src/lazy/lazy_imap.rs | 529 +++++++++++++------------ crates/storage/src/lazy/storage_vec.rs | 47 ++- 2 files changed, 295 insertions(+), 281 deletions(-) diff --git a/crates/storage/src/lazy/lazy_imap.rs b/crates/storage/src/lazy/lazy_imap.rs index 706f4a7205e..6a145b57a48 100644 --- a/crates/storage/src/lazy/lazy_imap.rs +++ b/crates/storage/src/lazy/lazy_imap.rs @@ -13,10 +13,10 @@ // limitations under the License. use super::{cache_cell::CacheCell, entry::EntryState, entry::StorageEntry}; -use core::{fmt, fmt::Debug, ptr::NonNull}; +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; +use ink_storage_traits::{Packed, StorageKey}; /// The index type used in the lazy storage chunk. pub type Index = u32; @@ -30,19 +30,13 @@ pub type Index = u32; /// chunk of storage cells. /// /// A chunk of storage cells is a contiguous range of `2^32` storage cells. -pub struct LazyIndexMap { - /// The offset key for the chunk of cells. - /// - /// If the lazy chunk has been initialized during contract initialization - /// the key will be `None` since there won't be a storage region associated - /// to the lazy chunk which prevents it from lazily loading elements. This, - /// however, is only checked at contract runtime. We might incorporate - /// compile-time checks for this particular use case later on. - key: Option, +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>(&'a CacheCell>); @@ -56,13 +50,14 @@ where } } -impl Debug for LazyIndexMap +impl Debug for LazyIndexMap where - V: Debug, + V: Packed + Debug, + KeyType: StorageKey, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("LazyIndexMap") - .field("key", &self.key) + .field("key", &KeyType::KEY) .field("cached_entries", &DebugEntryMap(&self.cached_entries)) .finish() } @@ -70,20 +65,21 @@ where #[test] fn debug_impl_works() { - let mut imap = >::new(); - // Empty imap. - assert_eq!( - format!("{:?}", &imap), - "LazyIndexMap { key: None, cached_entries: {} }", - ); - // Filled imap. - imap.put(0, Some(1)); - imap.put(42, Some(2)); - imap.put(999, None); - assert_eq!( - format!("{:?}", &imap), - "LazyIndexMap { \ - key: None, \ + 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), \ @@ -99,10 +95,17 @@ fn debug_impl_works() { }\ } \ }", - ); + ); + Ok(()) + }) + .unwrap() } -impl Default for LazyIndexMap { +impl Default for LazyIndexMap +where + V: Packed, + KeyType: StorageKey, +{ fn default() -> Self { Self::new() } @@ -117,7 +120,11 @@ impl Default for LazyIndexMap { /// [`LazyIndexMap::get`]. pub type EntryMap = BTreeMap>>; -impl LazyIndexMap { +impl LazyIndexMap +where + V: Packed, + KeyType: StorageKey, +{ /// Creates a new empty lazy map. /// /// # Note @@ -126,31 +133,11 @@ impl LazyIndexMap { /// All operations that directly or indirectly load from storage will panic. pub fn new() -> Self { Self { - key: None, - cached_entries: CacheCell::new(EntryMap::new()), - } - } - - /// Creates a new empty lazy map positioned at the given key. - /// - /// # Note - /// - /// This constructor is private and should never need to be called from - /// outside this module. It is used to construct a lazy index map from a - /// key that is only useful upon a contract call. Use [`LazyIndexMap::new`] - /// for construction during contract initialization. - fn lazy(key: Key) -> Self { - Self { - key: Some(key), cached_entries: CacheCell::new(EntryMap::new()), + _marker: Default::default(), } } - /// Returns the offset key of the lazy map if any. - pub fn key(&self) -> Option<&Key> { - self.key.as_ref() - } - /// Returns a shared reference to the underlying entries. fn entries(&self) -> &EntryMap { self.cached_entries.as_inner() @@ -213,9 +200,10 @@ impl LazyIndexMap { // } //}; -impl LazyIndexMap +impl LazyIndexMap where V: Packed, + KeyType: StorageKey, { /// Clears the underlying storage of the entry at the given index. /// @@ -228,19 +216,20 @@ where /// 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).expect("cannot clear in lazy state"); + let root_key = self.key_at(index); ink_env::clear_contract_storage(&root_key); } } -impl LazyIndexMap +impl LazyIndexMap where V: Packed, + KeyType: StorageKey, { /// Returns an offset key for the given index. - pub fn key_at(&self, index: Index) -> Option<(Key, Index)> { - self.key.map(|key| (key, index)) + pub fn key_at(&self, index: Index) -> (Key, Index) { + (KeyType::KEY, index) } /// Lazily loads the value at the given index. @@ -279,10 +268,7 @@ where NonNull::from(&mut **occupied.into_mut()) } BTreeMapEntry::Vacant(vacant) => { - let value = self - .key_at(index) - .map(|key| ink_env::get_contract_storage(&key).unwrap()) - .unwrap_or(None); + let value = ink_env::get_contract_storage(&self.key_at(index)).unwrap(); NonNull::from( &mut **vacant.insert(Box::new(StorageEntry::new( value, @@ -391,11 +377,11 @@ mod tests { super::{entry::EntryState, entry::StorageEntry}, Index, LazyIndexMap, }; - use ink_primitives::Key; + 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, + fn assert_cached_entries( + imap: &LazyIndexMap, expected: &[(Index, StorageEntry)], ) { assert_eq!(imap.entries().len(), expected.len()); @@ -409,19 +395,19 @@ mod tests { } } - #[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()); - } + //#[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; @@ -429,208 +415,223 @@ mod tests { // 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 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() { - 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)), - ], - ); + 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() { - 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); + 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() { - 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)), - ], - ); + 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() { - 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)), + 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(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)), + (3, StorageEntry::new(None, EntryState::Preserved)), (4, StorageEntry::new(None, EntryState::Preserved)), - (5, StorageEntry::new(None, EntryState::Preserved)), - (6, StorageEntry::new(Some(b'B'), EntryState::Mutated)), - ], - ); + ]; + // 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/storage_vec.rs b/crates/storage/src/lazy/storage_vec.rs index e944ae7ccf1..c223cce73cb 100644 --- a/crates/storage/src/lazy/storage_vec.rs +++ b/crates/storage/src/lazy/storage_vec.rs @@ -27,6 +27,7 @@ //#[cfg(all(test, feature = "ink-fuzz-tests"))] //mod fuzz_tests; +use ink_primitives::Key; use ink_storage_traits::{AutoKey, Packed, StorageKey}; //pub use self::iter::{Iter, IterMut}; @@ -52,9 +53,9 @@ where T: Packed, { /// The length of the vector. - len: Lazy, + len: Option, /// The synchronized cells to operate on the contract storage. - elems: LazyIndexMap, + elems: LazyIndexMap, } /// The index is out of the bounds of this vector. @@ -79,14 +80,18 @@ where /// Creates a new empty storage vector. pub fn new() -> Self { Self { - len: Lazy::new(), + 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.get().unwrap_or(0) + 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. @@ -112,13 +117,8 @@ where /// trait implementation. fn clear_cells(&self) { let len = self.len(); - ink_env::clear_contract_storage(&self.len.key()); + let _ = ink_env::clear_contract_storage(&KeyType::KEY); - if self.elems.key().is_none() { - // We won't clear any storage if we are in lazy state since there - // probably has not been any state written to storage, yet. - return; - } for index in 0..len { self.elems.clear_packed_at(index); } @@ -198,7 +198,7 @@ where last_index < core::u32::MAX, "cannot push more elements into the storage vector" ); - self.len.set(&last_index.checked_add(1).unwrap()); + self.len = Some(last_index.checked_add(1).unwrap()); self.elems.put(last_index, Some(value)); } @@ -395,7 +395,7 @@ where return None; } let last_index = self.len() - 1; - self.len.set(&last_index); + self.len = Some(last_index); self.elems.put_get(last_index, None) } @@ -412,7 +412,7 @@ where return None; } let last_index = self.len() - 1; - self.len.set(&last_index); + self.len = Some(last_index); self.elems.put(last_index, None); Some(()) } @@ -489,7 +489,7 @@ where let last_index = self.len() - 1; let last = self.elems.put_get(last_index, None); self.elems.put(n, last); - self.len.set(&last_index); + self.len = Some(last_index); Some(()) } @@ -521,7 +521,17 @@ where for index in 0..self.len() { self.elems.put(index, None); } - self.len.set(&0); + self.len = Some(0); + } + + pub fn write(&self) { + if self.is_empty() { + return; + } + + let len = self.len(); + ink_env::set_contract_storage(&KeyType::KEY, &len); + // todo } } @@ -578,9 +588,12 @@ where #[cfg(test)] mod tests { + use crate::Lazy; + use super::Vec as StorageVec; use core::cmp::Ordering; - use ink_storage_traits::Packed; + use ink_primitives::AccountId; + use ink_storage_traits::{ManualKey, Packed}; /* #[test] @@ -1193,7 +1206,7 @@ mod tests { // if the setup panics it should not cause the test to pass let setup_result = std::panic::catch_unwind(|| { - let vec = vec_from_slice(&[b'a', b'b', b'c', b'd']); + vec_from_slice(&[b'a', b'b', b'c', b'd']).write(); //SpreadLayout::push_spread(&vec, &mut KeyPtr::from(root_key)); //let _ = as SpreadLayout>::pull_spread(&mut KeyPtr::from( // root_key, From efcc01e6217dfb9e91e7061f578f1ae96df749e8 Mon Sep 17 00:00:00 2001 From: Cyrill Leutwiler Date: Thu, 9 Nov 2023 14:46:17 +0100 Subject: [PATCH 07/15] iter for storage vec Signed-off-by: Cyrill Leutwiler --- crates/storage/src/lazy/storage_vec.rs | 631 ++++++++++++++++++------- crates/storage/src/lib.rs | 22 +- 2 files changed, 481 insertions(+), 172 deletions(-) diff --git a/crates/storage/src/lazy/storage_vec.rs b/crates/storage/src/lazy/storage_vec.rs index c223cce73cb..16fd2b26dbd 100644 --- a/crates/storage/src/lazy/storage_vec.rs +++ b/crates/storage/src/lazy/storage_vec.rs @@ -27,11 +27,15 @@ //#[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, StorageKey}; //pub use self::iter::{Iter, IterMut}; -use crate::lazy::{Lazy, LazyIndexMap}; +use crate::{ + extend_lifetime, + lazy::{Lazy, LazyIndexMap}, +}; /// A contiguous growable array type, written `Vec` but pronounced "vector". /// @@ -137,9 +141,9 @@ where /// 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) - ///} + pub fn iter(&self) -> Iter { + Iter::new(self) + } /// Returns an iterator yielding exclusive references to all elements of the vector. /// @@ -148,9 +152,9 @@ where /// 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) - ///} + 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 { @@ -531,6 +535,7 @@ where let len = self.len(); ink_env::set_contract_storage(&KeyType::KEY, &len); + // todo } } @@ -585,55 +590,349 @@ where } } +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::Lazy; + use crate::{lazy::storage_vec::IndexOutOfBounds, Lazy}; use super::Vec as StorageVec; use core::cmp::Ordering; use ink_primitives::AccountId; use ink_storage_traits::{ManualKey, Packed}; - /* - #[test] - fn new_vec_works() { - // `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); - } - - #[test] - fn from_iterator_works() { - 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 - }); - } - - #[test] - fn from_empty_iterator_works() { - assert_eq!( - [].iter().copied().collect::>(), - StorageVec::new(), - ); - } - */ + #[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() { @@ -758,141 +1057,139 @@ mod tests { .unwrap() } - /* #[test] fn iter_next_works() { - 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); + 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() { - 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); + 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() { - 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); + 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() { - 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); + 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_eq!(vec.len() as usize, slice.len()); - for i in 0..slice.len() { - assert_eq!(slice[i], *vec.get(i as u32).unwrap()); - } - //assert!(vec.iter().zip(slice.iter()).all(|(lhs, rhs)| *lhs == *rhs)) + 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::>() - let mut result: StorageVec = Default::default(); - result.clear(); - - for element in slice.iter() { - result.push(*element); - } - - result + slice.iter().copied().collect::>() } #[test] @@ -1009,9 +1306,7 @@ mod tests { Ok(()) }) } - */ - /* #[test] #[should_panic(expected = "encountered empty storage cell")] fn spread_layout_clear_works() { @@ -1035,7 +1330,6 @@ mod tests { } */ - /* #[test] fn set_works() { ink_env::test::run_test::(|_| { @@ -1047,16 +1341,17 @@ mod tests { }) .unwrap() } - */ - /* #[test] fn set_fails_when_index_oob() { - let mut vec = vec_from_slice(&[b'a']); - let res = vec.set(1, b'x'); - assert_eq!(res, Err(IndexOutOfBounds)); + 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() { diff --git a/crates/storage/src/lib.rs b/crates/storage/src/lib.rs index 266a31c2f26..2d362e27e55 100644 --- a/crates/storage/src/lib.rs +++ b/crates/storage/src/lib.rs @@ -52,7 +52,21 @@ pub use ink_storage_traits as traits; pub(crate) mod lazy; #[doc(inline)] -pub use self::lazy::{ - Lazy, - Mapping, -}; +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) +} From c928390dd8ba0847c43e5c11bc26d28062a86f8e Mon Sep 17 00:00:00 2001 From: Cyrill Leutwiler Date: Thu, 9 Nov 2023 16:06:48 +0100 Subject: [PATCH 08/15] implement writing back to contract storage Signed-off-by: Cyrill Leutwiler --- crates/storage/src/lazy/entry.rs | 25 ++++++++++++++++++++----- crates/storage/src/lazy/lazy_imap.rs | 10 ++++++++-- crates/storage/src/lazy/storage_vec.rs | 20 +++++--------------- 3 files changed, 33 insertions(+), 22 deletions(-) diff --git a/crates/storage/src/lazy/entry.rs b/crates/storage/src/lazy/entry.rs index 7d41f7a8759..16e9528d97e 100644 --- a/crates/storage/src/lazy/entry.rs +++ b/crates/storage/src/lazy/entry.rs @@ -14,10 +14,11 @@ 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 { +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 @@ -29,7 +30,7 @@ pub struct StorageEntry { impl Debug for StorageEntry where - T: Debug, + T: Packed + Debug, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Entry") @@ -79,7 +80,7 @@ impl EntryState { impl scale::Encode for StorageEntry where - T: scale::Encode, + T: Packed, { #[inline] fn size_hint(&self) -> usize { @@ -104,7 +105,7 @@ where impl scale::Decode for StorageEntry where - T: scale::Decode, + T: Packed, { fn decode(input: &mut I) -> Result { Ok(Self::new( @@ -114,7 +115,10 @@ where } } -impl StorageEntry { +impl StorageEntry +where + T: Packed, +{ /// Creates a new entry with the value and state. pub fn new(value: Option, state: EntryState) -> Self { Self { @@ -168,4 +172,15 @@ impl StorageEntry { } 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 index 6a145b57a48..da4b97bfd33 100644 --- a/crates/storage/src/lazy/lazy_imap.rs +++ b/crates/storage/src/lazy/lazy_imap.rs @@ -39,11 +39,11 @@ pub struct LazyIndexMap { _marker: PhantomData (V, KeyType)>, } -struct DebugEntryMap<'a, V>(&'a CacheCell>); +struct DebugEntryMap<'a, V: Packed>(&'a CacheCell>); impl<'a, V> Debug for DebugEntryMap<'a, V> where - V: Debug, + V: Packed + Debug, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_map().entries(self.0.as_inner().iter()).finish() @@ -220,6 +220,12 @@ where 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 diff --git a/crates/storage/src/lazy/storage_vec.rs b/crates/storage/src/lazy/storage_vec.rs index 16fd2b26dbd..29ea8f3be97 100644 --- a/crates/storage/src/lazy/storage_vec.rs +++ b/crates/storage/src/lazy/storage_vec.rs @@ -28,14 +28,10 @@ //mod fuzz_tests; use core::iter::{Extend, FromIterator}; -use ink_primitives::Key; use ink_storage_traits::{AutoKey, Packed, StorageKey}; //pub use self::iter::{Iter, IterMut}; -use crate::{ - extend_lifetime, - lazy::{Lazy, LazyIndexMap}, -}; +use crate::{extend_lifetime, lazy::LazyIndexMap}; /// A contiguous growable array type, written `Vec` but pronounced "vector". /// @@ -536,7 +532,7 @@ where let len = self.len(); ink_env::set_contract_storage(&KeyType::KEY, &len); - // todo + self.elems.write(); } } @@ -877,12 +873,11 @@ where #[cfg(test)] mod tests { - use crate::{lazy::storage_vec::IndexOutOfBounds, Lazy}; + use crate::lazy::storage_vec::IndexOutOfBounds; use super::Vec as StorageVec; use core::cmp::Ordering; - use ink_primitives::AccountId; - use ink_storage_traits::{ManualKey, Packed}; + use ink_storage_traits::Packed; #[test] fn new_vec_works() { @@ -1497,15 +1492,10 @@ mod tests { //#[should_panic(expected = "encountered empty storage cell")] fn drop_works() { ink_env::test::run_test::(|_| { - //let root_key = Key::from([0x42; 32]); - // 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(); - //SpreadLayout::push_spread(&vec, &mut KeyPtr::from(root_key)); - //let _ = as SpreadLayout>::pull_spread(&mut KeyPtr::from( - // root_key, - //)); + // vec is dropped which should clear the cells }); assert!(setup_result.is_ok(), "setup should not panic"); From 510268f139809b06b8ed1e5a3df369b2c6c23dd0 Mon Sep 17 00:00:00 2001 From: Cyrill Leutwiler Date: Fri, 10 Nov 2023 11:50:59 +0100 Subject: [PATCH 09/15] basic test for write Signed-off-by: Cyrill Leutwiler --- crates/storage/src/lazy/storage_vec.rs | 41 +++++++++++++------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/crates/storage/src/lazy/storage_vec.rs b/crates/storage/src/lazy/storage_vec.rs index 29ea8f3be97..a5b5a52e79e 100644 --- a/crates/storage/src/lazy/storage_vec.rs +++ b/crates/storage/src/lazy/storage_vec.rs @@ -529,9 +529,7 @@ where return; } - let len = self.len(); - ink_env::set_contract_storage(&KeyType::KEY, &len); - + ink_env::set_contract_storage(&KeyType::KEY, &self.len()); self.elems.write(); } } @@ -877,7 +875,7 @@ mod tests { use super::Vec as StorageVec; use core::cmp::Ordering; - use ink_storage_traits::Packed; + use ink_storage_traits::{ManualKey, Packed}; #[test] fn new_vec_works() { @@ -1286,22 +1284,6 @@ mod tests { } /* - #[test] - fn spread_layout_push_pull_works() -> ink_env::Result<()> { - ink_env::test::run_test::(|_| { - let vec1 = vec_from_slice(&[b'a', b'b', b'c', b'd']); - let root_key = Key::from([0x42; 32]); - SpreadLayout::push_spread(&vec1, &mut KeyPtr::from(root_key)); - // Load the pushed storage vector into another instance and check that - // both instances are equal: - let vec2 = as SpreadLayout>::pull_spread(&mut KeyPtr::from( - root_key, - )); - assert_eq!(vec1, vec2); - Ok(()) - }) - } - #[test] #[should_panic(expected = "encountered empty storage cell")] fn spread_layout_clear_works() { @@ -1514,4 +1496,23 @@ mod tests { }) .unwrap() } + + #[test] + fn write_work() { + ink_env::test::run_test::(|_| { + let 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() + } } From 411c11fbca0a6cac1296e1ce6a000d6f427c0783 Mon Sep 17 00:00:00 2001 From: Cyrill Leutwiler Date: Fri, 10 Nov 2023 18:22:31 +0100 Subject: [PATCH 10/15] poc Signed-off-by: Cyrill Leutwiler --- crates/ink/src/lib.rs | 1 + crates/storage/src/lazy/entry.rs | 1 + crates/storage/src/lazy/lazy_imap.rs | 32 ++-- crates/storage/src/lazy/mod.rs | 3 +- crates/storage/src/lazy/storage_vec.rs | 228 +++++++++++++++++-------- crates/storage/src/lazy/vec.rs | 1 - crates/storage/src/lib.rs | 1 + 7 files changed, 181 insertions(+), 86 deletions(-) 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/lazy/entry.rs b/crates/storage/src/lazy/entry.rs index 16e9528d97e..48eefd13851 100644 --- a/crates/storage/src/lazy/entry.rs +++ b/crates/storage/src/lazy/entry.rs @@ -56,6 +56,7 @@ fn debug_impl_works() { /// 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, diff --git a/crates/storage/src/lazy/lazy_imap.rs b/crates/storage/src/lazy/lazy_imap.rs index da4b97bfd33..97660e12662 100644 --- a/crates/storage/src/lazy/lazy_imap.rs +++ b/crates/storage/src/lazy/lazy_imap.rs @@ -12,7 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -use super::{cache_cell::CacheCell, entry::EntryState, entry::StorageEntry}; +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; @@ -153,8 +156,8 @@ where /// # 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. + /// - Prefer this method over [`LazyIndexMap::put_get`] if you are not interested in + /// the old value of the same cell index. /// /// # Panics /// @@ -177,7 +180,7 @@ where } //#[cfg(feature = "std")] -//const _: () = { +// const _: () = { // use crate::traits::StorageLayout; // use ink_metadata::layout::{ArrayLayout, CellLayout, Layout, LayoutKey}; // use scale_info::TypeInfo; @@ -298,9 +301,9 @@ where /// - 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. + // - 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() } } @@ -331,8 +334,8 @@ where /// /// # Note /// - /// - Use [`LazyIndexMap::put_get`]`(None)` in order to remove an element - /// and retrieve the old element back. + /// - Use [`LazyIndexMap::put_get`]`(None)` in order to remove an element and retrieve + /// the old element back. /// /// # Panics /// @@ -380,12 +383,13 @@ where #[cfg(test)] mod tests { use super::{ - super::{entry::EntryState, entry::StorageEntry}, + 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. + /// Asserts that the cached entries of the given `imap` is equal to the `expected` + /// slice. fn assert_cached_entries( imap: &LazyIndexMap, expected: &[(Index, StorageEntry)], @@ -402,7 +406,7 @@ mod tests { } //#[test] - //fn new_works() { + // fn new_works() { // let imap = >::new(); // // Key must be none. // assert_eq!(imap.key(), None); @@ -415,14 +419,14 @@ mod tests { // assert_eq!(imap.entries(), default_imap.entries()); //} - //fn add_key(key: &Key, offset: u32) -> Key { + // fn add_key(key: &Key, offset: u32) -> Key { // let mut result = *key; // result += offset; // result //} //#[test] - //fn lazy_works() { + // fn lazy_works() { // let key = Key::default(); // let imap = >::lazy(key); // // Key must be none. diff --git a/crates/storage/src/lazy/mod.rs b/crates/storage/src/lazy/mod.rs index 241e5c9d8c9..7f4da8e14fe 100644 --- a/crates/storage/src/lazy/mod.rs +++ b/crates/storage/src/lazy/mod.rs @@ -25,10 +25,9 @@ mod mapping; mod storage_vec; mod vec; -pub use self::lazy_imap::LazyIndexMap; #[doc(inline)] pub use self::mapping::Mapping; -pub use self::vec::StorageVec; +pub use self::{lazy_imap::LazyIndexMap, storage_vec::Vec, vec::StorageVec}; use crate::traits::{AutoKey, StorableHint, StorageKey}; use core::marker::PhantomData; diff --git a/crates/storage/src/lazy/storage_vec.rs b/crates/storage/src/lazy/storage_vec.rs index a5b5a52e79e..d12a1589491 100644 --- a/crates/storage/src/lazy/storage_vec.rs +++ b/crates/storage/src/lazy/storage_vec.rs @@ -17,20 +17,21 @@ //! 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; +// mod impls; +// mod iter; +// mod storage; // //#[cfg(test)] -//mod tests; +// mod tests; // //#[cfg(all(test, feature = "ink-fuzz-tests"))] -//mod fuzz_tests; +// mod fuzz_tests; use core::iter::{Extend, FromIterator}; -use ink_storage_traits::{AutoKey, Packed, StorageKey}; +use ink_primitives::Key; +use ink_storage_traits::{AutoKey, Packed, Storable, StorableHint, StorageKey}; -//pub use self::iter::{Iter, IterMut}; +// pub use self::iter::{Iter, IterMut}; use crate::{extend_lifetime, lazy::LazyIndexMap}; /// A contiguous growable array type, written `Vec` but pronounced "vector". @@ -58,6 +59,75 @@ where 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), + )) + } + } +}; + /// The index is out of the bounds of this vector. #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct IndexOutOfBounds; @@ -524,11 +594,10 @@ where self.len = Some(0); } + /// Write the cached in-memory data back to storage. + /// + /// This does only write elements that were modified. pub fn write(&self) { - if self.is_empty() { - return; - } - ink_env::set_contract_storage(&KeyType::KEY, &self.len()); self.elems.write(); } @@ -1283,29 +1352,27 @@ mod tests { .unwrap(); } - /* - #[test] - #[should_panic(expected = "encountered empty storage cell")] - fn spread_layout_clear_works() { - ink_env::test::run_test::(|_| { - let vec1 = vec_from_slice(&[b'a', b'b', b'c', b'd']); - let root_key = Key::from([0x42; 32]); - SpreadLayout::push_spread(&vec1, &mut KeyPtr::from(root_key)); - // It has already been asserted that a valid instance can be pulled - // from contract storage after a push to the same storage region. - // - // Now clear the associated storage from `vec1` and check whether - // loading another instance from this storage will panic since the - // vector's length property cannot read a value: - SpreadLayout::clear_spread(&vec1, &mut KeyPtr::from(root_key)); - let _ = as SpreadLayout>::pull_spread(&mut KeyPtr::from( - root_key, - )); - Ok(()) - }) - .unwrap() - } - */ + // #[test] + // #[should_panic(expected = "encountered empty storage cell")] + // fn spread_layout_clear_works() { + // ink_env::test::run_test::(|_| { + // let vec1 = vec_from_slice(&[b'a', b'b', b'c', b'd']); + // let root_key = Key::from([0x42; 32]); + // SpreadLayout::push_spread(&vec1, &mut KeyPtr::from(root_key)); + // It has already been asserted that a valid instance can be pulled + // from contract storage after a push to the same storage region. + // + // Now clear the associated storage from `vec1` and check whether + // loading another instance from this storage will panic since the + // vector's length property cannot read a value: + // SpreadLayout::clear_spread(&vec1, &mut KeyPtr::from(root_key)); + // let _ = as SpreadLayout>::pull_spread(&mut KeyPtr::from( + // root_key, + // )); + // Ok(()) + // }) + // .unwrap() + // } #[test] fn set_works() { @@ -1435,40 +1502,38 @@ mod tests { .unwrap() } - /* - #[test] - #[should_panic(expected = "encountered empty storage cell")] - fn storage_is_cleared_completely_after_pull_lazy() { - ink_env::test::run_test::(|_| { - // given - let root_key = Key::from([0x42; 32]); - let mut lazy_vec: Lazy> = Lazy::new(StorageVec::new()); - lazy_vec.push(13u32); - lazy_vec.push(13u32); - SpreadLayout::push_spread(&lazy_vec, &mut KeyPtr::from(root_key)); - let pulled_vec = > as SpreadLayout>::pull_spread( - &mut KeyPtr::from(root_key), - ); - - // when - SpreadLayout::clear_spread(&pulled_vec, &mut KeyPtr::from(root_key)); - - // then - 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); - let _ = *> as SpreadLayout>::pull_spread(&mut KeyPtr::from( - root_key, - )); - - Ok(()) - }) - .unwrap() - } - */ + // #[test] + // #[should_panic(expected = "encountered empty storage cell")] + // fn storage_is_cleared_completely_after_pull_lazy() { + // ink_env::test::run_test::(|_| { + // given + // let root_key = Key::from([0x42; 32]); + // let mut lazy_vec: Lazy> = Lazy::new(StorageVec::new()); + // lazy_vec.push(13u32); + // lazy_vec.push(13u32); + // SpreadLayout::push_spread(&lazy_vec, &mut KeyPtr::from(root_key)); + // let pulled_vec = > as SpreadLayout>::pull_spread( + // &mut KeyPtr::from(root_key), + // ); + // + // when + // SpreadLayout::clear_spread(&pulled_vec, &mut KeyPtr::from(root_key)); + // + // then + // 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); + // let _ = *> as SpreadLayout>::pull_spread(&mut KeyPtr::from( + // root_key, + // )); + // + // Ok(()) + // }) + // .unwrap() + // } #[test] //#[should_panic(expected = "encountered empty storage cell")] @@ -1489,7 +1554,7 @@ mod tests { .expect("used cells must be returned"); assert_eq!(used_cells, 0); - //let _ = as SpreadLayout>::pull_spread(&mut KeyPtr::from( + // let _ = as SpreadLayout>::pull_spread(&mut KeyPtr::from( // root_key, //)); Ok(()) @@ -1515,4 +1580,29 @@ mod tests { }) .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/vec.rs b/crates/storage/src/lazy/vec.rs index 8bbd41e6f7a..7ca754adaac 100644 --- a/crates/storage/src/lazy/vec.rs +++ b/crates/storage/src/lazy/vec.rs @@ -85,7 +85,6 @@ use crate::{Lazy, Mapping}; /// element is calcualted as follows: /// /// `E = scale::Encode((K, N))` -/// #[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] pub struct StorageVec { len: Lazy, diff --git a/crates/storage/src/lib.rs b/crates/storage/src/lib.rs index 2d362e27e55..4189c9201e1 100644 --- a/crates/storage/src/lib.rs +++ b/crates/storage/src/lib.rs @@ -51,6 +51,7 @@ pub use ink_storage_traits as traits; #[allow(dead_code)] pub(crate) mod lazy; +pub use self::lazy::Vec as StorageVec; #[doc(inline)] pub use self::lazy::{Lazy, Mapping}; From 77c1715512c2163ebdefb0fb09f6be0c13e5fdfb Mon Sep 17 00:00:00 2001 From: Cyrill Leutwiler Date: Mon, 13 Nov 2023 13:12:26 +0100 Subject: [PATCH 11/15] move storage vec into collections library Signed-off-by: Cyrill Leutwiler --- .../src/generator/trait_def/trait_registry.rs | 4 +- crates/ink/ir/src/ir/attrs.rs | 4 +- crates/storage/src/collections/mod.rs | 3 + .../src/{lazy => collections}/storage_vec.rs | 65 ++-- crates/storage/src/lazy/cache_cell.rs | 7 +- crates/storage/src/lazy/entry.rs | 6 +- crates/storage/src/lazy/lazy_imap.rs | 39 +- crates/storage/src/lazy/mod.rs | 26 +- crates/storage/src/lazy/vec.rs | 365 ------------------ crates/storage/src/lib.rs | 8 +- 10 files changed, 116 insertions(+), 411 deletions(-) create mode 100644 crates/storage/src/collections/mod.rs rename crates/storage/src/{lazy => collections}/storage_vec.rs (98%) delete mode 100644 crates/storage/src/lazy/vec.rs diff --git a/crates/ink/codegen/src/generator/trait_def/trait_registry.rs b/crates/ink/codegen/src/generator/trait_def/trait_registry.rs index 6d6471e53e3..b0fe1a25bdb 100644 --- a/crates/ink/codegen/src/generator/trait_def/trait_registry.rs +++ b/crates/ink/codegen/src/generator/trait_def/trait_registry.rs @@ -21,7 +21,9 @@ use super::TraitDefinition; use crate::{ - generator::{self,}, + generator::{ + self, + }, traits::GenerateCode, EnforcedErrors, }; diff --git a/crates/ink/ir/src/ir/attrs.rs b/crates/ink/ir/src/ir/attrs.rs index 4cd8320f434..100efa06548 100644 --- a/crates/ink/ir/src/ir/attrs.rs +++ b/crates/ink/ir/src/ir/attrs.rs @@ -32,7 +32,9 @@ use syn::{ }; use crate::{ - ast::{self,}, + ast::{ + self, + }, error::ExtError as _, ir, ir::{ 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/lazy/storage_vec.rs b/crates/storage/src/collections/storage_vec.rs similarity index 98% rename from crates/storage/src/lazy/storage_vec.rs rename to crates/storage/src/collections/storage_vec.rs index d12a1589491..4db7a84f089 100644 --- a/crates/storage/src/lazy/storage_vec.rs +++ b/crates/storage/src/collections/storage_vec.rs @@ -27,12 +27,24 @@ //#[cfg(all(test, feature = "ink-fuzz-tests"))] // mod fuzz_tests; -use core::iter::{Extend, FromIterator}; +use core::iter::{ + Extend, + FromIterator, +}; use ink_primitives::Key; -use ink_storage_traits::{AutoKey, Packed, Storable, StorableHint, StorageKey}; +use ink_storage_traits::{ + AutoKey, + Packed, + Storable, + StorableHint, + StorageKey, +}; // pub use self::iter::{Iter, IterMut}; -use crate::{extend_lifetime, lazy::LazyIndexMap}; +use crate::{ + extend_lifetime, + lazy::LazyIndexMap, +}; /// A contiguous growable array type, written `Vec` but pronounced "vector". /// @@ -112,7 +124,11 @@ where #[cfg(feature = "std")] const _: () = { use crate::traits::StorageLayout; - use ink_metadata::layout::{Layout, LayoutKey, RootLayout}; + use ink_metadata::layout::{ + Layout, + LayoutKey, + RootLayout, + }; impl StorageLayout for Vec where @@ -225,7 +241,7 @@ where /// 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); + return Some(index) } None } @@ -233,7 +249,7 @@ where /// Returns a shared reference to the first element if any. pub fn first(&self) -> Option<&T> { if self.is_empty() { - return None; + return None } self.get(0) } @@ -241,7 +257,7 @@ where /// Returns a shared reference to the last element if any. pub fn last(&self) -> Option<&T> { if self.is_empty() { - return None; + return None } let last_index = self.len() - 1; self.get(last_index) @@ -385,7 +401,7 @@ where } else if cmp == Greater { right = mid; } else { - return Ok(mid); + return Ok(mid) } size = right - left; @@ -462,7 +478,7 @@ where /// Returns `None` if the vector is empty. pub fn pop(&mut self) -> Option { if self.is_empty() { - return None; + return None } let last_index = self.len() - 1; self.len = Some(last_index); @@ -479,7 +495,7 @@ where /// since it avoids reading from contract storage in some use cases. pub fn pop_drop(&mut self) -> Option<()> { if self.is_empty() { - return None; + return None } let last_index = self.len() - 1; self.len = Some(last_index); @@ -490,7 +506,7 @@ where /// 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; + return None } self.get_mut(0) } @@ -498,7 +514,7 @@ where /// 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; + return None } let last_index = self.len() - 1; self.get_mut(last_index) @@ -535,7 +551,7 @@ where /// 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; + return None } self.elems.swap(n, self.len() - 1); self.pop() @@ -553,7 +569,7 @@ where /// read for some use cases. pub fn swap_remove_drop(&mut self, n: u32) -> Option<()> { if self.is_empty() { - return None; + return None } self.elems.put(n, None); let last_index = self.len() - 1; @@ -571,7 +587,7 @@ where #[inline] pub fn set(&mut self, index: u32, new_value: T) -> Result<(), IndexOutOfBounds> { if self.within_bounds(index).is_none() { - return Err(IndexOutOfBounds); + return Err(IndexOutOfBounds) } self.elems.put(index, Some(new_value)); Ok(()) @@ -586,7 +602,7 @@ where /// any of the elements (whereas `pop()` does). pub fn clear(&mut self) { if self.is_empty() { - return; + return } for index in 0..self.len() { self.elems.put(index, None); @@ -716,7 +732,7 @@ where { fn eq(&self, other: &Self) -> bool { if self.len() != other.len() { - return false; + return false } self.iter().zip(other.iter()).all(|(lhs, rhs)| lhs == rhs) } @@ -783,7 +799,7 @@ where debug_assert!(self.begin <= self.end); let n = n as u32; if self.begin + n >= self.end { - return None; + return None } let cur = self.begin + n; self.begin += 1 + n; @@ -811,7 +827,7 @@ where debug_assert!(self.begin <= self.end); let n = n as u32; if self.begin >= self.end.saturating_sub(n) { - return None; + return None } self.end -= 1 + n; self.vec @@ -900,7 +916,7 @@ where debug_assert!(self.begin <= self.end); let n = n as u32; if self.begin + n >= self.end { - return None; + return None } let cur = self.begin + n; self.begin += 1 + n; @@ -928,7 +944,7 @@ where debug_assert!(self.begin <= self.end); let n = n as u32; if self.begin >= self.end.saturating_sub(n) { - return None; + return None } self.end -= 1 + n; self.get_mut(self.end) @@ -940,11 +956,14 @@ where #[cfg(test)] mod tests { - use crate::lazy::storage_vec::IndexOutOfBounds; + use crate::collections::storage_vec::IndexOutOfBounds; use super::Vec as StorageVec; use core::cmp::Ordering; - use ink_storage_traits::{ManualKey, Packed}; + use ink_storage_traits::{ + ManualKey, + Packed, + }; #[test] fn new_vec_works() { diff --git a/crates/storage/src/lazy/cache_cell.rs b/crates/storage/src/lazy/cache_cell.rs index ddbb700f0c0..50688bca054 100644 --- a/crates/storage/src/lazy/cache_cell.rs +++ b/crates/storage/src/lazy/cache_cell.rs @@ -12,7 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -use core::{cell::UnsafeCell, fmt, fmt::Debug, ptr::NonNull}; +use core::{ + cell::UnsafeCell, + fmt, + fmt::Debug, + ptr::NonNull, +}; /// A cache for a `T` that allow to mutate the inner `T` through `&self`. /// diff --git a/crates/storage/src/lazy/entry.rs b/crates/storage/src/lazy/entry.rs index 48eefd13851..97f9738d9eb 100644 --- a/crates/storage/src/lazy/entry.rs +++ b/crates/storage/src/lazy/entry.rs @@ -12,7 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -use core::{cell::Cell, fmt, fmt::Debug}; +use core::{ + cell::Cell, + fmt, + fmt::Debug, +}; use ink_prelude::vec::Vec; use ink_storage_traits::Packed; diff --git a/crates/storage/src/lazy/lazy_imap.rs b/crates/storage/src/lazy/lazy_imap.rs index 97660e12662..c9660542261 100644 --- a/crates/storage/src/lazy/lazy_imap.rs +++ b/crates/storage/src/lazy/lazy_imap.rs @@ -14,12 +14,26 @@ use super::{ cache_cell::CacheCell, - entry::{EntryState, StorageEntry}, + entry::{ + EntryState, + StorageEntry, + }, +}; +use core::{ + fmt, + fmt::Debug, + marker::PhantomData, + ptr::NonNull, +}; +use ink_prelude::{ + boxed::Box, + collections::BTreeMap, }; -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}; +use ink_storage_traits::{ + Packed, + StorageKey, +}; /// The index type used in the lazy storage chunk. pub type Index = u32; @@ -356,7 +370,7 @@ where pub fn swap(&mut self, x: Index, y: Index) { if x == y { // Bail out early if both indices are the same. - return; + return } let (loaded_x, loaded_y) = // SAFETY: The loaded `x` and `y` entries are distinct from each @@ -370,7 +384,7 @@ where ) }; 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; + return } // Set the `mutate` flag since at this point at least one of the loaded // values is guaranteed to be `Some`. @@ -383,10 +397,17 @@ where #[cfg(test)] mod tests { use super::{ - super::entry::{EntryState, StorageEntry}, - Index, LazyIndexMap, + super::entry::{ + EntryState, + StorageEntry, + }, + Index, + LazyIndexMap, + }; + use ink_storage_traits::{ + AutoKey, + StorageKey, }; - use ink_storage_traits::{AutoKey, StorageKey}; /// Asserts that the cached entries of the given `imap` is equal to the `expected` /// slice. diff --git a/crates/storage/src/lazy/mod.rs b/crates/storage/src/lazy/mod.rs index 7f4da8e14fe..83caa472bf4 100644 --- a/crates/storage/src/lazy/mod.rs +++ b/crates/storage/src/lazy/mod.rs @@ -22,18 +22,24 @@ mod cache_cell; mod entry; mod lazy_imap; mod mapping; -mod storage_vec; -mod vec; +pub use self::lazy_imap::LazyIndexMap; #[doc(inline)] pub use self::mapping::Mapping; -pub use self::{lazy_imap::LazyIndexMap, storage_vec::Vec, vec::StorageVec}; -use crate::traits::{AutoKey, StorableHint, StorageKey}; +use crate::traits::{ + AutoKey, + StorableHint, + StorageKey, +}; use core::marker::PhantomData; use ink_primitives::Key; use ink_storage_traits::Storable; -use scale::{Error, Input, Output}; +use scale::{ + Error, + Input, + Output, +}; /// A simple wrapper around a type to store it in a separate storage cell under its own /// storage key. If you want to update the value, first you need to @@ -156,7 +162,7 @@ where .expect("targets of less than 32bit pointer size are not supported; qed"); if encoded_length > ink_env::BUFFER_SIZE { - return Some(Err(ink_env::Error::BufferTooSmall)); + return Some(Err(ink_env::Error::BufferTooSmall)) } self.get().map(Ok) @@ -176,7 +182,7 @@ where /// Fails if `value` exceeds the static buffer size. pub fn try_set(&mut self, value: &V) -> ink_env::Result<()> { if value.encoded_size() > ink_env::BUFFER_SIZE { - return Err(ink_env::Error::BufferTooSmall); + return Err(ink_env::Error::BufferTooSmall) }; self.set(value); @@ -239,7 +245,11 @@ where #[cfg(feature = "std")] const _: () = { use crate::traits::StorageLayout; - use ink_metadata::layout::{Layout, LayoutKey, RootLayout}; + use ink_metadata::layout::{ + Layout, + LayoutKey, + RootLayout, + }; impl StorageLayout for Lazy where diff --git a/crates/storage/src/lazy/vec.rs b/crates/storage/src/lazy/vec.rs deleted file mode 100644 index 7ca754adaac..00000000000 --- a/crates/storage/src/lazy/vec.rs +++ /dev/null @@ -1,365 +0,0 @@ -// Copyright (C) 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 simple storage vector implementation built on top of [Mapping]. -//! -//! # Note -//! -//! This vector doesn't actually "own" any data. -//! Instead it is just a simple wrapper around the contract storage facilities. - -use ink_primitives::Key; -use ink_storage_traits::{AutoKey, Packed, Storable, StorableHint, StorageKey}; -use scale::EncodeLike; - -use crate::{Lazy, Mapping}; - -/// A vector of values (elements) directly on contract storage. - -/// # Important -/// -/// [StorageVec] requires its own pre-defined storage key where to store values. By -/// default, the is automatically calculated using [`AutoKey`](crate::traits::AutoKey) -/// during compilation. However, anyone can specify a storage key using -/// [`ManualKey`](crate::traits::ManualKey). Specifying the storage key can be helpful for -/// upgradeable contracts or you want to be resistant to future changes of storage key -/// calculation strategy. -/// -/// # Differences between `ink::prelude::vec::Vec` and [StorageVec] -/// -/// Any `Vec` will exhibit [Packed] storage layout; where -/// [StorageVec] stores each value under it's own storage key. -/// -/// Hence, any read or write from or to a `Vec` on storage will load -/// or store _all_ of its elements. -/// -/// This can be undesirable: -/// The cost of reading or writing a _single_ element grows linearly -/// corresponding to the number of elements in the vector (its length). -/// Additionally, the maximum capacity of the _whole_ vector is limited by -/// the size of the static buffer used during ABI encoding and decoding -/// (default 16KiB). -/// -/// [StorageVec] on the other hand allows to access each element individually. -/// Thus, it can theoretically grow to infinite size. -/// However, we currently limit the length at 2 ^ 32 elements. In practice, -/// even if the vector elements are single bytes, it'll allow to store -/// more than 4GB data in blockchain storage. -/// -/// # Caveats -/// -/// Iteration is not providided. [StorageVec] is expected to be used to -/// store a lot or large values where iterating through elements would be -/// rather inefficient anyways. -/// -/// The decision whether to use `Vec` or [StorageVec] can be seen as an -/// optimization problem with several factors: -/// * How large you expect the vector to grow -/// * The size of individual elements being stored -/// * How frequentely reads, writes and iterations happen -/// -/// For example, if a vector is expected to stay small but is frequently -/// iteratet over. Chooosing a `Vec` instead of [StorageVec] will be -/// preferred as indiviudal storage reads are much more expensive as -/// opposed to retrieving and decoding the whole collections with a single -/// storage read. -/// -/// # Storage Layout -/// -/// At given [StorageKey] `K`, the length of the [StorageVec] is hold. -/// Each element `E` is then stored under a combination of the [StorageVec] -/// key `K` and the elements index. -/// -/// Given [StorageVec] under key `K`, the storage key `E` of the `N`th -/// element is calcualted as follows: -/// -/// `E = scale::Encode((K, N))` -#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] -pub struct StorageVec { - len: Lazy, - len_cached: Option, - elements: Mapping, -} - -impl Default for StorageVec -where - V: Packed, - KeyType: StorageKey, -{ - fn default() -> Self { - Self::new() - } -} - -impl Storable for StorageVec -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 StorageVec -where - V: Packed, - Key: StorageKey, - InnerKey: StorageKey, -{ - type Type = StorageVec; - type PreferredKey = InnerKey; -} - -impl StorageKey for StorageVec -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 StorageVec - 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), - )) - } - } -}; - -impl StorageVec -where - V: Packed, - KeyType: StorageKey, -{ - /// Creates a new empty `StorageVec`. - pub const fn new() -> Self { - Self { - len: Lazy::new(), - len_cached: None, - elements: Mapping::new(), - } - } - - /// Returns the number of elements in the vector, also referred to as its length. - /// - /// The length is cached; subsequent calls (without writing to the vector) won't - /// trigger additional storage reads. - #[inline] - pub fn len(&self) -> u32 { - self.len_cached - .unwrap_or_else(|| self.len.get().unwrap_or(u32::MIN)) - } - - fn set_len(&mut self, new_len: u32) { - self.len.set(&new_len); - self.len_cached = Some(new_len); - } - - /// Returns `true` if the vector contains no elements. - pub fn is_empty(&self) -> bool { - self.len() == 0 - } - - /// Appends an element to the back of the vector. - /// - /// # Panics - /// - /// * If the vector is at capacity (max. of 2 ^ 32 elements). - /// * If the value overgrows the static buffer size. - /// * If there was already a value at the current index. - pub fn push(&mut self, value: &T) - where - T: Storable + scale::EncodeLike, - { - let slot = self.len(); - self.set_len(slot.checked_add(1).unwrap()); - - assert!(self.elements.insert(slot, value).is_none()); - } - - /// Pops the last element from the vector and returns it. - // - /// Returns `None` if the vector is empty. - /// - /// # Panics - /// - /// * If the value overgrows the static buffer size. - /// * If there is no value at the current index. - pub fn pop(&mut self) -> Option { - let slot = self.len().checked_sub(1)?; - - self.set_len(slot); - self.elements.take(slot).unwrap().into() - } - - /// Access an element at given `index`. - /// - /// # Panics - /// - /// * If encoding the element exceeds the static buffer size. - pub fn get(&self, index: u32) -> Option { - self.elements.get(index) - } - - /// Set the `value` at given `index`. - /// - /// # Panics - /// - /// * If the index is out of bounds. - /// * If decoding the element exceeds the static buffer size. - pub fn set(&mut self, index: u32, value: &T) - where - T: Storable + EncodeLike, - { - assert!(index < self.len()); - - let _ = self.elements.insert(index, value); - } -} - -impl ::core::fmt::Debug for StorageVec -where - V: Packed, - KeyType: StorageKey, -{ - fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { - f.debug_struct("StorageVec") - .field("key", &KeyType::KEY) - .field("len", &self.len) - .finish() - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::traits::ManualKey; - - #[test] - fn default_values() { - ink_env::test::run_test::(|_| { - let mut array: StorageVec = StorageVec::new(); - - assert!(array.is_empty()); - assert_eq!(array.pop(), None); - - Ok(()) - }) - .unwrap() - } - #[test] - fn push_and_pop_work() { - ink_env::test::run_test::(|_| { - let mut array: StorageVec = StorageVec::new(); - - let value = "test".to_string(); - array.push(&value); - assert_eq!(array.len(), 1); - assert_eq!(array.pop(), Some(value)); - - assert_eq!(array.len(), 0); - assert_eq!(array.pop(), None); - - Ok(()) - }) - .unwrap() - } - - #[test] - fn storage_keys_are_correct() { - ink_env::test::run_test::(|_| { - const BASE: u32 = 123; - let mut array: StorageVec> = StorageVec::new(); - - let expected_value = 127; - array.push(&expected_value); - - let actual_length = ink_env::get_contract_storage::<_, u32>(&BASE); - assert_eq!(actual_length, Ok(Some(1))); - - let actual_value = ink_env::get_contract_storage::<_, u8>(&(BASE, 0u32)); - assert_eq!(actual_value, Ok(Some(expected_value))); - - Ok(()) - }) - .unwrap() - } - - #[test] - fn push_and_pop_work_for_two_vecs_with_same_manual_key() { - ink_env::test::run_test::(|_| { - let expected_value = 255; - - let mut array: StorageVec> = StorageVec::new(); - array.push(&expected_value); - - let mut array2: StorageVec> = StorageVec::new(); - assert_eq!(array2.pop(), Some(expected_value)); - - Ok(()) - }) - .unwrap() - } - - #[test] - fn set_and_get_work() { - ink_env::test::run_test::(|_| { - let mut array: StorageVec = StorageVec::new(); - - let value = "test".to_string(); - array.push(&value); - assert_eq!(array.get(0), Some(value)); - assert_eq!(array.len(), 1); - - let replaced_value = "foo".to_string(); - array.set(0, &replaced_value); - assert_eq!(array.get(0), Some(replaced_value)); - - Ok(()) - }) - .unwrap() - } - - #[test] - #[should_panic] - fn set_panics_on_oob() { - ink_env::test::run_test::(|_| { - StorageVec::::new().set(0, &0); - - Ok(()) - }) - .unwrap() - } -} diff --git a/crates/storage/src/lib.rs b/crates/storage/src/lib.rs index 4189c9201e1..ce16c667971 100644 --- a/crates/storage/src/lib.rs +++ b/crates/storage/src/lib.rs @@ -48,12 +48,16 @@ pub use ink_storage_traits as traits; +pub(crate) mod collections; #[allow(dead_code)] pub(crate) mod lazy; -pub use self::lazy::Vec as StorageVec; +pub use self::collections::Vec as StorageVec; #[doc(inline)] -pub use self::lazy::{Lazy, Mapping}; +pub use self::lazy::{ + Lazy, + Mapping, +}; /// Extends the lifetime `'a` to the outliving lifetime `'b` for the given reference. /// From 36ffe5551c89109ffa48d0db961893502a7722d5 Mon Sep 17 00:00:00 2001 From: Cyrill Leutwiler Date: Mon, 13 Nov 2023 13:39:38 +0100 Subject: [PATCH 12/15] basic integration test Signed-off-by: Cyrill Leutwiler --- .../storagevec-integration-tests/.gitignore | 9 ++ .../storagevec-integration-tests/Cargo.toml | 23 ++++ .../storagevec-integration-tests/lib.rs | 111 ++++++++++++++++++ 3 files changed, 143 insertions(+) create mode 100755 integration-tests/storagevec-integration-tests/.gitignore create mode 100755 integration-tests/storagevec-integration-tests/Cargo.toml create mode 100755 integration-tests/storagevec-integration-tests/lib.rs 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..7f37b0c7331 --- /dev/null +++ b/integration-tests/storagevec-integration-tests/lib.rs @@ -0,0 +1,111 @@ +//! A smart contract which demonstrates functionality of `Mapping` functions. + +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +#[ink::contract] +mod storagevec_integration_tests { + /// Defines the storage of your contract. + /// Add new fields to the below struct in order + /// to add new static storage fields to your contract. + #[ink(storage)] + pub struct StorageVector { + /// Stores a single `bool` value on the storage. + vec: ink::storage::StorageVec, + } + + impl StorageVector { + /// Constructor that initializes the `bool` value to `false`. + /// + /// Constructors can delegate to other constructors. + #[ink(constructor, payable)] + pub fn default() -> Self { + Self { + vec: Default::default(), + } + } + + /// A message that can be called on instantiated contracts. + /// This one flips the value of the stored `bool` from `true` + /// to `false` and vice versa. + #[ink(message)] + pub fn push(&mut self, value: u8) { + self.vec.push(value); + self.vec.write(); + } + + /// Simply returns the current value of our `bool`. + #[ink(message)] + pub fn pop(&mut self) -> Option { + let result = self.vec.pop(); + self.vec.write(); + result + } + + #[ink(message)] + pub fn peek(&self, at: u32) -> Option { + self.vec.get(at).copied() + } + + #[ink(message)] + pub fn get(&self) -> ink::prelude::vec::Vec { + self.vec.iter().copied().collect() + } + } + + #[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 push_and_pop_works( + 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 insert = call.push(0); + let _ = client + .call(&ink_e2e::alice(), &insert) + .submit() + .await + .expect("Calling `insert_balance` failed"); + + // then + let value = client + .call(&ink_e2e::alice(), &call.pop()) + .dry_run() + .await + .return_value(); + assert_eq!(value, Some(0)); + + client + .call(&ink_e2e::alice(), &call.pop()) + .submit() + .await + .unwrap(); + + let value = client + .call(&ink_e2e::alice(), &call.pop()) + .dry_run() + .await + .return_value(); + assert_eq!(value, None); + + Ok(()) + } + } +} From ef9f3a4e8ab5e9278882c8dff5191ff3af1e877c Mon Sep 17 00:00:00 2001 From: Cyrill Leutwiler Date: Wed, 15 Nov 2023 16:50:18 +0100 Subject: [PATCH 13/15] add test for clear Signed-off-by: Cyrill Leutwiler --- crates/storage/src/collections/storage_vec.rs | 112 ++++++------------ .../storagevec-integration-tests/lib.rs | 19 ++- 2 files changed, 41 insertions(+), 90 deletions(-) diff --git a/crates/storage/src/collections/storage_vec.rs b/crates/storage/src/collections/storage_vec.rs index 4db7a84f089..76143e8f19f 100644 --- a/crates/storage/src/collections/storage_vec.rs +++ b/crates/storage/src/collections/storage_vec.rs @@ -241,7 +241,7 @@ where /// 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) + return Some(index); } None } @@ -249,7 +249,7 @@ where /// Returns a shared reference to the first element if any. pub fn first(&self) -> Option<&T> { if self.is_empty() { - return None + return None; } self.get(0) } @@ -257,7 +257,7 @@ where /// Returns a shared reference to the last element if any. pub fn last(&self) -> Option<&T> { if self.is_empty() { - return None + return None; } let last_index = self.len() - 1; self.get(last_index) @@ -401,7 +401,7 @@ where } else if cmp == Greater { right = mid; } else { - return Ok(mid) + return Ok(mid); } size = right - left; @@ -478,7 +478,7 @@ where /// Returns `None` if the vector is empty. pub fn pop(&mut self) -> Option { if self.is_empty() { - return None + return None; } let last_index = self.len() - 1; self.len = Some(last_index); @@ -495,7 +495,7 @@ where /// since it avoids reading from contract storage in some use cases. pub fn pop_drop(&mut self) -> Option<()> { if self.is_empty() { - return None + return None; } let last_index = self.len() - 1; self.len = Some(last_index); @@ -506,7 +506,7 @@ where /// 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 + return None; } self.get_mut(0) } @@ -514,7 +514,7 @@ where /// 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 + return None; } let last_index = self.len() - 1; self.get_mut(last_index) @@ -551,7 +551,7 @@ where /// 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 + return None; } self.elems.swap(n, self.len() - 1); self.pop() @@ -569,7 +569,7 @@ where /// read for some use cases. pub fn swap_remove_drop(&mut self, n: u32) -> Option<()> { if self.is_empty() { - return None + return None; } self.elems.put(n, None); let last_index = self.len() - 1; @@ -587,7 +587,7 @@ where #[inline] pub fn set(&mut self, index: u32, new_value: T) -> Result<(), IndexOutOfBounds> { if self.within_bounds(index).is_none() { - return Err(IndexOutOfBounds) + return Err(IndexOutOfBounds); } self.elems.put(index, Some(new_value)); Ok(()) @@ -602,7 +602,7 @@ where /// any of the elements (whereas `pop()` does). pub fn clear(&mut self) { if self.is_empty() { - return + return; } for index in 0..self.len() { self.elems.put(index, None); @@ -613,7 +613,7 @@ where /// Write the cached in-memory data back to storage. /// /// This does only write elements that were modified. - pub fn write(&self) { + pub fn write(&mut self) { ink_env::set_contract_storage(&KeyType::KEY, &self.len()); self.elems.write(); } @@ -732,7 +732,7 @@ where { fn eq(&self, other: &Self) -> bool { if self.len() != other.len() { - return false + return false; } self.iter().zip(other.iter()).all(|(lhs, rhs)| lhs == rhs) } @@ -799,7 +799,7 @@ where debug_assert!(self.begin <= self.end); let n = n as u32; if self.begin + n >= self.end { - return None + return None; } let cur = self.begin + n; self.begin += 1 + n; @@ -827,7 +827,7 @@ where debug_assert!(self.begin <= self.end); let n = n as u32; if self.begin >= self.end.saturating_sub(n) { - return None + return None; } self.end -= 1 + n; self.vec @@ -916,7 +916,7 @@ where debug_assert!(self.begin <= self.end); let n = n as u32; if self.begin + n >= self.end { - return None + return None; } let cur = self.begin + n; self.begin += 1 + n; @@ -944,7 +944,7 @@ where debug_assert!(self.begin <= self.end); let n = n as u32; if self.begin >= self.end.saturating_sub(n) { - return None + return None; } self.end -= 1 + n; self.get_mut(self.end) @@ -1371,27 +1371,21 @@ mod tests { .unwrap(); } - // #[test] - // #[should_panic(expected = "encountered empty storage cell")] - // fn spread_layout_clear_works() { - // ink_env::test::run_test::(|_| { - // let vec1 = vec_from_slice(&[b'a', b'b', b'c', b'd']); - // let root_key = Key::from([0x42; 32]); - // SpreadLayout::push_spread(&vec1, &mut KeyPtr::from(root_key)); - // It has already been asserted that a valid instance can be pulled - // from contract storage after a push to the same storage region. - // - // Now clear the associated storage from `vec1` and check whether - // loading another instance from this storage will panic since the - // vector's length property cannot read a value: - // SpreadLayout::clear_spread(&vec1, &mut KeyPtr::from(root_key)); - // let _ = as SpreadLayout>::pull_spread(&mut KeyPtr::from( - // root_key, - // )); - // 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() { @@ -1521,39 +1515,6 @@ mod tests { .unwrap() } - // #[test] - // #[should_panic(expected = "encountered empty storage cell")] - // fn storage_is_cleared_completely_after_pull_lazy() { - // ink_env::test::run_test::(|_| { - // given - // let root_key = Key::from([0x42; 32]); - // let mut lazy_vec: Lazy> = Lazy::new(StorageVec::new()); - // lazy_vec.push(13u32); - // lazy_vec.push(13u32); - // SpreadLayout::push_spread(&lazy_vec, &mut KeyPtr::from(root_key)); - // let pulled_vec = > as SpreadLayout>::pull_spread( - // &mut KeyPtr::from(root_key), - // ); - // - // when - // SpreadLayout::clear_spread(&pulled_vec, &mut KeyPtr::from(root_key)); - // - // then - // 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); - // let _ = *> as SpreadLayout>::pull_spread(&mut KeyPtr::from( - // root_key, - // )); - // - // Ok(()) - // }) - // .unwrap() - // } - #[test] //#[should_panic(expected = "encountered empty storage cell")] fn drop_works() { @@ -1561,8 +1522,6 @@ mod tests { // 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(); - - // vec is dropped which should clear the cells }); assert!(setup_result.is_ok(), "setup should not panic"); @@ -1573,9 +1532,6 @@ mod tests { .expect("used cells must be returned"); assert_eq!(used_cells, 0); - // let _ = as SpreadLayout>::pull_spread(&mut KeyPtr::from( - // root_key, - //)); Ok(()) }) .unwrap() @@ -1584,7 +1540,7 @@ mod tests { #[test] fn write_work() { ink_env::test::run_test::(|_| { - let a = [1, 1, 2, 2, 3, 3, 3] + let mut a = [1, 1, 2, 2, 3, 3, 3] .iter() .copied() .collect::>>(); diff --git a/integration-tests/storagevec-integration-tests/lib.rs b/integration-tests/storagevec-integration-tests/lib.rs index 7f37b0c7331..6e40d40a009 100755 --- a/integration-tests/storagevec-integration-tests/lib.rs +++ b/integration-tests/storagevec-integration-tests/lib.rs @@ -1,22 +1,17 @@ -//! A smart contract which demonstrates functionality of `Mapping` functions. +//! A smart contract which demonstrates functionality of `StorageVec` functions. #![cfg_attr(not(feature = "std"), no_std, no_main)] #[ink::contract] mod storagevec_integration_tests { - /// Defines the storage of your contract. - /// Add new fields to the below struct in order - /// to add new static storage fields to your contract. + #[ink(storage)] pub struct StorageVector { - /// Stores a single `bool` value on the storage. + /// Stores indivudual bytes values on the storage. vec: ink::storage::StorageVec, } impl StorageVector { - /// Constructor that initializes the `bool` value to `false`. - /// - /// Constructors can delegate to other constructors. #[ink(constructor, payable)] pub fn default() -> Self { Self { @@ -24,16 +19,14 @@ mod storagevec_integration_tests { } } - /// A message that can be called on instantiated contracts. - /// This one flips the value of the stored `bool` from `true` - /// to `false` and vice versa. + /// Push another byte value to storage. #[ink(message)] pub fn push(&mut self, value: u8) { self.vec.push(value); self.vec.write(); } - /// Simply returns the current value of our `bool`. + /// Pop the last byte value from storage (removing it from storage). #[ink(message)] pub fn pop(&mut self) -> Option { let result = self.vec.pop(); @@ -41,11 +34,13 @@ mod storagevec_integration_tests { result } + /// Peek at the last byte value without removing it from storage. #[ink(message)] pub fn peek(&self, at: u32) -> Option { self.vec.get(at).copied() } + /// Read and return the whole vector from storage. #[ink(message)] pub fn get(&self) -> ink::prelude::vec::Vec { self.vec.iter().copied().collect() From ef2f9342210de26b2eb7ee3434a9f69952b02682 Mon Sep 17 00:00:00 2001 From: Cyrill Leutwiler Date: Thu, 16 Nov 2023 19:35:20 +0100 Subject: [PATCH 14/15] spell Signed-off-by: Cyrill Leutwiler --- integration-tests/storagevec-integration-tests/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-tests/storagevec-integration-tests/lib.rs b/integration-tests/storagevec-integration-tests/lib.rs index 6e40d40a009..5f0427f502e 100755 --- a/integration-tests/storagevec-integration-tests/lib.rs +++ b/integration-tests/storagevec-integration-tests/lib.rs @@ -7,7 +7,7 @@ mod storagevec_integration_tests { #[ink(storage)] pub struct StorageVector { - /// Stores indivudual bytes values on the storage. + /// Stores individual bytes values on the storage. vec: ink::storage::StorageVec, } From c4337cd954fdfa68d3b793031590f1bdd1d7d8ea Mon Sep 17 00:00:00 2001 From: Cyrill Leutwiler Date: Fri, 17 Nov 2023 12:31:20 +0100 Subject: [PATCH 15/15] update integration test Signed-off-by: Cyrill Leutwiler --- crates/storage/src/collections/storage_vec.rs | 3 +- .../storagevec-integration-tests/lib.rs | 118 +++++++++++++----- 2 files changed, 87 insertions(+), 34 deletions(-) diff --git a/crates/storage/src/collections/storage_vec.rs b/crates/storage/src/collections/storage_vec.rs index 76143e8f19f..be4b556c004 100644 --- a/crates/storage/src/collections/storage_vec.rs +++ b/crates/storage/src/collections/storage_vec.rs @@ -136,9 +136,10 @@ const _: () = { KeyType: StorageKey + scale_info::TypeInfo + 'static, { fn layout(_: &Key) -> Layout { - Layout::Root(RootLayout::new::( + Layout::Root(RootLayout::new( LayoutKey::from(&KeyType::KEY), ::layout(&KeyType::KEY), + scale_info::meta_type::(), )) } } diff --git a/integration-tests/storagevec-integration-tests/lib.rs b/integration-tests/storagevec-integration-tests/lib.rs index 5f0427f502e..c7b3a805d6a 100755 --- a/integration-tests/storagevec-integration-tests/lib.rs +++ b/integration-tests/storagevec-integration-tests/lib.rs @@ -4,46 +4,90 @@ #[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 { - /// Stores individual bytes values on the storage. - vec: ink::storage::StorageVec, + proposals: StorageVec, } impl StorageVector { #[ink(constructor, payable)] pub fn default() -> Self { Self { - vec: Default::default(), + proposals: Default::default(), } } - /// Push another byte value to storage. - #[ink(message)] - pub fn push(&mut self, value: u8) { - self.vec.push(value); - self.vec.write(); + /// Checks whether given account is allowed to vote and didn't already + /// participate. + fn is_eligible(&self, _voter: AccountId) -> bool { + // todo + true } - /// Pop the last byte value from storage (removing it from storage). + /// Vote to approve the current proposal. #[ink(message)] - pub fn pop(&mut self) -> Option { - let result = self.vec.pop(); - self.vec.write(); - result + 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(); + } } - /// Peek at the last byte value without removing it from storage. + /// Create a new proposal. + /// + /// Returns `None` if the current proposal is not yet finished. #[ink(message)] - pub fn peek(&self, at: u32) -> Option { - self.vec.get(at).copied() + 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) } - /// Read and return the whole vector from storage. #[ink(message)] - pub fn get(&self) -> ink::prelude::vec::Vec { - self.vec.iter().copied().collect() + pub fn get(&self, at: u32) -> Option { + self.proposals.get(at).cloned() } } @@ -55,7 +99,7 @@ mod storagevec_integration_tests { type E2EResult = std::result::Result>; #[ink_e2e::test] - async fn push_and_pop_works( + async fn create_and_vote( mut client: Client, ) -> E2EResult<()> { // given @@ -72,33 +116,41 @@ mod storagevec_integration_tests { let mut call = contract.call::(); // when - let insert = call.push(0); + 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::alice(), &insert) + .call(&ink_e2e::bob(), &approve) .submit() .await - .expect("Calling `insert_balance` failed"); + .expect("Voting failed"); // then let value = client - .call(&ink_e2e::alice(), &call.pop()) + .call(&ink_e2e::alice(), &create) .dry_run() .await + .expect("create trapped when it shouldn't") .return_value(); - assert_eq!(value, Some(0)); - - client - .call(&ink_e2e::alice(), &call.pop()) - .submit() - .await - .unwrap(); + assert_eq!(value, None); let value = client - .call(&ink_e2e::alice(), &call.pop()) + .call(&ink_e2e::alice(), &call.get(0)) .dry_run() .await + .expect("get trapped when it shouldn't") .return_value(); - assert_eq!(value, None); + assert_eq!(value.unwrap().approvals, 2); Ok(()) }