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

Single `GmxProxy` Shared by Multiple Vaults

Summary

A single GmxProxy contract can be shared by multiple PerpetualVaults—even though code comments say it is “not recommended.” If that happens, each vault’s GMX order uses (and overwrites) the same queue.requestKey. Overwriting the stored request key causes the first vault’s order to be “lost.” When GMX finishes that first order, the mismatch in queue.requestKey triggers a revert or finalizes incorrectly—freezing the first vault’s partial flow. Thus, one vault’s order can effectively break all other vaults.

By allowing a single GmxProxy to service multiple vaults, the protocol enables a cross-vault overwrite scenario for pending GMX orders. This leads to indefinite stuck flows for the vault whose order was overwritten. The recommended fix is either restrict each GmxProxy to exactly one vault or implement multi-order tracking so cross-vault overwrites cannot occur.


Technical Details

  • GmxProxy: Holds an OrderQueue queue; with a single bytes32 requestKey. Each createOrder() or settle() call sets queue.requestKey to the newly created order’s ID.

  • Multiple Vaults: If vault A calls createOrder(), it stores requestKey A. Vault B then calls createOrder(), overwriting that queue with requestKey B.

  • Callback Execution: GMX eventually executes or cancels the first order for vault A. The callback tries to match requestKey == queue.requestKey but sees it was overwritten by vault B’s new order. The callback reverts or uses the new key, leaving vault A in a permanent partial flow.

Proof of Cross-Vault Overwrite

  1. VaultA calls createOrder()queue.requestKey = keyA.

  2. VaultB calls createOrder()queue.requestKey = keyB (overwrites keyA).

  3. GMX finishes order for VaultA (the old keyA). The callback sees queue.requestKey == keyB. Mismatch → revert or mismatch finalization.

  4. VaultA’s flow never finalizes, stuck in flow != FLOW.NONE.


Impact

  1. Cross-Vault DoS: If any vault overwrites the queue while an older order for a different vault is still pending, that first vault’s order is irrecoverably “lost.” The vault remains locked out of subsequent flows, since flow never resets.

  2. Impossible to Resolve: The re-entrancy or repeated attempts do not fix the mismatch. The system cannot revert to the lost keyA once overwritten.


Exploit Scenario

Though it requires that multiple vaults are indeed sharing a GmxProxy, an attacker or even normal usage across two teams (Vault A and Vault B) can inadvertently cause indefinite blockages:

  1. Vault A does a big deposit or partial close, creating an order.

  2. Vault B quickly calls a new order for a separate deposit or swap.

  3. GMX finalizes Vault A’s order, sees the new requestKey, fails → Vault A is stuck.


Recommendations

Option A: Disallow Shared Proxy

Explicitly enforce that GmxProxy can only be linked to one PerpetualVault.

  • For instance, in setPerpVault(...), if perpVault is already set, revert if a second call tries to set a new vault address.

  • Update documentation to clearly state each vault must deploy its own GmxProxy.

Option B: Support Multiple In-Flight Orders

Convert the single queue.requestKey into a mapping from bytes32 => OrderInfo so each newly created GMX order is tracked separately.

  • On callback, afterOrderExecution() matches the exact key, finalizes the correct vault’s flow, and does not overwrite or conflict with other vaults’ orders.

Updates

Lead Judging Commences

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

Admin is trusted / Malicious keepers

Please read the CodeHawks documentation to know which submissions are valid. If you disagree, provide a coded PoC and explain the real likelihood and the detailed impact on the mainnet without any supposition (if, it could, etc) to prove your point. Keepers are added by the admin, there is no "malicious keeper" and if there is a problem in those keepers, that's out of scope. ReadMe and known issues states: " * System relies heavily on keeper for executing trades * Single keeper point of failure if not properly distributed * Malicious keeper could potentially front-run or delay transactions * Assume that Keeper will always have enough gas to execute transactions. There is a pay execution fee function, but the assumption should be that there's more than enough gas to cover transaction failures, retries, etc * There are two spot swap functionalies: (1) using GMX swap and (2) using Paraswap. We can assume that any swap failure will be retried until success. " " * Heavy dependency on GMX protocol functioning correctly * Owner can update GMX-related addresses * Changes in GMX protocol could impact system operations * We can assume that the GMX keeper won't misbehave, delay, or go offline. " "Issues related to GMX Keepers being DOS'd or losing functionality would be considered invalid."

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.

Support

FAQs

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