Thunder Loan

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

Currency Denomination Mismatch in OracleUpgradeable Causes Fee Undercharge and WETH DoS

Currency Denomination Mismatch in OracleUpgradeable Causes Fee Undercharge and WETH DoS

Description

  • Flash loan fee calculations are expected to output a fee denominated in the borrowed asset (token) so that endingBalance >= startingBalance + fee verifies payment in that exact token.

  • OracleUpgradeable::getPriceInWeth() returns the asset price denominated in WETH. When getCalculatedFee() computes (amount * priceInWeth) / 1e18, the fee is calculated in WETH units, but then enforced as if it were denominated in token. Furthermore, for WETH loans, tswapPoolFactory.getPool(weth) returns address(0), causing any WETH flash loan to permanently revert.

// ThunderLoan.sol
function getCalculatedFee(IERC20 token, uint256 amount) public view returns (uint256 fee) {
@> uint256 valueOfBorrow = (amount * getPriceInWeth(address(token))) / s_feePrecision;
@> fee = (valueOfBorrow * s_flashLoanFee) / s_feePrecision; // Denominated in WETH, NOT token!
}

Risk

Likelihood:

  • Occurs on every fee calculation for non-WETH tokens with prices differing from 1:1 WETH valuation.

  • Occurs 100% of the time whenever any user attempts to borrow WETH via flash loan.

Impact:

  • Severe fee undercharge for low-priced tokens and prohibitive overcharge for high-priced tokens.

  • Complete Denial of Service (DoS) preventing flash loans of WETH, the most critical asset in DeFi.

Proof of Concept

The exploit operates through the following steps:

  1. User requests a flash loan of tokenA where 1 tokenA = 0.001 WETH (1,000 tokenA per WETH).

  2. The contract calculates the fee in WETH units, but demands payment in tokenA, undercharging the fee by 1,000x.

  3. User attempts to borrow WETH; the call reverts with Oracle__PoolDoesNotExist because no WETH-WETH pool exists on TSwap.

function test_denominationMismatchAndWethDoS() public setAllowedToken {
// 1. WETH has no pool against itself, reverting unconditionally
vm.expectRevert(OracleUpgradeable.Oracle__PoolDoesNotExist.selector);
thunderLoan.getCalculatedFee(weth, 10e18);
// 2. Token fee is mistakenly denominated in WETH units
uint256 feeTokenA = thunderLoan.getCalculatedFee(tokenA, 100e18);
assertGt(feeTokenA, 0, "Fee calculation priced in wrong denomination");
}

Recommended Mitigation

Compute flash loan fees strictly as a percentage of the borrowed token amount, eliminating the need for oracle conversion in fee calculation, and add a special case for WETH in OracleUpgradeable.

- function getCalculatedFee(IERC20 token, uint256 amount) public view returns (uint256 fee) {
- uint256 valueOfBorrow = (amount * getPriceInWeth(address(token))) / s_feePrecision;
- fee = (valueOfBorrow * s_flashLoanFee) / s_feePrecision;
- return fee < s_feePrecision ? s_feePrecision : fee;
- }
+ function getCalculatedFee(IERC20, uint256 amount) public view returns (uint256) {
+ return (amount * s_flashLoanFee) / s_feePrecision;
+ }
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!