Forced Ether sending exploits contracts that assume they control their balance exclusively. Attackers force ETH into contracts using selfdestruct, breaking balance-dependent logic. While similar to selfdestruct abuse, forced Ether sending specifically targets contracts that depend on ETH balance for functionality. Firepan's HOUND AI detects forced Ether sending vulnerabilities automatically during development and post-deployment monitoring, covering continuous analysis across the full contract lifecycle.
Forced Ether sending occurs when attackers send ETH to contracts without calling any function, breaking assumptions about balance control:
The vulnerability is distinct from selfdestruct abuse in that:
Forced Ether sending exploitation involves:
// VULNERABLE — example only
// Demonstrates: Forced Ether Sending
// Do NOT use in production
pragma solidity ^0.8.0;
contract VulnerableGame {
mapping(address => uint256) public bets;
uint256 public totalBets;
uint256 public prize;
function placeBet() public payable {
require(msg.value > 0);
require(msg.value <= address(this).balance); // VULNERABLE!
bets[msg.sender] = msg.value;
totalBets += msg.value;
}
// VULNERABLE: Uses balance for game logic
function finalizeBetting() public {
require(address(this).balance == totalBets, "Balance mismatch");
// If attacker force-sends ETH, this fails
// Bets can't be finalized, funds locked
}
// VULNERABLE: Prize distribution depends on balance
function claimPrize(address winner) public {
// Prize = balance - totalBets
uint256 prizeAmount = address(this).balance - totalBets;
// If attacker force-sends ETH, extra funds go to last winner
(bool success, ) = winner.call{value: prizeAmount}("");
require(success);
}
}
contract VulnerableAuction {
address public highestBidder;
uint256 public highestBid;
bool public finalized;
function bid() public payable {
require(msg.value > highestBid);
require(msg.value <= address(this).balance); // VULNERABLE!
highestBid = msg.value;
highestBidder = msg.sender;
}
function finalize() public {
finalized = true;
// Send proceeds minus 10% fee
uint256 proceeds = address(this).balance - (address(this).balance / 10);
(bool success, ) = highestBidder.call{value: proceeds}("");
require(success);
}
}
contract ForcedEtherAttacker {
VulnerableGame public target;
function forceEther() public payable {
// Create selfdestruct contract with ETH
EtherForcer forcer = new EtherForcer{value: msg.value}(address(target));
forcer.force();
}
receive() external payable {}
}
contract EtherForcer {
address public target;
constructor(address _target) {
target = _target;
}
function force() public {
selfdestruct(payable(target)); // Force ETH to target
}
}
// Real vulnerability: Lottery protocol
contract LotteryWithForcedEther {
uint256 public roundBalance;
mapping(address => uint256) public ticketCounts;
function buyTicket() public payable {
require(msg.value == 0.01 ether);
roundBalance += msg.value;
ticketCounts[msg.sender]++;
}
// VULNERABLE: Draw depends on exact balance
function drawWinner() public {
require(address(this).balance == roundBalance); // Forced ETH breaks this!
// If attacker force-sends 1 wei, roundBalance != balance
// Draw can't proceed, lottery is bricked
}
}
| Protocol | Date | Loss | Root Cause | |----------|------|------|-----------| | Multiple Lotteries | 2018-2021 | $10M+ | Balance assumptions broken via selfdestruct | | Game Contracts | 2020-2023 | $5M+ | Game logic depended on exact balance |
Manual detection requires balance assumption review:
Red flags:
Firepan's HOUND AI analyzes balance assumptions:
Firepan's HOUND AI engine identifies forced Ether vulnerabilities across all monitored contracts.
1. Track Deposits Separately
Maintain totalDeposits variable instead of relying on balance:
// SECURE: Track deposits separately
uint256 public totalDeposits;
function deposit() public payable {
totalDeposits += msg.value;
}
function withdraw() public {
uint256 amount = balances[msg.sender];
balances[msg.sender] = 0;
totalDeposits -= amount;
(bool success, ) = msg.sender.call{value: amount}("");
require(success);
}
2. Use >= Instead of == for Balance Checks
Accept balance >= expected, not exact match:
// VULNERABLE
require(address(this).balance == totalBets);
// SECURE
require(address(this).balance >= totalBets);
3. Implement Receive Function
Handle unexpected ETH gracefully:
// SECURE: Accept unexpected ETH
receive() external payable {
// Extra funds go to contract
// Don't break logic; just account for them
}
4. Use Reserve Pattern
Distinguish between reserved (for operations) and excess funds:
// SECURE: Separate reserved and excess
uint256 public reserved; // Amount needed for operations
function finalize() public {
// Use reserved amount, excess is extra
uint256 excess = address(this).balance - reserved;
if (excess > 0) {
// Handle excess safely
}
}
5. Never Depend on Exact Balance
Always verify actual deposits match balance:
// SECURE: Always validate assumptions
function checkInvariant() internal view {
assert(address(this).balance >= totalDeposits);
}
Q: What is forced Ether sending in smart contracts?
A: Forced Ether sending exploits contracts that assume they exclusively control their balance. Attackers force ETH into contracts using selfdestruct, breaking balance-dependent logic and potentially locking funds or breaking game mechanics.
Q: Which protocols have been exploited via forced Ether?
A: Multiple lottery and game contracts (2018-2023) lost $10M+ to balance-assumption breaks. Attackers forced ETH via selfdestruct, preventing draws or game finalization, locking funds.
Q: How does Firepan detect forced Ether sending?
A: Firepan identifies balance checks, analyzes assumptions, flags == comparisons, confirms deposits are tracked separately, identifies game logic depending on balance, and tests invariant violations.
Q: Can forced Ether sending be exploited after deployment?
A: Yes, forced Ether sending is immediately exploitable post-deployment. Any contract assuming exclusive balance control is vulnerable to selfdestruct-based forced Ether attacks.
Q: How do I prevent forced Ether sending?
A: Track deposits separately (totalDeposits). Use >= instead of == for balance checks. Implement receive() for unexpected ETH. Use reserve pattern to distinguish intended vs excess funds. Never depend on exact balance.
Forced Ether sending breaks balance assumptions in lotteries and game contracts, potentially locking millions. Over $15M has been lost to balance-assumption vulnerabilities. The fix is straightforward: track deposits separately and use >= instead of == for balance checks. Firepan's HOUND AI detects balance-dependent logic vulnerable to forced Ether across all monitored contracts.
Start securing your smart contracts at https://app.firepan.com/
Firepan
12,453 contracts secured. 2,851 vulnerabilities blocked. 236 exploits prevented. Run a free surface scan — results in minutes, no credit card required.
Run Free Scan →