Skip to content

Commit

Permalink
only use block heights in cw-snapshot-vector-map
Browse files Browse the repository at this point in the history
  • Loading branch information
NoahSaso committed Oct 11, 2024
1 parent ab14d73 commit 92fed36
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 103 deletions.
4 changes: 0 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 0 additions & 3 deletions packages/cw-snapshot-vector-map/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@ repository = { workspace = true }
version = { workspace = true }

[dependencies]
cosmwasm-schema = { workspace = true }
cosmwasm-std = { workspace = true }
cw-storage-plus = { workspace = true }
cw-utils = { workspace = true }
cw20 = { workspace = true }
serde = { workspace = true }
35 changes: 10 additions & 25 deletions packages/cw-snapshot-vector-map/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,21 +44,9 @@ compact ID storage.
## Example

```rust
use cosmwasm_std::{testing::mock_dependencies, Addr, BlockInfo, Timestamp};
use cw20::Expiration;
use cw_utils::Duration;
use cosmwasm_std::{testing::mock_dependencies, Addr};
use cw_snapshot_vector_map::{LoadedItem, SnapshotVectorMap};

macro_rules! b {
($x:expr) => {
&BlockInfo {
chain_id: "CHAIN".to_string(),
height: $x,
time: Timestamp::from_seconds($x),
}
};
}

let storage = &mut mock_dependencies().storage;
let svm: SnapshotVectorMap<Addr, String> = SnapshotVectorMap::new(
"svm__items",
Expand All @@ -72,32 +60,32 @@ let first = "first".to_string();
let second = "second".to_string();

// store the first item at block 1, expiring in 10 blocks (at block 11)
svm.push(storage, &key, &first, b!(1), Some(Duration::Height(10))).unwrap();
svm.push(storage, &key, &first, 1, Some(10)).unwrap();

// store the second item at block 5, which does not expire
svm.push(storage, &key, &second, b!(5), None).unwrap();
svm.push(storage, &key, &second, 5, None).unwrap();

// remove the second item (ID: 1) at height 15
svm.remove(storage, &key, 1, b!(15)).unwrap();
svm.remove(storage, &key, 1, 15).unwrap();

// the vector at block 3 should contain only the first item
assert_eq!(
svm.load_all(storage, &key, b!(3)).unwrap(),
svm.load_all(storage, &key, 3).unwrap(),
vec![LoadedItem {
id: 0,
item: first.clone(),
expiration: Some(Expiration::AtHeight(11)),
expiration: Some(11),
}]
);

// the vector at block 7 should contain both items
assert_eq!(
svm.load_all(storage, &key, b!(7)).unwrap(),
svm.load_all(storage, &key, 7).unwrap(),
vec![
LoadedItem {
id: 0,
item: first.clone(),
expiration: Some(Expiration::AtHeight(11)),
expiration: Some(11),
},
LoadedItem {
id: 1,
Expand All @@ -109,7 +97,7 @@ assert_eq!(

// the vector at block 12 should contain only the first item
assert_eq!(
svm.load_all(storage, &key, b!(12)).unwrap(),
svm.load_all(storage, &key, 12).unwrap(),
vec![LoadedItem {
id: 1,
item: second.clone(),
Expand All @@ -118,8 +106,5 @@ assert_eq!(
);

// the vector at block 17 should contain nothing
assert_eq!(
svm.load_all(storage, &key, b!(17)).unwrap(),
vec![]
);
assert_eq!(svm.load_all(storage, &key, 17).unwrap(), vec![]);
```
50 changes: 24 additions & 26 deletions packages/cw-snapshot-vector-map/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]

use cw20::Expiration;
use cw_utils::Duration;
use serde::de::DeserializeOwned;
use serde::Serialize;

use cosmwasm_std::{BlockInfo, StdResult, Storage};
use cosmwasm_std::{StdResult, Storage};
use cw_storage_plus::{KeyDeserialize, Map, Prefixer, PrimaryKey, SnapshotMap, Strategy};

/// Map to a vector that allows reading the subset of items that existed at a
Expand All @@ -17,8 +15,8 @@ pub struct SnapshotVectorMap<'a, K, V> {
/// The next item ID to use per-key.
next_ids: Map<'a, K, u64>,
/// The IDs of the items that are active for a key at a given height, and
/// optionally when they expire.
active: SnapshotMap<'a, K, Vec<(u64, Option<Expiration>)>>,
/// optionally the height at which they expire.
active: SnapshotMap<'a, K, Vec<(u64, Option<u64>)>>,
}

/// A loaded item from the vector, including its ID and expiration.
Expand All @@ -29,8 +27,8 @@ pub struct LoadedItem<V> {
pub id: u64,
/// The item.
pub item: V,
/// When the item expires, if set.
pub expiration: Option<Expiration>,
/// The block height at which the item expires, if set.
pub expiration: Option<u64>,
}

impl<'a, K, V> SnapshotVectorMap<'a, K, V> {
Expand Down Expand Up @@ -78,17 +76,17 @@ where
// &(key, ID) is a key in a map
for<'b> &'b (K, u64): PrimaryKey<'b>,
{
/// Adds an item to the vector at the current block, optionally expiring in
/// the future, returning the ID of the new item. This block should be
/// greater than or equal to the blocks all previous items were
/// Adds an item to the vector at the current block height, optionally
/// expiring in the future, returning the ID of the new item. This block
/// should be greater than or equal to the blocks all previous items were
/// added/removed at. Pushing to the past will lead to incorrect behavior.
pub fn push(
&self,
store: &mut dyn Storage,
k: &K,
data: &V,
block: &BlockInfo,
expire_in: Option<Duration>,
curr_height: u64,
expire_in: Option<u64>,
) -> StdResult<u64> {
// get next ID for the key, defaulting to 0
let next_id = self
Expand All @@ -104,42 +102,42 @@ where

// remove expired items
active.retain(|(_, expiration)| {
expiration.map_or(true, |expiration| !expiration.is_expired(block))
expiration.map_or(true, |expiration| expiration > curr_height)
});

// add new item and save list
active.push((next_id, expire_in.map(|d| d.after(block))));
active.push((next_id, expire_in.map(|d| curr_height + d)));

// save the new list
self.active.save(store, k.clone(), &active, block.height)?;
self.active.save(store, k.clone(), &active, curr_height)?;

// update next ID
self.next_ids.save(store, k.clone(), &(next_id + 1))?;

Ok(next_id)
}

/// Removes an item from the vector by ID and returns it. The block should
/// be greater than or equal to the blocks all previous items were
/// Removes an item from the vector by ID and returns it. The block height
/// should be greater than or equal to the blocks all previous items were
/// added/removed at. Removing from the past will lead to incorrect
/// behavior.
pub fn remove(
&self,
store: &mut dyn Storage,
k: &K,
id: u64,
block: &BlockInfo,
curr_height: u64,
) -> StdResult<V> {
// get active list for the key
let mut active = self.active.may_load(store, k.clone())?.unwrap_or_default();

// remove item and any expired items
active.retain(|(active_id, expiration)| {
active_id != &id && expiration.map_or(true, |expiration| !expiration.is_expired(block))
active_id != &id && expiration.map_or(true, |expiration| expiration > curr_height)
});

// save the new list
self.active.save(store, k.clone(), &active, block.height)?;
self.active.save(store, k.clone(), &active, curr_height)?;

// load and return the item
self.load_item(store, k, id)
Expand All @@ -150,7 +148,7 @@ where
&self,
store: &dyn Storage,
k: &K,
block: &BlockInfo,
height: u64,
limit: Option<u64>,
offset: Option<u64>,
) -> StdResult<Vec<LoadedItem<V>>> {
Expand All @@ -159,13 +157,13 @@ where

let active_ids = self
.active
.may_load_at_height(store, k.clone(), block.height)?
.may_load_at_height(store, k.clone(), height)?
.unwrap_or_default();

// load paged items, skipping expired ones
let items = active_ids
.iter()
.filter(|(_, expiration)| expiration.map_or(true, |exp| !exp.is_expired(block)))
.filter(|(_, expiration)| expiration.map_or(true, |exp| exp > height))
.skip(offset)
.take(limit)
.map(|(id, expiration)| -> StdResult<LoadedItem<V>> {
Expand All @@ -181,14 +179,14 @@ where
Ok(items)
}

/// Loads all items at the given block that are not expired.
/// Loads all items at the given block height that are not expired.
pub fn load_all(
&self,
store: &dyn Storage,
k: &K,
block: &BlockInfo,
height: u64,
) -> StdResult<Vec<LoadedItem<V>>> {
self.load(store, k, block, None, None)
self.load(store, k, height, None, None)
}

/// Loads an item from the vector by ID.
Expand Down
Loading

0 comments on commit 92fed36

Please sign in to comment.