Stratax Contracts

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

No deadline protection for 1inch swaps

Author Revealed upon completion

Description

  • Time‑sensitive swaps should include a deadline/expiry so a transaction that lingers in the mempool cannot be executed later at an unfavorable price (e.g., after major market moves or when front‑run/sandwiched). This is a standard MEV/time‑delay protection pattern used across DEXes.

  • Stratax forwards raw 1inch calldata to _call1InchSwap and only enforces a minReturnAmount. There is no deadline check anywhere in the open/unwind flash‑loan flows. As a result, a transaction can be mined much later than intended and still pass minReturnAmount (which may be loose by necessity), exposing users to stale execution and adverse selection.

// Stratax.sol
// _executeOpenOperation: calls the swap with no time limit
uint256 returnAmount =
_call1InchSwap(flashParams.oneInchSwapData, /* @> no deadline */ flashParams.borrowToken, flashParams.minReturnAmount);
// _executeUnwindOperation: same issue (no deadline gating before swap)
uint256 returnAmount =
_call1InchSwap(unwindParams.oneInchSwapData, /* @> no deadline */ _asset, unwindParams.minReturnAmount);
// _call1InchSwap: directly does a low-level call; no wall-clock guard
(bool success, bytes memory result) = address(oneInchRouter).call(_swapParams);
require(success, "1inch swap failed");
// @> No deadline enforcement in this helper either

Risk

Likelihood: Medium

  • Transactions on public mempools routinely experience delays, re‑ordering, and inclusion uncertainty; during volatile periods, inclusion can happen many blocks later.

  • Users/integrators may set a generous minReturnAmount to avoid benign reverts, which increases the window for adverse fills when no deadline is enforced.

Impact: Medium

  • Adverse execution / value loss: A delayed open/unwind may still clear minReturnAmount yet execute at materially worse prices than intended, harming user PnL.

  • MEV amplification: Lack of expiry enables searchers to keep transactions hanging and include them only when it benefits them (or harms the user).

Proof of Concept

  • This shows that without an explicit block‑timestamp gate, stale execution proceeds as long as minReturnAmount is satisfied.

// User prepares: minReturnAmount computed at T0 with tight spreads; no deadline in calldata.
// The tx sits in mempool while market moves unfavorably, but still above minReturnAmount.
// Miner/MEV bot includes the tx at T0 + N blocks.
// In _executeOpenOperation/_executeUnwindOperation:
// - No require(block.timestamp <= deadline);
// - _call1InchSwap executes at stale time and returns a value >= minReturnAmount.
// Result: Transaction succeeds at a materially worse price than intended.

Recommended Mitigation

  • Add an explicit deadline/expiry to both open and unwind flows and enforce it just before the swap (or at the start of each flash‑loan callback). Keep it simple and chain‑agnostic.

// In Stratax.sol
// 1) Extend the parameter structs with a deadline
struct FlashLoanParams {
address collateralToken;
uint256 collateralAmount;
address borrowToken;
uint256 borrowAmount;
bytes oneInchSwapData;
uint256 minReturnAmount;
+ uint256 deadline; // unix timestamp after which swap must not execute
}
struct UnwindParams {
address collateralToken;
uint256 collateralToWithdraw;
address debtToken;
uint256 debtAmount;
bytes oneInchSwapData;
uint256 minReturnAmount;
+ uint256 deadline; // unix timestamp after which swap must not execute
}
// 2) Thread deadline from the public entry points (ABI change is intentional)
function createLeveragedPosition(
address _flashLoanToken,
uint256 _flashLoanAmount,
uint256 _collateralAmount,
address _borrowToken,
uint256 _borrowAmount,
bytes calldata _oneInchSwapData,
- uint256 _minReturnAmount
+ uint256 _minReturnAmount,
+ uint256 _deadline
) public onlyOwner {
// ...
FlashLoanParams memory params = FlashLoanParams({
collateralToken: _flashLoanToken,
collateralAmount: _collateralAmount,
borrowToken: _borrowToken,
borrowAmount: _borrowAmount,
oneInchSwapData: _oneInchSwapData,
- minReturnAmount: _minReturnAmount
+ minReturnAmount: _minReturnAmount,
+ deadline: _deadline
});
// ...
}
function unwindPosition(
address _collateralToken,
uint256 _collateralToWithdraw,
address _debtToken,
uint256 _debtAmount,
bytes calldata _oneInchSwapData,
- uint256 _minReturnAmount
+ uint256 _minReturnAmount,
+ uint256 _deadline
) external onlyOwner {
UnwindParams memory params = UnwindParams({
collateralToken: _collateralToken,
collateralToWithdraw: _collateralToWithdraw,
debtToken: _debtToken,
debtAmount: _debtAmount,
oneInchSwapData: _oneInchSwapData,
- minReturnAmount: _minReturnAmount
+ minReturnAmount: _minReturnAmount,
+ deadline: _deadline
});
// ...
}
// 3) Enforce the deadline in both flash-loan paths
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));
+ require(block.timestamp <= flashParams.deadline, "Swap deadline expired (OPEN)");
// ... then perform 1inch swap
}
function _executeUnwindOperation(address _asset, uint256 _amount, uint256 _premium, bytes calldata _params)
internal returns (bool)
{
(, address user, UnwindParams memory unwindParams) =
abi.decode(_params, (OperationType, address, UnwindParams));
+ require(block.timestamp <= unwindParams.deadline, "Swap deadline expired (UNWIND)");
// ... then perform 1inch swap
}

Support

FAQs

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

Give us feedback!