Skip to main content
Sigvex

Ether Balance Assumption Remediation

Track deposits with internal accounting instead of relying on address(this).balance, which can be inflated by forced ETH.

Ether Balance Assumption Remediation

Overview

A contract that treats address(this).balance as authoritative state assumes the balance only changes through its own payable functions. That assumption is false: ETH can be forced into any contract via selfdestruct, block coinbase rewards, or pre-funding before deployment. A strict equality check against an expected balance can be permanently broken by sending a single wei, creating a low-cost denial-of-service.

Related Detector: Ether Balance Assumption

Before (Vulnerable)

function totalDeposits() public view returns (uint256) {
    return address(this).balance; // Can be inflated by selfdestruct
}

After (Fixed)

mapping(address => uint256) public deposits;
uint256 public totalDeposited;

function deposit() external payable {
    deposits[msg.sender] += msg.value;
    totalDeposited += msg.value;
}

function totalDeposits() public view returns (uint256) {
    return totalDeposited;
}

Internal accounting records exactly what each caller deposited through controlled entry points. Forcibly sent ETH increases the raw balance but never touches the bookkeeping variables, so invariants and withdrawal math stay correct regardless of external interference.

Alternative Mitigations

  • If you must read the raw balance, never use it in a strict equality (==) invariant. A >= threshold tolerates surplus ETH without bricking.
  • Provide a sweep function so any unexpected surplus can be recovered rather than locked.
  • Validate deposit amounts against msg.value at the entry point instead of inferring them from balance deltas.

Common Mistakes

  • Replacing == with >= but still deriving deposit totals from the balance, which remains vulnerable to inflation attacks.
  • Assuming selfdestruct is fully removed; forced-send vectors still exist on many chains and through pre-funding.
  • Computing per-user shares from address(this).balance, letting an attacker dilute or inflate accounting with a tiny forced transfer.

References