GMX engaged Guardian to review the security of its liquidity vault for GM tokens, allowing seamless shifting of liquidity across markets according to their utilization. From the 30th of July to the 12th of August, a team of 6 auditors reviewed the source code in scope.
- Published
- Review window
- July 30 to August 12, 2024
- Language
- Solidity
- Chains
- Arbitrum, Avalanche
- Sector
- Perpetuals
- 1 Critical
- 2 High
- 12 Medium
- 31 Low
- 0 Informational
Scope
Overview
GMX engaged Guardian to review the security of its liquidity vault for GM tokens, allowing seamless shifting of liquidity across markets according to their utilization. From the 30th of July to the 12th of August, a team of 6 auditors reviewed the source code in scope.
Findings 46
-
C-01 Critical GLV Arbitraged With pnlToPoolFactor Logical Error Resolved
Description
Proof of concept: PoC
Upon deposits to GLV the GM tokens in the vault are valued using the
MAX_PNL_FACTOR_FOR_DEPOSITS, and on withdrawals the GM tokens in the vault are valued using theMAX_PNL_FACTOR_FOR_WITHDRAWALS.The
MAX_PNL_FACTOR_FOR_DEPOSITSwill value GM tokens at a lower value than theMAX_PNL_FACTOR_FOR_WITHDRAWALSas the trader’s PnL is capped using a higher percentage, resulting in more trader profits being allowed using the depositpnlToPoolFactormeasurement.In the existing GMX V2 system this is not exploitable since withdrawals are not allowed if the market is over the
MAX_PNL_FACTOR_FOR_WITHDRAWALSratio. However in GLV there is no constraint on withdrawing a different GM market using your GLV tokens and benefiting from this arbitrage in total GLV value between deposits and withdrawals.For example:
- GLV has GM A and GM B
- The GM A
MAX_PNL_FACTOR_FOR_WITHDRAWALSis 40% andMAX_PNL_FACTOR_FOR_DEPOSITSis 60% - GM A has +$500,000 of pending trader PnL and the
pnlToPoolFactoris currently 50% - User A deposits GM B to the GLV, the GLV is valued using
MAX_PNL_FACTOR_FOR_DEPOSITSwhich allows
for the full trader pnl of $500,000
- User A then withdraws GM B from the GLV, the GLV is valued using
MAX_PNL_FACTOR_FOR_WITHDRAWALSwhich allows for only $400,000 of trader pnl, which values the GM A tokens higher.As a result User A receives more GM B out of the withdrawal than they had initially deposited because of the arbitrage between the deposit factor and withdrawal factor.
Recommendation
Consider valuing all GM tokens in the GLV using the
MAX_PNL_FACTOR_FOR_DEPOSITSeven on withdrawals. This way the value of the GM tokens is minimized when thepnlToPoolFactoris currently above the withdrawal factor. This would inaccurately account for the value out of the GM tokens a user would receive if they withdrew those tokens, however those tokens would not be withdrawable if they are above this factor anyways.Alternatively, consider disallowing all GLV withdrawals when any one of the market tokens is above it’s
MAX_PNL_FACTOR_FOR_WITHDRAWALS.Resolution
GMX Team: Resolved.
-
H-01 High Execution fee locked in router on cancellation Logical Error Resolved
Description
When a user cancels a GLV deposit/withdraw the keeper is set to
msg.senderinstead of account. Themsg.senderin this case would be theGlvRoutercontract. Resulting in the keeper portion of the execution fee being sent to the router instead of the user who initiated the cancellations.Recommendation
Pass in
account()instead ofmsg.senderwhen a user initiates a cancellation.Resolution
GMX Team: Resolved.
-
H-02 High GLV Callbacks Do Not Validate Remaining Gas Validation Resolved
Description
In the
afterGlvDepositExecution,afterGlvDepositCancellation,afterGlvWithdrawalExecution, andafterGlvWithdrawalCancellationfunctions thevalidateGasLeftForCallbackis not performed as it is in all other callbacks.As a result these callbacks may forward less than the expected callback gas limit and cause integrating systems to silently revert, leading to loss of funds in these systems. Specifically, in validation done in the respective
_handleErrorfunctions is against theMIN_HANDLE_EXECUTION_ERROR_GASwhich is configured as 1,200,000 and does not take into account cancellation's callback gas usage.Therefore this check can pass while gas provided by keeper is insufficient for the cancellation + callback gas which will lead to forwarding less than enough gas to the callback contract. Without this validation keepers will not correctly account for the gas necessary for these callbacks upon estimation and may on accident or on purpose cause loss of funds in integrators.
Recommendation
Implement the
validateGasLeftForCallbackvalidation in theafterGlvDepositExecution,afterGlvDepositCancellation,afterGlvWithdrawalExecution, andafterGlvWithdrawalCancellationfunctions so keepers may adequately estimate gas and cannot accidentally or purposefully cause loss of funds in these systems.Resolution
GMX Team: Resolved.
-
M-01 Medium Excess execution fee will be required Logical Error Resolved
Description
To deposit GM, the long token address must be 0.
if (params.initialLongToken != address(0))However, when estimating execution fees, the only way to not pay for a deposit is to have the long address equal to the market address.
if (glvDeposit.market() == glvDeposit.initialLongToken())Due to these conflicting statements, users depositing GM tokens will be required to pay an execution fee as if they were depositing the underlying asset.
Recommendation
Consider only charging the deposit fee when an underlying long or short has been deposited.
Resolution
GMX Team: Resolved.
-
M-02 Medium GLV Withdrawal Callback Gas Cost Ignored Logical Error Resolved
Description
For the GLV Withdrawal flow, the callback call is made at the end of
GlvWithdrawalUtils::executeGlvWithdrawal().Since the callback is made after
payExecutionFee()is called, the gas used in the callback will not come out of the user’s execution fee and will instead be charged to GMX.The max callback gas limit is set to 3,000,000 for Arbitrum and 2,000,000 for Avalanche, which over time will cause GMX incur substantial losses.
Recommendation
Move the callback call to before
payExecutionFee()is called.Resolution
GMX Team: Resolved.
-
M-03 Medium executionFee Should Be Updated Logical Error Resolved
Description
The
createGlvWithdrawalfunction does not update theparams.executionFeeafter recording the transferredwntAmountwithrecordTransferIn. As a consequence, users may not receive a full refund when the transferredwntamount exceeds the provided input value.Furthermore, the
validateExecutionFeefunction utilizes the providedparams.executionFeeinstead of the actual transferredwntamount, potentially causing the function to inaccurately revert even when the transferred amount is enough to cover execution costs.Recommendation
Update the
params.executionFeeafter recording the transfer.Resolution
GMX Team: Resolved.
-
M-04 Medium GLV Used To Exit Illiquid Markets Logical Error Acknowledged
Description
GLV allows users to essentially swap between GM markets with no fees by triggering a GLV deposit of GM market A and triggering a GLV withdrawal of GM market B.
An actor holding GM market A can observe that GM market A is locked due to
pnlToPoolRatiovalidations or reserves validations and use GLV to exit their GM A tokens into GM B tokens.This will come at the expense of all other GLV holders, who are now left with the illiquid GM A tokens.
Recommendation
Consider applying an additional fee to GLV withdrawals to disincentivize this or disallowing GLV deposits when the underlying GM market is illiquid.
Resolution
GMX Team: Acknowledged.
-
M-05 Medium GLV Actions Cannot Be Simulated Logical Error Resolved
Description
The
simulateExecuteGlvDepositandsimulateExecuteGlvWithdrawalfunctions have theonlyControllermodifier, however they are not exposed through theGlvRouterand therefore cannot be called.Recommendation
Expose the
simulateExecuteGlvDepositandsimulateExecuteGlvWithdrawalfunctions through theGlvRouter.Resolution
GMX Team: Resolved.
-
M-06 Medium GLV Used For Atomic Withdrawals Gaming Resolved
Description
In the
_processMarketWithdrawalfunction the collected glv amount is converted into a market token amount and withdrawn from the GLV address.However because the
marketTokenAmountis determined during the time of execution, a user may abuse the GLV withdrawal to perform an atomic withdrawal while avoiding the atomic withdrawal fee.Consider the following scenario:
- address(1) holds 1 GLV
- User A holds 1000 GLV
- The
totalSupplyof GLV is 1001 - User A creates a withdrawal for their 1000 GLV
- User A watches for the keeper’s execution transaction and frontruns it to donate 50,000 GM tokens
to the GLV in the same block
- The keeper’s execution transaction now credits User A with 1000/1001 * 50,000 of these GM
tokens for their GLV withdrawal
- User A is able to withdraw their GM tokens in a single block while only paying the
TwoStep
swapPricingfee and 10 basis points for their loss to the initial deposit addressRecommendation
This manipulation is not attractive when there are multiple even holders of a GLV supply, as the donated tokens will be split up amongst each holder. However, be wary of this gaming when launching new GLVs and consider using an internal storage for the tracking of the GM token balance of each GLV rather than relying on the
balanceOf.Resolution
GMX Team: Resolved.
-
M-07 Medium GLV Trapped Funds Upon Disabled DoS Resolved
Description
In the
createGlvWithdrawalfunction thevalidateGlvMarketvalidation uses true as theshouldBeEnabledconfiguration. Therefore users may not withdraw from the portion of a Glv that is a disabled Glv market.As a result when a market becomes disabled via the
isGlvMarketDisabledKeykey, users are not able to fully withdraw their deposited value from the Glv unless the keeper explicitly shifts all funds out of the disabled Glv market.Recommendation
Consider allowing users to withdraw a disabled glv market, but not deposit it.
Resolution
GMX Team: Resolved.
-
M-08 Medium GLV Read-only Reentrancy Risk Reentrancy Resolved
Description
In the
executeGlvWithdrawalfunction the withdrawal is performed with_processMarketWithdrawalbefore burning theglvWithdrawal.glvTokenAmount()from theglvVault. As a result any withdrawals which use shouldUnwrapNative token as true and receive weth will have the opportunity to exploit any systems which attempt to read the value of a GLV.This is because during this transfer of native tokens to the receiver address the GLV supply has not yet been reduced, but the amount of GM tokens in GLV has been reduced. The receiver then gains control over the transaction execution if it is a contract with a receive function.
For example, any protocols attempting to use GLV as collateral can errantly count this collateral as being insufficient and allow incorrect liquidations since the GLV price is incorrectly reduced during this external transfer of native tokens.
Recommendation
Burn the GLV tokens before executing the withdrawal with the
ExecuteWithdrawalUtils.executeWithdrawalfunction, but after themarketTokenAmountis computed with the_getMarketTokenAmountfunction.Resolution
GMX Team: Resolved.
-
M-09 Medium User GLV Deposits Errantly Maximized Logical Error Resolved
Description
In the
_getMintAmountfunction thepoolValueused to compute the value of the depositor’s GM tokens is maximized to avoid applying a double spread from the GLV value computation. However the computation of the GLV value is not guaranteed to have a maximizing effect in excess of the maximizing effect experienced by the depositor.For example:
- GLV is supports Markets A, B, and C
- GLV totalSupply is 100
- GLV holds 10 GM A, 10 GM B, and 0 GM C
- The minimum price of GM A is $1 and the maximum is $1.01
- The minimum price of GM B is $1 and the maximum is $1.01
- The minimum price of GM C is $1 and the maximum is $1.05 due to
indexTokenspread, trader pnl,
impact pool amount, etc…
- User A deposits 10 GM C tokens
- The GLV value if minimized is $20, User A’s deposits is $10 if minimized, User A would receive 50
GLV tokens if both were minimized
- The GLV value since it is maximized is $20.20, User A’s deposit is valued at $10.50 since it is
maximized
- As a result the maximization of both of these values positively affects User A such that they now
receive $10.50/$20.20 * 100 ~= 51.98 GLV
Due to the maximization of both values in this example, User A receives roughly 2 more GLV tokens than if the values were to not be maximized. Additionally, the same effect would apply if a user is simply depositing more $ value than the GLV currently holds, since the maximization would have a greater absolute value impact on the numerator than the denominator due to being applied for a larger size.
Recommendation
Consider valuing the GLV value at the maximum, while valuing the user’s deposits at the minimum to ensure that under no circumstances the protocol is rounding in the user’s favor. Additionally, consider applying the same spread to withdrawals to protect the protocols from these cases. This behavior will negatively impact users, but protect against profitable arbitrages.
Resolution
GMX Team: Resolved.
-
M-10 Medium Virtual Inventory Ignored On Withdrawal Logical Error Acknowledged
Description
When withdrawing from a GM market in GMX V2, no price impact is applied since the withdrawn tokens are taken at the ratio of pool balances of the backing liquidity for the market. However if the market is within a virtual inventory, this ratio which was withdrawn directly from the market may have a negative impact on the virtual inventory (VI), but is ignored since no price impact is applied on withdrawal.
Consider the following scenario:
- Markets A, B, and C are all backed by WETH/USDC and make up a VI
- Market A holds $100 of WETH and $200 of USDC
- Market B holds $200 of WETH and $100 of USDC
- Market C holds $100 of WETH and $100 of USDC
- The total VI balances are $400 WETH & $400 USDC, the VI is balanced
- User A withdraws 50% of the GM supply from Market A
- Market A now holds $50 of WETH and $100 of USDC, ignoring fees
- The total VI balances are $350 WETH and $300 USDC, the VI is now unbalanced
Since there is no way to be positively impacted for balancing the virtual inventory, no value can be directly extracted this way. Though this leads to cases where the virtual inventory is negatively imbalanced, affecting other users who experience subsequent negative impact within the same virtual inventory on swaps and deposits, but while failing to negatively impact the user who created the imbalance.
Additionally, as the virtual inventory was implemented to disincentivize profitable manipulations in price impact across similar markets, ignoring the VI on withdrawals may allow a case where this is not sufficiently protected against, though none have been identified at this time.
Recommendation
Consider consulting the virtual inventory and applying the relevant negative impact if a withdrawal would cause an unwanted imbalance.
Resolution
GMX Team: Acknowledged.
-
M-11 Medium GLV Oracle Count Wrong For Homogenous Markets Unexpected Behavior Acknowledged
Description
The
estimateGlvDepositOraclePriceCountandestimateGlvWithdrawalOraclePriceCountfunctions add 2 constant oracle prices for every GLV. Additionally, theestimateGlvShiftOraclePriceCountis a static 4 oracle price feeds. However in the future it is planned that homogenous market GLV's may be supported.Therefore the oracle price count is consistently over-estimated for GLV's with homogenous markets. As a result users and shifts will unintentionally have to pay a higher margin to the keepers on every interaction with a homogenous market GLV.
Recommendation
Consider adjusting the constant oracle price count for homogenous market GLVs to avoid charging excessive execution fees.
Resolution
GMX Team: Acknowledged.
-
M-12 Medium Insufficient Gas Forwarded For Refund Validation Resolved
Description
In the
refundExecutionFeefunction theREFUND_EXECUTION_FEE_GAS_LIMITis forwarded to the callbackContract when calling the refundExecutionFee value. However the transaction execution is not validated to have the necessaryREFUND_EXECUTION_FEE_GAS_LIMITwith the gas left for the transaction.As the
REFUND_EXECUTION_FEE_GAS_LIMITis currently configured to 200,000 gas units and the refundExecutionFee call takes place at the end of action executions it is possible that keepers would not have provided enough gas to forward the entire limit. Instead 63/64 of the remaining gas left will be forwarded to therefundExecutionFeefunction call in these cases.This may cause unexpected reverts for integrating systems and result in mis-accounted funds. However this instance of not validating the gas left with the
validateGasLeftForCallbackis not as severe as the lack of this validation for the GLV callbacks, as the refund fee is configured with a lower gas limit and handles less funds.Recommendation
Validate the gas left for the
refundExecutionFeecallback with thevalidateGasLeftForCallbackfunction.Resolution
GMX Team: Resolved.
-
L-01 Low Execution Fees Increased By Empty Markets Logical Error Resolved
Description
The
payExecutionFeefunction has a parameteroraclePriceCountthat is calculated as follows:2 +marketCount + swapCount. If there are no swaps involved,oraclePriceCountis equal to2 +marketCount.In case there is GLV with one GM market, and a new GM market is added,
oraclePriceCountmoves from 3 to 4. This value is a multiplier used forbaseGasLimit, when calculatingexecutionFeeForKeeper.As there is only a way to add markets, but not remove them,
marketCountwill monotonically increase. Disabling a market in glv is possible, but glv value will still iterate through all markets added.Therefore, users will continuously pay more execution fees as
marketCountincreases, even if there are empty markets due to new ones added, or liquidity shifting, leaving markets empty.Recommendation
Consider only counting non-zero value markets for the
oraclePriceCount. Additionally, add aonlyConfigKeeperfunction to remove empty markets from the list.Resolution
GMX Team: Resolved.
-
L-02 Low Feature validation occurs before try-catch Logical Error Resolved
Description
The GLV functions validate whether a feature is enabled before entering the try-catch block. This means that if the feature is not enabled, the execution will revert instead of cancelling the order.
This method of handling orders differs from the rest of GMX. Not cancelling an order when a feature is disabled means that users will not have access to their funds for a period of time. To manually cancel, users must wait for a predetermined amount of time.
Recommendation
To address this issue, it is advisable to move the feature validation check inside the try-catch block, similar to how it is implemented in other functions for both GLV deposits and withdrawals.
Resolution
GMX Team: Resolved.
-
L-03 Low DoS Via Type Casting DoS Acknowledged
Description
_getGlvMarketValue()callsgetPoolValueInfo()to determine the pool value for each market. The issue is that_getGlvMarketValue()will cast pool value to auint256. However, pool value can potentially be a negative value if the PnL and impact pool are large enough._getGlvMarketValue()is called in a loop inside ofgetGlvValue(), which, in turn, is called in the GLV deposit, withdrawal, and shift flow.This means that if a single market in the GLV market has a negative pool value, any GLV feature will be disabled until that market has a positive pool value again. In practice, this is unlikely to occur because of ADL,
pnlToPoolRatio, and reserve validationsRecommendation
Do not cast pool value to a
uint256. This will allow for a more accurate calculation of the true value of the GLV market.Resolution
GMX Team: Acknowledged.
-
L-04 Low Unused marketCount Variable Superfluous Code Resolved
Description
The
GlvUtilscontract contains two distinctgetGlvValuefunctions. One of these functions utilizes an oracle to retrieve all market addresses based on market count, and is used in multiple processes such as deposit and withdrawal.The other
getGlvValuefunction is specifically accessed through a reader contract, where both market addresses and token prices are provided by the function caller. This function does not rely onmarketCountand instead uses user-provided market addresses. Additionally, the input values are not validated within this function.Recommendation
If the function is intended to be accessed via
GlvReaderwithout input validation, it is suggested to remove the linescache.marketListKey = Keys.glvSupportedMarketListKey(glv)andcache.marketCount = dataStore.getAddressCount(cache.marketListKey)from the function as they are unnecessary.Alternatively, if input validation is required, consider validating the user-provided
marketAddressesarray againstmarketCount.Resolution
GMX Team: Resolved.
-
L-05 Low GLV Deposit Config Added Twice Superfluous Code Resolved
Description
In
Config.sol,CREATE_GLV_DEPOSIT_FEATURE_DISABLED,CANCEL_GLV_WITHDRAWAL_FEATURE_DISABLED, andEXECUTE_GLV_DEPOSIT_FEATURE_DISABLEDare all assigned twice.This is an unnecessary operation that wastes gas, and can be removed.
Recommendation
Remove the extra assignment from the
Config.sol.Resolution
GMX Team: Resolved.
-
L-06 Low GM Deposits With False isMarketTokenDeposit Validation Acknowledged
Description
The
createGlvDepositfunction includes anisMarketTokenDepositparameter to specify if the deposited tokens are GM tokens. However, it is still possible to create a market token deposit request whenisMarketTokenDepositis false. This is due to the absence of any validation ensuring that the market token address is not the same as the initial long token or short token addresses.The impact is minimal as the execution of a deposit will revert as the long and short tokens do not match, however invalid deposits should not be created.
Recommendation
Consider implementing checks to verify that
params.market != params.initialLongTokenandparams.market != params.initialShortTokenwhenisMarketTokenDepositisfalse.Resolution
GMX Team: Acknowledged.
-
L-07 Low No Keeper Incentives To Create Shifts Logical Error Acknowledged
Description
Keepers are in charge of creating and executing GLV shifts, to rebalance liquidity amongst GM pools. The issue relies on the shift creation, as there is no incentive for keeper to trigger this transaction.
First, any keeper that calls
createGlvShiftwill need to pay execution fees inwnttokens. Then, bothexecuteGlvShiftandcancelGlvShift, set uses keeper asmsg.senderwhich is then used as therefundReceiverfor the execution fee paid.As an example:
KeeperAcreates a shift, pays execution feesKeeperBexecutes shift (or fails and cancels shift)KeeperBreceives both the execution fees and the refund.KeeperAloses funds.
Recommendation
Consider storing the keeper address that triggers the shift creations, and set it as the refunds receiver for shift executions and cancellations.
Resolution
GMX Team: Acknowledged.
-
L-08 Low Unused Struct Params Superfluous Code Acknowledged
Description
There are some struct declarations that contain parameters that are not in use:
CreateGlvShiftCache;fromMarket, toMarket; andtoMarketTokenPrice
Recommendation
Remove unused params in structs mentioned above.
Resolution
GMX Team: Acknowledged.
-
L-09 Low Shifting Is Allowed To Same Market Validation Acknowledged
Description
Keepers have the ability to initiate a shift from one market to another. However, there is a missing validation to ensure that both markets are not the same.
Despite the missing validation, the shift execution will still be successful, resulting in withdrawals and deposits being made to the same market.
Recommendation
Implement a check to revert the transaction if both the
fromMarketandtoMarketare the same.Resolution
GMX Team: Acknowledged.
-
L-10 Low Cancelling Deposits Unnecessarily Transfers Tokens Optimization Resolved
Description
Users can opt to cancel their deposits, and receive their tokens back. The
cancelGlvDepositunnecessarily checks a non-zero value for all 3 token amounts (marketToken, longToken,shortToken), and transfers out these amounts to the account.This is unnecessary as deposits will either have short and long tokens or market tokens, and never both.
Recommendation
Consider creating an
if-elsecondition, so that long and short token amounts are not checked ifmarketTokenAmountis non-zero.Resolution
GMX Team: Resolved.
-
L-11 Low Malicious Code Can Be Set In Token Name/Symbol XSS Resolved
Description
The
GlvFactoryallowsmarketKeeperto deploy a customglvTokenpassing a name and symbol for the new token. Although currently this is a trusted origin, if the plan is to allow permissionless glv deployments, it is possible for an attacker to craft a name or symbol such that it includes markup that can contain Javascript code.If loaded into a frontend without XSS protection, this can cause potential harm to users of the platform as was the case in the
EtherDeltaexploit. For more details on theEtherDeltaexploit please refer to this article.Recommendation
Sanitize or limit the length of the token
nameandsymbolpassed into thecreateGlvfunction from theGlvFactorycontract.Resolution
GMX Team: Resolved.
-
L-12 Low Attacker can extract value from shifts Warning Acknowledged
Description
An attacker can monitor the GLV keeper and when it identifies a shift being created, it can execute a swap on the
tomarketto alter the pool's long/short ratio. This manipulation will result in a greater price impact than anticipated for the keepers shift, allowing the attacker to swap back into the pool the amount just swapped, thereby rebalancing the pool and capturing some value from the keeper's shift.Although this extraction is limited by the price impact validation during GLV shifts, if a malicious actor can discern the logic used by a keeper to determine shifts they could repeatedly frontrun and extract value from the GLV this way.
Recommendation
Be aware of this potential manipulation and consider implementing safety rails in the keeper logic. Additionally, carefully consider this scenario when assigning the fee rates and slippage tolerance for shifts.
Resolution
GMX Team: Acknowledged.
-
L-13 Low Missing function for feature key Logical Error Resolved
Description
The
CANCEL_GLV_SHIFT_FEATURE_DISABLEDdetermines whether the glv shift cancellation feature is enabled or disabled. However, this key is currently unused as there is no cancel function for GLV shifts.Recommendation
Implement a cancellation function for GLV or remove this key.
Resolution
GMX Team: Resolved.
-
L-14 Low Unused errors Superfluous Code Resolved
Description
The following errors have been created but are currently unused:
error EmptyGlvShift();error GlvInvalidReceiver(address glv, address receiver);error GlvInvalidCallbackContract(address glvHandler, address callbackContract);anderror InvalidMarketTokenPrice(address market, int256 price)
Recommendation
Consider implementing or removing the unused errors.
Resolution
GMX Team: Resolved.
-
L-15 Low GLV Deposit Creation DoS DoS Acknowledged
Description
In the
estimateExecuteGlvDepositGasLimitfunction the glv deposit gas limit is dependent on whether the deposit uses both theinitialLongTokenAmountand theinitialShortTokenamount.Therefore a user or integrator may provide an
executionFeewhich is sufficient for a single token deposit while a malicious actor donates a single wei ofinitialShortTokenAmountto cause the deposit to have a higher estimated gas limit.As a result the
executionFeevalidation would fail if the user or integrator did not anticipate this and provide more than the necessaryexecutionFeeupon creation.Recommendation
Document this for users and integrators so they can be sure to set an appropriate
executionFeewhen DoS manipulations would have negative consequences.Resolution
GMX Team: Acknowledged.
-
L-16 Low GLV GM Balance Validation Minimizes Value Validation Resolved
Description
When validating that the GLV GM token balance is below the configured
maxMarketTokenBalanceUsdamount, the pool value is minimized rather than maximized. As a result GM tokens which would exceed themaxMarketTokenBalanceUsdwith maximize as true, but are less than this value when maximize is false are allowed.Recommendation
Consider maximizing the pool value for to market during the
maxMarketTokenBalanceUsdvalidation. And either use maximized for the from market price impact validation, or keep the pool value minimized for the to market price impact validation.Resolution
GMX Team: Resolved.
-
L-17 Low Users Charged For Oracles On Cancel Logical Error Acknowledged
Description
When a user cancels a deposit or withdrawal, there is no use of oracles. However, the user is still charged for oracle usage because
oraclePriceCountis calculated and passed topayExecutionFee. This will lead to users having to pay unnecessary execution fees.These execution fees will be sent back to the account as they are the keeper in this scenario, however this unexpended gas amount should technically be delivered to the
refundReceiverorcallbackContractas an execution fee refund.Recommendation
Pass
0for theoraclePriceCountwhen callingpayExecutionFeeon a cancel.Resolution
GMX Team: Acknowledged.
-
L-18 Low Funds Are Not Synced In The GLV Vault Warning Resolved
Description
During GLV withdrawals and GLV shifts GM tokens are transferred out of the GLV contract and into the
glvVault, however the balances for the glvVault are not synced to account for these additional tokens being added.If a malicious actor would be able to trigger any action relying on a
recordTransferIn, it would account for these GM tokens being credited to the attacker. Currently there is no pathway for such an attack to occur, as the tokens are subsequently burned from theglvVaultand the balance is synced.However out of an abundance of caution it may be worthwhile to sync the token balance in the
glvVaultafter transferring GM tokens from GLV.Recommendation
Consider adding a
syncTokenBalancefunction call to the GLV withdrawal and shift executions directly after transferring GM tokens out of glv.Resolution
GMX Team: Resolved.
-
L-19 Low GLV Deposits Incorrectly Value Fees Logical Error Resolved
Description
During deposits the GLV value is computed based upon the value of the market tokens before the deposit occurs. However the deposit itself will increase the price of a GM token as fees are taken from the depositor and allocated to the pool. When determining the value of a user’s received market tokens after the deposit, the deposit fees are included in that valuation.
This creates a small increase in the GM token valuation for the user’s deposit relative to the GLV holdings. Additionally deposits which swap through the target market and charge more fees increase this effect. The user would be paying more in fees than the value of their GM tokens would increase relative to the GLV valuation, however this may have some non-trivial effect over longer periods with larger amounts of capital.
Additionally if a user were to currently hold a significant portion of the backing GM market liquidity then they would not be losing the fees which were allocated to the pool, and could potentially extract value from GLV this way, though this is an unlikely scenario.
Recommendation
Consider refactoring the GLV value computation to be performed after the deposit, but excluding the balance of GM tokens received from the deposit itself. An immediate fix may not be necessary, but it is worth being aware of and monitoring.
Resolution
GMX Team: Resolved.
-
L-20 Low Incorrect Param Used in Event Logical Error Resolved
Description
The
emitGlvDepositExecutedfunction emit an event that expects the last param to bereceivedMarketTokensbutcache.mintAmountis passed instead. Therefore, glv minted amount is emitted instead of GM tokens deposited by the user.Recommendation
Update the last param passed in the function to
cache.receivedMarketTokensinstead ofcache.mintAmountResolution
GMX Team: Resolved.
-
L-21 Low Superfluous receivedUsd Value Optimization Resolved
Description
When calculating the minted value of a GLV deposit the
cache.receivedUsdis assigned however not used afterwards.Recommendation
Consider removing this value or implementing it's intended use-case.
Resolution
GMX Team: Resolved.
-
L-22 Low Superfluous Imports Optimization Resolved
Description
The
ShiftUtils.solfile is imported twice in theShiftHandler.solcontract.Recommendation
Remove the duplicated import.
Resolution
GMX Team: Resolved.
-
L-23 Low Lacking GlobalNonReentrant Modifier Reentrancy Resolved
Description
In the
GlvHandlercontract theaddMarketToGlvfunction does not have theglobalNonReentrantand therefore may be potentially unexpectedly re-entered into by a compromisedonlyConfigKeeper.Recommendation
Consider adding the
globalNonReentrantmodifier to theaddMarketToGlvfunction.Resolution
GMX Team: Resolved.
-
L-25 Low Errant GLV Keys Logical Error Resolved
Description
In the Keys library there are
GLV_LONG_TOKENandGLV_SHORT_TOKENkeys which differ from theLONG_TOKEN and SHORT_TOKENkeys defined in theGlvStoreUtilslibrary. TheseGLV_LONG_TOKENandGLV_SHORT_TOKENkeys are not used currently, however they should be removed to prevent future errant use-cases. Additionally, theGLV_KEYin theGlvStoreUtilslibrary is unused.Recommendation
Remove the unused
GLV_LONG_TOKENandGLV_SHORT_TOKENkeys from the Keys library and remove theGLV_KEYfrom theGlvStoreUtilslibrary.Resolution
GMX Team: Resolved.
-
L-26 Low GLV Salt Lookup Not Cleared Logical Error Acknowledged
Description
Upon adding a GLV with the
GlvStoreUtils.setfunction, the GLV is given a lookup where the GLV address can be obtained with the GLV salt.This uses the
getGlvSaltHashresult as the key in the dataStore, however this key is not cleared in theGlvStoreUtils.removefunction.Recommendation
Clear the
getGlvSaltHashlookup from thedataStorein theGlvStoreUtils.removefunction.Resolution
GMX Team: Acknowledged.
-
L-27 Low Misleading setMarketTokenAmount Function Typo Resolved
Description
The
GlvWithdrawalStoreUtils.setMarketTokenAmountfunction sets the GLV token amount on a GLV withdrawal. This naming does not agree with what the function assigns.Recommendation
Rename the function to
setGlvTokenAmount.Resolution
GMX Team: Resolved.
-
L-28 Low Lacking GM Market Count Cap Validation Resolved
Description
In the GLV system there is no cap on the amount of GM markets which the trusted
configKeepercan add to a GLV. In an extreme case this may cause very gassy deposit, withdrawal, and shift transactions which may require more gas than a single transaction can use.Recommendation
Consider implementing a cap on the amount of GM markets which can be added to a GLV.
Resolution
GMX Team: Resolved.
-
L-29 Low block.timestamp Usage Suggestion Resolved
Description
When setting the
glvShiftLastExecutedAtKeyvalue theblock.timestampis used. However this does not match the pattern of using theChain.currentTimestampfunction for the block timestamp.Recommendation
Consider using
Chain.currentTimestampto follow the accepted pattern and automatically support any changes to thecurrentTimestampfunction.Resolution
GMX Team: Resolved.
-
L-30 Low Users can imbalance glv to reduce glv yield Logical Error Acknowledged
Description
The GLV shift feature can be exploited by temporarily increasing the utilization in a market that typically has low utilization. Once the keeper executes the shift, the attacker can lower the utilization back to its normal levels. Users can create this increase by opening a leveraged position briefly, consuming most of the available size to raise the utilization enough for a shift to occur. After the shift is completed, the attacker can close the position.
As a result, GLV holders will experience lower yield as they become stuck in a market with low utilization until another shift is performed. The attacker can then repeat this process, pressuring GLV holders to keep their funds in a less profitable market.
Recommendation
It is suggested to implement a time-weighted average utilization (TWAU) to prevent artificial spikes in utilization from triggering a keeper shift.
Resolution
GMX Team: Acknowledged.
-
L-31 Low Withdrawal/Shift count reserved GM as available Logical Error Acknowledged
Description
When a keeper calls the
createGlvShiftfunction, it will perform the following check:uint256 fromMarketTokenBalance = ERC20(params.fromMarket).balanceOf(params.glv);The issue here is that a portion of the GLV's GM balance will be reserved by users who have created a withdrawal but have not been executed yet. Because of this, the shift will succeed even though it is moving funds that will soon be used for withdrawals.This may result in the user's withdrawal failing because there is not enough GM token available for withdrawal. This can lead to wrongly failed withdrawals for users. Alternatively, if the withdrawal executes before the shift, the shift will fail.
Recommendation
It is recommended to establish a minimum percentage of GM tokens that should not be included in the shift, this check can be done off chain as the shifts will be performed by GMX keepers.
Additionally, the keeper is recommended to take into account the pending withdrawals when determining the shift amount.
Resolution
GMX Team: Acknowledged.
-
L-32 Low Deposits With Zero Short Token Fail Logical Error Resolved
Description
In the
createGlvDepositfunction deposits with aninitialShortTokenare allowed to support markets where the long token is the same as the short token.However in this case the
initialShortTokenshould be the same token as the long token, not address(0).Deposits with the
initialShortTokenas the zero address will revert upon depositing and expecting that theoutputTokenof the swap is the expected short token of the market.https://github.com/gmx-io/gmx-synthetics/blob/1938e365dc009342aa288aa6b42fc1fd3cd9e45d/c
Recommendation
Require that neither the long token or short token are the zero address when creating a deposit.
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.
