DeFiFoundry
50,000 USDC
View results
Submission Details
Severity: high
Invalid

`createIncreasePosition` and `settle` function always revert because of the `swapPath` is set to 0

Description

The function _createIncreasePosition in the PerpetualVault contract and settle function in the gmxProxy is consistently reverting due to an issue with the swap path validation mechanism within the GMX protocol. The core problem stems from the swapPath being set to an empty array (new address ), which leads to a chain of function calls that ultimately causes the transaction to revert.

Here i only discuss the case for _createIncreasePosition function, for settle it will be same

Root Cause Analysis:

Swap Path Validation Failure:

First the keeper calls the run function to open a position assume with the leverage of 20000(2x) in the run function calls the _createIncreasePosition.

function run(
bool isOpen,
bool isLong,
MarketPrices memory prices,
bytes[] memory metadata
) external nonReentrant {
_noneFlow();
_onlyKeeper();
if (gmxProxy.lowerThanMinEth()) {
revert Error.LowerThanMinEth();
}
flow = FLOW.SIGNAL_CHANGE;
if (isOpen) {
if (positionIsClosed) {
if (_isFundIdle() == false) {
revert Error.InsufficientAmount();
}
if (_isLongOneLeverage(isLong)) {
_runSwap(metadata, true, prices);
} else {
(uint256 acceptablePrice) = abi.decode(metadata[0], (uint256));
@=> _createIncreasePosition(isLong, acceptablePrice, prices);
}

The function _createIncreasePosition prepares order data with an empty swapPath array.

function _createIncreasePosition(
bool _isLong,
uint256 acceptablePrice,
MarketPrices memory prices
) internal {
...
IGmxProxy.OrderData memory orderData = IGmxProxy.OrderData({
market: market,
indexToken: indexToken,
initialCollateralToken: address(collateralToken),
@=> swapPath: new address[](0),
isLong: _isLong,
sizeDeltaUsd: sizeDelta,
initialCollateralDeltaAmount: 0,
amountIn: amountIn,
callbackGasLimit: callbackGasLimit,
acceptablePrice: acceptablePrice,
minOutputAmount: 0
});
_gmxLock = true;
@=> gmxProxy.createOrder(orderType, orderData);
}

This order data is used to call gmxProxy.createOrder, which interacts with GMX's exchangeRouter.createOrder function.

gmxProxy createOrder

function createOrder(
Order.OrderType orderType,
IGmxProxy.OrderData memory orderData
) public returns (bytes32) {
require(msg.sender == perpVault, "invalid caller");
...
CreateOrderParamsAddresses memory paramsAddresses = CreateOrderParamsAddresses({
receiver: perpVault,
cancellationReceiver: address(perpVault),
callbackContract: address(this),
uiFeeReceiver: address(0),
market: orderData.market,
initialCollateralToken: orderData.initialCollateralToken,
@=> swapPath: orderData.swapPath
});
CreateOrderParamsNumbers memory paramsNumber = CreateOrderParamsNumbers({
sizeDeltaUsd: orderData.sizeDeltaUsd,
initialCollateralDeltaAmount: orderData.initialCollateralDeltaAmount,
triggerPrice: 0,
acceptablePrice: orderData.acceptablePrice,
executionFee: positionExecutionFee,
callbackGasLimit: orderData.callbackGasLimit,
minOutputAmount: orderData.minOutputAmount, // this param is used when swapping. is not used in opening position even though swap involved.
validFromTime: 0
});
CreateOrderParams memory params = CreateOrderParams({
addresses: paramsAddresses,
numbers: paramsNumber,
orderType: orderType,
decreasePositionSwapType: Order
.DecreasePositionSwapType
.SwapPnlTokenToCollateralToken,
isLong: orderData.isLong,
shouldUnwrapNativeToken: false,
autoCancel: false,
referralCode: referralCode
});
@=> bytes32 requestKey = gExchangeRouter.createOrder(params);
queue.requestKey = requestKey;
return requestKey;
}

The gExchangeRouter.createOrder function calls the OrderHandler.createOrder
Within GMX’s internal flow, OrderUtils.createOrder is triggered by the OrderHandler.createOrder, which calls MarketUtils.validateSwapPath(dataStore, params.addresses.swapPath); to validate the swap path.

In MarketUtils.validateSwapPath, each address in the swapPath is checked.

Since swapPath is empty, marketAddress defaults to 0x0000000000000000000000000000000000000000.

This invalid market address is then passed to validateSwapMarket.

validateSwapMarket attempts to retrieve market data using MarketStoreUtils.get, but since the marketAddress is zero, it returns an empty market structure.

The function validateEnabledMarket is then called, where it checks if market.marketToken is zero.

Since the retrieved market has no valid token it fails the check and triggers a revert with Errors.EmptyMarket().

Impact

Any attempt to call _createIncreasePosition results in a transaction failure.

Keeper cannot open positions due to the incorrect swap path configuration.

Smart contract functionality is effectively blocked for increasing positions.

Tools Used

Manual Review and Arbitrum explorer

Recommendation

Explicitly Define a Valid Swap Path:
Instead of using new address ensure the swapPath contains at least one valid market address.
Fetch the correct swap path dynamically from GMX’s DataStore.

Add a Pre-Validation Check:

Before calling gmxProxy.createOrder, check if the swapPath is empty and provide a meaningful error message.

Ensure Market Addresses are Resolved Correctly:

Confirm that marketToken is correctly set before calling GMX’s order functions.

Fetch market data properly to avoid passing zero addresses.

Updates

Lead Judging Commences

n0kto Lead Judge 8 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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