crates/lint/docs/write-after-write.md
Severity: Gas
ID: write-after-write
Flags storage variables that are written to consecutively without the first value ever being read. The first write is dead code; it pays the SSTORE cost but its value is immediately discarded when the second write overwrites it.
Reports the first assignment to a state variable when that same variable is written a second time before being read. Detection covers:
= assignments and delete on bare state variable identifiers(x, y) = (1, 2) tracks each component individually++x, x--, etc.) — these read then write, so the write
they produce can itself become dead if immediately overwrittenThe analysis recurses into branch bodies (if/else, loops, try clauses) and modifier bodies
with fresh state so intra-body pairs are still caught. Conditional boundaries (&&, ||,
ternary) are handled conservatively to avoid false positives across short-circuit paths. Compound
assignments (+=, |=, etc.) and index/member writes (mapping[k], struct.field) are
excluded to avoid false positives.
Every SSTORE costs at least 2,900 gas (warm slot) or 20,000 gas (cold slot). Writing a value to storage and then immediately overwriting it wastes that gas with no observable effect; only the final write matters.
contract C {
uint256 public x;
function bad(uint256 v) external {
x = 0; // write-after-write: this value is never read
x = v; // second write overwrites the first
}
}
contract C {
uint256 public x;
// Write once with the final value directly.
function good(uint256 v) external {
x = v;
}
// Reading between writes is fine.
function goodRead(uint256 v) external returns (uint256 prev) {
x = 1;
prev = x; // x is read here
x = v;
}
// Compound assignments read before writing, not flagged.
function goodCompound() external {
x = 1;
x += 1;
}
}