Storage Variables

As stated previously, storage variables allow you to store data that will be stored in the contract's storage that is itself stored on the blockchain. These data are persistent and can be accessed and modified anytime once the contract is deployed.

Storage variables in Starknet contracts are stored in a special struct called Storage:

#[starknet::contract]
mod contract {
    use starknet::ContractAddress;
    #[storage]
    struct Storage {
        id: u8,
        names: LegacyMap::<ContractAddress, felt252>,
    }
}

Listing 99-2: A Storage Struct

The storage struct is a struct like any other, except that it must be annotated with #[storage] allowing you to store mappings using the LegacyMap type.

Storing Mappings

Mappings are a key-value data structure that you can use to store data within a smart contract. They are essentially hash tables that allow you to associate a unique key with a corresponding value. Mappings are also useful to store sets of data, as it's impossible to store arrays in storage.

A mapping is a variable of type LegacyMap, in which the key and value types are specified within angular brackets <>. It is important to note that the LegacyMap type can only be used inside the Storage struct, and can't be used to define mappings in user-defined structs.

You can also create more complex mappings than that; you can find one in Listing 99-2bis like the popular allowances storage variable in the ERC20 Standard which maps the owner and spender to the allowance using tuples:

    #[storage]
    struct Storage {
        allowances: LegacyMap::<(ContractAddress, ContractAddress), u256>
    }

Listing 99-2bis: Storing mappings

In mappings, the address of the value at key k_1,...,k_n is h(...h(h(sn_keccak(variable_name),k_1),k_2),...,k_n) where ℎ is the Pedersen hash and the final value is taken mod2251−256. You can learn more about the contract storage layout in the Starknet Documentation

Storing custom structs

The compiler knows how to store basic data types, such as unsigned integers (u8, u128, u256...), felt252, ContractAddress, etc. But what if you want to store a custom struct in storage? In that case, you have to explicitly tell the compiler how to store your struct in storage. In our example, we want to store a Person struct in storage, so we have to tell the compiler how to store it in storage by adding a derive attribute of the starknet::Store trait to our struct definition.

    #[derive(Copy, Drop, Serde, starknet::Store)]
    struct Person {
        name: felt252,
        address: ContractAddress
    }

Reading from Storage

To read the value of the storage variable names, we call the read function on the names storage variable, passing in the key address as a parameter.

            let name = self.names.read(address);

Listing 99-3: Calling the read function on the names variable

Note: When the storage variable does not store a mapping, its value is accessed without passing any parameters to the read method

Writing to Storage

To write a value to the storage variable names, we call the write function on the names storage variable, passing in the key and values as arguments.

            self.names.write(user, name);

Listing 99-4: Writing to the names variable