Stratax Contracts

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

`_call1InchSwap` fallback balance check uses wrong token in open operation

Author Revealed upon completion

Root Cause + Impact

In _executeOpenOperation(), the swap converts borrowToken to collateralToken. But _call1InchSwap is called with borrowToken as the _asset parameter (L514). When the 1inch router returns empty data (the fallback path), the function checks the balance of borrowToken instead of collateralToken. Since all borrowTokens were consumed by the swap, this returns ~0, and the subsequent require(returnAmount >= totalDebt) at L522 reverts.

Description

_executeOpenOperation() borrows tokens from Aave, swaps them via 1inch to get collateral tokens, then uses the proceeds to repay the flash loan. _call1InchSwap handles the swap and return value decoding.

At L513-514, _call1InchSwap is called with flashParams.borrowToken:

// Stratax.sol:513-514
uint256 returnAmount =
// @> _call1InchSwap(flashParams.oneInchSwapData, flashParams.borrowToken, flashParams.minReturnAmount);

Inside _call1InchSwap, when the 1inch router's low-level call returns no data (result.length == 0), the fallback at L624-625 checks the balance of _asset:

// Stratax.sol:621-626
if (result.length > 0) {
(returnAmount,) = abi.decode(result, (uint256, uint256));
} else {
// @> returnAmount = IERC20(_asset).balanceOf(address(this));
}

Since _asset is the borrow token and the swap consumed all borrow tokens, the balance is ~0. Then at L522, require(returnAmount >= totalDebt) fails because 0 < totalDebt.

The primary path (when result.length > 0) works correctly. The unwind path (L584) is also correct because _asset there is the debt token, which IS the swap output.

Risk

Likelihood: Medium

The fallback path triggers when the 1inch router doesn't return data. The standard swap() function returns data, but unoswap() and some legacy router functions may not. 1inch has multiple router versions deployed on different chains.

Impact: Medium

When triggered, createLeveragedPosition() always reverts for the affected swap route. Users cannot open leveraged positions through that path. No funds are lost (the whole transaction reverts atomically), but core protocol functionality is broken for those routes.

Proof of Concept

Code path trace for createLeveragedPosition(WETH, 2e18, 1e18, USDC, 5000e6, swapData, 4990e6):

  1. Flash loan: 2 WETH received (_asset = WETH)

  2. Supply 3 WETH to Aave as collateral

  3. Borrow 5000 USDC from Aave

  4. Swap: 5000 USDC -> ~2.05 WETH via 1inch

  5. _call1InchSwap(swapData, USDC, 4990e6) called with _asset = USDC

  6. If 1inch returns empty data: returnAmount = IERC20(USDC).balanceOf(this) = 0 (all USDC swapped away)

  7. require(0 >= totalDebt) -> reverts

The fix is one word: pass _asset (the collateral/flash loan token) instead of flashParams.borrowToken.

Recommended Mitigation

Pass _asset (the collateral/flash loan token) instead of flashParams.borrowToken. In _executeOpenOperation, _asset is the swap output token, so the fallback balance check will read the correct balance:

uint256 returnAmount =
- _call1InchSwap(flashParams.oneInchSwapData, flashParams.borrowToken, flashParams.minReturnAmount);
+ _call1InchSwap(flashParams.oneInchSwapData, _asset, flashParams.minReturnAmount);

Support

FAQs

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

Give us feedback!