From 99ac5c36b57032c05debfaedce65b3fa26e488cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Vel=C3=A1zquez?= Date: Mon, 11 Aug 2025 12:40:29 -0600 Subject: [PATCH 1/4] Add comprehensive documentation for implementing EnumerableSet with custom storage types - Enhanced existing EnumerableSet docs with usage examples - Created comprehensive Antora documentation for custom storage types - Added note about StorageBytes/StorageString limitations - Updated navigation and cross-references Closes #761 --- .../src/utils/structs/enumerable_set/mod.rs | 58 ++++- docs/modules/ROOT/nav.adoc | 1 + .../ROOT/pages/enumerable-set-custom.adoc | 243 ++++++++++++++++++ docs/modules/ROOT/pages/utilities.adoc | 2 +- 4 files changed, 296 insertions(+), 8 deletions(-) create mode 100644 docs/modules/ROOT/pages/enumerable-set-custom.adoc diff --git a/contracts/src/utils/structs/enumerable_set/mod.rs b/contracts/src/utils/structs/enumerable_set/mod.rs index e343b030a..79b885981 100644 --- a/contracts/src/utils/structs/enumerable_set/mod.rs +++ b/contracts/src/utils/structs/enumerable_set/mod.rs @@ -1,14 +1,56 @@ //! Smart contract for managing sets. +//! +//! Sets have the following properties: +//! +//! * Elements are added, removed, and checked for existence in constant time +//! (O(1)). +//! * Elements are enumerated in O(n). No guarantees are made on the ordering. +//! * Set can be cleared (all elements removed) in O(n). +//! +//! ## Usage +//! +//! `EnumerableSet` works with many primitive types out of the box: +//! +//! ``` +//! extern crate alloc; +//! +//! use alloy_primitives::{Address, U256}; +//! use stylus_sdk::prelude::*; +//! use openzeppelin_stylus::utils::structs::enumerable_set::EnumerableSet; +//! +//! #[storage] +//! struct MyContract { +//! whitelist: EnumerableSet
, +//! } +//! +//! #[public] +//! impl MyContract { +//! fn add_to_whitelist(&mut self, address: Address) -> bool { +//! self.whitelist.add(address) +//! } +//! +//! fn remove_from_whitelist(&mut self, address: Address) -> bool { +//! self.whitelist.remove(address) +//! } +//! +//! fn is_whitelisted(&self, address: Address) -> bool { +//! self.whitelist.contains(address) +//! } +//! +//! fn get_whitelist_size(&self) -> U256 { +//! self.whitelist.length() +//! } +//! } +//! ``` +//! +//! ## Custom Storage Types +//! +//! You can implement `EnumerableSet` for your own storage types by implementing +//! the `Element` and `Accessor` traits. See `element.rs` for trait definitions +//! and the documentation for comprehensive examples. pub mod element; -/// Sets have the following properties: -/// -/// * Elements are added, removed, and checked for existence in constant -/// time (O(1)). -/// * Elements are enumerated in O(n). No guarantees are made on the -/// ordering. -/// * Set can be cleared (all elements removed) in O(n). use alloc::{vec, vec::Vec}; use alloy_primitives::{uint, U256}; @@ -188,6 +230,8 @@ mod tests { use stylus_sdk::prelude::TopLevelStorage; use alloy_primitives::private::proptest::{prop_assert, prop_assert_eq, proptest}; + extern crate alloc; + unsafe impl TopLevelStorage for $set_type {} #[public] diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc index 4ce942940..70977039f 100644 --- a/docs/modules/ROOT/nav.adoc +++ b/docs/modules/ROOT/nav.adoc @@ -20,3 +20,4 @@ ** xref:uups-proxy.adoc[UUPS Proxy] * xref:utilities.adoc[Utilities] +** xref:enumerable-set-custom.adoc[EnumerableSet] \ No newline at end of file diff --git a/docs/modules/ROOT/pages/enumerable-set-custom.adoc b/docs/modules/ROOT/pages/enumerable-set-custom.adoc new file mode 100644 index 000000000..0c8a31c0c --- /dev/null +++ b/docs/modules/ROOT/pages/enumerable-set-custom.adoc @@ -0,0 +1,243 @@ += EnumerableSet + +The `EnumerableSet` utility in OpenZeppelin Stylus Contracts provides an efficient way to manage sets of values in smart contracts. While it comes with built-in support for many primitive types like `Address`, `U256`, and `B256`, you can also implement it for your own custom storage types by implementing the required traits. + +[[overview]] +== Overview + +`EnumerableSet` is a generic data structure that provides O(1) time complexity for adding, removing, and checking element existence, while allowing enumeration of all elements in O(n) time. The generic type `T` must implement the `Element` trait, which associates the element type with its corresponding storage type. + +[[built-in-types]] +== Built-in Supported Types + +The following types are already supported out of the box: + +- `Address` → `StorageAddress` +- `B256` → `StorageB256` +- `U8` → `StorageU8` +- `U16` → `StorageU16` +- `U32` → `StorageU32` +- `U64` → `StorageU64` +- `U128` → `StorageU128` +- `U256` → `StorageU256` + +[[custom-implementation]] +== Implementing for Custom Storage Types + +To use `EnumerableSet` with your own storage types, you need to implement two traits: + +1. **`Element`** - Associates your element type with its storage type +2. **`Accessor`** - Provides getter and setter methods for the storage type + +[[element-trait]] +=== The Element Trait + +The `Element` trait requires your type to implement `StorageKey + Copy` and defines an associated type `StorageElement`: + +[source,rust] +---- +pub trait Element: StorageKey + Copy { + /// Set element type in storage. + type StorageElement: for<'a> StorageType = Self> + + Accessor + + for<'a> SimpleStorageType<'a> + + Erase; +} +---- + +[[accessor-trait]] +=== The Accessor Trait + +The `Accessor` trait provides the interface for reading and writing values to storage: + +[source,rust] +---- +pub trait Accessor { + /// Type of the number associated with the storage type. + type Wraps; + + /// Gets underlying element from persistent storage. + fn get(&self) -> Self::Wraps; + + /// Sets underlying element in persistent storage. + fn set(&mut self, value: Self::Wraps); +} +---- + +[[implementation-example]] +== Complete Implementation Example + +Here's a complete example showing how to implement `EnumerableSet` for a custom `User` struct: + +[source,rust] +---- +use openzeppelin_stylus::{ + utils::structs::enumerable_set::{EnumerableSet, Element, Accessor}, + prelude::*, +}; +use stylus_sdk::storage::{StorageMap, StorageU256, StorageVec}; + +// Define your custom struct +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +struct User { + id: U256, + name: String, +} + +// Define the storage type for User +#[storage] +struct StorageUser { + id: StorageU256, + name: StorageString, +} + +// Implement Element trait for User +impl Element for User { + type StorageElement = StorageUser; +} + +// Implement Accessor trait for StorageUser +impl Accessor for StorageUser { + type Wraps = User; + + fn get(&self) -> Self::Wraps { + User { + id: self.id.get(), + name: self.name.get(), + } + } + + fn set(&mut self, value: Self::Wraps) { + self.id.set(value.id); + self.name.set(value.name); + } +} + +// Now you can use EnumerableSet in your contract +#[storage] +struct MyContract { + users: EnumerableSet, + user_count: StorageU256, +} + +#[public] +impl MyContract { + fn add_user(&mut self, user: User) -> bool { + let added = self.users.add(user); + if added { + self.user_count.set(self.user_count.get() + U256::from(1)); + } + added + } + + fn remove_user(&mut self, user: User) -> bool { + let removed = self.users.remove(user); + if removed { + self.user_count.set(self.user_count.get() - U256::from(1)); + } + removed + } + + fn get_user_at(&self, index: U256) -> Option { + self.users.at(index) + } + + fn get_all_users(&self) -> Vec { + self.users.values() + } + + fn user_count(&self) -> U256 { + self.user_count.get() + } +} +---- + +[[limitations]] +== Current Limitations + +**Note:** `StorageBytes` and `StorageString` cannot currently be implemented for `EnumerableSet` due to limitations in the Stylus SDK. This limitations may change in future versions of the Stylus SDK. + + +[[best-practices]] +== Best Practices + +1. **Keep element types small**: Since `EnumerableSet` stores all elements in storage, large element types will increase gas costs significantly. + +2. **Use appropriate storage types**: Choose storage types that efficiently represent your data. For example, use `StorageU64` instead of `StorageU256` if your values fit in 64 bits. + +3. **Consider gas costs**: Each operation (add, remove, contains) has a gas cost. For frequently accessed sets, consider caching frequently used values in memory. + +4. **Test thoroughly**: Use property-based testing to ensure your custom implementation maintains the mathematical properties of sets (idempotency, commutativity, associativity, etc.). + +[[testing]] +== Testing Your Implementation + +The built-in tests for `EnumerableSet` use property-based testing to verify set properties. You can run these tests to ensure your custom implementation works correctly: + +[source,bash] +---- +cargo test --package openzeppelin-stylus-contracts --test enumerable_set +---- + +[[advanced-usage]] +== Advanced Usage Patterns + +=== Role-based Access Control + +`EnumerableSet` is commonly used in access control systems to manage role members: + +[source,rust] +---- +#[storage] +struct AccessControl { + role_members: StorageMap>, +} + +impl AccessControl { + fn grant_role(&mut self, role: B256, account: Address) { + self.role_members.get(role).add(account); + } + + fn revoke_role(&mut self, role: B256, account: Address) { + self.role_members.get(role).remove(account); + } + + fn get_role_members(&self, role: B256) -> Vec
{ + self.role_members.get(role).values() + } +} +---- + +=== Whitelist Management + +Manage whitelisted addresses efficiently: + +[source,rust] +---- +#[storage] +struct Whitelist { + allowed_addresses: EnumerableSet
, + max_whitelist_size: StorageU256, +} + +impl Whitelist { + fn add_to_whitelist(&mut self, address: Address) -> Result<(), String> { + if self.allowed_addresses.length() >= self.max_whitelist_size.get() { + return Err("Whitelist is full".to_string()); + } + + if self.allowed_addresses.add(address) { + Ok(()) + } else { + Err("Address already in whitelist".to_string()) + } + } +} +---- + +[[conclusion]] +== Conclusion + +By implementing the `Element` and `Accessor` traits, you can extend `EnumerableSet` to work with any custom storage type that meets the requirements. This provides a powerful and flexible way to manage sets of complex data structures in your Stylus smart contracts while maintaining the performance characteristics of the underlying implementation. + +Remember to consider gas costs and storage efficiency when designing your custom types, and always test thoroughly to ensure correctness. \ No newline at end of file diff --git a/docs/modules/ROOT/pages/utilities.adoc b/docs/modules/ROOT/pages/utilities.adoc index 13c8c9925..b32fb6098 100644 --- a/docs/modules/ROOT/pages/utilities.adoc +++ b/docs/modules/ROOT/pages/utilities.adoc @@ -48,4 +48,4 @@ Contracts for Stylus provides these libraries for enhanced data structure manage - https://docs.rs/openzeppelin-stylus/0.3.0-rc.1/openzeppelin_stylus/utils/structs/bitmap/index.html[`BitMaps`]: Store packed booleans in storage. - https://docs.rs/openzeppelin-stylus/0.3.0-rc.1/openzeppelin_stylus/utils/structs/checkpoints/index.html[`Checkpoints`]: Checkpoint values with built-in lookups. -- https://docs.rs/openzeppelin-stylus/0.3.0-rc.1/openzeppelin_stylus/utils/structs/enumerable_set/index.html[`EnumerableSets`]: Contract for managing sets of many primitive types. +- https://docs.rs/openzeppelin-stylus/0.3.0-rc.1/openzeppelin_stylus/utils/structs/enumerable_set/index.html[`EnumerableSets`]: Contract for managing sets of many primitive types. For information on implementing with custom storage types, see xref:enumerable-set-custom.adoc[EnumerableSet Custom Types]. \ No newline at end of file From 80719cffd222d60b93e2c9874d8e1d53967b1792 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jos=C3=A9=20v?= <52646071+Peponks9@users.noreply.github.com> Date: Fri, 15 Aug 2025 18:19:09 -0600 Subject: [PATCH 2/4] Update contracts/src/utils/structs/enumerable_set/mod.rs Add `rust` Co-authored-by: Nenad --- contracts/src/utils/structs/enumerable_set/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contracts/src/utils/structs/enumerable_set/mod.rs b/contracts/src/utils/structs/enumerable_set/mod.rs index 79b885981..6648266ac 100644 --- a/contracts/src/utils/structs/enumerable_set/mod.rs +++ b/contracts/src/utils/structs/enumerable_set/mod.rs @@ -11,7 +11,7 @@ //! //! `EnumerableSet` works with many primitive types out of the box: //! -//! ``` +//! ```rust //! extern crate alloc; //! //! use alloy_primitives::{Address, U256}; @@ -19,6 +19,7 @@ //! use openzeppelin_stylus::utils::structs::enumerable_set::EnumerableSet; //! //! #[storage] +//! #[entrypoint] //! struct MyContract { //! whitelist: EnumerableSet
, //! } From 9fdc13f0453f2550ec0fd48a6d68c068d8bc6471 Mon Sep 17 00:00:00 2001 From: Peponks9 Date: Fri, 15 Aug 2025 19:30:39 -0600 Subject: [PATCH 3/4] Improve EnumerableSet documentation - Add comprehensive primitive type list with rustdoc links - Move detailed docs from module level to struct level - Update custom implementation guide with step-by-step breakdown - Fix documentation structure and formatting issues - Update limitations section with proper type links --- .../src/utils/structs/enumerable_set/mod.rs | 108 +++++----- docs/modules/ROOT/nav.adoc | 2 +- .../ROOT/pages/enumerable-set-custom.adoc | 186 ++++++++++++------ docs/modules/ROOT/pages/utilities.adoc | 2 +- 4 files changed, 187 insertions(+), 111 deletions(-) diff --git a/contracts/src/utils/structs/enumerable_set/mod.rs b/contracts/src/utils/structs/enumerable_set/mod.rs index 79b885981..89042efba 100644 --- a/contracts/src/utils/structs/enumerable_set/mod.rs +++ b/contracts/src/utils/structs/enumerable_set/mod.rs @@ -1,53 +1,4 @@ //! Smart contract for managing sets. -//! -//! Sets have the following properties: -//! -//! * Elements are added, removed, and checked for existence in constant time -//! (O(1)). -//! * Elements are enumerated in O(n). No guarantees are made on the ordering. -//! * Set can be cleared (all elements removed) in O(n). -//! -//! ## Usage -//! -//! `EnumerableSet` works with many primitive types out of the box: -//! -//! ``` -//! extern crate alloc; -//! -//! use alloy_primitives::{Address, U256}; -//! use stylus_sdk::prelude::*; -//! use openzeppelin_stylus::utils::structs::enumerable_set::EnumerableSet; -//! -//! #[storage] -//! struct MyContract { -//! whitelist: EnumerableSet
, -//! } -//! -//! #[public] -//! impl MyContract { -//! fn add_to_whitelist(&mut self, address: Address) -> bool { -//! self.whitelist.add(address) -//! } -//! -//! fn remove_from_whitelist(&mut self, address: Address) -> bool { -//! self.whitelist.remove(address) -//! } -//! -//! fn is_whitelisted(&self, address: Address) -> bool { -//! self.whitelist.contains(address) -//! } -//! -//! fn get_whitelist_size(&self) -> U256 { -//! self.whitelist.length() -//! } -//! } -//! ``` -//! -//! ## Custom Storage Types -//! -//! You can implement `EnumerableSet` for your own storage types by implementing -//! the `Element` and `Accessor` traits. See `element.rs` for trait definitions -//! and the documentation for comprehensive examples. pub mod element; @@ -60,7 +11,63 @@ use stylus_sdk::{ storage::{StorageMap, StorageType, StorageU256, StorageVec}, }; -/// State of an [`EnumerableSet`] contract. +/// Sets have the following properties: +/// +/// * Elements are added, removed, and checked for existence in constant time +/// (O(1)). +/// * Elements are enumerated in O(n). No guarantees are made on the ordering. +/// * Set can be cleared (all elements removed) in O(n). +/// +/// ## Usage +/// +/// `EnumerableSet` works with the following primitive types out of the box: +/// +/// * [`alloy_primitives::Address`] - Ethereum addresses +/// * [`alloy_primitives::B256`] - 256-bit byte arrays +/// * [`alloy_primitives::U8`] - 8-bit unsigned integers +/// * [`alloy_primitives::U16`] - 16-bit unsigned integers +/// * [`alloy_primitives::U32`] - 32-bit unsigned integers +/// * [`alloy_primitives::U64`] - 64-bit unsigned integers +/// * [`alloy_primitives::U128`] - 128-bit unsigned integers +/// * [`alloy_primitives::U256`] - 256-bit unsigned integers +/// +/// ```rust +/// extern crate alloc; +/// +/// use alloy_primitives::{Address, U256}; +/// use stylus_sdk::prelude::*; +/// use openzeppelin_stylus::utils::structs::enumerable_set::EnumerableSet; +/// +/// #[storage] +/// struct MyContract { +/// whitelist: EnumerableSet
, +/// } +/// +/// #[public] +/// impl MyContract { +/// fn add_to_whitelist(&mut self, address: Address) -> bool { +/// self.whitelist.add(address) +/// } +/// +/// fn remove_from_whitelist(&mut self, address: Address) -> bool { +/// self.whitelist.remove(address) +/// } +/// +/// fn is_whitelisted(&self, address: Address) -> bool { +/// self.whitelist.contains(address) +/// } +/// +/// fn get_whitelist_size(&self) -> U256 { +/// self.whitelist.length() +/// } +/// } +/// ``` +/// +/// ## Custom Storage Types +/// +/// You can implement `EnumerableSet` for your own storage types by implementing +/// the `Element` and `Accessor` traits. See [`element.rs`] for trait +/// definitions and the documentation for comprehensive examples. #[storage] pub struct EnumerableSet { /// Values in the set. @@ -230,7 +237,6 @@ mod tests { use stylus_sdk::prelude::TopLevelStorage; use alloy_primitives::private::proptest::{prop_assert, prop_assert_eq, proptest}; - extern crate alloc; unsafe impl TopLevelStorage for $set_type {} diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc index 70977039f..2ce52e69e 100644 --- a/docs/modules/ROOT/nav.adoc +++ b/docs/modules/ROOT/nav.adoc @@ -20,4 +20,4 @@ ** xref:uups-proxy.adoc[UUPS Proxy] * xref:utilities.adoc[Utilities] -** xref:enumerable-set-custom.adoc[EnumerableSet] \ No newline at end of file +** xref:enumerable-set-custom.adoc[EnumerableSet Implementation for Custom Storage Types] \ No newline at end of file diff --git a/docs/modules/ROOT/pages/enumerable-set-custom.adoc b/docs/modules/ROOT/pages/enumerable-set-custom.adoc index 0c8a31c0c..8d7afe9d2 100644 --- a/docs/modules/ROOT/pages/enumerable-set-custom.adoc +++ b/docs/modules/ROOT/pages/enumerable-set-custom.adoc @@ -1,4 +1,4 @@ -= EnumerableSet += EnumerableSet Implementation for Custom Storage Types The `EnumerableSet` utility in OpenZeppelin Stylus Contracts provides an efficient way to manage sets of values in smart contracts. While it comes with built-in support for many primitive types like `Address`, `U256`, and `B256`, you can also implement it for your own custom storage types by implementing the required traits. @@ -12,62 +12,144 @@ The `EnumerableSet` utility in OpenZeppelin Stylus Contracts provides an efficie The following types are already supported out of the box: -- `Address` → `StorageAddress` -- `B256` → `StorageB256` -- `U8` → `StorageU8` -- `U16` → `StorageU16` -- `U32` → `StorageU32` -- `U64` → `StorageU64` -- `U128` → `StorageU128` -- `U256` → `StorageU256` +[[built-in-types]] +== Built-in Supported Types + +The following types are already supported out of the box: + +- https://docs.rs/alloy-primitives/latest/alloy_primitives/struct.Address.html[`Address`] → https://docs.rs/stylus-sdk/latest/stylus_sdk/storage/struct.StorageAddress.html[`StorageAddress`] +- https://docs.rs/alloy-primitives/latest/alloy_primitives/aliases/type.B256.html[`B256`] → https://docs.rs/stylus-sdk/latest/stylus_sdk/storage/type.StorageB256.html[`StorageB256`] +- https://docs.rs/alloy-primitives/latest/alloy_primitives/aliases/type.U8.html[`U8`] → https://docs.rs/stylus-sdk/latest/stylus_sdk/storage/type.StorageU8.html[`StorageU8`] +- https://docs.rs/alloy-primitives/latest/alloy_primitives/aliases/type.U16.html[`U16`] → https://docs.rs/stylus-sdk/latest/stylus_sdk/storage/type.StorageU16.html[`StorageU16`] +- https://docs.rs/alloy-primitives/latest/alloy_primitives/aliases/type.U32.html[`U32`] → https://docs.rs/stylus-sdk/latest/stylus_sdk/storage/type.StorageU32.html[`StorageU32`] +- https://docs.rs/alloy-primitives/latest/alloy_primitives/aliases/type.U64.html[`U64`] → https://docs.rs/stylus-sdk/latest/stylus_sdk/storage/type.StorageU64.html[`StorageU64`] +- https://docs.rs/alloy-primitives/latest/alloy_primitives/aliases/type.U128.html[`U128`] → https://docs.rs/stylus-sdk/latest/stylus_sdk/storage/type.StorageU128.html[`StorageU128`] +- hhttps://docs.rs/alloy-primitives/latest/alloy_primitives/aliases/type.U256.html[`U256`] → https://docs.rs/stylus-sdk/latest/stylus_sdk/storage/type.StorageU256.html[`StorageU256`] [[custom-implementation]] == Implementing for Custom Storage Types To use `EnumerableSet` with your own storage types, you need to implement two traits: -1. **`Element`** - Associates your element type with its storage type -2. **`Accessor`** - Provides getter and setter methods for the storage type +1. https://docs.rs/openzeppelin-stylus/0.3.0-rc.1/openzeppelin_stylus/utils/structs/enumerable_set/element/trait.Element.html[`Element`] - Associates your element type with its storage type +2. https://docs.rs/openzeppelin-stylus/0.3.0-rc.1/openzeppelin_stylus/utils/structs/enumerable_set/element/trait.Accessor.html[`Accessor`] - Provides getter and setter methods for the storage type + +[[implementation-example]] +== Step-by-Step Implementation + +Let's implement `EnumerableSet` for a custom `User` struct by breaking it down into clear steps: -[[element-trait]] -=== The Element Trait +=== Step 1: Define Your Custom Type -The `Element` trait requires your type to implement `StorageKey + Copy` and defines an associated type `StorageElement`: +First, define your custom struct that will be stored in the set. It must implement the required traits for hashing and comparison: [source,rust] ---- -pub trait Element: StorageKey + Copy { - /// Set element type in storage. - type StorageElement: for<'a> StorageType = Self> - + Accessor - + for<'a> SimpleStorageType<'a> - + Erase; +use alloy_primitives::U256; +use stylus_sdk::prelude::*; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +struct User { + id: U256, + role: u8, } ---- -[[accessor-trait]] -=== The Accessor Trait +=== Step 2: Create a Storage Wrapper -The `Accessor` trait provides the interface for reading and writing values to storage: +Create a storage struct that mirrors your custom type using Stylus storage types: [source,rust] ---- -pub trait Accessor { - /// Type of the number associated with the storage type. - type Wraps; +use stylus_sdk::storage::{StorageU256, StorageU8}; - /// Gets underlying element from persistent storage. - fn get(&self) -> Self::Wraps; +#[storage] +struct StorageUser { + id: StorageU256, + role: StorageU8, +} +---- + +=== Step 3: Implement the Required Traits - /// Sets underlying element in persistent storage. - fn set(&mut self, value: Self::Wraps); +Implement both the `Element` and `Accessor` traits to connect your type with its storage representation: + +[source,rust] +---- +use openzeppelin_stylus::utils::structs::enumerable_set::{Element, Accessor}; + +// Connect User with its storage type +impl Element for User { + type StorageElement = StorageUser; +} + +// Provide get/set methods for the storage type +impl Accessor for StorageUser { + type Wraps = User; + + fn get(&self) -> Self::Wraps { + User { + id: self.id.get(), + role: self.role.get(), + } + } + + fn set(&mut self, value: Self::Wraps) { + self.id.set(value.id); + self.role.set(value.role); + } } ---- -[[implementation-example]] -== Complete Implementation Example +=== Step 4: Use Your Custom EnumerableSet -Here's a complete example showing how to implement `EnumerableSet` for a custom `User` struct: +Now you can use `EnumerableSet` in your smart contract: + +[source,rust] +---- +use openzeppelin_stylus::utils::structs::enumerable_set::EnumerableSet; + +#[storage] +struct MyContract { + users: EnumerableSet, + user_count: StorageU256, +} + +#[public] +impl MyContract { + fn add_user(&mut self, user: User) -> bool { + let added = self.users.add(user); + if added { + self.user_count.set(self.user_count.get() + U256::from(1)); + } + added + } + + fn remove_user(&mut self, user: User) -> bool { + let removed = self.users.remove(user); + if removed { + self.user_count.set(self.user_count.get() - U256::from(1)); + } + removed + } + + fn get_user_at(&self, index: U256) -> Option { + self.users.at(index) + } + + fn get_all_users(&self) -> Vec { + self.users.values() + } + + fn user_count(&self) -> U256 { + self.user_count.get() + } +} +---- + +=== Complete Implementation Example + +Here's the complete code putting all the steps together: [source,rust] ---- @@ -75,45 +157,46 @@ use openzeppelin_stylus::{ utils::structs::enumerable_set::{EnumerableSet, Element, Accessor}, prelude::*, }; -use stylus_sdk::storage::{StorageMap, StorageU256, StorageVec}; +use stylus_sdk::storage::{StorageU256, StorageU8}; +use alloy_primitives::U256; -// Define your custom struct +// Step 1: Define your custom struct #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] struct User { id: U256, - name: String, + role: u8, } -// Define the storage type for User +// Step 2: Define the storage type for User #[storage] struct StorageUser { id: StorageU256, - name: StorageString, + role: StorageU8, } -// Implement Element trait for User +// Step 3: Implement Element trait for User impl Element for User { type StorageElement = StorageUser; } -// Implement Accessor trait for StorageUser +// Step 3: Implement Accessor trait for StorageUser impl Accessor for StorageUser { type Wraps = User; fn get(&self) -> Self::Wraps { User { id: self.id.get(), - name: self.name.get(), + role: self.role.get(), } } fn set(&mut self, value: Self::Wraps) { self.id.set(value.id); - self.name.set(value.name); + self.role.set(value.role); } } -// Now you can use EnumerableSet in your contract +// Step 4: Use EnumerableSet in your contract #[storage] struct MyContract { users: EnumerableSet, @@ -155,8 +238,7 @@ impl MyContract { [[limitations]] == Current Limitations -**Note:** `StorageBytes` and `StorageString` cannot currently be implemented for `EnumerableSet` due to limitations in the Stylus SDK. This limitations may change in future versions of the Stylus SDK. - +**Note:** https://docs.rs/alloy-primitives/latest/alloy_primitives/struct.Bytes.html[`Bytes`] and `String` cannot currently be implemented for `EnumerableSet` due to limitations in the Stylus SDK. These limitations may change in future versions of the Stylus SDK. [[best-practices]] == Best Practices @@ -169,11 +251,6 @@ impl MyContract { 4. **Test thoroughly**: Use property-based testing to ensure your custom implementation maintains the mathematical properties of sets (idempotency, commutativity, associativity, etc.). -[[testing]] -== Testing Your Implementation - -The built-in tests for `EnumerableSet` use property-based testing to verify set properties. You can run these tests to ensure your custom implementation works correctly: - [source,bash] ---- cargo test --package openzeppelin-stylus-contracts --test enumerable_set @@ -233,11 +310,4 @@ impl Whitelist { } } } ----- - -[[conclusion]] -== Conclusion - -By implementing the `Element` and `Accessor` traits, you can extend `EnumerableSet` to work with any custom storage type that meets the requirements. This provides a powerful and flexible way to manage sets of complex data structures in your Stylus smart contracts while maintaining the performance characteristics of the underlying implementation. - -Remember to consider gas costs and storage efficiency when designing your custom types, and always test thoroughly to ensure correctness. \ No newline at end of file +---- \ No newline at end of file diff --git a/docs/modules/ROOT/pages/utilities.adoc b/docs/modules/ROOT/pages/utilities.adoc index b32fb6098..abb8b5550 100644 --- a/docs/modules/ROOT/pages/utilities.adoc +++ b/docs/modules/ROOT/pages/utilities.adoc @@ -48,4 +48,4 @@ Contracts for Stylus provides these libraries for enhanced data structure manage - https://docs.rs/openzeppelin-stylus/0.3.0-rc.1/openzeppelin_stylus/utils/structs/bitmap/index.html[`BitMaps`]: Store packed booleans in storage. - https://docs.rs/openzeppelin-stylus/0.3.0-rc.1/openzeppelin_stylus/utils/structs/checkpoints/index.html[`Checkpoints`]: Checkpoint values with built-in lookups. -- https://docs.rs/openzeppelin-stylus/0.3.0-rc.1/openzeppelin_stylus/utils/structs/enumerable_set/index.html[`EnumerableSets`]: Contract for managing sets of many primitive types. For information on implementing with custom storage types, see xref:enumerable-set-custom.adoc[EnumerableSet Custom Types]. \ No newline at end of file +- https://docs.rs/openzeppelin-stylus/0.3.0-rc.1/openzeppelin_stylus/utils/structs/enumerable_set/index.html[`EnumerableSets`]: Contract for managing sets of many primitive types. For information on implementing with custom storage types, see: xref:enumerable-set-custom.adoc[EnumerableSet Implementation for Custom Storage Types]. \ No newline at end of file From 7fe7c36bb04d564db47f06e66b379424f326d960 Mon Sep 17 00:00:00 2001 From: Peponks9 Date: Fri, 15 Aug 2025 19:40:59 -0600 Subject: [PATCH 4/4] Correct mod.rs file --- .../src/utils/structs/enumerable_set/mod.rs | 53 ------------------- 1 file changed, 53 deletions(-) diff --git a/contracts/src/utils/structs/enumerable_set/mod.rs b/contracts/src/utils/structs/enumerable_set/mod.rs index fdaec8183..89042efba 100644 --- a/contracts/src/utils/structs/enumerable_set/mod.rs +++ b/contracts/src/utils/structs/enumerable_set/mod.rs @@ -1,57 +1,4 @@ //! Smart contract for managing sets. -<<<<<<< HEAD -======= -//! -//! Sets have the following properties: -//! -//! * Elements are added, removed, and checked for existence in constant time -//! (O(1)). -//! * Elements are enumerated in O(n). No guarantees are made on the ordering. -//! * Set can be cleared (all elements removed) in O(n). -//! -//! ## Usage -//! -//! `EnumerableSet` works with many primitive types out of the box: -//! -//! ```rust -//! extern crate alloc; -//! -//! use alloy_primitives::{Address, U256}; -//! use stylus_sdk::prelude::*; -//! use openzeppelin_stylus::utils::structs::enumerable_set::EnumerableSet; -//! -//! #[storage] -//! #[entrypoint] -//! struct MyContract { -//! whitelist: EnumerableSet
, -//! } -//! -//! #[public] -//! impl MyContract { -//! fn add_to_whitelist(&mut self, address: Address) -> bool { -//! self.whitelist.add(address) -//! } -//! -//! fn remove_from_whitelist(&mut self, address: Address) -> bool { -//! self.whitelist.remove(address) -//! } -//! -//! fn is_whitelisted(&self, address: Address) -> bool { -//! self.whitelist.contains(address) -//! } -//! -//! fn get_whitelist_size(&self) -> U256 { -//! self.whitelist.length() -//! } -//! } -//! ``` -//! -//! ## Custom Storage Types -//! -//! You can implement `EnumerableSet` for your own storage types by implementing -//! the `Element` and `Accessor` traits. See `element.rs` for trait definitions -//! and the documentation for comprehensive examples. ->>>>>>> 80719cffd222d60b93e2c9874d8e1d53967b1792 pub mod element;