In case where the perp vault position gets liquidated within a user's withdrawal flow , the user would unintentionally incur losses , no choice is given to the user if he wants to cancel his withdrawal in cases of liquidation and wait for a new healthier position to be open on GMX.
Consider the following ->
1.) There is an active perp vault position on GMX with leverage > 1x.
2.) A user has requested a withdraw via withdraw()
->
the flow is assigned as WITHDRAW at L255 , and since if (curPositionKey != bytes32(0))
(cause a position is open on GMX with leverage > 1x) ->
The withdrawing user (for example) expects around ~1000 USDC (just for example , can be way higher) amount back after his withdrawal i.e. when he submitted his withdrawal via the withdraw function.
And , next action is WITHDRAW_ACTION and _settle()
is called.
3.) Inside _settle()
a settle order is created (routed through GmxProxy.sol) ->
[https://github.com/CodeHawks-Contests/2025-02-gamma/blob/main/contracts/PerpetualVault.sol#L964]
4.) After a successful settle order , afterOrderExecution()
would be invoked by GmxProxy and nextAction would be assigned as WITHDRAW_ACTION
->
5.) Now lets say the position in GMX got liquidated , therefore afterLiquidationExecution()
would be invoked (L563) ->
and since sizeInTokens
would be 0 (fully liquidated ) then curPositionKey
would be deleted and since flow was WITHDRAW , nextAction.selector would be assigned WITHDRAW_ACTION
6.) Then keeper would invoke runNextAction()
and since nextAction is WITHDRAW_ACTION
, this branch would be invoked (L371-L381)->
Therefore _withdraw()
is invoked.
7.) And inside withdraw , this branch would be invoked since curPositionKey = 0 (L1102) , ->
this means that in the _handleReturnFunction
we would enter this branch (L1133) ->
8.) Say , the current balance of the collateral token in the perp vault after liquidation is 500 USDC (just for example) , and user owns 50% of the shares (just for example) , therefore the amount calculated would be 500/2 = 250 and these 250 tokens are then transferred with _transferToken(depositId, amount);
9.) Therefore , the user has unintentionally suffered a loss of 750 , when he initiated the withdrawal he calculated his risks according to the position size open in GMX but withing the flow of this the position got liquidated.
In the design of the perp vault if the position gets liquidated a new position would be opened on GMX by gamma keepers , a user who was withdrawing might want that if position got liquidated then cancel the withdraw flow cause he might want to withdraw later now when a new position would be opened and be bullish that it makes profit (using the returned amount after liquidation a new position can be opened) , in short he would never want to withdraw if he would be incurring losses .
The user has unintentionally suffered a loss of 750 , when he initiated the withdrawal he calculated his risks according to the position size open in GMX but withing the flow of this the position got liquidated.
Manual analysis
A user can mention a flag where if set to true then the withdrawal flow would be cancelled if liquidated or threshold hit and if set to false (emergency kind of wothdrawal) then even if liquidated the user would receive the withdrawal amount (even in losses).
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.
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.