Summary:
The protocol’s execution fee mechanism is implemented in both PerpetualVault and GmxProxy. In PerpetualVault, refund logic in functions like _mint and _handleReturn uses a global counter (e.g. depositInfo[counter]) to determine which deposit’s fee to refund. Meanwhile, GmxProxy’s refundExecutionFee function (callable only by the vault) does not cross‑reference the refunded fee with a deposit‑specific identifier. As a result, if multiple orders (and associated deposits) are processed in close succession—or if asynchronous callbacks occur out‑of‑order—the refunded execution fee may be misattributed to the wrong deposit. This misattribution violates the invariant that each depositor’s fee should match exactly the cost incurred by their order.
Where the Issue Is:
In PerpetualVault:
The refund logic in functions such as _mint and _handleReturn uses the global deposit counter to access deposit information:
This means that the fee refund is applied based solely on the latest value of counter, rather than on a deposit‑specific ID.
In GmxProxy:
The refund is executed via:
There is no mechanism here that binds the refunded fee to a particular order or deposit.
Root Cause:
The execution fee refund mechanism lacks a binding between the asynchronous order flows and the deposit-specific data. By using a global counter (depositInfo[counter]) to reference fee data, the vault does not ensure that the fee refund corresponds to the deposit that actually incurred the fee. This creates a risk that, if multiple orders are initiated before earlier callbacks are processed, the refund will be misattributed to the most recent deposit rather than the intended one.
Attack Path / Foundry Proof-of-Concept:
Scenario Setup:
The vault receives a deposit and initiates an order (Order1). At this point, depositInfo[counter] holds the fee information for Order1.
Before the callback for Order1 is processed, the vault (or an attacker influencing the sequence) triggers a second deposit and order (Order2). The global counter is incremented and depositInfo[counter] now holds data for Order2.
Callback Mismatch:
Later, when the callback for Order1 is processed, the refund logic in PerpetualVault references depositInfo[counter]. However, since counter now points to Order2’s data, the refund is computed based on Order2’s execution fee rather than Order1’s.
Outcome:
The depositor associated with Order1 either receives an incorrect fee refund or, conversely, Order2’s fee is misapplied. This misalignment disrupts the invariant that each depositor’s fee should only reflect the cost incurred for their specific order.
An attacker who can force rapid successive order submissions (or influence the timing of callbacks via gas price manipulation or network delays) can deliberately cause this misattribution, effectively reducing the effective fee cost on malicious orders or overcharging honest users.
-->This PoC clearly demonstrates that by relying on a global counter (i.e., depositInfo[counter]) to handle execution fee refunds, the protocol misattributes refunds when multiple deposits/orders are processed in rapid succession. The refund function ends up applying the refund data of the latest deposit (e.g., Depositor B) rather than that of an earlier deposit (e.g., Depositor A). This misalignment directly violates the protocol’s invariant that each depositor is only charged (or refunded) for the fee corresponding to their own order, potentially enabling fee manipulation or unintended fund transfers.
File: src/FakePerpetualVault.sol
Compile and Run Tests:
From the project root, run:
The test should pass, demonstrating that the refund is misattributed due to the global counter referencing the latest deposit rather than the intended one.
Impact:
Fee Accounting Violation: Each depositor may not receive (or may be overcharged for) the correct execution fee, breaking the fairness of the protocol.
Financial Risk: Persistent misattribution of fees can lead to imbalances in the vault’s accounting, potentially allowing an attacker to extract excess funds or dilute other depositors’ positions over time.
State Inconsistency: The protocol’s invariant that every deposit’s execution fee is matched exactly to its incurred cost is violated, undermining the trust in vault accounting.
Recommendation:
Bind Refunds to Deposit IDs:
At the time an order is initiated, store a unique deposit ID (or a mapping linking the order’s unique request key to the deposit data) rather than relying solely on a global counter. In the refund callback, verify that the deposit associated with the refund matches the deposit that incurred the fee.
Cross-Check Refund Data:
Modify the refund logic in both PerpetualVault and GmxProxy so that the refund amount is cross‑checked against the recorded fee for the specific deposit. This prevents misattribution if orders overlap.
Enforce Sequential Flow (if feasible):
Alternatively, ensure that a new order cannot be initiated until the previous order’s callback is fully processed, thereby eliminating the possibility of overlapping fee refund states.
Severity:
High – This vulnerability directly undermines the protocol’s core accounting principles and can lead to exploitable discrepancies in fee management, potentially resulting in financial loss.
Tools Used:
Manual.
Likelihood: Medium/High, when withdraw on a 1x vault. Impact: High, the fees will be distributed to the last depositor and not the withdrawer.
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.