Skip to content

Commit

Permalink
Core Lib Documentation:Map module (#7021)
Browse files Browse the repository at this point in the history
Co-authored-by: enitrat <[email protected]>
  • Loading branch information
TAdev0 and enitrat authored Jan 7, 2025
1 parent 35a40ec commit 536872b
Showing 1 changed file with 160 additions and 14 deletions.
174 changes: 160 additions & 14 deletions corelib/src/starknet/storage/map.cairo
Original file line number Diff line number Diff line change
@@ -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<ContractAddress, u256>,
//! }
//!
//! 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<ContractAddress, Map<ContractAddress, u256>>,
//! }
//!
//! 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<ContractAddress, u256>,
/// allowances: Map<ContractAddress, Map<ContractAddress, u256>>,
/// }
///
/// 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<TMemberState> {
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<ContractAddress, u256>,
/// allowances: Map<ContractAddress, Map<ContractAddress, u256>>,
/// }
///
/// 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<TMemberState> {
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<ContractAddress, u256>,
/// }
///
/// // Get the storage path for the balance of a specific address
/// let balance_path = self.balances.entry(address);
/// ```
pub trait StoragePathEntry<C> {
type Key;
type Value;
fn entry(self: C, key: Self::Key) -> StoragePath<Self::Value>;
}

/// 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<K, V> {}

Expand All @@ -41,7 +187,7 @@ impl EntryInfoImpl<K, V> of EntryInfo<Map<K, V>> {
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<T>, +core::hash::Hash<EntryInfo::<T>::Key, StoragePathHashState>,
> of StoragePathEntry<StoragePath<T>> {
Expand All @@ -52,7 +198,7 @@ impl EntryInfoStoragePathEntry<
}
}

/// Same as `StoragePathEntryMap`, but for Mutable<T>, forwards the Mutable wrapper onto the value
/// Same as `StoragePathEntryMap`, but for `Mutable<T>`, forwards the Mutable wrapper onto the value
/// type.
impl MutableEntryStoragePathEntry<
T,
Expand All @@ -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<T>,
Expand Down Expand Up @@ -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<T>,
Expand All @@ -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<T>,
Expand All @@ -137,7 +284,6 @@ impl MutableStorableEntryWriteAccess<
}
}


impl StorageAsPathWriteForward<
T,
impl PathImpl: StorageAsPath<T>,
Expand All @@ -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<T>,
Expand Down

0 comments on commit 536872b

Please sign in to comment.