After the entire flow is complete it needs to be finalized i.e. flow , flowData and swappedData needs to be cleared out ( as mentioned in the invariant section in readme After all actions completed, nextAction, swapProgressData should be empty.
) , but under a certain case this is violated and the flow is not emptied out.
Consider the following ->
1.) There's an active perp vault position open on GMX and it is 1x Long.
2.) A user deposits by calling deposit()
->
[https://github.com/CodeHawks-Contests/2025-02-gamma/blob/main/contracts/PerpetualVault.sol#L215]
the flow is assigned as DEPOSIT at L226 ,
and since positionIsClosed
is false (as mentioned there is a position open on GMX) next action would be INCREASE_POSITION
->
3.) Keeper would call runNextAction()
->
and since next action is INCREASE_ACTION
this branch would be executed (remember the position was 1x long)->
4.) Therefore , _runSwap()
is invoked (L976) and assuming that metadata
length == 1 (which was passed in runNextAction) we enter this branch ->
5.) _doDexSwap()
is performed , and since flow is DEPOSIT we would enter the if (flow == FLOW.DEPOSIT) {
branch and perform _mint
of the swapped amount and shares are assigned to the user , and then we return.
6.) But , we did not clear out the flow and the nextAction , meaning we should have called _finalize(hex'')
after the mint operation to correctly finalize the flow , hence breaking the invariant. Now no further deposits/withdraw or run() functions can take place due to the _noneFlow()
check.
There can be another case with the same issue , where a GMX swap requested after a swap on DEX which would update swappedData.swapped and assign swappedData.remaining but that order failed and on retry the remaining swap would be done by DEX and then similar issue as above where _mint
would be called but swappedData would not be cleared meaning that for the next swap swappedData.swapped would be way higher (L1005) and the user would receive way higher shares.
We did not clear out the flow and the nextAction (it would still point to INCREASE_ACTION) , meaning we should have called _finalize(hex'')
after the mint operation to correctly finalize the flow , hence breaking the invariant.
Manual analysis
We should have called _finalize(hex'')
after the mint operation to correctly finalize the flow.
Likelihood: Medium/High, - Leverage = 1x - beenLong = True - positionIsClosed = False - Metadata → 1 length and Dex Swap Impact: Medium/High, DoS on any new action before the admin uses setVaultState Since this seems to be the most probable path for a 1x PerpVault, this one deserves a High.
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.