The ThunderLoan contract assume that if the contract gets the fee from the FlashLoan action, The fee will share to all liquidity providers. In deposit function, there is no fee get yet, so it is error for Doing updateExchangeRate action.
The updateExchangeRate action in deposit function will cause s_exchangeRate getting bigger and bigger. Then depositers will get more asset than they deposit in the protocol, even though there is no FlashLoan action.
The depositers can get more assets if they redeem firstly. Some of depositers can not get their asserts back forever.
pragma solidity 0.8.20;
import { Test, console } from "forge-std/Test.sol";
import { BaseTest, ThunderLoan } from "./BaseTest.t.sol";
import { AssetToken } from "../../src/protocol/AssetToken.sol";
import { MockFlashLoanReceiver } from "../mocks/MockFlashLoanReceiver.sol";
contract ThunderLoanTestBugs is BaseTest {
uint256 constant AMOUNT = 10e18;
uint256 constant DEPOSIT_AMOUNT = AMOUNT * 100;
address liquidityProvider = address(123);
address liquidityProvider2 = address(124);
address user = address(456);
MockFlashLoanReceiver mockFlashLoanReceiver;
function setUp() public override {
super.setUp();
vm.prank(user);
mockFlashLoanReceiver = new MockFlashLoanReceiver(address(thunderLoan));
}
modifier setAllowedToken() {
vm.prank(thunderLoan.owner());
thunderLoan.setAllowedToken(tokenA, true);
_;
}
function testDepositMintsAssetAndRedeem() public setAllowedToken {
tokenA.mint(liquidityProvider, AMOUNT);
vm.startPrank(liquidityProvider);
tokenA.approve(address(thunderLoan), AMOUNT);
thunderLoan.deposit(tokenA, AMOUNT);
vm.expectRevert(bytes("ERC20: transfer amount exceeds balance"));
thunderLoan.redeem(tokenA, AMOUNT);
vm.stopPrank();
}
function testTwoUsersDepositMintsAssetAndRedeem() public setAllowedToken {
tokenA.mint(liquidityProvider, AMOUNT);
tokenA.mint(liquidityProvider2, AMOUNT);
vm.startPrank(liquidityProvider);
tokenA.approve(address(thunderLoan), AMOUNT);
thunderLoan.deposit(tokenA, AMOUNT);
vm.stopPrank();
vm.startPrank(liquidityProvider2);
tokenA.approve(address(thunderLoan), AMOUNT);
thunderLoan.deposit(tokenA, AMOUNT);
vm.stopPrank();
vm.startPrank(liquidityProvider);
thunderLoan.redeem(tokenA, AMOUNT);
vm.stopPrank();
AssetToken asset = thunderLoan.getAssetFromToken(tokenA);
uint256 exchangeRate = asset.getExchangeRate();
uint256 moreAMOUNT = (AMOUNT * exchangeRate) / asset.EXCHANGE_RATE_PRECISION();
assertGt(moreAMOUNT, AMOUNT);
assertEq(tokenA.balanceOf(liquidityProvider), moreAMOUNT);
assertEq(asset.balanceOf(liquidityProvider), 0);
assertEq(tokenA.balanceOf(address(asset)), 2*AMOUNT-moreAMOUNT);
assertLt(tokenA.balanceOf(address(asset)), AMOUNT);
}
modifier hasDeposits() {
vm.startPrank(liquidityProvider);
tokenA.mint(liquidityProvider, DEPOSIT_AMOUNT);
tokenA.approve(address(thunderLoan), DEPOSIT_AMOUNT);
thunderLoan.deposit(tokenA, DEPOSIT_AMOUNT);
vm.stopPrank();
_;
}
}
function deposit(IERC20 token, uint256 amount) external revertIfZero(amount) revertIfNotAllowedToken(token) {
AssetToken assetToken = s_tokenToAssetToken[token];
uint256 exchangeRate = assetToken.getExchangeRate();
uint256 mintAmount = (amount * assetToken.EXCHANGE_RATE_PRECISION()) / exchangeRate;
emit Deposit(msg.sender, token, amount);
assetToken.mint(msg.sender, mintAmount);
token.safeTransferFrom(msg.sender, address(assetToken), amount);
}