The Perpetual Vault contract enables 1x Long positions by swapping the collateral token deposited by users into the index token. These swaps are executed using ParaSwap and GMX. To facilitate these transactions, the PerpetualVault
contract includes a _runSwap
function that takes a bytes array containing swap data, the swap direction, and the current prices of the market tokens. The swap data is generated off-chain and may contain only ParaSwap data, GMX swap data, or both as per the code natspec.
The problem arises when a user deposits collateral while a 1x Long position is active. The protocol is designed to add the new collateral to the existing position. This process is managed via a keeper mechanism. The nextAction.selector
is set toINCREASE_ACTION
, then the keeper calls runNextAction
, the function invokes _runSwap
to swap the newly added collateral into the index token.
The issue occurs if the swap data includes only ParaSwap data. The collateral swap is executed using _doDexSwap
, but the flow variable is not reset after the transaction, leading to an inconsistent state.
PerpetualVault.sol#L1003-L1006
The vault has an active long position with 1x leverage.
A user deposits collateral into the vault, flow is set to DEPOSIT.
The keeper generates swap data that only involves swapping via _doDexSwap
.
The swap takes place, but the flow does not get reset as intended.
This leads to the loss of major protocol functionality.
This vulnerability results in a complete denial-of-service (DoS) for critical protocol functions such as:
Deposits
Withdrawals
Opening / Closing positions
Claiming collateral rebates
The following test case demonstrates the issue using the provided ParaSwap swap data in the test suite:
This test case confirms that after the swap is completed, the flow state remains DEPOSIT
(1
), preventing further protocol actions.
Manual Review
Foundry
To resolve this issue, call the _finalize()
function at the end of the deposit flow during a swap that takes place via _runSwap
when ParaSwap is the sole DEX used. This resets the global state variables and allows the protocol to continue operating as intended.
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.