//! Storage access primitives for Starknet contract storage.
//!
//! This module provides abstractions over the system calls for reading from and writing to Starknet
//! contract storage. It includes traits and implementations for storing various data types
//! efficiently.
//!
//! # Storage Architecture
//!
//! * Storage addresses range from `[0, 2^251)`
//! * Base addresses can be combined with offsets, allowing storage of up to 255 values sequentially
//! * Multiple storage domains can be supported, each with its own set of storage space.
//! Currently, only the domain `0` is supported. Values stored in domain `0` are committed to
//! Ethereum as part of the state diffs.
//!
//! # Core Components
//!
//! * [`StorageAddress`]: Represents a specific storage location
//! * [`StorageBaseAddress`]: Base address that can be combined with offsets
//! * [`Store<T>`]: Core trait for types that can be stored in contract storage
//! * [`StorePacking<T,P>`]: Trait for efficient packing/unpacking of values
//!
//! Generally, you don't need to implement the [`Store`] trait yourself. Most types of the core
//! library, at the exception of collection types, implement the [`Store`] trait - and thus, you can
//! derive the [`Store`] trait for your own types, as long as they don't contain any collections.
use core::RangeCheck;
use core::array::ArrayTrait;
use core::byte_array::ByteArrayTrait;
use core::option::OptionTrait;
use core::serde::Serde;
use core::traits::{Into, TryInto};
#[allow(unused_imports)]
use starknet::SyscallResult;
#[allow(unused_imports)]
use starknet::class_hash::{ClassHash, ClassHashIntoFelt252, Felt252TryIntoClassHash};
#[allow(unused_imports)]
use starknet::contract_address::{
ContractAddress, ContractAddressIntoFelt252, Felt252TryIntoContractAddress,
};
#[allow(unused_imports)]
use starknet::syscalls::{storage_read_syscall, storage_write_syscall};
/// Represents the address of a storage value in a Starknet contract.
/// The value range of this type is `[0, 2**251)`.
pub extern type StorageAddress;
impl StorageAddressCopy of Copy<StorageAddress>;
impl StorageAddressDrop of Drop<StorageAddress>;
/// Represents a base storage address that can be combined with offsets.
/// The value range of this type is `[0, 2**251 - 256)`.
pub extern type StorageBaseAddress;
impl StorageBaseAddressCopy of Copy<StorageBaseAddress>;
impl StorageBaseAddressDrop of Drop<StorageBaseAddress>;
/// Returns a `StorageBaseAddress` given a constant `felt252` value.
///
/// The value is validated to be in the range `[0, 2**251 - 256)` at compile time.
///
/// # Examples
///
/// ```
/// use starknet::storage_access::storage_base_address_const;
///
/// let base_address = storage_base_address_const::<0>();
/// ```
pub extern fn storage_base_address_const<const address: felt252>() -> StorageBaseAddress nopanic;
/// Returns a `StorageBaseAddress` given a `felt252` value.
///
/// Wraps around the value if it is not in the range `[0, 2**251 - 256)`.
pub extern fn storage_base_address_from_felt252(
addr: felt252,
) -> StorageBaseAddress implicits(RangeCheck) nopanic;
pub(crate) extern fn storage_address_to_felt252(address: StorageAddress) -> felt252 nopanic;
/// Sums the base address and the offset to return a storage address.
pub extern fn storage_address_from_base_and_offset(
base: StorageBaseAddress, offset: u8,
) -> StorageAddress nopanic;
/// Converts a `StorageBaseAddress` into a `StorageAddress`.
///
/// This should be used through the high-level `Into` trait.
pub extern fn storage_address_from_base(base: StorageBaseAddress) -> StorageAddress nopanic;
pub(crate) extern fn storage_address_try_from_felt252(
address: felt252,
) -> Option<StorageAddress> implicits(RangeCheck) nopanic;
impl Felt252TryIntoStorageAddress of TryInto<felt252, StorageAddress> {
fn try_into(self: felt252) -> Option<StorageAddress> {
storage_address_try_from_felt252(self)
}
}
impl StorageAddressIntoFelt252 of Into<StorageAddress, felt252> {
fn into(self: StorageAddress) -> felt252 {
storage_address_to_felt252(self)
}
}
impl StorageAddressSerde of Serde<StorageAddress> {
fn serialize(self: @StorageAddress, ref output: Array<felt252>) {
storage_address_to_felt252(*self).serialize(ref output);
}
fn deserialize(ref serialized: Span<felt252>) -> Option<StorageAddress> {
Some(storage_address_try_from_felt252(Serde::<felt252>::deserialize(ref serialized)?)?)
}
}
impl StorageBaseAddressIntoFelt252 of Into<StorageBaseAddress, felt252> {
fn into(self: StorageBaseAddress) -> felt252 {
storage_address_to_felt252(storage_address_from_base(self))
}
}
impl DebugStorageAddress = core::fmt::into_felt252_based::DebugImpl<StorageAddress>;
impl DebugStorageBaseAddress of core::fmt::Debug<StorageBaseAddress> {
fn fmt(self: @StorageBaseAddress, ref f: core::fmt::Formatter) -> Result<(), core::fmt::Error> {
DebugStorageAddress::fmt(@storage_address_from_base(*self), ref f)
}
}
impl LowerHexStorageAddress = core::fmt::into_felt252_based::LowerHexImpl<StorageAddress>;
impl LowerHexStorageBaseAddress of core::fmt::LowerHex<StorageBaseAddress> {
fn fmt(self: @StorageBaseAddress, ref f: core::fmt::Formatter) -> Result<(), core::fmt::Error> {
LowerHexStorageAddress::fmt(@storage_address_from_base(*self), ref f)
}
}
/// Trait for types that can be stored in Starknet contract storage.
///
/// The `Store` trait enables types to be stored in and retrieved from Starknet's contract storage.
/// Cairo implements `Store` for most primitive types. However, collection types (arrays, dicts,
/// etc.) do not implement `Store` directly. Instead, use specialized storage types, such as [`Vec`]
/// or [`Map`].
///
/// [`Map`]: starknet::storage::Map
/// [`Vec`]: starknet::storage::Vec
///
/// # Derivation
///
/// To make a type storable in contract storage, simply derive the `Store` trait:
///
/// ```
/// #[derive(Drop, starknet::Store)]
/// struct Sizes {
/// tiny: u8, // 8 bits
/// small: u32, // 32 bits
/// medium: u64, // 64 bits
/// }
/// ```
///
/// This allows the `Size` struct to be stored in a contract's storage.
///
/// There's no real reason to implement this trait yourself, as it can be trivially derived.
/// For efficiency purposes, consider manually implementing [`StorePacking`] to optimize storage
/// usage.
pub trait Store<T> {
/// Reads a value from storage at the given domain and base address.
///
/// # Arguments
///
/// * `address_domain` - The storage domain (currently only 0 is supported)
/// * `base` - The base storage address to read from
fn read(address_domain: u32, base: StorageBaseAddress) -> SyscallResult<T>;
/// Writes a value to storage at the given domain and base address.
///
/// # Arguments
///
/// * `address_domain` - The storage domain (currently only 0 is supported)
/// * `base` - The base storage address to write to
/// * `value` - The value to store
fn write(address_domain: u32, base: StorageBaseAddress, value: T) -> SyscallResult<()>;
/// Reads a value from storage at a base address plus an offset.
///
/// # Arguments
///
/// * `address_domain` - The storage domain (currently only 0 is supported)
/// * `base` - The base storage address
/// * `offset` - The offset from the base address where the value should be read
fn read_at_offset(
address_domain: u32, base: StorageBaseAddress, offset: u8,
) -> SyscallResult<T>;
/// Writes a value to storage at a base address plus an offset.
///
/// # Arguments
///
/// * `address_domain` - The storage domain (currently only 0 is supported)
/// * `base` - The base storage address
/// * `offset` - The offset from the base address where the value should be written
/// * `value` - The value to store
fn write_at_offset(
address_domain: u32, base: StorageBaseAddress, offset: u8, value: T,
) -> SyscallResult<()>;
/// Returns the size in storage for this type.
///
/// This is bounded to 255, as the offset is a u8. As such, a single type can only take up to
/// 255 slots in storage.
fn size() -> u8;
/// Clears the storage area by writing zeroes to it.
///
/// # Arguments
///
/// * `address_domain` - The storage domain
/// * `base` - The base storage address to start clearing
/// * `offset` - The offset from the base address where clearing should start
///
/// The operation writes zeroes to storage starting from the specified base address and offset,
/// and continues for the size of the type as determined by the `size()` function.
///
/// Note: This is a flat operation. It only zeros the storage slots that belong to this value as
/// determined by `size()`. It does not traverse or delete storage that is pointed to by
/// pointers within the value (e.g., ByteArray data stored via pointers). To fully clear such
/// nested data, users should handle clearing on the nested storage components as well.
#[inline]
fn scrub(
address_domain: u32, base: StorageBaseAddress, offset: u8,
) -> SyscallResult<
(),
> {
let mut result = Result::Ok(());
let mut offset = offset;
for _ in 0..Self::size() {
if let Result::Err(err) =
storage_write_syscall(
address_domain, storage_address_from_base_and_offset(base, offset), 0,
) {
result = Result::Err(err);
break;
}
offset += 1;
}
result
}
}
/// Trait for efficient packing of values into optimized storage representations.
///
/// This trait enables bit-packing of complex types into simpler storage types to reduce gas costs
/// by minimizing the number of storage slots used. When a type implements `StorePacking`, the
/// compiler automatically uses [`StoreUsingPacking`] to handle storage operations. As such, a type
/// cannot implement both `Store` and `StorePacking`.
///
/// # Storage Optimization
///
/// Each storage slot in Starknet is a `felt252`, and storage operations are expensive. By packing
/// multiple values into fewer slots, you can significantly reduce gas costs. For example:
/// - Multiple small integers can be packed into a single `felt252`
/// - Structs with several fields can be compressed into a single storage slot
///
/// # Implementation Requirements
///
/// To implement `StorePacking`, ensure that the `PackedT` type implements [`Store`]. The packed
/// representation must preserve all necessary information to allow unpacking back to the original
/// type. Additionally, the `pack` and `unpack` operations must be reversible, meaning that packing
/// followed by unpacking should return the original value.
///
/// # Example
///
/// Packing multiple integer fields into a single storage slot:
///
/// ```
/// use starknet::storage_access::StorePacking;
///
/// #[derive(Drop)]
/// struct Sizes {
/// tiny: u8, // 8 bits
/// small: u32, // 32 bits
/// medium: u64, // 64 bits
/// }
///
/// const TWO_POW_8: u128 = 0x100;
/// const TWO_POW_40: u128 = 0x10000000000;
///
/// impl SizesStorePacking of StorePacking<Sizes, u128> {
/// fn pack(value: Sizes) -> u128 {
/// value.tiny.into() +
/// (value.small.into() * TWO_POW_8) +
/// (value.medium.into() * TWO_POW_40)
/// }
///
/// fn unpack(value: u128) -> Sizes {
/// let tiny = value & 0xff;
/// let small = (value / TWO_POW_8) & 0xffffffff;
/// let medium = (value / TWO_POW_40);
///
/// Sizes {
/// tiny: tiny.try_into().unwrap(),
/// small: small.try_into().unwrap(),
/// medium: medium.try_into().unwrap(),
/// }
/// }
/// }
/// ```
///
/// By implementing `StorePacking` for `Sizes`, the `Sizes` will be stored in its packed form,
/// using a single storage slot instead of 3. When retrieved, it will automatically be unpacked back
/// into the original type.
pub trait StorePacking<T, PackedT> {
/// Packs a value into its optimized storage representation.
fn pack(value: T) -> PackedT;
/// Unpacks a storage representation back into the original type.
fn unpack(value: PackedT) -> T;
}
impl StoreUsingPacking<
T, PackedT, impl TPacking: StorePacking<T, PackedT>, impl PackedTStore: Store<PackedT>,
> of Store<T> {
#[inline]
fn read(address_domain: u32, base: StorageBaseAddress) -> SyscallResult<T> {
Ok(TPacking::unpack(PackedTStore::read(address_domain, base)?))
}
#[inline]
fn write(address_domain: u32, base: StorageBaseAddress, value: T) -> SyscallResult<()> {
PackedTStore::write(address_domain, base, TPacking::pack(value))
}
#[inline]
fn read_at_offset(
address_domain: u32, base: StorageBaseAddress, offset: u8,
) -> SyscallResult<T> {
Ok(TPacking::unpack(PackedTStore::read_at_offset(address_domain, base, offset)?))
}
#[inline]
fn write_at_offset(
address_domain: u32, base: StorageBaseAddress, offset: u8, value: T,
) -> SyscallResult<()> {
PackedTStore::write_at_offset(address_domain, base, offset, TPacking::pack(value))
}
#[inline]
fn size() -> u8 {
PackedTStore::size()
}
}
impl StoreFelt252 of Store<felt252> {
#[inline]
fn read(address_domain: u32, base: StorageBaseAddress) -> SyscallResult<felt252> {
storage_read_syscall(address_domain, storage_address_from_base(base))
}
#[inline]
fn write(address_domain: u32, base: StorageBaseAddress, value: felt252) -> SyscallResult<()> {
storage_write_syscall(address_domain, storage_address_from_base(base), value)
}
#[inline]
fn read_at_offset(
address_domain: u32, base: StorageBaseAddress, offset: u8,
) -> SyscallResult<felt252> {
storage_read_syscall(address_domain, storage_address_from_base_and_offset(base, offset))
}
#[inline]
fn write_at_offset(
address_domain: u32, base: StorageBaseAddress, offset: u8, value: felt252,
) -> SyscallResult<()> {
storage_write_syscall(
address_domain, storage_address_from_base_and_offset(base, offset), value,
)
}
#[inline]
fn size() -> u8 {
1_u8
}
}
impl StorePackingBool of StorePacking<bool, felt252> {
fn pack(value: bool) -> felt252 {
value.into()
}
#[inline]
fn unpack(value: felt252) -> bool {
value != 0
}
}
impl StorePackingU8 of StorePacking<u8, felt252> {
fn pack(value: u8) -> felt252 {
value.into()
}
#[inline]
fn unpack(value: felt252) -> u8 {
value.try_into().expect('StoreU8 - non u8')
}
}
impl StorePackingI8 of StorePacking<i8, felt252> {
fn pack(value: i8) -> felt252 {
value.into()
}
#[inline]
fn unpack(value: felt252) -> i8 {
value.try_into().expect('StoreI8 - non i8')
}
}
impl StorePackingU16 of StorePacking<u16, felt252> {
fn pack(value: u16) -> felt252 {
value.into()
}
#[inline]
fn unpack(value: felt252) -> u16 {
value.try_into().expect('StoreU16 - non u16')
}
}
impl StorePackingI16 of StorePacking<i16, felt252> {
fn pack(value: i16) -> felt252 {
value.into()
}
#[inline]
fn unpack(value: felt252) -> i16 {
value.try_into().expect('StoreI16 - non i16')
}
}
impl StorePackingU32 of StorePacking<u32, felt252> {
fn pack(value: u32) -> felt252 {
value.into()
}
#[inline]
fn unpack(value: felt252) -> u32 {
value.try_into().expect('StoreU32 - non u32')
}
}
impl StorePackingI32 of StorePacking<i32, felt252> {
fn pack(value: i32) -> felt252 {
value.into()
}
#[inline]
fn unpack(value: felt252) -> i32 {
value.try_into().expect('StoreI32 - non i32')
}
}
impl StorePackingU64 of StorePacking<u64, felt252> {
fn pack(value: u64) -> felt252 {
value.into()
}
#[inline]
fn unpack(value: felt252) -> u64 {
value.try_into().expect('StoreU64 - non u64')
}
}
impl StorePackingI64 of StorePacking<i64, felt252> {
fn pack(value: i64) -> felt252 {
value.into()
}
#[inline]
fn unpack(value: felt252) -> i64 {
value.try_into().expect('StoreI64 - non i64')
}
}
impl StorePackingU128 of StorePacking<u128, felt252> {
fn pack(value: u128) -> felt252 {
value.into()
}
#[inline]
fn unpack(value: felt252) -> u128 {
value.try_into().expect('StoreU128 - non u128')
}
}
impl StorePackingI128 of StorePacking<i128, felt252> {
fn pack(value: i128) -> felt252 {
value.into()
}
#[inline]
fn unpack(value: felt252) -> i128 {
value.try_into().expect('StoreI128 - non i128')
}
}
impl StorePackingU256 of StorePacking<u256, (u128, u128)> {
fn pack(value: u256) -> (u128, u128) {
(value.low, value.high)
}
#[inline]
fn unpack(value: (u128, u128)) -> u256 {
let (low, high) = value;
u256 { low, high }
}
}
impl StorePackingBytes31 of StorePacking<bytes31, felt252> {
fn pack(value: bytes31) -> felt252 {
value.into()
}
#[inline]
fn unpack(value: felt252) -> bytes31 {
value.try_into().expect('StoreBytes31 - non bytes31')
}
}
impl StorePackingNonZero<T, +TryInto<T, NonZero<T>>> of StorePacking<NonZero<T>, T> {
fn pack(value: NonZero<T>) -> T {
value.into()
}
#[inline]
fn unpack(value: T) -> NonZero<T> {
value.try_into().expect('StoreNonZero - zero value')
}
}
impl StorePackingStorageAddress of StorePacking<StorageAddress, felt252> {
fn pack(value: StorageAddress) -> felt252 {
value.into()
}
#[inline]
fn unpack(value: felt252) -> StorageAddress {
value.try_into().expect('Non StorageAddress')
}
}
impl StorePackingContractAddress of StorePacking<ContractAddress, felt252> {
fn pack(value: ContractAddress) -> felt252 {
value.into()
}
#[inline]
fn unpack(value: felt252) -> ContractAddress {
value.try_into().expect('Non ContractAddress')
}
}
impl StorePackingClassHash of StorePacking<ClassHash, felt252> {
fn pack(value: ClassHash) -> felt252 {
value.into()
}
#[inline]
fn unpack(value: felt252) -> ClassHash {
value.try_into().expect('Non ClassHash')
}
}
/// Store implementation for a tuple of size 0.
impl TupleSize0Store of Store<()> {
#[inline]
fn read(address_domain: u32, base: StorageBaseAddress) -> SyscallResult<()> {
Ok(())
}
#[inline]
fn write(address_domain: u32, base: StorageBaseAddress, value: ()) -> SyscallResult<()> {
Ok(())
}
#[inline]
fn read_at_offset(
address_domain: u32, base: StorageBaseAddress, offset: u8,
) -> SyscallResult<()> {
Ok(())
}
#[inline]
fn write_at_offset(
address_domain: u32, base: StorageBaseAddress, offset: u8, value: (),
) -> SyscallResult<()> {
Ok(())
}
#[inline]
fn size() -> u8 {
0
}
}
/// Store packing for tuples of size 1.
impl StorePackingTuple1<T> of StorePacking<(T,), T> {
fn pack(value: (T,)) -> T {
let (value,) = value;
value
}
fn unpack(value: T) -> (T,) {
(value,)
}
}
/// Store packing for small fixed sized arrays.
impl StorePackingFixedSizedArray0<T> of StorePacking<[T; 0], ()> {
fn pack(value: [T; 0]) -> () {
let [] = value;
()
}
#[inline]
fn unpack(value: ()) -> [T; 0] {
[]
}
}
/// Store packing for fixed sized arrays of size 1.
impl StorePackingFixedSizedArray1<T> of StorePacking<[T; 1], T> {
fn pack(value: [T; 1]) -> T {
let [value] = value;
value
}
fn unpack(value: T) -> [T; 1] {
[value]
}
}
/// Store implementation for a tuple of size 2 and more.
impl TupleNextStore<
T,
impl TH: core::metaprogramming::TupleSplit<T>,
impl HeadStore: Store<TH::Head>,
impl RestStore: Store<TH::Rest>,
+Drop<TH::Head>,
+Drop<TH::Rest>,
// The following bound is to allow the recursion to be more efficient at size 1.
+core::metaprogramming::TupleSplit<TH::Rest>,
> of Store<T> {
#[inline]
fn read(address_domain: u32, base: StorageBaseAddress) -> SyscallResult<T> {
let head = HeadStore::read(address_domain, base)?;
let rest = RestStore::read_at_offset(address_domain, base, HeadStore::size())?;
Ok(TH::reconstruct(head, rest))
}
#[inline]
fn write(address_domain: u32, base: StorageBaseAddress, value: T) -> SyscallResult<()> {
let (head, rest) = TH::split_head(value);
HeadStore::write(address_domain, base, head)?;
RestStore::write_at_offset(address_domain, base, HeadStore::size(), rest)
}
#[inline]
fn read_at_offset(
address_domain: u32, base: StorageBaseAddress, offset: u8,
) -> SyscallResult<T> {
let head = HeadStore::read_at_offset(address_domain, base, offset)?;
let rest = RestStore::read_at_offset(address_domain, base, offset + HeadStore::size())?;
Ok(TH::reconstruct(head, rest))
}
#[inline]
fn write_at_offset(
address_domain: u32, base: StorageBaseAddress, offset: u8, value: T,
) -> SyscallResult<()> {
let (head, rest) = TH::split_head(value);
HeadStore::write_at_offset(address_domain, base, offset, head)?;
RestStore::write_at_offset(address_domain, base, offset + HeadStore::size(), rest)
}
#[inline]
fn size() -> u8 {
HeadStore::size() + RestStore::size()
}
}
const RESULT_OK_INDICATOR: felt252 = 0;
const RESULT_ERR_INDICATOR: felt252 = 1;
impl ResultStore<T, E, +Store<T>, +Store<E>, +Drop<T>, +Drop<E>> of Store<Result<T, E>> {
#[inline]
fn read(address_domain: u32, base: StorageBaseAddress) -> SyscallResult<Result<T, E>> {
let idx = Store::<felt252>::read(address_domain, base)?;
if idx == RESULT_OK_INDICATOR {
starknet::SyscallResult::Ok(Ok(Store::read_at_offset(address_domain, base, 1_u8)?))
} else if idx == RESULT_ERR_INDICATOR {
starknet::SyscallResult::Ok(Err(Store::read_at_offset(address_domain, base, 1_u8)?))
} else {
starknet::SyscallResult::Err(array!['Incorrect index:'])
}
}
#[inline]
fn write(
address_domain: u32, base: StorageBaseAddress, value: Result<T, E>,
) -> SyscallResult<()> {
match value {
Ok(x) => {
Store::write(address_domain, base, RESULT_OK_INDICATOR)?;
Store::write_at_offset(address_domain, base, 1_u8, x)?;
},
Err(x) => {
Store::write(address_domain, base, RESULT_ERR_INDICATOR)?;
Store::write_at_offset(address_domain, base, 1_u8, x)?;
},
}
starknet::SyscallResult::Ok(())
}
#[inline]
fn read_at_offset(
address_domain: u32, base: StorageBaseAddress, offset: u8,
) -> SyscallResult<Result<T, E>> {
let idx = Store::<felt252>::read_at_offset(address_domain, base, offset)?;
if idx == RESULT_OK_INDICATOR {
starknet::SyscallResult::Ok(
Ok(Store::read_at_offset(address_domain, base, offset + 1_u8)?),
)
} else if idx == RESULT_ERR_INDICATOR {
starknet::SyscallResult::Ok(
Err(Store::read_at_offset(address_domain, base, offset + 1_u8)?),
)
} else {
starknet::SyscallResult::Err(array!['Incorrect index:'])
}
}
#[inline]
fn write_at_offset(
address_domain: u32, base: StorageBaseAddress, offset: u8, value: Result<T, E>,
) -> SyscallResult<()> {
match value {
Ok(x) => {
Store::write_at_offset(address_domain, base, offset, RESULT_OK_INDICATOR)?;
Store::write_at_offset(address_domain, base, offset + 1_u8, x)?;
},
Err(x) => {
Store::write_at_offset(address_domain, base, offset, RESULT_ERR_INDICATOR)?;
Store::write_at_offset(address_domain, base, offset + 1_u8, x)?;
},
}
starknet::SyscallResult::Ok(())
}
#[inline]
fn size() -> u8 {
1 + core::cmp::max(Store::<T>::size(), Store::<E>::size())
}
}
const OPTION_NONE_INDICATOR: felt252 = 0;
const OPTION_SOME_INDICATOR: felt252 = 1;
impl OptionStore<T, +Store<T>, +Drop<T>> of Store<Option<T>> {
#[inline]
fn read(address_domain: u32, base: StorageBaseAddress) -> SyscallResult<Option<T>> {
let idx = Store::<felt252>::read(address_domain, base)?;
if idx == OPTION_SOME_INDICATOR {
starknet::SyscallResult::Ok(Some(Store::read_at_offset(address_domain, base, 1_u8)?))
} else if idx == OPTION_NONE_INDICATOR {
starknet::SyscallResult::Ok(None)
} else {
starknet::SyscallResult::Err(array!['Incorrect index:'])
}
}
#[inline]
fn write(address_domain: u32, base: StorageBaseAddress, value: Option<T>) -> SyscallResult<()> {
match value {
Some(x) => {
Store::write(address_domain, base, OPTION_SOME_INDICATOR)?;
Store::write_at_offset(address_domain, base, 1_u8, x)?;
},
None(_) => { Store::write(address_domain, base, OPTION_NONE_INDICATOR)?; },
}
starknet::SyscallResult::Ok(())
}
#[inline]
fn read_at_offset(
address_domain: u32, base: StorageBaseAddress, offset: u8,
) -> SyscallResult<Option<T>> {
let idx = Store::<felt252>::read_at_offset(address_domain, base, offset)?;
if idx == OPTION_SOME_INDICATOR {
starknet::SyscallResult::Ok(
Some(Store::read_at_offset(address_domain, base, offset + 1_u8)?),
)
} else if idx == OPTION_NONE_INDICATOR {
starknet::SyscallResult::Ok(None)
} else {
starknet::SyscallResult::Err(array!['Incorrect index:'])
}
}
#[inline]
fn write_at_offset(
address_domain: u32, base: StorageBaseAddress, offset: u8, value: Option<T>,
) -> SyscallResult<()> {
match value {
Some(x) => {
Store::write_at_offset(address_domain, base, offset, OPTION_SOME_INDICATOR)?;
Store::write_at_offset(address_domain, base, offset + 1_u8, x)?;
},
None(_x) => {
Store::write_at_offset(address_domain, base, offset, OPTION_NONE_INDICATOR)?;
},
}
starknet::SyscallResult::Ok(())
}
#[inline]
fn size() -> u8 {
1 + Store::<T>::size()
}
}
/// Store for a `ByteArray`.
///
/// The layout of a `ByteArray` in storage is as follows:
/// * Only the length in bytes is stored in the original address where the byte array is logically
/// stored.
/// * The actual data is stored in chunks of 256 `bytes31`s in another place in storage
/// determined by the hash of:
/// - The address storing the length of the array.
/// - The chunk index.
/// - The short string `ByteArray`.
impl ByteArrayStore of Store<ByteArray> {
#[inline]
fn read(address_domain: u32, base: StorageBaseAddress) -> SyscallResult<ByteArray> {
inner_read_byte_array(address_domain, storage_address_from_base(base))
}
#[inline]
fn write(address_domain: u32, base: StorageBaseAddress, value: ByteArray) -> SyscallResult<()> {
inner_write_byte_array(address_domain, storage_address_from_base(base), value)
}
#[inline]
fn read_at_offset(
address_domain: u32, base: StorageBaseAddress, offset: u8,
) -> SyscallResult<ByteArray> {
inner_read_byte_array(address_domain, storage_address_from_base_and_offset(base, offset))
}
#[inline]
fn write_at_offset(
address_domain: u32, base: StorageBaseAddress, offset: u8, value: ByteArray,
) -> SyscallResult<()> {
inner_write_byte_array(
address_domain, storage_address_from_base_and_offset(base, offset), value,
)
}
#[inline]
fn size() -> u8 {
1
}
}
/// Returns a pointer to the `chunk`'th chunk of the byte array at `address`.
/// The pointer is the `Poseidon` hash of:
/// * `address` - The address of the ByteArray (where the length is stored).
/// * `chunk` - The index of the chunk.
/// * The short string `ByteArray` is used as the capacity argument of the sponge construction
/// (domain separation).
fn inner_byte_array_pointer(address: StorageAddress, chunk: felt252) -> StorageBaseAddress {
let (r, _, _) = core::poseidon::hades_permutation(address.into(), chunk, 'ByteArray'_felt252);
storage_base_address_from_felt252(r)
}
/// Reads a byte array from storage from domain `address_domain` and address `address`.
/// The length of the byte array is read from `address` at domain `address_domain`.
/// For more info read the documentation of `ByteArrayStore`.
fn inner_read_byte_array(address_domain: u32, address: StorageAddress) -> SyscallResult<ByteArray> {
let Some::<usize>(len) = starknet::syscalls::storage_read_syscall(address_domain, address)?
.try_into() else {
return Err(array!['Invalid ByteArray length']);
};
let (mut remaining_full_words, pending_word_len) = crate::byte_array::len_parts(len);
let mut chunk = 0;
let mut chunk_base = inner_byte_array_pointer(address, chunk);
let mut index_in_chunk = 0_u8;
let mut result: ByteArray = Default::default();
loop {
if remaining_full_words == 0 {
break;
}
let value = starknet::syscalls::storage_read_syscall(
address_domain, storage_address_from_base_and_offset(chunk_base, index_in_chunk),
)?;
let Some::<bytes31>(value) = value.try_into() else {
return Err(array!['Invalid value']);
};
result.data.append(value);
remaining_full_words -= 1;
index_in_chunk = match core::integer::u8_overflowing_add(index_in_chunk, 1) {
Ok(x) => x,
Err(_) => {
// After reading 256 `bytes31`s `index_in_chunk` will overflow and we move to the
// next chunk.
chunk += 1;
chunk_base = inner_byte_array_pointer(address, chunk);
0
},
};
}
if pending_word_len != 0 {
let pending_word = starknet::syscalls::storage_read_syscall(
address_domain, storage_address_from_base_and_offset(chunk_base, index_in_chunk),
)?;
if !core::byte_array::is_valid_pending_word(pending_word, pending_word_len) {
return Err(array!['Invalid pending word']);
}
result.pending_word = pending_word;
result.pending_word_len = pending_word_len;
}
Ok(result)
}
/// Writes a byte array to storage to domain `address_domain` and address `address`.
/// The length of the byte array is written to `address` at domain `address_domain`.
/// For more info read the documentation of `ByteArrayStore`.
fn inner_write_byte_array(
address_domain: u32, address: StorageAddress, value: ByteArray,
) -> SyscallResult<()> {
let len = value.len();
starknet::syscalls::storage_write_syscall(address_domain, address, len.into())?;
let mut full_words = value.data.span();
let mut chunk = 0;
let mut chunk_base = inner_byte_array_pointer(address, chunk);
let mut index_in_chunk = 0_u8;
loop {
let curr_value = match full_words.pop_front() {
Some(x) => x,
None => { break Ok(()); },
};
match starknet::syscalls::storage_write_syscall(
address_domain,
storage_address_from_base_and_offset(chunk_base, index_in_chunk),
(*curr_value).into(),
) {
Ok(_) => {},
Err(err) => { break Err(err); },
}
index_in_chunk = match core::integer::u8_overflowing_add(index_in_chunk, 1) {
Ok(x) => x,
Err(_) => {
// After writing 256 `byte31`s `index_in_chunk` will overflow and we move to the
// next chunk.
chunk += 1;
chunk_base = inner_byte_array_pointer(address, chunk);
0
},
};
}?;
if value.pending_word_len != 0 {
starknet::syscalls::storage_write_syscall(
address_domain,
storage_address_from_base_and_offset(chunk_base, index_in_chunk),
value.pending_word,
)?;
}
Ok(())
}