Component-Contract Storage Collisions

Components can declare their own storage variables.

When a contract uses a component, the component storage is merged with the contract storage. The storage layout is only determined by the variables names, so variables with the same name will collide.

In a future release, the #[substorage(v1)] will determine the storage layout based on the component as well, so collisions will be avoided.

A good practice is to prefix the component storage variables with the component name, as shown in the Switchable component example.

Example

Here's an example of a collision on the switchable_value storage variable of the Switchable component.

Interface:

#[starknet::interface]
pub trait ISwitchCollision<TContractState> {
    fn set(ref self: TContractState, value: bool);
    fn get(ref self: TContractState) -> bool;
}

Here's the storage of the contract (you can expand the code snippet to see the full contract and tests):

#[starknet::interface]
pub trait ISwitchCollision<TContractState> {
    fn set(ref self: TContractState, value: bool);
    fn get(ref self: TContractState) -> bool;
}

#[starknet::contract]
pub mod SwitchCollisionContract {
    use components::switchable::switchable_component;

    component!(path: switchable_component, storage: switch, event: SwitchableEvent);

    #[abi(embed_v0)]
    impl SwitchableImpl = switchable_component::Switchable<ContractState>;
    impl SwitchableInternalImpl = switchable_component::SwitchableInternalImpl<ContractState>;

    #[storage]
    struct Storage {
        switchable_value: bool,
        #[substorage(v0)]
        switch: switchable_component::Storage,
    }

    #[constructor]
    fn constructor(ref self: ContractState) {
        self.switch._off();
    }

    #[event]
    #[derive(Drop, starknet::Event)]
    enum Event {
        SwitchableEvent: switchable_component::Event,
    }

    #[abi(embed_v0)]
    impl SwitchCollisionContract of super::ISwitchCollision<ContractState> {
        fn set(ref self: ContractState, value: bool) {
            self.switchable_value.write(value);
        }

        fn get(ref self: ContractState) -> bool {
            self.switchable_value.read()
        }
    }
}

#[cfg(test)]
mod switch_collision_tests {
    use components::switchable::switchable_component::SwitchableInternalTrait;
    use components::switchable::{ISwitchable, ISwitchableDispatcher, ISwitchableDispatcherTrait};
    use super::{
        SwitchCollisionContract, ISwitchCollisionDispatcher, ISwitchCollisionDispatcherTrait
    };
    use starknet::storage::StorageMemberAccessTrait;
    use starknet::SyscallResultTrait;
    use starknet::syscalls::deploy_syscall;

    fn deploy() -> (ISwitchCollisionDispatcher, ISwitchableDispatcher) {
        let (contract_address, _) = deploy_syscall(
            SwitchCollisionContract::TEST_CLASS_HASH.try_into().unwrap(), 0, array![].span(), false
        )
            .unwrap_syscall();

        (
            ISwitchCollisionDispatcher { contract_address },
            ISwitchableDispatcher { contract_address },
        )
    }

    #[test]
    fn test_collision() {
        let (mut contract, mut contract_iswitch) = deploy();

        assert_eq!(contract.get(), false);
        assert_eq!(contract_iswitch.is_on(), false);

        contract_iswitch.switch();
        assert_eq!(contract_iswitch.is_on(), true);
        assert_eq!(contract.get(), true);

        // `collision` between component storage 'value' and contract storage 'value'
        assert_eq!(contract.get(), contract_iswitch.is_on());

        contract.set(false);
        assert_eq!(contract.get(), contract_iswitch.is_on());
    }
}

Both the contract and the component have a switchable_value storage variable, so they collide:

    fn test_collision() {
        let (mut contract, mut contract_iswitch) = deploy();

        assert_eq!(contract.get(), false);
        assert_eq!(contract_iswitch.is_on(), false);

        contract_iswitch.switch();
        assert_eq!(contract_iswitch.is_on(), true);
        assert_eq!(contract.get(), true);

        // `collision` between component storage 'value' and contract storage 'value'
        assert_eq!(contract.get(), contract_iswitch.is_on());

        contract.set(false);
        assert_eq!(contract.get(), contract_iswitch.is_on());
    }
Last change: 2024-07-01, commit: 6f4d055