Atomic State Update
Detects non-atomic multi-step state updates where intermediate consistency checks are missing between storage writes.
Atomic State Update
Overview
The atomic state update detector identifies functions that perform multiple storage writes to different slots without intermediate validation. When two or more state variables must remain consistent (e.g., debiting one balance and crediting another), skipping the check between writes creates a window where the contract state is inconsistent. If the function reverts partway through or an external call interrupts execution, the partial update can leave the contract in an exploitable state.
Research shows 116 instances of this pattern across 352 audited contracts resulted in $63.8M+ in losses during 2024-2025.
Why This Is an Issue
DeFi protocols maintain invariants across multiple storage variables: total supply must equal the sum of all balances, a lending pool’s debt must not exceed its collateral value, and a swap’s input token balance change must match the output. When a function updates these correlated variables without checking consistency between writes, three problems arise:
- Partial execution: If a revert occurs between the first and second write, the first write persists (in the reverted call’s context, it does not, but in cross-contract interactions with callbacks, the intermediate state is observable).
- Loop inconsistency: Batch operations that update multiple slots per iteration without per-iteration invariant checks can accumulate drift.
- Blind state transitions: State machine contracts that overwrite status variables without reading the current state can skip invalid transitions.
How to Resolve
// Before: Vulnerable -- two writes without intermediate check
function swap(uint256 amountIn) external {
balances[tokenA] += amountIn;
balances[tokenB] -= calculateOutput(amountIn);
// No check that invariant holds
}
// After: Fixed -- validate invariant between writes
function swap(uint256 amountIn) external {
uint256 amountOut = calculateOutput(amountIn);
balances[tokenA] += amountIn;
require(balances[tokenB] >= amountOut, "Insufficient liquidity");
balances[tokenB] -= amountOut;
require(
balances[tokenA] * balances[tokenB] >= k,
"Invariant violated"
);
}
Examples
Vulnerable
function transfer(address to, uint256 amount) external {
balances[msg.sender] -= amount; // Write 1
balances[to] += amount; // Write 2
totalTransferred += amount; // Write 3
// No intermediate checks between writes
}
Fixed
function transfer(address to, uint256 amount) external {
require(balances[msg.sender] >= amount, "Insufficient balance");
balances[msg.sender] -= amount;
balances[to] += amount;
require(
balances[msg.sender] + balances[to] >= amount,
"Balance invariant"
);
totalTransferred += amount;
}
Sample Sigvex Output
[HIGH] atomic-state-update
Multi-step state update without intermediate checks
Location: swap @ block 0, instruction 2
Confidence: 0.75
Function 'swap' performs multiple storage writes (slots: balance,
supply) without intermediate consistency validation.
Detection Methodology
- State operation tracking: Identifies all
SSTORE(writes),SLOAD(reads), and comparison operations within each function. - Multi-step write detection: Finds pairs of writes to different storage slots in the same or adjacent basic blocks without an intervening comparison or read.
- Loop analysis: Detects loop bodies with multiple state writes and fewer conditional checks than writes.
- Blind write detection: Flags functions that write to multiple slots without reading any of them first, indicating state transitions without validation.
- Context-aware suppression: Reduces confidence for ERC-20 transfer patterns (debit+credit is expected), reentrancy-guarded functions, and access-controlled contracts.
Limitations
False positives: Standard ERC-20 transfers perform a debit-then-credit pattern across two storage slots, which is flagged but suppressed to low severity for recognized token contracts. Admin functions in access-controlled contracts that intentionally perform batch configuration updates are also flagged. False negatives: Multi-step updates split across helper functions or performed via DELEGATECALL to a library are not traced.
Related Detectors
- Callback State Mutation — detects state writes after external calls
- Reentrancy — detects classic reentrancy allowing double-spend