diff --git a/.github/workflows/doc-test.yml b/.github/workflows/doc-test.yml index e6efc0bf..fbbf4127 100644 --- a/.github/workflows/doc-test.yml +++ b/.github/workflows/doc-test.yml @@ -27,6 +27,8 @@ jobs: cargo update -p cosmwasm-schema cargo update -p cosmwasm-std cargo update -p cw2 + cargo update -p cw-storage-plus + cargo update -p serde - uses: Swatinem/rust-cache@v2 with: workspaces: | diff --git a/docs-test-gen/Cargo.lock b/docs-test-gen/Cargo.lock index b46f9f31..5df94131 100644 --- a/docs-test-gen/Cargo.lock +++ b/docs-test-gen/Cargo.lock @@ -416,6 +416,7 @@ dependencies = [ "cw2", "glob", "phf", + "serde", "sha2 0.10.8", "strum", ] diff --git a/docs-test-gen/Cargo.toml b/docs-test-gen/Cargo.toml index 333bd21d..6784b20f 100644 --- a/docs-test-gen/Cargo.toml +++ b/docs-test-gen/Cargo.toml @@ -18,3 +18,4 @@ cosmwasm-std = { version = "*", features = ["stargate", "staking", "cosmwasm_2_0 sha2 = "0.10.8" cosmos-sdk-proto = { version = "0.21.1", default-features = false } # Used in IBC code cw-storage-plus = "*" +serde = "*" diff --git a/docs-test-gen/src/main.rs b/docs-test-gen/src/main.rs index fde8e5b6..aa8ca9de 100644 --- a/docs-test-gen/src/main.rs +++ b/docs-test-gen/src/main.rs @@ -10,6 +10,7 @@ static TEMPLATES: phf::Map<&'static str, &'static str> = phf_map! { "execute" => include_str!("../templates/execute.tpl"), "instantiate-spec" => include_str!("../templates/instantiate-spec.tpl"), "ibc-channel" => include_str!("../templates/ibc-channel.tpl"), + "storage" => include_str!("../templates/storage.tpl"), }; #[inline] diff --git a/docs-test-gen/templates/storage.tpl b/docs-test-gen/templates/storage.tpl new file mode 100644 index 00000000..060159e5 --- /dev/null +++ b/docs-test-gen/templates/storage.tpl @@ -0,0 +1,15 @@ +#[allow(unused_imports)] +mod imports { + pub use cosmwasm_std::*; + pub use cosmwasm_schema::cw_serde; +} + +#[allow(unused_imports)] +use imports::*; + +#[test] +fn doctest() { + #[allow(unused_mut)] + let mut storage = cosmwasm_std::testing::MockStorage::new(); + {{code}} +} diff --git a/scripts/watch.sh b/scripts/watch.sh new file mode 100755 index 00000000..27fe5fad --- /dev/null +++ b/scripts/watch.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -o nounset -o pipefail + +inotifywait -e modify,move,create,delete --recursive --monitor --format "%e %w%f" src | while read changed; do echo $changed; scripts/test-rust.sh; done diff --git a/src/pages/cw-storage-plus/_meta.json b/src/pages/cw-storage-plus/_meta.json index 91e1e445..7cbef22b 100644 --- a/src/pages/cw-storage-plus/_meta.json +++ b/src/pages/cw-storage-plus/_meta.json @@ -1,7 +1,6 @@ { "basics": "Basics", "containers": "Containers", - "iteration": "Iteration", "snapshots": "Snapshots", "multi-indexes": "Multi index collections" } diff --git a/src/pages/cw-storage-plus/containers/deque.mdx b/src/pages/cw-storage-plus/containers/deque.mdx index 0807a616..d43884ae 100644 --- a/src/pages/cw-storage-plus/containers/deque.mdx +++ b/src/pages/cw-storage-plus/containers/deque.mdx @@ -1,7 +1,104 @@ +import { Callout } from "nextra/components"; + # `Deque` -## Overview +A `Deque` imitates a traditional double-ended queue. This collection is designed +for efficient pushes and pops from either the beginning or end, but not for +insertions/deletions from the middle. It can easily serve as a queue or stack. + +The main operations available here are [`push_back`], [`push_front`], +[`pop_back`], and [`pop_front`]. It is also possible to check the [`len`]gth of +the deque, [`get`] an element by index, and [`iter`]ate over the elements. + +[`push_back`]: + https://docs.rs/cw-storage-plus/latest/cw_storage_plus/struct.Deque.html#method.push_back +[`push_front`]: + https://docs.rs/cw-storage-plus/latest/cw_storage_plus/struct.Deque.html#method.push_front +[`pop_back`]: + https://docs.rs/cw-storage-plus/latest/cw_storage_plus/struct.Deque.html#method.pop_back +[`pop_front`]: + https://docs.rs/cw-storage-plus/latest/cw_storage_plus/struct.Deque.html#method.pop_front +[`len`]: + https://docs.rs/cw-storage-plus/latest/cw_storage_plus/struct.Deque.html#method.len +[`get`]: + https://docs.rs/cw-storage-plus/latest/cw_storage_plus/struct.Deque.html#method.get +[`iter`]: + https://docs.rs/cw-storage-plus/latest/cw_storage_plus/struct.Deque.html#method.iter + + + The maximum capacity of a `Deque` is `u32::MAX - 1` elements. Trying to push + more elements is considered Undefined Behaviorđź’€. + + +More information can be found in the +[API docs](https://docs.rs/cw-storage-plus/latest/cw_storage_plus/struct.Deque.html). ## Examples -### ? +### Pushing and popping + +```rust template="storage" +use cw_storage_plus::Deque; + +let deque: Deque = Deque::new("d"); + +deque.push_back(&mut storage, &2).unwrap(); +deque.push_back(&mut storage, &3).unwrap(); +deque.push_front(&mut storage, &1).unwrap(); + +// at this point, we have [1, 2, 3] + +assert_eq!(deque.pop_back(&mut storage).unwrap(), Some(3)); +assert_eq!(deque.pop_front(&mut storage).unwrap(), Some(1)); +assert_eq!(deque.pop_back(&mut storage).unwrap(), Some(2)); +assert_eq!(deque.pop_back(&mut storage).unwrap(), None); +``` + +### Checking length + +```rust template="storage" +use cw_storage_plus::Deque; + +let deque: Deque = Deque::new("d"); + +assert_eq!(deque.len(&storage).unwrap(), 0); + +deque.push_back(&mut storage, &1).unwrap(); +deque.push_back(&mut storage, &2).unwrap(); + +assert_eq!(deque.len(&storage).unwrap(), 2); +``` + +### Getting an element by index + +```rust template="storage" +use cw_storage_plus::Deque; + +let deque: Deque = Deque::new("d"); + +deque.push_back(&mut storage, &1).unwrap(); +deque.push_back(&mut storage, &2).unwrap(); + +assert_eq!(deque.get(&storage, 0).unwrap(), Some(1)); +assert_eq!(deque.get(&storage, 1).unwrap(), Some(2)); +assert_eq!(deque.get(&storage, 2).unwrap(), None); +``` + +### Iterating over elements + +```rust template="storage" +use cw_storage_plus::Deque; + +let deque: Deque = Deque::new("d"); + +deque.push_back(&mut storage, &2).unwrap(); +deque.push_back(&mut storage, &3).unwrap(); +deque.push_front(&mut storage, &1).unwrap(); + +let mut iter = deque.iter(&storage).unwrap(); + +assert_eq!(iter.next(), Some(Ok(1))); +assert_eq!(iter.next(), Some(Ok(2))); +assert_eq!(iter.next(), Some(Ok(3))); +assert_eq!(iter.next(), None); +``` diff --git a/src/pages/cw-storage-plus/containers/item.mdx b/src/pages/cw-storage-plus/containers/item.mdx index 908d5398..25973346 100644 --- a/src/pages/cw-storage-plus/containers/item.mdx +++ b/src/pages/cw-storage-plus/containers/item.mdx @@ -1,9 +1,80 @@ # `Item` -## Overview +An `Item` is a container that stores a single value under a specific key in +storage. -## Examples +Merely constructing the `Item` object does not commit anything to storage. If an +`Item` has never been written to before (or the value has been +[removed](https://docs.rs/cw-storage-plus/latest/cw_storage_plus/struct.Item.html#method.remove)), +it will be empty. + +Under the hood, values are serialized with [`serde`](https://serde.rs/) and +[`serde_json_wasm`](https://docs.rs/serde_json_wasm/). + +Use `save` to write to an `Item`. + +Use `load` to read from the `Item`, producing an error if the `Item` is empty or +if deserialization fails. + +Use `may_load` if you want to explicitly handle the possibility the `Item` is +empty - this will produce an +[`StdError`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/enum.StdError.html) +if deserialization fails, but produce an `Ok(None)` if it is empty. + +More information can be found in the +[API docs](https://docs.rs/cw-storage-plus/latest/cw_storage_plus/struct.Item.html). + +## Usage examples ### Saving an admin address -### Saving a config structure +```rust template="storage" +use cw_storage_plus::Item; + +let admin: Item = Item::new("a"); +assert_eq!(admin.may_load(&storage).unwrap(), None); + +admin.save(&mut storage, &"some_address".to_string()).unwrap(); +assert_eq!(admin.load(&storage).unwrap(), "some_address"); +``` + +### Maintaining a config structure + +```rust template="storage" +use cw_storage_plus::Item; +use serde::{Serialize, Deserialize}; + +#[cw_serde] +struct Config { + admin: String, + interest_rate: Decimal, +} + +let cfg = Config { + admin: "some_address".to_string(), + interest_rate: Decimal::percent(5), +}; +let cfg_storage: Item = Item::new("c"); +cfg_storage.save(&mut storage, &cfg).unwrap(); + +assert_eq!(cfg_storage.load(&storage).unwrap(), cfg); +``` + +### Default values + +Sometimes you might like to read a value, but if it may have never been set, you +want to provide a default. This is a common pattern for counters or other +numeric values. + +```rust template="storage" +use cw_storage_plus::Item; + +let counter: Item = Item::new("t"); + +let mut total = counter.may_load(&storage).unwrap().unwrap_or(0); + +assert_eq!(total, 0); +total += 1; + +counter.save(&mut storage, &total).unwrap(); +``` diff --git a/src/pages/cw-storage-plus/containers/map.mdx b/src/pages/cw-storage-plus/containers/map.mdx index 8c968b68..0867cc71 100644 --- a/src/pages/cw-storage-plus/containers/map.mdx +++ b/src/pages/cw-storage-plus/containers/map.mdx @@ -1,7 +1,159 @@ +import { Callout } from "nextra/components"; + # `Map` -## Overview +A `Map` is a key-value store. Unlike the raw storage backend, the keys and +values of a map are typed. + +## Keys + +The key type has to implement the +[`PrimaryKey`](https://docs.rs/cw-storage-plus/latest/cw_storage_plus/trait.PrimaryKey.html) +trait. Most commonly, the key is simply a `String` or +[`Addr`](https://docs.rs/cosmwasm-std/latest/cosmwasm_std/struct.Addr.html). +Other types include binary strings (`Vec`, `[u8; N]`, `&[u8]`), numerical +types, or even tuples, which can be used to create composite keys. + + +Unlike values, keys do **not** need to implement anything like `serde::Serialize` or `serde::Deserialize`. Key encoding is handled by the `PrimaryKey` trait. + + +## Values + +The values, as usual, are serialized as JSON and must implement the +[`Serialize`](https://docs.serde.rs/serde/trait.Serialize.html) and +[`Deserialize`](https://docs.serde.rs/serde/trait.Deserialize.html) traits. + +## Operations + +Similar to an [`Item`](./item), a `Map` defines methods like `load`, `save`, and +`may_load`. The difference is that `Map`'s methods take a key as an argument. + +Most _CosmWasm_-enabled chains will enable iteration. If they do, it is possible +to iterate over the entries in a map using methods like +[`keys`](https://docs.rs/cw-storage-plus/latest/cw_storage_plus/struct.Map.html#method.keys) +or +[`range`](https://docs.rs/cw-storage-plus/latest/cw_storage_plus/struct.Map.html#method.range). + + + [For each bound, it's possible to decide if it's meant to be inclusive or exclusive.](https://docs.rs/cw-storage-plus/latest/cw_storage_plus/enum.Bound.html) + + +More information can be found in the +[API docs](https://docs.rs/cw-storage-plus/latest/cw_storage_plus/struct.Map.html). -## Examples +## Usage examples ### Keeping users' balances + +#### Checking a user's balance + +```rust template="storage" +use cw_storage_plus::Map; + +let balances: Map = Map::new("balances"); + +let alice_balance = balances + .may_load(&storage, "alice".to_string()) + .unwrap() + .unwrap_or(0); // if the entry does not exist yet, assume the balance is 0 + +assert_eq!(alice_balance, 0); +``` + +#### Updating a user's balance + +```rust template="storage" +use cw_storage_plus::Map; + +let balances: Map = Map::new("balances"); + +let mut alice_balance = balances + .may_load(&storage, "alice".to_string()) + .unwrap() + .unwrap_or(0); // if the entry does not exist yet, assume the balance is 0 + +alice_balance += 100; + +balances.save(&mut storage, "alice".to_string(), &alice_balance).unwrap(); +assert_eq!(balances.load(&storage, "alice".to_string()).unwrap(), 100); +``` + +### Keeping users' balances with a composite key + +#### Basic use + +```rust template="storage" +use cw_storage_plus::Map; + +// The first string is the user's address, the second is the denomination +let balances: Map<(String, String), u128> = Map::new("balances"); + +let mut alice_balance = balances + .may_load(&storage, ("alice".to_string(), "uusd".to_string())) + .unwrap() + .unwrap_or(0); // if the entry does not exist yet, assume the balance is 0 + +alice_balance += 100; + +balances + .save(&mut storage, ("alice".to_string(), "uusd".to_string()), &alice_balance) + .unwrap(); + +assert_eq!( + balances + .load(&storage, ("alice".to_string(), "uusd".to_string())) + .unwrap(), + 100 +); +``` + +#### Iterating over composite keys + +```rust template="storage" +use cw_storage_plus::Map; + +let balances: Map<(String, String), u128> = Map::new("balances"); + +balances.save(&mut storage, ("alice".to_string(), "uusd".to_string()), &100).unwrap(); +balances.save(&mut storage, ("alice".to_string(), "osmo".to_string()), &200).unwrap(); +balances.save(&mut storage, ("bob".to_string(), "uusd".to_string()), &300).unwrap(); + +let all_balances: Vec<_> = balances + .range(&storage, None, None, Order::Ascending) + .collect::>() + .unwrap(); + +assert_eq!( + all_balances, + vec![ + (("bob".to_string(), "uusd".to_string()), 300), + (("alice".to_string(), "osmo".to_string()), 200), + (("alice".to_string(), "uusd".to_string()), 100), + ] +); + +let alices_balances: Vec<_> = balances + .prefix("alice".to_string()) + .range(&storage, None, None, Order::Ascending) + .collect::>() + .unwrap(); + +assert_eq!(alices_balances, vec![("osmo".to_string(), 200), ("uusd".to_string(), 100)]); +``` + + + +As seen here, the order of keys isn't always lexicographic. + +If you need to rely on iteration order in maps with composite keys, here's how +things work: under the hood, every component of a composite key is +length-prefixed except for the last one. If you're only iterating over the last +component, you can expect things to be ordered lexicographically. For non-final +components, shorter strings will always come before longer ones. + +In the example, note how `"bob"` (a non-last component) comes before `"alice"`. +Also note how once we lock the first component to `"alice"`, entries are ordered +lexicographically by the second component. + + diff --git a/src/pages/cw-storage-plus/iteration.md b/src/pages/cw-storage-plus/iteration.md deleted file mode 100644 index 7adf4b8e..00000000 --- a/src/pages/cw-storage-plus/iteration.md +++ /dev/null @@ -1,9 +0,0 @@ -# Iteration - -TODO: how to generally take advantage of iteration - -TODO: bounds, inclusive-exclusive - -TODO: `Map` iteration - surprising behavior with order and bounds? - -TODO: how to use `Prefix` and such