Thunder Loan

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

[H-1] Incorrect Share Minting Due to Token Decimal Mismatch Causes Permanent User Loss

Incorrect Share Minting Due to Token Decimal Mismatch Causes Permanent User Loss

Description

  • The protocol implicitly assumes a uniform decimal precision when calculating mintAmount during deposits. However, ERC20 tokens are not required to use 18 decimals and may use lower precision values such as 6 or 8 decimals.

    Because the deposit logic does not normalize token amounts to a common precision before share calculation, the computed mintAmount becomes inaccurate when non-18-decimal tokens are deposited. As a result, users receive fewer shares than they should for the same economic value.

    Since redemption logic depends on the number of shares held, this discrepancy directly reduces the amount of assets users can withdraw. The protocol remains solvent on paper, but individual users experience irreversible losses, making this a critical accounting flaw.

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);
}

Risk

Likelihood:

  • High.
    This issue will occur whenever the protocol supports or integrates ERC20 tokens that do not use 18 decimals (e.g., USDC with 6 decimals). Such tokens are extremely common in production DeFi systems, making this scenario highly realistic rather than theoretical.

Impact:

  • High — Permanent Loss of User Funds.
    Users depositing tokens with fewer decimals than expected will receive an incorrect number of shares. Since redemption calculations rely on these shares, affected users will permanently lose a portion of their deposited assets. This loss compounds over time and cannot be recovered through normal protocol operations.

Proof of Concept

Please add this test on test/unit/TestThunderLoan.t.sol

function testDeposit1000xAndWithdraw() public setAllowedToken{
uint256 manyTimesLoop = 1000;
vm.startPrank(liquidityProvider);
tokenA.mint(liquidityProvider, DEPOSIT_AMOUNT * manyTimesLoop);
tokenA.approve(address(thunderLoan), DEPOSIT_AMOUNT * manyTimesLoop);
for(uint256 i = 0; i < manyTimesLoop; i++){
thunderLoan.deposit(tokenA, DEPOSIT_AMOUNT);
}
vm.stopPrank();
AssetToken asset = thunderLoan.getAssetFromToken(tokenA);
console.log("asset token liquidityProvider before withdraw: ", asset.balanceOf(liquidityProvider));
console.log("totalSupply assetToken: ", asset.totalSupply());
vm.startPrank(liquidityProvider);
thunderLoan.redeem(tokenA, asset.balanceOf(liquidityProvider));
vm.stopPrank();
console.log("asset token liquidityProvider after withdraw: ", asset.balanceOf(liquidityProvider));
assertEq(tokenA.balanceOf(liquidityProvider),DEPOSIT_AMOUNT * manyTimesLoop);
}

Recommended Mitigation

Consider to add a logic to handle different decimals on any other erc20

Consider to add a logic to handle different decimals on any other erc20
Updates

Lead Judging Commences

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

[H-03] fee are less for non standard ERC20 Token

## Description Within the functions `ThunderLoan::getCalculatedFee()` and `ThunderLoanUpgraded::getCalculatedFee()`, an issue arises with the calculated fee value when dealing with non-standard ERC20 tokens. Specifically, the calculated value for non-standard tokens appears significantly lower compared to that of standard ERC20 tokens. ## Vulnerability Details //ThunderLoan.sol ```solidity 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 fee = (valueOfBorrowedToken * s_flashLoanFee) / s_feePrecision; } ``` ```solidity //ThunderLoanUpgraded.sol function getCalculatedFee(IERC20 token, uint256 amount) public view returns (uint256 fee) { //slither-disable-next-line divide-before-multiply @> uint256 valueOfBorrowedToken = (amount * getPriceInWeth(address(token))) / FEE_PRECISION; //slither-disable-next-line divide-before-multiply @> fee = (valueOfBorrowedToken * s_flashLoanFee) / FEE_PRECISION; } ``` ## Impact Let's say: - user_1 asks a flashloan for 1 ETH. - user_2 asks a flashloan for 2000 USDT. ```solidity function getCalculatedFee(IERC20 token, uint256 amount) public view returns (uint256 fee) { //1 ETH = 1e18 WEI //2000 USDT = 2 * 1e9 WEI uint256 valueOfBorrowedToken = (amount * getPriceInWeth(address(token))) / s_feePrecision; // valueOfBorrowedToken ETH = 1e18 * 1e18 / 1e18 WEI // valueOfBorrowedToken USDT= 2 * 1e9 * 1e18 / 1e18 WEI fee = (valueOfBorrowedToken * s_flashLoanFee) / s_feePrecision; //fee ETH = 1e18 * 3e15 / 1e18 = 3e15 WEI = 0,003 ETH //fee USDT: 2 * 1e9 * 3e15 / 1e18 = 6e6 WEI = 0,000000000006 ETH } ``` The fee for the user_2 are much lower then user_1 despite they asks a flashloan for the same value (hypotesis 1 ETH = 2000 USDT). ## Recommendations Adjust the precision accordinly with the allowed tokens considering that the non standard ERC20 haven't 18 decimals.

Support

FAQs

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

Give us feedback!