Thunder Loan

AI First Flight #7
Beginner FriendlyFoundryDeFiOracle
EXP
View results
Submission Details
Impact: high
Likelihood: high
Invalid

Fee computed in WETH-value units but checked against a raw token balance misprices every non-WETH loan

Fee is computed in WETH-value units but the repayment check compares it against a raw token balance, mispricing every non-WETH loan

Description

getCalculatedFee scales the fee by the token's WETH price, producing a value denominated in WETH. The flash-loan repayment check, however, adds that WETH-scaled fee to startingBalance, which is a raw balance of the borrowed token. The two are only equivalent when the token's price equals 1e18 (i.e. the token is WETH itself).

// ThunderLoan.sol:246-250
uint256 valueOfBorrowedToken = (amount * getPriceInWeth(address(token))) / s_feePrecision;
fee = (valueOfBorrowedToken * s_flashLoanFee) / s_feePrecision; // @> fee is in WETH value units
// ThunderLoan.sol:212-214
if (endingBalance < startingBalance + fee) // @> startingBalance is raw token units, mismatched with `fee`

Risk

Likelihood:
Affects every flash loan of any token other than WETH — that is the normal case, not an edge case.

Impact:
For cheap tokens (price below 1e18) the charged fee falls far below the intended 0.3%, leaking protocol revenue. For expensive tokens (price above 1e18) the required repayment becomes larger than 0.3% and can exceed what a legitimate borrower can return, reverting valid loans. The fee is correct only for WETH.

Proof of Concept

Quote the fee for a high-priced token and observe it diverges from 0.3% of the token amount.

function test_feeUnitsMismatch() public {
// tokenA priced at 2e18 WETH each
uint256 amount = 100e18;
uint256 fee = thunderLoan.getCalculatedFee(tokenA, amount);
uint256 expected = amount * 3e15 / 1e18; // intended 0.3% of token amount
assertNotEq(fee, expected); // fee is WETH-scaled, not token-scaled
}

Recommended Mitigation

Charge the fee as a percentage of the token amount directly, or collect the fee in WETH consistently with how it is computed.

- uint256 valueOfBorrowedToken = (amount * getPriceInWeth(address(token))) / s_feePrecision;
- fee = (valueOfBorrowedToken * s_flashLoanFee) / s_feePrecision;
+ // fee charged in the borrowed token, matching the raw-balance repayment check
+ fee = (amount * s_flashLoanFee) / s_feePrecision;
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 6 hours ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!