The VaultReader.getPnl() function incorrectly calculates PnL when processing partial withdrawals by using the entire position size instead of the proportional size specified in the withdrawal request. This leads to withdrawers being penalized/credited for the full position’s PnL instead of their proportionate share in case if PnL is negative.
In PerpetualVault._withdraw(), when a user attempts to withdraw a subset of their shares (partial withdrawal), the protocol calls VaultReader.getPnl() with a sizeDeltaUsd calculated as:
This sizeDeltaInUsd represents only the portion of the position being withdrawn (user proportion according shares weight like userShares/totalShares). However, VaultReader.getPnl() forces usePositionSizeAsSizeDeltaUsd = true in its call to GMX’s getPositionInfo(), overriding the intended sizeDeltaUsd with the full position size:
Example Scenario:
Total position size: 100,000;
User withdraws 10% (shares / totalShares = 0.1) → valid sizeDeltaInUsd = 10,000
Total Position PnL: -5,000 (5% loss)
User portion PnL for withdrawal: -500 (10% of 5,000)
Actual user portion PnL due to bug: -5,000 full loss applied invalidly to the 10,000 withdrawal.
Unfair Losses: Withdrawing users are penalized with the entire position’s loss, even if they only exit a small portion.
Manual review
Modify VaultReader.getPnl() to respect the passed sizeDeltaUsd by setting usePositionSizeAsSizeDeltaUsd = false.
Likelihood: Medium/High, every withdrawal from a short or leveraged vault that is not liquidated and has a negative PnL. Impact: High, subtract collateralDeltaAmount from the entire PnL of the position instead of the delta amount PnL. DoS or loss of funds
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.