Summary
Atleast one liquidity provider will not be able to withdraw their funds because of asset's exchangeRate functionality.
Vulnerability Details
When a LP deposits fund in the pool through ThunderLoan::deposit
then this function calculates fee
based on amount & token, and updates the asset's exchangeRate(basically it increases the exchangeRate).
Now let's take an example, when a LP deposit 10 tokenA at an exchangeRate of 1:1 then LP receives 10 lpTokenA and the exchangeRate gets updated at 1.12(example). now this LP wanted to redeem
its liquidity by giving all 10 lpTokneA, then LP should get 11.2 (10 * 1.12) tokenA but potocol has only 10 token therefore LP will never get their full amount back.
As the protocol size grows more and more LP will be unable to redeem
their funds because of increase in exchangeRate.
//here is the POC, and I've taken two LP, one will not be able to withdraw but other will not therefore test will fail.
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 ThunderLoanTest is BaseTest {
uint256 constant AMOUNT = 10e18;
uint256 constant DEPOSIT_AMOUNT = AMOUNT * 100;
address liquidityProvider = address(123);
address user = address(456);
address user2 = address(789);
MockFlashLoanReceiver mockFlashLoanReceiver;
function setUp() public override {
super.setUp();
vm.prank(user);
mockFlashLoanReceiver = new MockFlashLoanReceiver(address(thunderLoan));
}
function test_one_LP_will_not_able_to_redeem() public {
vm.prank(thunderLoan.owner());
thunderLoan.setAllowedToken(tokenA, true);
AssetToken assetToken = thunderLoan.getAssetFromToken(tokenA);
tokenA.mint(user, 2e18);
tokenA.mint(user2, 4e18);
vm.startPrank(user);
tokenA.approve(address(thunderLoan), 2e18);
thunderLoan.deposit(tokenA, 2e18);
vm.startPrank(user2);
tokenA.approve(address(thunderLoan), 4e18);
thunderLoan.deposit(tokenA, 4e18);
vm.startPrank(user);
thunderLoan.redeem(tokenA, assetToken.balanceOf(user));
vm.startPrank(user2);
thunderLoan.redeem(tokenA, assetToken.balanceOf(user2));
}
}
Result
Running 1 test for test/unit/MyTest.t.sol:ThunderLoanTest
[FAIL. Reason: ERC20: transfer amount exceeds balance] testUnApprovedTokenDeposite() (gas: 1336210)
Test result: FAILED. 0 passed; 1 failed; 0 skipped; finished in 1.64ms
Ran 1 test suites: 0 tests passed, 1 failed, 0 skipped (1 total tests)
Impact
LP will lose their funds.
Tools Used
Manual Review, foundry
Recommendations
Protocol can do 1 thing: Take small amount of fee from LP while depositing.