Stratax Contracts

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

`Stratax::_call1InchSwap` `balanceOf` fallback can bypass `minReturnAmount` slippage protection

Author Revealed upon completion

Stratax::_call1InchSwap balanceOf fallback can bypass minReturnAmount slippage protection

Description

When 1inch returns no data from a swap, Stratax::_call1InchSwap falls back to using the contract's entire token balance as the returnAmount:

function _call1InchSwap(bytes memory _swapParams, address _asset, uint256 _minReturnAmount)
internal
returns (uint256 returnAmount)
{
(bool success, bytes memory result) = address(oneInchRouter).call(_swapParams);
require(success, "1inch swap failed");
if (result.length > 0) {
(returnAmount,) = abi.decode(result, (uint256, uint256));
} else {
@> returnAmount = IERC20(_asset).balanceOf(address(this));
}
@> require(returnAmount >= _minReturnAmount, "Insufficient return amount from swap");
return returnAmount;
}

The returnAmount here does not ONLY represent the tokens received from the swap — it includes any pre-existing balance of _asset already sitting in the contract. This directly defeats the minReturnAmount slippage protection: a swap could return far less than expected, but as long as the contract's prior balance of _asset, plus what was actually returned from the swap exceeds _minReturnAmount, the check passes silently.

Two ways tokens can accumulate in the contract to enable this:

  1. Excess collateral from unwinds: Stratax::_executeUnwindOperation currently withdraws more collateral from Aave than the 1inch swap consumes due to the mismatched collateralToWithdraw calculations. The surplus sits in the contract.

  2. Direct transfers: Anyone can send tokens directly to the contract address. There is no mechanism to prevent this, and these tokens would be absorbed into the balanceOf reading.

Beyond the slippage bypass, the inflated returnAmount also propagates downstream in both _executeOpenOperation and _executeUnwindOperation:

// In both operations:
uint256 totalDebt = _amount + _premium;
require(returnAmount >= totalDebt, "Insufficient funds to repay flash loan");
// Excess gets supplied back to Aave
@> if (returnAmount - totalDebt > 0) {
IERC20(_asset).approve(address(aavePool), returnAmount - totalDebt);
aavePool.supply(_asset, returnAmount - totalDebt, address(this), 0);
}

Risk

Likelihood:

  • Stratax::_executeUnwindOperation routinely withdraws more collateral from Aave than the 1inch swap consumes, so excess tokens accumulating in the contract is the expected state after any unwind — not an edge case.

  • Additionally, anyone can send tokens directly to the contract at any time.

  • This triggers when 1inch returns empty result data, which depends on the specific swap function called.

Impact:

  • The minReturnAmount slippage protection is completely bypassed — a swap executed at an unfavourable rate passes the check as long as the contract holds a sufficient pre-existing balance.

  • Pre-existing contract balances are silently swept into Aave positions via the excess supply logic, effectively losing those tokens into the position.

Proof of Concept

  1. A user opens and unwinds a position — Stratax::_executeUnwindOperation withdraws more collateral than the swap consumes, leaving excess collateral in the contract

  2. The user opens a new position where the 1inch swap returns no data

  3. Stratax::_call1InchSwap falls back to balanceOf, which now includes the accumulated USDC

  4. The inflated returnAmount passes the minReturnAmount slippage check despite the swap returning insufficient tokens

Recommended Mitigation

Snapshot the balance before the swap and use the delta as the returnAmount:

function _call1InchSwap(bytes memory _swapParams, address _asset, uint256 _minReturnAmount)
internal
returns (uint256 returnAmount)
{
+ uint256 balanceBefore = IERC20(_asset).balanceOf(address(this));
+
(bool success, bytes memory result) = address(oneInchRouter).call(_swapParams);
require(success, "1inch swap failed");
if (result.length > 0) {
(returnAmount,) = abi.decode(result, (uint256, uint256));
} else {
- returnAmount = IERC20(_asset).balanceOf(address(this));
+ returnAmount = IERC20(_asset).balanceOf(address(this)) - balanceBefore;
}
require(returnAmount >= _minReturnAmount, "Insufficient return amount from swap");
return returnAmount;
}

Support

FAQs

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

Give us feedback!