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.
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
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.
## 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.
The contest is live. Earn rewards by submitting a finding.
Submissions are being reviewed by our AI judge. Results will be available in a few minutes.
View all submissionsThe contest is complete and the rewards are being distributed.