Thunder Loan

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

flash-loan fee scales with token WETH price, so sub-1-WETH tokens (USDC, stablecoins) pay far below 0.3%

Description

ThunderLoan::getCalculatedFee derives the fee from the borrowed token's WETH value, not from the borrowed amount:

uint256 valueOfBorrowedToken = (amount * getPriceInWeth(address(token))) / s_feePrecision;
@> fee = (valueOfBorrowedToken * s_flashLoanFee) / s_feePrecision;

The effective fee rate is therefore s_flashLoanFee * price / 1e18, i.e. 0.3% * (tokenPriceInWeth / 1e18). That equals the intended 0.3% only for a token trading exactly 1:1 with WETH. Every token priced below 1 WETH — every stablecoin, the in-scope USDC, and the vast majority of ERC20s — pays a fee far below 0.3%, so liquidity providers earn almost nothing on those markets. This is a formula-level design flaw, independent of the spot-price manipulation vector and independent of dust-amount rounding.

Risk

Likelihood: High

  • Applies to every allowed token not priced at ~1 WETH, on every flash loan — essentially all real tokens, with no attacker action required.

Impact: Medium

  • LP fee revenue is systematically undercharged (by ~3,300x for a dollar-priced token), undermining the protocol's core yield promise and mispricing the service.

Proof of Concept

For a token priced at 0.0003 WETH (a realistic dollar-ish token vs ETH), borrowing 100 tokens yields a fee about 3,300x smaller than the intended 0.3% — no manipulation, ordinary borrow size:

function test_M5_feeMispricedForNon1WethTokens() public allowTokenA {
uint256 amount = 100e18;
address pool = mockPoolFactory.getPool(address(tokenA));
vm.mockCall(pool, abi.encodeWithSignature("getPriceOfOnePoolTokenInWeth()"), abi.encode(uint256(3e14)));
uint256 fee = thunderLoan.getCalculatedFee(tokenA, amount); // 9e13
uint256 intended = amount * 3e15 / 1e18; // 3e17 (0.3% of amount)
assertLt(fee, intended / 1000); // actual fee is >1000x too small
}

Recommended Mitigation

Charge the fee as a flat percentage of the borrowed amount (fee = amount * s_flashLoanFee / s_feePrecision), matching how flash-loan fees work elsewhere (e.g. Aave). If a value-denominated fee is genuinely intended, normalize by token decimals and drop the extra s_feePrecision division so the effective rate is a stable 0.3% regardless of the token's WETH price.

Updates

Lead Judging Commences

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