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):
#[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()
}
}
}
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');
}
}