Typographical Error Remediation
Overview
A typo that compiles is a logic bug. The detector flags three shapes: assignment where accumulation was intended (= / legacy =+ instead of +=), identity assignments (x = x), and consecutive operations that cancel out. The fix itself is a one-character change; the real remediation work is confirming which behavior was intended and adding a test that would have caught the difference.
Related Detector: Typographical Error
Recommended Fix
Before (Vulnerable)
mapping(address => uint256) public balances;
function deposit() external payable {
// Overwrites the balance — a second deposit erases the first
balances[msg.sender] = msg.value;
}
After (Fixed)
mapping(address => uint256) public balances;
function deposit() external payable {
balances[msg.sender] += msg.value;
}
The distinction is observable on-chain: the vulnerable version discards the previous balance, so a user depositing twice loses the first amount into the contract’s unaccounted balance. A conservation invariant makes the bug untestable-around:
// Invariant test: sum of credited balances equals total ETH received
function invariant_conservation() public view {
assertEq(sumOfBalances(), address(this).balance);
}
For identity assignments, find the name that was actually meant — owner = owner; in a transferOwnership(address newOwner) body should be owner = newOwner;. The dropped parameter is the tell.
Alternative Mitigations
- Fuzz and invariant testing: property-based tests over deposit/withdraw sequences catch overwrite-vs-accumulate errors that single-call unit tests miss.
- Source linting in CI: Solidity 0.5.0+ rejects the literal
=+token sequence, and standard linters flag self-assignments; keep both in the pipeline so the mistake never reaches review. - Peer review focused on state writes: review every
=against the question “should any prior value survive this write?”
Common Mistakes
- Fixing the operator without checking history. If the buggy version was live, users may have lost balances already; the fixed contract may need a migration or reimbursement path, not just correct future behavior.
- Assuming the assignment was the typo. Sometimes the overwrite is correct and the variable name is wrong — a setter writing to the wrong slot looks identical in a diff. Confirm intent before choosing which side to change.
- Silencing the finding with a redundant read (
x = x + 0) instead of fixing the logic. The detector’s job is to point at the suspicious write; make the write meaningful or remove it.