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;
use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess};
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::{ISwitchableDispatcher, ISwitchableDispatcherTrait};
use super::{
SwitchCollisionContract, ISwitchCollisionDispatcher, ISwitchCollisionDispatcherTrait
};
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());
}