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.
https://www.damnvulnerabledefi.xyz/challenges/truster/
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.
Free flash loans from a pool of 1 million tokens? Count me in… even if I have nothing. 🤔
Let’s take a look at the contract we are given:
There is one function flashLoan()
and the contract is protected from re-entrancy.
Reading through the contract we can infer a few things;
1. token
is DamnValuableToken (an ERC20 which we have none of and the pool has 1 million of)
2. flashLoan
function takes 4 arguments: amount, borrower address, target address, calldata data
.
3. Upon calling the function it sets balanceBefore
= DVT balance of this address (1 million tokens as the description told us)
4. Then it makes a functionCall
to the target address
, passing the calldata
.
5. Lastly it checks that the DVT balanceOf
this address is not less than the balanceBefore
.
There are no checks on the borrower address, target address, amount, or calldata
, therefor our exploit should lie in the way we use these arguments.
Let’s look up functionCall
from OpenZeppelin’s Address.sol that is imported at the top of the file.
The following is an excerpt cut from Lines 71 to 92, that describes the function and shows it’s code.
From the descriptor, target must be a contract and the call we make to it must not revert. Simple enough.
It also mentions we need to use abi.decode
to convert the raw data to the expected return value.
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
Let’s craft our flashLoan call. Remember our function looks like so; flashLoan(uint256 amount, address borrower, address target, bytes calldata data)
amount is going to be 0 since we don’t currently hold any DVT,
borrower will be our address as we are the one making the call,
target will be the DVT token contract address,
lastly the calldata needs to contain the function that we want to call and the data to pass to it.
The way we are going to approach this is by using the ERC20 function .approve(spender,amount)
where we are the spender and the amount is the pool’s balance, approving us to transfer all these tokens to our self.
To do this in 1 transaction let’s create a new contract to attack with, I just added this in the same file after the TrusterLenderPool contract.
Then move to our challenge.js file to deploy this new contract and make a call to our fakeLoan() function.
All that’s left to do is yarn run truster
to ensure our solution worked.
Congratulations, we just solved challenge #3 Truster.
DAVE