PerpetualVault
has logic to account for every different type of situation / deposit type, depending on the user action, the current state of the position, and the keeper inputs -> there are several different moving pieces and many different code paths that can be executed to fulfill a deposit.
However, when a user deposits when the strategy is currently open
, 1x Long Leverage
-> there is a situation where the state is not updated properly and left in a state that will not allow any further deposits
or withdraws
.
Lets follow the exact path of a user depositing, when the protocol position is currently: open
and 1x long leverage
:
User calls deposit -> flow
is set to FLOW.DEPOSIT
becasue, NextActionSelector.INCREASE.ACTION
-> the function runNextAction
is called:
Here, the function accurately deletes nextAction
-> which sets NEXT_ACTION = NONE
The last line of code of this function that is executed is _runSwap
The important piece here is that the inputs are generated off-chain and the keeper calls this function and inputs those values. Of those values is metadata
metadata
can include both swap parameters for dexSwap
and gmxSwap
or just 1 of them. Since it is 1x long leverage
, there will only be a swap done
For this situation, the keeper only sends the dexSwap
for the metadata
, meaning the entire swap will only take place on dexSwap
-> which is the preferred method of the protocol. The inline documentation also reaffirms that there can be either both swap paths sent in or just 1 - the code itself also has logic to handle the situation when both gmxSwap
and dexSwap
are sent in, or just 1 of them.
_runSwap
is executed, passing in ONLY dexSwap
for the metadata
As can be seen in the function above, swapProgressData
is used (and is also never deleted / reset)
The FINAL line of code that is executed in this function under this scenario, is _mint
-> becasue the current flow
is still FLOW.DEPOSIT
The inline documentation before calling _mint
also shows that it is expected that the global state will be updated (which it isnt)
** THE FINAL AND END OF THIS CODE PATH -> _mint
all of the inline documentation is original, i did not modify or edit it at all. As can be seen, this is the end of deposit flow
At the end of this, the user is accounted for their shares and the totalShares is updated to include the new shares, the user also gets refunded any excess fee (becasue they did not go through GMX for the swap)
BUT THAT IS IT, THATS THE END OF THE PATH. Global state variables left populated and never deleted include:
flow
-> it is still FLOW.DEPOSIT
swapProgressData
is never deleted
Users will not be able to deposit
or withdraw
becasue flow
is still FLOW.DEPOSIT
They wont be able to, becasue, at the beginning of the deposit
and withdraw
functions, there is this check to ensure there is no active flow
:
Also, the documentation in the contest page says that a key invariant
-> is that swapProgressData
should always be empty after an action is completed.
Manual Review
All of the other scenarios handle updating the state changes after completing an action by either calling handleReturn
or updateState
-> and updateState
has logic that is specific to handle this scenario's code path , but it is never called!
nextAction
was deleted within the code path, therefore it is NextActionSelector.NO_ACTION
-> which is exactly what this function handles. IF called, this will update the state PERFECTLY.
delete flow
delete swapProgressData
Which are the EXACT variables that need to be updated and are left populated.
Call updateState
after the _mint
and update the state properly.
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.