Stratax Contracts

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

Wrong token address passed to _call1InchSwap

Author Revealed upon completion

Description

  • During open, the protocol borrows borrowToken from Aave and immediately swaps it to the collateral/flash‑loan token (_asset) to repay the flash loan. The helper _call1InchSwap should therefore be told the destination token (the token expected to be received from 1inch), so that if the router returns no data, the fallback balance check inspects the correct token.

  • In _executeOpenOperation, the code passes flashParams.borrowToken (the input of the swap) as the _asset argument to _call1InchSwap. The swap path is borrowToken → collateralToken (_asset), so the destination is the collateral token. If 1inch returns no return data, _call1InchSwap falls back to IERC20(_asset).balanceOf(address(this))—but _asset is incorrectly set to the borrow token, so the balance check reads the wrong token, and may revert even when the router returned the correct output.

// Stratax.sol::_executeOpenOperation
IERC20(flashParams.borrowToken).approve(address(oneInchRouter), flashParams.borrowAmount);
// Execute swap via 1inch
uint256 returnAmount =
// @> BUG: passing borrowToken instead of the destination token (_asset)
_call1InchSwap(flashParams.oneInchSwapData, flashParams.borrowToken, flashParams.minReturnAmount);
// Stratax.sol::_call1InchSwap
// If router returns no data, measure balance of `_asset` to infer the received amount.
if (result.length > 0) {
(returnAmount,) = abi.decode(result, (uint256, uint256));
} else {
// @> This checks the token address provided above.
returnAmount = IERC20(_asset).balanceOf(address(this));
}

Risk

Likelihood: Medium

  • 1inch (or any router) may legitimately return no data for certain paths/encodings, or a non‑conforming integration may produce empty return bytes. In such cases, this bug will trigger the fallback branch.

  • Multi‑chain differences and router upgrades make “empty return” behavior plausible during normal operations.

Impact: Medium

  • False negative on repayment: Even when the swap correctly returns collateral tokens (_asset), the code inspects the borrow token balance and can report returnAmount = 0, causing a revert at require(returnAmount >= totalDebt, ...).

  • Operational DoS: Valid position opens can fail unpredictably depending on router behavior or calldata shape.

Proof of Concept

In a drop‑in test, deploy a MockOneInchRouter whose fallback:

  1. Transfers USDC (the collateral/flash‑loan token) to msg.sender (Stratax) in an amount ≥ flash‑loan + premium, and

  2. Returns empty bytes (no return data).

Then call createLeveragedPosition (USDC as _flashLoanToken, WETH as _borrowToken).
Inside _executeOpenOperation, _call1InchSwap receives flashParams.borrowToken (WETH) as _asset, so the fallback reads balanceOf(WETH) which is 0, setting returnAmount = 0.
The subsequent check require(returnAmount >= totalDebt, "Insufficient funds to repay flash loan"); reverts despite the contract holding enough USDC to repay - because the balance check looked at the wrong token.

Recommended Mitigation

  • Pass the destination token to _call1InchSwap in the open path—i.e., the collateral/flash‑loan token (_asset), not borrowToken. Optionally add an assertion that the open path’s destination equals the flash‑loan asset.

function _executeOpenOperation(address _asset, uint256 _amount, uint256 _premium, bytes calldata _params)
internal
returns (bool)
{
(, address user, FlashLoanParams memory flashParams) =
abi.decode(_params, (OperationType, address, FlashLoanParams));
// ... supply & borrow omitted ...
IERC20(flashParams.borrowToken).approve(address(oneInchRouter), flashParams.borrowAmount);
- uint256 returnAmount =
- _call1InchSwap(flashParams.oneInchSwapData, flashParams.borrowToken, flashParams.minReturnAmount);
+ // Swap borrowed tokens -> flash-loan asset to repay the flash loan
+ // Destination token must be the flash-loan/collateral asset (`_asset`).
+ uint256 returnAmount =
+ _call1InchSwap(flashParams.oneInchSwapData, _asset, flashParams.minReturnAmount);
// ... rest unchanged ...
}

Support

FAQs

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

Give us feedback!