diff --git a/corelib/src/starknet/storage/vec.cairo b/corelib/src/starknet/storage/vec.cairo index a5bebfc0d25..5e03fa885c4 100644 --- a/corelib/src/starknet/storage/vec.cairo +++ b/corelib/src/starknet/storage/vec.cairo @@ -1,18 +1,101 @@ +//! Vector-like storage collection for persisting data in contract storage. +//! +//! This module provides a vector-like collection that stores elements in contract storage. +//! Unlike memory arrays, storage vectors persist data onchain, meaning that values can be retrieved +//! even after the end of the current context. +//! +//! # Storage Layout +//! +//! A storage vector consists of two parts: +//! - The vector length stored at the base storage address (`sn_keccak(variable_name)`) +//! - The elements stored at addresses computed as `h(base_address, index)` where: +//! - `h` is the Pedersen hash function +//! - `index` is the element's position in the vector +//! +//! # Interacting with [`Vec`] +//! +//! Storage vectors can be accessed through two sets of traits: +//! +//! 1. Read-only access using `VecTrait`: +//! ``` +//! // Get length +//! let len = self.my_vec.len(); +//! +//! // Read element (panics if out of bounds) +//! let value = self.my_vec.at(0).read(); +//! +//! // Read element (returns Option) +//! let maybe_value: Option = self.my_vec.get(0).map(|ptr| ptr.read()); +//! ``` +//! +//! 2. Mutable access using `MutableVecTrait`: +//! ``` +//! // Append new element +//! self.my_vec.append().write(value); +//! +//! // Modify existing element +//! self.my_vec.at(0).write(new_value); +//! ``` +//! +//! # Examples +//! +//! Basic usage: +//! +//! ``` +//! use core::starknet::storage::{Vec, VecTrait, MutableVecTrait, StoragePointerReadAccess, +//! StoragePointerWriteAccess}; +//! +//! #[storage] +//! struct Storage { +//! numbers: Vec, +//! } +//! +//! fn store_number(ref self: ContractState, number: u256) { +//! // Append new number +//! self.numbers.append().write(number); +//! +//! // Read first number +//! let first = self.numbers[0].read(); +//! +//! // Get current length +//! let size = self.numbers.len(); +//! } +//! ``` +//! +//! Loading the numbers into a memory array: +//! +//! ``` +//! use core::starknet::storage::{Vec, VecTrait, StoragePointerReadAccess}; +//! +//! fn to_array(self: @ContractState) -> Array { +//! let mut arr = array![]; +//! +//! let len = self.numbers.len(); +//! for i in 0..len { +//! arr.append(self.numbers[i].read()); +//! } +//! arr +//! } +//! ``` + use core::Option; use super::{ Mutable, StorageAsPath, StorageAsPointer, StoragePath, StoragePathTrait, StoragePathUpdateTrait, StoragePointer0Offset, StoragePointerReadAccess, StoragePointerWriteAccess, }; -/// A type to represent a vec in storage. The length of the storage is stored in the storage -/// base, while the elements are stored in hash(storage_base, index). +/// Represents a dynamic array in contract storage. +/// +/// This type is zero-sized and cannot be instantiated. +/// Vectors can only be used in storage contexts and manipulated using the associated `VecTrait` +/// and `MutableVecTrait` traits. #[phantom] pub struct Vec {} impl VecDrop of Drop> {} impl VecCopy of Copy> {} -/// Implement as_ptr for Vec. +/// Implement `as_ptr` for `Vec`. impl VecAsPointer of StorageAsPointer>> { type Value = u64; fn as_ptr(self: @StoragePath>) -> StoragePointer0Offset { @@ -20,7 +103,7 @@ impl VecAsPointer of StorageAsPointer>> { } } -/// Implement as_ptr for Mutable. +/// Implement `as_ptr` for `Mutable`. impl MutableVecAsPointer of StorageAsPointer>>> { type Value = Mutable; fn as_ptr(self: @StoragePath>>) -> StoragePointer0Offset> { @@ -28,18 +111,79 @@ impl MutableVecAsPointer of StorageAsPointer>>> { } } - -/// Trait for the interface of a storage vec. +/// Provides read-only access to elements in a storage [`Vec`]. +/// +/// This trait enables retrieving elements and checking the vector's length without +/// modifying the underlying storage. pub trait VecTrait { type ElementType; + + /// Returns a storage path to the element at the specified index, or `None` if out of bounds. + /// + /// # Examples + /// + /// ``` + /// use core::starknet::storage::{Vec, VecTrait, StoragePointerReadAccess}; + /// + /// #[storage] + /// struct Storage { + /// numbers: Vec, + /// } + /// + /// fn maybe_number(self: @ContractState, index: u64) -> Option { + /// self.numbers.get(index).map(|ptr| ptr.read()) + /// } + /// ``` fn get(self: T, index: u64) -> Option>; + + /// Returns a storage path to access the element at the specified index. + /// + /// # Panics + /// + /// Panics if the index is out of bounds. + /// + /// # Examples + /// + /// ``` + /// use core::starknet::storage::{Vec, VecTrait, StoragePointerReadAccess}; + /// + /// #[storage] + /// struct Storage { + /// numbers: Vec, + /// } + /// + /// fn get_number(self: @ContractState, index: u64) -> u256 { + /// self.numbers.at(index).read() + /// } + /// ``` fn at(self: T, index: u64) -> StoragePath; + + /// Returns the number of elements in the vector. + /// + /// The length is stored at the vector's base storage address and is automatically + /// updated when elements are appended. + /// + /// # Examples + /// + /// ``` + /// use core::starknet::storage::{Vec, VecTrait}; + /// + /// #[storage] + /// struct Storage { + /// numbers: Vec, + /// } + /// + /// fn is_empty(self: @ContractState) -> bool { + /// self.numbers.len() == 0 + /// } + /// ``` fn len(self: T) -> u64; } /// Implement `VecTrait` for `StoragePath>`. impl VecImpl of VecTrait>> { type ElementType = T; + fn get(self: StoragePath>, index: u64) -> Option> { let vec_len = self.len(); if index < vec_len { @@ -48,17 +192,19 @@ impl VecImpl of VecTrait>> { Option::None } } + fn at(self: StoragePath>, index: u64) -> StoragePath { assert!(index < self.len(), "Index out of bounds"); self.update(index) } + fn len(self: StoragePath>) -> u64 { self.as_ptr().read() } } -/// Implement `VecTrait` for any type that implements StorageAsPath into a storage path -/// that implements VecTrait. +/// Implement `VecTrait` for any type that implements `StorageAsPath` into a storage path +/// that implements `VecTrait`. impl PathableVecImpl< T, +Drop, @@ -66,29 +212,121 @@ impl PathableVecImpl< impl VecTraitImpl: VecTrait>, > of VecTrait { type ElementType = VecTraitImpl::ElementType; + fn get(self: T, index: u64) -> Option> { self.as_path().get(index) } + fn at(self: T, index: u64) -> StoragePath { self.as_path().at(index) } + fn len(self: T) -> u64 { self.as_path().len() } } -/// Trait for the interface of a mutable storage vec. +/// Provides mutable access to elements in a storage [`Vec`]. +/// +/// This trait extends the read functionality with methods to append new elements +/// and modify existing ones. pub trait MutableVecTrait { type ElementType; + + /// Returns a mutable storage path to the element at the specified index, or `None` if out of + /// bounds. + /// + /// # Examples + /// + /// ``` + /// use core::starknet::storage::{Vec, MutableVecTrait, StoragePointerWriteAccess}; + /// + /// #[storage] + /// struct Storage { + /// numbers: Vec, + /// } + /// + /// fn set_number(ref self: ContractState, index: u64, number: u256) -> bool { + /// if let Option::Some(ptr) = self.numbers.get(index) { + /// ptr.write(number); + /// true + /// } else { + /// false + /// } + /// } + /// ``` fn get(self: T, index: u64) -> Option>>; + + /// Returns a mutable storage path to the element at the specified index. + /// + /// # Panics + /// + /// Panics if the index is out of bounds. + /// + /// # Examples + /// + /// ``` + /// use core::starknet::storage::{Vec, MutableVecTrait, StoragePointerWriteAccess}; + /// + /// #[storage] + /// struct Storage { + /// numbers: Vec, + /// } + /// + /// fn set_number(ref self: ContractState, index: u64, number: u256) { + /// self.numbers.at(index).write(number); + /// } + /// ``` fn at(self: T, index: u64) -> StoragePath>; + + /// Returns the number of elements in the vector. + /// + /// The length is stored at the vector's base storage address and is automatically + /// updated when elements are appended. + /// + /// # Examples + /// + /// ``` + /// use core::starknet::storage::{Vec, MutableVecTrait}; + /// + /// #[storage] + /// struct Storage { + /// numbers: Vec, + /// } + /// + /// fn is_empty(self: @ContractState) -> bool { + /// self.numbers.len() == 0 + /// } + /// ``` fn len(self: T) -> u64; + + /// Returns a mutable storage path to write a new element at the end of the vector. + /// + /// This operation: + /// 1. Increments the vector's length + /// 2. Returns a storage path to write the new element + /// + /// # Examples + /// + /// ``` + /// use core::starknet::storage::{Vec, MutableVecTrait, StoragePointerWriteAccess}; + /// + /// #[storage] + /// struct Storage { + /// numbers: Vec, + /// } + /// + /// fn push_number(ref self: ContractState, number: u256) { + /// self.numbers.append().write(number); + /// } + /// ``` fn append(self: T) -> StoragePath>; } /// Implement `MutableVecTrait` for `StoragePath>`. impl MutableVecImpl of MutableVecTrait>>> { type ElementType = T; + fn get(self: StoragePath>>, index: u64) -> Option>> { let vec_len = self.len(); if index < vec_len { @@ -97,13 +335,16 @@ impl MutableVecImpl of MutableVecTrait>>> { Option::None } } + fn at(self: StoragePath>>, index: u64) -> StoragePath> { assert!(index < self.len(), "Index out of bounds"); self.update(index) } + fn len(self: StoragePath>>) -> u64 { self.as_ptr().read() } + fn append(self: StoragePath>>) -> StoragePath> { let vec_len = self.len(); self.as_ptr().write(vec_len + 1); @@ -120,15 +361,19 @@ impl PathableMutableVecImpl< impl VecTraitImpl: MutableVecTrait>, > of MutableVecTrait { type ElementType = VecTraitImpl::ElementType; + fn get(self: T, index: u64) -> Option>> { self.as_path().get(index) } + fn at(self: T, index: u64) -> StoragePath> { self.as_path().at(index) } + fn len(self: T) -> u64 { self.as_path().len() } + fn append(self: T) -> StoragePath> { self.as_path().append() }