Stratax Contracts

First Flight #57
Beginner FriendlyDeFi
100 EXP
Submission Details
Impact: medium
Likelihood: medium

Flash loan fee mismatch

Author Revealed upon completion

Description

  • When opening/unwinding with an Aave flash loan, the contract should use the actual flash‑loan fee charged by Aave for that call. In Aave V3, the true fee is provided to the callback as _premium and may vary by market/upgrade.

  • Stratax stores a configurable flashLoanFeeBps (default 9) and uses it inside calculateOpenParams to size the borrow such that the 1inch swap will cover flashLoanAmount + fee. However, the actual fee is determined by Aave and passed as _premium in executeOperation. If flashLoanFeeBps is out of sync with Aave’s real fee (e.g., fee changed on the pool, misconfigured by owner, different per‑chain), calculateOpenParams can underestimate the needed return, and the position will revert later in the callback:

// Stratax.sol :: calculateOpenParams
uint256 flashLoanFee = (flashLoanAmount * flashLoanFeeBps) / FLASHLOAN_FEE_PREC;
uint256 minRequiredAfterSwap = flashLoanAmount + flashLoanFee;
// ...
require(borrowValueInCollateral >= minRequiredAfterSwap, "Insufficient borrow to repay flash loan");
// Stratax.sol :: _executeOpenOperation
uint256 totalDebt = _amount + _premium; // <-- actual fee from Aave
require(returnAmount >= totalDebt, "Insufficient funds to repay flash loan");

Risk

Likelihood: Medium

  • Aave fee schedules can differ across networks/markets or change over time; operationally, owners will forget to update flashLoanFeeBps, or set it incorrectly between deployments.

  • Integrations that rely on calculateOpenParams to size trades will routinely hit this path.

Impact: Medium

  • Operational DoS / late revert: Opens (and potentially unwinds) pass pre‑checks but revert in the flash‑loan callback because _premium > computed fee.

  • Hidden safety gap: If flashLoanFeeBps is set lower than reality, your borrow sizing and minReturnAmount are optimistic, increasing failure probability under load.

Proof of Concept

  • Conceptual pseudocode:

// Precondition: owner leaves flashLoanFeeBps = 9 (0.09%), but Aave now charges 10 bps.
// User calls calculateOpenParams() -> gets flashLoanFee = amount * 9 / 10_000.
// User sets minReturnAmount = flashLoanAmount + fee(9 bps) and submits createLeveragedPosition().
// In callback:
totalDebt = _amount + _premium // where _premium == amount * 10 / 10_000
// Router returned exactly (flashLoanAmount + fee at 9 bps), so:
returnAmount < totalDebt // by 1 bp
// => require(returnAmount >= totalDebt) reverts, wasting gas and bricking the open.
  • A similar flow holds if the owner mistakenly sets flashLoanFeeBps to an outdated or chain‑inappropriate value.

Recommended Mitigation

  • Extend IPool interface to expose the pool’s flash‑loan premium (e.g., FLASHLOAN_PREMIUM_TOTAL() in Aave V3).

  • In calculateOpenParams, read the current premium bps from the pool instead of using flashLoanFeeBps.

- uint256 flashLoanFee = (flashLoanAmount * flashLoanFeeBps) / FLASHLOAN_FEE_PREC;
+ uint256 premiumBps = aavePool.FLASHLOAN_PREMIUM_TOTAL(); // add to IPool
+ uint256 flashLoanFee = (flashLoanAmount * premiumBps) / FLASHLOAN_FEE_PREC;

Support

FAQs

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

Give us feedback!