From bc35debb5f3089c81267140c3ed34786874333fc Mon Sep 17 00:00:00 2001 From: TAdev0 Date: Tue, 7 Jan 2025 18:34:56 +0100 Subject: [PATCH 1/5] storage node module --- .../src/starknet/storage/storage_node.cairo | 163 +++++++++--------- 1 file changed, 83 insertions(+), 80 deletions(-) diff --git a/corelib/src/starknet/storage/storage_node.cairo b/corelib/src/starknet/storage/storage_node.cairo index 78caa727b03..c47a9df3d3d 100644 --- a/corelib/src/starknet/storage/storage_node.cairo +++ b/corelib/src/starknet/storage/storage_node.cairo @@ -1,86 +1,89 @@ -use super::{Mutable, StoragePath}; +//! The storage node is a struct that is used to structure the storage of a struct, while taking +//! into account this structure when computing the address of the struct members in the storage. +//! The trigger for creating a storage node is the `#[starknet::storage_node]` attribute. +//! +//! Storage nodes are used in order to structure the storage of a struct, while not enforcing the +//! struct to be sequential in the storage. This is useful for structs that contains phantom types +//! such as `Map` and `Vec`. As a result, structs attributed with `#[starknet::storage_node]` are +//! also considered to be phantom types, although not explicitly annotated as such. +//! Structs which do not contain phantom types, can still be declared a storage node, and it will +//! make them a phantom type. +//! However, it may be preferable to simply make this struct storable (i.e. `#[derive(Store)]') +//! instead. This will still allow accessing individual members of the struct (see `SubPointers`), +//! but will not make the struct a phantom type. +//! +//! For example, given the following struct: +//! +//! ``` +//! #[starknet::storage_node] +//! struct MyStruct { +//! a: felt252, +//! b: Map, +//! } +//! ``` +//! +//! The following storage node struct and impl will be generated: +//! +//! ``` +//! struct MyStructStorageNode { +//! a: PendingStoragePath, +//! b: PendingStoragePath>, +//! } +//! +//! impl MyStructStorageNodeImpl of StorageNode { +//! fn storage_node(self: StoragePath) -> MyStructStorageNode { +//! MyStructStorageNode { +//! a: PendingStoragePathTrait::new(@self, selector!("a")), +//! b: PendingStoragePathTrait::new(@self, selector!("b")), +//! } +//! } +//! } +//! ``` +//! +//! For a type `T` that implements `StorageNode` (e.g. `MyStruct` in the example above), +//! `Deref>` is implemented as simply calling `storage_node`, and thus exposing the +//! members of the storage node (`a` and `b` in the example above). +//! For example, given the following storage: +//! +//! ``` +//! #[storage] +//! struct Storage { +//! my_struct: MyStruct, +//! a: felt52, +//! } +//! +//! We can access the members of the storage node as follows: +//! +//! ``` +//! fn use_storage(self: @ContractState) { +//! let a_value = self.a.read(); +//! let inner_a_value = self.my_struct.a.read(); +//! let b_value = self.my_struct.b.entry(42).read(); +//! } +//! ``` +//! +//! If a member is annotated with `#[flat]`, the storage node will be flattened, and the +//! member name (i.e. `my_struct`) will not affect the address of the storage object. +//! In the storage example above, it will look like: +//! ``` +//! #[storage] +//! struct Storage { +//! #[flat] +//! my_struct: MyStruct, +//! a: felt52, +//! } +//! +//! In this case, the storage node will be flattened, and both `self.a` and `self.my_struct.a` will +//! point to the same address. This behavior is rarely intended, and thus `#[flat]` should be used +//! with caution. +//! +//! Notice that the members of the storage node are `PendingStoragePath` instances, which are used +//! to lazily get the updated storage path of the struct members, in this way only members that are +//! accessed are actually evaluated. +use super::{Mutable, StoragePath}; /// A trait that given a storage path of a struct, generates the storage node of this struct. -/// -/// The storage node is a struct that is used to structure the storage of a struct, while taking -/// into account this structure when computing the address of the struct members in the storage. -/// The trigger for creating a storage node is the `#[starknet::storage_node]` attribute. -/// -/// Storage nodes are used in order to structure the storage of a struct, while not enforcing the -/// struct to be sequential in the storage. This is useful for structs that contains phantom types -/// such as `Map` and `Vec`. As a result, structs attributed with `#[starknet::storage_node]` are -/// also considered to be phantom types, although not explicitly annotated as such. -/// Structs which do not contain phantom types, can still be declared a storage node, and it will -/// make them a phantom type. -/// However, it may be preferable to simply make this struct storable (i.e. `#[derive(Store)]') -/// instead. This will still allow accessing individual members of the struct (see `SubPointers`), -/// but will not make the struct a phantom type. -/// -/// For example, given the following struct: -/// ``` -/// #[starknet::storage_node] -/// struct MyStruct { -/// a: felt252, -/// b: Map, -/// } -/// ``` -/// -/// The following storage node struct and impl will be generated: -/// ``` -/// struct MyStructStorageNode { -/// a: PendingStoragePath, -/// b: PendingStoragePath>, -/// } -/// -/// impl MyStructStorageNodeImpl of StorageNode { -/// fn storage_node(self: StoragePath) -> MyStructStorageNode { -/// MyStructStorageNode { -/// a: PendingStoragePathTrait::new(@self, selector!("a")), -/// b: PendingStoragePathTrait::new(@self, selector!("b")), -/// } -/// } -/// } -/// ``` -/// For a type `T` that implement `StorageNode` (e.g. `MyStruct` in the example above), -/// `Deref>` is implemented as simply calling `storage_node`, and thus exposing the -/// members of the storage node (`a` and `b` in the example above). -/// For example, given the following storage: -/// ``` -/// #[storage] -/// struct Storage { -/// my_struct: MyStruct, -/// a: felt52, -/// } -/// -/// We can access the members of the storage node as follows: -/// ``` -/// fn use_storage(self: @ContractState) { -/// let a_value = self.a.read(); -/// let inner_a_value = self.my_struct.a.read(); -/// let b_value = self.my_struct.b.entry(42).read(); -/// } -/// ``` -/// -/// If a member is annotated with `#[flat]`, the storage node will be flattened, and the -/// member name (i.e. `my_struct`) will not affect the address of the storage object. -/// In the storage example above, it will look like: -/// ``` -/// #[storage] -/// struct Storage { -/// #[flat] -/// my_struct: MyStruct, -/// a: felt52, -/// } -/// -/// In this case, the storage node will be flattened, and both `self.a` and `self.my_struct.a` will -/// point to the same address. This behavior is rarely intended, and thus `#[flat]` should be used -/// with caution. -/// -/// Notice that the members of the storage node are `PendingStoragePath` instances, which are used -/// to lazily get the updated storage path of the struct members, in this way only members that are -/// accessed are actually evaluated. -/// pub trait StorageNode { type NodeType; fn storage_node(self: StoragePath) -> Self::NodeType; From 9caf46a1d5c06effbe0d84037942ba6bddc9fa87 Mon Sep 17 00:00:00 2001 From: enitrat Date: Thu, 9 Jan 2025 19:49:20 +0100 Subject: [PATCH 2/5] improve doc --- .../src/starknet/storage/storage_node.cairo | 64 +++++++++++-------- 1 file changed, 39 insertions(+), 25 deletions(-) diff --git a/corelib/src/starknet/storage/storage_node.cairo b/corelib/src/starknet/storage/storage_node.cairo index c47a9df3d3d..fd6ebd2c3e1 100644 --- a/corelib/src/starknet/storage/storage_node.cairo +++ b/corelib/src/starknet/storage/storage_node.cairo @@ -1,18 +1,27 @@ -//! The storage node is a struct that is used to structure the storage of a struct, while taking -//! into account this structure when computing the address of the struct members in the storage. -//! The trigger for creating a storage node is the `#[starknet::storage_node]` attribute. -//! -//! Storage nodes are used in order to structure the storage of a struct, while not enforcing the -//! struct to be sequential in the storage. This is useful for structs that contains phantom types -//! such as `Map` and `Vec`. As a result, structs attributed with `#[starknet::storage_node]` are -//! also considered to be phantom types, although not explicitly annotated as such. -//! Structs which do not contain phantom types, can still be declared a storage node, and it will -//! make them a phantom type. -//! However, it may be preferable to simply make this struct storable (i.e. `#[derive(Store)]') -//! instead. This will still allow accessing individual members of the struct (see `SubPointers`), -//! but will not make the struct a phantom type. -//! -//! For example, given the following struct: +//! Storage nodes provide a way to structure contract storage data, reflecting their structure in +//! the storage address computation of their members. They are special structs that can contain any +//! storeable type and are marked with the `#[starknet::storage_node]` attribute. +//! +//! # Purpose and Benefits +//! +//! Storage nodes provide a flexible way to structure storage data by allowing non-sequential +//! storage layouts. They allow the creation of storage-only types, that can contain both +//! storage-specific types (like `Map` and `Vec`) and regular types - so long as these types are +//! storable. +//! +//! Storage nodes are particularly valuable when defining structs containing phantom types like +//! `Map` and `Vec`. When a struct is marked with `#[starknet::storage_node]`, it automatically +//! becomes a phantom type, regardless of whether it was explicitly annotated as such. +//! +//! While you can declare any struct as a storage node (even those without phantom types), doing so +//! will make that struct a phantom type. For structs that don't contain phantom types, it's often +//! more appropriate to make them storable using `#[derive(Store)]`. This alternative approach +//! still enables access to individual struct members through `SubPointers` without imposing the +//! phantom type behavior. +//! +//! # Examples +//! +//! Here's how to define a storage node: //! //! ``` //! #[starknet::storage_node] @@ -22,7 +31,7 @@ //! } //! ``` //! -//! The following storage node struct and impl will be generated: +//! For the struct above, the following storage node struct and impl will be generated: //! //! ``` //! struct MyStructStorageNode { @@ -62,9 +71,11 @@ //! } //! ``` //! -//! If a member is annotated with `#[flat]`, the storage node will be flattened, and the -//! member name (i.e. `my_struct`) will not affect the address of the storage object. -//! In the storage example above, it will look like: +//! # Flattening Storage Nodes +//! +//! Storage Nodes members can be annotated with `#[flat]` to flatten the storage hierarchy and not +//! use the member name in the computation of the storage address for its fields. +//! //! ``` //! #[storage] //! struct Storage { @@ -72,14 +83,17 @@ //! my_struct: MyStruct, //! a: felt52, //! } +//! ``` +//! +//! When flattened, the storage node's field name (e.g., `my_struct`) doesn't affect storage address +//! computation. In the example above, both `self.a` and `self.my_struct.a` will point to the same +//! address. Use `#[flat]` with caution as this behavior is rarely intended. //! -//! In this case, the storage node will be flattened, and both `self.a` and `self.my_struct.a` will -//! point to the same address. This behavior is rarely intended, and thus `#[flat]` should be used -//! with caution. +//! # Performance Considerations //! -//! Notice that the members of the storage node are `PendingStoragePath` instances, which are used -//! to lazily get the updated storage path of the struct members, in this way only members that are -//! accessed are actually evaluated. +//! Storage node members are implemented as `PendingStoragePath` instances, enabling lazy evaluation +//! of storage paths. This means storage addresses are only computed for members that are actually +//! accessed. use super::{Mutable, StoragePath}; From e6a16bb8bd8186610f8b49191fed87fe5bf04aea Mon Sep 17 00:00:00 2001 From: enitrat Date: Thu, 9 Jan 2025 19:55:37 +0100 Subject: [PATCH 3/5] typo --- corelib/src/starknet/storage/storage_node.cairo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/corelib/src/starknet/storage/storage_node.cairo b/corelib/src/starknet/storage/storage_node.cairo index fd6ebd2c3e1..97e1248a745 100644 --- a/corelib/src/starknet/storage/storage_node.cairo +++ b/corelib/src/starknet/storage/storage_node.cairo @@ -1,6 +1,6 @@ //! Storage nodes provide a way to structure contract storage data, reflecting their structure in //! the storage address computation of their members. They are special structs that can contain any -//! storeable type and are marked with the `#[starknet::storage_node]` attribute. +//! storable type and are marked with the `#[starknet::storage_node]` attribute. //! //! # Purpose and Benefits //! From e9e265f4576dc061cce85c6b9f6ad4cc594cdc9d Mon Sep 17 00:00:00 2001 From: TAdev0 Date: Fri, 10 Jan 2025 15:00:58 +0100 Subject: [PATCH 4/5] address Orizi comment --- corelib/src/starknet/storage/storage_node.cairo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/corelib/src/starknet/storage/storage_node.cairo b/corelib/src/starknet/storage/storage_node.cairo index 97e1248a745..c7a93e74a18 100644 --- a/corelib/src/starknet/storage/storage_node.cairo +++ b/corelib/src/starknet/storage/storage_node.cairo @@ -11,7 +11,7 @@ //! //! Storage nodes are particularly valuable when defining structs containing phantom types like //! `Map` and `Vec`. When a struct is marked with `#[starknet::storage_node]`, it automatically -//! becomes a phantom type, regardless of whether it was explicitly annotated as such. +//! becomes a phantom type. //! //! While you can declare any struct as a storage node (even those without phantom types), doing so //! will make that struct a phantom type. For structs that don't contain phantom types, it's often From 50e39e9d66b21abe9a67d03efca8b61e00430810 Mon Sep 17 00:00:00 2001 From: TAdev0 Date: Sat, 11 Jan 2025 18:03:37 +0100 Subject: [PATCH 5/5] add precisions about struct / storage node storage layout --- corelib/src/starknet/storage/storage_node.cairo | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/corelib/src/starknet/storage/storage_node.cairo b/corelib/src/starknet/storage/storage_node.cairo index c7a93e74a18..190a7ff0bca 100644 --- a/corelib/src/starknet/storage/storage_node.cairo +++ b/corelib/src/starknet/storage/storage_node.cairo @@ -19,6 +19,13 @@ //! still enables access to individual struct members through `SubPointers` without imposing the //! phantom type behavior. //! +//! The storage layout differs significantly between these two approaches: +//! * `#[derive(Store)]`: Members are stored continuously in the same variable space, with a limit +//! of 256 field elements. +//! * Storage node: Each member is stored at a different location. For a storage node member `m` +//! within a storage variable `variable_name`, the path to that member is computed as +//! `h(sn_keccak(variable_name), sn_keccak(m))`, where `h` is the Pedersen hash. +//! //! # Examples //! //! Here's how to define a storage node: