Storing Arrays

On Starknet, complex values (e.g. tuples or structs) are stored in a continuous segment starting from the address of the storage variable. There is a limitation in Cairo that restricts complex storage values to a maximum of 256 field elements. This means that to store arrays with more than 255 elements, you would have to split them into segments of size n <= 255 and store these segments at multiple storage addresses. There is currently no native support for storing arrays in Cairo, so you would need to write your own implementation of the Store trait for the array type you wish to store.

However, the ByteArray struct can be used to store Array<bytes31> in storage without additional implementation. Before implementing your own Store trait, consider wether the ByteArray struct can be used to store the data you need! See the ByteArray section for more information.

Note: While storing arrays in storage is possible, it is not always recommended, as the read and write operations can get very costly. For example, reading an array of size n requires n storage reads, and writing to an array of size n requires n storage writes. If you only need to access a single element of the array at a time, it is recommended to use a LegacyMap and store the length in another variable instead.

The following example demonstrates how to write a simple implementation of the StorageAccess trait for the Array<felt252> type, allowing us to store arrays of up to 255 felt252 elements.

impl StoreFelt252Array of Store<Array<felt252>> {
    fn read(address_domain: u32, base: StorageBaseAddress) -> SyscallResult<Array<felt252>> {
        StoreFelt252Array::read_at_offset(address_domain, base, 0)
    }

    fn write(
        address_domain: u32, base: StorageBaseAddress, value: Array<felt252>
    ) -> SyscallResult<()> {
        StoreFelt252Array::write_at_offset(address_domain, base, 0, value)
    }

    fn read_at_offset(
        address_domain: u32, base: StorageBaseAddress, mut offset: u8
    ) -> SyscallResult<Array<felt252>> {
        let mut arr: Array<felt252> = array![];

        // Read the stored array's length. If the length is greater than 255, the read will fail.
        let len: u8 = Store::<u8>::read_at_offset(address_domain, base, offset)
            .expect('Storage Span too large');
        offset += 1;

        // Sequentially read all stored elements and append them to the array.
        let exit = len + offset;
        loop {
            if offset >= exit {
                break;
            }

            let value = Store::<felt252>::read_at_offset(address_domain, base, offset).unwrap();
            arr.append(value);
            offset += Store::<felt252>::size();
        };

        // Return the array.
        Result::Ok(arr)
    }

    fn write_at_offset(
        address_domain: u32, base: StorageBaseAddress, mut offset: u8, mut value: Array<felt252>
    ) -> SyscallResult<()> {
        // Store the length of the array in the first storage slot.
        let len: u8 = value.len().try_into().expect('Storage - Span too large');
        Store::<u8>::write_at_offset(address_domain, base, offset, len).unwrap();
        offset += 1;

        // Store the array elements sequentially
        while let Option::Some(element) = value
            .pop_front() {
                Store::<felt252>::write_at_offset(address_domain, base, offset, element).unwrap();
                offset += Store::<felt252>::size();
            };

        Result::Ok(())
    }

    fn size() -> u8 {
        255 * Store::<felt252>::size()
    }
}

You can then import this implementation in your contract and use it to store arrays in storage:

#[starknet::interface]
pub trait IStoreArrayContract<TContractState> {
    fn store_array(ref self: TContractState, arr: Array<felt252>);
    fn read_array(self: @TContractState) -> Array<felt252>;
}

#[starknet::contract]
pub mod StoreArrayContract {
    use super::StoreFelt252Array;

    #[storage]
    struct Storage {
        arr: Array<felt252>
    }

    #[abi(embed_v0)]
    impl StoreArrayImpl of super::IStoreArrayContract<ContractState> {
        fn store_array(ref self: ContractState, arr: Array<felt252>) {
            self.arr.write(arr);
        }

        fn read_array(self: @ContractState) -> Array<felt252> {
            self.arr.read()
        }
    }
}
Last change: 2024-06-09, commit: 3fbfb60