Contract storage
Storage layout
The contract’s storage is a persistent storage space where you can read, write, modify, and persist data. The storage is a map with \(2^{251}\) slots, where each slot is a felt which is initialized to 0.
Storage low level functions
The basic function for writing to storage writes, value to key is:
storage_write_syscall(address_domain, address, value)
storage_read
is a basic function that is used for getting the storage address, this function is created by the compiler when defining a storage variable, as explained below. This function returns the address of the storage variable. Below we discuss
how this
address is determined from the variable’s name and keys.
Both storage_read
and storage_write
are system calls that can be imported by adding the line:
use starknet::syscalls::storage_read_syscall;
use starknet::syscalls::storage_write_syscall;
Storage variables
The most common way to interact with a contract’s storage is through storage variables.
The #[storage]
attribute above the Storage
struct declares that the contents of this struct are part of the contract storage. The storage variables stored inside this struct can consist of a single felt, or it can be a mapping from multiple arguments to a tuple of felts or structs.
To use this variable, the var.read(args)
, var.write(args, value)
and var.address(args)
functions are automatically created by the #[storage]
attribute, for reading the storage value, writing the storage value and getting the storage address, respectively.
The Starknet contract compiler generates the Cairo code that maps the storage variable’s name and argument values to an address — so that it can be part of the generated proof.
The address of a storage variable is computed as follows:
-
If it is a single value, then the address is
sn_keccak(variable_name)
, where variable_name is the ASCII encoding of the variable’s name. -
If it is a (nested) mapping, then the address of the value at key
k_1,...,k_n
ish(...h(h(sn_keccak(variable_name),k_1),k_2),...,k_n)
where \(h\) is the Pedersen hash and the final value is taken \(\bmod 2^{251}-256\) -
If it is a mapping to complex values (e.g., tuples or structs), then this complex value lies in a continuous segment starting from the address calculated in the previous point. Note that 256 field elements is the current limitation on the maximal size of a complex storage value.
-
Note that when calling
var.address(args)
for a storage variable with complex values, the returned value is the address of the first element in the storage.
We can summarize the above as follows:
storage variable address := pedersen(keccak(variable name), keys)
The following example defines storage variables with complex values.
#[storage]
struct Storage {
name: felt252,
symbol: felt252,
decimals: u8,
total_supply: u256,
balances: LegacyMap::<ContractAddress, u256>,
allowances: LegacyMap::<(ContractAddress, ContractAddress), u256>,
}