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

Max amount of deposited token cant always be redeemed

Summary

The Liquidity Provider isn't always able to redeem the maximum amount of deposited tokens (type(uint256).max).

In the ThunderLoan::deposit() function, after the IERC20 token is deposited, the ThunderLoan::updateExchangeRate() adjusts based on the deposited token amount.

During the redemption process (ThunderLoan::redeem), the token amount to be redeemed is determined using the adjusted exchangeRate.
Consequently, the Liquidity Provider is entitled to receive the deposited token amount multiplied by exchangeRate.
If no flash loan has happen providing the required fee, or if the taken flashloan hasn't provided sufficient fees, the smart contract won't have enough tokens available for the Liquidity Provider.

Vulnerability Details

function redeem(
IERC20 token,
uint256 amountOfAssetToken
)
external
revertIfZero(amountOfAssetToken)
revertIfNotAllowedToken(token)
{
AssetToken assetToken = s_tokenToAssetToken[token];
uint256 exchangeRate = assetToken.getExchangeRate();
@> if (amountOfAssetToken == type(uint256).max) {
@> amountOfAssetToken = assetToken.balanceOf(msg.sender);
}
@> uint256 amountUnderlying = (amountOfAssetToken * exchangeRate) / assetToken.EXCHANGE_RATE_PRECISION();
emit Redeemed(msg.sender, token, amountOfAssetToken, amountUnderlying);
assetToken.burn(msg.sender, amountOfAssetToken);
assetToken.transferUnderlyingTo(msg.sender, amountUnderlying);
}

Impact

//The Liquidity Provider can't redeem the deposited max amount of tokenA until someone takes a flashloan with sufficient fee.
function testCantRedeemMaxDepositedAmount() public setAllowedToken {
AssetToken assetToken = thunderLoan.getAssetFromToken(tokenA);
uint256 initilaExchangeRate = assetToken.getExchangeRate();
console.log("The initial exchangeRate is: %s", initilaExchangeRate);
tokenA.mint(liquidityProvider, DEPOSIT_AMOUNT);
//Deposit tokenA
vm.startPrank(liquidityProvider);
tokenA.approve(address(thunderLoan), DEPOSIT_AMOUNT);
thunderLoan.deposit(tokenA, DEPOSIT_AMOUNT);
uint256 calculatedFee = thunderLoan.getCalculatedFee(tokenA, DEPOSIT_AMOUNT);
console.log("The calculatedFee is: %s", calculatedFee);
uint256 newExchangeRate = assetToken.getExchangeRate();
console.log("The newExchangeRate after deposit is: %s", newExchangeRate);
uint256 initialBalance = tokenA.balanceOf(liquidityProvider);
uint256 maxAmountToRedeem = assetToken.balanceOf(liquidityProvider);
uint256 expectedBalance =
initialBalance + (maxAmountToRedeem * newExchangeRate) / assetToken.EXCHANGE_RATE_PRECISION();
console.log("The Liquidity Provided expectedBalance is: %s", expectedBalance);
uint256 assetTokenBalance = tokenA.balanceOf(address(assetToken));
console.log("The balance of the tokenA on the contract is: %s", assetTokenBalance);
// Call redeem for the max amount and revert
assetToken.approve(address(thunderLoan), maxAmountToRedeem);
vm.expectRevert();
thunderLoan.redeem(tokenA, maxAmountToRedeem);
vm.stopPrank();
}
Running 1 test for test/unit/ThunderLoanTest.t.sol:ThunderLoanTest
[PASS] testCantRedeemMaxDepositedAmount() (gas: 1205905)
Logs:
The Liquidity Provider expectedBalance is: 1003000000000000000000
The balance of the tokenA in the contract is: 1000000000000000000000
Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 8.74ms

Tools Used

Manual review

Recommendations

Delete the updateExchangeRate() in the deposit() function and leave it only in the flashloan() 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.