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

Global Queue Overwrite Leading to Order Mismatch in GmxProxy

GmxProxy

Summary:
The GmxProxy contract uses a single global OrderQueue variable to store the active order’s request key and settlement flag. When a new order is created via createOrder or settle, the request key is stored by simply writing to this global queue. Later, in callback functions like afterOrderExecution and afterOrderCancellation, the entire queue is deleted. This design assumes that only one order is active at a time. If the associated PerpetualVault (or its trusted caller) inadvertently initiates a second order before the callback for the first order is received, the global queue is overwritten. As a result, the callback for the earlier order will use the wrong—or missing—request key, causing misprocessing of order results, such as incorrect fee accounting or state updates.


Where the Issue Is:
The vulnerability is in the GmxProxy contract’s use of a single global OrderQueue variable:

OrderQueue public queue;

In both the createOrder and settle functions, the new order’s request key is stored as follows:

queue.requestKey = requestKey;

Later, when processing order callbacks in afterOrderExecution and afterOrderCancellation, the code simply clears the queue (using delete queue), without verifying that the callback corresponds to the correct order.


Root Cause:
The core issue stems from using a single, global state variable (queue) to store order information. This design enforces a one‑order‑at‑a‑time invariant. However, if two orders are submitted in rapid succession (for example, due to a coding error or inadvertent vault behavior), the second order’s request key overwrites the first. Consequently, when GMX sends a callback for the earlier order, the stored request key does not match, leading to misprocessing of order results.


Proof-of-Concept:

VulnerableGmxProxy.sol

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.4;
/**
* @title VulnerableGmxProxy
* @dev A simplified version of the GmxProxy contract that demonstrates the vulnerability.
* It uses a single global OrderQueue to store an order’s request key.
*/
contract VulnerableGmxProxy {
struct OrderQueue {
bytes32 requestKey;
bool isSettle;
}
// Global queue for the active order.
OrderQueue public queue;
/**
* @notice Simulates order creation.
* @param requestKey The unique key for the order.
*/
function createOrder(bytes32 requestKey) external {
// Overwrites any existing order in the queue.
queue.requestKey = requestKey;
queue.isSettle = false;
}
/**
* @notice Simulates order settlement.
* @param requestKey The unique key for the settlement order.
*/
function settleOrder(bytes32 requestKey) external {
// Overwrites any existing order in the queue.
queue.requestKey = requestKey;
queue.isSettle = true;
}
/**
* @notice Returns the current order queue.
* @return requestKey The stored order request key.
* @return isSettle The settlement flag.
*/
function getQueue() external view returns (bytes32 requestKey, bool isSettle) {
return (queue.requestKey, queue.isSettle);
}
/**
* @notice Clears the order queue.
*/
function clearQueue() external {
delete queue;
}
}

VulnerableGmxProxyTest.t.sol

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.4;
import "forge-std/Test.sol";
import "../src/VulnerableGmxProxy.sol";
/**
* @title VulnerableGmxProxyTest
* @dev This test demonstrates that if two orders are created in quick succession,
* the global queue is overwritten and the callback for the first order would receive
* the wrong request key.
*/
contract VulnerableGmxProxyTest is Test {
VulnerableGmxProxy proxy;
function setUp() public {
proxy = new VulnerableGmxProxy();
}
function testQueueOverwrite() public {
// Define two distinct order request keys.
bytes32 orderKey1 = keccak256(abi.encode("order1"));
bytes32 orderKey2 = keccak256(abi.encode("order2"));
// Simulate creating the first order.
proxy.createOrder(orderKey1);
// Simulate creating a second order before the callback for the first is processed.
proxy.createOrder(orderKey2);
// Retrieve the current queue.
(bytes32 currentKey, bool isSettle) = proxy.getQueue();
// Assert that the global queue now holds the second order's key.
assertEq(currentKey, orderKey2, "Queue was not overwritten with orderKey2");
// Verify that the first order's key is lost.
assertTrue(currentKey != orderKey1, "OrderKey1 should have been overwritten");
}
}

Run the Tests:
From the project root, run:

forge test --match-path test/VulnerableGmxProxyTest.t.sol

Impact:

  • State Inconsistency: Order callbacks will process mismatched data, leading to incorrect fee distribution, collateral adjustments, or share minting.

  • Financial Risk: Incorrect processing of orders can result in misallocated funds, causing loss or dilution of depositor balances.

  • Invariant Violation: The intended one‑order‑at‑a‑time invariant is broken, undermining the protocol’s assumption of sequential, isolated order processing.


Remediation:

  • Support Multiple Concurrent Orders:
    Replace the single global OrderQueue with a mapping or an array keyed by the unique request key. This ensures that each order’s state is tracked independently.

    Example:

    mapping(bytes32 => OrderQueue) public orderQueues;

    Then, in the callback functions, reference the order using its unique request key.

  • Enforce Strict Sequential Order Submission:
    If concurrent orders are not intended, add strict checks in the vault or proxy to ensure that a new order cannot be initiated until the callback for the previous order is processed.


Severity:
High – The flaw directly affects the core order processing logic, leading to potential misallocation of funds and breaches in state consistency, which can have significant financial repercussions.


Tools Used:

manual.

Updates

Lead Judging Commences

n0kto Lead Judge 9 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

Suppositions

There is no real proof, concrete root cause, specific impact, or enough details in those submissions. Examples include: "It could happen" without specifying when, "If this impossible case happens," "Unexpected behavior," etc. Make a Proof of Concept (PoC) using external functions and realistic parameters. Do not test only the internal function where you think you found something.

invalid_queue_requestKey_overwrite

Order is proceed one by one and requestKey is only used to cancelOrder. I didn’t see any real scenario where it will cause a problem. Flow and gmxLock will prevent that to happen.

Support

FAQs

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

Give us feedback!