The _settle() function in PerpetualVault.sol exhibits a similar issue to the _settleCashBalance() function from Escrow.sol. Specifically, it attempts to settle outstanding fees by calling settle() on IGmxProxy, but the value being settled is not correctly calculated. This could lead to incorrect free collateral calculations, unintended liquidation, or incorrect deduction of protocol reserves.
Collateral Calculation Bug Leads to Excess Liquidation
Step 1: A user attempts to withdraw collateral.
Step 2: _settle() is called to ensure all outstanding fees are deducted.
Step 3: Since initialCollateralDeltaAmount = 0, no fees are settled.
Step 4: The protocol incorrectly assumes the position has insufficient collateral and triggers unnecessary liquidation.
Step 5: The user's funds are liquidated or reduced unexpectedly due to incorrect free collateral calculation.
Setup
A trader has $10,000 USDC deposited as collateral in the PerpetualVault.
The trader opens a long position worth $50,000 (5x leverage).
The GMX protocol charges a borrowing fee of $50 per hour.
The trader leaves the position open for 10 hours, accumulating a total fee of $500.
The trader wants to withdraw their collateral (thinking they have $10,000 - $500 = $9,500 available).
_settle() is called to deduct the fees before withdrawal.
Incorrect Behavior in _settle()
The problem in _settle() is that it does not correctly deduct the pending fees before processing the withdrawal.
Current Code:
What’s Wrong?
_settle() is not deducting the actual fees before settlement.
Since initialCollateralDeltaAmount = 0, GMX does not properly deduct fees.
This leaves $500 in outstanding debt in the trader’s position.
GMX sees that the position has less collateral than required, triggering forced liquidation.
How the Liquidation Happens
User Requests Withdrawal:
The user initiates a withdrawal request expecting to receive $9,500 after fees.
_settle() is called to deduct the $500 fee before proceeding.
Settlement is Incorrect:
_settle() sends an incorrect settlement request because initialCollateralDeltaAmount = 0.
GMX does not deduct the $500 fee because the function never tells it to.
GMX Thinks There is Insufficient Collateral:
GMX checks the trader’s collateral and sees they are missing $500.
The protocol assumes the user is undercollateralized.
Forced Liquidation:
GMX forcefully closes the user’s position, selling the trader’s collateral to cover the debt.
The trader loses $10,000 of collateral instead of just paying the $500 fee.
Severity: HIGH
Exploitability: MODERATE
Potential Loss: Users may lose their collateral due to miscalculated liquidation, or the protocol may deduct too much from reserves.
Execution Risk: The system may miscalculate available collateral, leading to user funds being stuck, overcharged, or liquidated prematurely.
Manual Review
We need to properly account for pending fees and ensure GMX deducts them before settlement.
Fixed Code:
Please read the CodeHawks documentation to know which submissions are valid. If you disagree, provide a coded PoC and explain the real likelihood and the detailed impact on the mainnet without any supposition (if, it could, etc) to prove your point.
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.