Thunder Loan

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

getCalculatedFee computes the fee in WETH-value units but charges it in token units: LP fee revenue mispriced by token price

Description

Normal behavior: a 0.3% fee on a flash loan should correspond to 0.3% of the borrowed amount (or, if value-based, the protocol should convert the WETH-denominated fee back into token units before charging).

The issue: getCalculatedFee() multiplies the borrowed amount by the token's WETH price, producing a WETH-denominated value, then applies the 0.3% rate to that value — but flashloan() charges the result as a raw TOKEN amount (ThunderLoan.sol:212-215, endingBalance < startingBalance + fee). The effective fee rate therefore scales with the token's ETH price instead of being a flat 0.3% of the borrowed tokens.

function getCalculatedFee(IERC20 token, uint256 amount) public view returns (uint256 fee) {
@> uint256 valueOfBorrowedToken = (amount * getPriceInWeth(address(token))) / s_feePrecision; // WETH terms
@> fee = (valueOfBorrowedToken * s_flashLoanFee) / s_feePrecision; // charged as token units below
}
// flashloan():
@> if (endingBalance < startingBalance + fee) revert ThunderLoan__NotPaidBack(...); // token-unit check

Consequences: a token priced at 0.0005 ETH (USDC-like) is charged 0.00015% instead of 0.3% (undercharged ~2000x — LP yield evaporates); a token priced at 30 ETH (WBTC-like) is overcharged ~9% extra (borrowers overpay, loans under-used). This holds for any listed token whose price deviates from exactly 1 ETH — independent of token decimals, no manipulation required.

Risk

Likelihood: High.

  • Reason 1: Applies automatically to every flash loan of every listed token whose price differs from 1 WETH — the README explicitly contemplates USDT/USDC/BNB-style assets, none of which trade at 1 ETH.

  • Reason 2: No attacker action, victim mistake, or admin misconfiguration is needed; the mispricing is static and always-on.

Impact:

  • Impact 1: Persistent loss of LP fee revenue for tokens priced below 1 ETH (measured 2000x undercharge at 0.0005 ETH) — the protocol's core yield mechanism fails to collect.

  • Impact 2: For tokens priced above 1 ETH, borrowers are overcharged proportionally, suppressing legitimate flash-loan usage and the associated LP revenue.

Proof of Concept

Foundry test: test/poc/PocFeeOracle.t.sol, case testFeeUnitMismatchUndercharges (PoC file added under test/poc/ in the contest repo; configurable mock pool set to a USDC-like price of 0.0005e18):

intended fee (0.3% in token units): 3000000000000000000000 (3,000e18 for a 1,000,000e18 loan)
actual charged fee (token units): 1500000000000000000 (1.5e18)
undercharge factor: 2000

Actual vs expected: expected 0.3% of the borrowed token amount (3,000e18); actual charge is the WETH-value of the fee expressed in token units (1.5e18) — exactly a 2000x undercharge at 0.0005 ETH/token.

Recommended Mitigation

Convert the WETH-denominated fee back into token units before charging (keeps the intended value-proportional fee):

function getCalculatedFee(IERC20 token, uint256 amount) public view returns (uint256 fee) {
- uint256 valueOfBorrowedToken = (amount * getPriceInWeth(address(token))) / s_feePrecision;
- fee = (valueOfBorrowedToken * s_flashLoanFee) / s_feePrecision;
+ uint256 valueOfBorrowedToken = (amount * getPriceInWeth(address(token))) / s_feePrecision;
+ uint256 feeInWeth = (valueOfBorrowedToken * s_flashLoanFee) / s_feePrecision;
+ fee = (feeInWeth * s_feePrecision) / getPriceInWeth(address(token)); // back to token units
}

(Simpler alternative: charge a flat 0.3% of the borrowed amount in token units — fee = (amount * s_flashLoanFee) / s_feePrecision — dropping the price term entirely.)Root + Impact

Description

  • Describe the normal behavior in one or more sentences

  • Explain the specific issue or problem in one or more sentences

// Root cause in the codebase with @> marks to highlight the relevant section

Risk

Likelihood:

  • Reason 1 // Describe WHEN this will occur (avoid using "if" statements)

  • Reason 2

Impact:

  • Impact 1

  • Impact 2

Proof of Concept

Recommended Mitigation

- remove this code
+ add this code
Updates

Lead Judging Commences

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