Forcing a smart contract to hold an Ether balance can influence its internal accounting and security assumptions. There are multiple ways a smart contract can receive Ether. The hierarchy is as follows:
- Check whether a payable external
receive
function is defined. - If not, check whether a payable external
fallback
function is defined. - Revert.
The precedence of each function is explained in this great graphic from the Solidity by Example article:
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:
pragma solidity ^0.8.13;
contract Vulnerable {
receive() external payable {
revert();
}
function somethingBad() external {
require(address(this).balance > 0);
// Do something bad
}
}
The contract's logic seemingly disallows direct payments and prevents "something bad" from happening.
However, calling revert
in both fallback
and receive
cannot prevent the contract from receiving Ether.
The following techniques can be used to force-feed Ether to a smart contract.
When the SELFDESTRUCT
opcode is called, funds of the calling address are sent to the address on the stack, and execution is immediately halted.
Since this opcode works on the EVM-level, Solidity-level functions that might block the receipt of Ether will not be executed.
Additionally, the target address of newly deployed smart contracts is generated in a deterministic fashion. The address generation can be looked up in any EVM implementation, such as the py-evm reference implementation by the Ethereum Foundation:
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 has happened. This is also illustrated by this 2017 Underhanded Solidity Contest submission.
Depending on the attacker's capabilities, they can also start proof-of-work mining.
By setting the target address to their coinbase
, block rewards will be added to its balance.
As this is yet another EVM-level capability, checks performed by Solidity are ineffective.
The above effects illustrate that relying on exact comparisons to the contract's Ether balance is unreliable. The smart contract's business logic must consider that the actual balance associated with it can be higher than the internal accounting's value.
In general, we strongly advise against using the contract's balance as a guard.
More information can be found in SWC-132.