Damn Vulnerable DeFi — Real DeFi Exploits
After Ethernaut, Damn Vulnerable DeFi (DVDF) is where your training becomes serious. Created by tinchoabbate (The Red Guild), every challenge replicates a real DeFi vulnerability class that has caused actual losses. Where Ethernaut teaches individual opcodes and simple patterns, DVDF teaches you to think like a DeFi attacker — across protocols, across flash loans, across governance systems.
Why DVDF Is Harder Than Ethernaut
| Feature | Ethernaut | Damn Vulnerable DeFi |
|---|---|---|
| Difficulty | Beginner to intermediate | Intermediate to advanced |
| Protocol complexity | Single contracts | Multi-contract DeFi systems |
| Flash loans | Not required | Core mechanic in many levels |
| Capital required | None (simple calls) | Flash loans provide unlimited capital |
| Attack style | Single transaction usually | Complex multi-step transactions |
| Setup | Browser + MetaMask | Foundry (local) recommended |
| Real analogues | Historical bugs | Current DeFi attack patterns |
| Time per challenge | 30 min - 2 hrs | 2 hrs - 2 days |
DeFi Primitives You Must Know First
Before attempting DVDF, you need solid understanding of these DeFi building blocks:
1. AMMs (Automated Market Makers) — e.g., Uniswap
x * y = k (constant product formula)
- Liquidity providers deposit token pairs
- Price determined by pool ratio (not orderbook)
- Slippage: large trades move price unfavorably
- Oracle risk: price can be manipulated in a block
2. Lending Protocols — e.g., Aave, Compound
- Deposit collateral → borrow other assets
- Health factor = collateral value / borrowed value
- Liquidation if health factor drops below 1
- Interest rates: algorithmic, based on utilization
3. Flash Loans
- Borrow ANY amount with NO collateral
- Must repay principal + fee in the SAME transaction
- If not repaid: entire transaction reverts (no risk to protocol)
- Used legitimately for: arbitrage, collateral swaps, liquidations
- Used maliciously for: price manipulation, governance attacks
4. Governance Tokens
- Token holders vote on protocol changes
- Voting power proportional to token balance
- Flash loan + governance = massive voting power for one block
5. Price Oracles
- Smart contracts that report asset prices
- On-chain: derived from AMM pool ratios (manipulable)
- Off-chain: Chainlink (cryptographically secure, not manipulable)Flash Loans — Mechanics in Depth
Flash Loan Flow (Aave V3 pattern):
┌─────────────────────────────────────────────────────────┐
│ Single Transaction │
│ │
│ 1. Attacker calls flashLoan(amount) │
│ ↓ │
│ 2. Pool transfers 'amount' tokens to Attacker │
│ ↓ │
│ 3. Pool calls executeOperation() on Attacker │
│ ↓ │
│ 4. *** Attacker has the funds — do anything here *** │
│ - Manipulate price oracles │
│ - Vote in governance │
│ - Liquidate undercollateralized positions │
│ - Drain other contracts │
│ ↓ │
│ 5. Attacker repays amount + fee (0.05-0.09%) │
│ ↓ │
│ 6. Pool checks: repaid in full? YES → tx succeeds │
│ NO → entire tx REVERTS│
└─────────────────────────────────────────────────────────┘
Key insight: Between steps 3-5, attacker can do ANYTHING
with amounts far exceeding their capital (e.g., $1B flash loans exist)// Generic flash loan attacker (Aave V3 pattern)
contract FlashLoanAttacker is IFlashLoanSimpleReceiver {
address immutable POOL;
address immutable TOKEN;
constructor(address pool, address token) {
POOL = pool;
TOKEN = token;
}
function attack() external {
// Borrow as much as the pool has
uint256 amount = IERC20(TOKEN).balanceOf(POOL);
IFlashLoanPool(POOL).flashLoanSimple(
address(this), // receiver
TOKEN, // asset
amount, // amount
"", // params
0 // referral code
);
}
// Called by pool AFTER sending us the tokens
function executeOperation(
address asset,
uint256 amount,
uint256 premium, // fee amount
address, // initiator (ignored)
bytes calldata // params (ignored)
) external returns (bool) {
// *** YOUR ATTACK LOGIC HERE ***
// At this point we have 'amount' tokens
// Example: manipulate oracle, liquidate, drain victim
// Repay: approve pool to take back amount + fee
uint256 amountOwed = amount + premium;
IERC20(asset).approve(POOL, amountOwed);
return true; // Signal successful execution
}
}The 7 Most Important DVDF Challenges
1. Unstoppable — Flash Loan Griefing
The pool counts its token balance using a storage variable. But what if someone sends tokens directly to the contract (not through the deposit function)? The storage variable would no longer match the actual token balance. When the flash loan function checks the two values are equal, it reverts. A simple 1-token transfer breaks the protocol forever — this is a DoS via invariant violation, not a fund theft.
// The vulnerable check in UnstoppableVault:
uint256 balanceBefore = totalAssets();
// ... flash loan executes ...
if (convertToShares(totalSupply) != balanceBefore) revert InvalidBalance();
// If accounting variable != actual balance, every flash loan reverts
// Attack: transfer 1 token directly to the pool (bypassing deposit())
// Now: accounting says X, actual balance is X+1 → PERMANENT DoS2. Naive Receiver — Flash Loan Fee Drain
A receiver contract has ETH but its flash loan callback anyone can trigger. The fee is 1 ETH per flash loan call. The attacker keeps initiating flash loans on behalf of the victim receiver — each call costs the receiver 1 ETH in fees. After 10 calls, the receiver is drained. The vulnerability: the receiver doesn't check who initiated the flash loan.
3. Truster — Arbitrary Call Exploit
The flash loan pool allows the borrower to specify an arbitrary target and data for a call() after lending. The attacker uses this to make the POOL itself call approve(attacker, MAX_UINT) on the token. After the flash loan ends (and attacker repays), the attacker calls transferFrom(pool, attacker, pool_balance). The pool approved itself — now the attacker drains it in a second transaction.
// Pool allows arbitrary call in its name during flash loan:
pool.flashLoan(
0, // borrow 0 — we don't need the funds
attacker, // borrower address (doesn't matter)
address(token), // target: the token contract itself
abi.encodeWithSignature(
"approve(address,uint256)",
attacker,
type(uint256).max // Give attacker unlimited allowance FROM the pool
)
);
// Now pool has approved attacker to spend pool's tokens
// In next tx: token.transferFrom(pool, attacker, token.balanceOf(pool))4. Side Entrance — Flash Loan Reentrancy
The pool checks repayment by comparing ETH balance before and after the loan. During the flash loan callback, the attacker calls deposit() with the borrowed funds. This counts as "repayment" (balance is restored) but also credits the attacker's internal balance. After the flash loan, attacker calls withdraw() — draining the ETH that was just "deposited".
5. The Rewarder — Flash Loan + Reward Manipulation
The reward pool distributes rewards proportional to deposited tokens at snapshot time. The attacker flash loans a massive amount of tokens, deposits them to claim a huge share of rewards, collects rewards, withdraws, and repays the flash loan — all in one transaction. No capital needed to steal a majority of the reward pool.
6. Selfie — Governance Manipulation
The governance contract allows anyone with enough DVT tokens to queue an action. The attacker flash loans massive DVT, queues a drainAllFunds() governance proposal, repays the flash loan. 2 days later (after the governance delay), executes the proposal — draining the pool. Flash loans enable governance attacks even without actual token holdings.
7. Compromised — Oracle Price Manipulation
This challenge gives you encoded private keys in what appears to be a server response. These keys belong to the trusted oracle signers. With oracle signing keys, you can set the NFT price to 0 ETH, buy all NFTs cheaply, set price to max, sell them back, and drain the exchange. Real-world analogues: compromised price feed keys have caused massive oracle manipulation losses.
Setting Up DVDF with Foundry
# Clone the DVDF repository (Foundry version)
git clone https://github.com/tinchoabbate/damn-vulnerable-defi
cd damn-vulnerable-defi
# Install dependencies
forge install
# Verify tests run (all should FAIL initially — that's expected)
forge test
# Project structure:
damn-vulnerable-defi/
├── src/ # Vulnerable protocol contracts
│ ├── unstoppable/
│ ├── naive-receiver/
│ └── ...
├── test/ # Challenge setup + validation
│ ├── unstoppable/
│ │ └── Unstoppable.t.sol # Your solution goes here
│ └── ...
└── foundry.toml
# Test structure in each challenge file:
// setUp() — sets up the scenario (victims, funds, etc.)
// test_xxxx() — your exploit goes here
// _isSolved() — validation (must return true to pass)
# Run a specific challenge:
forge test --match-contract Unstoppable -vvvvHow to Approach a Challenge You're Stuck On
Step 1: READ THE WIN CONDITION FIRST
Open the test file and read _isSolved()
This tells you exactly what state you need to achieve
Example: attacker has all tokens + pool has 0
Step 2: MAP THE SYSTEM
List all contracts in this challenge
Identify: who holds what funds? what are the roles?
Draw a diagram if needed
Step 3: FIND ALL ENTRY POINTS
What external/public functions can you call?
What do they do to state?
Can any of them be called by anyone? (missing access control)
Step 4: LOOK FOR INVARIANT VIOLATIONS
What invariants should ALWAYS hold?
"Pool balance >= deposits"
"Reward proportional to deposit"
Can you violate one of these?
Step 5: CONSIDER FLASH LOANS
Do you need capital to break an invariant?
Can you borrow it via flash loan?
What's the sequence: borrow → exploit → repay?
Step 6: BUILD AND TEST IN FOUNDRY
Write your attacker contract
Call it from the test
Use console.log() to debug state at each step
forge test -vvvv shows full execution traceKey DeFi Vulnerability Categories in DVDF
| Category | DVDF Levels | Real-World Example | Typical Loss |
|---|---|---|---|
| Flash Loan + Governance | Selfie, Free Rider | Beanstalk ($182M, 2022) | $50M-$200M |
| Price Oracle Manipulation | Compromised, Puppet | Mango Markets ($100M, 2022) | $50M-$100M |
| Reentrancy | Side Entrance | The DAO ($60M, 2016) | $10M-$100M |
| Arbitrary External Calls | Truster | Euler Finance ($197M, 2023) | $50M-$200M |
| Accounting Inconsistency | Unstoppable | Many DeFi protocols | $1M-$50M |
| Fee Griefing | Naive Receiver | Relay contract exploits | $1M-$10M |
| Flash Loan + Rewards | The Rewarder | Various yield protocols | $5M-$50M |
Common Mistakes Section
DVDF assumes you understand reentrancy, visibility, modifiers, and basic Solidity patterns. If you are jumping straight into DVDF without Ethernaut, you will struggle unnecessarily. Complete at least the first 10 Ethernaut levels before starting DVDF.
If your flash loan callback reverts for any reason — a failed call, a failed assertion, running out of gas — the ENTIRE transaction reverts including the flash loan itself. This means you need to test your exploit in full sequence in Foundry before committing to it. Use try/catch carefully and check all return values inside the callback.
Each DVDF challenge involves multiple contracts. Read ALL of them before writing any exploit code. The vulnerability is usually in an interaction between two contracts, not within a single contract. Missing the context of how contracts interact with each other leads to hours of wrong-direction exploration.
DVDF teaches you techniques that are directly applicable to real DeFi attacks. These skills exist to help you find and report vulnerabilities (through bug bounty programs) — not to exploit them for personal gain. Exploiting live DeFi protocols is illegal in most jurisdictions. Use these skills ethically: report bugs, earn bounties, protect users.
Summary / Key Takeaways
| Concept | Key Lesson | How to Apply |
|---|---|---|
| Flash loans | Unlimited capital for one transaction | Always ask: "can a flash loan fund this attack?" |
| Price oracles | On-chain prices are manipulable | Check if protocol uses Chainlink or spot price |
| Governance attacks | Flash loans = instant voting power | Look for governance with no time-weighted balances |
| Accounting invariants | Internal accounting must match reality | Check all paths that change balances |
| Arbitrary calls | Unvalidated calls let attacker take control | Any low-level .call() with attacker-controlled data is suspect |
| DVDF methodology | Read win condition → map system → find invariant violation | Apply to real audits too |