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