Smart Contract Security

Gas Griefing and Denial of Service: When Smart Contracts Run Out of Gas

Kennedy OwiroJanuary 19, 20268 min read

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 TypeMechanismImpact
Unbounded loopsGrow array until iteration exceeds block gasFunction permanently blocked
External call revertMalicious recipient always revertsBlocks all subsequent users
Storage bloatFill storage slots to increase gas costsOperations become unaffordable
Gas token mintingConsume gas in callbacks to drain forwarded gasParent 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

  1. Slither — Flags unbounded loops, external calls in loops, and missing gas limits
  2. Pattern DB — 13 gas griefing patterns from real DoS incidents
  3. 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.

gas griefingdenial of serviceDoSblock gas limitsmart contract securityunbounded loops
Share

Written by

Kennedy Owiro

Founder & CTO, Vultbase

14+ years building security and QA systems at scale. Background in fintech security and Web3 smart contract testing. Built Vultbase's Intelligence Engine with 1,200+ exploit patterns from $40B+ in historical DeFi losses.

Protect your protocol before launch.

Submit your smart contracts for automated security analysis powered by 1,200+ real exploit patterns.

Start Your Audit →