Thunder Loan

AI First Flight #7
Beginner FriendlyFoundryDeFiOracle
EXP
View results
Submission Details
Severity: low
Valid

getCalculated fee will retrun 0 for low amount

Root

Description

  • ThunderLoan.deposit() updates the asset token exchange rate using a fee calculated from the deposited amount.
    However, due to integer division truncation in getCalculatedFee(), deposits below a certain threshold result in a zero fee.

    When a zero fee is passed to updateExchangeRate():

    • The exchange rate does not increase, breaking protocol assumptions, or

    • The transaction reverts, depending on the strictness of the exchange-rate check

    This creates inconsistent behavior for small deposits and allows fee evasion via deposit splitting.

  • getCalculatedFee() performs two integer divisions using s_feePrecision, which causes the fee to round down to zero for small values:

// Root cause in the codebase with @> marks to highlight the relevant section
function getCalculatedFee(IERC20 token, uint256 amount) public view returns (uint256 fee) {
//slither-disable-next-line divide-before-multiply
@> uint256 valueOfBorrowedToken = (amount * getPriceInWeth(address(token))) / s_feePrecision;
//slither-disable-next-line divide-before-multiply
// == value * 3 / 1000
// if amount <= 333 => fee == 0
@> fee = (valueOfBorrowedToken * s_flashLoanFee) / s_feePrecision;
}

Risk

Likelihood

  • if valueOfBorrowedToken < s_feePrecision / s_flashLoanFee

Impact:

  • then fee == 0

  • assetToken.updateExchangeRate(calculatedFee) will revert ;

Proof of Concept

function testFlashLoanSmallAMOUNT() public setAllowedToken hasDeposits {
uint256 minAmount = 1e18 / uint256(3e15) + 1;
uint256 amountToBorrow = minAmount;
uint256 calculatedFee = thunderLoan.getCalculatedFee(tokenA, amountToBorrow);
assertNotEq(0, calculatedFee);
vm.startPrank(user);
tokenA.mint(address(mockFlashLoanReceiver), minAmount);
thunderLoan.flashloan(address(mockFlashLoanReceiver), tokenA, amountToBorrow, "");
vm.stopPrank();
assertEq(mockFlashLoanReceiver.getbalanceDuring(), amountToBorrow + minAmount);
assertEq(mockFlashLoanReceiver.getBalanceAfter(), minAmount - calculatedFee);
}
function testFeeForSmallAMOUNT() public setAllowedToken hasDeposits {
// 333 doesnt work 334 does
uint256 minAmount = 1e18 / uint256(3e15) + 1;
uint256 calculatedFee = thunderLoan.getCalculatedFee(tokenA, minAmount);
assertNotEq(calculatedFee, 0);
}

Recommended Mitigation

- remove this code
+ add this code
+ uint256 MIN_DEPOSIT;
// in ThunderLoan
function deposit(IERC20 token, uint256 amount) external revertIfZero(amount) revertIfNotAllowedToken(token) {
+ require( amount > MIN_DEPOSIT , "amount deposit must be at least MIN_DEPOSIT")
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);
}
function getCalculatedFee(IERC20 token, uint256 amount) public view returns (uint256 fee) {
// price precision must be 1e18 at least same than s_feePrecision
+ require( amount > MIN_DEPOSIT , "amount deposit must be at least MIN_DEPOSIT")
uint256 valueOfBorrowedToken = (amount * getPriceInWeth(address(token))) / s_feePrecision;
// if valueOfBorrowed token < s_feePrecision / s_flashLoanFee ===> fees = 0
fee = (valueOfBorrowedToken * s_flashLoanFee) / s_feePrecision;
}
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 3 hours ago
Submission Judgement Published
Validated
Assigned finding tags:

[L-01] getCalculatedFee can be 0

## Description getCalculatedFee can be as low as 0 ## Vulnerability Details Any value up to 333 for "amount" can result in 0 fee based on calculation ``` function testFuzzGetCalculatedFee() public { AssetToken asset = thunderLoan.getAssetFromToken(tokenA); uint256 calculatedFee = thunderLoan.getCalculatedFee( tokenA, 333 ); assertEq(calculatedFee ,0); console.log(calculatedFee); } ``` ## Impact Low as this amount is really small ## Recommendations A minimum fee can be used to offset the calculation, though it is not that important.

Support

FAQs

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

Give us feedback!