Storage Structs
Storage structs are structs designed to hold persistent blockchain state. They serve as effect types, enabling the explicit storage access pattern that Fe uses.
What Makes a Storage Struct
Section titled “What Makes a Storage Struct”A storage struct contains fields that persist on-chain:
pub struct TokenStorage { pub balances: Map<u256, u256>, pub total_supply: u256, pub owner: u256,}Key characteristics:
- Contains
Mapfields for mappings - Contains primitive fields for simple values
- Used as effect types with
usesclause - Bound to contract fields
Storage Structs as Effects
Section titled “Storage Structs as Effects”Storage structs become effect types. Functions declare them in uses:
pub struct TokenStorage { pub balances: Map<u256, u256>, pub total_supply: u256,}
// Function that reads from storagefn get_balance(account: u256) -> u256 uses (store: TokenStorage) { store.balances.get(account)}
// Function that writes to storagefn set_balance(account: u256, amount: u256) uses (mut store: TokenStorage) { store.balances.set(account, amount)}Connecting to Contracts
Section titled “Connecting to Contracts”Contracts hold storage structs as fields and provide them as effects:
contract Token { mut store: TokenStorage,
recv TokenMsg { BalanceOf { account } -> u256 uses (store) { get_balance(account) }
Transfer { to, amount } -> bool uses (mut store) { transfer(caller(), to, amount) } }}The handler’s uses (store) clause binds the contract field to the effect.
Designing Storage Structs
Section titled “Designing Storage Structs”Single Storage Struct
Section titled “Single Storage Struct”For simple contracts, one storage struct is sufficient:
pub struct CounterStorage { pub value: u256,}
fn get_value() -> u256 uses (store: CounterStorage) { store.value}
fn increment() uses (mut store: CounterStorage) { store.value = store.value + 1}Multiple Storage Structs
Section titled “Multiple Storage Structs”For complex contracts, split storage by concern:
// Token balancespub struct BalanceStorage { pub balances: Map<u256, u256>, pub total_supply: u256,}
// Allowance trackingpub struct AllowanceStorage { pub allowances: Map<u256, Map<u256, u256>>,}
// Access controlpub struct OwnerStorage { pub owner: u256, pub pending_owner: u256,}
// Pausabilitypub struct PauseStorage { pub paused: bool,}Each becomes an independent effect:
fn transfer(from: u256, to: u256, amount: u256) -> bool uses (mut balances: BalanceStorage, pause: PauseStorage){ if pause.paused { return false } // ... transfer logic true}Map Fields
Section titled “Map Fields”Map is the primary collection type for storage:
pub struct Registry { // Simple mapping: key -> value pub entries: Map<u256, u256>,
// Nested mapping: key -> (key -> value) pub nested: Map<u256, Map<u256, u256>>,}Access patterns:
fn get_entry(key: u256) -> u256 uses (reg: Registry) { reg.entries.get(key)}
fn set_entry(key: u256, value: u256) uses (mut reg: Registry) { reg.entries.set(key, value)}
fn get_nested(outer: u256, inner: u256) -> u256 uses (reg: Registry) { reg.nested.get(outer).get(inner)}
fn set_nested(outer: u256, inner: u256, value: u256) uses (mut reg: Registry) { reg.nested.get(outer).set(inner, value)}Visibility
Section titled “Visibility”Storage structs and their fields are typically public:
pub struct TokenStorage { pub balances: Map<u256, u256>, // Public for effect access pub total_supply: u256,}The pub on fields allows store.balances syntax in functions using the effect.
Storage Structs vs Regular Structs
Section titled “Storage Structs vs Regular Structs”| Aspect | Storage Struct | Regular Struct |
|---|---|---|
| Purpose | Persistent state | In-memory data |
| Contains | Map, primitives | Any types |
| Used as | Effect type | Value type |
| Access | Via uses clause | Direct |
| Location | On-chain | Memory |
Complete Example
Section titled “Complete Example”A full token with storage structs:
// Storage definitionspub struct TokenStorage { pub balances: Map<u256, u256>, pub allowances: Map<u256, Map<u256, u256>>, pub total_supply: u256,}
pub struct MetadataStorage { pub name_hash: u256, pub symbol_hash: u256, pub decimals: u8,}
// Functions using storage effectsfn get_balance(account: u256) -> u256 uses (tokens: TokenStorage) { tokens.balances.get(account)}
fn transfer(from: u256, to: u256, amount: u256) -> bool uses (mut tokens: TokenStorage) { let from_bal = tokens.balances.get(from) if from_bal < amount { return false } tokens.balances.set(from, from_bal - amount)
let to_bal = tokens.balances.get(to) tokens.balances.set(to, to_bal + amount) true}
fn get_decimals() -> u8 uses (metadata: MetadataStorage) { metadata.decimals}
// Contract binding storage to effectscontract Token { mut tokens: TokenStorage, metadata: MetadataStorage,
recv Erc20 { Transfer { to, amount } -> bool uses (mut tokens) { transfer(caller(), to, amount) }
BalanceOf { account } -> u256 uses (tokens) { get_balance(account) }
Decimals -> u8 uses (metadata) { get_decimals() } }}Summary
Section titled “Summary”| Concept | Description |
|---|---|
| Storage struct | Struct holding persistent state |
| Effect type | Storage struct used in uses clause |
Map<K, V> | Key-value storage field |
uses (store: Storage) | Read-only access |
uses (mut store: Storage) | Read-write access |
Handler uses (field) | Bind contract field to effect in handlers |