DeFiFoundry
50,000 USDC
View results
Submission Details
Severity: high
Valid

User Operations gets blocked for 1x long position.

Summary

flow is not deleted after the deposit flow is finished for a 1x leverage long position. Hence all the user operations in the perpetual contract is blocked.

Vulnerability Details

Current position is an open 1x long .

Alice deposited some collatteral tokens.

Keeper calls runNextAction(bytes[] memory metadata, .. ) with metadata containing only the paraswap details and hence no call for gmx market will be done.

Since the nextaction.selector is INCREASE_ACTION , the protocol is PROTOCOL.DEX and the flow is FLOW.DEPOSIT the below code is executed

if (_protocol == PROTOCOL.DEX) {
uint256 outputAmount = _doDexSwap(data, isCollateralToIndex);
// update global state
if (flow == FLOW.DEPOSIT) {
// last `depositId` equals with `counter` because another deposit is not allowed before previous deposit is completely processed
=> _mint(counter, outputAmount + swapProgressData.swapped, true, prices); //@audit-issue flow is not deleted, hence no more deposit happens.
} ....
return true;
}

Since this is the last action for a DEPOSIT.FLOW , flow and flowdata needs to be deleted inorder to continue the contracts operation. (gmxLock is never enabled since no gmx operations is required for this)

POC

Here once the keeper calls runNextAction() to complete the alice's deposit , all other operations is blocked. When user tries to call any operation it reverts with FlowInProgress() error.

run cmd : forge test --match-test test_dhank_Run_Paraswap --rpc-url https://arbitrum-mainnet.infura.io/v3/{api} -vv

function test_dhank_Run_Paraswap() external {
address keeper = PerpetualVault(vault).keeper();
address alice = makeAddr("alice");
depositFixture(alice, 1e10);
MarketPrices memory prices = mockData.getMarketPrices();
bytes memory paraSwapData = mockData.getParaSwapData(vault);
bytes[] memory swapData = new bytes[](1);
swapData[0] = abi.encode(PROTOCOL.DEX, paraSwapData);
vm.prank(keeper);
PerpetualVault(vault).run(true, true, prices, swapData);
IERC20 collateralToken = PerpetualVault(vault).collateralToken();
uint256 amount = 1e10;
deal(address(collateralToken), alice, amount);
deal(alice, 1e18);
uint256 executionFee = PerpetualVault(vault).getExecutionGasLimit(true);
vm.startPrank(alice);
collateralToken.approve(vault, amount);
PerpetualVault(vault).deposit{value: executionFee * tx.gasprice}(amount);
vm.stopPrank();
prices = mockData.getMarketPrices();
vm.prank(keeper);
PerpetualVault(vault).runNextAction(prices, swapData);
bool isGmxlock = PerpetualVault(vault).isLock();
assertEq(isGmxlock, false);
(PerpetualVault.NextActionSelector selector, ) = PerpetualVault(vault).nextAction();
assertEq(uint8(selector), 0);
depositFixture(alice, 1e10);
}

Impact

Since since no action for deleting the flow is done after the DEPOSIT.FLOW is finished for a 1x long position market , all the operations are blocked

Tools Used

Manual

Recommendations

Call finalize(hex'') after the _mint to delte the flow and flowData

Updates

Lead Judging Commences

n0kto Lead Judge 5 months ago
Submission Judgement Published
Validated
Assigned finding tags:

finding_deposit_1x_long_dex_positionIsOpened_DoS_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.

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.