Mappings

Maps are a key-value data structure used to store data within a smart contract. In Cairo they are implemented using the LegacyMap type. It's important to note that the LegacyMap type can only be used inside the Storage struct of a contract and that it can't be used elsewhere.

Here we demonstrate how to use the LegacyMap type within a Cairo contract, to map between a key of type ContractAddress and value of type felt252. The key-value types are specified within angular brackets <>. We write to the map by calling the write() method, passing in both the key and value. Similarly, we can read the value associated with a given key by calling the read() method and passing in the relevant key.

Some additional notes:

  • More complex key-value mappings are possible, for example we could use LegacyMap::<(ContractAddress, ContractAddress), felt252> to create an allowance on an ERC20 token contract.

  • 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

use starknet::ContractAddress;

#[starknet::interface]
pub trait IMapContract<TContractState> {
    fn set(ref self: TContractState, key: ContractAddress, value: felt252);
    fn get(self: @TContractState, key: ContractAddress) -> felt252;
}

#[starknet::contract]
pub mod MapContract {
    use starknet::ContractAddress;

    #[storage]
    struct Storage {
        // The `LegacyMap` type is only available inside the `Storage` struct.
        map: LegacyMap::<ContractAddress, felt252>,
    }

    #[abi(embed_v0)]
    impl MapContractImpl of super::IMapContract<ContractState> {
        fn set(ref self: ContractState, key: ContractAddress, value: felt252) {
            self.map.write(key, value);
        }

        fn get(self: @ContractState, key: ContractAddress) -> felt252 {
            self.map.read(key)
        }
    }
}

#[cfg(test)]
mod test {
    use super::{MapContract, IMapContractDispatcher, IMapContractDispatcherTrait};
    use starknet::{ContractAddress, SyscallResultTrait, syscalls::deploy_syscall};

    #[test]
    fn test_deploy_and_set_get() {
        let (contract_address, _) = deploy_syscall(
            MapContract::TEST_CLASS_HASH.try_into().unwrap(), 0, array![].span(), false
        )
            .unwrap_syscall();
        let mut contract = IMapContractDispatcher { contract_address };

        // Write to map.
        let value: felt252 = 1;
        contract.set(key: contract_address, value: value);

        // Read from map.
        let read_value = contract.get(contract_address);
        assert(read_value == 1, 'wrong value read');
    }
}
Last change: 2024-04-12, commit: 864ccd5