State Analysis
Ora's compiler includes sophisticated state change tracking that automatically analyzes storage access patterns in your smart contracts, helping you write more efficient, secure, and maintainable code.
Overviewâ
State analysis runs automatically during compilation and provides insights about:
- đ Storage reads and writes: Which functions access which variables
- đ Function properties: Stateless, readonly, or state-modifying
- â ī¸ Potential issues: Dead stores, missing validation, unused variables
- ⥠Gas optimization: Identify expensive storage operations
Automatic Analysisâ
Every compilation includes state analysis with zero overhead:
ora contract.ora
â ī¸ State Analysis Warnings for MyContract (2):
đĄ [DeadStore] Storage variable 'unusedData' is written by 'setData'
but never read by any function in the contract
đŦ Remove unused storage variable or add read logic
âšī¸ [MissingCheck] Function 'approve' modifies storage without reading any state
đŦ Add validation checks before modifying storage (e.g., require conditions)
Clean output: Only warnings are shown. No noise during compilation!
Detailed Analysis Modeâ
For comprehensive analysis, use the --analyze-state flag:
ora --analyze-state contract.ora
This shows complete details for every function:
=== State Analysis: SimpleToken ===
Function: transfer
ââ Stateless: false
ââ Readonly: false
ââ Modifies State: true
ââ Reads: balances
ââ Writes: balances
Function: balanceOf
ââ Stateless: true
ââ Readonly: true
ââ Modifies State: false
ââ Reads: balances
ââ Writes: (none)
Function: getTotalSupply
ââ Stateless: false
ââ Readonly: true
ââ Modifies State: false
ââ Reads: totalSupply
ââ Writes: (none)
Function Propertiesâ
Stateless Functionsâ
Pure computation with no storage access:
pub fn add(a: u256, b: u256) -> u256 {
return a + b; // â
Stateless: No storage access
}
Benefits:
- Can be inlined for gas savings
- Safe to call from any context
- Predictable behavior
Readonly Functionsâ
Only read storage, never write:
pub fn balanceOf(account: address) -> u256 {
return balances[account]; // â
Readonly: Only reads
}
Benefits:
- Safe for view-like queries
- Can be called off-chain
- No state changes
State-Modifying Functionsâ
Write to storage:
pub fn transfer(recipient: address, amount: u256) {
balances[std.msg.sender()] -= amount; // â ī¸ Modifies state
balances[recipient] += amount;
}
Note: The compiler checks if validation is missing!
Warning Typesâ
1. Dead Store (â ī¸ Warning)â
Storage variable written but never read by any function:
contract Example {
storage unusedData: u256; // â ī¸ Problem!
pub fn writeData(value: u256) {
unusedData = value; // Written but never read
}
// No function reads unusedData!
}
Warning:
â ī¸ [DeadStore] Storage variable 'unusedData' is written by 'writeData'
but never read by any function in the contract
đŦ Remove unused storage variable or add read logic in another function
Fix: Either use it or remove it!
2. Missing Check (âšī¸ Info)â
Function modifies storage without validation:
pub fn approve(spender: address, amount: u256) {
allowances[std.msg.sender()][spender] = amount; // âšī¸ No validation!
}
Warning:
âšī¸ [MissingCheck] Function 'approve' modifies storage without reading any state
đŦ Add validation checks before modifying storage (e.g., require conditions)
Good practice:
pub fn approve(spender: address, amount: u256) {
require(spender != address(0), "Invalid spender"); // â
Validation added
require(amount > 0, "Amount must be positive");
allowances[std.msg.sender()][spender] = amount;
}
3. Unvalidated Constructor Parameter (âšī¸ Info)â
Constructor stores parameters without validation:
pub fn init(initialSupply: u256) {
totalSupply = initialSupply; // âšī¸ No validation!
}
Warning:
âšī¸ [UnvalidatedConstructorParam] Constructor 'init' stores parameters without validation
đŦ Add validation for constructor parameters (e.g., require amount > 0, address != 0)
Good practice:
pub fn init(initialSupply: u256) {
require(initialSupply > 0, "Supply must be positive"); // â
Validation
totalSupply = initialSupply;
}
Real-World Example: ERC20â
Here's what state analysis shows for an ERC20 token:
contract SimpleToken {
storage totalSupply: u256;
storage balances: map[address, u256];
storage allowances: doublemap[address, address, u256];
pub fn init(initialSupply: u256) {
totalSupply = initialSupply;
balances[std.msg.sender()] = initialSupply;
}
pub fn balanceOf(account: address) -> u256 {
return balances[account];
}
pub fn transfer(recipient: address, amount: u256) -> bool {
let sender = std.msg.sender();
let senderBalance = balances[sender];
if (senderBalance < amount) {
return false;
}
balances[sender] = senderBalance - amount;
balances[recipient] = balances[recipient] + amount;
return true;
}
pub fn approve(spender: address, amount: u256) -> bool {
allowances[std.msg.sender()][spender] = amount;
return true;
}
}
Analysis Results:
Function: balanceOf
ââ Stateless: false
ââ Readonly: true â
Only reads, never writes
ââ Reads: balances
ââ Writes: (none)
Function: transfer
ââ Stateless: false
ââ Readonly: false
ââ Modifies State: true
ââ Reads: balances â
Reads before writing (good!)
ââ Writes: balances
Function: approve
ââ Stateless: false
ââ Readonly: false
ââ Modifies State: true
ââ Reads: (none) â ī¸ No validation checks
ââ Writes: allowances
Warnings:
âšī¸ [MissingCheck] Function 'approve' modifies storage without reading
How It Worksâ
Contract-Level Analysisâ
State analysis tracks reads and writes across all functions:
contract Example {
storage counter: u256;
pub fn increment() {
counter += 1; // Writes to 'counter'
}
pub fn getCounter() -> u256 {
return counter; // Reads from 'counter'
}
}
Result: â
No dead store warning because getCounter reads what increment writes.
Smart Detectionâ
The analyzer understands:
- Direct access:
counter = 5 - Map access:
balances[addr] = 100 - Compound assignments:
counter += 1 - Multiple operations: Reading then writing same variable
False Positive Preventionâ
Old approach (function-level):
â Warning: 'approve' writes 'allowances' but doesn't read it
(False positive! transferFrom reads it!)
New approach (contract-level):
â
No warning: 'allowances' is written by approve() and read by transferFrom()
Benefitsâ
For Developersâ
- đ Instant insights: See what each function does to storage
- đ Catch bugs early: Find unused variables before deployment
- đ Self-documenting: Function behavior is explicit
- ⥠Gas awareness: Identify expensive operations
For Securityâ
- đ Audit trail: Know exactly what modifies state
- đ¯ Attack surface: Identify state-changing entry points
- â Missing checks: Detect unvalidated writes
- đī¸ Dead stores: Find unused or redundant operations
For Optimizationâ
- đ° Gas savings: Remove unnecessary storage writes
- đĻ Caching: Identify readonly functions
- đ¯ Inlining: Mark stateless functions for optimization
Best Practicesâ
- Review warnings: Don't ignore state analysis output
- Validate inputs: Add checks before storage writes
- Remove dead stores: Clean up unused variables
- Use readonly: Make functions readonly when possible
- Keep it simple: Fewer storage operations = lower gas costs
Advanced Usageâ
CI/CD Integrationâ
Fail builds on warnings:
ora --analyze-state contract.ora | grep "â ī¸" && exit 1
Comparison Across Versionsâ
# Before changes
ora --analyze-state contract.ora > before.txt
# After changes
ora --analyze-state contract.ora > after.txt
# Compare
diff before.txt after.txt
Technical Detailsâ
Zero Runtime Overhead:
- Analysis runs after parsing, before MLIR generation
- No impact on compilation speed
- No changes to generated bytecode
Comprehensive Tracking:
- All storage operations (SLOAD/SSTORE)
- Direct variable access
- Map and array operations
- Nested structures
Smart Heuristics:
- Identifies storage variables by declaration
- Tracks reads and writes separately
- Handles complex expressions correctly
Why This Mattersâ
Smart contracts interact with expensive storage operations. State analysis helps you:
- Avoid waste: Don't store values you never read
- Add validation: Check before you write
- Optimize gas: Minimize storage operations
- Improve security: Validate all state changes
- Better code: Clear understanding of side effects
State analysis is always on, helping you write better Ora contracts automatically! đ