Part 1 of the Smart Contract Security: Solodit Checklist Series
Introduction: The Hidden Threat of DoS in Smart Contracts
Imagine launching a decentralized auction platform on Ethereum. Users are bidding, funds are flowing—until a malicious actor disrupts everything with thousands of micro-bids. Suddenly, refunds fail, funds lock, and your project's reputation crumbles.
This is the impact of a Denial-of-Service (DoS) attack—a threat that doesn’t steal funds but renders smart contracts unusable by exploiting Ethereum’s gas limits (currently ~30M gas per block as of June 2025).
In this guide, we’ll explore:
- Real-world DoS attack examples (e.g., Fomo3D).
- Vulnerable vs. secure code patterns.
- Mitigation strategies like pull-over-push and gas-efficient design.
- Advanced tools (Slither, MythX, Foundry) for resilience.
Why DoS Attacks Matter
EVM operations consume gas. When transactions exceed block gas limits, they fail. Attackers exploit this to:
- Lock funds: Cripple withdrawals or refunds.
- Waste gas: Failed transactions still burn gas.
- Damage reputations: dApps become unreliable.
Case Study: Fomo3D (2018)
Attackers spammed the game with tiny bids, inflating state size. Gas-intensive functions (e.g., prize distribution) exceeded limits, delaying payouts.
How DoS Attacks Work
Primary Vectors (Solodit SOL-AM-DoS-1)
- Unbounded Loops: Iterating dynamic arrays/mappings without gas constraints.
- External Call Failures: Reverts from external contracts halt execution.
Vulnerable Code Example: Auction Contract
contract VulnerableAuction {
address[] public bidders; // Dynamic array risk!
function refundAll() external {
for (uint i = 0; i < bidders.length; i++) { // Unbounded loop!
(bool success, ) = bidders[i].call{value: bids[i]}("");
require(success, "Refund failed"); // Single failure blocks all!
}
}
}Attack Scenarios:
- Gas Limit Abuse: Thousands of bids make
refundAll()exceed 30M gas. - Malicious Receiver: A contract reverting on
receive()halts all refunds.
Solution: Pull-Over-Push Pattern
Secure Contract:
contract FixedAuction {
mapping(address => uint256) public refunds; // Mappings over arrays!
function withdrawRefund() external {
uint256 amount = refunds[msg.sender];
refunds[msg.sender] = 0;
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Refund failed"); // Isolated failure!
}
}Benefits:
- No unbounded loops.
- Gas costs distributed to users.
- Malicious actors only affect themselves.
👉 Learn more about gas optimization
Additional DoS Mitigations
External Calls: Limit gas and use circuit breakers.
(bool success, ) = recipient.call{value: amount, gas: 50000}("");- State Bloat: Cap data structure sizes (e.g.,
MAX_BIDDERS = 1000). - Gas Griefing: Monitor with tools like Forta.
Best Practices for DoS-Resistant Contracts
- Prefer mappings over arrays for O(1) access.
- Batch operations: Process data in chunks.
- Implement pull patterns: Let users claim funds.
- Add circuit breakers: Pause functions during attacks.
- Test extensively: Use Foundry (
forge test --gas-report) and Echidna.
👉 Explore advanced security tools
Advanced Tools (2025 Update)
- Slither: Detects unbounded loops (
slither --detect high-gas). - MythX: Identifies gas-intensive paths.
- Foundry: Simulates mainnet gas limits via forks.
FAQ
Q: Can DoS attacks steal funds?
A: No, but they lock funds by disabling critical functions.
Q: How do I test for DoS vulnerabilities?
A: Use Foundry to simulate large datasets (forge test --gas-report) and Echidna for fuzzing.
Q: Are pull patterns always the solution?
A: Mostly, but combine with gas caps and circuit breakers for robustness.
Conclusion
DoS attacks exploit Ethereum’s gas mechanics to disrupt contracts. By adopting pull-over-push, gas-efficient design, and tools like Slither, developers can build resilient systems.
Next in Series: Part 2: Defending Against Reentrancy Attacks!
👉 Master smart contract security
### Keywords:
- Denial-of-Service (DoS)
- Gas optimization
- Pull-over-push pattern
- Solodit checklist
- Smart contract security
- Ethereum gas limit
- Fomo3D attack