Description:
_noneFlow function checks if the flow is not none and if so, reverts. This function gets called at the start of these functions in PerpetualVault:
deposit
withdraw
run
claimCollateralRebates
This makes the flow very important, because if it is not deleted when it must be, it stops users and the keeper from interacting with the protocol.
Consider this scenario:
A user calls PerpetualVault::deposit, where flow gets set to FLOW.DEPOSIT. At this time, position is open, so next action gets set to INCREASE_ACTION.
The keeper calls PerpetualVault::runNextAction. At this time the position is 1x long, so _runSwap gets called:
Let's assume the _protocol variable, which was encoded in metadata (provided by keeper), is equal to PROTOCOL.DEX, which means that the swap occurs on ParaSwap. After this, _mint is called, because the current flow is still FLOW.DEPOSIT:
Flow is not deleted anywhere in _mint function, which leaves it as FLOW.DEPOSIT and locks the main functions of the protocol until owner calls setVaultState function.
Additional note:
PerpetualVault::_cancelFlow cannot be called by the keeper to solve this situation, because safeTransfer will most likely revert, due to the contract not having enough collateral tokens, since they were swapped to index tokens, because current position is 1x long.
Impact:
Halted protocol until owner sets the flow. This can get ugly quick in a situation where nobody is monitoring the state of the system, because a signal could come to close the position or to switch position side, and keeper will not be able to do it, due to _noneFlow function causing reverts. Also during this time users will not be able to deposit or withdraw, so if the market is moving down rapidly, it will lead to losses that users cannot do anything about, and the keeper not being able to close the position or change side, until owner intervenes.
Likelihood:
This issue will occur commonly in vaults that have 1x leverage.
Proof of Concept:
Add the test provided below to PerpetualVault.t.sol
Run the test with this command: forge test --mt test_Flow_NotDeleted --via-ir --rpc-url <YOUR_RPC_URL_HERE> -vv
Read the comments in the provided test to understand what's happening
Recommended Mitigation:
Add a call to _finalize function with empty data after _mint, which will delete 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.