getCalculatedFee divides every token amount by the fixed 1e18 precision. Allowed ERC20s may use other decimals, so economically equivalent loans produce radically different fees. A 6-decimal asset can be undercharged by 1e12 or round to a zero fee.
At src/protocol/ThunderLoan.sol:246-250, amount * price is normalized with s_feePrecision == 1e18 regardless of IERC20Metadata(token).decimals(). The oracle price convention does not compensate for the input token's unit scale.
For a token such as USDC, one token is 1e6 units, not 1e18. Applying the fixed divisor treats the loan as a trillion times smaller than its actual token-denominated value.
Borrowers of non-18-decimal assets pay negligible or zero fees, LP yield is lost, and exchange-rate accounting becomes inconsistent across assets.
Normalize amount with 10 ** IERC20Metadata(address(token)).decimals() when converting it to WETH value, while retaining an explicit 1e18 scale for oracle prices and fee percentages. Document and validate the oracle's returned decimals.
Configure 6-, 8-, and 18-decimal tokens with equal economic prices and borrow equal values. Assert the calculated fees are economically equal (within documented rounding) and none unexpectedly rounds to zero.
## 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.