Thunder Loan

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

Flash-loan fees are less for non-standard ERC20 tokens: `getCalculatedFee` ignores token decimals (fixed 1e18)

Description

getCalculatedFee computes the fee using a hardcoded precision of s_feePrecision = 1e18, with no adjustment for the borrowed token's own decimals:

function getCalculatedFee(IERC20 token, uint256 amount) public view returns (uint256 fee) {
uint256 valueOfBorrowedToken = (amount * getPriceInWeth(address(token))) / s_feePrecision; // @> /1e18
fee = (valueOfBorrowedToken * s_flashLoanFee) / s_feePrecision;
}

getPriceInWeth returns a WETH-denominated price (18 decimals), but amount is expressed in the token's own decimals. For a non-standard token with fewer than 18 decimals (e.g. 6-decimal USDC/USDT, both in scope), amount is numerically much smaller for the same real value, so (amount * price) / 1e18 understates valueOfBorrowedToken, and the resulting fee is proportionally far lower than for an 18-decimal token of equal value. In short, fees are less for non-standard (low-decimal) ERC20 tokens.

Risk

Likelihood: Medium

  • Any flash loan of a non-18-decimal token (USDC/USDT are explicitly in scope) hits this. The attacker simply chooses a low-decimal token.

Impact: High

  • Borrowers of low-decimal tokens pay a disproportionately tiny fee relative to the value borrowed, extracting the protocol's flash-loan service far below the intended 0.3% and denying liquidity providers their fair yield. The fee is systematically wrong for the majority of real-world stablecoins, a persistent loss of protocol revenue.

Proof of Concept

// 18-decimal token: amount = 1e18 (1 token), price = 1e18 -> valueOfBorrowedToken = 1e18 -> fee ~ 3e15
// 6-decimal token: amount = 1e6 (1 token), price = 1e18 -> valueOfBorrowedToken = (1e6 * 1e18)/1e18 = 1e6
// -> fee = (1e6 * 3e15)/1e18 = 3000 (astronomically smaller for the same 1-token value)

Recommended Mitigation

Normalize amount to a common precision using the token's decimals before applying the price and fee math:

uint256 normalizedAmount = amount * (10 ** (18 - IERC20Metadata(address(token)).decimals()));
uint256 valueOfBorrowedToken = (normalizedAmount * getPriceInWeth(address(token))) / s_feePrecision;
Updates

Lead Judging Commences

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