GMX engaged Guardian to review the security of GMX Crosschain architecture. From the 19th of May to the 26th of May, a team of 6 auditors reviewed the source code in scope.
- Published
- Review window
- May 19 to 26, 2025
- Language
- Solidity
- Chains
- Arbitrum, Avalanche
- Sector
- Perpetuals
- 3 Critical
- 6 High
- 13 Medium
- 48 Low
- 0 Informational
Scope
Overview
GMX engaged Guardian to review the security of GMX Crosschain architecture. From the 19th of May to the 26th of May, a team of 6 auditors reviewed the source code in scope.
Findings 70
-
C-01 Critical Anyone Can Drain The Multichain Balance Validation Resolved
Description
GMX has added a new optional functionality in
lzCompose, allowing users to instantly deposit their bridged tokens in exchange for GM or GLV tokens.However, since GMX relies on the decoded account from the compose message, anyone can spoof any other user.
Previously, this wasn’t an issue—if someone used account B instead of their own account A, they would simply end up donating their bridged tokens to account B’s multichain balance.
But with the introduction of
handleDepositFromBridge/_handleGlvDepositFromBridge, which acceptsactionDataas another user-controlled field, account B can now spoof account A and use A’s multichain balance to perform a GM or GLV deposit, with themselves (B) as the receiver.Recommendation
Consider validating that the action to be executed for a given account is authorized via a signature from that account, using a digest of the
actionData.Resolution
GMX Team: Resolved.
-
C-02 Critical Anyone Can Drain Multichain GM Token’s Balance Validation Resolved
Description
GMX added
createGlvDepositFromBridgeinMultichainGlvRouterto facilitate_handleGlvDepositFromBridge. However, this function is missing theonlyControllermodifier.As a result, anyone can trigger a GLV deposit on behalf of any user, with themselves as the receiver—since this call is not validated using a signature.
Recommendation
Consider adding the
onlyControllermodifier tocreateGlvDepositFromBridgeto restrict access to authorized callers only.Resolution
GMX Team: Resolved.
-
C-03 Critical Arbitrary Execution & Balance Drain Risk Logical Error Resolved
Description
bridgeOutFromControllerhas been added toexecuteDepositto allow users to instantly bridge their received GM tokens to a chosen chain. However, this function also carries thewithRelaymodifier when used via the transfer router, enabling arbitrary external calls or sends.Since
bridgeOutFromControllerusesdeposit.receiver()as the input or payee forwithRelay, this opens up a way for an attacker to drain another user’s multichain balance. For example, attacker A can call the function using depositor B as the receiver, effectively spending B’s funds with arbitrary logic viawithRelay.Additionally, using
withRelayhere introduces other problems:- Since
msg.senderwon't be Gelato, the call is treated as sponsored, and users will be
unnecessarily charged a Gelato fee — something they already cover through GMX’s keeper fees.
- This
withRelayusage also enables risk-free deposits with conditional execution:
withRelayhook allows arbitrary external calls, and since gas cost can vary depending on the logic, this allows user to manipulate gas consumption to cause conditional reverts.- Since oracle prices are preset in
executeDeposit, any modification or re-setting of those prices
during
handleRelayBeforeAction(if attempted) could cause the transaction to revert.Recommendation
Consider removing the
withRelaymodifier frombridgeOutFromControllerentirely, as it doesn't appear to serve a valid use case in this context and introduces multiple risks.Resolution
GMX Team: Resolved.
- Since
-
H-01 High Wrong Token Is Used In bridgeOutFromController Logical Error Resolved
Description
The
bridgeOutFromControllerfunction is introduced to bridge the GM and GLV tokens to thesrcChainbased on the provideddataList.However, incorrect token and amount values are used when calling
bridgeOutFromControllerin theexecuteGlvDepositfunction during the GLV deposit flow.The
markettoken is used instead of theglvtoken, and thereceivedMarketTokensvalue is used instead ofmintAmount.Recommendation
Use the
glvtoken andmintAmountwhen calling this function during the GLV flow.Resolution
GMX Team: Resolved.
-
H-02 High Overflow DoS In withdrawFromPositionImpactPool DoS Resolved
Description
The pending impact amount is now globally tracked with the
totalPendingImpactAmountKeyas aint256value. During position increase, thepriceImpactAmountis applied as delta to this global value.However,
priceImpactAmountcan be either positive or negative, depending on the open interest values of the market.During
withdrawFromPositionImpactPool, thegetTotalPendingImpactAmountis read from storage and converted touint256so it can be compared toadjustedImpactPoolAmount.Consequently, this call will revert every time the total impact amount is negative, DoS'ing impact pool withdrawals.
Recommendation
Consider validating if
totalPendingImpactAmountis positive before converting it touint256. The sufficiency check is not required for negative pending impact amounts, as those are meant to be paid by users at position decrease.Resolution
GMX Team: Resolved.
-
H-03 High Pending Amounts Errantly Capped To Zero Logical Error Resolved
Description
In the
capPositiveImpactUsdByPositionImpactPoolfunction if thetotalImpactPoolAmountvalue is negative the resulting capped impact is immediately set to 0. However this prevents traders from claiming their positive pending amounts when there is indeed some portion of backing value in the impact pool to cover it.Consider the following scenario:
- Trader A has a pending impact of -5
- Trader B has a pending impact of 3
- The total pending impact is -2
- Trader A closes their position and receives positive impact of 6
- The impact pool amount is 4 at time 0
- The computed
totalImpactPoolAmountvalue for Trader A at time 0 is 4 (impact pool amount) - 3 (totalPending- Trader A
pending) = 1
- At time 2 the impact pool distributes down to 2.99
- The computed
totalImpactPoolAmountvalue for Trader A at time 2 is 2.99 (impact pool amount) - 3
(
totalPendingImpactAmount, -2 + 5) = -0.01- The impact is capped to 0, when in reality the amalgam of impact assets could cover a portion of the positive impact: 2.99
(impact pool amount) - 2 (pending impact amount) = 0.99 coverable of the 1 net impact.
Recommendation
The crux of this issue is an over complicated handling of the pending impact amount during the capping process. Pending amounts were capped already on increase and can be considered as effectively a part of the impact pool. The current
capPositiveImpactUsdByPositionImpactPoolfunction includes the pending impact as a part of the impact that is capped on decrease, and then corrects for this by removing thepositionProportionalPendingImpactAmountfrom thetotalPendingImpactAmount.However this action makes an underlying flawed assumption, that the
positionProportionalPendingImpactAmountwill be simply removed from the amalgamation of the impact pool, pending impact, andlentAmount. Instead thepositionProportionalPendingImpactAmountjust transfers which variable it is tracked in but stays within the amalgamation of these three variables which make up the totality of the impact pool. To remove this edge case and others which possibly exist but are hard to analyze, re-implement the capping of impact on decrease in the following way:- The pending impact amount should be treated as already realized and not capped by the impact amalgamation variables
- The pending impact now does not need to be corrected for in the
totalPendingImpactAmountas it already belongs to the impact
amalgamation and will stay there
Resolution
GMX Team: Resolved.
-
H-04 High Positive Impact Becomes Unbacked Logical Error Resolved
Description
The
lentAmountlogic is meant to enable positive impact to be realized when there is no immediate amount available in the position impact pool.The
lentAmountbecomes a loan against a corresponding amount of negative impact that is pending for another position. However this loan is made against "collateral" that can disappear on decrease.In the event that a position with a large negative pending impact is decreased and has it's negative impact capped by the
maxPriceImpactFactor, the negative pending impact is essentially erased.As a result it is possible for the lent amounts to become more than the value of the total pending impact.
Recommendation
When a position is decreasing and realizing a negative pending impact, require that the position pays a portion of the
lentAmountthat it owes rather than capping it freely. Otherwise consider removing positive price impact for markets which will have their negative impact capped.Resolution
GMX Team: Resolved.
-
H-05 High Max Impact Caps Double Count Lent Amounts Logical Error Resolved
Description
In the
capPositiveImpactUsdByPositionImpactPoolfunction thetotalImpactPoolAmountis computed as the total net impact token amount available between the impact pool and the pending impact amounts. This amount represents the total impact amount available assuming that all traders with pending impact settle their positions.When a trader receives positive impact and realizes it, withdrawing from the impact pool, the impact pool amount is deducted and that is therefore reflected in the lowering of this cap. However, when a trader receives positive impact and realizes it, withdrawing instead against the pending impact amount and creating a
lentAmount, neither the impact pool amount or the pending impact amounts are reduced to reflect a lowering of the cap.Consider the following example:
- Start with a new market
- Trader A opens long size 100 and receives -10 pending impact
- Trader B opens short size 100 and receives 10 pending impact
- The impact pool is still 0
- Trader C opens short size 100 and receives -10 pending impact
- Trader B closes their position and realizes 10 new impact and their 10 pending impact
- The total impact cap is 20 from A and C, and B's pending impact is removed from the
totalPendingImpactAmount- B receives all 20 impact and the lent amount is increased to 20
- On any future caps the
maxPriceImpactUsdreports that the full 20 impact amount from A and C's
pending impact can be used, however it is already lent to B.
Recommendation
Deduct the
cache.lentUsdin thecache.maxPriceImpactUsdcalculation like so:cache.maxPriceImpactUsd = cache.totalImpactPoolAmount * prices.indexTokenPrice.min.toInt256()
- cache.lentUsd;
Resolution
GMX Team: Resolved.
-
H-06 High Unclear Lending Constraints Logical Error Resolved
Description
The
capPositiveImpactUsdByPositionImpactPoolfunction is designed to cap the positive price impact a user can realize. It takes into account a market-configuredmaxLendableamount, which represents how much price impact the protocol can “lend” to users even when the impact pool is empty.This design introduces the concept of "lent price impact" — a soft mechanism that allows the system to temporarily cover a user’s positive price impact beyond the available impact pool, under the assumption that future negative price impact (pending) will compensate for it.
However, the function does not enforce any constraint that this lent impact must be matched by existing or eventual pending price impact. It allows users to borrow up to
maxLendable, regardless of whether the system has enough pending impact to support it, or whether all positions could close without offsetting it.Recommendation
Due to the current behavior, it’s unclear what the intended design philosophy behind “lent” price impact is. There appear to be two possible interpretations:
- Lent as a free-floating loan against
C(pool value) up to some ratio
In this model, pending impact is expected to eventually pay it back, but there is no enforced relation or constraint. Users are allowed to borrow up to
maxLendableregardless of whether sufficient pending impact exists. → This is flexible but introduces systemic risk if pending impact never materializes.- Lent must be backed by pending price impact
In this stricter model, you can only borrow positive price impact if there is matching (realized or pending) negative price impact. Lending and pending are directly tied. → This ensures solvency but is more conservative.
If Option 2 is the intended model, consider enforcing caps on positive impacts using pending amounts directly, rather than relying solely on the
maxLendableratio. This would ensure that all lent amounts are matched by actual system liabilities, maintaining consistency and preventing impact pool deficits.Resolution
GMX Team: Resolved.
- Lent as a free-floating loan against
-
M-01 Medium Unnecessary Relay Fee Charged Logical Error Resolved
Description
GMX now allows users to instantly deposit their bridged tokens using
createDepositFromBridgeandcreateGlvDepositFromBridge. However, both of these functions are wrapped with thewithRelaymodifier.During execution, this causes
handleRelayFeeto treat the call as sponsored, sincemsg.senderis not Gelato. As a result, users are forced to pay a Gelato relay fee unnecessarily.Recommendation
Consider implementing a modified
withRelayvariant specifically for bridge-based deposits. This version should:- Only account for deposit-specific keeper fees and collateral.
- Avoid charging an additional Gelato relay fee
This would prevent redundant fees and align the cost model with user expectations for bridged deposit
Resolution
GMX Team: Resolved.
-
M-02 Medium Untrusted srcChainId Logical Error Resolved
Description
In the
lzComposefunction thesrcChainIdis decoded directly from thecomposeMessagewhich is entirely controlled by the sender.As a result, this value may not accurately reflect the chain that the message was bridged from and can be used to bypass the
isSrcChainIdEnabledvalidations.Recommendation
Instead, the trusted
srcEidincluded in the message in thelzComposecall should be used to infer the source chain.This can be retrieved using the
OFTComposeMsgCodec.srcEidfunction. If desired thesrcEidcan be mapped back to asrcChainId.Resolution
GMX Team: Resolved.
-
M-03 Medium Referrals Controlled For Smart Contract Traders Access Control Acknowledged
Description
The
MultichainSendercontract allows the controller of an address on one chain to set the referral code for the owner of that same address on Arbitrum.In the case of Smart Contract accounts, the controller of this address may be different across chains. As a result an untrusted party is able to control the referral code used for the target account.
Recommendation
Consider requiring that referral codes be set by a signature for multichain actions, therefore ensuring that the account is an EOA. For accounts that are smart contracts on Arbitrum, they can set their referral code on Arbitrum itself.
Resolution
GMX Team: Acknowledged.
-
M-04 Medium Composed Deposit Censoring Frontrunning Resolved
Description
The
lzComposefunction can be called by any actor after the configured DVNs have verified the message.As a result an actor can invoke the
lzComposefunction on theLayerZeroendpoint and provide an insufficient amount of gas to fully execute thelzComposereceived funds accounting and the_handleDepositFromBridgeor_handleGlvDepositFromBridgeaction that follows.This would allow the malicious executor to cause the
createDepositFromBridgeorcreateGlvDepositFromBridgecalls to run out of gas during execution and enter the catch block which they are wrapped in.The affected user would then have to create another deposit action through the
MultichainGmRoutercontract.Recommendation
Consider validating that the amount of gas that has been provided is sufficient before calling the
createDepositFromBridgeorcreateGlvDepositFromBridgefunctions.Resolution
GMX Team: Resolved.
-
M-05 Medium Inaccurate Liquidations Logical Error Acknowledged
Description
GMX currently fails to correctly apply caps for both positive and negative price impacts in
isPositionLiquidatable, which can incorrectly returnfalsewhen a position should be liquidated.Positive Price Impact:
- GMX does not cap the positive price impact to the available amount in the
impactPoolAmount. - As a result, a position might appear solvent based on a theoretical positive price impact, even
though GMX cannot actually pay the profit due to insufficient funds in the impact pool.
- This leads to positions avoiding liquidation despite being undercollateralized in practice.
Negative Price Impact:
- Due to the new deferred accounting of price impacts (i.e., unrealized/pending impact on position
increase), users can open positions with a large negative price impact that is not immediately realized.
- During liquidation checks,
isPositionLiquidatablecomputes the total price impact and applies the
maxNegativePriceImpactUsdcap to it.- However, this cap was originally meant to protect against sudden adverse price movements, not
unrealized negative impact from user’s own position open.
- By applying the cap to the total impact (including the user’s pending impact), the function can
understate the risk and allow a user to avoid liquidation.
Recommendation
- Positive Price Impact: Cap it to
impactPoolAmountduring liquidation checks.
capPositiveImpactUsdByPositionImpactPool- Negative Price Impact: Only cap new negative price impact incurred from current price slippage
during liquidation, not the previously accrued/pending impact from position opening.
Resolution
GMX Team: Acknowledged.
- GMX does not cap the positive price impact to the available amount in the
-
M-06 Medium Incorrect Atomic Oracle Provider Handling Logical Error Resolved
Description
The atomic oracle provider validation is intended to be separated from the reference oracle check and timestamp adjustment logic.
However instead of separating this logic for the timestamp adjustment, the timestamp adjustment is still always applied based on the
isAtomicProviderstatus.Furthermore, if an action is
forAtomicActionand the providershouldAdjustTimestampvalue is assigned astruethen the oracle reverts withNonAtomicOracleProvider.Recommendation
Replace the initial atomic oracle validation with:
if (forAtomicAction) { if (isAtomicProvider) { revert Errors.NonAtomicOracleProvider(_provider); } }And replace the timestamp adjustment logic with:
if (provider.shouldAdjustTimestamp()) { uint256 timestampAdjustment = dataStore.getUint(Keys.oracleTimestampAdjustmentKey(_provider, token)); validatedPrice.timestamp = timestampAdjustment; }Resolution
GMX Team: Resolved.
-
M-07 Medium Max Lent Bypassed By Splitting Orders Warning Resolved
Description
The
maxLentvalidation serves as a sanity check to cap the price impact by a proportion of the underlying market short and long tokens.However this check can be easily circumvented for increase orders by splitting up a large order into several smaller orders so the price impact on any given order is smaller than the
maxLendableUsdbased on the backing token amounts in the market.Consider the following scenario:
- A GM market has 100 backing long tokens and 100 backing short tokens
- User A has an order that will experience 30 positive impact
- The
maxLendableUsdvalue is 20 - User A splits their increase order up into two halves which each experience 15 positive impact
- The execution of the first increase order has no effect on the
maxLendablefor the second order
since the impact pool is not adjusted for the impact amount on increase
- Therefore User A is able to bank their 30 positive impact as a pending impact amount when
otherwise they would have only been able to save 20
This gives informed users who can abuse this behavior an advantage over those who are unaware in order to accumulate more aggregate price impact.
Recommendation
Consider if the max lendable validation should be applied on increase. If it shouldn’t be then consider only applying the
maxLendablevalidation on decrease to prevent this gaming.Resolution
GMX Team: Resolved.
-
M-08 Medium Impact Pool Withdrawals Use An Invalid Ratio Logical Error Resolved
Description
The
withdrawFromPositionImpactPoolfunction removes long and short tokens at a 50/50 dollar value split. However in the majority of GM markets the backing token amounts will not be evenly distributed 50/50.In many cases this removal can cause a further imbalance in a GM market or even revert due to unavailability of tokens.
Consider the following scenario:
- Our GM market has 10 token A and 20 token B
- Both token A and token B are valued at $1
- $10 is withdrawn with the
withdrawFromPositionImpactPoolfunction, a large value is used for
explication
- 5 token A and 5 token B is removed from the GM market
- the GM market is left with 5 token A and 15 token B
- The imbalance in the market has gone from 33%/67% to 25%/75%
Because of this behavior, each
withdrawFromPositionImpactPoolaction increases the amount of positive price impact that can be earned without providing negative impact into the impact pool, invalidating an important invariant of the GMX exchange.Recommendation
The backing token amounts must be removed at the ratio of the backing tokens in the existing GM market, similarly to the GM withdrawal logic.
Resolution
GMX Team: Resolved.
-
M-09 Medium Pending Price Impact Can Be Lost Validation Resolved
Description
During position decrease, the
capPositiveImpactUsdByPositionImpactPoolis invoked with thetotalImpactUsdand theproportionalPendingImpactAmount.Previously, this function will only cap positive impact to the usd value of the impact pool tokens. However, with the introduction of
lentAmount, themaxPriceImpactUsdwill be capped at themaxLendableUsd.Consequently, if the
maxLendableUsdis lower than themaxPriceImpactUsdand there are no lent funds, user will receive a lowerpriceImpactUsd, even if the impact pool has enough funds to cover it.Recommendation
Consider capping the
maxPriceImpactUsdonly whenlentUsd > 0Resolution
GMX Team: Resolved.
-
M-10 Medium Gas Limits Underestimated Configuration Acknowledged
Description
The current configured gas limit for GM and GLV deposits are 1,800,000 and 2,000,000 respectively. Additionally, the
minAdditionalGasForExecutionis set to 1,000,000.However, this gas limit does not account for the optional feature of bridging out tokens after deposit execution. During execution, keeper's gas will be validated against these values using
GasUtils.validateExecutionGas.This check might be valid for deposits without the bridge out option, but the gas will not be enough if it contains the extra feature.
Although keepers may send more gas to account for this issue, the gas validation is incorrect and can make them experience Out of Gas errors.
Recommendation
Consider adjusting the
depositGasLimitandglvDepositGasLimitto account for the gas spent in the new bridge out feature after deposit executionResolution
GMX Team: Acknowledged.
-
M-11 Medium Malicious Token Gas Griefs Keepers Warning Resolved
Description
During an increase order execution, the provided
initialCollateraltoken is not validated to be a valid collateral token associated with any market at the point of theSwapUtils.swapfunction when the initialparams.bank.transferOutinvocation is made.As a result, the
tokenIncould be a malicious token which returns maliciousreturnDatawhich holds revert reason bytes which proclaim to be an enormously long string and thus force the keepers to expend a large amount of gas above and beyond what the original execution fee covers.Recommendation
Consider validating if the initial token is a valid collateral token of the first market token in the
swapPathin theswapfunction.Resolution
GMX Team: Resolved.
-
M-12 Medium Impossible To Execute Some Timelock Actions Logical Error Resolved
Description
In the
ConfigTimelockControllercontract theexecuteWithOraclePricesfunction hard-codes 0 as a predecessor and 0 as a salt.As a result, any actions that have been scheduled with a salt or predecessor that require oracle prices cannot be executed through the
ConfigTimelockController.This proves especially problematic when the executor wishes to withdraw from the position impact pool of the same market.
The caller will be unable to withdraw the same amount to the same receiver since the id of this action will have already been marked as Done.
This immediately affects the
signalWithdrawTokensfunction but may also apply to future use-cases of the timelock.Recommendation
Add predecessor and salt parameters to the
executeWithOraclePricesfunction so that transactions can be differentiated and ordered.Resolution
GMX Team: Resolved.
-
M-13 Medium Subaccounts May Divert Multichain Funds Unexpected Behavior Acknowledged
Description
The
lastSrcChainIdis assigned for the position upon execution of every increase or decrease order. If the integration is not careful to handle funds being unexpectedly sent to the multichain balance rather than the expected native arbitrumsrcChainId,then funds could potentially be trapped.A sub account can maliciously redirect funds to the multichain balance by creating a market order with a multichain
srcChainIdshortly before the position is either liquidated or ADL’d. For smart contract accounts on Arbitrum this can lead to trapped funds.Recommendation
Consider allowing integrations to explicitly set the chain id associated with their position with a
setSavedSrcChainIdfunction, similar to thesetSavedCallbackContractfunction.Resolution
GMX Team: Acknowledged.
-
L-01 Low External Call Gas Is Not Corrected Logical Error Acknowledged
Description
The
executeDepositFromControllerfunction was introduced in theDepositHandlercontract to reduce dependencies necessary across contracts. However this adds an additional external call in the deposit execution process during shifts.This additional external call does not adjust the
params.startingGasfor the 63/64 rule and therefore would overestimate the amount of gas used for the deposit execution.However the
executionFeeassigned for the deposit is assigned as zero, meaning that the gas calculations for the deposit are not made nor reflected in any fee charged.Recommendation
No change is necessary since there is no net impact of the omission of the external call gas correction.
This finding serves merely to document this behavior in the event that future uses of the
executeDepositFromControllerwould include deposits that use a nonzeroexecutionFee.Resolution
GMX Team: Acknowledged.
-
L-02 Low MarketPoolValueInfo NatSpec Wrong Informational Resolved
Description
The
NatSpecof theMarketPoolValueInfofunction does not match the real variables.Recommendation
Update the
NatSpec.Resolution
GMX Team: Resolved.
-
L-03 Low bridgeIn Is Marked As Payable Best Practices Acknowledged
Description
The
bridgeInfunction is marked as payable, but there are no functions that use native currency or handle refunds. Therefore, any native tokens sent with this functions will be permanently lost.Recommendation
Remove the payable modifier to avoid loss of funds. If this is used to save transaction gas, consider documenting this to users.
Resolution
GMX Team: Acknowledged.
-
L-04 Low Duplicated Code Best Practices Resolved
Description
In the
SubaccountRouter.createOrderfunction the validations on the receiver and cancellation receiver are repetitive with the implementation ofvalidateCreateOrderParams.The only difference being that in the
SubaccountRouter.createOrderfunction theInvalidReceiverForSubaccountOrdererror is used, while in thevalidateCreateOrderParamsfunction theInvalidReceivererror is used.Recommendation
Consider deduplicating this validation so that in the future any adjustments to the
validateCreateOrderParamsvalidation will not be missed from the validation in theSubaccountRouter.createOrderfunction.Furthermore, consider using the
InvalidReceiverForSubaccountOrdererror in thevalidateCreateOrderParamsfunction.Resolution
GMX Team: Resolved.
-
L-05 Low MessagingReceipt Not Returned Best Practices Acknowledged
Description
The
MultichainSenderwill allow users to send a message through Layer Zero. However, thesendMessagefunction does not return theMessagingReceiptparam, which is available in the value returned from_lzSend. This will a nice feature to have for integrations and UI/UXRecommendation
Return the
MessagingReceiptin thesendMessagefunction.Resolution
GMX Team: Acknowledged.
-
L-06 Low Unnecessary referralStorage Variable Superfluous Code Acknowledged
Description
There is no implemented use for the
referralStoragevariable in theMultichainOrderRoutercontract.Recommendation
Consider either implementing the use-case for the
referralStoragevariable or removing it from theMultichainOrderRoutercontract.Resolution
GMX Team: Acknowledged.
-
L-07 Low Unnecessary Ternary Gas Optimization Resolved
Description
In the
LiquidationUtilslibrary the boolean expressioncache.lastSrcChainId = 0 true : falseis used, however thecache.lastSrcChainId = 0value can be used directly.Recommendation
Remove the unnecessary ternary operator.
Resolution
GMX Team: Resolved.
-
L-08 Low Lendable Amt May Not Belong To Pool Logical Error Acknowledged
Description
In the
capPositiveImpactUsdByPositionImpactPoolfunction themaxLendableUsdvariable is calculated by applying a factor on the value of the tokens in the pool. This does not factor in PnL, borrowing fees, etc.This could lead to giving out funds that the pool doesn't actually own which could for example lead to users not being able to close their positions as there are currently not enough funds in the pool to do so. This is especially dangerous in new pools which do not have accrued much liquidity yet.
Recommendation
Consider working with the
getPoolValueInfofunction instead of the value of the pool amounts.Resolution
GMX Team: Acknowledged.
-
L-09 Low Price Impact Not Guaranteed Logical Error Acknowledged
Description
Price impact from increase is still not 100% guaranteed. The reason for this are the following:
- The price impact amount is reserved now but the price of these tokens can change between
increase and decrease
- The lending capacity limited by the
maxLendableImpactFactorcan be exhausted - the price impact pool is distributed to the pool over time
Recommendation
Rethink or acknowledge and document this behaviour.
Resolution
GMX Team: Acknowledged.
-
L-10 Low Incorrect Zero Amount Check In recordBridgeIn Validation Resolved
Description
The
lzComposewill record bridged funds usingMultichainUtils.recordBridgeIn. Although the function reverts ifamount = 0, this check is done on the param passed, not on the actual recorded amount.Therefore, if
multichainVault.recordTransferIn(token)records zero amount, the transaction won't revert. ThelzComposewill succeed but user will not receive the tokens.Recommendation
Consider validating zero amounts with the value returned from the
recordTransferInfunction.Resolution
GMX Team: Resolved.
-
L-11 Low Early Return Should Include Equality Operator Gas Optimization Resolved
Description
The
capPositiveImpactUsdByPositionImpactPoolfunction will early return if thetotalImpactPoolAmountis less than 0. This means that there are no available funds in the impact pool to pay for more positive impact.However, if
totalImpactPoolAmount = 0, it will waste some gas calculating the other parameters, as themaxPriceImpactUsdwill also be zero, and the final check will cap thepriceImpactUsdto 0.Recommendation
Add the equality operator to the early return check:
if (cache.totalImpactPoolAmount = 0) { return 0; }Resolution
GMX Team: Resolved.
-
L-12 Low Inconsistent Handler Addresses Configuration Acknowledged
Description
Certain library calls were refactored into using external calls to handlers. New state variables were added to these handlers to store contracts like
swapHandler,multichainTransferRouter,depositHandler,withdrawalHandler, etc.Therefore, deploying a new handler (i.e.
swapHandler) will require the other handlers to be updated. However, there are no setter functions for this update, which will require a re-deploy of all contracts that new handler.This may also cause some issues when contracts use different handler or router versions.
Recommendation
Document this behavior and make sure contracts are re-deployed for new handler/router updates, with the appropriate role revoking and granting.
Resolution
GMX Team: Acknowledged.
-
L-13 Low Incorrect Comparison Operator Logical Error Resolved
Description
The
withdrawFromPositionImpactPoolfunction includes some sanity checks to make sure the withdrawn funds do not exceed the impact pool amount, accounting for thetotalPendingImpactAmount.However, the transaction will revert if the amount to withdraw is exactly the same as the available in the impact pool:
if (adjustedImpactPoolAmount <= amount) { revert Errors.InsufficientImpactPoolValueForWithdrawal(amount, poolValueInfo.impactPoolAmount, totalPendingImpactAmount); }Recommendation
Consider replacing the comparison operator
<=for<to avoid unexpected reverts.Resolution
GMX Team: Resolved.
-
L-14 Low Missing MAX_DATA_LENGTH Initialization Configuration Resolved
Description
The
MAX_DATA_LENGTHis added as an allowed key but its value is never initialized. Therefore, any order created with adataListlength greater than zero will revert.Recommendation
Consider adding
MAX_DATA_LENGTHconfiguration to deploy script and avoid failed order creations.Resolution
GMX Team: Resolved.
-
L-15 Low eidToSrcChainId Might Not Be Defined Configuration Resolved
Description
The
eidToSrcChainIdwill convert LZ ids to actual chain ids. However, this mapping of ids needs to be implemented by the config keeper. If user bridges out to adstEidthat is not defined in GMX data store, asrcChainIdof 0 will be used.Although this issue only impacts events, it may mislead off chain services and data analytics, if a value of zero is used for
srcChainId.Recommendation
Make sure all LZ eids are mapped to the corresponding src chain ids or at least the ones supported by Stargate. Alternatively, consider reverting if the
srcChainIdis zero to make sure only the GMX supported Eids are used.Resolution
GMX Team: Resolved.
-
L-16 Low Modifier Gas Not Accounted For Logical Error Acknowledged
Description
The new controller functions in the Multichain routers contain three modifiers in the following order:
nonReentrant,onlyController,withRelayTherefore, the
startingGascached inwithRelaywill not account for the gas used inonlyController.Recommendation
Consider moving
onlyControllerto the last position in the list of modifiers.Resolution
GMX Team: Acknowledged.
-
L-17 Low Features Not Validated During Shifts Validation Resolved
Description
The shift flow requires a withdrawal from one market and a deposit to another, using
executeDepositFromControllerandexecuteWithdrawalFromController.These functions only validate if sender has a controller role, but not if the actual deposit and withdrawal feature are enabled.
Recommendation
Validate deposit and withdrawal feature in the respective controller function.
Resolution
GMX Team: Resolved.
-
L-18 Low Inability To Bridge Out During Shift Logical Error Resolved
Description
The shift execution includes a withdrawal from a market and deposit to a new one. During the deposit flow of a shift, an optional
bridgeOutFromControlleris executed, which will only early return if thedataListis not correctly formed.However, during this shift deposit, the
multichainTransferRouteris set asaddress(0). This causes all shift executions to revert ifdataListcontains theKeys.GMX_DATA_ACTION.Recommendation
If this is the expected behavior, consider documenting it for users, so it's clear that the
dataListcan't containGMX_DATA_ACTIONduring shift creation.It will be wise to add this validation during
createShiftto avoid cancellations. Alternatively, consider passingnew bytes32[](0)asdataListparam to theexecuteDepositFromController, just like it's done in theexecuteGlvShift.Resolution
GMX Team: Resolved.
-
L-19 Low Missing nonReentrant Protection Validation Resolved
Description
For dependency management, GMX has segregated previous contracts into various executors, such as
IncreaseOrderExecutor,SwapOrderExecutor, andDecreaseOrderExecutor. However, these executors currently lacknonReentrantprotection.Although we haven’t identified a specific exploit path—since the parent contracts (callers) are protected using global or local reentrancy guards—the tradeoff between minimal additional gas and improved safety justifies adding protection at the executor level.
Recommendation
Consider adding
nonReentrantmodifiers to relevant methods within the executor contracts to improve robustness.Resolution
GMX Team: Resolved.
-
L-20 Low Misspelled Filenames In Executor Contracts Best Practices Resolved
Description
The filenames of both
IncreaseOrderExecutorandDecreaseOrderExecutorare misspelled asSwapOrdeExecutorandDecreaseOrdeExecutorrespectively, with the letter "r" missing in the word "Order".Recommendation
Consider correcting the filenames to reflect accurate spelling for clarity and maintainability.
Resolution
GMX Team: Resolved.
-
L-21 Low Potential Feed ID Collision Due To Truncation Validation Acknowledged
Description
In
EdgeDataStreamProvider, theleftPadBytesfunction truncates thefeedIdto 32 bytes. This can lead to a potential replay issue: if two differentfeedIds share the same first 32 bytes, data for one could be reused or validated incorrectly for the other.Recommendation
According to Chaos Labs documentation, this scenario appears unlikely given current feed configurations. However, it remains a future risk.
To future-proof the system, especially if you retain control over the signature and signed data format, consider signing the
keccak256hash of the fullfeedIdinstead of the raw (or truncated) bytes.Resolution
GMX Team: Acknowledged.
-
L-22 Low Old Orders Can Overwrite Newer Ones Validation Resolved
Description
There is still the possibility that the user's last
srcChainIdused is not being the one saved inpositionLastSrcChainId.Here is an example:
- User opens a position at t0
- User creates a limit order for the position at t1
- At t2 an ADL or liquidation order uses the
srcChainIdfrom t0 not from t1 even though the data from
t1 is more recent
Recommendation
Consider fixing this edge case scenario or document this behaviour.
Resolution
GMX Team: Resolved.
-
L-23 Low Missing Validation For bridgeOutFromController Validation Resolved
Description
During GLV and GM deposits, users will have the option to bridge out these tokens using the
ExecuteDepositUtils.bridgeOutFromController.This function will early return if the
dataListis not correctly configured. However, ifsrcChainId = 0, the GM and GLV tokens were already sent to the receiver, but the transaction will revert as it will try to do a cross chain withdrawal.Recommendation
Add
srcChainId = 0to the early return check inExecuteDepositUtils.bridgeOutFromControllerResolution
GMX Team: Resolved.
-
L-24 Low LayerZero Configuration Configuration Acknowledged
Description
The
MultichainSenderandMultichainReceiverare Layer Zero OApps. Therefore, a proper wiring configuration should be performed to guarantee a safe message transferring.These involve:
OApp.setPeer(dstEid, peer)OApp.setEnforcedOptions()EndpointV2.setSendLibrary(OApp, dstEid, newLib)EndpointV2.setReceiveLibrary(OApp, dstEid, newLib, gracePeriod)EndpointV2.setReceiveLibraryTimeout(OApp, dstEid, lib, gracePeriod)EndpointV2.setConfig(OApp, sendLibrary, sendConfig)EndpointV2.setConfig(OApp, receiveLibrary, receiveConfig)EndpointV2.setDelegate(delegate)- Sending chain sending confirmations should match receiving chain receiving confirmations
- Sending chain DVNs should match receiving chain DVNs
Recommendation
Consider adding the proper Layer Zero configuration to these OApps
Resolution
GMX Team: Acknowledged.
-
L-25 Low isSrcChainIdEnabledKey Can Be Bypassed Validation Resolved
Description
During the
bridgeOutflow, the user-providedsrcChainIdis validated using theisSrcChainIdEnabledKey. However, the actual transfer occurs to the chain identified bydstEid, which is resolved viaeidToSrcChainId(cache.dstEid).There is no check to ensure that the user-provided
srcChainIdmatches the chain ID derived fromdstEid. This check can be bypassed by providing a validsrcChainIdalong with an unsupporteddstEid.Recommendation
Validate that the provided
srcChainIdmatches the chain ID resolved fromdstEid.Resolution
GMX Team: Resolved.
-
L-26 Low Keepers May Censor Actions Using BridgeOut Censoring Acknowledged
Description
The Stargate
sendandsendTokensfunctions use anonReentrantAndNotPausedmodifier which can be leveraged to force any system's interaction with Stargate to revert.This can be achieved when the tx originator first enters into the Stargate send function and receives a native fee refund from the
LayerZero EndpointV2contract while within the send function execution.The malicious tx originator will then call the victim system within the receive function and within the context of the Stargate send function.
Keepers on the GMX platform could leverage this behavior to cause the
StargatePool.sendfunction to revert, resulting in deposits that are cancelled rather than executed.Recommendation
Be aware of this censoring vector if GMX keepers are to be decentralized in the future.
Resolution
GMX Team: Acknowledged.
-
L-27 Low Lacking Stargate Fee Protection Warning Acknowledged
Description
The
minAmountvalue for stargate send invocations is determined by the result of the stargatequoteOftfunction. This means the GMX protocol will accept whatever fee the Stargate system reports.Therefore if users create actions that will
bridgeOutthrough stargate and the stargate owner address updates the fee rate to a large value then the user will have no protections against this.Recommendation
It may be acknowledged that the Stargate owner address is a trusted party, in that case simply be aware of this counterparty risks that GMX users are taking on during the
bridgeOutflow.Resolution
GMX Team: Acknowledged.
-
L-28 Low Missing NatSpec Param Documentation Resolved
Description
The
lentImpactPoolAmountparameter has been added toMarketPoolValueInfo.Props, but it's missing from the NatSpec-style field comments.Recommendation
Add
lentImpactPoolAmountto the struct comments.Resolution
GMX Team: Resolved.
-
L-29 Low Dangerous Timelock Admin Assignment Best Practices Acknowledged
Description
In the constructor for the
TimelockControllercontract the admin parameter is assigned as themsg.senderdeploying theConfigTimelockControllercontract.This grants unnecessary power to an EOA address, the admin role should be carefully managed. The ideal configuration would be to leave the
TimelockControlleras the only address with theTIMELOCK_ADMIN_ROLE.Recommendation
Consider assigning the
adminaddress asaddress(0)in the constructor of theConfigTimelockControllercontract. Otherwise be sure to transfer theTIMELOCK_ADMIN_ROLEto a multisig and away from the EOA deploying the system.Resolution
GMX Team: Acknowledged.
-
L-30 Low Misleading Timelock Transaction Success Unexpected Behavior Resolved
Description
The
TimelockControllercontract fromOpenZeppelinperforms the external call for execution with a simple success check on the result of the call.This however will incorrectly report the action as successful when attempting to invoke a function on an EOA address.
Recommendation
Be aware of this misleading behavior in the base Open Zeppelin contract and consider overriding
_executefunction if you would like to resolve it.Resolution
GMX Team: Resolved.
-
L-31 Low Outdated TimelockController Used Best Practices Acknowledged
Description
The
OpenZeppelin TimelockControllerversion used by GMX is outdated. The version used is listed aslast updated v4.9.0, however recent versions have been updated as recent asv5.3.0.Recommendation
Consider updating the
TimelockControllerversion to the latest available.Resolution
GMX Team: Acknowledged.
-
L-32 Low Unfavorable Impact Withdrawal Rounding Rounding Resolved
Description
The amount withdrawn from the GM markets should be rounded down to favor the protocol market value. However the
longTokenWithdrawalAmountandshortTokenWithdrawalAmountare not always rounded down.The maximum value for the
longTokenPriceandshortTokenPriceis used. However these values can have much smaller deviations than theindexTokenPricewhich is also maximized.In the case where the
indexTokenPricehas a large spread and thelongTokenPriceandshortTokenPricehave a small spread, the withdrawn amount is actually rounded up rather than down.Recommendation
Use the minimum of the
indexTokenprice when computing thelongTokenWithdrawalAmountandshortTokenWithdrawalAmountto ensure that the amounts withdrawn from the GM market are always minimized.Resolution
GMX Team: Resolved.
-
L-33 Low Lacking Default Admin Rules Best Practices Acknowledged
Description
The
ConfigTimelockControllercontract inherits from theTimelockControllercontract without implementing theAccessControlDefaultAdminRules.This is typically a recommended safe guard against operations like unintentionally revoking the default admin role.
However the contract adds a significant amount of complexity so it may be best to forgo it's implementation.
Recommendation
Be aware of the lack of safeguards in place when managing the default admin role and interacting with the time lock.
Resolution
GMX Team: Acknowledged.
-
L-34 Low Unnecessary amountLD Param In recordBridgeIn Informational Resolved
Description
The overloaded
recordTransferIn(token, amount)function was removed as part of the fixes for L-06 and L-07.However, the
amountLDparameter is still being passed torecordBridgeIn, even though it is no longer used to determine the bridged amount, which is now fetched directly via therecordTransferIn(token)call.Recommendation
Remove the amount parameter from the
recordBridgeInfunction.Resolution
GMX Team: Resolved.
-
L-35 Low Inaccurate executionPrice Emitted Events Acknowledged
Description
In the
getExecutionPriceForDecreasefunction only the maximum impact factor is applied to cap the resultingpriceImpactUsd.This excludes the capping that is performed based on the contents of the price impact pool and as a result, the
executionPricethat is computed does not reflect the actualexecutionPricethat is experienced by the order.Recommendation
Consider re-calculating the
executionPricewith a version of thepriceImpactUsdafter it has been capped by the impact pool amounts.Otherwise when computing the
executionPriceinitially, base it off of a version that has been capped by the impact pool amount.If little complexity should be introduced, consider just acknowledging this and documenting this behavior for consumers of the
executionPriceemitted on decrease.Resolution
GMX Team: Acknowledged.
-
L-36 Low Impact Distributions For Pending Impact Warning Acknowledged
Description
The pending impact changes have split the impact pool across three separate variables: the impact pool amount, the pending impact amount, and the
lentAmount.However the impact pool amount is still the only variable which experiences decay at the impact pool distribution rate.
With the change to move a significant amount of impact in the market to the pending impact, the magnitude of impact pool distributions will be markedly lowered relative to without it.
Recommendation
Be aware of this change in behavior and consider if it is intended. If it is intended then consider refactoring the configuration of the distribution rate. Otherwise introduce a distribution mechanism for the pending impacts.
Resolution
GMX Team: Acknowledged.
-
L-37 Low maxLendableFactor Configuration Configuration Resolved
Description
The
maxLendableFactorhas been introduced as a sanity check against the price impact available in a market.However it must now be configured to a non-zero value for every market to ensure positive price impact is not capped to zero, if that is not intended.
Recommendation
Be aware of this configuration requirement and consider if a default value should be introduced.
Resolution
GMX Team: Resolved.
-
L-38 Low Price Impact Griefing Warning Acknowledged
Description
With the introduction of the
maxLendableUsdvalidation, it is possible for a malicious LP to intentionally withdraw with an atomic withdrawal to force an account to be unable to realize their positive impact.Recommendation
This scenario is unlikely to yield any benefit given the LP would likely have to hold a large position and also be exposed to atomic withdrawal fees. However the possibility should be documented for integrations.
Resolution
GMX Team: Acknowledged.
-
L-39 Low Stargate Quote Inaccuracies Warning Acknowledged
Description
The
quoteOftfunction in theStargatePoolcaps theamountInby the available credit in the pool. This behavior creates two unexpected cases for consumers of thequoteOftfunction.The first case is when the original requested
amountInwould have produced anamountOutthat is larger than the credit of the pool. In GMX's case the fullamountInis still used in the SendParam.However the
minAmountLdis adjusted to be the result of the cappedamountInminus fees. This means GMX is assigning a large slippage threshold.For example:
- Credit available is 10
- Fee rate is 10%
- GMX quotes send for 15
amountInfor the quote is adjusted to 10amountOutreported is 9- GMX uses 15 amount in and 9 as the
minAmount, allowing a fee of 6 - This action reverts anyways as the resulting amount of 13.5 is greater than the available credit
The second case is when the original requested
amountInwould have produced anamountOutthat is within the credit of the pool.For example:
- Credit available is 10
- Fee rate is 10%
- GMX Quotes send for 11
amountInfor the quote is adjusted to 10amountOutis reported to be 9- GMX uses the 11 amount and actually receives 9.9 after execution
- The slippage was overallocated in this case
Recommendation
In either case there is no possibility for extraction of the additional slippage room, so no action is necessary. Simply be aware of these edge cases for future use of the
quoteOftfunction and any adjustments to theminAmount.Resolution
GMX Team: Acknowledged.
-
L-40 Low Lacking Collateral Factor Validations Validation Acknowledged
Description
The general
minCollateralFactorshould always be larger than theminCollateralFactorForLiquidationsto ensure that users cannot create an immediately liquidatable position however this is not validated in the Config contract.Recommendation
Be aware of this lacking validation when configuring these variables, otherwise consider implementing the validation in the Config contract.
Resolution
GMX Team: Acknowledged.
-
L-41 Low Asymmetric Capping Warning Acknowledged
Description
As a continuation of the discussion in H-0X and H-0X, this finding serves to call out abnormalities when the negative impact in a market is capped significantly without entirely or nearly entirely reducing positive impact to zero.
Positive impact must be paid for by negative impact as defined by the impact pool, therefore if negative impact is always capped to zero then positive impact cannot be offered.
On the other hand, If a feature is adopted where the lent amount is reserved and cannot be capped away for negative impact, then it may be important to continue to cap the pending positive impact on decrease by the remainder available in the impact amalgam.
This is because, in this paradigm, it is no longer guaranteed that positive impact is realized for a position on increase.
In that sense, the conversion of pending positive impact to a
lentAmountis what locks in a pending positive impact and users are incentivized to close their position quickly to realize it.Recommendation
Make clear the intentions for positive impact in the future as this will define several design decisions with the zero impact mechanism.
Resolution
GMX Team: Acknowledged.
-
L-42 Low Functions Cannot Be Used With Multicall Warning Acknowledged
Description
The
transferOutandbridgeOutfunctions in theMultichainTransferRoutercontract are not marked aspayableand therefore cannot be used within a multicall context.Recommendation
Consider if this is the intended behavior or if these functions should be able to be called within a multicall. If they are, then mark these functions as payable.
Resolution
GMX Team: Acknowledged.
-
L-43 Low Missing isSrcChainIdEnabled Check Access Control Acknowledged
Description
In the
lzComposefunction, if there is no composed deposit action theisSrcChainIdEnabledvalidation is not performed.Therefore bridges may occur originating from chains which are not enabled with the
isSrcChainIdEnabledKey.Recommendation
Consider adding validation on the
isSrcChainIdEnabledKeyfor all incominglzComposeactions.Resolution
GMX Team: Acknowledged.
-
L-44 Low Price Impact Factors Should Be Adjusted Warning Acknowledged
Description
With the introduction of pending price impact, the
totalPriceImpactUsdthat is realized on any given order and position can be multiples of what it was before.This is notable when comparing against the maximum positive and negative impact factors when these amounts may not be calibrated for the higher magnitude being realized more regularly.
Recommendation
Consider increasing the positive and negative price impact cap factors in accordance with the fact that the magnitude of price impact realized at any one time can be larger. Be sure to also address this in the
isPositionLiquidatablecheck.Resolution
GMX Team: Acknowledged.
-
L-45 Low Redundant WithRelayCache Struct Informational Resolved
Description
The
WithRelayCachestruct is no longer used in the modifier after the fix of L-23. However, the struct is still defined in theBaseGelatoRelayRoutercontract, making it redundant.Recommendation
Remove the
WithRelayCachestruct.Resolution
GMX Team: Resolved.
-
L-46 Low Initialize Frontrunning Frontrunning Resolved
Description
The
MultichainTransferRoutercontract uses aninitializefunction that is ungated to assign critical values.This initialize function could be frontrun by a malicious actor during deployment if the initialization is not performed in the same transaction as the deployment.
Recommendation
Consider either adding a trusted modifier to the initialize function or ensuring that the deployment initializes this function in the same transaction.
Resolution
GMX Team: Resolved.
-
L-47 Low Unclear Native Deposit BridgeOut Validation Validation Resolved
Description
During the deposit flow, the
bridgeOutFromControllerfunction is invoked even for native deposit actions. For these actions thesrcChainIdwill be assigned as 0 and the execution will continue to make an external call to themultichainTransferRoutercontractbridgeOutFromControllerfunction.This function will always revert for deposits that have been made natively on Arbitrum due to the
isSrcChainIdEnabledvalidation that occurs in the_validateCallWithoutSignaturefunction. ThesrcChainIdof 0 will not be enabled.Recommendation
If it is intended that deposits made on Arbitrum cannot bridge out to other chains, then consider validating this or early returning explicitly and earlier in the execution flow in the
ExecuteDepositUtils.bridgeOutFromControllerfunction.If it is intended that native deposits should be able to bridge out to other chains, consider refactoring the way this flow is handled such that the
srcChainIdof 0 allows GM or GLV funds to be sent to their target chain.Resolution
GMX Team: Resolved.
-
L-48 Low Unexpected Deposit Execution Reverts Validation Resolved
Description
Users now can optionally bridge out GM and GLV tokens using Stargate, at the end of the deposit execution.
However, the
bridgeOutFromControllercall is not in a try/catch block so any revert in the bridge out flow will make the entire deposit execution fail, cancelling the request.Users can either inadvertently or maliciously cause the bridge out flow to revert, in different ways:
- Not having enough WNT multichain balance to pay bridge fee or relay fee
- Making an external call fail in
MultichainTransferRouter - if
srcChainIdis 0, as it's not an enabled chain
Recommendation
Consider wrapping the
bridgeOutFromControllerin a try/catch block to avoid the entire deposit execution to revert. Alternatively, add some validations during deposit creation if the user wants to bridge out the funds.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.
