Managing Persistent State in Cairo: Implementing Storage Variables
Learn how to implement persistent state in Cairo using the Storage trait and StorageMap. This guide covers the implementation of a counter contract and the verification process using Scarb and Starkli.
06 Aug 2026, 14:57 UTC

The Problem: Persistent State in Cairo
Unlike traditional application development where state is managed in a database, Cairo contracts on Starknet must explicitly define how data is mapped to the blockchain's global storage. A common mistake for developers transitioning from Solidity is treating storage as a simple variable; in Cairo, storage is a trait-based mapping where every piece of data must be associated with a unique key to avoid collisions.
The takeaway: Use the #[storage] attribute to define a storage struct, and interact with it using the Storage trait to ensure state persists across transactions.
Prerequisites
- Scarb: The Cairo package manager and build tool installed on your local machine.
- Starkli: The CLI tool for interacting with Starknet networks.
- Cairo 1.0+ Knowledge: This guide assumes the use of the Rust-like syntax introduced in Cairo 1.0; Cairo 0 code is not compatible.
Implementing a State-Bearing Contract
To store data, you must define a Storage struct. This struct does not hold the data itself but defines the mapping used to locate data in the blockchain's state.
#[starknet::interface]
trait ICounter {
fn increment(ref self);
fn get_count(self: @ContractAddress) -> u256;
}In the implementation below, we define a storage variable count. In Cairo, u256 is commonly used for counters to prevent overflow, while felt252 (field element) is the native word size of the VM.
#[starknet::contract]
mod Counter {
use starknet::ContractAddress;
use starknet::storage::{Storage, StorageMap};
#[storage]
struct Storage {
// StorageMap maps a key to a value.
// Here, we use a single key to store one value.
count: StorageMap<u8, u256>,
}
#[abi(embed_v0)]
impl CounterImpl of super::ICounter {
fn increment(ref self) {
// Access storage via self.storage
let current_count = self.storage.count.read(0);
self.storage.count.write(0, current_count + 1);
}
fn get_count(self: @ContractAddress) -> u256 {
// 'view' functions use @ContractAddress to read state without changing it
let count = self.storage.count.read(0);
count
}
}
}Technical Decision: StorageMap vs. Simple Variables
In Cairo, you will often see StorageMap used even for single values. This is because the underlying Starknet storage is a massive key-value store. When you define count: StorageMap<u8, u256>, you are telling the VM how to calculate the storage slot for that variable. Using a fixed key (like 0 in the example) effectively turns the map into a single persistent variable.
| Feature | Standard Function | View Function |
|---|---|---|
| State Access | Read and Write | Read Only |
| Gas Cost | High (State updates) | Low/Zero (Off-chain) |
| Execution | Requires Transaction | Call via RPC |
Deployment and Verification
To verify the state logic, run these commands in your terminal. Ensure you have a local Devnet running or are connected to a testnet.
- Compile the contract: Run
scarb buildin the project root. This checks for type errors and generates the CASM (Cairo Assembly) files. - Deploy: Use
starkli deploy-contract --contract-id. Note the deployedContractAddress. - Execute State Change: Run
starkli invoke increment. This requires a signature and pays gas to update the storage slot. - Verify Result: Run
starkli call get_count. The expected output is the current value of the counter (e.g.,1).
Limitations and Risks
- Storage Collisions: If you manually define storage keys, ensure they are unique. Overlapping keys will cause one variable to overwrite another.
- Gas Sensitivity: Writing to storage is the most expensive operation in Cairo. Avoid updating storage inside tight loops.
- Felt Constraints: Remember that
felt252cannot represent all 256-bit integers; useu256for large numbers to avoid runtime panics.
Rollback Procedure
Because blockchain state is immutable, you cannot "undo" a storage write. To revert the state of a counter, you must execute a new transaction that writes the previous value back to the storage slot (e.g., implementing a decrement function).
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.