Skip to content

Unexpected Ether Transfers (Force Feeding)

Forcing a smart contract to hold an Ether balance influences its internal accounting and security assumptions. A smart contract receives Ether through the following hierarchy:

  1. Check whether a payable external receive function is defined.
  2. If not, check whether a payable external fallback function is defined.
  3. Revert.

The Solidity by Example article illustrates the precedence:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
Which function is called fallback() or receive()?

           send Ether
               |
         msg.data is empty?
              / \
            yes  no
            /     \
receive() exists?  fallback()
         /   \
        yes   no
        /      \
    receive()   fallback()

Consider the following example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
pragma solidity ^0.8.17;

contract Vulnerable {
    receive() external payable {
        revert();
    }

    function somethingBad() external {
        // @audit the contract balance is treated as a trustworthy guard
        require(address(this).balance > 0);
        // Do something bad
    }
}

The logic seemingly disallows direct payments and prevents "something bad" from happening. Calling revert in both fallback and receive nonetheless cannot prevent the contract from receiving Ether. Two techniques force-feed Ether to a smart contract.

Selfdestruct

When the SELFDESTRUCT opcode is called, funds of the calling address are sent to the address on the stack, and execution halts immediately. The opcode works at the EVM level, so Solidity-level functions that would block the receipt of Ether are not executed.

Pre-calculated Deployments

The target address of a newly deployed smart contract is generated deterministically. The address generation is visible in any EVM implementation, such as the py-evm reference implementation by the Ethereum Foundation:

1
2
def generate_contract_address(address: Address, nonce: int) -> Address:
    return force_bytes_to_address(keccak(rlp.encode([address, nonce])))

An attacker can send funds to this address before the deployment happens. A 2017 Underhanded Solidity Contest submission demonstrates the technique.

Mitigation

Exact comparisons against the contract's Ether balance are unreliable. Business logic has to account for an actual balance higher than the value tracked by internal accounting. The contract balance is not a trustworthy guard.