1#![cfg(feature = "iterator")]
2mod item;
3mod map;
4
5pub use item::SnapshotItem;
6pub use map::SnapshotMap;
7
8use crate::bound::Bound;
9use crate::de::KeyDeserialize;
10use crate::namespace::Namespace;
11use crate::{Map, Prefixer, PrimaryKey};
12use cosmwasm_std::{Order, StdError, StdResult, Storage};
13use serde::de::DeserializeOwned;
14use serde::{Deserialize, Serialize};
15
16#[derive(Debug, Clone)]
21pub(crate) struct Snapshot<K, T> {
22 checkpoints: Map<u64, u32>,
23
24 pub changelog: Map<(K, u64), ChangeSet<T>>,
27
28 strategy: Strategy,
30}
31
32impl<K, T> Snapshot<K, T> {
33 pub const fn new(
37 checkpoints: &'static str,
38 changelog: &'static str,
39 strategy: Strategy,
40 ) -> Snapshot<K, T> {
41 Snapshot {
42 checkpoints: Map::new(checkpoints),
43 changelog: Map::new(changelog),
44 strategy,
45 }
46 }
47
48 pub fn new_dyn(
52 checkpoints: impl Into<Namespace>,
53 changelog: impl Into<Namespace>,
54 strategy: Strategy,
55 ) -> Snapshot<K, T> {
56 Snapshot {
57 checkpoints: Map::new_dyn(checkpoints),
58 changelog: Map::new_dyn(changelog),
59 strategy,
60 }
61 }
62
63 pub fn add_checkpoint(&self, store: &mut dyn Storage, height: u64) -> StdResult<()> {
64 self.checkpoints
65 .update::<_, StdError>(store, height, |count| Ok(count.unwrap_or_default() + 1))?;
66 Ok(())
67 }
68
69 pub fn remove_checkpoint(&self, store: &mut dyn Storage, height: u64) -> StdResult<()> {
70 let count = self
71 .checkpoints
72 .may_load(store, height)?
73 .unwrap_or_default();
74 if count <= 1 {
75 self.checkpoints.remove(store, height);
76 Ok(())
77 } else {
78 self.checkpoints.save(store, height, &(count - 1))
79 }
80 }
81}
82
83impl<'a, K, T> Snapshot<K, T>
84where
85 T: Serialize + DeserializeOwned + Clone,
86 K: PrimaryKey<'a> + Prefixer<'a> + KeyDeserialize,
87{
88 pub fn should_checkpoint(&self, store: &dyn Storage, k: &K) -> StdResult<bool> {
90 match self.strategy {
91 Strategy::EveryBlock => Ok(true),
92 Strategy::Never => Ok(false),
93 Strategy::Selected => self.should_checkpoint_selected(store, k),
94 }
95 }
96
97 fn should_checkpoint_selected(&self, store: &dyn Storage, k: &K) -> StdResult<bool> {
99 let checkpoint = self
101 .checkpoints
102 .range(store, None, None, Order::Descending)
103 .next()
104 .transpose()?;
105 if let Some((height, _)) = checkpoint {
106 let start = Bound::inclusive(height);
108 let first = self
109 .changelog
110 .prefix(k.clone())
111 .range_raw(store, Some(start), None, Order::Ascending)
112 .next()
113 .transpose()?;
114 if first.is_none() {
115 return Ok(true);
117 }
118 }
119 Ok(false)
121 }
122
123 pub fn assert_checkpointed(&self, store: &dyn Storage, height: u64) -> StdResult<()> {
125 let has = match self.strategy {
126 Strategy::EveryBlock => true,
127 Strategy::Never => false,
128 Strategy::Selected => self.checkpoints.may_load(store, height)?.is_some(),
129 };
130 match has {
131 true => Ok(()),
132 false => Err(StdError::not_found("checkpoint")),
133 }
134 }
135
136 pub fn has_changelog(&self, store: &mut dyn Storage, key: K, height: u64) -> StdResult<bool> {
137 Ok(self.changelog.may_load(store, (key, height))?.is_some())
138 }
139
140 pub fn write_changelog(
141 &self,
142 store: &mut dyn Storage,
143 key: K,
144 height: u64,
145 old: Option<T>,
146 ) -> StdResult<()> {
147 self.changelog
148 .save(store, (key, height), &ChangeSet { old })
149 }
150
151 pub fn may_load_at_height(
157 &self,
158 store: &dyn Storage,
159 key: K,
160 height: u64,
161 ) -> StdResult<Option<Option<T>>> {
162 self.assert_checkpointed(store, height)?;
163
164 let start = Bound::inclusive(height);
167 let first = self
168 .changelog
169 .prefix(key)
170 .range_raw(store, Some(start), None, Order::Ascending)
171 .next();
172
173 if let Some(r) = first {
174 r.map(|(_, v)| Some(v.old))
176 } else {
177 Ok(None)
178 }
179 }
180}
181
182#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize)]
183pub enum Strategy {
184 EveryBlock,
185 Never,
186 Selected,
192}
193
194#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize)]
195pub struct ChangeSet<T> {
196 pub old: Option<T>,
197}
198
199#[cfg(test)]
200mod tests {
201 use super::*;
202 use cosmwasm_std::testing::MockStorage;
203
204 type TestSnapshot = Snapshot<&'static str, u64>;
205
206 const NEVER: TestSnapshot = Snapshot::new("never__check", "never__change", Strategy::Never);
207 const EVERY: TestSnapshot =
208 Snapshot::new("every__check", "every__change", Strategy::EveryBlock);
209 const SELECT: TestSnapshot =
210 Snapshot::new("select__check", "select__change", Strategy::Selected);
211
212 const DUMMY_KEY: &str = "dummy";
213
214 #[test]
215 fn should_checkpoint() {
216 let storage = MockStorage::new();
217
218 assert_eq!(NEVER.should_checkpoint(&storage, &DUMMY_KEY), Ok(false));
219 assert_eq!(EVERY.should_checkpoint(&storage, &DUMMY_KEY), Ok(true));
220 assert_eq!(SELECT.should_checkpoint(&storage, &DUMMY_KEY), Ok(false));
221 }
222
223 #[test]
224 fn assert_checkpointed() {
225 let mut storage = MockStorage::new();
226
227 assert_eq!(
228 NEVER.assert_checkpointed(&storage, 1),
229 Err(StdError::not_found("checkpoint"))
230 );
231 assert_eq!(EVERY.assert_checkpointed(&storage, 1), Ok(()));
232 assert_eq!(
233 SELECT.assert_checkpointed(&storage, 1),
234 Err(StdError::not_found("checkpoint"))
235 );
236
237 NEVER.add_checkpoint(&mut storage, 1).unwrap();
239 EVERY.add_checkpoint(&mut storage, 1).unwrap();
240 SELECT.add_checkpoint(&mut storage, 1).unwrap();
241
242 assert_eq!(
243 NEVER.assert_checkpointed(&storage, 1),
244 Err(StdError::not_found("checkpoint"))
245 );
246 assert_eq!(EVERY.assert_checkpointed(&storage, 1), Ok(()));
247 assert_eq!(SELECT.assert_checkpointed(&storage, 1), Ok(()));
248
249 NEVER.remove_checkpoint(&mut storage, 1).unwrap();
251 EVERY.remove_checkpoint(&mut storage, 1).unwrap();
252 SELECT.remove_checkpoint(&mut storage, 1).unwrap();
253
254 assert_eq!(
255 NEVER.assert_checkpointed(&storage, 1),
256 Err(StdError::not_found("checkpoint"))
257 );
258 assert_eq!(EVERY.assert_checkpointed(&storage, 1), Ok(()));
259 assert_eq!(
260 SELECT.assert_checkpointed(&storage, 1),
261 Err(StdError::not_found("checkpoint"))
262 );
263 }
264
265 #[test]
266 fn has_changelog() {
267 let mut storage = MockStorage::new();
268
269 assert_eq!(NEVER.has_changelog(&mut storage, DUMMY_KEY, 1), Ok(false));
270 assert_eq!(EVERY.has_changelog(&mut storage, DUMMY_KEY, 1), Ok(false));
271 assert_eq!(SELECT.has_changelog(&mut storage, DUMMY_KEY, 1), Ok(false));
272
273 assert_eq!(NEVER.has_changelog(&mut storage, DUMMY_KEY, 2), Ok(false));
274 assert_eq!(EVERY.has_changelog(&mut storage, DUMMY_KEY, 2), Ok(false));
275 assert_eq!(SELECT.has_changelog(&mut storage, DUMMY_KEY, 2), Ok(false));
276
277 assert_eq!(NEVER.has_changelog(&mut storage, DUMMY_KEY, 3), Ok(false));
278 assert_eq!(EVERY.has_changelog(&mut storage, DUMMY_KEY, 3), Ok(false));
279 assert_eq!(SELECT.has_changelog(&mut storage, DUMMY_KEY, 3), Ok(false));
280
281 NEVER
283 .write_changelog(&mut storage, DUMMY_KEY, 2, Some(3))
284 .unwrap();
285 EVERY
286 .write_changelog(&mut storage, DUMMY_KEY, 2, Some(4))
287 .unwrap();
288 SELECT
289 .write_changelog(&mut storage, DUMMY_KEY, 2, Some(5))
290 .unwrap();
291
292 assert_eq!(NEVER.has_changelog(&mut storage, DUMMY_KEY, 1), Ok(false));
293 assert_eq!(EVERY.has_changelog(&mut storage, DUMMY_KEY, 1), Ok(false));
294 assert_eq!(SELECT.has_changelog(&mut storage, DUMMY_KEY, 1), Ok(false));
295
296 assert_eq!(NEVER.has_changelog(&mut storage, DUMMY_KEY, 2), Ok(true));
297 assert_eq!(EVERY.has_changelog(&mut storage, DUMMY_KEY, 2), Ok(true));
298 assert_eq!(SELECT.has_changelog(&mut storage, DUMMY_KEY, 2), Ok(true));
299
300 assert_eq!(NEVER.has_changelog(&mut storage, DUMMY_KEY, 3), Ok(false));
301 assert_eq!(EVERY.has_changelog(&mut storage, DUMMY_KEY, 3), Ok(false));
302 assert_eq!(SELECT.has_changelog(&mut storage, DUMMY_KEY, 3), Ok(false));
303 }
304
305 #[test]
306 fn may_load_at_height() {
307 let mut storage = MockStorage::new();
308
309 assert_eq!(
310 NEVER.may_load_at_height(&storage, DUMMY_KEY, 3),
311 Err(StdError::not_found("checkpoint"))
312 );
313 assert_eq!(EVERY.may_load_at_height(&storage, DUMMY_KEY, 3), Ok(None));
314 assert_eq!(
315 SELECT.may_load_at_height(&storage, DUMMY_KEY, 3),
316 Err(StdError::not_found("checkpoint"))
317 );
318
319 NEVER.add_checkpoint(&mut storage, 3).unwrap();
321 EVERY.add_checkpoint(&mut storage, 3).unwrap();
322 SELECT.add_checkpoint(&mut storage, 3).unwrap();
323
324 assert_eq!(
325 NEVER.may_load_at_height(&storage, DUMMY_KEY, 3),
326 Err(StdError::not_found("checkpoint"))
327 );
328 assert_eq!(EVERY.may_load_at_height(&storage, DUMMY_KEY, 3), Ok(None));
329 assert_eq!(SELECT.may_load_at_height(&storage, DUMMY_KEY, 3), Ok(None));
330
331 NEVER
333 .write_changelog(&mut storage, DUMMY_KEY, 3, Some(100))
334 .unwrap();
335 EVERY
336 .write_changelog(&mut storage, DUMMY_KEY, 3, Some(101))
337 .unwrap();
338 SELECT
339 .write_changelog(&mut storage, DUMMY_KEY, 3, Some(102))
340 .unwrap();
341
342 assert_eq!(
343 NEVER.may_load_at_height(&storage, DUMMY_KEY, 3),
344 Err(StdError::not_found("checkpoint"))
345 );
346 assert_eq!(
347 EVERY.may_load_at_height(&storage, DUMMY_KEY, 3),
348 Ok(Some(Some(101)))
349 );
350 assert_eq!(
351 SELECT.may_load_at_height(&storage, DUMMY_KEY, 3),
352 Ok(Some(Some(102)))
353 );
354 assert_eq!(
357 NEVER.may_load_at_height(&storage, DUMMY_KEY, 2),
358 Err(StdError::not_found("checkpoint"))
359 );
360 assert_eq!(
361 EVERY.may_load_at_height(&storage, DUMMY_KEY, 2),
362 Ok(Some(Some(101)))
363 );
364 assert_eq!(
365 SELECT.may_load_at_height(&storage, DUMMY_KEY, 2),
366 Err(StdError::not_found("checkpoint"))
367 );
368
369 NEVER
371 .write_changelog(&mut storage, DUMMY_KEY, 4, None)
372 .unwrap();
373 EVERY
374 .write_changelog(&mut storage, DUMMY_KEY, 4, None)
375 .unwrap();
376 SELECT
377 .write_changelog(&mut storage, DUMMY_KEY, 4, None)
378 .unwrap();
379 NEVER.add_checkpoint(&mut storage, 4).unwrap();
381 EVERY.add_checkpoint(&mut storage, 4).unwrap();
382 SELECT.add_checkpoint(&mut storage, 4).unwrap();
383
384 assert_eq!(
385 NEVER.may_load_at_height(&storage, DUMMY_KEY, 4),
386 Err(StdError::not_found("checkpoint"))
387 );
388 assert_eq!(
389 EVERY.may_load_at_height(&storage, DUMMY_KEY, 4),
390 Ok(Some(None))
391 );
392 assert_eq!(
393 SELECT.may_load_at_height(&storage, DUMMY_KEY, 4),
394 Ok(Some(None))
395 );
396
397 assert_eq!(
399 NEVER.may_load_at_height(&storage, DUMMY_KEY, 3),
400 Err(StdError::not_found("checkpoint"))
401 );
402 assert_eq!(
403 EVERY.may_load_at_height(&storage, DUMMY_KEY, 3),
404 Ok(Some(Some(101)))
405 );
406 assert_eq!(
407 SELECT.may_load_at_height(&storage, DUMMY_KEY, 3),
408 Ok(Some(Some(102)))
409 );
410 }
411}