GMX engaged Guardian to review the security of its decentralized synthetics perpetuals exchange. From the 8th of December to the 8th of January, a team of 4 auditors reviewed the source code in scope.
- Published
- Review window
- December 8, 2022 to January 8, 2023
- Language
- Solidity
- Chains
- Arbitrum, Avalanche
- Sector
- Perpetuals
- 18 Critical
- 7 High
- 15 Medium
- 45 Low
- 0 Informational
Scope
Test suiteGuardianAudits/GMX_2
Overview
GMX engaged Guardian to review the security of its decentralized synthetics perpetuals exchange. From the 8th of December to the 8th of January, a team of 4 auditors reviewed the source code in scope.
Findings 85
-
ORDH-1 Critical Risk Free Trades From Empty Positions Protocol Manipulation Resolved
Description
Proof of concept: PoC
The custom handling for the
Keys.EMPTY_POSITION_ERROR_KEYallows users to createMarketDecreaseorders that continue to revert and be retried until the user creates a position. TheMarketDecreaseorder would then be executed at the prices of the block in which the decrease order was created.This way a user can submit a long
MarketDecreasefor an empty position, and wait until the price of the index token decreases before submitting aMarketIncreaseorder and realizing risk-free profits from the exchange.Recommendation
Do not revert and retry on
Keys.EMPTY_POSITION_ERROR_KEY.Resolution
GMX Team: The recommendation was implemented.
-
ORDU-1 Critical Cancelled Order In beforeOrderExecution Callback Protocol Manipulation Resolved
Description
Proof of concept: PoC
In the
beforeOrderExecutioncallback it is possible to cancel the order prior to processing which returns funds to the user and removes the order from theorderStore. However, the order will still execute and create a position with the initial collateral delta and USD size.This results in a deficit in the
orderStorebalances which causes accounting issues across the entire exchange.Recommendation
Do not allow order cancellation to occur during the execution of that order, possibly by moving the
cancelOrderfunction to theorderHandlerand allowingNonReentrantmodifiers to resolve this issue. Furthermore, ensure consistency between storage and cached parameters.Resolution
GMX Team: A
globalNonReentrantmodifier was added to prevent this. -
GLOBAL-1 Critical shouldUnwrapNativeToken DoS Denial-of-Service Resolved
Description
Proof of concept: PoC
The
shouldUnwrapNativeTokenflag can be exploited by users to create positions that cannot be decreased by liquidations or ADL orders.For both liquidation orders and ADL orders the
shouldUnwrapNativeTokenflag is set totrue, however the position can be created by a contract that is unable to receive the native token, causing the order execution to revert.Recommendation
Refactor the
shouldUnwrapNativeTokenlogic so that it cannot be used to determine whether or not transactions are able to succeed, and optionally set theshouldUnwrapNativeTokenflag tofalsefor liquidation and ADL orders.Resolution
GMX Team: Now if an address cannot accept the native asset, it is re-wrapped and the wrapped native asset is transferred to the address.
-
ORDU-2 Critical Uncancellable/Unfreezable Order Protocol Manipulation Resolved
Description
Proof of concept: PoC
In the process of cancelling or freezing an order, the
executionFeeis paid to the keeper and a native token refund is issued to the user. However, the address of the user can point to a contract that is unable to accept the native token, causing the cancellation or freezing to revert.This way, the user may cause their order execution to revert and be retried until they wish their order to be executed – enabling risk free trades with knowledge of how the market moved after their order was created.
Recommendation
Consider refunding the user in WNT rather than the native token directly to avoid transaction manipulation.
Resolution
GMX Team: Now if an address cannot accept the native asset, it is re-wrapped and the wrapped native asset is transferred to the address.
-
DPU-1 Critical Open Interest Errantly Increased Logical Error Resolved
Description
Proof of concept: PoC
The call to
MarketUtils.applyDeltaToOpenInterestInTokensapplies the positivesizeDeltaInTokensto the open interest in tokens while the position is being decreased by that amount of tokens rather than increased.This incorrectly represents the accounting of the decrease order and perturbs all open interest, pnl and reserves accounting for that market.
Recommendation
Negate the
sizeDeltaInTokens, as these tokens are being removed from the open interest.Resolution
GMX Team: The recommendation was implemented.
-
DPU-2 Critical Incorrect Impact Calculation Logical Error Resolved
Description
Proof of concept: PoC
The calculation of the
priceImpactAmountis computed with theparams.order.sizeDeltaUsd, however the price impact that the user actually experiences is based on theadjustedSizeDeltaUsdwhich can be significantly smaller than theparams.order.sizeDeltaUsd.This way the impact that is applied to the accounting for the pool and the impact that actually takes place can be significantly different and cause the market to start double counting funds in both the impact pool and the
poolAmount.Recommendation
Compute the
priceImpactAmountusing theadjustedSizeDeltaUsd.Resolution
GMX Team: The recommendation was implemented.
-
DPU-3 Critical Incorrect Fee Decrement Logical Error Resolved
Description
Proof of concept: PoC
In the condition where the
values.outputAmountis intended to be subtracted from the fee amount, thevalues.outputAmountis set to 0 before being subtracted from the fee amount, causing the fee to be taken from both theoutputAmountand the user’s collateral.In the case of large fees, this can lead to significant loss of assets for users interacting with the exchange, and can potentially cause unexpected accounting within a market.
Recommendation
Set the
values.outputAmountto 0 after subtracting it from thefees.totalNetCostAmount.Resolution
GMX Team: The recommendation was implemented.
-
DPU-4 Critical Wrong Token Amount Applied Logical Error Resolved
Description
Proof of concept: PoC
The
pnlAmountForPoolis either thecollateralTokenif the user realized losses or thepnlTokenif the user realized gains. However theapplyDeltaToPoolAmounttreats this amount ascollateralTokenno matter what, therefore perturbing the accounting of the pool by applying apnlTokenamount to acollateralTokenamount.Recommendation
Be sure to
applyDeltaToPoolAmountfor the correct token that is being removed from the pool.Resolution
GMX Team: The recommendation was implemented.
-
OBU-1 Critical StopLossDecrease Orders Cannot Execute Logical Error Resolved
Description
Proof of concept: PoC
shouldValidateAscendingPriceerrantly applies toStopLossDecreaseorders as well, stipulating that for long positionStopLossDecreaseorders, the price must be increasing over the range to be executed and the inverse for short positionStopLossDecreaseorders.These price range requirements are unexpected for
StopLossDecreaseorders and leads to them not acting as stop losses for positions, which would cause tremendous loss for traders.Recommendation
Use a separate condition to validate the price range for
StopLossDecreaseorders.Resolution
GMX Team:
StopLossDecreaseorders are now validated with a separate condition. -
SWPU-1 Critical Price Impact Not Transferred Logical Error Resolved
Description
Proof of concept: PoC
When swapping, the current
MarketTokentransfers thepoolAmountOutto the next pool in the swap route, but this value does not include any positive price impact that the user might have accrued. This way the positive impact amount is left, unaccounted for, in the currentMarketTokencontract and the followingMarketTokenin the route “thinks” it has received the positive impact amount but it hasn’t.This causes a deficit in the accounting for the
MarketTokenwhich was told that it received the positive impact amount, but never did.Recommendation
Be sure to send the positive impact amount to the next
MarketTokenin the swap route.Resolution
GMX Team: The recommendation was implemented.
-
DOU-1 Critical Swap From Arbitrary Market Protocol Manipulation Resolved
Description
Proof of concept: PoC
When swapping after decreasing an order, the first market in the
swapPathis not required to be the market the position was in. This way a user can specify a market that accepts theresult.outputTokenand theresult.outputAmountwill be swapped from that market, but the user’s profits are left in the original market.This breaks the accounting system in the market that was provided in place of the position’s market as the first market in the
swapPath.Recommendation
Transfer the funds to the first market in the
swapPath, or require that the first market be the position’s market.Resolution
GMX Team: The recommendation was implemented.
-
GLOBAL-2 Critical Funding Fees Not Properly Incremented Logical Error Resolved
Description
Proof of concept: PoC
When both increasing and decreasing a position, funding fees are incremented for a user when the
fees.funding.longTokenFundingFeeAmountorfees.funding.shortTokenFundingFeeAmountare greater than 0.However the funding fees are intended to be paid for by the user when they are positive and received by the user when they are negative. Currently, when funding fees are positive, the user both pays for and receives funding fees. But when they are negative, the funding fees are entirely ignored.
Recommendation
Refactor the
incrementClaimableFundingAmountlogic when both increasing and decreasing a position so that funding fees are paid out when they are negative.Resolution
GMX Team: The claimable funding fee logic was refactored.
-
MKTU-1 Critical Unliquidateable Short With Long Collateral Protocol Manipulation Resolved
Description
Proof of concept: PoC
It is possible for user to create unliquidateable short positions with long collateral because when computing the
openInterestWithPnLtheCalc.sumoperation underflows and reverts when the PnL is negative with magnitude greater than the open interest of the pool.When a pool reaches this state, it is impossible to increase or decrease any positions within the pool, as the
openInterestWithPnLis calculated during both actions.Recommendation
Do not allow shorts to be able to lose more than the open interest in the pool or rectify these outstanding losses somehow.
Resolution
GMX Team: The
openInterestWithPnLfunction is no longer used. -
IOU-1 Critical Token Transferred To Wrong Market Logical Error Resolved
Description
Proof of concept: PoC
When creating an increase order, the collateral token gets sent to the market parameter on the order. This becomes problematic when a
swapPathis provided, and theinitialCollateralTokenis not a token found in the order’s market.Consider the following example:
- Order with ETHUSD market, WBTC as initial collateral, and [BTCUSD, ETHUSD]
swapPath. - WBTC gets transferred to the ETHUSD market rather than the BTCUSD market
- BTCUSD market accounting is now off as the pool thinks there is more WBTC backing positions than there really is.
Recommendation
Transfer the collateral token to the first market in the
swapPathif aswapPathis provided.Resolution
GMX Team: The recommendation was implemented.
- Order with ETHUSD market, WBTC as initial collateral, and [BTCUSD, ETHUSD]
-
MKTU-2 Critical Incorrect Funding Per Size Calculation Logical Error Resolved
Description
shortCollateralFundingPerSizeForLongsis being both added to and subtracted in the same branch which not only misrepresents the true value ofshortCollateralFundingPerSizeForLongs, but also leaves thelongCollateralFundingPerSizeForShortsuninitialized. This negatively impacts the purpose of the funding fee which is to incentivize long and short balance in the pool.Recommendation
Change line 655 to
cache.fps.longCollateralFundingPerSizeForShorts -=cache.fps.fundingAmountPerSizeForLongCollateralForShorts.toInt256();Change line 661 to
cache.fps.longCollateralFundingPerSizeForShorts +=cache.fps.fundingAmountPerSizeForLongCollateralForShorts.toInt256();Resolution
GMX Team: The recommendation was implemented.
-
DPU-5 Critical Swapping Collateral to PnL Token Inflates Output Logical Error Resolved
Description
Proof of concept: PoC
After the swap from collateral token to PnL token,
values.outputAmountis incremented instead of replaced with theswapOutputAmount. This leads to the addition of two different tokens which can dramatically increase thevalues.outputAmountdepending on the PnL token’s precision.For example, if
values.outputAmountrepresents $10,000 USDC, and then it swaps to WETH ($2000/ETH),swapOutputAmountwill be 5 WETH which will get added to the $10,000 USDC. WETH, with 18 decimals of precision, will drastically inflate the user’s output. While this is unlikely to succeed due to the large difference in precision for USDC and WETH, tokens with closer precisions are highly susceptible to this bug.Recommendation
Set
values.outputAmountto 0 andvalues.pnlAmountForUsertoswapOutputAmountResolution
GMX Team: The swap from collateral token to PnL token logic was refactored.
-
MKTU-3 Critical Incorrect Funding Fees Accounting Logical Error Resolved
Description
Proof of concept: PoC1
If there is no open interest on a trader’s side and their collateral token, the funding amount per size stamped on their position is 0. This makes them immune from providing funding fees to the other side when necessary as the funding fee amount would simply be 0.
Furthermore, a separate trader whose claimable funding fees are incremented wouldn’t be receiving their tokens from the immune trader (as the funding fee to pay is always 0), but would transfer tokens directly from the market causing a disruption to the market’s accounting.
Recommendation
Consider refactoring funding fee logic so that if a newly created position is stamped with a 0 funding amount per size on their collateral token, the trader is able to pay a funding fee once there is open interest on the other side.
Resolution
GMX Team: The funding fee logic was refactored.
-
DPU-6 Critical No Market Validation When Swapping Collateral to PnL Input Validation Resolved
Description
Because the
swapPathis simply the first element of theswapPathMarket, a malicious user could supply an arbitrary market which contains the collateral token. Such a market does not need to contain the PnL token, even though the purpose of the swap is to get the PnL token from collateral token. With the current logic, this can be used to inflate thevalues.outputAmountas per DPU-5.If the logic was amended so that the
cache.outputTokenwas thepnlToken,the amount ofpnlTokento withdraw from the market can be inflated.Recommendation
Add validation such that the market in the
swapPathMarketsactually contains both the collateral token and PnL token, and that the token received from the swap is indeed the PnL token.Resolution
GMX Team: The swap collateral token to PnL token logic was refactored.
-
DOU-2 High Decrease Order Gas Attack Gas Vamp Attack Resolved
Description
Proof of concept: PoC
When the
sizeDeltaUsdof aLimitDecreaseorder exceeds the positionsizeInUsd, the order lives on in theorderStoreand theexecutionFeeis set to 0. This can lead to a potential gas vamp attack on the keeper, where a user continually submits increase orders to create small positions to be decreased by aLimitDecreaseorder that remains in theorderStore.Each execution of the
LimitDecreaseorder can be arbitrarily expensive due to callbacks and the keeper would receive no remuneration for a potentially large amount of gas expenditure. An orchestrated attack like this could drain the keeper of its native tokens and shut down all execution on the exchange.Recommendation
Require an
executionFeefor additional executions ofLimitDecreaseorders.Resolution
GMX Team: Partially filled orders are now removed from the order store.
-
DOU-3 High Incorrect LimitDecrease Size Assignment Logical Error Resolved
Description
Proof of concept: PoC
When the
sizeDeltaUsdof aLimitDecreaseorder exceeds the positionsizeInUsd, the order lives on in theorderStorebut thesizeDeltaUsdof the order is set to theresult.adjustedSizeDeltaUsd, which is the amount that the order was just able to decrease the position by, not the amount that the order has left to decrease.Recommendation
Assign the
sizeDeltaUsdof the order to be theorder.sizeDeltaUsd() - result.adjustedSizeDeltaUsd.Resolution
GMX Team: Partially filled orders are now removed from the order store.
-
ORDH-2 High Frozen Order Execution Loop Gas Vamp Attack Resolved
Description
Proof of concept: PoC
When the execute order feature is blocked, all non-market orders will become frozen rather than being cancelled.
This could potentially spur on an infinite loop of execution for the frozen order keeper without any remuneration for gas costs.
Additionally, loops of continually failing frozen orders like this could occur from a number of errors caused during order execution.
Recommendation
Consider cancelling all orders or simply reverting when the execute order feature is blocked. Additionally take special care around the frozen order keeper logic to avoid costly infinite loops of frozen orders–and consider requiring an
executionFeefor additional executions of orders after they are frozen.Resolution
GMX Team: Orders are now cancelled when a feature is blocked.
-
GLOBAL-3 High Lack of Open Interest Caps Lack of Controls Resolved
Description
There is a lack of general open interest caps per
MarketTokenaside from the reserves validation.This enables users to open positions and increase the open interest right up to the reserve limit so that anyone attempting to withdraw from the
MarketToken(or possibly swap with thisMarketToken) will be unable to passvalidateReserves. This way depositors funds can be held hostage – only being freed if others deposit (at which point someone may increase the open interest yet again and hold these new funds hostage as well).Additionally, in the event of arbitrage attacks the exchange has no effective mechanism to limit the open interest per market to limit the attack size.
Recommendation
Implement open interest caps that limit the total open interest a
MarketTokencan have for either direction.Resolution
GMX Team: Open Interest caps were implemented.
-
OBU-2 High Cannot Only Increase Position Collateral Logical Error Resolved
Description
Proof of concept: PoC
The price calculation in
getExecutionPricedivides by thesizeDeltaUsd, therefore reverting when thesizeDeltaUsdis 0.This results in users not being able to increase their collateral without increasing the size of their position. If a user were rushing to increase their collateral to avoid liquidation, the transaction would revert and the user would likely not be able to figure out why before their position becomes liquidated.
Recommendation
Refactor the
getExecutionPricelogic to allow for asizeDeltaUsdof 0.Resolution
GMX Team: The
getExecutionPricelogic was refactored. -
ORDU-3 High Cannot Decrease Position Collateral Logical Error Resolved
Description
Proof of concept: PoC
When creating an order, there is no condition to handle the
initialCollateralDeltaAmountfor decrease orders, therefore theinitialCollateralDeltaAmountis always 0 for decrease orders and it is impossible for users to decrease their collateral amount without fully closing their position.Recommendation
Implement a condition for the
initialCollateralDeltaAmountthat allows users to decrease their collateral as expected.Resolution
GMX Team: The recommendation was implemented.
-
MKTU-3 High Broken Swap Logical Error Resolved
Description
In the
getMarketsfunction, the loop continues in the case of theNO_SWAP,SWAP_PNL_TOKEN_TO_COLLATERAL_TOKEN, andSWAP_COLLATERAL_TOKEN_TO_PNL_TOKENaddresses. This means that the index of these markets is left as an uninitializedMarket.Propsstruct.Currently, when decreasing a position and swapping the collateral token to the PnL token, the first market in the
swapPathMarketsis used. However according to the logic ingetMarketsthis market will be uninitialized. Therefore a decrease order using theshouldSwapCollateralTokenToPnlTokenor theshouldSwapPnlTokenToCollateralTokenfeature will always revert.In the worst case, a user may make a
StopLossDecreaseorder with either of these address variables in theswapPathexpecting a swap to take place when their stop loss is hit. However the stop loss would revert upon execution, potentially leading to unintended loss of user funds, because their position is never closed.Recommendation
Limit these address variables to only the first index of the
swapPath, and use the second item in theswapPathMarketsto perform the corresponding swap.Resolution
GMX Team: The decrease order swap logic was refactored.
-
GLOBAL-4 Medium Centralization Risk Centralization / Privilege Acknowledged
Description
The exchange keeper and other permissioned addresses have the power to do nearly anything:
- Execute any order or liquidation at any arbitrary price
- Defer execution of any arbitrary deposit, withdrawal, order, or liquidation
- Select the order of execution for all incoming deposits, withdrawals, and orders
- Power to shut off any feature – including withdrawals and closing positions
- Change out the protocol-wide variables used in the
dataStore
There are many assumptions made on the form of the price input from the keeper. Adding validation for the expected format and range of acceptable inputs would reduce the risk of high-cost mistakes, and assist in limiting the scope of the internal exploits available to the keeper.
The keeper must diligently execute orders, for example stop loss orders must be executed with the correct range of prices while staying within the
MAX_ORACLE_PRICE_AGElimit.Recommendation
Treat the keeper’s private key(s) with the utmost level of security and introduce as many safeguard checks as possible to limit the scope of the keepers potential attack vectors.
Resolution
GMX Team: Acknowledged.
-
ERTR-1 Medium Any Address May Rescue Trapped ETH Permissioning Resolved
Description
The
sendWntfunction can be called by anyone to collect any ether that may find itself in theExchangeRoutercontract.Meanwhile, the
FundReceivercontract that theExchangeRouterextends stipulates that the controller is the only address that can recover these funds using therecoverNativeTokenfunction.Recommendation
If it is not desired that any address be able to rescue trapped ether, consider refactoring the
sendWntlogic to be able to safely use the actual amount of ether the user provided. Otherwise, no changes are necessary.Resolution
GMX Team: The recommendation was implemented.
-
GLOBAL-5 Medium Execution Fee DoS Denial-of-Service Resolved
Description
There is a potential DoS with
createDeposit,createWithdrawal, andcreateOrderas a malicious address may send a miniscule amount of WNT to the deposit/withdrawal/order store such that another user’s WNT deposit no longer matches theexecutionFeein their deposit. Thus, the deposit execution reverts and is unable to be created.Recommendation
Consider removing the strict equality for the
executionFeeor handling the accounting in thedepositStoresuch that another address cannot increase the deposit amount for a user.Resolution
GMX Team: The strict equality has been replaced with a greater than or equal to check.
-
GLOBAL-6 Medium Missing Validation Validation Resolved
Description
When creating a deposit, there is no validation that the
longTokenor theshortTokenare valid for the supplied market. If WBTC is accidentally used as the long token in an ETH/USDC market, the user would lose their WBTC in thedepositStore.Furthermore, there is no validation that either the long token amount or short token amount is non-zero. This check should be added to prevent the keeper from executing trivial deposits.
Additionally, when creating an order, there is no validation that the initial collateral token is valid for the provided market, which can lead to invalid orders stored in the
orderStore.Recommendation
Implement the above mentioned validations.
Resolution
GMX Team: The recommendation was implemented.
-
ERTR-2 Medium Weak Referrals Incentives Acknowledged
Description
A trader is allowed to specify a different referral code each time an order is created. Only one affiliate is permitted per trader account, so when an order is created with a different affiliate, the affiliate associated with the trader’s account is updated.
This can lead to affiliates missing out on rewards if a trader decides to use another affiliate’s referral code even if they were the one to bring the trader onto the platform.
Recommendation
Consider whether this is desired behavior, if not refactor the referral logic to continue to reward referrers who first bring a trader to the platform.
Resolution
GMX Team: This is the desired behavior.
-
GLOBAL-7 Medium Cannot Cancel Deposits/Withdrawals Locked Funds Resolved
Description
A user does not have the capability to cancel their deposit or withdrawal like with an order. As a result, if the keeper for some reason does not execute their deposit/withdrawal, their funds are locked.
Recommendation
Allow users to cancel their deposits and withdrawals and recover their funds.
Resolution
GMX Team:
cancelDepositandcancelWithdrawalfunctions were added. -
ORDU-4 Medium Unnecessary Execution Fee Logical Error Resolved
Description
A user can cancel an order on their own without the need of a keeper. Even if a user cancels an order on their own accord, the
executionFeeis paid to the keeper which doubles the amount of gas a user expends.Recommendation
Do not pay the
executionFeeto the keeper if the user is simply cancelling the order.Resolution
GMX Team: The
executionFeewill still go to the user in this instance as their address is passed as thekeeper. -
GLOBAL-8 Medium Unintelligible Revert Reasons Incorrect Parsing Resolved
Description
In every
catch (bytes memory _reason)case, the reason is parsed withstring(abi.encode(_reason))However, parsing the bytes reason like this results in unintelligible revert strings.The first 4 bytes of the
_reasonbytes represent the selector for the error that caused the revert, and the rest represent the data that accompanies the error. There ought to be a way (perhaps off-chain) to map from these 4 bytes to the error type and decode the data.Additionally, panic reverts will be caught in this case and should not be parsed.
Recommendation
Consider an alternative approach to parsing the bytes revert reasons, and know that it is possible by chance for the 4 byte selector for two separate errors to be the same if they are not defined in the same contract.
Resolution
GMX Team: Reasons are no longer parsed for bytes reverts.
-
OBU-3 Medium Unexpected Frozen Order Logical Error Resolved
Description
When a limit order is executed with an invalid increasing/decreasing price range, the order reverts with a bespoke revert string–which results in the order getting frozen. But this is unexpected as all other order price-related errors simply revert and will be retried. This might lead to a poor execution of limit order types or a complete lack of execution of limit orders.
Recommendation
Consider reverting in this case with a custom error that is handled similarly to the
UNACCEPTABLE_PRICE_ERROR_KEY.Resolution
GMX Team: The recommendation was implemented.
-
GLOBAL-9 Medium Possible Loss of Funds Validation Resolved
Description
When creating a decrease order or a swap order, there is no validation that the
receiveraddress is not the zero address. In this case, when the order is executed and a swap takes place, the user’s funds will be left in theMarketToken, otherwise if no swap is executed during the decrease, the funds would be sent to the zero address.Recommendation
Do not allow users to possibly lose funds this way and validate that the
receiveris not the zero address for decrease and swap orders.Resolution
GMX Team: The recommendation was implemented.
-
FR-1 Medium Lack of Access Control For Events Access Control Resolved
Description
Anyone can call
notifyFeeReceivedas there is a lack of access control, which leads to a depreciation of the authenticity of the event.Recommendation
Implement access control concerning who may call
notifyFeeReceived.Resolution
GMX Team: The recommendation was implemented.
-
MKTU-4 Medium Lack of Funding Fees When OI Is Stacked Logical Error Acknowledged
Description
When computing funding fees in the
getNextFundingAmountPerSizefunction, thecache.oi.longOpenInterest == 0 || cache.oi.shortOpenInterest == 0condition stipulates that when all of the open interest is stacked on one side no funding fees are charged to the users who have positions on the stacked side. This means there is a lack of disincentive for the users with stacked positions to switch sides.In such a case, the funding fees could be redirected to the
poolAmountfor the benefit of LPers.Exchanges like Binance have a minimum funding fee that is held at all times to properly disincentivize such imbalances.
Recommendation
Consider whether or not funding fees should be charged when there is no opposing side open interest. If funding fees should in fact be charged when only one side has all of the open interest, refactor the existing logic to charge funding fees and optionally send them to the pool or an arbitrary fee receiver.
Resolution
GMX Team: The current behavior is desired.
-
OCL-1 Medium Chainlink Feed Validation Pricefeed Validation Declined
Description
Extra validation checks should be added on the result from the Chainlink price feed to ensure non-stale data. The price from the data feed influences the execution of orders and liquidations so it is imperative the data is up to date and correct.
Recommendation
Add the following require statements to validate the price feed:
require(answeredInRound >= roundID, "Chainlink:: Stale price")require(timestamp > 0, "Chainlink:: Round not complete")
Resolution
GMX Team: We were informed by the Chainlink team that these checks are now outdated
-
MKTF-1 Medium Malicious Backing Token Protocol Manipulation Acknowledged
Description
In the future, GMX hopes to enable permissionless
MarketTokencreation. However, aMarketTokencan use an arbitrary malicious backing token that performs mischievous actions such as cancelling orders on the exchange during execution when the token is transferred.Additionally, it is possible for contracts of the existing tokens on the exchange to be upgraded with new logic that is able to maliciously exploit the exchange.
Recommendation
Be wary of malicious tokens and ensure there is no way to re-enter into the application during token transfers.
Resolution
GMX Team: Acknowledged.
-
OCL-2 Medium Median Price Validation Validation Resolved
Description
There should be validation that the
medianMaxPriceis greater than themedianMinPrice. Otherwise, many critical exchange values such aspoolValue,pnlToPoolFactor, and many others could be adversely affected.Recommendation
Implement the validation that
medianMaxPriceis greater than themedianMinPrice.Resolution
GMX Team: The recommendation was implemented.
-
ERTR-3 Low Bespoke String Prefer Constants Acknowledged
Description
When cancelling an order, a bespoke
“USER_INITIATED_CANCEL”string is used as the reason. This reason string might be better suited as a constant variable that can be reused when checking to see if the cancel was user generated.Recommendation
Consider making the
“USER_INITIATED_CANCEL”string a constant variable.Resolution
GMX Team: Acknowledged.
-
RST-1 Low Zero Address Checks Validation Acknowledged
Description
Currently, there is no zero address check for the
_newAccount. Users may accidentally burn their code ownership if the zero address is provided.Recommendation
Consider adding a zero address check for the
_newAccount.Resolution
GMX Team: Acknowledged.
-
RST-2 Low Function Naming Documentation Acknowledged
Description
The
codeOwners,referrerDiscountShares, andreferrerTiersfunctions should be made singular as they all return a single value rather than multiple.Additionally, the
tiersfunction may be better named astierValuesas this more clearly represents what the function returns.Recommendation
Consider renaming the
codeOwners,referrerDiscountShares,referrerTiers, andtiersfunctions.Resolution
GMX Team: Acknowledged.
-
ORDH-3 Low Typo Typo Resolved
Description
The comment “the account of the position to liquidation” should read “the account of the position to liquidate”.
Recommendation
Update the comment.
Resolution
GMX Team: The recommendation was implemented.
-
ORD-1 Low Typo Typo Resolved
Description
In the comment, “current” is misspelled as “curent”.
Recommendation
Update the comment.
Resolution
GMX Team: The recommendation was implemented.
-
ORD-2 Low Typo Typo Resolved
Description
In the comment, “the initial token sent it for the swap” should read “the initial token sent in for the swap”.
Recommendation
Update the comment.
Resolution
GMX Team: The recommendation was implemented.
-
DOU-4 Low Typo Typo Acknowledged
Description
In the comment, “Library” is misspelled as “Libary”.
Recommendation
Update the comment.
Resolution
GMX Team: Acknowledged.
-
MKTU-5 Low Typo Typo Resolved
Description
In the comment, “for withdrawals a market” should read as “for withdrawals for a market”
Recommendation
Update the comment.
Resolution
GMX Team: The recommendation was implemented.
-
MKTU-6 Low Missing Event Events Resolved
Description
There is no corresponding event emitted when the
fundingAmountPerSizeis set inupdateFundingAmountPerSize.Recommendation
Consider emitting an event when updating the
fundingAmountPerSize.Resolution
GMX Team: The recommendation was implemented.
-
MKTU-7 Low Missing Event Events Resolved
Description
There is no corresponding event emitted when the
cumulativeBorrowingFactoris set inupdateCumulativeBorrowingFactor.Recommendation
Consider emitting an event when updating the
cumulativeBorrowingFactor.Resolution
GMX Team: The recommendation was implemented.
-
ERTR-4 Low Potential Social Engineering Attack Social Engineering Acknowledged
Description
Since the
claimFundingFeesandclaimAffiliateRewardfunctions accept an arbitraryreceiveraddress, they are somewhat prone to frontend code injection or related social engineering attacks.Recommendation
Consider sending the rewards directly to the
msg.senderfor increased security. If thereceiveraddress is kept, be sure to validate that it is not the zero address so users are not able to accidentally burn their fees.Resolution
GMX Team: Acknowledged.
-
IPU-1 Low Superfluous Code Optimization Resolved
Description
At the beginning of the
increasePositionfunction theaccount,market,collateralToken, andisLongare set on the position from the params. But the position must already have these values as it is created with them and keyed off of them.Recommendation
Remove these lines, otherwise document why they are necessary.
Resolution
GMX Team: The recommendation was implemented.
-
GOV-1 Low Pull Over Push Ownership Ownership / Privilege Resolved
Description
The ownership transfer process should have a push and pull step rather than executing in a single transaction with
setGov.This way the protocol may avoid catastrophic errors such as setting the wrong governance address.
Recommendation
Implement a two step transfer process for the
govrole where the newgovaddress must explicitly accept its new role.Resolution
GMX Team: The recommendation was implemented.
-
SWOU-1 Low Empty Market Validation Validation Acknowledged
Description
Swap orders are required to be executed with a zero address market, this should be validated when first creating a swap order to save the keeper from executing orders that will possibly trivially fail.
Recommendation
Consider adding validation that the market address is 0 for all swap orders in the
createOrderfunction.Resolution
GMX Team: Acknowledged.
-
DEPU-1 Low Recomputed Values Optimization Acknowledged
Description
The
longTokenUsdandshortTokenUsdvalues are computed on line 190, but those same values are recomputed on line 203.Recommendation
Instead of recomputing the
longTokenUsdandshortTokenUsd, use those variables in place of the recomputation.Resolution
GMX Team: Acknowledged.
-
GLOBAL-10 Low Lack of Constants Prefer Constants Acknowledged
Description
Every time the
emitSwapFeesCollectedfunction is called, theactionbytes are recomputed. These commonactionbytes would be better suited as “action keys” in the Keys.sol contract.Recommendation
Create keys for the
actionbytes.Resolution
GMX Team: Acknowledged.
-
OBU-4 Low Unused Variable Optimization Acknowledged
Description
In the
ExecuteOrderParamsstruct, thepositionKeyis never set or used.Recommendation
Remove the extraneous
positionKey.Resolution
GMX Team: Acknowledged.
-
GSU-1 Low Superfluous Code Optimization Acknowledged
Description
The
estimateExecuteIncreaseOrderGasLimit,estimateExecuteDecreaseOrderGasLimit, andestimateExecuteSwapOrderGasLimitall have the same exact logic, the only difference being the order type gas limit key.A common function that abstracts over the order type gas limit key could de-duplicate these three estimation functions.
Recommendation
Implement a single estimation function that can be used with any order type gas limit key.
Resolution
GMX Team: Acknowledged.
-
KEY-1 Low Poor Naming Choice Documentation Resolved
Description
The
maxPnlFactorForWithdrawalsvariable is poorly named. It serves as a lower bound for thepnlToPoolFactorafter an ADL has been executed, but the naming as a max and relation to withdrawals are not immediately obvious without additional documentation.Recommendation
Rename the variable or provide more documentation expounding upon the naming.
Resolution
GMX Team:
maxPnlFactorForWithdrawalsis nowminPnlFactorAfterAdl. -
OCL-3 Low Outdated Variable Name Documentation Resolved
Description
The name of the
uint256emitted with theMaxPriceAgeExceedederror isblockNumber, however thisuint256now represents a timestamp rather than a block number.Recommendation
Update the variable name appropriately.
Resolution
GMX Team: The recommendation was implemented.
-
ERTR-5 Low Unnecessary Variable Optimization Acknowledged
Description
Throughout the
ExchangeRoutercontract, anaccountvariable is used to store themsg.sender, but this variable is often only used once. Instead of storing this variable on the stack, referencemsg.senderdirectly to save gas.Recommendation
Remove the sub-optimal declarations of the
accountaddress variable.Resolution
GMX Team: Acknowledged.
-
DPU-7 Low Duplicate Condition Checks Optimization Acknowledged
Description
The
isLongboolean is checked twice with two ternary operators, but it would save gas to only checkisLongonce with anifcondition.Recommendation
Replace the two ternaries with a single
ifcondition.Resolution
GMX Team: Acknowledged.
-
PPU-1 Low Unnecessary Variable Optimization Acknowledged
Description
The
openInterestParamsvariable is declared and only used once, rather than declaring it use the return value ofgetNextOpenInterestdirectly in the call to_getPriceImpactUsdRecommendation
Remove the declaration of the
openInterestParamsvariable.Resolution
GMX Team: Acknowledged.
-
IOU-2 Low Earlier Market Validation Optimization Acknowledged
Description
The market can be checked if it is empty before transferring the collateral token to save gas upon failure.
Recommendation
Move validation to the top of the function.
Resolution
GMX Team: Acknowledged.
-
OCL-4 Low Cached Variables Optimization Acknowledged
Description
The
Chain.currentBlockNumber()andChain.currentTimestamp()values can be stored outside the for-loop to save gas.Recommendation
Implement the suggestion above.
Resolution
GMX Team: Acknowledged.
-
MKTU-8 Low Unnecessary Variable Optimization Acknowledged
Description
Gas can be saved by not declaring the
pnlvariable before returning it.Recommendation
Directly return the value.
Resolution
GMX Team: Acknowledged.
-
SWU-2 Low Cached Variables Optimization Acknowledged
Description
The
nextIndex,receiver, and_paramsvariables can be declared outside the for-loop and re-assigned upon each iteration to save gas.Recommendation
Implement the suggestion above.
Resolution
GMX Team: Acknowledged.
-
PPU-2 Low Recomputed Value Optimization Resolved
Description
The
fees.totalNetCostincludes thefees.positionFeeAmountForPoolandfees.borrowingFeeAmountin it’s summation calculation, but these two numbers were already summed for thefees.feesForPoolvariable. Thefees.feesForPoolcan be used to sum thetotalNetCostand save some gas.Recommendation
Implement the suggestion above.
Resolution
GMX Team: The recommendation was implemented.
-
OCL-5 Low Cached Variables Optimization Resolved
Description
The
signerIndexandsignerIndexBitvariables can be declared outside the for-loop and re-assigned upon each iteration to save gas.Recommendation
Implement the suggestion above.
Resolution
GMX Team: The recommendation was implemented.
-
OCL-6 Low Cached Variables Optimization Resolved
Description
The
medianMinPriceandmedianMaxPricevariables can be declared outside the for-loop and re-assigned upon each iteration to save gas.Recommendation
Implement the suggestion above.
Resolution
GMX Team: The recommendation was implemented.
-
OCL-7 Low Custom Reverts Optimization Acknowledged
Description
The check
require(priceFeedAddress != address(0), "Oracle: invalid price feed")could use a custom revert to save gas.Recommendation
Implement the suggestion above.
Resolution
GMX Team: Acknowledged.
-
ORDH-4 Low Unnecessary Variable Optimization Acknowledged
Description
The
isMarketOrdervariable is only used once. Rather than allocating stack variable space, theifcondition can simply useOrderBaseUtils.isMarketOrder(order.orderType())to reduce gas expenditure.Recommendation
Implement the suggestion above.
Resolution
GMX Team: Acknowledged.
-
SWOU-2 Low Inconsistent Use of Variable Optimization Acknowledged
Description
The order is stored as a variable here, but it is often simply accessed through the params. Either undeclare it to save gas or use it to access every order attribute rather than repeatedly using the params.
Recommendation
Implement the suggestion above.
Resolution
GMX Team: Acknowledged.
-
WTDH-1 Low Unnecessary Variable Optimization Acknowledged
Description
The
ExecuteWithdrawalParamsto execute a withdrawal can be created directly in theexecuteWithdrawalfunction call to save gas.Recommendation
Implement the suggestion above.
Resolution
GMX Team: Acknowledged.
-
DEPH-1 Low Unnecessary Variable Optimization Acknowledged
Description
The
ExecuteDepositParamsto execute a deposit can be created directly in theexecuteDepositfunction call to save gas.Recommendation
Implement the suggestion above.
Resolution
GMX Team: Acknowledged.
-
DPU-8 Low Superfluous Code Optimization Acknowledged
Description
The values.remainingCollateralAmount -= params.order.initialCollateralDeltaAmount().toInt256() calculation can occur on the line above where
values.remainingCollateralAmountis declared.Recommendation
Implement the suggestion above.
Resolution
GMX Team: Acknowledged.
-
NCU-1 Low Unnecessary Variable Optimization Acknowledged
Description
Instead of declaring the
keyvariable, simply return the hash to save on gas expenditure.Recommendation
Implement the suggestion above.
Resolution
GMX Team: Acknowledged.
-
DPU-9 Low Unnecessary Variable Optimization Acknowledged
Description
Instead of declaring the
reasonvariable, pass the result directly to the event.Recommendation
Implement the suggestion above.
Resolution
GMX Team: Acknowledged.
-
WH-2 Low Unnecessary Variable Optimization Acknowledged
Description
Instead of declaring the
reasonvariable, pass the result directly tocancelWithdrawal.Recommendation
Implement the suggestion above.
Resolution
GMX Team: Acknowledged.
-
OCL-8 Low Cached Variables Optimization Acknowledged
Description
The
token,priceFeed,_price,price,precision,stablePrice, andpricePropsvariables can be declared outside the for-loop and re-assigned upon each iteration to save gas.Recommendation
Implement the suggestion above.
Resolution
GMX Team: Acknowledged.
-
LIQU-1 Low Unnecessary Variables Optimization Acknowledged
Description
The
address,numbers,flags, andordervariables can be created directly when calling theorderStore.setfunction to save gas.Recommendation
Considering the trade-off in readability, Implement the suggestion above.
Resolution
GMX Team: Acknowledged.
-
ORDH-5 Low Unnecessary Centralization Optimization Acknowledged
Description
There is no in-code requirement that the keeper must
updateAdlStateto turn ADL mode off. This requires users to trust that the keeper will turn ADL off when necessary, but this could be avoided by adding a time range to ADL mode or validating themaxPnlFactorupon ADL execution.Recommendation
Consider adding a time range to ADL mode or validating the
maxPnlFactorupon ADL execution.Resolution
GMX Team: Acknowledged.
-
GLOBAL-11 Low Initialization of Default Values Optimization Acknowledged
Description
Throughout the codebase the index for many for-loops in initialized to 0, the default value for
uint. Avoid the unnecessary initialization and allow the default values to be implicitly assigned to theseuintvariables:- MarketUtils.sol::1272
- Oracle.sol::242
- Oracle.sol::287
- Oracle.sol::424
- Oracle.sol::454
- Oracle.sol::472
- Oracle.sol::523
- OracleUtils.sol::121
- Reader.sol::59
- ExchangeRouter.sol::273
- ExchangeRouter.sol::302
- SwapUtils.sol::92
- Timelock.sol::44
- Timelock.sol::57
- Array.sol::37
- Array.sol::54
- PayableMulticall.sol::20
Recommendation
Implement the suggestion above.
Resolution
GMX Team: Acknowledged.
-
GLOBAL-12 Low Cached Array Length Optimization Acknowledged
Description
Throughout the codebase there are many for-loops that compute the length of an array upon each iteration. Caching the length of these arrays will decrease gas costs throughout the application:
- MarketUtils.sol::1272
- Oracle.sol::242
- Oracle.sol::424
- Oracle.sol::454
- Oracle.sol::472
- Oracle.sol::523
- Reader.sol::59
- ExchangeRouter.sol::273
- ExchangeRouter.sol::302
- SwapUtils.sol::92
- Timelock.sol::44
- Timelock.sol::57
- PayableMulticall.sol::20
Recommendation
Implement the suggestion above.
Resolution
GMX Team: Acknowledged.
-
GLOBAL-13 Low Initialization of Default Values Optimization Acknowledged
Description
Throughout the codebase
uintvariables are checked to be> 0however!= 0is a more efficient comparison to check ifuintvalues are positive. Switching to!= 0will decrease gas costs throughout the application:- DepositUtils.sol::215
- DepositUtils.sol::233
- DepositUtils.sol::295
- DepositUtils.sol::304
- Oracle.sol::551
- DecreaseOrderUtils.sol::74
- OrderUtils.sol::182
- DecreasePositionUtils.sol::302
- DecreasePositionUtils.sol::345
- DecreasePositionUtils.sol::386
- DecreasePositionUtils.sol::552
- IncreasePositionUtils.sol::243
- IncreasePositionUtils.sol::292
- TokenUtils.sol::186
- WithdrawalUtils.sol::233
- WithdrawalUtils.sol::255
Recommendation
Implement the suggestion above.
Resolution
GMX Team: Acknowledged.
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.
