Not every smart contract attack steals funds. Some attacks are designed to make contracts unusable — freezing withdrawals, blocking governance votes, or making gas costs prohibitively expensive. Gas griefing and DoS attacks have frozen millions in protocol funds and disrupted operations for days.
How Gas Griefing Works
Every Ethereum transaction has a gas limit. If a function requires more gas than the block gas limit (30M gas), it can never be executed. Attackers exploit this by making functions consume more gas than available.
Unbounded Loop Attack
// VULNERABLE: Iterates over all depositors
function distributeRewards() external {
for (uint i = 0; i < depositors.length; i++) {
// If depositors.length grows to 10,000+
// this exceeds block gas limit and ALWAYS reverts
payable(depositors[i]).transfer(rewards[depositors[i]]);
}
}
// Attacker: create thousands of tiny deposits to bloat the array
External Call Griefing
// VULNERABLE: Recipient can consume all forwarded gas
function sendPrize(address winner) external {
(bool success, ) = winner.call{value: prize}("");
require(success); // Malicious winner always reverts → nobody gets prize
}
Types of Gas-Based DoS
| Attack Type | Mechanism | Impact |
|---|---|---|
| Unbounded loops | Grow array until iteration exceeds block gas | Function permanently blocked |
| External call revert | Malicious recipient always reverts | Blocks all subsequent users |
| Storage bloat | Fill storage slots to increase gas costs | Operations become unaffordable |
| Gas token minting | Consume gas in callbacks to drain forwarded gas | Parent transaction fails |
Prevention Patterns
// SECURE: Pull-over-push + bounded iteration
mapping(address => uint256) public pendingRewards;
function claimReward() external {
uint256 reward = pendingRewards[msg.sender];
pendingRewards[msg.sender] = 0;
(bool success, ) = msg.sender.call{value: reward}("");
require(success);
}
// Batch processing with pagination
function processRewards(uint256 start, uint256 count) external {
uint256 end = start + count;
if (end > depositors.length) end = depositors.length;
for (uint i = start; i < end; i++) {
pendingRewards[depositors[i]] += calculateReward(depositors[i]);
}
}
- ✅ Use pull-over-push for payments — let users withdraw, don't push to them
- ✅ Avoid unbounded loops — use pagination or off-chain computation
- ✅ Set gas limits on external calls:
call{gas: 10000, value: amount}("") - ✅ Don't rely on
require(success)for non-critical external calls - ✅ Implement circuit breakers for gas-intensive operations
How Vultbase Detects Gas Griefing
- Slither — Flags unbounded loops, external calls in loops, and missing gas limits
- Pattern DB — 13 gas griefing patterns from real DoS incidents
- Challenge Execution — Tests functions with adversarial inputs that maximize gas consumption
A DoS attack doesn't steal funds but can freeze them forever. Audit your gas-sensitive functions before an attacker makes them unusable.