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');
}
}