From 536872b4544e0199cf3aa5c12df17bb5cd280cf6 Mon Sep 17 00:00:00 2001 From: Tristan <122918260+TAdev0@users.noreply.github.com> Date: Tue, 7 Jan 2025 23:39:11 +0100 Subject: [PATCH] Core Lib Documentation:`Map` module (#7021) Co-authored-by: enitrat --- corelib/src/starknet/storage/map.cairo | 174 +++++++++++++++++++++++-- 1 file changed, 160 insertions(+), 14 deletions(-) diff --git a/corelib/src/starknet/storage/map.cairo b/corelib/src/starknet/storage/map.cairo index d62670078b5..ec4334b9f59 100644 --- a/corelib/src/starknet/storage/map.cairo +++ b/corelib/src/starknet/storage/map.cairo @@ -1,32 +1,178 @@ +//! Key-value storage mapping implementation for Starknet contracts. +//! +//! This module provides the core mapping functionality used in Starknet smart contracts, +//! enabling persistent key-value storage. Unlike traditional hash tables, storage mappings +//! do not store the key data itself. Instead, they use the hash of the key to compute +//! a storage slot address where the corresponding value is stored. +//! +//! # Interacting with [`Map`] +//! +//! Storage maps can be accessed through two sets of traits, each serving different use cases: +//! +//! 1. Direct access using `StorageMapReadAccess`/`StorageMapWriteAccess`: +//! These traits allow you to read from or write to a map directly by providing the key(s) +//! and value: +//! ``` +//! // Read directly with key +//! let value = self.my_map.read(key); +//! +//! // Write directly with key and value +//! self.my_map.write(key, value); +//! ``` +//! +//! 2. Path-based access combining `StoragePathEntry` with +//! `StoragePointerReadAccess`/`StoragePointerWriteAccess`: +//! This approach first computes a `StoragePath` for the entry, which can then be used with +//! the `StoragePointer` access traits from `starknet::storage`: +//! ``` +//! // Get storage path for the entry +//! let path = self.my_map.entry(key); +//! +//! // Read/write using the storage pointer traits +//! let value = path.read(); +//! path.write(new_value); +//! ``` +//! +//! The path-based approach is particularly useful for: +//! - Nested mappings where you need to chain multiple keys +//! - Cases where you need to reuse the same storage path multiple times +//! +//! # Storage Address Computation +//! +//! Storage addresses for mapping entries are deterministically computed using hash functions: +//! +//! * For a single key mapping: +//! ```text +//! address = h(sn_keccak(variable_name), k) mod N +//! ``` +//! where: +//! - `h` is the Pedersen hash function +//! - `k` is the key value +//! - `N` is 2^251 - 256 +//! +//! * For nested mappings with multiple keys: +//! ```text +//! address = h(h(...h(h(sn_keccak(variable_name), k₁), k₂)...), kₙ) mod N +//! ``` +//! where each key `kᵢ` is hashed sequentially with the result of the previous hash. +//! +//! # Examples +//! +//! Basic usage with a single mapping: +//! +//! ``` +//! #[storage] +//! struct Storage { +//! balances: Map, +//! } +//! +//! fn read_storage(self: @ContractState) { +//! let balance = self.balances.read(address); +//! let balance = self.balances.entry(address).read(); +//! } +//! ``` +//! +//! Nested mappings: +//! +//! ``` +//! #[storage] +//! struct Storage { +//! allowances: Map>, +//! } +//! +//! fn read_storage(self: @ContractState) { +//! let allowance = self.allowances.entry(owner).entry(spender).read(); +//! let allowance = self.allowances.read(owner, spender); +//! } +//! ``` + #[allow(unused_imports)] use super::{ Mutable, MutableTrait, StorageAsPath, StorageAsPointer, StoragePath, StoragePathHashState, StoragePathTrait, StoragePathUpdateTrait, StoragePointerReadAccess, StoragePointerWriteAccess, }; -/// Trait for reading a contract/component storage member in a specific key place. +/// Provides direct read access to values in a storage [`Map`]. +/// +/// # Examples +/// +/// ``` +/// #[storage] +/// struct Storage { +/// balances: Map, +/// allowances: Map>, +/// } +/// +/// fn read_storage(self: @ContractState) { +/// // Read from single mapping +/// let balance = self.balances.read(address); +/// +/// // Read from nested mapping +/// let allowance = self.allowances.read(owner, spender); +/// } +/// ``` pub trait StorageMapReadAccess { type Key; type Value; fn read(self: TMemberState, key: Self::Key) -> Self::Value; } -/// Trait for writing contract/component storage member in a specific key place. +/// Provides direct write access to values in a storage [`Map`]. +/// +/// Enables directly storing values in the contract's storage at the address of the given key. +/// +/// # Examples +/// +/// ``` +/// #[storage] +/// struct Storage { +/// balances: Map, +/// allowances: Map>, +/// } +/// +/// fn write_storage(ref self: ContractState) { +/// // Write to single mapping +/// self.balances.write(address, 100); +/// +/// // Write to nested mapping +/// self.allowances.write(owner, spender, 50); +/// } +/// ``` pub trait StorageMapWriteAccess { type Key; type Value; fn write(self: TMemberState, key: Self::Key, value: Self::Value); } - -/// Trait for updating the hash state with a value, using an `entry` method. +/// Computes storage paths for accessing [`Map`] entries. +/// +/// The storage path combines the variable's base path with the key's hash to create a unique +/// identifier for the storage slot. This path can then be used for subsequent read or write +/// operations, or advanced further by chaining the `entry` method. +/// +/// # Examples +/// +/// ``` +/// #[storage] +/// struct Storage { +/// balances: Map, +/// } +/// +/// // Get the storage path for the balance of a specific address +/// let balance_path = self.balances.entry(address); +/// ``` pub trait StoragePathEntry { type Key; type Value; fn entry(self: C, key: Self::Key) -> StoragePath; } -/// A struct that represents a map in a contract storage. +/// A persistent key-value store in contract storage. +/// +/// This type cannot be instantiated as it is marked with `#[phantom]`. This is by design: +/// `Map` is a compile-time type that only exists to provide type information for the compiler. +/// It represents a mapping in storage, but the actual storage operations are handled by the +/// [`StorageMapReadAccess`], [`StorageMapWriteAccess`], and [`StoragePathEntry`] traits. #[phantom] pub struct Map {} @@ -41,7 +187,7 @@ impl EntryInfoImpl of EntryInfo> { type Value = V; } -/// Implement StoragePathEntry for any `EntryInfo` type if their key implements `Hash`. +/// Implement `StoragePathEntry` for any `EntryInfo` type if their key implements `Hash`. impl EntryInfoStoragePathEntry< T, +EntryInfo, +core::hash::Hash::Key, StoragePathHashState>, > of StoragePathEntry> { @@ -52,7 +198,7 @@ impl EntryInfoStoragePathEntry< } } -/// Same as `StoragePathEntryMap`, but for Mutable, forwards the Mutable wrapper onto the value +/// Same as `StoragePathEntryMap`, but for `Mutable`, forwards the Mutable wrapper onto the value /// type. impl MutableEntryStoragePathEntry< T, @@ -67,7 +213,8 @@ impl MutableEntryStoragePathEntry< } } -/// Implement StorageMapAccessTrait for any type that implements StoragePathEntry and Store. +/// Implement `StorageMapReadAccess` trait for any type that implements `StoragePathEntry` and +/// `Store`. impl StorableEntryReadAccess< T, +EntryInfo, @@ -96,8 +243,8 @@ impl StorageAsPathReadForward< } } -/// Implement StorageMapAccessTrait for any Mutable type that implements StoragePathEntry and -/// Store. +/// Implement `StorageMapReadAccess` trait for any mutable type that implements `StoragePathEntry` +/// and `Store`. impl MutableStorableEntryReadAccess< T, +MutableTrait, @@ -116,8 +263,8 @@ impl MutableStorableEntryReadAccess< } -/// Implement StorageMapAccessTrait for any Mutable type that implements StoragePathEntry and -/// Store. +/// Implement `StorageMapWriteAccess` trait for any mutable type that implements `StoragePathEntry` +/// and `Store`. impl MutableStorableEntryWriteAccess< T, +MutableTrait, @@ -137,7 +284,6 @@ impl MutableStorableEntryWriteAccess< } } - impl StorageAsPathWriteForward< T, impl PathImpl: StorageAsPath, @@ -153,7 +299,7 @@ impl StorageAsPathWriteForward< } } -/// Implement StoragePathEntry for any type that implements StoragePath and StoragePathEntry. +/// Implement `StoragePathEntry` for any type that implements `StoragePath` and `StoragePathEntry`. impl PathableStorageEntryImpl< T, impl PathImpl: StorageAsPath,