Damn Vulnerable DeFi - Challenge #3 - Truster
1) Challenge
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.
Currently the pool has 1 million DVT tokens in balance. And you have nothing.
But don’t worry, you might be able to take them all from the pool. In a single transaction. (link)
Challenge created by @tinchoabbate.
2) Code Review
Let’s start by analyzing how the flash loads are provided.
TrusterLenderPool
contract (source code).
|
|
The flashLoan
function:
- checks if the token balance is
>=
the amount we want to borrow (line33
) - transfers the borrowed amount to the borrower
- calls
functionCall
from theAddress
library on a target address by providing data bytes as a parameter (line36
).flashLoan
expects thatfunctionCall
will call another function contract that will be responsible for paying back the flash loan - checks the amount is paid back (line
39
)
Our goal is to take all the 1 million DVT
from the pool.
functionCall
?When calling functionCall
, the msg.sender
will be the pool contract. Also, since we can control the target address and the data to be sent (target
and data
parameters, respectively), we can call any function on any contract and have the msg.sender
as the pool contract.
We can use the pool to call any contract function, which means we can also call functions on the DVT token itself. For example, we can call transfer
from the ERC-20 token (the target
address) and sends all its balance to our contract/address.
Unfortunately, the function will fail because the condition at line 39
will be false (the balacenceAfter
will be less that the original balance) ðŸ˜.
Remember challenge 1 (if not here is the link)? transfer
is not the only function to transfer tokens, there is also transferFrom
. However, before calling transferFrom
, we need to first approve
the spender to spend the caller tokens.
In order to get all the tokens from the pool, we can call the flashLoan
function with the following parameters:
- set the
borrowAmount
equal0
(there are no checks on theborrowAmount
), so there will be no token transfer and all therequire
in the function will be satisfied - set the
borrower
to be the attacker contract - set the
target
address to be the DVT address, so that the pool will call itsapprove
function - set the
data
to be the encoding of theapprove
function withtarget
address set to the attacker address and theamount
to be the pool balance
Finally, since we have have been approved by the pool to spend all its token, we can call the transferFrom
and transfer all the token’s pool to us.
3) Solution
Like the previous challenge, this one can be solved in multiple ways.
3.1) Solution 1: multiple transactions
We can implement the above steps by executing 2
transactions: one for calling the flashLoan
and one for transferFrom
.
truster.challenge.js
:
|
|
3.2) Solution 2: single transaction
The same solution can be executed in a single transaction by using an attack contract. In this case we will first approve our contract to spend the pool tokens and then we will transfer them to our address.
AttackTruster.sol
|
|
truster.challenge.js
:
|
|
You can find the complete code here and here.