In the PerpetualVault contract, after each flow completes, the flow variable is expected to reset to FLOW.NONE. However, in a specific deposit flow where:
deposit() is called when positionIsClosed == false.
runNextAction() executes.
_runSwap() is triggered due to _isLongOneLeverage(_isLong) and _nextAction.selector == NextActionSelector.INCREASE_ACTION.
_mint() is called when metadata.length == 1, _protocol == PROTOCOL.DEX, and flow == FLOW.DEPOSIT.
The flow is not finalized after _mint(), meaning flow remains FLOW.DEPOSIT instead of being reset to FLOW.NONE. This blocks further deposit and withdrawal functions, creating a stuck state. Additionally, calling cancelFlow() in this state leads to an unintended double refund of the execution fee, as the fee was already refunded during _mint().
The deposit process does not complete correctly when _mint() is executed in _runSwap().
flow remains stuck at FLOW.DEPOSIT, preventing new deposits or withdrawals.
If cancelFlow() is called, it refunds the execution fee again, even though _mint() has already issued a refund of the remaining execution fee.
Users cannot deposit or withdraw funds due to the stuck flow state.
Execution fee is refunded twice if cancelFlow id called, leading to unintended fund distribution.
Potential for denial-of-service (DoS) attack by repeatedly triggering this condition.
paste in /test/PerpetualVault.t.sol
Ensure flow Resets to FLOW.NONE After _mint()by calling _finalize.
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.