unc_sdk/store/lazy/
mod.rs1mod impls;
8
9use borsh::{to_vec, BorshDeserialize, BorshSerialize};
10use once_cell::unsync::OnceCell;
11
12use unc_sdk_macros::unc;
13
14use crate::env;
15use crate::store::ERR_INCONSISTENT_STATE;
16use crate::utils::{CacheEntry, EntryState};
17use crate::IntoStorageKey;
18
19const ERR_VALUE_SERIALIZATION: &str = "Cannot serialize value with Borsh";
20const ERR_VALUE_DESERIALIZATION: &str = "Cannot deserialize value with Borsh";
21const ERR_NOT_FOUND: &str = "No value found for the given key";
22
23fn expect_key_exists<T>(val: Option<T>) -> T {
24 val.unwrap_or_else(|| env::panic_str(ERR_NOT_FOUND))
25}
26
27fn expect_consistent_state<T>(val: Option<T>) -> T {
28 val.unwrap_or_else(|| env::panic_str(ERR_INCONSISTENT_STATE))
29}
30
31pub(crate) fn load_and_deserialize<T>(key: &[u8]) -> CacheEntry<T>
32where
33 T: BorshDeserialize,
34{
35 let bytes = expect_key_exists(env::storage_read(key));
36 let val =
37 T::try_from_slice(&bytes).unwrap_or_else(|_| env::panic_str(ERR_VALUE_DESERIALIZATION));
38 CacheEntry::new_cached(Some(val))
39}
40
41pub(crate) fn serialize_and_store<T>(key: &[u8], value: &T)
42where
43 T: BorshSerialize,
44{
45 let serialized = to_vec(value).unwrap_or_else(|_| env::panic_str(ERR_VALUE_SERIALIZATION));
46 env::storage_write(key, &serialized);
47}
48
49#[unc(inside_uncsdk)]
65pub struct Lazy<T>
66where
67 T: BorshSerialize,
68{
69 storage_key: Box<[u8]>,
71 #[borsh(skip, bound(deserialize = ""))] cache: OnceCell<CacheEntry<T>>,
74}
75
76impl<T> Lazy<T>
77where
78 T: BorshSerialize,
79{
80 pub fn new<S>(key: S, value: T) -> Self
86 where
87 S: IntoStorageKey,
88 {
89 Self {
90 storage_key: key.into_storage_key().into_boxed_slice(),
91 cache: OnceCell::from(CacheEntry::new_modified(Some(value))),
92 }
93 }
94
95 pub fn set(&mut self, value: T) {
97 if let Some(v) = self.cache.get_mut() {
98 *v.value_mut() = Some(value);
99 } else {
100 self.cache
101 .set(CacheEntry::new_modified(Some(value)))
102 .unwrap_or_else(|_| env::panic_str("cache is checked to not be filled above"))
103 }
104 }
105
106 pub fn flush(&mut self) {
110 if let Some(v) = self.cache.get_mut() {
111 if v.is_modified() {
112 let value = expect_consistent_state(v.value().as_ref());
114 serialize_and_store(&self.storage_key, value);
115
116 v.replace_state(EntryState::Cached);
119 }
120 }
121 }
122}
123
124impl<T> Lazy<T>
125where
126 T: BorshSerialize + BorshDeserialize,
127{
128 pub fn get(&self) -> &T {
134 let entry = self.cache.get_or_init(|| load_and_deserialize(&self.storage_key));
135
136 expect_consistent_state(entry.value().as_ref())
137 }
138
139 pub fn get_mut(&mut self) -> &mut T {
145 self.cache.get_or_init(|| load_and_deserialize(&self.storage_key));
146 let entry = self.cache.get_mut().unwrap_or_else(|| env::abort());
147
148 expect_consistent_state(entry.value_mut().as_mut())
149 }
150}
151
152#[cfg(not(target_arch = "wasm32"))]
153#[cfg(test)]
154mod tests {
155 use super::*;
156
157 #[test]
158 pub fn test_lazy() {
159 let mut a = Lazy::new(b"a", 8u32);
160 assert_eq!(a.get(), &8);
161
162 assert!(!env::storage_has_key(b"a"));
163 a.flush();
164 assert_eq!(u32::try_from_slice(&env::storage_read(b"a").unwrap()).unwrap(), 8);
165
166 a.set(42);
167
168 assert_eq!(u32::try_from_slice(&env::storage_read(b"a").unwrap()).unwrap(), 8);
170 assert_eq!(*a, 42);
171
172 *a = 30;
173 let serialized = to_vec(&a).unwrap();
174 drop(a);
175 assert_eq!(u32::try_from_slice(&env::storage_read(b"a").unwrap()).unwrap(), 30);
176
177 let lazy_loaded = Lazy::<u32>::try_from_slice(&serialized).unwrap();
178 assert!(lazy_loaded.cache.get().is_none());
179
180 let b = Lazy::new(b"b", 30);
181 assert!(!env::storage_has_key(b"b"));
182
183 assert_eq!(lazy_loaded, b);
186 }
187
188 #[test]
189 pub fn test_debug() {
190 let mut lazy = Lazy::new(b"m", 8u8);
191 if cfg!(feature = "expensive-debug") {
192 assert_eq!(format!("{:?}", lazy), "8");
193 } else {
194 assert_eq!(format!("{:?}", lazy), "Lazy { storage_key: [109], cache: Some(CacheEntry { value: Some(8), state: Modified }) }");
195 }
196
197 lazy.flush();
198 if !cfg!(feature = "expensive-debug") {
199 assert_eq!(format!("{:?}", lazy), "Lazy { storage_key: [109], cache: Some(CacheEntry { value: Some(8), state: Cached }) }");
200 }
201
202 let serialized = borsh::to_vec(&lazy).unwrap();
204 drop(lazy);
205 let lazy = Lazy::<u8>::try_from_slice(&serialized).unwrap();
206 if cfg!(feature = "expensive-debug") {
207 assert_eq!(format!("{:?}", lazy), "8");
208 } else {
209 assert_eq!(format!("{:?}", lazy), "Lazy { storage_key: [109], cache: None }");
210 }
211 }
212}