getCalculatedFee rounds down to zero for small borrows, giving free flash loansSeverity: Low · Impact: Low · Likelihood: Medium
Every flash loan should pay a non-zero fee proportional to the amount borrowed.
getCalculatedFee divides by s_feePrecision (1e18) twice, and Solidity integer division truncates. For small borrow amounts the intermediate products are less than 1e18, so the fee rounds down to 0.
Likelihood:
Occurs for any borrow small enough that amount * price * s_flashLoanFee < 1e36 — with the default 0.3% fee and a 1e18 price, any borrow below 334 base units pays no fee. It is also hit routinely by low-decimal / low-value in-scope tokens.
Impact:
Borrowers obtain flash loans for free (and can split large borrows into many tiny ones to reduce total fees), eroding LP/protocol revenue. Low severity as it does not risk principal.
Save the block below as test/PocL2.t.sol and run forge test --mt test_L2_fee_rounds_to_zero -vv. Borrowing 333 base units costs a fee of 0.
Enforce a minimum non-zero fee (revert if the computed fee is 0), or restructure the calculation to divide only once so precision is not lost, e.g. fee = amount * price * s_flashLoanFee / (s_feePrecision * s_feePrecision) with a require(fee > 0) guard.
## Description getCalculatedFee can be as low as 0 ## Vulnerability Details Any value up to 333 for "amount" can result in 0 fee based on calculation ``` function testFuzzGetCalculatedFee() public { AssetToken asset = thunderLoan.getAssetFromToken(tokenA); uint256 calculatedFee = thunderLoan.getCalculatedFee( tokenA, 333 ); assertEq(calculatedFee ,0); console.log(calculatedFee); } ``` ## Impact Low as this amount is really small ## Recommendations A minimum fee can be used to offset the calculation, though it is not that important.
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.