Thunder Loan

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

Fee calculation for non-standard ERC20 tokens produces drastically lower fees than standard 18-decimal tokens

Description

  • Normal: Fee calculation should normalize all tokens to a common precision to ensure consistent fees regardless of token decimals.

  • Bug: getCalculatedFee() divides by s_feePrecision (1e18) without accounting for the token's actual decimals. Tokens with fewer than 18 decimals (USDC=6, WBTC=8) have their fee values dramatically undervalued — proportionally to the decimal gap.

// ThunderLoan.sol / ThunderLoanUpgraded.sol — getCalculatedFee()
uint256 valueOfBorrowedToken = (amount * getPriceInWeth(address(token))) / s_feePrecision;
fee = (valueOfBorrowedToken * s_flashLoanFee) / s_feePrecision;

Risk

Likelihood:

  • Any non-18-decimal token allowed by the protocol triggers this

  • USDC (6 decimals) is one of the most commonly used tokens in DeFi

  • The bug is deterministic — no runtime condition prevents it

Impact:

  • User borrowing 2000 USDC (~1 ETH) pays ~0.000000000006 ETH in fees vs 0.003 ETH for 1 ETH borrow — a 500,000× undercharge

  • Protocol collects nearly zero revenue from non-standard token flash loans

  • Attacker can borrow large values of non-standard tokens at negligible cost

Proof of Concept

Assume 1 ETH = 2000 USDC, both worth the same:

Scenario Calculation Fee
Borrow 1 ETH (1e18 WEI) valueOfBorrowedToken = 1e18 × 1e18 / 1e18 = 1e18 fee = 1e18 × 3e15 / 1e18 = 0.003 ETH
Borrow 2000 USDC (2e9 WEI) valueOfBorrowedToken = 2e9 × 1e18 / 1e18 = 2e9 fee = 2e9 × 3e15 / 1e18 = 6e6 WEI = 0.000000000006 ETH

Both borrow the same value, but the USDC fee is negligible compared to the ETH fee.

forge test --match-test testFuzzGetCalculatedFee -vvv

Recommended Mitigation

function getCalculatedFee(IERC20 token, uint256 amount) public view returns (uint256 fee) {
+ uint8 decimals = IERC20Metadata(address(token)).decimals();
+ uint256 tokenPrecision = 10 ** decimals;
- uint256 valueOfBorrowedToken = (amount * getPriceInWeth(address(token))) / s_feePrecision;
+ uint256 valueOfBorrowedToken = (amount * getPriceInWeth(address(token))) / tokenPrecision;
fee = (valueOfBorrowedToken * s_flashLoanFee) / s_feePrecision;
}
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge 3 days 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!