Skip to content
$1,000,000 in security audit grants are live now, Apply here →

Security review · October 2023

GLP to GMX V2 Migration

for GMX

GMX engaged Guardian to review the security of its liquidity migration contracts from GLP to GMX V2 Markets. From the 9th of October to the 23th of October, a team of 2 auditors reviewed the source code in scope.

Published
Review window
October 9 to 23, 2023
Language
Solidity
Chains
Arbitrum, Avalanche
Sector
Perpetuals
  • 0 Critical
  • 2 High
  • 6 Medium
  • 2 Low
  • 0 Informational

9 resolved · 1 acknowledged

Scope

Overview

GMX engaged Guardian to review the security of its liquidity migration contracts from GLP to GMX V2 Markets. From the 9th of October to the 23th of October, a team of 2 auditors reviewed the source code in scope.

Findings 10

  1. GLPM-1 High Reduced Redemption Fees Gamed Logical Error Acknowledged
    Location
    GlpMigrator.sol: 263

    Description

    In the _redeemGlp function, users are allowed to make any arbitrary external calls with the redemptionInfo.externalCallTargets and redemptionInfo.externalCallDataList.

    Therefore a user seeking to redeem GLP using the discounted redemption may do so as the external call is executed within the context of the withReducedRedemptionFees modifier.

    Users may abuse the system in a similar way by simply providing an EOA address as the redemptionInfo.receiver rather than the DepositVault, or by using the subsequent external call to transfer out the redeemed tokens to their EOA.

    Recommendation

    Require that the redeemedTokenAmount (or at least a majority, accounting for potential fees & slippage) end up in the DepositVault contract.

    This validation also serves as a safety net in the event that the provided redemptionInfo.externalCallTargets or redemptionInfo.externalCallDataList hold errors.

    Resolution

    GMX Team: Acknowledged and comment added in commit 2de90ca.

  2. GLPM-2 High USDC vs USDC.e Validation Resolved
    Location
    GlpMigrator.sol: 186-188

    Description

    Currently GLP consists of a large amount of USDC.e (bridged USDC), which has a different token address than USDC. On the other hand, GMX V2 pools all use USDC.

    Because a user is required to pass a migrationItem.short.token that matches the cache.market.shortToken, they cannot redeem with USDC.e as tokenOut since the short token validation will revert.

    Rather, the user is forced to redeem for the limited amount of USDC directly so that they can deposit the USDC into the GMX V2 market.

    Recommendation

    In the case of USDC, modify the InvalidShortTokenForMigration check such that USDC.e can still pass and then be swapped for native USDC.

    Furthermore, consider adding a state check after the external calls to check the token balances of the depositVault, to ensure the user has not mistakenly sent the USDC.e to the GMX V2 system and lost it.

    Resolution

    GMX Team: The InvalidLongTokenForMigration and InvalidShortTokenForMigration checks have been removed in commit 3a696d5.

  3. GLPM-3 Medium Additional Ether Lost Logical Error Resolved
    Location
    GlpMigrator.sol: 139

    Description

    If the provided msg.value is greater than the executionFee * migrationItems.length then the excess Ether is not refunded to the user and can be used by the user who calls the migrate function next.

    Recommendation

    Add validation to ensure that msg.value == executionFee * migrationItems.length, otherwise refund any excess Ether to the caller.

    Resolution

    GMX Team: The recommendation has been implemented in commit 58312d4.

  4. GLPM-4 Medium User Forced To Deposit Both Tokens Logical Error Resolved
    Location
    GlpMigrator.sol: 165-191

    Description

    In RewardRouter.sol for GMX V1, function unstakeAndRedeemGlp() requires that _glpAmount > 0:

    require(_glpAmount > 0, "RewardRouter: invalid _glpAmount");

    In the GlpMigrator contract, if a user wants to only redeem for a the long token in a market, and leaves the GlpRedemption short with migrationItem.short.glpAmount = 0, the migration will fail due to the above revert.

    This is unexpected behavior as it forces users to not only populate the migrationItem.short.token to match the market's short token, but also set a miniscule amount of glpAmount to redeem for the short token to avoid migration failure.

    The same behavior applies if a user wants to solely redeem for the short token in a market, and leave the long token untouched.

    Recommendation

    If the glpAmount for either the long or short token is 0, skip the call unstakeAndRedeemGlp() for that token.

    Resolution

    GMX Team: The recommendation has been implemented in commit 58312d4.

  5. GLPM-5 Medium Reduced Burn Fee Can Be Larger Than Current Validation Resolved
    Location
    GlpMigrator.sol: 73, 122, 125,

    Description

    GMX is choosing to reduce the burn fee to further incentivize migration from GMX V1 to its latest GMX V2 system. However, there is no guarantee that the reducedMintBurnFeeBasisPoints is less than or equal to the current mintBurnFeeBasisPoints.

    The burn fee can be increased to be larger than its current value, causing users to redeem less tokens than expected.

    Recommendation

    Inside modifier withReducedRedemptionFees, only update the burn fee in GMX V1 if the _reducedMintBurnFeeBasisPoints is smaller:

    bool shouldUpdateFees = _reducedMintBurnFeeBasisPoints < mintBurnFeeBasisPoints;

    Resolution

    GMX Team: The recommendation has been implemented in commit 58312d4.

  6. GLPM-6 Medium Lists With Different Lengths Validation Resolved
    Location
    GlpMigrator.sol: 264-265

    Description

    The externalCallTargets and externalCallDataList are used to call an external protocol with user-passed data.

    However, externalCallDataList may have a different length than externalCallTargets, potentially causing an out-of-bounds error or the incorrect data being used for a particular target.

    Similarly, refundTokens and refundReceivers may be different lengths, potentially causing an out-of-bounds error or funds being sent to an unintended receiver.

    Recommendation

    Add validation such that externalCallTargets and externalCallDataList are the same length and that refundTokens and refundReceivers are the same length:

    require(externalCallTargets.length == externalCallDataList.length)

    require(refundTokens.length == refundReceivers.length)

    Resolution

    GMX Team: The recommendation has been implemented in commit 3a696d5.

  7. EXTH-1 Medium Lack of Contract Existence Check Low-Level Calls Resolved
    Location
    ExternalHandler.sol: 51

    Description

    The low-level call returns a success boolean of true if the target contract does not exist. As a result, the migration may not detect some failed external calls, leading to loss of funds for users.

    Recommendation

    Consider implementing a contract existence check prior to the call.

    Resolution

    GMX Team: The recommendation has been implemented in commit 3a696d5.

  8. EXTH-2 Medium Lack Of safeTransfer For Arbitrary Token Logical Error Resolved
    Location
    ExternalHandler.sol: 42

    Description

    In the makeExternalCalls function, the arbitrary refundToken is transferred using the transfer function, however safeTransfer should be used to avoid potential loss if the token chooses to return false rather than reverting upon failure.

    Recommendation

    Prefer safeTransfer to transfer.

    Resolution

    GMX Team: The recommendation has been implemented in commit 3a696d5.

  9. GLPM-7 Low Inflexible executionFee Optimization Resolved
    Location
    GlpMigrator.sol: 210

    Description

    The same executionFee is used for every migrationItem in the migrationItems list, however some migrations may require a smaller executionFee than others depending on if they are single token deposits.

    Recommendation

    Allow an individual executionFee to be specified on a migrationItem basis.

    Resolution

    GMX Team: The recommendation has been implemented in commit 58312d4.

  10. GLPM-8 Low Migration Contracts Needs To Be Set As Handler Access Control Resolved
    Location
    GlpMigrator.sol: 76

    Description

    In order for glpTimelock.setSwapFees() to succeed, the GlpMigrator contract must be given the necessary access control to bypass the onlyKeeperAndAbove modifier in GMX V1.

    Recommendation

    Set the migration contract as a handler in the GLP Timelock contract.

    Resolution

    GMX Team: Confirmed the GlpMigrator will have the necessary privileges.

More from GMX

All 44 reports
  1. Timelock Updates

    4 findings 4 findings: 3 low, 1 informational
  2. LayerZeroProvider Routing

    1 finding 1 finding: 1 medium
  3. Open Interest Updates

    5 findings 5 findings: 2 medium, 3 low
  4. Updates Branch

    2 findings 2 findings: 2 low

Put your code through the same review.

This review started with a conversation about scope. Tell us what you are building and we will plan yours with you.

Get a quote