Writing to any storage slot

On Starknet, a contract's storage is a map with 2^251 slots, where each slot is a felt which is initialized to 0. The address of storage variables is computed at compile time using the formula: storage variable address := pedersen(keccak(variable name), keys). Interactions with storage variables are commonly performed using the self.var.read() and self.var.write() functions.

Nevertheless, we can use the storage_write_syscall and storage_read_syscall syscalls, to write to and read from any storage slot. This is useful when writing to storage variables that are not known at compile time, or to ensure that even if the contract is upgraded and the computation method of storage variable addresses changes, they remain accessible.

En el siguiente ejemplo, utilizamos la función hash Poseidon para calcular la dirección de una variable de almacenamiento. Poseidon es una función hash compatible con ZK que es más barata y rápida que Pedersen, lo que la convierte en una excelente elección para cálculos en cadena. Una vez calculada la dirección, utilizamos las llamadas al sistema de almacenamiento para interactuar con ella.

#[starknet::interface]
trait IWriteToAnySlots<TContractState> {
    fn write_slot(ref self: TContractState, value: u32);
    fn read_slot(self: @TContractState) -> u32;
}

#[starknet::contract]
mod WriteToAnySlot {
    use starknet::syscalls::{storage_read_syscall, storage_write_syscall};
    use starknet::SyscallResultTrait;
    use poseidon::poseidon_hash_span;
    use starknet::storage_access::Felt252TryIntoStorageAddress;
    use starknet::StorageAddress;

    #[storage]
    struct Storage {}

    const SLOT_NAME: felt252 = 'test_slot';

    #[abi(embed_v0)]
    impl WriteToAnySlot of super::IWriteToAnySlots<ContractState> {
        fn write_slot(ref self: ContractState, value: u32) {
            storage_write_syscall(0, get_address_from_name(SLOT_NAME), value.into());
        }

        fn read_slot(self: @ContractState) -> u32 {
            storage_read_syscall(0, get_address_from_name(SLOT_NAME))
                .unwrap_syscall()
                .try_into()
                .unwrap()
        }
    }
    fn get_address_from_name(variable_name: felt252) -> StorageAddress {
        let mut data: Array<felt252> = ArrayTrait::new();
        data.append(variable_name);
        let hashed_name: felt252 = poseidon_hash_span(data.span());
        let MASK_250: u256 = 0x03ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff;
        // By taking the 250 least significant bits of the hash output, we get a valid 250bits storage address.
        let result: felt252 = (hashed_name.into() & MASK_250).try_into().unwrap();
        let result: StorageAddress = result.try_into().unwrap();
        result
    }
}

Visita el contrato en Voyager o juega con él en Remix.

Last change: 2023-10-12, commit: 90aa7c0