Denial of Service in Smart Contracts
A contract doesn't need to be drained to be broken. DoS vulnerabilities make functions permanently uncallable — locking user funds, freezing governance, or halting an entire protocol — without a single token leaving.
Three ways DoS kills contracts
Unbounded loops
A loop over an array that anyone can grow will eventually exceed the block gas limit. Once the array is long enough, the function can never complete — it always runs out of gas.
Push payment trap
When a contract sends ETH to a list of recipients in a single transaction, any one failed transfer (e.g., to a contract without a receive() function) blocks all remaining payouts.
External call reverts blocking state
If a contract calls an external address and that call reverts, the whole transaction rolls back. An attacker who controls that address can permanently prevent state changes from happening.
Real-world incidents
| Protocol | Year | Pattern | Impact |
|---|---|---|---|
| GovernMental | 2016 | Unbounded loop in jackpot payout | 1,100 ETH locked (recovered via mining) |
| King of the Ether | 2016 | Push payment to contract without receive() | Refund permanently stuck |
| Auction contracts (generic) | Ongoing | refundAll() loop over bidder array | Funds locked when array grows large |
| DAO-style voting contracts | Ongoing | Vote tallying over unbounded voter list | executeProposal() becomes uncallable |
Related vulnerabilities
Reentrancy Attacks
External calls that trigger callbacks before state is updated — a different but related control-flow exploit.
Integer Overflow
Arithmetic wrapping that can corrupt counters used inside loops, enabling unexpected behavior.
Unchecked Return Values
Ignoring failed call() return values leads to silent payment failures that corrupt contract accounting.