Skip to content
2 changes: 1 addition & 1 deletion crates/storage/src/alloc/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ fn spread_pull_push_works() {
}

#[test]
#[should_panic(expected = "encountered empty storage cell")]
#[should_panic(expected = "index is out of bounds")]
fn spread_clear_works() {
run_default_test(|| {
let alloc = spread_layout_alloc_setup();
Expand Down
16 changes: 13 additions & 3 deletions crates/storage/src/collections/binary_heap/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -229,8 +229,10 @@ fn spread_layout_clear_works() {
// loading another instance from this storage will panic since the
// heap's length property cannot read a value:
SpreadLayout::clear_spread(&heap1, &mut KeyPtr::from(root_key));
let _ =
let instance =
<BinaryHeap<u8> as SpreadLayout>::pull_spread(&mut KeyPtr::from(root_key));
instance.len();

Ok(())
})
.unwrap()
Expand Down Expand Up @@ -266,8 +268,12 @@ fn drop_works() {
.expect("Used cells must be returned");
assert_eq!(used_cells, 0);

let _ =
// We try and grab the length from storage, but it doesn't exist anymore
// so this will panic
let instance =
<BinaryHeap<u8> as SpreadLayout>::pull_spread(&mut KeyPtr::from(root_key));
instance.len();

Ok(())
})
.unwrap()
Expand Down Expand Up @@ -300,8 +306,12 @@ fn drop_works() {
.expect("Used cells must be returned");
assert_eq!(used_cells, 0);

let _ =
// We try and grab the length from storage, but it doesn't exist anymore
// so this will panic
let instance =
<BinaryHeap<u8> as SpreadLayout>::pull_spread(&mut KeyPtr::from(root_key));
instance.len();

Ok(())
})
.unwrap()
Expand Down
6 changes: 3 additions & 3 deletions crates/storage/src/collections/bitstash/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ fn spread_layout_push_pull_works() {
}

#[test]
#[should_panic(expected = "encountered empty storage cell")]
#[should_panic(expected = "index is out of bounds")]
fn spread_layout_clear_works() {
ink_env::test::run_test::<ink_env::DefaultEnvironment, _>(|_| {
let default = filled_bitstash();
Expand All @@ -155,9 +155,9 @@ fn spread_layout_clear_works() {
<BitStash as SpreadLayout>::pull_spread(&mut KeyPtr::from(root_key));
// We have to prevent calling its destructor since that would also panic but
// in an uncontrollable way.
let mut invalid = core::mem::ManuallyDrop::new(invalid);
let invalid = core::mem::ManuallyDrop::new(invalid);
// Now interact with invalid instance.
let _ = invalid.put();
invalid.get(0);
Ok(())
})
.unwrap()
Expand Down
17 changes: 16 additions & 1 deletion crates/storage/src/collections/bitvec/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,22 @@ fn spread_layout_clear_works() {
// loading another instance from this storage will panic since the
// vector's length property cannot read a value:
SpreadLayout::clear_spread(&bv1, &mut KeyPtr::from(root_key));
let _ = <StorageBitvec as SpreadLayout>::pull_spread(&mut KeyPtr::from(root_key));

let len_result = std::panic::catch_unwind(|| {
let instance =
<StorageBitvec as SpreadLayout>::pull_spread(&mut KeyPtr::from(root_key));
let _ = crate::Lazy::get(&instance.len);
});
assert!(
len_result.is_err(),
"Length shouldn't be in storage at this point."
);

// In practice we wouldn't get the raw `len` field though, so no panic occurs
let instance =
<StorageBitvec as SpreadLayout>::pull_spread(&mut KeyPtr::from(root_key));
assert!(instance.is_empty());

Ok(())
})
.unwrap()
Expand Down
12 changes: 6 additions & 6 deletions crates/storage/src/collections/vec/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ where

/// Returns the number of elements in the vector, also referred to as its length.
pub fn len(&self) -> u32 {
*self.len
*Lazy::try_get(&self.len).unwrap_or(&0)
}

/// Returns `true` if the vector contains no elements.
Expand Down Expand Up @@ -197,7 +197,7 @@ where
"cannot push more elements into the storage vector"
);
let last_index = self.len();
*self.len += 1;
Lazy::set(&mut self.len, last_index + 1);
self.elems.put(last_index, Some(value));
}

Expand Down Expand Up @@ -387,7 +387,7 @@ where
return None
}
let last_index = self.len() - 1;
*self.len = last_index;
Lazy::set(&mut self.len, last_index);
self.elems.put_get(last_index, None)
}

Expand All @@ -404,7 +404,7 @@ where
return None
}
let last_index = self.len() - 1;
*self.len = last_index;
Lazy::set(&mut self.len, last_index);
self.elems.put(last_index, None);
Some(())
}
Expand Down Expand Up @@ -481,7 +481,7 @@ where
let last_index = self.len() - 1;
let last = self.elems.put_get(last_index, None);
self.elems.put(n, last);
*self.len = last_index;
Lazy::set(&mut self.len, last_index);
Some(())
}

Expand Down Expand Up @@ -513,6 +513,6 @@ where
for index in 0..self.len() {
self.elems.put(index, None);
}
*self.len = 0;
Lazy::set(&mut self.len, 0);
}
}
108 changes: 102 additions & 6 deletions crates/storage/src/collections/vec/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,6 @@ fn spread_layout_push_pull_works() -> ink_env::Result<()> {
}

#[test]
#[should_panic(expected = "encountered empty storage cell")]
fn spread_layout_clear_works() {
ink_env::test::run_test::<ink_env::DefaultEnvironment, _>(|_| {
let vec1 = vec_from_slice(&[b'a', b'b', b'c', b'd']);
Expand All @@ -388,8 +387,23 @@ fn spread_layout_clear_works() {
// 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 _ =

let len_result = std::panic::catch_unwind(|| {
let instance = <StorageVec<u8> as SpreadLayout>::pull_spread(
&mut KeyPtr::from(root_key),
);
let _ = Lazy::get(&instance.len);
});
assert!(
len_result.is_err(),
"Length shouldn't be in storage at this point."
);

// In practice we wouldn't get the raw `len` field though, so no panic occurs
let instance =
<StorageVec<u8> as SpreadLayout>::pull_spread(&mut KeyPtr::from(root_key));
assert!(instance.is_empty());

Ok(())
})
.unwrap()
Expand Down Expand Up @@ -534,7 +548,6 @@ fn storage_is_cleared_completely_after_pull_lazy() {
}

#[test]
#[should_panic(expected = "encountered empty storage cell")]
#[cfg(not(feature = "ink-experimental-engine"))]
fn drop_works() {
ink_env::test::run_test::<ink_env::DefaultEnvironment, _>(|_| {
Expand All @@ -561,8 +574,22 @@ fn drop_works() {
.expect("used cells must be returned");
assert_eq!(used_cells, 0);

let _ =
let len_result = std::panic::catch_unwind(|| {
let instance = <StorageVec<u8> as SpreadLayout>::pull_spread(
&mut KeyPtr::from(root_key),
);
let _ = Lazy::get(&instance.len);
});
assert!(
len_result.is_err(),
"Length shouldn't be in storage at this point."
);

// In practice we wouldn't get the raw `len` field though, so no panic occurs
let instance =
<StorageVec<u8> as SpreadLayout>::pull_spread(&mut KeyPtr::from(root_key));
assert!(instance.is_empty());

Ok(())
})
.unwrap()
Expand Down Expand Up @@ -602,7 +629,6 @@ fn storage_is_cleared_completely_after_pull_lazy() {
}

#[test]
#[should_panic(expected = "encountered empty storage cell")]
#[cfg(feature = "ink-experimental-engine")]
fn drop_works() {
ink_env::test::run_test::<ink_env::DefaultEnvironment, _>(|_| {
Expand All @@ -626,9 +652,79 @@ fn drop_works() {
.expect("used cells must be returned");
assert_eq!(used_cells, 0);

let _ =
let len_result = std::panic::catch_unwind(|| {
let instance = <StorageVec<u8> as SpreadLayout>::pull_spread(
&mut KeyPtr::from(root_key),
);
let _ = Lazy::get(&instance.len);
});
assert!(
len_result.is_err(),
"Length shouldn't be in storage at this point."
);

// In practice we wouldn't get the raw `len` field though, so no panic occurs
let instance =
<StorageVec<u8> as SpreadLayout>::pull_spread(&mut KeyPtr::from(root_key));
assert!(instance.is_empty());
Ok(())
})
.unwrap()
}

// We explicitly test that `Drop` works here since there was a bug in which a `StorageVec`
// initialized with `SpreadAllocate` would panic on `Drop` due to an empty storage cell
// for its length.
#[test]
fn spread_allocate_drop_works() -> ink_env::Result<()> {
use crate::traits::{
KeyPtr,
SpreadAllocate,
};

ink_env::test::run_test::<ink_env::DefaultEnvironment, _>(|_| {
let setup_result = std::panic::catch_unwind(|| {
let root_key = Key::from([0x42; 32]);
let mut key_ptr = KeyPtr::from(root_key);
let instance =
<StorageVec<u8> as SpreadAllocate>::allocate_spread(&mut key_ptr);
drop(instance)
});

assert!(setup_result.is_ok());

Ok(())
})
}

#[test]
fn spread_allocate_vector_works() -> ink_env::Result<()> {
use crate::traits::{
KeyPtr,
SpreadAllocate,
};

ink_env::test::run_test::<ink_env::DefaultEnvironment, _>(|_| {
let root_key = Key::from([0x42; 32]);
let mut key_ptr = KeyPtr::from(root_key);
let mut instance =
<StorageVec<u8> as SpreadAllocate>::allocate_spread(&mut key_ptr);

instance.push(1);
assert_eq!(instance.pop(), Some(1));

instance.push(2);
assert!(instance.pop_drop().is_some());

instance.push(3);
assert!(instance.swap_remove_drop(0).is_some());

instance.push(4);
assert!(instance.set(0, 5).is_ok());

instance.clear();
assert!(instance.is_empty());

Ok(())
})
}
11 changes: 11 additions & 0 deletions crates/storage/src/lazy/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,17 @@ where
lazy.cell.get().expect("encountered empty storage cell")
}

/// Attempts to load a shared reference to a value in storage.
///
/// If there is no value to load `None` is returned.
///
/// This method may be useful in situations where a data structure has been
/// initialized with `SpreadAllocate`, but nothing has been written to its
/// storage yet.
pub fn try_get(lazy: &Self) -> Option<&T> {
lazy.cell.get()
}

/// Returns an exclusive reference to the lazily loaded value.
///
/// # Note
Expand Down