use super::{
CacheCell,
EntryState,
StorageEntry,
};
use crate::traits::{
clear_spread_root_opt,
pull_spread_root_opt,
ExtKeyPtr,
KeyPtr,
SpreadLayout,
};
use core::{
fmt,
fmt::Debug,
ptr::NonNull,
};
use ink_primitives::Key;
pub struct LazyCell<T>
where
T: SpreadLayout,
{
key: Option<Key>,
cache: CacheCell<Option<StorageEntry<T>>>,
}
impl<T> Debug for LazyCell<T>
where
T: Debug + SpreadLayout,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("LazyCell")
.field("key", &self.key)
.field("cache", self.cache.as_inner())
.finish()
}
}
#[test]
fn debug_impl_works() -> ink_env::Result<()> {
ink_env::test::run_test::<ink_env::DefaultEnvironment, _>(|_| {
let c1 = <LazyCell<i32>>::new(None);
assert_eq!(
format!("{:?}", &c1),
"LazyCell { key: None, cache: Some(Entry { value: None, state: Mutated }) }",
);
let c2 = <LazyCell<i32>>::new(Some(42));
assert_eq!(
format!("{:?}", &c2),
"LazyCell { key: None, cache: Some(Entry { value: Some(42), state: Mutated }) }",
);
let c3 = <LazyCell<i32>>::lazy(Key::from([0x00; 32]));
assert_eq!(
format!("{:?}", &c3),
"LazyCell { \
key: Some(Key(0x_\
0000000000000000_\
0000000000000000_\
0000000000000000_\
0000000000000000)\
), \
cache: None \
}",
);
Ok(())
})
}
impl<T> Drop for LazyCell<T>
where
T: SpreadLayout,
{
fn drop(&mut self) {
if let Some(root_key) = self.key() {
match self.entry() {
Some(entry) => {
clear_spread_root_opt::<T, _>(root_key, || entry.value().into())
}
None => {
if <T as SpreadLayout>::REQUIRES_DEEP_CLEAN_UP {
clear_spread_root_opt::<T, _>(root_key, || self.get())
} else {
let footprint = <T as SpreadLayout>::FOOTPRINT;
assert_footprint_threshold(footprint);
let mut key_ptr = KeyPtr::from(*root_key);
for _ in 0..footprint {
ink_env::clear_contract_storage(key_ptr.advance_by(1));
}
}
}
}
}
}
}
#[cfg(feature = "std")]
const _: () = {
use crate::traits::StorageLayout;
use ink_metadata::layout::Layout;
impl<T> StorageLayout for LazyCell<T>
where
T: StorageLayout + SpreadLayout,
{
fn layout(key_ptr: &mut KeyPtr) -> Layout {
<T as StorageLayout>::layout(key_ptr)
}
}
};
impl<T> SpreadLayout for LazyCell<T>
where
T: SpreadLayout,
{
const FOOTPRINT: u64 = <T as SpreadLayout>::FOOTPRINT;
fn pull_spread(ptr: &mut KeyPtr) -> Self {
let root_key = ExtKeyPtr::next_for::<Self>(ptr);
Self::lazy(*root_key)
}
fn push_spread(&self, ptr: &mut KeyPtr) {
let root_key = ExtKeyPtr::next_for::<Self>(ptr);
if let Some(entry) = self.entry() {
entry.push_spread_root(root_key)
}
}
fn clear_spread(&self, ptr: &mut KeyPtr) {
let root_key = ExtKeyPtr::next_for::<Self>(ptr);
match <T as SpreadLayout>::REQUIRES_DEEP_CLEAN_UP {
true => {
clear_spread_root_opt::<T, _>(root_key, || self.get())
}
false => {
let footprint = <T as SpreadLayout>::FOOTPRINT;
assert_footprint_threshold(footprint);
let mut key_ptr = KeyPtr::from(*root_key);
for _ in 0..footprint {
ink_env::clear_contract_storage(key_ptr.advance_by(1));
}
}
}
}
}
impl<T> From<T> for LazyCell<T>
where
T: SpreadLayout,
{
fn from(value: T) -> Self {
Self::new(Some(value))
}
}
impl<T> Default for LazyCell<T>
where
T: Default + SpreadLayout,
{
fn default() -> Self {
Self::new(Some(Default::default()))
}
}
impl<T> LazyCell<T>
where
T: SpreadLayout,
{
#[must_use]
pub fn new(value: Option<T>) -> Self {
Self {
key: None,
cache: CacheCell::new(Some(StorageEntry::new(value, EntryState::Mutated))),
}
}
#[must_use]
pub fn lazy(key: Key) -> Self {
Self {
key: Some(key),
cache: CacheCell::new(None),
}
}
fn key(&self) -> Option<&Key> {
self.key.as_ref()
}
fn entry(&self) -> Option<&StorageEntry<T>> {
self.cache.as_inner().as_ref()
}
}
impl<T> LazyCell<T>
where
T: SpreadLayout,
{
unsafe fn load_through_cache(&self) -> NonNull<StorageEntry<T>> {
let cache = &mut *self.cache.get_ptr().as_ptr();
if cache.is_none() {
let value = self
.key
.map(|key| pull_spread_root_opt::<T>(&key))
.unwrap_or(None);
*cache = Some(StorageEntry::new(value, EntryState::Preserved));
}
debug_assert!(cache.is_some());
NonNull::from(cache.as_mut().expect("unpopulated cache entry"))
}
fn load_entry(&self) -> &StorageEntry<T> {
unsafe { &*self.load_through_cache().as_ptr() }
}
fn load_entry_mut(&mut self) -> &mut StorageEntry<T> {
let entry = unsafe { &mut *self.load_through_cache().as_ptr() };
entry.replace_state(EntryState::Mutated);
entry
}
#[must_use]
pub fn get(&self) -> Option<&T> {
self.load_entry().value().into()
}
#[must_use]
pub fn get_mut(&mut self) -> Option<&mut T> {
self.load_entry_mut().value_mut().into()
}
#[inline]
pub fn set(&mut self, new_value: T) {
let cache = unsafe { &mut *self.cache.get_ptr().as_ptr() };
if let Some(cache) = cache.as_mut() {
cache.put(Some(new_value));
} else {
*cache = Some(StorageEntry::new(Some(new_value), EntryState::Mutated));
}
debug_assert!(cache.is_some());
}
}
fn assert_footprint_threshold(footprint: u64) {
let footprint_threshold = crate::traits::FOOTPRINT_CLEANUP_THRESHOLD;
assert!(
footprint <= footprint_threshold,
"cannot clean-up a storage entity with a footprint of {}. maximum threshold for clean-up is {}.",
footprint,
footprint_threshold,
);
}
#[cfg(test)]
mod tests {
use super::{
EntryState,
LazyCell,
StorageEntry,
};
use crate::{
traits::{
KeyPtr,
SpreadLayout,
},
Lazy,
};
use ink_env::test::run_test;
use ink_primitives::Key;
#[test]
fn new_works() {
let mut a = <LazyCell<u8>>::new(Some(b'A'));
assert_eq!(a.key(), None);
assert_eq!(
a.entry(),
Some(&StorageEntry::new(Some(b'A'), EntryState::Mutated))
);
assert_eq!(a.get(), Some(&b'A'));
assert_eq!(a.get_mut(), Some(&mut b'A'));
let mut b = <LazyCell<u8>>::new(None);
assert_eq!(b.key(), None);
assert_eq!(
b.entry(),
Some(&StorageEntry::new(None, EntryState::Mutated))
);
assert_eq!(b.get(), None);
assert_eq!(b.get_mut(), None);
let default_lc = <LazyCell<u8>>::default();
let from_lc = LazyCell::from(u8::default());
let new_lc = LazyCell::new(Some(u8::default()));
assert_eq!(default_lc.get(), from_lc.get());
assert_eq!(from_lc.get(), new_lc.get());
assert_eq!(new_lc.get(), Some(&u8::default()));
}
#[test]
fn lazy_works() -> ink_env::Result<()> {
run_test::<ink_env::DefaultEnvironment, _>(|_| {
let root_key = Key::from([0x42; 32]);
let cell = <LazyCell<u8>>::lazy(root_key);
assert_eq!(cell.key(), Some(&root_key));
Ok(())
})
}
#[test]
fn lazy_get_works() -> ink_env::Result<()> {
run_test::<ink_env::DefaultEnvironment, _>(|_| {
let cell = <LazyCell<u8>>::lazy(Key::from([0x42; 32]));
let value = cell.get();
assert_eq!(value, None);
Ok(())
})
}
#[test]
fn get_mut_works() {
let mut cell = <LazyCell<i32>>::new(Some(1));
assert_eq!(cell.get(), Some(&1));
*cell.get_mut().unwrap() += 1;
assert_eq!(cell.get(), Some(&2));
}
#[test]
fn spread_layout_works() -> ink_env::Result<()> {
run_test::<ink_env::DefaultEnvironment, _>(|_| {
let cell_a0 = <LazyCell<u8>>::new(Some(b'A'));
assert_eq!(cell_a0.get(), Some(&b'A'));
let root_key = Key::from([0x42; 32]);
SpreadLayout::push_spread(&cell_a0, &mut KeyPtr::from(root_key));
let cell_a1 =
<LazyCell<u8> as SpreadLayout>::pull_spread(&mut KeyPtr::from(root_key));
assert_eq!(cell_a1.get(), cell_a0.get());
assert_eq!(cell_a1.get(), Some(&b'A'));
assert_eq!(
cell_a1.entry(),
Some(&StorageEntry::new(Some(b'A'), EntryState::Preserved))
);
let cell_a2 = <LazyCell<u8>>::lazy(root_key);
assert_eq!(cell_a2.get(), cell_a0.get());
assert_eq!(cell_a2.get(), Some(&b'A'));
assert_eq!(
cell_a2.entry(),
Some(&StorageEntry::new(Some(b'A'), EntryState::Preserved))
);
SpreadLayout::clear_spread(&cell_a1, &mut KeyPtr::from(root_key));
let cell_a3 = <LazyCell<u8>>::lazy(root_key);
assert_eq!(cell_a3.get(), None);
assert_eq!(
cell_a3.entry(),
Some(&StorageEntry::new(None, EntryState::Preserved))
);
Ok(())
})
}
#[test]
fn set_works() {
let mut cell = <LazyCell<i32>>::new(Some(1));
cell.set(23);
assert_eq!(cell.get(), Some(&23));
}
#[test]
fn lazy_set_works() -> ink_env::Result<()> {
run_test::<ink_env::DefaultEnvironment, _>(|_| {
let mut cell = <LazyCell<u8>>::lazy(Key::from([0x42; 32]));
let value = cell.get();
assert_eq!(value, None);
cell.set(13);
assert_eq!(cell.get(), Some(&13));
Ok(())
})
}
#[test]
fn lazy_set_works_with_spread_layout_push_pull() -> ink_env::Result<()> {
run_test::<ink_env::DefaultEnvironment, _>(|_| {
type MaybeValue = Option<u8>;
let k = Key::from([0x00; 32]);
let val: MaybeValue = None;
SpreadLayout::push_spread(&Lazy::new(val), &mut KeyPtr::from(k));
let mut v =
<Lazy<MaybeValue> as SpreadLayout>::pull_spread(&mut KeyPtr::from(k));
assert_eq!(*v, None);
let actual_value: MaybeValue = Some(13);
Lazy::set(&mut v, actual_value);
SpreadLayout::push_spread(&v, &mut KeyPtr::from(k));
let v2 =
<Lazy<MaybeValue> as SpreadLayout>::pull_spread(&mut KeyPtr::from(k));
assert_eq!(*v2, Some(13));
Ok(())
})
}
#[test]
fn regression_test_for_issue_528() -> ink_env::Result<()> {
run_test::<ink_env::DefaultEnvironment, _>(|_| {
let root_key = Key::from([0x00; 32]);
{
let pair = (LazyCell::new(Some(1i32)), 2i32);
SpreadLayout::push_spread(&pair, &mut KeyPtr::from(root_key));
}
{
let pulled_pair: (LazyCell<i32>, i32) =
SpreadLayout::pull_spread(&mut KeyPtr::from(root_key));
let mut pulled_pair = core::mem::ManuallyDrop::new(pulled_pair);
assert_eq!(pulled_pair.1, 2i32);
pulled_pair.1 = 3i32;
SpreadLayout::push_spread(&*pulled_pair, &mut KeyPtr::from(root_key));
}
{
let pulled_pair: (LazyCell<i32>, i32) =
SpreadLayout::pull_spread(&mut KeyPtr::from(root_key));
assert_eq!(pulled_pair.0.get(), Some(&1i32));
assert_eq!(pulled_pair.1, 3i32);
}
Ok(())
})
}
#[test]
fn regression_test_for_issue_570() -> ink_env::Result<()> {
run_test::<ink_env::DefaultEnvironment, _>(|_| {
let root_key = Key::from([0x00; 32]);
{
let v1: Option<u32> = None;
let v2: u32 = 13;
let mut ptr = KeyPtr::from(root_key);
SpreadLayout::push_spread(&v1, &mut ptr);
SpreadLayout::push_spread(&v2, &mut ptr);
}
{
let mut ptr = KeyPtr::from(root_key);
let pulled_v1: Option<u32> = SpreadLayout::pull_spread(&mut ptr);
let mut pulled_v1 = core::mem::ManuallyDrop::new(pulled_v1);
let pulled_v2: u32 = SpreadLayout::pull_spread(&mut ptr);
let pulled_v2 = core::mem::ManuallyDrop::new(pulled_v2);
assert_eq!(*pulled_v1, None);
assert_eq!(*pulled_v2, 13);
*pulled_v1 = Some(99u32);
SpreadLayout::push_spread(&*pulled_v1, &mut KeyPtr::from(root_key));
}
{
let mut ptr = KeyPtr::from(root_key);
let pulled_v1: Option<u32> = SpreadLayout::pull_spread(&mut ptr);
let pulled_v2: u32 = SpreadLayout::pull_spread(&mut ptr);
assert_eq!(pulled_v1, Some(99));
assert_eq!(pulled_v2, 13);
}
Ok(())
})
}
#[test]
fn second_regression_test_for_issue_570() -> ink_env::Result<()> {
run_test::<ink_env::DefaultEnvironment, _>(|_| {
let root_key = Key::from([0x00; 32]);
let none: Option<u32> = None;
let some: Option<u32> = Some(13);
let mut ptr_push_none = KeyPtr::from(root_key);
SpreadLayout::push_spread(&none, &mut ptr_push_none);
let mut ptr_pull_none = KeyPtr::from(root_key);
let v1: Option<u32> = SpreadLayout::pull_spread(&mut ptr_pull_none);
assert!(v1.is_none());
let mut ptr_clear_none = KeyPtr::from(root_key);
SpreadLayout::clear_spread(&none, &mut ptr_clear_none);
let mut ptr_push_some = KeyPtr::from(root_key);
SpreadLayout::push_spread(&some, &mut ptr_push_some);
let mut ptr_pull_some = KeyPtr::from(root_key);
let v2: Option<u32> = SpreadLayout::pull_spread(&mut ptr_pull_some);
assert!(v2.is_some());
let mut ptr_clear_some = KeyPtr::from(root_key);
SpreadLayout::clear_spread(&some, &mut ptr_clear_some);
let mut expected_post_op_ptr = KeyPtr::from(root_key);
expected_post_op_ptr.advance_by(1);
expected_post_op_ptr.advance_by(1);
assert_eq!(expected_post_op_ptr, ptr_push_none);
assert_eq!(ptr_push_none, ptr_push_some);
assert_eq!(expected_post_op_ptr, ptr_pull_none);
assert_eq!(ptr_pull_none, ptr_pull_some);
assert_eq!(expected_post_op_ptr, ptr_clear_none);
assert_eq!(ptr_clear_none, ptr_clear_some);
Ok(())
})
}
#[test]
#[should_panic(expected = "encountered empty storage cell")]
fn nested_lazies_are_cleared_completely_after_pull() {
ink_env::test::run_test::<ink_env::DefaultEnvironment, _>(|_| {
let root_key = Key::from([0x42; 32]);
let nested_lazy: Lazy<Lazy<u32>> = Lazy::new(Lazy::new(13u32));
SpreadLayout::push_spread(&nested_lazy, &mut KeyPtr::from(root_key));
let pulled_lazy = <Lazy<Lazy<u32>> as SpreadLayout>::pull_spread(
&mut KeyPtr::from(root_key),
);
SpreadLayout::clear_spread(&pulled_lazy, &mut KeyPtr::from(root_key));
let contract_id = ink_env::test::get_current_contract_account_id::<
ink_env::DefaultEnvironment,
>()
.expect("Cannot get contract id");
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 _ = *<Lazy<Lazy<u32>> as SpreadLayout>::pull_spread(&mut KeyPtr::from(
root_key,
));
Ok(())
})
.unwrap()
}
#[test]
#[should_panic(expected = "encountered empty storage cell")]
fn lazy_drop_works() {
ink_env::test::run_test::<ink_env::DefaultEnvironment, _>(|_| {
let root_key = Key::from([0x42; 32]);
let setup_result = std::panic::catch_unwind(|| {
let lazy: Lazy<u32> = Lazy::new(13u32);
SpreadLayout::push_spread(&lazy, &mut KeyPtr::from(root_key));
let _pulled_lazy =
<Lazy<u32> as SpreadLayout>::pull_spread(&mut KeyPtr::from(root_key));
});
assert!(setup_result.is_ok(), "setup should not panic");
let contract_id = ink_env::test::get_current_contract_account_id::<
ink_env::DefaultEnvironment,
>()
.expect("Cannot get contract id");
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 _ =
*<Lazy<u32> as SpreadLayout>::pull_spread(&mut KeyPtr::from(root_key));
Ok(())
})
.unwrap()
}
#[test]
#[should_panic(expected = "encountered empty storage cell")]
fn lazy_drop_works_with_greater_footprint() {
ink_env::test::run_test::<ink_env::DefaultEnvironment, _>(|_| {
let root_key = Key::from([0x42; 32]);
let setup_result = std::panic::catch_unwind(|| {
let lazy: Lazy<[u32; 5]> = Lazy::new([13, 14, 15, 16, 17]);
SpreadLayout::push_spread(&lazy, &mut KeyPtr::from(root_key));
let _pulled_lazy = <Lazy<[u32; 5]> as SpreadLayout>::pull_spread(
&mut KeyPtr::from(root_key),
);
});
assert!(setup_result.is_ok(), "setup should not panic");
let contract_id = ink_env::test::get_current_contract_account_id::<
ink_env::DefaultEnvironment,
>()
.expect("Cannot get contract id");
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 _ =
*<Lazy<u32> as SpreadLayout>::pull_spread(&mut KeyPtr::from(root_key));
Ok(())
})
.unwrap()
}
}