As currently designed, the protocol allows any user to take a flashloan of an allowed token by calling the flashloan
public function. The amount available to borrow for each call to flashloan
function is IERC20(token).balanceOf(address(assetToken))
(as AssetToken
contract holds the underlying tokens). The amount to borrow will be sent to a contract that implements executeOperation(address,uint256,uint256,address,bytes)
function. This function is then allowed to do whatever it wants, and must only send back borrowed tokens + fee (0.3% of the total borrowed amount) to address(assetToken)
in order to satisfy the following condition :
This can be achieved by calling repay
function, which can only be called during a flashloan transaction, and executes the safeTransferFrom
function. An approval must be done before calling repay
function.
Nevertheless, it is also possible to call deposit
function during the flashloan, achieving reimbursement but minting LP tokens in the process.
Let's consider the following scenario :
1000 tokens have been deposited into ThunderLoan
contract after the owner of the contract allowed this token in the protocol. For now, no flashloan has been executed for this token.
You deploy a contract to attack the protocol. This contract will call the flashloan
function, requesting the maximum available amount of token to borrow, which is 1000. flashloan
function will then callback executeOperation
function on your attacker contract, which will call deposit
function, sending 1003 tokens through it. This means your contract needs to hold at least 0.3% of the total amount of token you borrow through flashloan
for this attack to succeed. deposit
function will set mintAmount
to 1003 tokens (or 1003 * 1e18 with decimal precision), as STARTING_EXCHANGE_RATE = 1e18
. Ultimately, assetToken.mint(msg.sender, mintAmount)
will mint you 1003 LP tokens, and you will send back your tokens with token.safeTransferFrom(msg.sender, address(assetToken), amount);
(exactly the same line as in repay
function). This way, flashloan
initial call will succeed.
The result of this attack is :
1000 tokens held by AssetToken contract
2003 LP assetToken existing, and you own 1003 of them
Attacker can then call redeem
function to get back the underlying tokens at the current exchange rate.
This attack can be repeated indefinitely, even if flashloans already occurred. Anyone can proceed to this attack, and the cost of it is very low (0.3% of the borrowed amount).
The severity of this finding is critical as any attack of that kind would result in lost funds for the protocol, and ultimately for users who deposited in the protocol. Each attack will dilute all LP holders.
Manual and Foundry
I suggest to make sure deposit
function of ThunderLoan cannot be called during a flash loan. This way, no one could use borrowed tokens to mint LP tokens representing.
Adding :
after declaring the custom error error ThunderLoan__depositNotAllowedDuringFlashLoan();
would prevent this attack from happening.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.