Skip to content

Component-Contract Storage Collisions

Components can declare their own storage variables.

When a contract use 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):

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

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

mod switch_collision_tests {
    use components::switchable::switchable_component::SwitchableInternalTrait;
    use components::switchable::{ISwitchable, ISwitchableDispatcher, ISwitchableDispatcherTrait};
 
    use components::contracts::switch_collision::{
        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]
    #[available_gas(2000000)]
    fn test_collision() {
        let (mut contract, mut contract_iswitch) = deploy();
 
        assert(contract.get() == false, 'value !off');
        assert(contract_iswitch.is_on() == false, 'switch !off');
 
        contract_iswitch.switch();
        assert(contract_iswitch.is_on() == true, 'switch !on');
        assert(contract.get() == true, 'value !on');
 
        // `collision` between component storage 'value' and contract storage 'value'
        assert(contract.get() == contract_iswitch.is_on(), 'value != switch');
 
        contract.set(false);
        assert(contract.get() == contract_iswitch.is_on(), 'value != switch');
    }
}