mod mapping;
mod vec;
#[doc(inline)]
pub use self::mapping::Mapping;
pub use self::vec::StorageVec;
use crate::traits::{
AutoKey,
StorableHint,
StorageKey,
};
use core::marker::PhantomData;
use ink_primitives::Key;
use ink_storage_traits::Storable;
use scale::{
Error,
Input,
Output,
};
#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
pub struct Lazy<V, KeyType: StorageKey = AutoKey> {
_marker: PhantomData<fn() -> (V, KeyType)>,
}
impl<V, KeyType> Default for Lazy<V, KeyType>
where
KeyType: StorageKey,
{
fn default() -> Self {
Self::new()
}
}
impl<V, KeyType> Lazy<V, KeyType>
where
KeyType: StorageKey,
{
pub const fn new() -> Self {
Self {
_marker: PhantomData,
}
}
}
impl<V, KeyType> core::fmt::Debug for Lazy<V, KeyType>
where
KeyType: StorageKey,
{
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
f.debug_struct("Lazy").field("key", &KeyType::KEY).finish()
}
}
impl<V, KeyType> Lazy<V, KeyType>
where
V: Storable,
KeyType: StorageKey,
{
pub fn get(&self) -> Option<V> {
match ink_env::get_contract_storage::<Key, V>(&KeyType::KEY) {
Ok(Some(value)) => Some(value),
_ => None,
}
}
pub fn try_get(&self) -> Option<ink_env::Result<V>> {
let key_size = <Key as Storable>::encoded_size(&KeyType::KEY);
if key_size >= ink_env::BUFFER_SIZE {
return Some(Err(ink_env::Error::BufferTooSmall));
}
let value_size: usize = ink_env::contains_contract_storage(&KeyType::KEY)?
.try_into()
.expect("targets of less than 32bit pointer size are not supported; qed");
if key_size.saturating_add(value_size) > ink_env::BUFFER_SIZE {
return Some(Err(ink_env::Error::BufferTooSmall));
}
self.get().map(Ok)
}
pub fn set(&mut self, value: &V) {
ink_env::set_contract_storage::<Key, V>(&KeyType::KEY, value);
}
pub fn try_set(&mut self, value: &V) -> ink_env::Result<()> {
let key_size = <Key as Storable>::encoded_size(&KeyType::KEY);
let value_size = <V as Storable>::encoded_size(value);
if key_size.saturating_add(value_size) > ink_env::BUFFER_SIZE {
return Err(ink_env::Error::BufferTooSmall);
};
self.set(value);
Ok(())
}
}
impl<V, KeyType> Lazy<V, KeyType>
where
V: Storable + Default,
KeyType: StorageKey,
{
pub fn get_or_default(&self) -> V {
match ink_env::get_contract_storage::<Key, V>(&KeyType::KEY) {
Ok(Some(value)) => value,
_ => Default::default(),
}
}
}
impl<V, KeyType> Storable for Lazy<V, KeyType>
where
KeyType: StorageKey,
{
#[inline(always)]
fn encode<T: Output + ?Sized>(&self, _dest: &mut T) {}
#[inline(always)]
fn decode<I: Input>(_input: &mut I) -> Result<Self, Error> {
Ok(Default::default())
}
#[inline(always)]
fn encoded_size(&self) -> usize {
0
}
}
impl<V, Key, InnerKey> StorableHint<Key> for Lazy<V, InnerKey>
where
Key: StorageKey,
InnerKey: StorageKey,
V: StorableHint<Key>,
{
type Type = Lazy<V::Type, Key>;
type PreferredKey = InnerKey;
}
impl<V, KeyType> StorageKey for Lazy<V, KeyType>
where
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 Lazy<V, KeyType>
where
V: 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>(),
))
}
}
};
#[cfg(test)]
mod tests {
use super::*;
use crate::traits::ManualKey;
#[test]
fn set_and_get_work() {
ink_env::test::run_test::<ink_env::DefaultEnvironment, _>(|_| {
let mut storage: Lazy<u8> = Lazy::new();
storage.set(&2);
assert_eq!(storage.get(), Some(2));
Ok(())
})
.unwrap()
}
#[test]
fn set_and_get_work_for_two_lazy_with_same_manual_key() {
ink_env::test::run_test::<ink_env::DefaultEnvironment, _>(|_| {
let mut storage: Lazy<u8, ManualKey<123>> = Lazy::new();
storage.set(&2);
let storage2: Lazy<u8, ManualKey<123>> = Lazy::new();
assert_eq!(storage2.get(), Some(2));
Ok(())
})
.unwrap()
}
#[test]
fn gets_or_default_if_no_key_set() {
ink_env::test::run_test::<ink_env::DefaultEnvironment, _>(|_| {
let storage: Lazy<u8> = Lazy::new();
assert_eq!(storage.get_or_default(), 0);
Ok(())
})
.unwrap()
}
#[test]
fn gets_returns_none_if_no_value_was_set() {
ink_env::test::run_test::<ink_env::DefaultEnvironment, _>(|_| {
let storage: Lazy<u8> = Lazy::new();
assert_eq!(storage.get(), None);
Ok(())
})
.unwrap()
}
#[test]
fn fallible_storage_works_for_fitting_data() {
ink_env::test::run_test::<ink_env::DefaultEnvironment, _>(|_| {
const KEY_SIZE: usize = 4;
const VALUE_SIZE: usize = ink_env::BUFFER_SIZE - KEY_SIZE;
let mut storage: Lazy<[u8; VALUE_SIZE]> = Lazy::new();
let value = [0u8; VALUE_SIZE];
assert_eq!(storage.try_set(&value), Ok(()));
assert_eq!(storage.try_get(), Some(Ok(value)));
Ok(())
})
.unwrap()
}
#[test]
fn fallible_storage_fails_gracefully_for_overgrown_data() {
ink_env::test::run_test::<ink_env::DefaultEnvironment, _>(|_| {
const KEY_SIZE: usize = 4;
const VALUE_SIZE: usize = ink_env::BUFFER_SIZE - KEY_SIZE + 1;
let mut storage: Lazy<[u8; VALUE_SIZE]> = Lazy::new();
let value = [0u8; VALUE_SIZE];
assert_eq!(storage.try_get(), None);
assert_eq!(storage.try_set(&value), Err(ink_env::Error::BufferTooSmall));
ink_env::set_contract_storage(&storage.key(), &value);
assert_eq!(storage.try_get(), Some(Err(ink_env::Error::BufferTooSmall)));
Ok(())
})
.unwrap()
}
}