In the case where current position is being closed and a new position is being opened , if the market decrease order fails to close the current position , then the close order hook in perp vault (afterOrderCancellation()
) does not reset the nextAction breaking a core invariant in the README.
Consider the following (this flow has been confirmed by the sponsor) ->
1.) There's a perp vault position open on GMX with leverage > 1x. This position got liquidated and the perp vault received the remaining amount after liquidation from GMX.
2.) This position is being closed and a new one will be opened ( reason can be anything for ex - after liquidation etc the current position will be closed and a new one will be opened) , for that the keeper would call the run()
function with isOpen
as true and currently positionIsClosed
is false (since positionIsClosed is not updated on liquidation in afterLiquidationExecution()) , therefore this branch in the run()
function gets executed (L314-L325) ->
what this does is , closes the current position and requests to open a position in next action , therefore we see that ->
nextAction.selector = NextActionSelector.INCREASE_ACTION;
and , since leverage > 1x , _createDecreasePosition
will be called with 0 values to denote current position is being closed.
Also the flow is assigned to SIGNAL_CHANGE at L301.
3.) In _createDecreasePosition()
a market order of type MarketDecrease is created ->
[https://github.com/CodeHawks-Contests/2025-02-gamma/blob/main/contracts/PerpetualVault.sol#L936]
4.) For some reason this was not executed on GMX and now this order needs to be cancelled in perp vault , therefore the keeper would call cancelOrder()
, and after the order is cancelled the afterOrderCancellation()
hook will be called in perp vault ->
We can see that if flow = SIGNAL_CHANGE then the else block at the end would be triggered i.e.
5.) flowData and flow are deleted , but nextAction has not been deleted and is still assigned to INCREASE_ACTION
breaking the invariant mentioned in the README After all actions completed, nextAction, swapProgressData should be empty
, this is because nextAction is supposed to be registered off-chain for actions and will be completed on-chain by keepers , therefore , nextAction is incorrect.
nextAction has not been deleted and is still assigned to INCREASE_ACTION
breaking the invariant mentioned in the README After all actions completed, nextAction, swapProgressData should be empty
Manual analysis
Delete the nextAction also if order cancelled.
Normal behavior, the keeper will retry to increase the position. That’s why there is this condition in `_createIncreasePosition`: ``` if (flow == FLOW.DEPOSIT) { amountIn = depositInfo[counter].amount; flowData = vaultReader.getPositionSizeInTokens(curPositionKey); } else { amountIn = collateralToken.balanceOf(address(this)); } ```
Normal behavior, the keeper will retry to increase the position. That’s why there is this condition in `_createIncreasePosition`: ``` if (flow == FLOW.DEPOSIT) { amountIn = depositInfo[counter].amount; flowData = vaultReader.getPositionSizeInTokens(curPositionKey); } else { amountIn = collateralToken.balanceOf(address(this)); } ```
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.