Denial of Service

Force Feeding

Medium

Force-Feeding (Unexpected Ether)

A contract cannot reliably refuse to receive Ether. Logic that assumes address(this).balance only changes through the contract's own payable entry points—or that the balance can be held at an exact value such as 0—can be broken by an attacker who forcibly sends Ether to the contract. Relying on the contract's Ether balance as a guard or for internal accounting is therefore unsafe (see SWC-132).

Normally, a contract receives Ether through the following hierarchy:

  1. If msg.data is empty and a payable receive() function exists, it is called.
  2. Otherwise the payable fallback() function is called.
  3. If neither can accept the Ether, the call reverts.

So it would appear that a contract can block all incoming Ether simply by reverting in receive/fallback:

solidity
// INSECURE pragma solidity ^0.8.13; contract Vulnerable { receive() external payable { revert(); // "we never accept Ether" } function somethingBad() external { require(address(this).balance > 0); // assumed unreachable // ...do something that should be impossible... } }

This guard is an illusion. The following techniques bypass Solidity-level checks entirely.

Selfdestruct

When the SELFDESTRUCT opcode runs, the destroyed contract's remaining funds are forcibly sent to a target address. This happens at the EVM level, so the target's receive/fallback logic is never executed and cannot revert. An attacker can deploy a contract, fund it, and selfdestruct it with your contract as the beneficiary.

Pre-calculated deployments

A contract's deployment address is deterministic—keccak256(rlp([deployer, nonce])) for CREATE, or a function of the salt and init code for CREATE2. An attacker can compute the address of a contract before it is deployed and send Ether to it in advance, so the contract already holds a balance the moment it comes into existence (defeating a "balance must be 0 on first deposit" assumption). This was famously demonstrated in a 2017 Underhanded Solidity Contest submission.

Block rewards / coinbase

A miner/validator can set a contract's address as the coinbase (fee recipient) for a block, crediting it block rewards and fees directly. Again, this is an EVM-level credit that no Solidity check can intercept.

Solution

Exact comparisons against address(this).balance are unreliable. Track deposits in an internal accounting variable that you increment yourself, and reason about the real balance being greater than or equal to the value you expect—never exactly equal.

solidity
contract Safe { uint256 internal totalDeposited; // trusted internal accounting function deposit() external payable { totalDeposited += msg.value; } // Use `totalDeposited`, not address(this).balance, for any logic or invariants. }

In general, do not use the contract's Ether balance as a guard. A forcibly increased balance can also be used to trigger unexpected reverts in logic that requires an exact balance.

Sources