写入任何存储槽

在Starknet上,一个合约的存储是一个拥有 2^251 个槽的map,每个槽是一个初始化为 0 的 felt。存储变量的地址在编译时通过公式计算得出:存储变量地址 := pedersen(keccak(变量名), keys)。与存储变量的交互通常使用 self.var.read()self.var.write()

然而,我们可以使用 storage_write_syscallstorage_read_syscall 系统调用,来对任何存储槽进行写入和读取。 这在写入那些在编译时还未确定的存储变量时非常有用,这也可以确保即使合约升级且存储变量地址的计算方法改变,这些变量仍然可访问。

在以下示例中,我们使用 Poseidon 哈希函数来计算存储变量的地址。Poseidon 是一个 ZK 友好的哈希函数,比 Pedersen 更便宜、更快,是链上计算的绝佳选择。一旦地址被计算出来,我们就使用存储的系统调用与之交互。

#[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
    }
}

访问 Voyager 上的合约,或者在 Remix 中测试它.

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