DeFiFoundry
50,000 USDC
View results
Submission Details
Severity: low
Invalid

Unaccounted GMX Position Fees Lead to Leverage Drift and Increased Liquidation Risk

Summary

The PerpetualVault contract does not account for GMX's position fees when calculating position sizes. These fees reduce the collateral amount while maintaining the same position size, causing the effective leverage to increase over time. This "leverage drift" exposes positions to higher liquidation risk than intended.

Vulnerability Details

When increasing positions on GMX, fees are deducted from the collateral amount during order execution. This happens in ExecuteOrderUtils.processOrder() (OrderHandler: 0xe68CAAACdf6439628DFD2fe624847602991A31eB), specifically in the IncreasePositionUtil.processCollateral() function:

function processCollateral(...) internal returns (int256, PositionFees memory) {
// ...
// Calculate fees
PositionFees memory fees = PositionPricingUtils.getPositionFees(...);
// Deduct fees from collateral
collateralDeltaAmount -= fees.totalCostAmount.toInt256();
// Update pool states
// ...
}

getPositionFees() calculates the total fees associated with a position in a market and subtract it from collateralDeltaAmount

Also, note that unlike withdraw, deposit doesn't have the _settle() step before increasing positions

On the other hand, the position size remains unchanged during this process:

function increasePosition(...) external {
// ...
// Position size is increased without accounting for fee reduction
cache.nextPositionSizeInUsd = params.position.sizeInUsd() + params.order.sizeDeltaUsd();
params.position.setSizeInUsd(cache.nextPositionSizeInUsd);
params.position.setSizeInTokens(params.position.sizeInTokens() + cache.sizeDeltaInTokens);
// ...
}

The PerpetualVault contract calculates position sizes without considering these fees:

uint256 sizeDelta = prices.shortTokenPrice.max * amountIn * leverage / BASIS_POINTS_DIVISOR;

Proof of Concept

Consider a position with:

  • Initial deposit: 100 USDC

  • Target leverage: 2x

  • Position size: 200 USDC

  • Initial leverage: 200/100 = 2x

After fees of 1 USDC:

  • Remaining collateral: 99 USDC

  • Position size: 200 USDC (unchanged)

  • Actual leverage: 200/99 ≈ 2.02x

Impact Details

The leverage drift increases liquidation risk by making positions more leveraged than intended, potentially leading to unexpected liquidations. Additionally, it creates risk management issues, as the vault's actual exposure may exceed its stated parameters.

Tools Used

Manual Review

Recommendations

Adjust position size calculations to account for fees, similar to how it's done in PerpetualVault::_withdraw.

function _createIncreasePosition(
bool _isLong,
uint256 acceptablePrice,
MarketPrices memory prices
) internal {
// Calculate expected fees
uint256 fees = _calculateExpectedFees(amountIn, leverage);
// Adjust amount for fees
uint256 effectiveAmount = amountIn - fees;
// Calculate size with fee adjustment
uint256 sizeDelta = prices.shortTokenPrice.max * effectiveAmount * leverage / BASIS_POINTS_DIVISOR;
// ... rest of the function
}
function _calculateExpectedFees(uint256 amount, uint256 leverage) internal view returns (uint256) {
return PositionPricingUtils.getPositionFees(
amount,
leverage,
// other necessary parameters
);
}
Updates

Lead Judging Commences

n0kto Lead Judge 7 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Appeal created

krisrenzo Submitter
7 months ago
n0kto Lead Judge
6 months ago
n0kto Lead Judge 6 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

Informational or Gas

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.

Support

FAQs

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