In the GmxProxy
contract
Because GmxProxy
uses a single global queue.requestKey
and overwrites it on every createOrder()
or settle()
, the contract fails to track older pending orders correctly if multiple are created consecutively. Properly storing each order’s unique requestKey
is crucial to ensuring GMX callbacks and user cancellations reference the correct order, preventing logic confusion and potential partial breaks in the order flow.
RootCause & Where It Happens
In the contract, createOrder()
stores every newly created order’s requestKey
into the global:
similarly in settle()
:
The contract’s queue
struct only holds one requestKey
and an isSettle
boolean. There is no logic preventing the user (the perpVault
) from calling createOrder()
(or settle()
) multiple times in rapid succession. If two orders are created before the first one is executed by GMX, the second order’s requestKey
overwrites the first order’s stored key in queue
. Consequently, if the GMX keeper executes or cancels the first order later, that callback will produce a mismatch or the contract will no longer have the correct requestKey
in memory for the first order.
Loss of Reference to Older Orders
The first order’s requestKey
is overwritten by a second call to createOrder()
, so the contract’s queue
no longer references the original. Any callback or cancellation for that first order will not match the stored requestKey
, potentially leading to incorrect or missing finalization in the perpetual vault.
Potential Order-Flow Breakdowns
The contract depends on queue.requestKey
when finalizing or canceling orders. If multiple orders are stacked, the second one’s creation effectively “forgets” the older order. The older order may then execute or cancel without the appropriate “afterOrderExecution” or “afterOrderCancellation” logic triggered in perpVault
(or it triggers with an empty queue or a mismatched key).
Possible DoS or Confusion
If the keeper or user attempts to cancel the first order, cancelOrder()
only calls gExchangeRouter.cancelOrder(queue.requestKey)
, referencing the newest overwritten key, not the one for the older order. This either reverts (queue.requestKey != olderKey
) or cancels the wrong order.
The contract’s design allows multiple calls to createOrder()
from perpVault
with no mutual exclusion or queue system.
The code never guards against overwriting an active requestKey
.
The queue
concept is singly dimensioned, storing exactly one key. Storing multiple, unexecuted orders is not properly supported.
This leads to well-defined breakage if two or more orders remain in flight at the same time.
PoC test that demonstrates the “Single-Order Queue Overwritten by Consecutive Calls to createOrder()” vulnerability. In this PoC, we simulate two consecutive order creations on a test version of the GmxProxy. The test shows that the first order’s request key is overwritten by the second order, and then when a callback is simulated using the first key, the call fails (reverts). This proves that the proxy cannot reliably track multiple pending orders.
Recommended Remediation
Support Multiple Pending Orders
Use a mapping of orderId
or array instead of a single queue.requestKey
. Each newly created order is appended, and each callback references the correct key.
The contract can store requestKey -> bool isSettle
so that each order’s metadata is not lost.
Lock the Proxy for One Order at a Time
If the design truly wants only one order in flight, enforce a check that no requestKey
is set prior to creating a new order. For example:
Then the user cannot create a second order while the first is pending.
Handle Overwrites
If multiple orders must exist concurrently, the contract must unify how queue
is used or discard the single-queue approach, migrating to a multi-key approach.
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.
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.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.