Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Core Lib Documentation: Vec module #7025

Merged
merged 6 commits into from
Jan 9, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
263 changes: 254 additions & 9 deletions corelib/src/starknet/storage/vec.cairo
Original file line number Diff line number Diff line change
@@ -1,45 +1,189 @@
//! 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<u256> = 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<u256>,
//! }
//!
//! 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<u256> {
//! 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<T> {}

impl VecDrop<T> of Drop<Vec<T>> {}
impl VecCopy<T> of Copy<Vec<T>> {}

/// Implement as_ptr for Vec.
/// Implement `as_ptr` for `Vec`.
impl VecAsPointer<T> of StorageAsPointer<StoragePath<Vec<T>>> {
type Value = u64;
fn as_ptr(self: @StoragePath<Vec<T>>) -> StoragePointer0Offset<u64> {
StoragePointer0Offset { __storage_pointer_address__: (*self).finalize() }
}
}

/// Implement as_ptr for Mutable<Vec>.
/// Implement `as_ptr` for `Mutable<Vec>`.
impl MutableVecAsPointer<T> of StorageAsPointer<StoragePath<Mutable<Vec<T>>>> {
type Value = Mutable<u64>;
fn as_ptr(self: @StoragePath<Mutable<Vec<T>>>) -> StoragePointer0Offset<Mutable<u64>> {
StoragePointer0Offset { __storage_pointer_address__: (*self).finalize() }
}
}


/// 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<T> {
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<u256>,
/// }
///
/// fn maybe_number(self: @ContractState, index: u64) -> Option<u256> {
/// self.numbers.get(index).map(|ptr| ptr.read())
/// }
/// ```
fn get(self: T, index: u64) -> Option<StoragePath<Self::ElementType>>;

/// 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<u256>,
/// }
///
/// fn get_number(self: @ContractState, index: u64) -> u256 {
/// self.numbers.at(index).read()
/// }
/// ```
fn at(self: T, index: u64) -> StoragePath<Self::ElementType>;

/// 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<u256>,
/// }
///
/// fn is_empty(self: @ContractState) -> bool {
/// self.numbers.len() == 0
/// }
/// ```
fn len(self: T) -> u64;
}

/// Implement `VecTrait` for `StoragePath<Vec<T>>`.
impl VecImpl<T> of VecTrait<StoragePath<Vec<T>>> {
type ElementType = T;

fn get(self: StoragePath<Vec<T>>, index: u64) -> Option<StoragePath<T>> {
let vec_len = self.len();
if index < vec_len {
Expand All @@ -48,47 +192,141 @@ impl VecImpl<T> of VecTrait<StoragePath<Vec<T>>> {
Option::None
}
}

fn at(self: StoragePath<Vec<T>>, index: u64) -> StoragePath<T> {
assert!(index < self.len(), "Index out of bounds");
self.update(index)
}

fn len(self: StoragePath<Vec<T>>) -> 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<T>,
impl PathImpl: StorageAsPath<T>,
impl VecTraitImpl: VecTrait<StoragePath<PathImpl::Value>>,
> of VecTrait<T> {
type ElementType = VecTraitImpl::ElementType;

fn get(self: T, index: u64) -> Option<StoragePath<VecTraitImpl::ElementType>> {
self.as_path().get(index)
}

fn at(self: T, index: u64) -> StoragePath<VecTraitImpl::ElementType> {
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<T> {
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<u256>,
/// }
///
/// 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<StoragePath<Mutable<Self::ElementType>>>;

/// 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<u256>,
/// }
///
/// fn set_number(ref self: ContractState, index: u64, number: u256) {
/// self.numbers.at(index).write(number);
/// }
/// ```
fn at(self: T, index: u64) -> StoragePath<Mutable<Self::ElementType>>;

/// 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<u256>,
/// }
///
/// 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<u256>,
/// }
///
/// fn push_number(ref self: ContractState, number: u256) {
/// self.numbers.append().write(number);
/// }
/// ```
fn append(self: T) -> StoragePath<Mutable<Self::ElementType>>;
}

/// Implement `MutableVecTrait` for `StoragePath<Mutable<Vec<T>>`.
impl MutableVecImpl<T> of MutableVecTrait<StoragePath<Mutable<Vec<T>>>> {
type ElementType = T;

fn get(self: StoragePath<Mutable<Vec<T>>>, index: u64) -> Option<StoragePath<Mutable<T>>> {
let vec_len = self.len();
if index < vec_len {
Expand All @@ -97,13 +335,16 @@ impl MutableVecImpl<T> of MutableVecTrait<StoragePath<Mutable<Vec<T>>>> {
Option::None
}
}

fn at(self: StoragePath<Mutable<Vec<T>>>, index: u64) -> StoragePath<Mutable<T>> {
assert!(index < self.len(), "Index out of bounds");
self.update(index)
}

fn len(self: StoragePath<Mutable<Vec<T>>>) -> u64 {
self.as_ptr().read()
}

fn append(self: StoragePath<Mutable<Vec<T>>>) -> StoragePath<Mutable<T>> {
let vec_len = self.len();
self.as_ptr().write(vec_len + 1);
Expand All @@ -120,15 +361,19 @@ impl PathableMutableVecImpl<
impl VecTraitImpl: MutableVecTrait<StoragePath<PathImpl::Value>>,
> of MutableVecTrait<T> {
type ElementType = VecTraitImpl::ElementType;

fn get(self: T, index: u64) -> Option<StoragePath<Mutable<VecTraitImpl::ElementType>>> {
self.as_path().get(index)
}

fn at(self: T, index: u64) -> StoragePath<Mutable<VecTraitImpl::ElementType>> {
self.as_path().at(index)
}

fn len(self: T) -> u64 {
self.as_path().len()
}

fn append(self: T) -> StoragePath<Mutable<VecTraitImpl::ElementType>> {
self.as_path().append()
}
Expand Down
Loading