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

GMX swaps are not possible with two hop addresses in swapPath

Summary

GMX swaps with more than one hop address will revert when GMX validates the execution fee sent from Gamma during order creation, as the fee sent will be less than what GMX expects.

Vulnerability details

In the Gamma protocol, when creating an order for a normal GMX swap, there is an array of addresses passed as a metadata parameter by the keeper called swapPath (encoded in bytes):

The swapPath can have a maximum of 2 addresses (confirmed by the sponsor), meaning its maximum length is 2.

When GmxProxy.sol is called to create the order for the swap, it first calculates the positionExecutionFee:

This will be the amount sent to GMX contracts as a native token to cover the execution cost of the order.

After sending the required data and calling the GMX contracts for the creation of the order, the GMX protocol validates that the amount sent for the execution transaction is sufficient. For that it compares the amount sent by Gamma to its own calculated executionGasLimit, the calculation is similar than to the one done by Gamma, but with a few tweaks.

The main issue arises from how GMX calculates the executionGasLimit value, which differs from Gamma’s approach and leads to discrepancies.

In GMX contracts calculation is as this:

ExecutionGasLimit

EstimatedGasLimit

But in Gamma protocol the executionGasLimit is calculated as follows:

GammaCode

As we can see, when calculating the estimatedGasLimit in Gamma, they don't account for the swapPath.length. In contrast, GMX does consider this value.

As a result, when swapPath.length is greater than 1, the positionExecutionFee calculated by GMX will always be higher than the one calculated by Gamma.

If the positionExecutionFee sent by Gamma is lower than the amount expected by GMX, the transaction will revert.

function validateExecutionFee(DataStore dataStore, uint256 estimatedGasLimit, uint256 executionFee, uint256 oraclePriceCount) internal view {
uint256 gasLimit = adjustGasLimitForEstimate(dataStore, estimatedGasLimit, oraclePriceCount);
uint256 minExecutionFee = gasLimit * tx.gasprice;
@> if (executionFee < minExecutionFee) {
revert Errors.InsufficientExecutionFee(minExecutionFee, executionFee);
}
}

Thus, making not possible swaps with two hop addresses.

Attack Path

Variables extracted from GMX by keys:

  • swapOrderGasLimit = 2_500_000

  • gasPerSwap = 1_000_000

  • baseGasLimit = 1_647_830

  • estimatedGasFeePerOraclePrice = 1_936_848

  • multiplierFactor = 1e30

Gamma variables:

  • swapPath.length = 2 (parameter by the keeper)

  • callbackGasLimit = 2_000_000 (storage variable)

  • oraclePriceCount = 5 (hardcoded)

GMX variables:

  • swapPath.length = 2 (passed by Gamma)

  • callbackGasLimit = 2_000_000 (passed by Gamma)

  • oraclePriceCount = 3 + swapPath.length (5)

Calculation on GMX:

$ baseGasLimit = baseGasLimit + (estimatedGasFeePerOraclePrice * oraclePriceCount)$

GmxExecutionGasLimit = 17_832_070

Calculation on Gamma:

GammaExecutionGasLimit = 16_832_070

As GammaExecutionGasLimit < GmxExecutionGasLimit, swaps with more than 1 hop will always revert in the creation of the order.

Root cause

  • The formula of estimatedGasLimit on GmxProxy.sol don't account for swapPath.length:

Impact

  • Gmx swaps with more than one hop address, can't be done. Breaking at some level the functionality of the protocol, hence the medium severity.

Recommendations

Based on GMX contracts, add the swapPath.length into the formula to calculate the estimatedGasLimit:

Updates

Lead Judging Commences

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

finding_swapPath_does_not_increase_the_executionFee

Likelihood: Low/Medium, when swapPath has more than 1 item. Impact: Medium/High, could lead to not enough fee collected to execute the transaction in GMX

Support

FAQs

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

Give us feedback!