use core::cell::Cell;
use ink_primitives::Key;
use ink_storage_traits::{
AutoKey,
Packed,
Storable,
StorableHint,
StorageKey,
};
use pallet_contracts_uapi::ReturnErrorCode;
use scale::EncodeLike;
use crate::{
Lazy,
Mapping,
};
#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
pub struct StorageVec<V: Packed, KeyType: StorageKey = AutoKey> {
len: Lazy<u32, KeyType>,
#[cfg_attr(feature = "std", codec(skip))]
len_cached: CachedLen,
elements: Mapping<u32, V, KeyType>,
}
#[derive(Debug)]
struct CachedLen(Cell<Option<u32>>);
impl<V, KeyType> Default for StorageVec<V, KeyType>
where
V: Packed,
KeyType: StorageKey,
{
fn default() -> Self {
Self::new()
}
}
impl<V, KeyType> Storable for StorageVec<V, KeyType>
where
V: Packed,
KeyType: StorageKey,
{
#[inline]
fn encode<T: scale::Output + ?Sized>(&self, _dest: &mut T) {}
#[inline]
fn decode<I: scale::Input>(_input: &mut I) -> Result<Self, scale::Error> {
Ok(Default::default())
}
#[inline]
fn encoded_size(&self) -> usize {
0
}
}
impl<V, Key, InnerKey> StorableHint<Key> for StorageVec<V, InnerKey>
where
V: Packed,
Key: StorageKey,
InnerKey: StorageKey,
{
type Type = StorageVec<V, Key>;
type PreferredKey = InnerKey;
}
impl<V, KeyType> StorageKey for StorageVec<V, KeyType>
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<V, KeyType> StorageLayout for StorageVec<V, KeyType>
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),
<V as StorageLayout>::layout(&KeyType::KEY),
scale_info::meta_type::<Self>(),
))
}
}
};
impl<V, KeyType> StorageVec<V, KeyType>
where
V: Packed,
KeyType: StorageKey,
{
pub const fn new() -> Self {
Self {
len: Lazy::new(),
len_cached: CachedLen(Cell::new(None)),
elements: Mapping::new(),
}
}
#[inline]
pub fn len(&self) -> u32 {
let cached_len = self.len_cached.0.get();
debug_assert!(cached_len.is_none() || self.len.get() == cached_len);
cached_len.unwrap_or_else(|| {
let value = self.len.get();
self.len_cached.0.set(value);
value.unwrap_or(u32::MIN)
})
}
fn set_len(&mut self, new_len: u32) {
self.len.set(&new_len);
self.len_cached.0.set(Some(new_len));
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn push<T>(&mut self, value: &T)
where
T: Storable + scale::EncodeLike<V>,
{
let slot = self.len();
self.set_len(slot.checked_add(1).unwrap());
assert!(self.elements.insert(slot, value).is_none());
}
pub fn try_push<T>(&mut self, value: &T) -> Result<(), ink_env::Error>
where
T: Storable + scale::EncodeLike<V>,
{
let slot = self.len();
self.set_len(slot.checked_add(1).unwrap());
assert!(self.elements.try_insert(slot, value)?.is_none());
Ok(())
}
pub fn pop(&mut self) -> Option<V> {
if self.is_empty() {
return None;
}
let slot = self.len().checked_sub(1).unwrap();
self.set_len(slot);
self.elements.take(slot)
}
pub fn try_pop(&mut self) -> Option<Result<V, ink_env::Error>> {
if self.is_empty() {
return None;
}
let slot = self.len().checked_sub(1).unwrap();
self.set_len(slot);
self.elements.try_take(slot)
}
pub fn peek(&self) -> Option<V> {
if self.is_empty() {
return None;
}
let slot = self.len().checked_sub(1).unwrap();
self.elements.get(slot)
}
pub fn try_peek(&self) -> Option<Result<V, ink_env::Error>> {
if self.is_empty() {
return None;
}
let slot = self.len().checked_sub(1).unwrap();
self.elements.try_get(slot)
}
pub fn get(&self, index: u32) -> Option<V> {
self.elements.get(index)
}
pub fn try_get(&self, index: u32) -> Option<ink_env::Result<V>> {
self.elements.try_get(index)
}
pub fn set<T>(&mut self, index: u32, value: &T) -> Option<u32>
where
T: Storable + EncodeLike<V>,
{
assert!(index < self.len());
self.elements.insert(index, value)
}
pub fn try_set<T>(
&mut self,
index: u32,
value: &T,
) -> Result<Option<u32>, ink_env::Error>
where
T: Storable + EncodeLike<V>,
{
if index >= self.len() {
return Err(ReturnErrorCode::KeyNotFound.into());
}
self.elements.try_insert(index, value)
}
pub fn clear(&mut self) {
for i in 0..self.len() {
self.elements.remove(i);
}
self.set_len(0);
}
pub fn clear_at(&mut self, index: u32) {
assert!(index < self.len());
self.elements.remove(index);
}
}
impl<V, KeyType> FromIterator<V> for StorageVec<V, KeyType>
where
V: Packed + EncodeLike<V>,
KeyType: StorageKey,
{
fn from_iter<T: IntoIterator<Item = V>>(iter: T) -> Self {
let mut result = StorageVec::<V, KeyType>::new();
for element in iter {
result.push(&element);
}
result
}
}
impl<V, KeyType> ::core::fmt::Debug for StorageVec<V, KeyType>
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)
.field("len_cached", &self.len_cached)
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::traits::ManualKey;
#[test]
fn empty_vec_works_as_expected() {
ink_env::test::run_test::<ink_env::DefaultEnvironment, _>(|_| {
let mut array: StorageVec<String> = StorageVec::new();
assert_eq!(array.pop(), None);
assert_eq!(array.peek(), None);
assert_eq!(array.len(), 0);
assert!(array.is_empty());
Ok(())
})
.unwrap()
}
#[test]
fn push_and_pop_work() {
ink_env::test::run_test::<ink_env::DefaultEnvironment, _>(|_| {
let mut array: StorageVec<String> = StorageVec::new();
let value = "test".to_string();
array.push(&value);
assert_eq!(array.len(), 1);
assert_eq!(array.pop(), Some(value));
Ok(())
})
.unwrap()
}
#[test]
fn storage_keys_are_correct() {
ink_env::test::run_test::<ink_env::DefaultEnvironment, _>(|_| {
const BASE: u32 = 123;
let mut array: StorageVec<u8, ManualKey<BASE>> = 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::<ink_env::DefaultEnvironment, _>(|_| {
let expected_value = 255;
let mut array: StorageVec<u8, ManualKey<{ u32::MIN }>> = StorageVec::new();
array.push(&expected_value);
let mut array2: StorageVec<u8, ManualKey<{ u32::MIN }>> = StorageVec::new();
assert_eq!(array2.pop(), Some(expected_value));
Ok(())
})
.unwrap()
}
#[test]
fn set_and_get_work() {
ink_env::test::run_test::<ink_env::DefaultEnvironment, _>(|_| {
let mut array: StorageVec<String> = 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::<ink_env::DefaultEnvironment, _>(|_| {
StorageVec::<u8>::new().set(0, &0);
Ok(())
})
.unwrap()
}
#[test]
fn clear_works() {
ink_env::test::run_test::<ink_env::DefaultEnvironment, _>(|_| {
let mut array: StorageVec<u128> = (0..1024).collect();
array.clear();
assert_eq!(array.len(), 0);
assert_eq!(array.pop(), None);
Ok(())
})
.unwrap()
}
#[test]
fn clear_on_empty_works() {
ink_env::test::run_test::<ink_env::DefaultEnvironment, _>(|_| {
let mut array: StorageVec<bool> = StorageVec::new();
array.clear();
assert_eq!(array.len(), 0);
assert_eq!(array.pop(), None);
Ok(())
})
.unwrap()
}
#[test]
fn clear_at_works() {
ink_env::test::run_test::<ink_env::DefaultEnvironment, _>(|_| {
let mut array: StorageVec<u64> = (0..1024).collect();
array.clear_at(0);
assert_eq!(array.len(), 1024);
assert_eq!(array.get(0), None);
let last_idx = array.len() - 1;
assert_eq!(array.get(last_idx), Some(1023));
array.clear_at(last_idx);
assert_eq!(array.get(last_idx), None);
Ok(())
})
.unwrap()
}
#[test]
#[should_panic]
fn clear_at_invalid_index_panics() {
ink_env::test::run_test::<ink_env::DefaultEnvironment, _>(|_| {
StorageVec::<u32>::new().clear_at(0);
Ok(())
})
.unwrap()
}
#[test]
fn try_get_works() {
ink_env::test::run_test::<ink_env::DefaultEnvironment, _>(|_| {
let array: StorageVec<u32> = (0..10).collect();
assert_eq!(array.try_get(0), Some(Ok(0)));
assert_eq!(array.try_get(11), None);
Ok(())
})
.unwrap()
}
#[test]
fn try_set_works() {
ink_env::test::run_test::<ink_env::DefaultEnvironment, _>(|_| {
let mut array: StorageVec<u32> = (0..10).collect();
assert_eq!(array.try_set(0, &1), Ok(Some(4)));
assert_eq!(
array.try_set(10, &1),
Err(ink_env::Error::ReturnError(
ink_env::ReturnErrorCode::KeyNotFound
))
);
array.clear_at(0);
assert_eq!(array.try_set(0, &1), Ok(None));
Ok(())
})
.unwrap()
}
#[test]
fn fallible_push_pop_peek_works() {
ink_env::test::run_test::<ink_env::DefaultEnvironment, _>(|_| {
let mut array: StorageVec<u32> = (0..10).collect();
assert_eq!(array.try_push(&10), Ok(()));
assert_eq!(array.try_pop(), Some(Ok(10)));
assert_eq!(array.try_peek(), Some(Ok(9)));
array.clear();
assert_eq!(array.try_pop(), None);
assert_eq!(array.try_peek(), None);
Ok(())
})
.unwrap()
}
#[test]
fn peek_works() {
ink_env::test::run_test::<ink_env::DefaultEnvironment, _>(|_| {
let mut array = StorageVec::<u32>::new();
assert_eq!(array.peek(), None);
array.push(&0);
array.push(&9);
assert_eq!(array.peek(), Some(9));
assert_eq!(array.peek(), Some(9));
assert_eq!(array.len(), 2);
array.clear();
assert_eq!(array.peek(), None);
assert_eq!(array.len(), 0);
Ok(())
})
.unwrap()
}
#[test]
fn from_iter_works() {
ink_env::test::run_test::<ink_env::DefaultEnvironment, _>(|_| {
let array = StorageVec::<u32>::from_iter([u32::MIN, u32::MAX]);
assert_eq!(array.len(), 2);
assert_eq!(array.get(0), Some(u32::MIN));
assert_eq!(array.get(1), Some(u32::MAX));
Ok(())
})
.unwrap()
}
#[test]
#[should_panic(
expected = "assertion failed: cached_len.is_none() || self.len.get() == cached_len"
)]
fn cached_len_works() {
ink_env::test::run_test::<ink_env::DefaultEnvironment, _>(|_| {
let array = StorageVec::<u32>::from_iter([u32::MIN, u32::MAX]);
assert_eq!(array.len(), 2);
Lazy::<u32>::new().set(&u32::MAX);
let _ = array.len();
Ok(())
})
.unwrap()
}
}