From 1a5e29c74026e2272ad317854dd0b19cc63b41b2 Mon Sep 17 00:00:00 2001 From: Tomasz Kurcz Date: Wed, 15 Jan 2025 14:22:24 +0100 Subject: [PATCH 1/7] storey: container impl docs --- docs-test-gen/src/main.rs | 1 + .../templates/storey-container-impl.tpl | 200 ++++++++++++++ src/pages/storey/container-impl.mdx | 12 + src/pages/storey/container-impl/_meta.js | 3 + src/pages/storey/container-impl/my-map.mdx | 244 ++++++++++++++++++ 5 files changed, 460 insertions(+) create mode 100644 docs-test-gen/templates/storey-container-impl.tpl create mode 100644 src/pages/storey/container-impl/_meta.js create mode 100644 src/pages/storey/container-impl/my-map.mdx diff --git a/docs-test-gen/src/main.rs b/docs-test-gen/src/main.rs index 82a70b1f..ba9d5ebf 100644 --- a/docs-test-gen/src/main.rs +++ b/docs-test-gen/src/main.rs @@ -12,6 +12,7 @@ static TEMPLATES: phf::Map<&'static str, &'static str> = phf_map! { "ibc-channel" => include_str!("../templates/ibc-channel.tpl"), "ibc-packet" => include_str!("../templates/ibc-packet.tpl"), "storage" => include_str!("../templates/storage.tpl"), + "storey-container-impl" => include_str!("../templates/storey-container-impl.tpl"), "sylvia-storey-contract" => include_str!("../templates/sylvia/storey_contract.tpl"), "sylvia-cw-storage-contract" => include_str!("../templates/sylvia/cw_storage_contract.tpl"), "sylvia-empty" => include_str!("../templates/sylvia/empty.tpl"), diff --git a/docs-test-gen/templates/storey-container-impl.tpl b/docs-test-gen/templates/storey-container-impl.tpl new file mode 100644 index 00000000..99b31887 --- /dev/null +++ b/docs-test-gen/templates/storey-container-impl.tpl @@ -0,0 +1,200 @@ +#![allow( + unexpected_cfgs, + dead_code, + unused_variables, + unused_imports, + clippy::new_without_default +)] +use cosmwasm_schema::cw_serde; +use cosmwasm_std::*; +use storey::containers::{IterableStorable, NonTerminal, Storable}; +use storey::storage::{IntoStorage, StorageBranch}; + +mod users { + use super::*; + + use cw_storage_plus::{index_list, IndexedMap, MultiIndex, UniqueIndex}; + + pub type Handle = String; + + #[cw_serde] + pub struct User { + pub handle: String, + pub country: String, + } + + pub struct ExampleUsers { + pub alice: User, + pub bob: User, + } + + pub fn example_users() -> ExampleUsers { + ExampleUsers { + alice: User { + handle: "alice".to_string(), + country: "Wonderland".to_string(), + }, + bob: User { + handle: "bob".to_string(), + country: "USA".to_string(), + }, + } + } + + #[index_list(User)] + pub struct UserIndexes<'a> { + pub handle_ix: UniqueIndex<'a, Handle, User, Addr>, + pub country_ix: MultiIndex<'a, String, User, Addr>, + } + + pub fn user_indexes() -> UserIndexes<'static> { + user_indexes_custom("u", "uh", "uc") + } + + pub fn user_indexes_custom( + ns: &'static str, + handle_prefix: &'static str, + country_prefix: &'static str, + ) -> UserIndexes<'static> { + UserIndexes { + handle_ix: UniqueIndex::new(|user| user.handle.clone(), handle_prefix), + country_ix: MultiIndex::new(|_pk, user| user.country.clone(), ns, country_prefix), + } + } +} + +fn advance_height(env: &mut Env, blocks: u64) { + env.block.height += blocks; +} + +struct MyMap { + prefix: u8, + phantom: std::marker::PhantomData, +} + +impl MyMap +where + V: Storable, +{ + pub const fn new(prefix: u8) -> Self { + Self { + prefix, + phantom: std::marker::PhantomData, + } + } + + pub fn access(&self, storage: F) -> MyMapAccess> + where + (F,): IntoStorage, + { + let storage = (storage,).into_storage(); + Self::access_impl(StorageBranch::new(storage, vec![self.prefix])) + } +} + +pub struct MyMapAccess { + storage: S, + phantom: std::marker::PhantomData, +} + +impl Storable for MyMap +where + V: Storable, +{ + type Kind = NonTerminal; + type Accessor = MyMapAccess; + + fn access_impl(storage: S) -> MyMapAccess { + MyMapAccess { + storage, + phantom: std::marker::PhantomData, + } + } +} + +impl IterableStorable for MyMap +where + V: IterableStorable, + ::KeyDecodeError: std::fmt::Display, +{ + type Key = (u32, V::Key); + type KeyDecodeError = (); + type Value = V::Value; + type ValueDecodeError = V::ValueDecodeError; + + fn decode_key(key: &[u8]) -> Result { + todo!() + } + + fn decode_value(value: &[u8]) -> Result { + V::decode_value(value) + } +} + +impl MyMapAccess +where + V: Storable, +{ + pub fn entry(&self, key: u32) -> V::Accessor> { + let key = key.to_be_bytes().to_vec(); + + V::access_impl(StorageBranch::new(&self.storage, key)) + } + + pub fn entry_mut(&mut self, key: u32) -> V::Accessor> { + let key = key.to_be_bytes().to_vec(); + + V::access_impl(StorageBranch::new(&mut self.storage, key)) + } +} + +#[test] +fn doctest() { + #[allow(unused_variables, unused_mut)] + let mut storage = cosmwasm_std::testing::MockStorage::new(); + #[allow(unused_mut)] + let mut env = cosmwasm_std::testing::mock_env(); + + let users = cw_storage_plus::IndexedMap::::new( + "uu", + users::user_indexes_custom("uu", "uuh", "uuc"), + ); + + let users_data = [ + ( + Addr::unchecked("aaa"), + users::User { + handle: "alice".to_string(), + country: "Wonderland".to_string(), + }, + ), + ( + Addr::unchecked("bbb"), + users::User { + handle: "bob".to_string(), + country: "USA".to_string(), + }, + ), + ( + Addr::unchecked("ccc"), + users::User { + handle: "carol".to_string(), + country: "UK".to_string(), + }, + ), + ( + Addr::unchecked("ddd"), + users::User { + handle: "dave".to_string(), + country: "USA".to_string(), + }, + ), + ]; + + for (addr, user) in users_data { + users.save(&mut storage, addr, &user).unwrap(); + } + + #[rustfmt::skip] + {{code}} +} diff --git a/src/pages/storey/container-impl.mdx b/src/pages/storey/container-impl.mdx index 18c21131..70d4ea27 100644 --- a/src/pages/storey/container-impl.mdx +++ b/src/pages/storey/container-impl.mdx @@ -5,3 +5,15 @@ tags: ["storey"] import { Callout } from "nextra/components"; # Implementing new containers + +Storey provides a set of built-in containers, but you're not limited to just those. For special +needs, you can go ahead and create your own containers that will play along nicely with the rest of +the Storey "ecosystem". If it's appropriate, your container could for example allow for nesting +other containers - like [`Map`]. + +In this section, you will find examples of custom containers with their full code. + +# Guides + +- [MyMap](container-impl/my-map) + diff --git a/src/pages/storey/container-impl/_meta.js b/src/pages/storey/container-impl/_meta.js new file mode 100644 index 00000000..77f1e226 --- /dev/null +++ b/src/pages/storey/container-impl/_meta.js @@ -0,0 +1,3 @@ +export default { + "my-map": "MyMap", +}; diff --git a/src/pages/storey/container-impl/my-map.mdx b/src/pages/storey/container-impl/my-map.mdx new file mode 100644 index 00000000..35dfcbc8 --- /dev/null +++ b/src/pages/storey/container-impl/my-map.mdx @@ -0,0 +1,244 @@ +--- +tags: ["storey"] +--- + +import { Callout } from "nextra/components"; + +# MyMap + +Let's build our own version of the [`Map`] container. We'll call it `MyMap`. + +This one will be a little limited. It only accepts `u32` keys. + +First, let's create our struct. + +```rust template="storage" +struct MyMap { + prefix: u8, + phantom: std::marker::PhantomData, +} + +impl MyMap { + pub const fn new(prefix: u8) -> Self { + Self { + prefix, + phantom: std::marker::PhantomData, + } + } +} +``` + +No magic here. The `prefix` field is used when this collection is a top-level collection - it's a +single-byte key that creates a subspace for this collection's internal data. + +The `phantom` field allows us to use the type parameter `V` without actually storing any values of +that type. + +The `V` type parameter is the type of the **container** inside the map. If you've followed the +[`Map`] documentation, you'll know that `Map` is a composable container - it holds another container +inside. + +The constructor is simple - it just initializes the fields. + +The next step is to set up an accessor. Hold on tight! This will be a ride. + +```rust template="storage" {1-2, 10-11, 20-26, 29-46} +use storey::containers::{NonTerminal, Storable}; +use storey::storage::{IntoStorage, StorageBranch}; + +struct MyMap { + prefix: u8, + phantom: std::marker::PhantomData, +} + +impl MyMap +where + V: Storable, +{ + pub const fn new(prefix: u8) -> Self { + Self { + prefix, + phantom: std::marker::PhantomData, + } + } + + pub fn access(&self, storage: F) -> MyMapAccess> + where + (F,): IntoStorage, + { + let storage = (storage,).into_storage(); + Self::access_impl(StorageBranch::new(storage, vec![self.prefix])) + } +} + +pub struct MyMapAccess { + storage: S, + phantom: std::marker::PhantomData, +} + +impl Storable for MyMap +where + V: Storable, +{ + type Kind = NonTerminal; + type Accessor = MyMapAccess; + + fn access_impl(storage: S) -> MyMapAccess { + MyMapAccess { + storage, + phantom: std::marker::PhantomData, + } + } +} +``` + +Whew. Let's break this down. + +The `MyMapAccess` struct is our accessor. It's a facade that's used to actually access the data in +the collection given a `Storage` instance - this is usually a subspace of the "root" storage +backend. + +The [`Storable`] trait is the main trait a container must implement. The associated types tell the framework: + +| Associated type | Details | +|-------------------|-------------------------------------------------------------------------| +| `Kind` | We put `NonTerminal` here to signify our container creates subkeys rather than just saving data at the root. | +| `Accessor` | The accessor type. `MyMapAccess` in our case. | + +The method `access_impl` produces an accessor given a storage abstraction (usually representing a "slice" of the underlying storage.) + +`MyMap::access` is an access method in cases where you're using the container as a top-level +container. + +There's one thing we're missing for this to actually by useful. We need some methods for the +accessor. + +```rust template="storage" {49-64} +use storey::containers::{NonTerminal, Storable}; +use storey::storage::{IntoStorage, StorageBranch}; + +struct MyMap { + prefix: u8, + phantom: std::marker::PhantomData, +} + +impl MyMap +where + V: Storable, +{ + pub const fn new(prefix: u8) -> Self { + Self { + prefix, + phantom: std::marker::PhantomData, + } + } + + pub fn access(&self, storage: F) -> MyMapAccess> + where + (F,): IntoStorage, + { + let storage = (storage,).into_storage(); + Self::access_impl(StorageBranch::new(storage, vec![self.prefix])) + } +} + +pub struct MyMapAccess { + storage: S, + phantom: std::marker::PhantomData, +} + +impl Storable for MyMap +where + V: Storable, +{ + type Kind = NonTerminal; + type Accessor = MyMapAccess; + + fn access_impl(storage: S) -> MyMapAccess { + MyMapAccess { + storage, + phantom: std::marker::PhantomData, + } + } +} + +impl MyMapAccess +where + V: Storable, +{ + pub fn entry(&self, key: u32) -> V::Accessor> { + let key = key.to_be_bytes().to_vec(); + + V::access_impl(StorageBranch::new(&self.storage, key)) + } + + pub fn entry_mut(&mut self, key: u32) -> V::Accessor> { + let key = key.to_be_bytes().to_vec(); + + V::access_impl(StorageBranch::new(&mut self.storage, key)) + } +} +``` + +What does this new code do? It provides a way to create accessors for the inner container based on +the `u32` key. The [`StorageBranch`] type is a helper that creates a "subspace" of the storage +backend. It's used to create a "slice" of the storage granted to `MyMap`. + +Time to see this in action. + +```rust template="storey-container-impl" +use cw_storey::containers::{Item}; + +const MAP_IX: u8 = 0; + +let my_map: MyMap> = MyMap::new(MAP_IX); +let mut access = my_map.access(&mut storage); + +access.entry_mut(1).set(&100).unwrap(); +access.entry_mut(2).set(&200).unwrap(); + +assert_eq!(access.entry(1).get().unwrap(), Some(100)); +assert_eq!(access.entry(2).get().unwrap(), Some(200)); +``` + +# Iteration + +To be continued... + +| Associated type | Details | +|-------------------|-------------------------------------------------------------------------| +| `Key` | The key type to be returned by iterators. Here a tuple of `u32` and the key type of the inner container. | +| `KeyDecodeError` | The error to be returned on invalid key data. Here we don't care, so we use `()`. In production, use a proper error type. | +| `Value` | The value type to be returned by iterators. Here it's delegated to the inner container. | +| `ValueDecodeError`| The error type for invalid value data. Delegated to the inner container. | + +The methods: +| Method | Function | +|----------------|-----------------------------------------------------------------------------| +| `decode_value` | Used for iteration. This is how the framework knows how to decode values given raw data. | + +``` +impl IterableStorable for MyMap +where + V: IterableStorable, + ::KeyDecodeError: std::fmt::Display, +{ + type Key = (u32, V::Key); + type KeyDecodeError = (); + type Value = V::Value; + type ValueDecodeError = V::ValueDecodeError; + + fn decode_key(key: &[u8]) -> Result { + todo!() + } + + fn decode_value(value: &[u8]) -> Result { + V::decode_value(value) + } +} +``` + +[`Item`]: /storey/containers/item +[`Map`]: /storey/containers/map +[`Storable`]: https://docs.rs/storey/latest/storey/containers/trait.Storable.html +[`StorageBranch`]: https://docs.rs/storey/latest/storey/storage/struct.StorageBranch.html From 46141a88fa2834c4b6d46dd2828433505c5d942a Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Wed, 19 Feb 2025 19:51:34 +0000 Subject: [PATCH 2/7] [autofix.ci] apply automated fixes --- src/pages/storey/container-impl.mdx | 1 - src/pages/storey/container-impl/my-map.mdx | 36 ++++++++++++---------- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/pages/storey/container-impl.mdx b/src/pages/storey/container-impl.mdx index 70d4ea27..df9eafa6 100644 --- a/src/pages/storey/container-impl.mdx +++ b/src/pages/storey/container-impl.mdx @@ -16,4 +16,3 @@ In this section, you will find examples of custom containers with their full cod # Guides - [MyMap](container-impl/my-map) - diff --git a/src/pages/storey/container-impl/my-map.mdx b/src/pages/storey/container-impl/my-map.mdx index 35dfcbc8..e83ce2a1 100644 --- a/src/pages/storey/container-impl/my-map.mdx +++ b/src/pages/storey/container-impl/my-map.mdx @@ -98,14 +98,16 @@ The `MyMapAccess` struct is our accessor. It's a facade that's used to actually the collection given a `Storage` instance - this is usually a subspace of the "root" storage backend. -The [`Storable`] trait is the main trait a container must implement. The associated types tell the framework: +The [`Storable`] trait is the main trait a container must implement. The associated types tell the +framework: -| Associated type | Details | -|-------------------|-------------------------------------------------------------------------| -| `Kind` | We put `NonTerminal` here to signify our container creates subkeys rather than just saving data at the root. | -| `Accessor` | The accessor type. `MyMapAccess` in our case. | +| Associated type | Details | +| --------------- | ------------------------------------------------------------------------------------------------------------ | +| `Kind` | We put `NonTerminal` here to signify our container creates subkeys rather than just saving data at the root. | +| `Accessor` | The accessor type. `MyMapAccess` in our case. | -The method `access_impl` produces an accessor given a storage abstraction (usually representing a "slice" of the underlying storage.) +The method `access_impl` produces an accessor given a storage abstraction (usually representing a +"slice" of the underlying storage.) `MyMap::access` is an access method in cases where you're using the container as a top-level container. @@ -205,17 +207,17 @@ assert_eq!(access.entry(2).get().unwrap(), Some(200)); To be continued... -| Associated type | Details | -|-------------------|-------------------------------------------------------------------------| -| `Key` | The key type to be returned by iterators. Here a tuple of `u32` and the key type of the inner container. | -| `KeyDecodeError` | The error to be returned on invalid key data. Here we don't care, so we use `()`. In production, use a proper error type. | -| `Value` | The value type to be returned by iterators. Here it's delegated to the inner container. | -| `ValueDecodeError`| The error type for invalid value data. Delegated to the inner container. | - -The methods: -| Method | Function | -|----------------|-----------------------------------------------------------------------------| -| `decode_value` | Used for iteration. This is how the framework knows how to decode values given raw data. | +| Associated type | Details | +| ------------------ | ------------------------------------------------------------------------------------------------------------------------- | +| `Key` | The key type to be returned by iterators. Here a tuple of `u32` and the key type of the inner container. | +| `KeyDecodeError` | The error to be returned on invalid key data. Here we don't care, so we use `()`. In production, use a proper error type. | +| `Value` | The value type to be returned by iterators. Here it's delegated to the inner container. | +| `ValueDecodeError` | The error type for invalid value data. Delegated to the inner container. | + +The methods: | Method | Function | +|----------------|-----------------------------------------------------------------------------| | +`decode_value` | Used for iteration. This is how the framework knows how to decode values given raw +data. | ``` impl IterableStorable for MyMap From b5274ea9ff29762f51d41c769374990a06133ff6 Mon Sep 17 00:00:00 2001 From: Tomasz Kurcz Date: Wed, 5 Mar 2025 21:03:39 +0100 Subject: [PATCH 3/7] (dirty) iteration --- docs-test-gen/src/main.rs | 1 + .../templates/storey-container-impl-iter.tpl | 225 ++++++++++++++++++ .../templates/storey-container-impl.tpl | 21 +- src/pages/storey/container-impl/my-map.mdx | 98 +++++++- 4 files changed, 315 insertions(+), 30 deletions(-) create mode 100644 docs-test-gen/templates/storey-container-impl-iter.tpl diff --git a/docs-test-gen/src/main.rs b/docs-test-gen/src/main.rs index ba9d5ebf..09280a4d 100644 --- a/docs-test-gen/src/main.rs +++ b/docs-test-gen/src/main.rs @@ -13,6 +13,7 @@ static TEMPLATES: phf::Map<&'static str, &'static str> = phf_map! { "ibc-packet" => include_str!("../templates/ibc-packet.tpl"), "storage" => include_str!("../templates/storage.tpl"), "storey-container-impl" => include_str!("../templates/storey-container-impl.tpl"), + "storey-container-impl-iter" => include_str!("../templates/storey-container-impl-iter.tpl"), "sylvia-storey-contract" => include_str!("../templates/sylvia/storey_contract.tpl"), "sylvia-cw-storage-contract" => include_str!("../templates/sylvia/cw_storage_contract.tpl"), "sylvia-empty" => include_str!("../templates/sylvia/empty.tpl"), diff --git a/docs-test-gen/templates/storey-container-impl-iter.tpl b/docs-test-gen/templates/storey-container-impl-iter.tpl new file mode 100644 index 00000000..743042fe --- /dev/null +++ b/docs-test-gen/templates/storey-container-impl-iter.tpl @@ -0,0 +1,225 @@ +#![allow( + unexpected_cfgs, + dead_code, + unused_variables, + unused_imports, + clippy::new_without_default +)] +use cosmwasm_schema::cw_serde; +use cosmwasm_std::*; +use storey::containers::{IterableStorable, NonTerminal, Storable}; +use storey::storage::{IntoStorage, StorageBranch}; + +mod users { + use super::*; + + use cw_storage_plus::{index_list, IndexedMap, MultiIndex, UniqueIndex}; + + pub type Handle = String; + + #[cw_serde] + pub struct User { + pub handle: String, + pub country: String, + } + + pub struct ExampleUsers { + pub alice: User, + pub bob: User, + } + + pub fn example_users() -> ExampleUsers { + ExampleUsers { + alice: User { + handle: "alice".to_string(), + country: "Wonderland".to_string(), + }, + bob: User { + handle: "bob".to_string(), + country: "USA".to_string(), + }, + } + } + + #[index_list(User)] + pub struct UserIndexes<'a> { + pub handle_ix: UniqueIndex<'a, Handle, User, Addr>, + pub country_ix: MultiIndex<'a, String, User, Addr>, + } + + pub fn user_indexes() -> UserIndexes<'static> { + user_indexes_custom("u", "uh", "uc") + } + + pub fn user_indexes_custom( + ns: &'static str, + handle_prefix: &'static str, + country_prefix: &'static str, + ) -> UserIndexes<'static> { + UserIndexes { + handle_ix: UniqueIndex::new(|user| user.handle.clone(), handle_prefix), + country_ix: MultiIndex::new(|_pk, user| user.country.clone(), ns, country_prefix), + } + } +} + +fn advance_height(env: &mut Env, blocks: u64) { + env.block.height += blocks; +} + +pub struct MyMap { + prefix: u8, + phantom: std::marker::PhantomData, +} + +impl MyMap +where + V: Storable, +{ + pub const fn new(prefix: u8) -> Self { + Self { + prefix, + phantom: std::marker::PhantomData, + } + } + + pub fn access(&self, storage: F) -> MyMapAccess> + where + (F,): IntoStorage, + { + let storage = (storage,).into_storage(); + Self::access_impl(StorageBranch::new(storage, vec![self.prefix])) + } +} + +pub struct MyMapAccess { + storage: S, + phantom: std::marker::PhantomData, +} + +impl Storable for MyMap +where + V: Storable, +{ + type Kind = NonTerminal; + type Accessor = MyMapAccess; + + fn access_impl(storage: S) -> MyMapAccess { + MyMapAccess { + storage, + phantom: std::marker::PhantomData, + } + } +} + +impl MyMapAccess +where + V: Storable, +{ + pub fn entry(&self, key: u32) -> V::Accessor> { + let key = key.to_be_bytes().to_vec(); + + V::access_impl(StorageBranch::new(&self.storage, key)) + } + + pub fn entry_mut(&mut self, key: u32) -> V::Accessor> { + let key = key.to_be_bytes().to_vec(); + + V::access_impl(StorageBranch::new(&mut self.storage, key)) + } +} + +use storey::containers::IterableAccessor; +use storey::storage::IterableStorage; + +impl IterableStorable for MyMap +where + V: IterableStorable, + ::KeyDecodeError: std::fmt::Display, +{ + type Key = (u32, V::Key); + type KeyDecodeError = (); + type Value = V::Value; + type ValueDecodeError = V::ValueDecodeError; + + fn decode_key(key: &[u8]) -> Result { + if key.len() < 4 { + return Err(()); + } + + let key_arr = key[0..4].try_into().map_err(|_| ())?; + let this_key = u32::from_le_bytes(key_arr); + + let rest = V::decode_key(&key[4..]).map_err(|_| ())?; + + Ok((this_key, rest)) + } + + fn decode_value(value: &[u8]) -> Result { + V::decode_value(value) + } +} + +impl IterableAccessor for MyMapAccess +where + V: IterableStorable, + S: IterableStorage, +{ + type Storable = MyMap; + type Storage = S; + + fn storage(&self) -> &Self::Storage { + &self.storage + } +} + +#[test] +fn doctest() { + #[allow(unused_variables, unused_mut)] + let mut storage = cosmwasm_std::testing::MockStorage::new(); + #[allow(unused_mut)] + let mut env = cosmwasm_std::testing::mock_env(); + + let users = cw_storage_plus::IndexedMap::::new( + "uu", + users::user_indexes_custom("uu", "uuh", "uuc"), + ); + + let users_data = [ + ( + Addr::unchecked("aaa"), + users::User { + handle: "alice".to_string(), + country: "Wonderland".to_string(), + }, + ), + ( + Addr::unchecked("bbb"), + users::User { + handle: "bob".to_string(), + country: "USA".to_string(), + }, + ), + ( + Addr::unchecked("ccc"), + users::User { + handle: "carol".to_string(), + country: "UK".to_string(), + }, + ), + ( + Addr::unchecked("ddd"), + users::User { + handle: "dave".to_string(), + country: "USA".to_string(), + }, + ), + ]; + + for (addr, user) in users_data { + users.save(&mut storage, addr, &user).unwrap(); + } + + #[rustfmt::skip] + {{code}} +} diff --git a/docs-test-gen/templates/storey-container-impl.tpl b/docs-test-gen/templates/storey-container-impl.tpl index 99b31887..893c769c 100644 --- a/docs-test-gen/templates/storey-container-impl.tpl +++ b/docs-test-gen/templates/storey-container-impl.tpl @@ -67,7 +67,7 @@ fn advance_height(env: &mut Env, blocks: u64) { env.block.height += blocks; } -struct MyMap { +pub struct MyMap { prefix: u8, phantom: std::marker::PhantomData, } @@ -112,25 +112,6 @@ where } } -impl IterableStorable for MyMap -where - V: IterableStorable, - ::KeyDecodeError: std::fmt::Display, -{ - type Key = (u32, V::Key); - type KeyDecodeError = (); - type Value = V::Value; - type ValueDecodeError = V::ValueDecodeError; - - fn decode_key(key: &[u8]) -> Result { - todo!() - } - - fn decode_value(value: &[u8]) -> Result { - V::decode_value(value) - } -} - impl MyMapAccess where V: Storable, diff --git a/src/pages/storey/container-impl/my-map.mdx b/src/pages/storey/container-impl/my-map.mdx index e83ce2a1..a2328c45 100644 --- a/src/pages/storey/container-impl/my-map.mdx +++ b/src/pages/storey/container-impl/my-map.mdx @@ -13,7 +13,7 @@ This one will be a little limited. It only accepts `u32` keys. First, let's create our struct. ```rust template="storage" -struct MyMap { +pub struct MyMap { prefix: u8, phantom: std::marker::PhantomData, } @@ -46,7 +46,7 @@ The next step is to set up an accessor. Hold on tight! This will be a ride. use storey::containers::{NonTerminal, Storable}; use storey::storage::{IntoStorage, StorageBranch}; -struct MyMap { +pub struct MyMap { prefix: u8, phantom: std::marker::PhantomData, } @@ -119,7 +119,7 @@ accessor. use storey::containers::{NonTerminal, Storable}; use storey::storage::{IntoStorage, StorageBranch}; -struct MyMap { +pub struct MyMap { prefix: u8, phantom: std::marker::PhantomData, } @@ -205,7 +205,39 @@ assert_eq!(access.entry(2).get().unwrap(), Some(200)); # Iteration -To be continued... +While we have a functional collection by now, we can't perform iteration yet. Instead of trying to implement iteration on our own, there are benefits to using abstractions `storey` provides. This means implementing `IterableStorable` for `MyMap`. + +```rust template="storey-container-impl" +impl IterableStorable for MyMap +where + V: IterableStorable, + ::KeyDecodeError: std::fmt::Display, +{ + type Key = (u32, V::Key); + type KeyDecodeError = (); + type Value = V::Value; + type ValueDecodeError = V::ValueDecodeError; + + fn decode_key(key: &[u8]) -> Result { + if key.len() < 4 { + return Err(()); + } + + let key_arr = key[0..4].try_into().map_err(|_| ())?; + let this_key = u32::from_le_bytes(key_arr); + + let rest = V::decode_key(&key[4..]).map_err(|_| ())?; + + Ok((this_key, rest)) + } + + fn decode_value(value: &[u8]) -> Result { + V::decode_value(value) + } +} +``` + +Alright. Let's dive into the trait items! | Associated type | Details | | ------------------ | ------------------------------------------------------------------------------------------------------------------------- | @@ -214,12 +246,17 @@ To be continued... | `Value` | The value type to be returned by iterators. Here it's delegated to the inner container. | | `ValueDecodeError` | The error type for invalid value data. Delegated to the inner container. | -The methods: | Method | Function | -|----------------|-----------------------------------------------------------------------------| | -`decode_value` | Used for iteration. This is how the framework knows how to decode values given raw -data. | +| Method | Function | +|----------------|-----------------------------------------------------------------------------| +| `decode_key` | This is how the framework knows how to decode keys given raw bytes. | +| `decode_value` | This is how the framework knows how to decode values given raw bytes. In this case, we delegate to the inner collection. | + +We still need to implement a trait for the accessor. + +```rust template="storey-container-impl" {1-2, 32-44} +use storey::containers::IterableAccessor; +use storey::storage::IterableStorage; -``` impl IterableStorable for MyMap where V: IterableStorable, @@ -231,13 +268,54 @@ where type ValueDecodeError = V::ValueDecodeError; fn decode_key(key: &[u8]) -> Result { - todo!() + if key.len() < 4 { + return Err(()); + } + + let key_arr = key[0..4].try_into().map_err(|_| ())?; + let this_key = u32::from_le_bytes(key_arr); + + let rest = V::decode_key(&key[4..]).map_err(|_| ())?; + + Ok((this_key, rest)) } fn decode_value(value: &[u8]) -> Result { V::decode_value(value) } } + +impl IterableAccessor for MyMapAccess +where + V: IterableStorable, + S: IterableStorage, +{ + type Storable = MyMap; + type Storage = S; + + fn storage(&self) -> &Self::Storage { + &self.storage + } +} +``` + +Now that we've implemented the thing, let's use it! + +```rust template="storey-container-impl-iter" +use cw_storey::containers::{Item}; +use storey::containers::IterableAccessor as _; + +const MAP_IX: u8 = 1; + +let my_map: MyMap> = MyMap::new(MAP_IX); +let mut access = my_map.access(&mut storage); + +access.entry_mut(1).set(&100).unwrap(); +access.entry_mut(2).set(&200).unwrap(); +access.entry_mut(3).set(&300).unwrap(); + +let result: Result, _> = access.values().collect(); +assert_eq!(result.unwrap(), vec![100, 200, 300]); ``` [`Item`]: /storey/containers/item From cc7b63faa17ffad7565c2a9feb776333bc7daf6c Mon Sep 17 00:00:00 2001 From: Tomasz Kurcz Date: Tue, 18 Mar 2025 20:21:58 +0100 Subject: [PATCH 4/7] storey: finish mymap implementation --- .../templates/storey-container-impl-iter.tpl | 14 ++-- src/pages/storey/container-impl/my-map.mdx | 65 +++++++++++++++---- 2 files changed, 58 insertions(+), 21 deletions(-) diff --git a/docs-test-gen/templates/storey-container-impl-iter.tpl b/docs-test-gen/templates/storey-container-impl-iter.tpl index 743042fe..1629c394 100644 --- a/docs-test-gen/templates/storey-container-impl-iter.tpl +++ b/docs-test-gen/templates/storey-container-impl-iter.tpl @@ -3,7 +3,7 @@ dead_code, unused_variables, unused_imports, - clippy::new_without_default + clippy::new_without_default, )] use cosmwasm_schema::cw_serde; use cosmwasm_std::*; @@ -138,19 +138,19 @@ where ::KeyDecodeError: std::fmt::Display, { type Key = (u32, V::Key); - type KeyDecodeError = (); + type KeyDecodeError = String; type Value = V::Value; type ValueDecodeError = V::ValueDecodeError; - fn decode_key(key: &[u8]) -> Result { + fn decode_key(key: &[u8]) -> Result { if key.len() < 4 { - return Err(()); + return Err(String::from("Key too short")); } - let key_arr = key[0..4].try_into().map_err(|_| ())?; - let this_key = u32::from_le_bytes(key_arr); + let key_arr = key[0..4].try_into().map_err(|e| format!("Invalid key: {}", e))?; + let this_key = u32::from_be_bytes(key_arr); - let rest = V::decode_key(&key[4..]).map_err(|_| ())?; + let rest = V::decode_key(&key[4..]).map_err(|e| e.to_string())?; Ok((this_key, rest)) } diff --git a/src/pages/storey/container-impl/my-map.mdx b/src/pages/storey/container-impl/my-map.mdx index a2328c45..6b38e040 100644 --- a/src/pages/storey/container-impl/my-map.mdx +++ b/src/pages/storey/container-impl/my-map.mdx @@ -211,22 +211,22 @@ While we have a functional collection by now, we can't perform iteration yet. In impl IterableStorable for MyMap where V: IterableStorable, - ::KeyDecodeError: std::fmt::Display, + V::KeyDecodeError: std::fmt::Display, { type Key = (u32, V::Key); - type KeyDecodeError = (); + type KeyDecodeError = String; type Value = V::Value; type ValueDecodeError = V::ValueDecodeError; - fn decode_key(key: &[u8]) -> Result { + fn decode_key(key: &[u8]) -> Result { if key.len() < 4 { - return Err(()); + return Err(String::from("Key too short")); } - let key_arr = key[0..4].try_into().map_err(|_| ())?; - let this_key = u32::from_le_bytes(key_arr); + let key_arr = key[0..4].try_into().map_err(|e| format!("Invalid key: {}", e))?; + let this_key = u32::from_be_bytes(key_arr); - let rest = V::decode_key(&key[4..]).map_err(|_| ())?; + let rest = V::decode_key(&key[4..]).map_err(|e| e.to_string())?; Ok((this_key, rest)) } @@ -260,22 +260,22 @@ use storey::storage::IterableStorage; impl IterableStorable for MyMap where V: IterableStorable, - ::KeyDecodeError: std::fmt::Display, + V::KeyDecodeError: std::fmt::Display, { type Key = (u32, V::Key); - type KeyDecodeError = (); + type KeyDecodeError = String; type Value = V::Value; type ValueDecodeError = V::ValueDecodeError; - fn decode_key(key: &[u8]) -> Result { + fn decode_key(key: &[u8]) -> Result { if key.len() < 4 { - return Err(()); + return Err(String::from("Key too short")); } - let key_arr = key[0..4].try_into().map_err(|_| ())?; - let this_key = u32::from_le_bytes(key_arr); + let key_arr = key[0..4].try_into().map_err(|e| format!("Invalid key: {}", e))?; + let this_key = u32::from_be_bytes(key_arr); - let rest = V::decode_key(&key[4..]).map_err(|_| ())?; + let rest = V::decode_key(&key[4..]).map_err(|e| e.to_string())?; Ok((this_key, rest)) } @@ -318,6 +318,43 @@ let result: Result, _> = access.values().collect(); assert_eq!(result.unwrap(), vec![100, 200, 300]); ``` +This isn't all. What we've also enabled is the ability to iterate over outer containers. + +Let's create a regular `Map`, nest our `MyMap` inside it, and see what we can do! + +```rust template="storey-container-impl-iter" +use cw_storey::containers::{Item, Map}; +use storey::containers::IterableAccessor as _; + +const MAP_IX: u8 = 1; + +let map: Map>> = Map::new(MAP_IX); +let mut access = map.access(&mut storage); + +access.entry_mut("alice").entry_mut(1).set(&100).unwrap(); +access.entry_mut("alice").entry_mut(2).set(&200).unwrap(); +access.entry_mut("bob").entry_mut(1).set(&1100).unwrap(); +access.entry_mut("bob").entry_mut(3).set(&1300).unwrap(); + +let result: Result, _> = access.pairs().collect(); +assert_eq!(result.unwrap(), vec![ + (("bob".to_string(), (1, ())), 1100), + (("bob".to_string(), (3, ())), 1300), + (("alice".to_string(), (1, ())), 100), + (("alice".to_string(), (2, ())), 200) +]); +``` + +We can iterate over everything. Note we didn't have to write any special logic for this nesting in our custom container's implementation. +It's well integrated with Storey simply by implementing a few traits. + + + We know the parenthesized, nested keys are ugly. We'll eventually try to make this + prettier. + + For now, remember this ugliness allows unlimited depth of nested keys, which is pretty cool! Recursion rocks. Sometimes. + + [`Item`]: /storey/containers/item [`Map`]: /storey/containers/map [`Storable`]: https://docs.rs/storey/latest/storey/containers/trait.Storable.html From c423cd93170c3c0cbb270621933de47021fdd5b0 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Tue, 18 Mar 2025 19:24:51 +0000 Subject: [PATCH 5/7] [autofix.ci] apply automated fixes --- src/pages/storey/container-impl/my-map.mdx | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/pages/storey/container-impl/my-map.mdx b/src/pages/storey/container-impl/my-map.mdx index 6b38e040..c41a1f36 100644 --- a/src/pages/storey/container-impl/my-map.mdx +++ b/src/pages/storey/container-impl/my-map.mdx @@ -205,7 +205,9 @@ assert_eq!(access.entry(2).get().unwrap(), Some(200)); # Iteration -While we have a functional collection by now, we can't perform iteration yet. Instead of trying to implement iteration on our own, there are benefits to using abstractions `storey` provides. This means implementing `IterableStorable` for `MyMap`. +While we have a functional collection by now, we can't perform iteration yet. Instead of trying to +implement iteration on our own, there are benefits to using abstractions `storey` provides. This +means implementing `IterableStorable` for `MyMap`. ```rust template="storey-container-impl" impl IterableStorable for MyMap @@ -246,9 +248,9 @@ Alright. Let's dive into the trait items! | `Value` | The value type to be returned by iterators. Here it's delegated to the inner container. | | `ValueDecodeError` | The error type for invalid value data. Delegated to the inner container. | -| Method | Function | -|----------------|-----------------------------------------------------------------------------| -| `decode_key` | This is how the framework knows how to decode keys given raw bytes. | +| Method | Function | +| -------------- | ------------------------------------------------------------------------------------------------------------------------ | +| `decode_key` | This is how the framework knows how to decode keys given raw bytes. | | `decode_value` | This is how the framework knows how to decode values given raw bytes. In this case, we delegate to the inner collection. | We still need to implement a trait for the accessor. @@ -345,14 +347,16 @@ assert_eq!(result.unwrap(), vec![ ]); ``` -We can iterate over everything. Note we didn't have to write any special logic for this nesting in our custom container's implementation. -It's well integrated with Storey simply by implementing a few traits. +We can iterate over everything. Note we didn't have to write any special logic for this nesting in +our custom container's implementation. It's well integrated with Storey simply by implementing a few +traits. We know the parenthesized, nested keys are ugly. We'll eventually try to make this prettier. For now, remember this ugliness allows unlimited depth of nested keys, which is pretty cool! Recursion rocks. Sometimes. + [`Item`]: /storey/containers/item From 76630ce8fe9125b582679be8a50c163c203209a0 Mon Sep 17 00:00:00 2001 From: Tomasz Kurcz Date: Wed, 19 Mar 2025 12:23:09 +0100 Subject: [PATCH 6/7] fix lints --- .../templates/storey-container-impl.tpl | 98 +++++++++---------- src/pages/storey/container-impl/my-map.mdx | 2 +- 2 files changed, 50 insertions(+), 50 deletions(-) diff --git a/docs-test-gen/templates/storey-container-impl.tpl b/docs-test-gen/templates/storey-container-impl.tpl index 893c769c..247b7cf1 100644 --- a/docs-test-gen/templates/storey-container-impl.tpl +++ b/docs-test-gen/templates/storey-container-impl.tpl @@ -67,70 +67,70 @@ fn advance_height(env: &mut Env, blocks: u64) { env.block.height += blocks; } -pub struct MyMap { - prefix: u8, - phantom: std::marker::PhantomData, -} - -impl MyMap -where - V: Storable, -{ - pub const fn new(prefix: u8) -> Self { - Self { - prefix, - phantom: std::marker::PhantomData, - } +#[test] +fn doctest() { + pub struct MyMap { + prefix: u8, + phantom: std::marker::PhantomData, } - pub fn access(&self, storage: F) -> MyMapAccess> + impl MyMap where - (F,): IntoStorage, + V: Storable, { - let storage = (storage,).into_storage(); - Self::access_impl(StorageBranch::new(storage, vec![self.prefix])) + pub const fn new(prefix: u8) -> Self { + Self { + prefix, + phantom: std::marker::PhantomData, + } + } + + pub fn access(&self, storage: F) -> MyMapAccess> + where + (F,): IntoStorage, + { + let storage = (storage,).into_storage(); + Self::access_impl(StorageBranch::new(storage, vec![self.prefix])) + } } -} -pub struct MyMapAccess { - storage: S, - phantom: std::marker::PhantomData, -} + pub struct MyMapAccess { + storage: S, + phantom: std::marker::PhantomData, + } -impl Storable for MyMap -where - V: Storable, -{ - type Kind = NonTerminal; - type Accessor = MyMapAccess; - - fn access_impl(storage: S) -> MyMapAccess { - MyMapAccess { - storage, - phantom: std::marker::PhantomData, + impl Storable for MyMap + where + V: Storable, + { + type Kind = NonTerminal; + type Accessor = MyMapAccess; + + fn access_impl(storage: S) -> MyMapAccess { + MyMapAccess { + storage, + phantom: std::marker::PhantomData, + } } } -} -impl MyMapAccess -where - V: Storable, -{ - pub fn entry(&self, key: u32) -> V::Accessor> { - let key = key.to_be_bytes().to_vec(); + impl MyMapAccess + where + V: Storable, + { + pub fn entry(&self, key: u32) -> V::Accessor> { + let key = key.to_be_bytes().to_vec(); - V::access_impl(StorageBranch::new(&self.storage, key)) - } + V::access_impl(StorageBranch::new(&self.storage, key)) + } - pub fn entry_mut(&mut self, key: u32) -> V::Accessor> { - let key = key.to_be_bytes().to_vec(); + pub fn entry_mut(&mut self, key: u32) -> V::Accessor> { + let key = key.to_be_bytes().to_vec(); - V::access_impl(StorageBranch::new(&mut self.storage, key)) + V::access_impl(StorageBranch::new(&mut self.storage, key)) + } } -} -#[test] -fn doctest() { #[allow(unused_variables, unused_mut)] let mut storage = cosmwasm_std::testing::MockStorage::new(); #[allow(unused_mut)] diff --git a/src/pages/storey/container-impl/my-map.mdx b/src/pages/storey/container-impl/my-map.mdx index c41a1f36..f58238ae 100644 --- a/src/pages/storey/container-impl/my-map.mdx +++ b/src/pages/storey/container-impl/my-map.mdx @@ -320,7 +320,7 @@ let result: Result, _> = access.values().collect(); assert_eq!(result.unwrap(), vec![100, 200, 300]); ``` -This isn't all. What we've also enabled is the ability to iterate over outer containers. +This isn't all. What we've also enabled is the ability to iterate over any containers that nest `MyMap` inside. Let's create a regular `Map`, nest our `MyMap` inside it, and see what we can do! From 1be2c5fb31e019722a728b5b56978cd25fef4fed Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Wed, 19 Mar 2025 11:23:45 +0000 Subject: [PATCH 7/7] [autofix.ci] apply automated fixes --- src/pages/storey/container-impl/my-map.mdx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/storey/container-impl/my-map.mdx b/src/pages/storey/container-impl/my-map.mdx index f58238ae..99930669 100644 --- a/src/pages/storey/container-impl/my-map.mdx +++ b/src/pages/storey/container-impl/my-map.mdx @@ -320,7 +320,8 @@ let result: Result, _> = access.values().collect(); assert_eq!(result.unwrap(), vec![100, 200, 300]); ``` -This isn't all. What we've also enabled is the ability to iterate over any containers that nest `MyMap` inside. +This isn't all. What we've also enabled is the ability to iterate over any containers that nest +`MyMap` inside. Let's create a regular `Map`, nest our `MyMap` inside it, and see what we can do!