Guardian's review of Just-in-Time Liquidity for GMX, published November 2025. The report records 50 findings across 3 review rounds, including 2 high and 13 medium.
- Published
- Review window
- August 27 to November 5, 2025
- Rounds
- Main Review, Remediation Review 1, Remediation Review 3
- Language
- Solidity
- Chains
- Arbitrum, Avalanche
- Sector
- Perpetuals, Yield and vaults
- 0 Critical
- 2 High
- 13 Medium
- 34 Low
- 1 Informational
Scope
Findings 50
Main Review
41 findings · August 27 to September 3, 2025-
H-01 High Keeper Fees Are Trapped Logical Error Resolved
Description
In the
executeOrderFromControllerfunction the keeper is assigned as themsg.sender, however the caller of this function will be theJitOrderHandler. As a result the keeper fees will be sent to theJitOrderHandlercontract and trapped. The same will occur for the execution fee upon cancellation or freezing since the_handleOrderErrorassigns themsg.senderas the keeper always in the invocations toOrderUtils.cancelOrderandOrderUtils.freezeOrder.Recommendation
Add a keeper parameter to the
executeOrderFromControllerfunction. -
H-02 High Limit Orders Require Out Of Date Prices Logical Error Resolved
Description
When executing a
LimitIncreaseorStopIncreaseorder through theJitOrderHandlercontract, theupdatedAtTimeof theGlvShiftaction is set to theorderUpdatedAtTime. However in the case ofLimitIncreaseorStopIncreaseorders, theorderUpdatedAtTimewill often be far in the past. The logic for deposits and withdraws which will occur as a part of the GLV shift requires that the prices set in the oracle come from around the time of the action’supdatedAtTime. As a result, if a keeper were to execute this action with prices from the order’s creation the prices would be invalid for the current market value of GM markets. In practice, this simply reverts due to failing the oracle’s maximum staleness checks, soLimitIncreaseandStopIncreaseorders cannot be executed with JIT.Recommendation
Consider allowing the order keeper to provide two sets of prices, one for the Shift action and one for the order action, ensuring that prices are cleared after the shifts are executed and again after the order is executed. This way the difference in price requirements can be met no matter what. The shift action should use the most up to date prices possible, using the
block.timestampas theupdatedAtTimesimilar to atomic withdrawals. While the order action may have specific requirements on when the prices are from depending on when the trigger price was hit. Also notice that thesimulateExecuteJitOrderfunction will have to be updated to handle two separate price submissions. -
M-01 Medium Missing Feature Validation Validation Resolved
Description
In the
_processMarketDepositfunction during a GLV deposit, there is no validation to ensure that the GM market has deposits enabled before executing the deposit. This is in contrast to the way that Shift actions execute deposits using theexecuteDepositFromControllerfunction which validates the deposit feature. In the_processMarketWithdrawalfunction during a GLV withdrawal, there is no validation to ensure that the GM market has withdrawals enabled before executing the withdrawal. This is in contrast to the way that Shift actions execute deposits using theexecuteWithdrawalFromControllerfunction which validates the withdrawal feature.Recommendation
Consider validating that the withdrawal feature is enabled during these functions, or using the
executeDepositFromControllerandexecuteWithdrawalFromControllerfunctions on the respective handlers. -
M-02 Medium Orders Execution Fee Does Not Cover Shifts Validation Acknowledged
Description
Jit orders now require the execution of several shifts, the gas needed for which will be tracked by the
startingGasrecorded in theexecuteJitOrderfunction. However the order execution fee is not increased for these orders, as a result the keeper will often subsidize the gas consumption of these shifts.Recommendation
Be aware of this behavior and consider if it is acceptable for the keeper to subsidize the gas costs for these shifts.
-
M-03 Medium Inaccurate Gas Validations Validation Resolved
Description
In the
executeJitOrderfunction of theJitOrderHandlercontract thevalidateExecutionGasandgetExecutionGasvalidations are performed on thegasleft()value. However this gas value does not match the gas that is available in theexecuteOrderFromControllermostly due to the 63/64 rule for the external call to theOrderHandler. Therefore the gas available to the_executeOrderfunction is not accurately validated.Recommendation
Consider moving the gas validations into the
executeOrderFromControllerfunction similarly to theexecuteOrderfunction such as the following:function executeOrderFromController( bytes32 key, Order.Props memory order, uint256 startingGas, bool isSimulation ) external onlyController { uint256 executionGas = gasleft(); uint256 estimatedGasLimit = GasUtils.estimateExecuteOrderGasLimit(dataStore, order); GasUtils.validateExecutionGas(dataStore, executionGas, estimatedGasLimit); executionGas = GasUtils.getExecutionGas(dataStore, executionGas); try this._executeOrder{ gas: executionGas }( key, order, msg.sender, isSimulation ) { } catch (bytes memory reasonBytes) { _handleOrderError(key, startingGas, reasonBytes); } } -
M-04 Medium Users Only Pay Gas For Shifts On Failure Logical Error Resolved
Description
In the
executeJitOrderfunction the startingGas is declared before the shifts are processed in order to track the amount of gas used by the shift actions for the JIT order. However the startingGas value passed to theexecuteOrderFromControllerfunction is only used when the order execution fails. Upon the successful completion of an order the startingGas that is re-declared at the beginning of the_executeOrderfunction is used, which does not track the gas usage of the previous shift actions. As a result users with orders that successfully complete with JIT will not track the gas expenditure made for the shifts and may end up refunding users a portion of their execution fee which ought to have paid the keeper for the shift gas expenditure.Recommendation
Consider updating the
_executeOrderfunction to accept astartingGasvalue as a parameter rather than declaring it at the beginning of the function. -
M-05 Medium Misleading Simulation Results Unexpected Behavior Resolved
Description
In the
simulateExecuteJitOrderfunction the sharedorderHandler.executeOrderFromControllerfunction is used to execute the simulation. However this function includes a try/catch which will prevent any error encountered during the order’s execution from being raised. This is in contrast to the existingsimulateExecuteOrderfunction which does not include a try/catch.Recommendation
Create a separate
simulateExecuteOrderFromControllerfunction to be used for thesimulateExecuteJitOrderfunction which does not include a try/catch. -
M-06 Medium poolToPnlRatio Abuse With JIT Gaming Acknowledged
Description
Traders now have the ability to potentially influence where GLV shifts are made by creating large orders for distinct markets. As a result, traders could potentially extract value by pushing GLV liquidity into markets that have a
pnlToPoolRatioabove the withdrawal or trader max pnl levels. If a pool is above the withdrawal maxpnlToPoolRatio, any user who holds GM tokens in that market is incentivized to create a large order and trigger JIT to force liquidity to shift into that market and allow them to withdraw their GM tokens at the expense of the GLV holders, who are now holding a nearly illiquid GM token. If a pool is above the trader’s max pnlpnlToPoolRatio, any user who has a position which is being profit capped is incentivized to open a large trade in that market to draw more liquidity in to decrease thepnlToPoolRatioand unlock more of their profits at the expense of the GLV holders.Recommendation
Consider adding off-chain validations to avoid triggering Jit order executions for markets which have a
pnlToPoolRatioabove or even close to the withdrawalpnlToPoolRatio. -
M-07 Medium GLV Oracle Lag Causes Accounting Mismatch Gaming Acknowledged
Description
When using the oracle price for the
getGlvValueresult, the GLV price is lagging from a few seconds ago. However the price of the underlying market tokens which are subsequently used in the deposit or withdrawal actions, e.g. to figure out how many market tokens to withdraw, is from the current block. This time differential between when the GLV price is measured from and when the underlying GM price is measured from results in mispricing of GLV which occurs over time. The most obvious case of this is when a GM market experiences a stepwise jump in price. Though this is not necessary for this affect to take place over a period of time as small deviations build up and affect the GLV price. For the sake of example we will examine the affect on GLV price when an underlying GM market experiences a stepwise decrease in price resulting from the insolvent liquidation of a position. The stepwise decrease in price occurs from the system realizing in that single liquidation transaction that the position could not pay for it’s total negative PnL. (Figure. See the PDF report.)Consider the following scenario:
- GM A is priced at $2 in block 100
- GLV holds just 5 GM A tokens
- GLV supply is 5
- GLV is worth $10 in block 100
- User A withdraws 2 GLV to GM A and their GLV withdrawal is processed in block 105 using the GLV price from block 100 via the oracle
- GLV is valued at $2 per GLV during the withdrawal, giving User A $4 of withdrawal value
- GM A is valued at $1 during the withdrawal, because it is using the valuation of the market token from block 105
- User A withdraws 4 GM A tokens from the GLV
- Now GLV price is 1 GM A token per 3 GLV = $0.33
- If User A had not done this withdrawal straddling the stepwise decrease in GM A price, the GLV price would rightfully be $1.
Notice that a distinct stepwise decrease does not have to happen, but if minor deviations between these two times contribute to shifts in price over a long period of time GLV holders will have their earnings markedly affected.
Recommendation
Ideally, the price of the GM token being deposited and withdrawn would be valued from the same point in time as the oracle price for GLV. Otherwise an imperfect solution would be to simply ensure that the block lag is consistently as small as possible for the GLV oracle, and hope that no notable stepwise jumps are straddled by a GLV action.
-
M-08 Medium GLV Arbitraged With JIT Gaming Partially resolved
Description
The JIT execution strategy can be triggered by users when they submit an order that exceeds the available liquidity in the pool. Given that users can trigger the exact market that JIT will move funds towards, it’s possible for a user to frontrun the shift of a JIT action in order to benefit off of the liquidity amounts being deposited through price impact. For example:
- GM A and B are in the same GLV
- The GLV holds 50 of the 100 GM A supply
- GM A has $10,000 WETH and $20,000 USDC
- GM B has $15,000 WETH and $15,000 USDC
- User A deposits $5,000 of WETH and $5,000 of USDC into GM B
- GM B now has $20,000 WETH and $20,000 USDC
- User A triggers a JIT action to shift from GM A to GM B by opening a large order for GM B
- GLV Shifts 50% of GM A into GM B
- GM B now has $25,000 WETH and $30,000 USDC, GLV experiences negative impact during this shift because GM B is now imbalanced
- User A withdraws $2,500 WETH and $7,500 USDC and receives positive impact because they have now balanced GM B back to $22,500 WETH and $22,500 USDC
There are maximum loss checks that take place upon GLV Shifts, however this only applies for a single GLV shift action. If an actor repeats this process many times over a period of time they can arbitrage much more than the maximum loss percentage over several shifts. To make this strategy easily executable, users could continue to submit orders that trigger JIT but automatically fail their execution and are cancelled due to minimum output amount configurations on the increase order swap trivially failing to prevent the user from taking unwanted exposure for large orders.
Recommendation
Consider adding logic in the off-chain JIT keeper that prevents JIT actions from occurring when GLV Shifts have accumulated a certain threshold of losses due to price impact and other fees over a period of time. Furthermore, consider simulating the result of the JIT action and ensuring that the order is successfully executed and not cancelled or frozen by the JIT execution.
-
M-09 Medium Missing Keeper Error Validation Resolved
Description
In the
OrderUtils.cancelOrderfunction theInsufficientGasForAutoCancellationerror has been introduced which is raised instead of theInsufficientGasForCancellationerror for non-autoCancel orders. However theInsufficientGasForCancellationerror has special handling where it causes a revert in theOrderHandler._handleOrderErrorfunction to prevent keepers from censoring orders. TheInsufficientGasForAutoCancellationis not included in thevalidateNonKeeperErrorvalidation and therefore allows keepers to censor orders by providing insufficient gas for the auto cancellation order.Recommendation
Include the
InsufficientGasForAutoCancellationerror in thevalidateNonKeeperErrorvalidation. -
M-10 Medium GLV Used To Exit Illiquid Markets Gaming Acknowledged
Description
With the new JIT feature there is a pathway for holders of GM tokens that are illiquid due to poolToPnl ratio to exit their GM holdings at the expense of GLV. Consider the following scenario:
- User A holds 100 GM A
- GM A is at a pnlToPoolRatio of 30%
- The pnlToPoolRatioForWithdrawals is 20%
- GM A is currently locked out of withdrawals
- User A forces GLV to shift into GM A by creating a large trade in GM A
- The pnlToPoolRatio of GM A drops to 15% because User A’s large position has no profit
- User A closes their large position
- User A withdraws their GM A tokens since now the pnlToPoolRatio allows them to do so
- As a result of User A’s withdrawal, the pnlToPoolRatio goes back to 20%
- GLV is stuck with the now again illiquid GM A tokens
Recommendation
Be aware of this manipulation to exit illiquid GM markets. Consider adding validation to prevent such gaming based on the
pnlToPoolRatioof markets that are being shifted into. No market that is the receiver of aGlvShiftshould be above the maximumwithdrawalPnlToPoolRatio. -
M-11 Medium Target Shifts Can Not Be Simulated Logical Error Resolved
Description
In the
ShiftHandlercontract thesimulateExecuteShiftfunction still has theonlyControllermodifier which means that users cannot simulate the execution of a particular shift.Recommendation
Remove the
onlyControllermodifier from thesimulateExecuteShiftfunction in theShiftHandlercontract. -
L-01 Low Misleading Simulations Because Of Shared Key Warning Acknowledged
Description
The
simulateExecuteLatestJitOrderfunction assumes that the current key belongs to an order, however since the key is shared across multiple actions such as orders, deposits, and withdrawals, the key may not belong to an order object. This can lead to perturbed simulations when a deposit or withdrawal has been created after an order and the deposit or withdrawal is fetched from the dataStore in place of the latest order.Recommendation
Be aware of this behavior and consider if it is desired in the simulation flow.
-
L-02 Low ADLs Executed When Orders Disabled Warning Acknowledged
Description
The
AdlHandleronly validates if theexecuteAdlFeatureDisabledKeyfeature is disabled before executing an order with theExecuteOrderUtils.executeOrderfunction. This ignores theexecuteOrderFeatureDisabledKeyfeature and is in contrast to the liquidation handler which validates theexecuteOrderFeatureDisabledKeyfeature.Recommendation
Consider if this is the expected behavior and consider also validating the
executeOrderFeatureDisabledKeyfeature in theAdlHandler. -
L-03 Low Unused getGlvTokenPrice Function Superfluous Code Resolved
Description
The
getGlvTokenPricefunction which takes only 5 parameters defined here: https://github.com/gmx-io/gmx-synthetics/blob/1be5f5166fff8cd6016a8ce9d7b57ab93ded3870/contracts/glv/GlvUtils.sol#L145 is not used throughout the codebase.Recommendation
Consider if this version of the function should be used instead of the following one which is used in the
GlvReadercontract. -
L-04 Low Lacking Reentrancy Checks Best Practices Acknowledged
Description
In the new
executeOrderFromControllerfunction there is nononReentrantmodifier to prevent re-entrancy back into theexecuteOrderFromControllerfunction. For all functions on handler contracts which are notglobalNonReentrant, they should be declarednonReentrant. Ideally allglobalNonReentrantfunctions in a handler contract are also markednonReentrant, so that there can be no re-entrancies that begin with aglobalNonReentrantfunction and enter into a simplenonReentrantfunction. While not strictly necessary, the appropriate re-entrancy guards for nonglobalNonReentrantfunctions will further constrict the allowed execution paths to only expected pathways.Recommendation
Firstly, mark the
executeJitOrderfunction asnonReentrant. Secondly, consider making all handler functionsnonReentrantin addition toglobalNonReentrantso that the purelynonReentrantfunctions cannot be re-entered into from aglobalNonReentrantfunction. This extra validation comes with a cost of an additional storage write which will negatively impact gas costs. To combat this, instead a transient storage re-entrancy guard can be used. -
L-05 Low Lacking From And To Market Check Validation Resolved
Description
The
validateGlvShiftfunction does not validate if the from market and to market are the same market. This could lead to unexpected issues if theorderKeeperaccidentally initiates a shift where the from market is the same market as the to market.Recommendation
Consider adding a validation to the
validateGlvShiftfunction that prevents the from and to markets from being the same address. -
L-06 Low Outdated Naming Documentation Resolved
Description
In the
validatePriceImpactfunction, the function itself and several variables mention price impact. However the underlying key has been renamed to theglvShiftMaxLossFactorKey.Recommendation
Consider renaming the
validatePriceImpactfunction and the variables inside of it to be congruent with the newglvShiftMaxLossFactorKeyname. -
L-07 Low Unexpected Behavior When GLV First Created Warning Acknowledged
Description
Because GLV now uses an oracle for prices, there will be some lag between the current GLV price and the price reported by the oracle. One particular case which causes a significant difference between these is when a GLV has been created and receives it’s first deposit. The initial deposit requires that a small amount of GLV is locked to set an initial non-trivial supply and backing. This is the first jump where GLV goes from being worth nothing to being worth something. If the GLV oracle is already live at this point there may be unexpected behavior where in the block of the first deposit, and potentially in the subsequent blocks afterwards, the oracle continues to report GLV as being worth nothing while GLV is indeed worth something. This may lead to unexpected edge cases which are not forseen, for example this causes a divide by zero revert in the
usdToGlvTokenAmountfunction.Recommendation
Be sure to only create the oracle for a GLV and allow it to be used after the GLV is established with some amount of non-trivial TVL for a sufficient duration.
-
L-08 Low Max Pnl Validation Missed Validation Resolved
Description
In the
_processMarketDepositfunction thevalidateMaxPnlvalidation only occurs if the deposit is a market token deposit. However this validation could be performed even if the GLV deposit is not a market token deposit, since it is bad for GLV holders to receive a GM token which is above it’s max pnl factor for withdrawals regardless of how the depositor obtains them. There is a clear incentive for a depositor to use the GLV as exit liquidity if they are already holding these GM tokens, however it’s worthwhile to always prevent the illiquid tokens from winding up in GLV when the max pnl factor for withdrawals is exceeded.Recommendation
Consider performing the
validateMaxPnlalways in the_processMarketDepositfunction. -
L-09 Low Increased Risk Of Arbitrage Gaming Acknowledged
Description
Since the GLV price is now determined by an oracle which carries a small delay, there is a risk of arbitraging the outdated oracle price relative to the real underlying value of GLV. This is because users can directly observe the real value of GLV in realtime onchain, and compare this to the reported price from the oracle. This same staleness applies to all other actions on GMX, and typically oracle delays are not a large concern because of fees applied to actions which use the oracle reference price. However on GLV deposits with GM tokens, there are no new fees which are levied on the user. This allows users to have much more leeway to create a profitable arbitrage if e.g. the oracle reports GM A as being worth $1.50 while it is currently worth $1.53. Notice that this slightly better chance at realizing a profit from the slight oracle staleness only applies to users who are already holding GM tokens.
Recommendation
Consider requiring a lower tolerance for the maximum staleness of the GLV oracle if it is a dataStream, or use an aggregator with a very low deviation threshold.
-
L-10 Low GlvShift Interval DoS DoS Acknowledged
Description
When a GLV Shift occurs a cooldown is reset which prevents further GLV shifts from occurring for a given period of time. With the JIT feature, if this cooldown period is configured for a non-trivial amount of time, users could prevent legitimate rebalances or other JIT actions by consistently placing large trades and then closing them shortly thereafter.
Recommendation
Be aware of this DoS vector and ensure that the cooldown period for GLV shifts is assigned with this in mind.
-
L-11 Low Unclear getPrimaryPrice Behavior Documentation Resolved
Description
The
getPrimaryPricefunction implementation documentation describes that it reverts when the price is empty for the given token. However thegetPrimaryPricefunction does not revert when the token is the zero address and the price is instead returned as the empty price.Recommendation
Either make this behavior abundantly clear with a warning or consider changing the behavior of the
_getPrimaryPricefunction to revert ifraiseOnEmptyis true and the provided token is the zero address. -
L-12 Low GLV Value Updates Are Incorrect Logical Error Acknowledged
Description
In the
executeGlvDepositfunction thegetGlvValuefunction is used before and after the execution of the GLV deposit. Before the deposit thegetGlvValuefunction is used to determine the GLV mint amount based on the current value of GLV and the value of the assets being deposited by the user. After the deposit has been executed, thegetGlvValuefunction is used a second time to query the updated value of GLV to emit with theemitGlvValueUpdatedevent. However theforceCalculationboolean for this second invocation is false, and therefore it reports the same GLV price that was set by the oracle, showing no updated GLV value after the deposit. Similarly during GLV withdrawals and shift flows thegetGlvValuefunction is queried again to emit the updated value in theemitGlvValueUpdatedevent, though it inaccurately reports this because theforceCalculationisfalse.Recommendation
Consider removing the
emitGlvValueUpdatedevent from the GLV deposit, GLV withdrawal, and GLV shift flows. -
L-13 Low Unexpected Key Replay Unexpected Behavior Resolved
Description
In the
JitOrderHandler_createGlvShiftfunction the key of the GLV shift is based on theorderKeyand the index of the shift action in the shift list. Keys are generally expected to be unique to each action and only correspond to one distinct action on the exchange. However, it’s possible for the same orders to be executed multiple times through theJitOrderHandlerin the event that an order is initially frozen and then unfrozen by the user updating the order. As a result, the_createGlvShiftcan construct the same key for a corresponding GLV shift action, only this time the actual shift parameters of the from and to markets could be different. This breaks the typical assumption that an action key is only executed once and that an action key pertains to one unique action on the exchange. In this case the ultimate effect is just that events will emit updates for this key multiple times across potentially different GLV shift actions.Recommendation
Be aware of this behavior and if it is not desired, consider making the key unique to the GLV shift parameters so that the key must at least pertain to the same kind of shift. Or enforcing that it is unique to the JIT execution by hashing the
block.timestampwith the key. -
L-14 Low Shifts Occur No Matter The Order Result Unexpected Behavior Resolved
Description
The
JitOrderHandlerrelies on theexecuteOrderFromControllerfor order execution. ThisexecuteOrderFromControllerfunction does not revert if the order fails to execute, and instead uses the_handleOrderErrorfunction of the OrderHandler to silently handle the cancellation or freezing of said order. This results in shifts being executed and remaining executed in the event that an order executed through theJitOrderHandlerfails execution. This may be unexpected since liquidity was moved without being utilized by the increase order.Recommendation
Consider if this is the expected behavior. If not, consider reverting the
executeOrderFromControllerin the event that the order execution fails. -
L-15 Low Multiple Shifts Cannot Occur Logical Error Resolved
Description
In the
executeGlvShiftfunction thevalidateGlvShiftIntervalvalidation is performed to ensure that for a given GLV shifts are only executed at most once everyglvShiftMinInterval. However, in the context of a JIT order execution a list of shifts will be provided and all must be executed within the same GLV because the receiving GM market can only belong to one GLV token. The list of shifts will not be able to be executed because the first shift action will set theglvShiftLastExecutedAtKeyvalue to the currentblock.timestamp, causing the following shift executions to fail thevalidateGlvShiftIntervalvalidation.Recommendation
Consider either setting the
glvShiftMinIntervalto zero and retiring this validation, or allowing the validation to be bypassed by the JIT shift execution. -
L-16 Low Lacking doExecuteGlvShift Reentrancy Guard Reentrancy Resolved
Description
In the
GlvShiftHandlerthere is no reentrancy guard for thedoExecuteGlvShiftfunction which can by called by the contract itself or from a controller contract. A reentrancy guard could be added which would not clash with theglobalNonReentrantof theexecuteGlvShiftfunction and add some protection against any unexpected reentrancy into specifically thedoExecuteGlvShiftfrom outside controllers.Recommendation
Consider adding a
nonReentrantmodifier to thedoExecuteGlvShiftfunction. -
L-17 Low Order Execution Feature For Liquidation Contract Unexpected Behavior Acknowledged
Description
The
LiquidationHandlercontract validates theexecuteOrderFeatureDisabledKeyhowever it uses theaddress(this)which means that the feature enabled status will be validated with theLiquidationHandlercontract as the context. Therefore if the order feature is disabled for theOrderHandler, the order execution feature can still be enabled and orders can still be executed through theLiquidationHandler.Recommendation
Be aware of this behavior.
-
L-18 Low Unnecessary GLV Price Timestamp Requirements Unexpected Behavior Acknowledged
Description
During the execution of a GLV deposit or a GLV withdrawal, the prices used by the oracle are required to be accurate for the market order timestamp rules. This includes the GLV price which will be submitted to the oracle but not used for the withdrawal or deposit sub action processing. The GLV price is unnecessarily subject to the rules of the index, long, and short token prices that will be used for the deposit or withdrawal actions. This may cause issues in some cases where the most up to date price for GLV cannot be used to execute a GLV order that is somewhat stale, but still normally executable. This gives more credence to the issue described in H-02, where in some cases keeper’s may be required to submit a slightly outdated GLV price to meet these requirements. The same applies to GLV shifts, where the order keeper may create a GLV shift and not immediately execute the request. In this case unnecessary constraints may apply to the GLV price submission upon execution of the GLV shift.
Recommendation
Keepers must be adept at understanding when to provide the GLV price from the data stream versus when to not provide the GLV price from the data stream when the latest most accurate price cannot satisfy the deposit and withdrawal market order price timestamp requirements. Alternatively, similar to the solution of H-02, the computation could be forced for all GLV deposits and withdrawals and GLV shifts that are created in two-steps through the GLVShiftHandler.
-
L-19 Low Unexpected Order Cancellation Unexpected Behavior Acknowledged
Description
In the
OrderUtils.cancelOrderfunction theisSupportedOrdervalidation now optionally raises either theUnsupportedOrderTypeerror or theUnsupportedOrderTypeForAutoCancellationerror depending on if the cancellation being executed is an auto cancellation or not. TheUnsupportedOrderTypeerror is specifically handled in theOrderHandler._handleOrderErrorfunction so that the execution reverts instead of going on to cancel the order. However theUnsupportedOrderTypeForAutoCancellationerror is not explicitly handled this way. This means that when an outdatedautoCancelorder using an old order type which is no longer supported is encountered, the order that triggers this autoCancellation will fail it’s execution and be cancelled. The alternative behavior, if there were similar specific handling for theUnsupportedOrderTypeForAutoCancellationerror, would be that the order fails execution and simply reverts instead of being cancelled.Recommendation
This behavior may be fine, though it differs from the handling of other
UnsupportedOrderTypefailures. Be aware of this inconsistency and consider if this is the desired behavior when an outdated unsupported order type is encountered during auto cancellation. -
L-20 Low JIT DoS’d With Atomic Actions DoS Acknowledged
Description
Though there is minimal incentive for a perpetrator, it is possible to DoS the execution of a JIT action or the possibility of such execution by making atomic actions to swap or withdraw the necessary backing liquidity that would allow the JIT to take place and occur within the acceptable impact threshold configured.
Recommendation
Be aware of this DoS vector, though it is unlikely to provide any benefit to the perpetrator at a non-trivial cost.
-
L-21 Low JIT Short Term Risk Free Trades Gaming Resolved
Description
Due to the validations around the execution of JIT actions, it may be possible to carry out a short term trade depending on the keeper logic. For example, it may be too price impactful to carry out a JIT action to execute a large market order order at t = 100. The user could observe that price moves in their favor by t = 110 and decide to make a swap in the GM from market to balance it, thus allowing the JIT action to be within the overall price impact constraints. Then and only then is the JIT action carried out, using the prices adhering to the market order requirements at t = 100. This attack class applies to any validations that would prevent the JIT action from taking place, such as lacking GLV liquidity to shift, too significant resulting OI imbalance, too high specific GM concentration in a GLV, etc…
Recommendation
Rather than waiting to see if conditions improve to allow a JIT action to occur, execute an order normally to cancel or freeze the order immediately.
-
L-22 Low JIT May Cause Some Orders To Fail Unexpected Behavior Acknowledged
Description
If the increase order has a
swapPathwhich goes through a GM market that the JIT action pulls liquidity from, the JIT action itself could cause the order execution to fail. This may be unexpected and in some cases may unnecessarily cause orders to be cancelled rather than executed.Recommendation
If there is ever an option between using a GM market which is not in the order’s
swapPathand using one which is in the order’sswapPathfor a JIT action, the GM market which is not in the order’sswapPathshould be used. -
L-23 Low JIT May Prevent Withdrawals Warning Acknowledged
Description
Because GLV withdrawals are two step actions there may be a certain amount of GM assets that have open requests to be withdrawn from a GLV by GLV holders. If a GLVShift as a result of a JIT action occurs before these GLV withdrawals are executed then it may cause these withdrawals to fail as it removes the specific GM tokens that these withdrawals requested.
Recommendation
Be aware of this risk of DoSing in-flight withdrawals and consider having the keeper take into account open withdrawal requests before initiating a JIT action.
-
L-24 Low GLV May Be Left Improperly Allocated Warning Acknowledged
Description
If after a JIT action is executed a user closes their position shortly after it is successfully opened, the GLV may be left improperly allocated in a market that doesn’t hold much open interest. This will require another GLV Shift to shift back to a market with high utilization, which will ultimately cost more slippage upon the execution of this additional shift. Overtime this increased shifting may have a negative impact on GLV’s value accrual.
Recommendation
Be aware of this increased cost of additional shifts when markets quickly lose open interest due to concentrated positions as a result of JIT.
-
L-25 Low Unnecessary ReentrancyGuard Superfluous Code Resolved
Description
The
JitOrderHandlercontract inherits from the OpenZeppelinReentrancyGuardabstract contract, however does not use any of theReentrancyGuardfeatures. Instead theJitOrderHandlermakes use of theglobalNonReentrantmodifier which does not come from theReentrancyGuardcontract.Recommendation
Remove the
ReentrancyGuardcontract import and usage in theJitOrderHandlercontract. -
L-26 Low Multiple Shifts From A Market Validation Acknowledged
Description
In the
_processShiftsfunction there is no validation that ensures that a shift from afromMarkethas not already occurred. It doesn’t make sense to shift from the samefromMarkettwice since this could occur in one larger shift, therefore this unexpected execution path could be limited to avoid unexpected behavior or logical issues from the keeper.Recommendation
Consider adding validation that checks if a market exists in the
shiftParamsListas afromMarketmore than one time and reverts if such a market is found. -
L-27 Low Unnecessary Library Usage Gas Optimization Resolved
Description
In the
JitOrderHandlercontract the following library usages are unnecessary:using GlvDeposit for GlvDeposit.Propsusing GlvShift for GlvShift.Propsusing GlvWithdrawal for GlvWithdrawal.Props
Recommendation
Remove these unnecessary library usages.
-
L-28 Low Simulation Misses Reentrancy Guard Close Unexpected Behavior Resolved
Description
In the
simulateExecuteJitOrderfunction theglobalNonReentrantmodifier comes before thewithSimulatedOraclePricesmodifier. As a result the_globalNonReentrantAfterblock is unreachable because thewithSimulatedOraclePricesending reverts before this is reached. The net impact is just that the simulation may show an inaccurate amount of gas used, since there would be some gas refund for resetting theREENTRANCY_GUARD_STATUStoNOT_ENTERED. Furthermore, this contradicts the ordering that has been used throughout the GMX codebase for other simulation functions.Recommendation
Consider updating the ordering of the two modifiers such that the
withSimulatedOraclePricesmodifier comes before theglobalNonReentrantmodifier.
Remediation Review 1
6 findings · September 14 to 15, 2025-
M-01 Medium GLV Shift Misses Feature Validation Validation Resolved
Description
The
executeGlvShiftfunction directly uses theShiftUtils.executeShiftfunction to execute the underlying shift action, however does not validate if theexecuteShiftFeatureDisabledKeyfeature is disabled for theShiftHandleraddress. Therefore if shifts were to be disabled they could still be executed through theGLVShiftaction.Recommendation
Consider implementing and using an
executeShiftFromControllerfunction which similar to other functions, validates the feature and performs the shift action. -
L-01 Low Remaining ReentrancyGuard Import Best Practices Resolved
Description
In the
JitOrderHandlercontract there is aReentrancyGuardimport that is unused.Recommendation
Consider removing this unused import.
-
L-02 Low GLV Oracle Inaccuracy Logical Error Partially resolved
Description
Throughout the different actions that can use the new GLV price oracle, none of them account for the GLV price oracle being used in the relevant
estimateOraclePriceCountfunctions. This means that actions submitted forglvDeposit,glvWithdrawal, and orders that would use JIT liquidity do not accurately account for the GLV price oracle being used. In actions like glvDeposit and glvWithdrawal the GLV price oracle may replace several price oracles and therefore the oracle price count is over estimated. For JIT orders the oracle price count would be under estimated since the GLV oracle price would be additional to the prices required by the normal order execution flow and oracle count estimation for the order.Recommendation
Be aware of this inconsistency in the estimated oracle price count in GLV actions and JIT actions.
-
L-03 Low onlySelfOrController Unnecessary Superfluous Code Acknowledged
Description
Technically, the
onlySelfOrControllermodifier can simply be replaced by theonlyControllermodifier, since it is always used by contracts which will hold the controller role.Recommendation
Consider removing the unnecessary
onlySelfOrControllermodifier in favor of the already usedonlyControllermodifier. -
L-04 Low Integration Risk During Upgrade Warning Acknowledged
Description
During the upgrade period for the new GMX Shift and JIT updates, there may be multiple handlers active at the same time which means deposits, shifts, glv deposits, glv withdrawals, and orders could all have executions and callbacks coming from multiple handler contracts. For some integrations this may pose an issue.
Recommendation
Be sure to alert integrations about this upgrade in advanced.
-
L-05 Low Increased Hedge Risk For GLV Warning Acknowledged
Description
With the introduction of JIT, there is an increased risk that GLV is exposed to unhedged market exposure, since often the act of opening a large trade that requires new liquidity to back it will create an imbalance in the market that may or may not be filled.
Recommendation
Be aware of this increased risk of unhedged exposure that may be brought to GLV, and consider adding validations in the keeper to ensure that the resulting state of the market will not be skewed above an acceptable threshold either net long or short.
Remediation Review 3
3 findings · November 5, 2025-
M-01 Medium Manual Cancellation Sends Refund To Unexpected Address Unexpected Behavior Acknowledged
Description
In the manual cancellation flow the account of the relevant action is set as the keeper, therefore sending what is estimates as the keeper fee to the account as a refund. This includes the estimated costs for oracle prices, base execution fee amounts, and gas multipliers. However, the action may have had an associated callback contract or dedicated cancellation receiver/refund receiver which now does not receive these funds which should have been technically refunded to that separate account.
Recommendation
Be aware of this behavior and document it for integrators, otherwise consider putting whichever address that would be provided as a
refundReceiveras the keeper for the manual cancellation flow. -
L-01 Low Swap Oracle Requirement Understated Warning Acknowledged
Description
In the
estimateOraclePriceCountfunctions theswapCountonly introduces 1 oracle count per swap, however a swap will usually necessitate 2 new prices: 1 for the index token of the market and 1 for the opposite token which has not already been recorded in the base market or a previous swap path market.Recommendation
Be aware of this underallocation and consider if it should be adjusted to 2 prices per swap count.
-
I-01 Informational Single Market GLVs Do Not Collect Enough Fees Warning Acknowledged
Description
In the
estimateGlvDepositOraclePriceCountandestimateGlvWithdrawalOraclePriceCountfunctions the base oracle count whenglvTokenPriceUsedis true is 4. However for GLVs with only one market, there can be only 3 required oracle prices for that GLV action. Therefore specifyingglvTokenPriceUsedas false upon creation allows the user to potentially provide less executionFee than should be necessary if theglvTokenPriceUsedoftrueis used during execution.Recommendation
This may be safely acknowledged as GLVs with one market should not exist, however be aware of this quirk.
No findings match.
More from GMX
All 52 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.
