Unused Variables Remediation
Overview
A storage slot that is written but never read costs gas on every write and often marks unfinished logic: the check or state transition that was supposed to consume the value never got written. Remediation is a decision, not just a deletion — for each finding, determine whether the variable is dead (remove it), pending (wire it in), or consumed off-chain (keep it and accept the finding).
Related Detector: Unused Variables
Recommended Fix
Before (Vulnerable)
contract Oracle {
uint256 public lastPrice; // written every update, never read on-chain
uint256 public lastUpdated; // same
function update(uint256 price) external {
lastPrice = price;
lastUpdated = block.timestamp;
_settle(price);
}
}
After (Fixed)
contract Oracle {
uint256 public lastPrice;
uint256 public lastUpdated;
uint256 public constant MAX_DEVIATION_BPS = 500;
uint256 public constant MAX_STALENESS = 1 hours;
function update(uint256 price) external {
// The stored values now feed the checks they were meant for
require(block.timestamp - lastUpdated <= MAX_STALENESS, "stale feed");
require(_deviationBps(price, lastPrice) <= MAX_DEVIATION_BPS, "price jump");
lastPrice = price;
lastUpdated = block.timestamp;
_settle(price);
}
}
This is the “pending” case: the writes existed because a deviation/staleness check was intended, and the fix completes the logic. If instead the value is genuinely obsolete, delete the variable and its writes — but see the upgradeable-contract caveat below.
Alternative Mitigations
- Off-chain consumers: if the slot exists solely for
eth_callreaders (dashboards, indexers), keep it and emit an event alongside the write; events are cheaper for off-chain consumption than storage, and a future revision can drop the slot entirely. - Unused locals: if a computed local is dropped, either use it (it often represents a fee, bound, or check) or delete the computation to save gas.
- Upgradeable contracts: never remove a variable from the middle of the storage layout of a live upgradeable contract — replace it with a deprecated placeholder of the same type to preserve slot alignment, and stop writing to it.
Common Mistakes
- Deleting a public variable that off-chain systems read. On-chain data-flow cannot see
eth_callconsumers; check indexers and frontends before removing. - Reordering storage during cleanup of an upgradeable contract. Removing a slot shifts every subsequent variable and corrupts state on upgrade.
- Treating the finding as gas-only. A written-never-read slot is a prompt to ask what was supposed to read it; several real bugs (missing deviation checks, uncollected fees) look exactly like this.