GMX engaged Guardian to review the security of their GMX Crosschain architecture. From the 24th of February to the 5th of May, a team of 7 auditors reviewed the source code in scope.
- Published
- Review window
- February 24 to May 5, 2025
- Language
- Solidity
- Chains
- Arbitrum, Avalanche
- Sector
- Perpetuals
- 2 Critical
- 5 High
- 13 Medium
- 25 Low
- 0 Informational
Scope
Overview
GMX engaged Guardian to review the security of their GMX Crosschain architecture. From the 24th of February to the 5th of May, a team of 7 auditors reviewed the source code in scope.
Findings 45
-
C-01 Critical Missing Validations Logical Error Resolved
Description
The
transferFeeFromOrderOrPositionallows users to withdraw collateral from positions. ThevalidatePositionintends to verify that positions are still within size and collateral range, and not in a liquidatable state. If the check passes, collateral is withdrawn from the market.Nonetheless, critical state changes are not enforced to guarantee the security of the protocol:
applyDeltaToCollateralSumas funds are withdrawn from market. If omitted, it can lead to a
complete DoS of the market as the
validateMarketTokenBalancesafety check could revert.updateFundingAndBorrowingStateas fees applied to the position are incorrectly calculated, leading
to invalid liquidatable status.
incrementClaimableFundingAmountandhandleReferralto remain consistent with the decrease
position flow.
Recommendation
Consider removing the feature to allow withdrawing collateral from a position. If this is a non-negotiable feature, ensure all state changes are applied before validating the position, or use the
decreasePositionflow instead.Resolution
GMX Team: Resolved.
-
C-02 Critical Inconsistent Accounting Logical Error Resolved
Description
GMX maintains two fields for tracking a position’s pending price impact:
pendingImpactAmount(in index tokens) andpendingImpactUsd(in USD). However, onlypendingImpactAmountis correctly prorated during position decreases.In the decrease process, the USD impact is re-calculated as:
proportionalPendingImpactUsd = proportionalPendingImpactAmount × currentIndexPriceinstead of proportionally reducing the stored
pendingImpactUsd.This leads to discrepancies when the index price changes between position increase and decrease, causing:
pendingImpactUsdto exceed its initial value,pendingImpactUsdto become negative despite a positive remaining token amount,- Inaccurate liquidation checks, which rely on
pendingImpactUsd, potentially liquidating healthy positions or
causing reverts.
Example: 1. A user opens a position with index token price at $1:
pendingImpactAmount= 1000 tokenspendingImpactUsd= $1000
- The price increases to $2, and the user closes 75% of the position:
proportionalPendingImpactAmount= 750 tokensproportionalPendingImpactUsd= 750 × $2 = $1500
- Updated values:
pendingImpactAmount= 250 tokenspendingImpactUsd= $1000 − $1500 = -$500
Recommendation
Ensure both token‐ and USD‐denominated impacts are prorated from their own stored totals, rather than recalculating one from the current price. Or consider removing the explicit tracking for
pendingPriceImpactUsditself and default to usingpendingPriceImpact * indexTokenPriceResolution
GMX Team: Resolved.
-
H-01 High Transfer To EOA Instead Of Multichain Balance Logical Error Resolved
Description
Tokens that should have been included in the multichain balance are instead transferred directly to the user's EOA address.
This issue occurs when multichain orders fail during the execution process. While successful executions are handled as multichain, failed executions and refunds are processed as non-multichain actions.
_handleDepositError_handleGlvDepositError_handleWithdrawalError_handleGlvWithdrawalError_handleShiftError
Recommendation
Use the
srcChainIdwhen handling execution errors to determine the correct destination for transferring the tokens. If tokens are transferred toMultichainVault,recordTransferInmust be called post that to record the balance for the account.Resolution
GMX Team: Resolved.
-
H-02 High Position Validated With Atomic Oracles Validation Resolved
Description
The
validatePositionfunction is now called in thetransferFeeFromOrderOrPositionto check if the position is liquidatable and if it meets the minimum position size and collateral in usd.However the
_handleRelayBeforeActionForOrdersuses a an atomic oracle (chainlink price feed) used to fetch the market prices, which lacks behind the real price by the given deviation value.There are multiple impacts that arise from this issue:
- Min position collateral may be breached
- If deviation is against the user, users might not be able to take the collateral or remaining might
insufficient (liquidatable) so transaction reverts.
- If deviation is with the user needs, users will be able to withdraw more collateral from position
than they should be allowed to, as the deviation gives more room to reach the liquidatable status.
Additionally, if the deviation is 0.5% or more, and the user manages to frontrun the oracle update or the keeper liquidation, there is a risk of introducing bad debt to the protocol.
Recommendation
Our primary recommendation is to reconsider if this feature is truly essential and remove it otherwise. Alternatively, consider adding a extra buffer to market prices, so it
validatePositionreverts earlier.Resolution
GMX Team: Resolved.
-
H-03 High Missing Expo In messageHash Validation Resolved
Description
The
messageHashused for verifying signed price data does not include the expo field. As a result, when using the Chaos Labs Edge Data Stream provider, a malicious caller can supply an arbitrary expo value, thereby manipulating the effective price, since the price is derived as:price = value * 10^abs(expo)Chaos Labs’ publicly available price schema does not expose bid, ask, or clarify whether expo is included in the signed payload.
Furthermore, the public key required to verify Chaos' signatures is only shared during integration, so we were unable to independently verify whether expo is part of the signed message or not.
Depending on Chaos' signing scheme:
- If expo is included in the Chaos signature, signature verification will always revert on GMX.
- If expo is not included, it will allow price manipulation.
Recommendation
- If Chaos includes
expoin their signed payload, update the GMXmessageHashto includeexpo. - If Chaos does not include
expo, enforceexpoper feed as a configuration value in GMX’sDataStore. - We strongly recommend testing signature verification using a real Chaos API response. If Chaos
can provide a sample signed payload and the corresponding public key, we’d be happy to verify the correctness of the logic.
Resolution
GMX Team: Resolved.
-
H-04 High Price Impact Not Guaranteed Logical Error Resolved
Description
H-02 of the previous review is partially resolved. Even though the capping with impact pool amount during increase was removed from the code, issues related to time mismatch still persists. Since the delta amount is only applied during decrease orders, users with positive impact still miss out of their rebates.
For example:
- User A opens 10 long open interest and is negatively impacted.
- User A’s negative impact does not increase the impact pool amount because it is stored as pending
impact.
- User B opens 10 short open interest and is positively impacted, which is also stored as pending
impact.
- User C opens 10 short open interest and is negatively impacted.
- User C’s negative impact also does not increase the impact pool amount because it is stored as
pending impact.
- User B closes his position and is positively impacted. But this impact is still capped at 0 even
though both of user B's actions had positive impacts.
- User A and C close their positions with negative pending impact and impact pool balance is
increased.
In this scenario, User B would get their pending positive impact if they close the position after User A and C, but they won't get if they close it before. The ordering of executions significantly impacts users' price impact rebates and users has no power on this. It will depend on the executor/keeper's actions and blockchain's nature.
Recommendation
Consider implementing recommended changes in the previous H-02 and applying delta at the time of increase.
Resolution
GMX Team: Resolved.
-
H-05 High Uncapped Positive Impact In Liquidation Check Validation Resolved
Description
In the
isPositionLiquidatablefunction the price impact for closing the position is summed up with the pending price impact and the resulting total impact is capped at 0 (can only be negative).The price impact for closing the position is not capped based on the max positive price impact factor before adding the pending price impact (like during the actual decrease order).
This will therefore influence the validation and can result in positions appearing healthy which are actually liquidatable.
For example:
- A position is liquidatable (in reality)
- Its positive price impact for closing the position would be capped at $500 due to the max positive
price impact factor
- The pending price impact of the position is -$700
- The
isPositionLiquidatablefunction calculates a price impact of $800 for closing the position - The price impact is summed up with the pending price impact: $800 + (-$700) = 100 and therefore
it is capped at 0
- If the positive price impact would have been capped at 500 (as in the actual decrease order), the
result would have been: 500 + (-$700) = -$200 instead and this difference could have flagged the position as liquidatable instead of healthy
Recommendation
Cap the positive price impact for closing the position based on the max positive price impact factor before summing it up with the pending price impact in the
isPositionLiquidatablefunction.Resolution
GMX Team: Resolved.
-
M-01 Medium Missing validateSequencerUp Validation Resolved
Description
The
transferFeeFromOrderOrPositionfunction uses an atomic oracle to validate position (enabled market, collateral token, liquidatable status), whenever collateral is used to pay relay fees.However, there is a missing validation of the
oracle.validateSequencerUp()to make sure that the arbitrum sequencer is up and running.In case the sequencer is down this
validatePositioncalculations could be drastically off. Although the sequencer check is present when using atomic swaps, not all multichain transactions will involve a GMX v2 swap.Recommendation
Execute
oracle.validateSequencerUp()before validating the position.Resolution
GMX Team: Resolved.
-
M-02 Medium Incorrect Balance Check In bridgeOut Logical Error Resolved
Description
During the
bridgeOutflow, the bridging fee ofwntis transferred from the user’s multichain balance, then unwrapped to the native token to pay the fee.A balance check ensures the native transfer was successful. If it fails, the function is intended to return early without bridging and transfers tokens back to the user’s multichain balance.
However, it is not possible for
wntBalanceAfterto be greater thanwntBalanceBefore. It will either decrease (if successful) or stay the same (if unsuccessful). As a result, theif (wntBalanceAfter >wntBalanceBefore)block won’t be executed even if the native transfer fails.The function will not return early as intended and will continue executing, which can lead to one of two impacts:
- Bridging will be performed using the contract’s native balance: The contract will hold a native
token balance due to incoming bridging actions, as there's a time gap between the token transfer and the
lzComposecall. Thestargate.sendwill be executed using these incoming tokens, which will cause the subsequentlzComposeto fail, resulting in a loss of funds for the user attempting to bridge in. 2. Bridging will fail due to insufficient balance: If there is insufficient native token balance, thestargate.sendcall will revert. This will expose the user’s signature, which remains valid because the nonce is not incremented on revert. As a result, the same signature can be reused later by anyone.Recommendation
Check if the
wntBalanceAfteris equal towntBalanceBeforeto determine whether the native transfer has failed. Alternatively, compare the difference in thewntbalance against the requested withdrawal amount to ensure they are equal.Resolution
GMX Team: Resolved.
-
M-03 Medium Nonce Always Increased DoS Resolved
Description
The
_handleSubaccountActionfunction increases the nonce even if nosubaccountApprovalsignature was given.This could lead to DoS when multiple sub accounts interact with one main account and the main account tries to approve a sub account via a signature.
Recommendation
Only increase the nonce when a signature was validated.
Resolution
GMX Team: Resolved.
-
M-04 Medium Missing validateSequencerUp In Impact Pool Validation Resolved
Description
The
withdrawFromPositionImpactPoolfunction is executed with atomic oracle prices usingexecuteWithOraclePrices.Therefore, sequencer uptime should be validated before using
getMarketPricesto correctly calculate the pool value info.Recommendation
Make sure on chain prices are correct using
validateSequencerUpcheck.Resolution
GMX Team: Resolved.
-
M-05 Medium Collateral Mismatch Logical Error Resolved
Description
This function derives the position key using
order.initialCollateralToken(). However, GMX allows the user to swap from initial to actual collateral during execution of the order.As a result:
- The derived key might not match (causing revert), or
- It might unintentionally reference and deduct another position.
Recommendation
Consider using actual execution collateral to construct the correct key for the position. (if there is a swap path, last
tokenOut)Resolution
GMX Team: Resolved.
-
M-06 Medium Early Validation Allows Inconsistent Market State Validation Resolved
Description
The
validateMarketTokenBalancecheck is currently performed before the transfer of long and short tokens withinwithdrawFromPositionImpactPool.This ordering is incorrect, as it can allow
withdrawFromPositionImpactPoolto pass validation even when it shouldn’t, potentially leaving the market in an inconsistent state.Recommendation
Move the
validateMarketTokenBalancecall to occur after thetransferOutoperations.Resolution
GMX Team: Resolved.
-
M-07 Medium Stale Funding State Used Validation Resolved
Description
The
withdrawFromPositionImpactPoolfunction includes validations based onvalidateMarketTokenBalance.validateMarketTokenBalanceconsiders claimable funding fees.
However, since GMX does not call
updateFundingAndBorrowingStateprior to these validations, they operate on stale state, potentially causing the function to:- Revert when it shouldn’t, or
- Pass when it should revert.
Recommendation
Call
updateFundingAndBorrowingStateafterdistributePositionImpactPooland before any validations that depend on funding or borrowing data.Resolution
GMX Team: Resolved.
-
M-08 Medium Interface Changes Warning Acknowledged
Description
Several integrations of GMX have recently faced issues due to changes in reader contract interfaces — particularly when function signatures were updated without backward compatibility. Similar concerns now arise with the v2.2 changes, which modify both reader functions and state-changing flows, potentially causing integration breakage unless proactively addressed.
Reader Contract Changes Examples: Some commonly used reader functions have had their signatures or return types modified: 1.
isPositionLiquidatableforLiquidationis added as function input.- Integrations relying on this may now receive a function not found error.
getAccountOrders
- Return type has changed from
Order.Props[]memory toReaderUtils.OrderInfo[]memory, which now includes the order key. - This may cause deserialization or parsing issues for integrations expecting the previous format.
getExecutionPrice(viaReaderPricingUtils)
- The return struct has changed.
- Integrations parsing pricing data or simulating execution logic may break silently or return incorrect values.
State-Changing Flow Changes (Previously Reported): Additionally, state-changing functions across the board — including
createDeposit,createWithdrawal,createOrder, and associated callbacks — are impacted by:- The introduction of the
DataStore.Data[]datalist parameter. - Addition of
sourceChainIdas a required param. - Callback behavior modifications.
These changes are non-trivial and will require coordinated updates by integrators.
Recommendation
We recommend that the GMX team proactively notify all integrators and provide:
- A migration guide or changelog.
- Sample diffs or updated SDK adapters.
- Adequate lead time before deploying changes to production networks.
Resolution
GMX Team: Acknowledged.
-
M-09 Medium Missing Disable integrationId Feature Logical Error Acknowledged
Description
Users can add or remove sub accounts, granting some privileges to execute orders on behalf of the account with some limitations.
Additionally, users can set an
integrationIdfor each sub account so that the protocol can intervene if there are some compromised sub account keys, by disabling all sub account for a certain integration.However, there is no function that allows the protocol to disable an integration id.
Recommendation
Create a
disableIntegrationIdfunction protected by protocol admin to allow disabling integration ids.Resolution
GMX Team: Acknowledged.
-
M-10 Medium Integration Id Not Validated For Gasless Routers Validation Resolved
Description
Subaccounts using the
SubaccountRoutermay create, update or cancel orders on behalf of the account.Each of these actions trigger the
_handleSubaccountActionwhich validates subaccount feature, integration id, and handles subaccount action.Although a similar process is done by all actions in
SubaccountGelatoRelayRouterandMultichainSubaccountRouter, these routers use theSubaccountRouterUtils.handleSubaccountActionwhich does not contain the integration id validation.Recommendation
Consider adding the integration id validation in
SubaccountRouterUtils.handleSubaccountAction:SubaccountUtils.validateIntegrationId(dataStore, account, subaccount);Resolution
GMX Team: Resolved.
-
M-11 Medium Price Deviation In Impact Pool Withdraw Oracle Acknowledged
Description
The
withdrawFromPositionImpactPoolallows GMX to withdraw from the price impact pool. To do so it removes the amount from the position impact pool and it tries to remove an equal amount of long and short tokens from the pool itself. Usually this should cancel each other out and the LPs do not gain or lose funds because of this action.But as Chainlink oracle prices are used to do so the price deviation could lead to accidentally removing too much or too less long/short tokens in comparison to the removed impact pool amount.
As both the index price and the long/short token prices could be off, the stepwise jump in the LP share price could be up to 1% of the withdrawn impact pool amount (as a lot of price feeds on Arbitrum and Avalanche have a deviation value of 0.5%).
As the function is executed with a timelock controller, LPs are able to know when a
withdrawFromPositionImpactPoolcan be expected and might therefore be able to sandwich the call and gain from the action or avoid losses.Recommendation
Consider using recent prices instead of Chainlink prices in the
withdrawFromPositionImpactPoolfunction.Resolution
GMX Team: Acknowledged.
-
M-12 Medium Imbalance In Impact Pool From ADL & Liquidations Logical Error Acknowledged
Description
On a decrease position, the user will have to pay for any funding owed, negative PnL, fees, and then any negative price impact that the position has incurred. A scenario can arise that the user is unable to pay the entirety of their debt to the protocol.
In a normal decrease order this will lead to a revert, however, when this occurs through ADL or liquidations an event is emitted and the function will return early.
Since the impact pool amount is the last debt to be repaid, this leads to an imbalance in the impact pool amount, and no funds will be added to pay the traders who have received positive price impact.
Recommendation
Similarly to when funding fees can not be paid, an event should be emitted for the impact pool as well.
This event should signify that the insurance fund needs to add assets to the pool amount to cover price impact.
Additionally, an admin privileged function should be added in order to update the position impact pool amount.
Resolution
GMX Team: Acknowledged.
-
M-13 Medium validatePosition Might DoS Valid Actions Logical Error Resolved
Description
To fix a previous issue, a call to
validatePositionwas added after subtracting collateral from the position during the relay fee payment flow.This call is made with both
shouldValidateMinPositionSizeandshouldValidateMinCollateralUsdset to true.When decreasing positions, the same check is performed, but with these values set to false. As a result, it is possible for a position with a dust-sized amount to remain after a decrease, such as after a liquidation.
Normally, this wouldn’t be a problem if the user intends to increase the position and add more collateral later, since the next validation would occur only after the increase order is executed.
However, this becomes problematic in a multichain context, as the check occurs well before the position update, as part of
_handleRelayBeforeActionForOrders.A multichain
updateOrderorcancelOrdercall may fail due to previously remaining dust positions, even if the actions themselves are valid.Recommendation
Consider setting
shouldValidateMinCollateralUsdto true andshouldValidateMinPositionSizeto false during this validation intransferFeeFromOrderOrPosition.Resolution
GMX Team: Resolved.
-
L-01 Low Untrusted srcChainId Events Resolved
Description
In the
lzComposefunction thesrcChainIdis decoded from thecomposeMessage, however thecomposeMessageis controllable by the user.Therefore the
srcChainIdcan be incongruent with the chain the user actually bridged from. This may cause issues for consumers of theMultichainBridgeInandMultichainTransferInevents.Recommendation
Instead, the trusted
srcEidincluded in the message in thelzComposecall should be used to infer the source chain. If desired thesrcEidcan be mapped back to asrcChainId.Resolution
GMX Team: Resolved.
-
L-02 Low Bridged Funds Can Be Lost Or Trapped Informational Resolved
Description
When bridging funds using Stargate, users must carefully construct the transaction to ensure funds are delivered correctly and the
lzComposefunction is executed as intended.Several parameters are critical:
composeMsg: If omitted or malformed,lzComposewill either never be called or will revert on every
attempt, resulting in funds being irreversibly trapped.
account: If incorrectly set, funds may be credited to an unintended user in theMultichainVault.srcChainId: Must match the actual source chain to ensure event emissions on the destination chain
are valid.
receiver: Must be set to theLayerZeroProvidercontact address, as it is the recipient for funds and
where
lzComposeis invoked by theLayerZero EndpointV2.The current system lacks a mechanism for recovering funds when a malformed
composeMsgcauses repeated reverts, leading to permanent fund lockup in theLayerZeroProvider.Recommendation
- Enforce validation and thorough documentation of all Stargate bridging parameters in the GMX UI
to minimize user error.
- Consider introducing a trusted admin function to allow the protocol to recover or redirect funds that
are otherwise trapped due to malformed messages or misconfigurations.
Resolution
GMX Team: Resolved.
-
L-03 Low Misleading Comment For Bridge Out Fee Documentation Resolved
Description
The
LayerZeroProvider.bridgeOutwithdrawswnttokens from the multichain vault in order to pay the bridge fee stored incache.valueToSend.However, this value can also include the amount being bridged when interacting with the
StargatePoolNativecontract.Therefore, the
bridge out feecomment oncache.valueToSendis misleading and may confuse the reader.Recommendation
Add a note to the comment, explaining that this value may also include
amountSentLD, not just the bridge fee.Resolution
GMX Team: Resolved.
-
L-04 Low Bridge Out Same Chain Requires Two Transfers Gas Optimization Resolved
Description
When withdrawing tokens from Multichain vault using
MultichainTransferRouter.transferOut, the same-chain withdrawal path will first callMultichainUtils.transferOutto transfer tokens from theMultichainVaultto the router, and then a second transfer from the router to the account, spending more gas than needed.Recommendation
During
MultichainUtils.transferOut, set the receiver as theaccountto avoid a second token transferResolution
GMX Team: Resolved.
-
L-05 Low Unnecessary Approval For WNT When Bridging Logical Error Resolved
Description
When bridging out funds using
StargatePoolNative, both the bridge fee andamountSentLDare sent as native tokens instargate.send{ value: cache.valueToSend }. Therefore, there is no need to approveStargatefor WNT tokens.Recommendation
Avoid approving WNT tokens to Stargate pool when
stargate.token() = address(0x0).Resolution
GMX Team: Resolved.
-
L-06 Low transferOut Perturbs Multichain Accounting Logical Error Resolved
Description
In the
StrictBankcontract an overloaded version of the_recordTransferInfunction has been added which accepts a distinct amount value to transfer in.This is to allow individual cross-chain token transfers to be recorded individually upon
lzComposeexecution.However the
_afterTransferOutfunction sets the recorded balance to the currentStrictBankcontract balance.When there are funds which have been bridged but not yet recorded with the corresponding
lzCompsosecall, this will perturb the accounting and validation performed in the_recordTransferInfunction.This is because the
nextBalancewill be incremented past the recordedtokenBalancesentry causing reverts uponlzComposeexecution and trapping bridged tokens.Recommendation
Use an overridden implementation of the
transferOutfunction for theMultichainVaultcontract, whereby the specific amount is decremented from thetokenBalancesentry, rather than setting the entry to the current balance.Resolution
GMX Team: Resolved.
-
L-07 Low Incorrect recordTransferIn Usage Logical Error Resolved
Description
In the
MultichainUtils.recordTransferInfunction which is used after order execution to record outputs, themultichainVault.recordTransferIn(token)function is used.This function does not include the specific token amount being recorded in and therefore will overwrite the
tokenBalancesentry to the current balance of theMultichainVaultcontract.This will perturb the
tokenBalancesaccounting for cross-chain bridges and will result in in-flight bridges which have not yet had theirlzComposecall recorded being stuck due to reverts after the order execution action takes place and invokesmultichainVault.recordTransferIn(token).Recommendation
Use the
multichainVault.recordTransferIn(token, amount)overloaded function which includes the amount being recorded in theMultichainUtils.recordTransferInfunction.Resolution
GMX Team: Resolved.
-
L-08 Low Modifier Execution Order Logical Error Resolved
Description
In Solidity, when a function has multiple modifiers, they are executed in the order they appear in the function declaration (from left to right).
Although most functions declare
withRelayand thennonReentrant, it's not the same case for theMultichainOrderRouter.batch.Although there is no clear attack path, as permits are now blocked for multichain transactions, it's safer avoid potential issues.
Recommendation
In order to remain consistent, update the order of modifiers so
nonReentrantis declared first, and then thewithRelaymodifier after it.Resolution
GMX Team: Resolved.
-
L-09 Low Incorrect srcChainId Used Events Acknowledged
Description
According to the documentation:
srcChainId has been added to support multichain, e.g. if an order was signed from Base then broadcast on Arbitrum, the srcChainId would be the chainId of Base. For same chain actions, i.e. a
non multichain action, the srcChainId would be zeroHowever, this
srcChainIdvalue does not seem consistent in the codebase with the previous statement, as there are cases where the order is multichain, but a hardcoded value of 0 is used instead of the actualsrcChainId, or same chain orders using a non zero value.Some examples:
MultichainTransferRouter.transferOut: callsMultichainUtils.transferOutwith theblock.chainId
instead of 0.
DecreaseOrderUtils.processOrder: callMultichainUtils.recordTransferInwith the actualsrcChainId
value, but
DecreaseOrderUtils._handleSwapErroruses a hardcoded value of 0 forrecordTransferIn.OrderUtils.cancelOrder: even if it's a multichain order, it uses a value of 0 when returning funds to
user
GasUtils.payExecutionFee: id is always 0, no matter whatsrChainIdparam is.
Recommendation
Verify all instances where
srcChainIdis used, and make sure the correct value is set to signal a multichain order.Resolution
GMX Team: Acknowledged.
-
L-10 Low Precision Loss For Token Withdrawal Rounding Resolved
Description
The current calculation for
longTokenWithdrawalAmountandshortTokenWithdrawalAmountwill divide before multiplying.Recommendation
Although precision loss is just one or two wei, it's better to leave the division by two for the denominator param.
Resolution
GMX Team: Resolved.
-
L-11 Low Last Market Not Validated Validation Acknowledged
Description
During a gasless transaction, users are able to swap tokens to WNT in order to pay the relay fee. After the swap, a market validation is execute for the
feeSwapPath.However, the loop iterates from index 0 to the
feeSwapPath.length - 1, meaning that the last market in the path is not validated.This is different from what
ExecuteDepositUtils.swapfunction validates after performing a swap, where all theswapPathMarketsare validated.Recommendation
Do not subtract the 1 from the path length, so it will also validate the last market address.
Resolution
GMX Team: Acknowledged.
-
L-12 Low Unused Cache Struct Param Superfluous Code Resolved
Description
The
TransferFeeFromOrderOrPositionCacheis used as a struct to store temporal data in thetransferFeeFromOrderOrPositionfunction.However, the
initialCollateralDeltaAmountparam is never used, as the function creates a temporal variables instead of using the cache struct.Recommendation
Consider using the cache struct param or remove this from the struct if not in use.
Resolution
GMX Team: Resolved.
-
L-13 Low Inconsistent Validation Validation Resolved
Description
setIntegrationIdcorrectly checks sub account existence viavalidateSubaccount(). However, other functions of sub account router likesetSubaccountExpiresAt()andsetSubaccountAutoTopUpAmount()do not.Recommendation
Add
validateSubaccountto all sub account-related setters for consistency.Resolution
GMX Team: Resolved.
-
L-14 Low Missing Feature Flag Validation Validation Resolved
Description
The
batchClaimAffiliateRewardsfunction includes a feature flag check:FeatureUtils.validateFeature(dataStore, Keys.claimAffiliateRewardsFeatureDisabledKey(address(this)));However, the standalone
claimAffiliateRewardfunction does not include this validation, even though it is marked aspublic.Currently, this isn’t an issue since
claimAffiliateRewardisn’t being used anywhere. But if there are plans to expose or use this function in the future, the lack of feature flag validation could lead to inconsistent behavior or unintentional access.Recommendation
If
claimAffiliateRewardis intended for future use, consider adding the samevalidateFeaturecall to it for consistency and control. Alternatively, make itinternalorprivateif it's not meant to be directly used.Resolution
GMX Team: Resolved.
-
L-15 Low Liquidation Optimization createLiquidation Gas Optimization Resolved
Description
dataStore.getUint(Keys.positionLastSrcChainId(positionKey))is read twice — once for order assignment and again for flag assignment.Recommendation
Cache the value locally to avoid redundant reads, similar to how it’s done in ADL logic.
Resolution
GMX Team: Resolved.
-
L-16 Low Frontrunning Risk Warning Acknowledged
Description
Since this function is executed via a timelock, there’s potential for frontrunning or sandwiching. Although we didn’t identify any major exploit paths during analysis, it remains a sensitive admin action with theoretical exposure to manipulation.
Refer to previous price deviation issues for examples
Recommendation
GMX should always execute this function using a frontrunning-resistant private RPC. This will reduce exposure and ensure consistent execution behavior.
Resolution
GMX Team: Acknowledged.
-
L-17 Low Missing Safeguards Validation Resolved
Description
The
transferFeeFromOrderOrPositionfunction allows users to reduce order collateral or update position collateral via fee deduction.This makes it functionally similar to actions like
cancelOrderordecreaseOrder. However, unlike those flows, it currently lacks important safeguards, including:nonReentrant(global)FeatureUtils.validateFeature(...)
These protections are standard across GMX’s other state-changing functions and help ensure safety and feature control.
Recommendation
Add the same protections found in equivalent order-related flows to ensure consistent safety and behavior across user pathways.
Resolution
GMX Team: Resolved.
-
L-18 Low Old Orders Can Overwrite Newer Ones Validation Resolved
Description
The
updatePositionLastSrcChainIdis called during order execution and is used to save if the user interacted the last time from this chain or used a multichain account.As the order could be a limit or stop order an old order could overwrite the last src chain id of a newer order.
For example:
- User created a limit order months ago
- User sees that the new multichain feature exists now and as he prefers to use another chain he
sticks to using the system with a multichain account from now on and performs multiple market and limit orders from the multichain account
- The old limit order gets executed
- The system now thinks that the user prefers to receive tokens on the local chain
Recommendation
Only overwrite the
lastSrcChainIdif the order's creation timestamp is more recent than the last oneResolution
GMX Team: Resolved.
-
L-19 Low Integration Ids Can't Be Set With Multichain Logical Error Acknowledged
Description
Multichain users are able to grant sub account approval by signing a struct. Additionally, the
removeSubaccountis available to remove this approval using multichain.However, the new
setIntegrationIdis only available in non-gasless transactions, using theSubaccountRouter.Recommendation
Consider adding a
setIntegrationIdto the gasless sub account routers.Resolution
GMX Team: Acknowledged.
-
L-20 Low Validate Receiver In batchClaimAffiliateRewards Validation Resolved
Description
The
validateReceiverfunction is not called duringbatchClaimAffiliateRewards, unlike in other claim flows such asbatchClaimFundingFeesorbatchClaimCollateral.Recommendation
Validate the receiver in
batchClaimAffiliateRewardsas well.Resolution
GMX Team: Resolved.
-
L-21 Low Impact Pool Withdrawals May Constantly Revert Informational Acknowledged
Description
The time lock admin can trigger a
signalWithdrawFromPositionImpactPool, to withdraw a specific amount from the position impact pool.This will create a delayed execution of the given withdrawal. Currently, this delay is set to 1 day (86400 seconds).
Therefore, any positive impact received by traders during this timeframe will reduce the position impact pool, causing reverts during the withdrawal execution if pool amount is insufficient.
Additionally, this admin function withdraws from the impact pool in both long and short tokens. Although markets will normally have sufficient token balances, there could be cases when there is only one token available, making the transaction fail.
Recommendation
Consider these edge cases when signaling the withdrawal to avoid reverts during execution.
Resolution
GMX Team: Acknowledged.
-
L-22 Low Risk Free Trade By Causing Reverts Validation Resolved
Description
GMX introduced a feature called
transferFeeFromOrderOrPosition, which allows users to use collateral from their existing order or position to pay fees in multichain orders. However, this feature introduces a critical vector for risk-free trade.Attack Scenario:
- An attacker opens a position, and also creates a dummy limit order that is not executable.
- The attacker then creates a market increase order.
- If the price moves unfavorably before execution, the attacker invokes an update or cancel action
on the dummy limit order, using the position's collateral to pay the fee.
- As a result, the market order fails to execute due to insufficient collateral.
- The attacker successfully avoids downside risk without any cost — enabling risk-free trade
behavior.
This attack works because collateral for the market order is silently drained through an unrelated limit order, and GMX does not block this cross-order collateral usage.
Recommendation
Add a check inside
transferFeeFromOrderOrPositionto revert the if there are any preexisting open market order associated with the same position.Resolution
GMX Team: Resolved.
-
L-23 Low Incorrect Gas Tracking For Relay Logical Error Resolved
Description
withRelay(),withRelayForOrders(), &withRelayForClaims()all declareWithRelayCachein memory prior to the call to track gas. Although it is initiated as empty, solidity will still allocate space for the array in memory.This will lead to gas usage that is untracked, which will be paid for by GMX instead of the user. This leads to any call to a multi-chain or gasless feature to continuously cost GMX.
Recommendation
Move the declaration for
WithRelayCacheto after starting gas is assigned.Resolution
GMX Team: Resolved.
-
L-24 Low Frontrunning withdrawFromPositionImpactPool Warning Acknowledged
Description
A new
withdrawFromPositionImpactPoolfunction has been added to allow token withdrawals from the impact pool. However, this function is time-locked and not atomic. Additionally, the withdrawal amount is determined at the time the time lock is scheduled.Users are aware of when this action will be executed. Since positive price impact is capped based on the impact pool amount, users with pending positive impact will decrease their positions before the timelock execution to ensure they receive their rebates without being affected by the cap.
This not only creates race conditions and unfair advantages between users, but will also cause the timelock execution to fail due to a reduction in the available impact pool amount.
Additionally, since the amount is determined at the time of scheduling, but the available amount check is performed at the time of execution, the function might fail due to changes in the price impact pool after regular user trades in this time period.
Recommendation
Beware of this possibility, and if considered likely:
Consider validating the withdrawal amount and deducting it from the price impact pool at the time of scheduling, effectively reserving this amount for withdrawal until the time lock execution. Alternatively, consider performing this action atomically without a timelock.
Resolution
GMX Team: Acknowledged.
-
L-25 Low Unnecessary srcChainId In bridgeIn Events Resolved
Description
The
bridgeInfunction accepts a user-providedsrcChainIdas a parameter. However, this function is only used for same-chain transfers from the user's EOA to the multichain balance.Therefore, the
srcChainIdis unnecessary. Additionally, this arbitrarysrcChainIdwill be used in themultiChainTransferInevent, causing misleading event emissions for external listeners.Recommendation
Consider removing the
srcChainIdfor same-chainbridgeIntransfers.Resolution
GMX Team: Resolved.
No findings match.
More from GMX
All 44 reportsPut 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.
