mod impls;
use borsh::{to_vec, BorshDeserialize, BorshSerialize};
use once_cell::unsync::OnceCell;
use unc_sdk_macros::unc;
use crate::env;
use crate::store::ERR_INCONSISTENT_STATE;
use crate::utils::{CacheEntry, EntryState};
use crate::IntoStorageKey;
const ERR_VALUE_SERIALIZATION: &str = "Cannot serialize value with Borsh";
const ERR_VALUE_DESERIALIZATION: &str = "Cannot deserialize value with Borsh";
const ERR_NOT_FOUND: &str = "No value found for the given key";
fn expect_key_exists<T>(val: Option<T>) -> T {
val.unwrap_or_else(|| env::panic_str(ERR_NOT_FOUND))
}
fn expect_consistent_state<T>(val: Option<T>) -> T {
val.unwrap_or_else(|| env::panic_str(ERR_INCONSISTENT_STATE))
}
pub(crate) fn load_and_deserialize<T>(key: &[u8]) -> CacheEntry<T>
where
T: BorshDeserialize,
{
let bytes = expect_key_exists(env::storage_read(key));
let val =
T::try_from_slice(&bytes).unwrap_or_else(|_| env::panic_str(ERR_VALUE_DESERIALIZATION));
CacheEntry::new_cached(Some(val))
}
pub(crate) fn serialize_and_store<T>(key: &[u8], value: &T)
where
T: BorshSerialize,
{
let serialized = to_vec(value).unwrap_or_else(|_| env::panic_str(ERR_VALUE_SERIALIZATION));
env::storage_write(key, &serialized);
}
#[unc(inside_uncsdk)]
pub struct Lazy<T>
where
T: BorshSerialize,
{
storage_key: Box<[u8]>,
#[borsh(skip, bound(deserialize = ""))] cache: OnceCell<CacheEntry<T>>,
}
impl<T> Lazy<T>
where
T: BorshSerialize,
{
pub fn new<S>(key: S, value: T) -> Self
where
S: IntoStorageKey,
{
Self {
storage_key: key.into_storage_key().into_boxed_slice(),
cache: OnceCell::from(CacheEntry::new_modified(Some(value))),
}
}
pub fn set(&mut self, value: T) {
if let Some(v) = self.cache.get_mut() {
*v.value_mut() = Some(value);
} else {
self.cache
.set(CacheEntry::new_modified(Some(value)))
.unwrap_or_else(|_| env::panic_str("cache is checked to not be filled above"))
}
}
pub fn flush(&mut self) {
if let Some(v) = self.cache.get_mut() {
if v.is_modified() {
let value = expect_consistent_state(v.value().as_ref());
serialize_and_store(&self.storage_key, value);
v.replace_state(EntryState::Cached);
}
}
}
}
impl<T> Lazy<T>
where
T: BorshSerialize + BorshDeserialize,
{
pub fn get(&self) -> &T {
let entry = self.cache.get_or_init(|| load_and_deserialize(&self.storage_key));
expect_consistent_state(entry.value().as_ref())
}
pub fn get_mut(&mut self) -> &mut T {
self.cache.get_or_init(|| load_and_deserialize(&self.storage_key));
let entry = self.cache.get_mut().unwrap_or_else(|| env::abort());
expect_consistent_state(entry.value_mut().as_mut())
}
}
#[cfg(not(target_arch = "wasm32"))]
#[cfg(test)]
mod tests {
use super::*;
#[test]
pub fn test_lazy() {
let mut a = Lazy::new(b"a", 8u32);
assert_eq!(a.get(), &8);
assert!(!env::storage_has_key(b"a"));
a.flush();
assert_eq!(u32::try_from_slice(&env::storage_read(b"a").unwrap()).unwrap(), 8);
a.set(42);
assert_eq!(u32::try_from_slice(&env::storage_read(b"a").unwrap()).unwrap(), 8);
assert_eq!(*a, 42);
*a = 30;
let serialized = to_vec(&a).unwrap();
drop(a);
assert_eq!(u32::try_from_slice(&env::storage_read(b"a").unwrap()).unwrap(), 30);
let lazy_loaded = Lazy::<u32>::try_from_slice(&serialized).unwrap();
assert!(lazy_loaded.cache.get().is_none());
let b = Lazy::new(b"b", 30);
assert!(!env::storage_has_key(b"b"));
assert_eq!(lazy_loaded, b);
}
#[test]
pub fn test_debug() {
let mut lazy = Lazy::new(b"m", 8u8);
if cfg!(feature = "expensive-debug") {
assert_eq!(format!("{:?}", lazy), "8");
} else {
assert_eq!(format!("{:?}", lazy), "Lazy { storage_key: [109], cache: Some(CacheEntry { value: Some(8), state: Modified }) }");
}
lazy.flush();
if !cfg!(feature = "expensive-debug") {
assert_eq!(format!("{:?}", lazy), "Lazy { storage_key: [109], cache: Some(CacheEntry { value: Some(8), state: Cached }) }");
}
let serialized = borsh::to_vec(&lazy).unwrap();
drop(lazy);
let lazy = Lazy::<u8>::try_from_slice(&serialized).unwrap();
if cfg!(feature = "expensive-debug") {
assert_eq!(format!("{:?}", lazy), "8");
} else {
assert_eq!(format!("{:?}", lazy), "Lazy { storage_key: [109], cache: None }");
}
}
}