GMX engaged Guardian to review the security of updates to it’s synthetic assets exchange. From the 3rd of June to the 6th of June, a team of 7 auditors reviewed the source code in scope.
- Published
- Review window
- June 3 to 6, 2024
- Language
- Solidity
- Chains
- Arbitrum, Avalanche
- Sector
- Perpetuals
- 2 Critical
- 1 High
- 7 Medium
- 8 Low
- 0 Informational
Scope
Overview
GMX engaged Guardian to review the security of updates to it’s synthetic assets exchange. From the 3rd of June to the 6th of June, a team of 7 auditors reviewed the source code in scope.
Findings 18
-
C-01 Critical Liquidations Prevented With updateOrder Protocol Manipulation Resolved
Description
createOrderchecks every new decrease order for if it will pass over the maxautoCancelgas limit usingvalidateTotalCallbackGasLimitForAutoCancelOrders.However the same check is missed inside
updateOrder, enabling the user to:- Create 5 decrease orders with their max allowed callback gas, without putting them inside the
autoCancellist.- Call
updateOrderfor all of these order withautoCancelvariable set to true.
With this method users can bypass 5 million maximum callback gas limit for Auto Cancel orders and reach up to 10 million. Which will result in reverting liquidations because gas required to liquidate will bypass block gas limit in avalanche.
Recommendation
Add the
validateTotalCallbackGasLimitForAutoCancelOrdersvalidation insideupdateOrder.Resolution
GMX Team: Resolved.
-
C-02 Critical LimitSwaps Cannot Execute After Request Expiration Logical Error Resolved
Description
When handling swap orders, the validation for the
requestExpirationPeriodis meant to only be applied toMarketSwaps. Since it is applied to both swap types, it will revert for nearly allLimitSwaps.This will occur because the majority of
LimitSwapswill not be eligible to be executed until a later time has passed than theREQUEST_EXPIRATION_TIME.Recommendation
Only perform this verification for
MarketSwaps.Resolution
GMX Team: Resolved.
-
H-01 High Keeper's Not Remunerated For Cancellation Callback Logical Error Resolved
Description
The callback gas amount is included inside an order’s
executionFee, however thepayExecutionFeefunction will refund the not used part ofexecutionFeeto the user.Since this unused part includes callback gas, the keeper will not be remunerated for the gas spent during the cancellation callback.
Recommendation
Change the places for order cancellation callback call and execution fee payment.
Resolution
GMX Team: Resolved.
-
M-01 Medium No way for user to add cancellation receiver Logical Error Resolved
Description
When an order gets cancelled there is a check to see if the order has a
cancellationReceiver. If it does the funds will be sent there, if not then theaccountwill get the funds. This works well, however there is no way for a user to add acancellationReceiverwhen creating an order. Preventing the use of this feature.Recommendation
Set the
cancellationReceiverwhen creating an order and validate that the address used is valid.Resolution
GMX Team: Resolved.
-
M-02 Medium Sequencer Outage Risks Logical Error Acknowledged
Description
The sequencer uptime check is performed only in: Atomic Withdrawal, Normal Withdrawal and Liquidations.
If sequencer is down, while it won't be possible to execute these functions, rest of the protocol will continue functioning if they don't have a priceFeed to check for reference price.
Recommendation
Localize the sequencer checks to exactly where the Chainlink Aggregator Price is used.
Resolution
GMX Team: Acknowledged.
-
M-03 Medium Callback Gas Validation Ignores 63/64 Rule Logical Error Resolved
Description
validateGasLeftForCallback()verifies that the gas left in the transaction is enough to call the callback contract. However,validateGasLeftForCallback()checksgasLeft()and forgets to account that 1/64th of the gas is reserved when making an external call. Although this case is less likely to occur, it has the same impact as H-06.Recommendation
Verify that the
gasLeft()subtracted by the gas withheld from making an external call is greater than the callback gas limit.Resolution
GMX Team: Resolved.
-
M-04 Medium AutoCancel Validation May DoS Order Creation Logical Error Acknowledged
Description
MAX_TOTAL_CALLBACK_GAS_LIMIT_FOR_AUTO_CANCEL_ORDERScan change according to gas requirements of the system/chain. If this value decreases however, the position holders that already have maximum amount of callback gas used for their autoCancel orders can not call decrease order because the call will revert withMaxTotalCallbackGasLimitForAutoCancelOrdersExceeded.Recommendation
Before reducing this variable inform users about this problem and let them prepare their positions to handle with this case. Additionally, this validation does not need to take place for
MarketDecreaseorders.Resolution
GMX Team: Acknowledged.
-
M-05 Medium Orders Which Close Positions May Be Censored Logical Error Resolved
Description
If a
decreaseOrderwill close the position altogether, theautoCancelListwill be cleared out during that order's execution. But gas provided by the keeper won't be checked if it is sufficient to handle the gas required for cancellations of these orders that are inautoCancelList. If keepers don't provide enough gas for all auto-cancellation logic, since the gas provided is not validated to cover these auto-cancellations withvalidateExecutionGas, a position closing order created by the user will be cancelled instead of reverting. This can lead to the censoring of closing orders for users and can lead to unfair liquidations and loss of funds.Recommendation
There are different possible solutions that comes with some caveats. 1- Query the position to see if order size is the entire position. If so, increase the result of
estimateExecuteOrderGasLimitwhen the decrease order has auto cancel orders. 2- Before startingclearAutoCancelOrders()check if there is enough gas, if not revert such that order is not cancelled and the error is caught in the_handleOrderErrorfunction as a keeper mistake. Note that both of these first two solutions has a griefing vector whereby someone can frontrun the execution transaction and update an order to be an auto-cancel one such that the required gas for both will change. Which can lead to revert for keeper's execution error. 3- Separating the logic of auto cancellation from order execution. Emitting an event after position is completely closed and letting keepers to callclearAutoCancelOrdersin a seperate transaction can solve the problem in a safer way, which will also address high gas usage concerns. The caveat for this is the execution logic change itself.Resolution
GMX Team: Resolved.
-
M-06 Medium Liquidation Gas Usage May Exceed Block Gas Limit Protocol Manipulation Acknowledged
Description
Proof of concept: PoC
Based on the estimated gas usage configurations, the gas required to execute some decrease and liquidation orders may exceed the Avalanche block gas limit of 15,000,000 gas:
- Liquidation's gas usage itself: 4,000,000 (Decrease order gas limit)
- afterOrderExecution callback gas: 2,000,000
- Main payExecutionFee: 500,000
- 5 autoCancel order cancellation: 5 x 600.000 = 3,000,000
- 5 autoCancel order cancellation callback: 5,000,000
- 5 autoCancel payExecutionFee: 5 x 500,000 = 2,500,000
- In total = 17,100,000 which is 2,100,000 more than avalanche block gas limit.
However in practice, it is unlikely that a liquidation will consume 15,000,000 or more gas units, refer to the attached PoC where we show that the rough maximum gas usage for a liquidation is around 14,000,000 gas.
If liquidation execution can consume more than 15,000,000 gas units this would result in unliquidatable positions on the Avalanche network, which will introduce bad debt into the system.
Recommendation
Carefully consider this limit when making future code updates and modifying the
refundExecutionFeeGasLimitas well as other gas configurations.Resolution
GMX Team: Acknowledged.
-
M-07 Medium Old Estimated Execution Base Gas Fee Used Logical Error Resolved
Description
The
EXECUTION_GAS_FEE_BASE_AMOUNTkey has been replaced with anEXECUTION_GAS_FEE_BASE_AMOUNT_V2_1key to allow an increased base fee to be charged for additional gas expenditures in V2.1.However the corresponding estimated fee which is required upon order creation is still based upon the
ESTIMATED_GAS_FEE_BASE_AMOUNTwhich corresponds with the old estimated base gas fee amount.As a result the estimated fee which users are required to pay upfront may be insufficient to cover the gas expenditure for order execution in the V2.1 system.
Recommendation
Consider implementing a
ESTIMATED_GAS_FEE_BASE_AMOUNT_V2_1which corresponds to theEXECUTION_GAS_FEE_BASE_AMOUNT_V2_1value.Resolution
GMX Team: Resolved.
-
L-01 Low Config Uses realtimeFeed Instead Of dataStream Configuration Resolved
Description
The file
config/tokens.tsimplements the configuration for all tokens and their oracles. In the GMX tokens,realtimeFeedIdandrealtimeFeedDecimalsare used instead of the newdataStreamFeedIdanddataStreamFeedDecimals. This will cause the setup to fail/revert.Recommendation
Change the names of the two variables.
Resolution
GMX Team: Resolved.
-
L-02 Low Users Pay Extra in Fees In Certain Markets Documentation Acknowledged
Description
estimatedDepositOraclePriceCount(),estimatedWithdrawalOraclePriceCount(),estimateOrderOraclePriceCount(), &estimateShiftOraclePriceCount()make an assumption that the long, short, and index tokens will all be different tokens. This is not always the case since index token, long token, and short token can be the same. The oracle price count is then multiplied byEXECUTION_GAS_FEE_PER_ORACLE_PRICEand added to the Keeper’s fee. This will charge users unnecessary fees with each interaction to the protocol.Recommendation
Store a variable that tracks the amount of tokens that have their prices set. Then when calculating the Keeper’s fee, utilize this value.
Resolution
GMX Team: Acknowledged.
-
L-03 Low Total AutoCancel Gas Supersedes Max Auto Cancels Documentation Acknowledged
Description
The maximum amount of auto cancels multiplied by the max callback gas limit is greater than the max total callback gas limit for auto cancels. This can be an issue for users and integrators who are not aware of this caveat, and attempt to add the maximum amount of auto cancels to a position.
Recommendation
Be sure to document this behavior to alert users and integrators of this scenario.
Resolution
GMX Team: Acknowledged.
-
L-04 Low Callback And Refund Receiver Risks Documentation Acknowledged
Description
Because the funds are sent to the
accountinstead of the callback contract when an order is cancelled it could be unexpected for users and integrating protocols, making it become difficult for the callback contract to handle these funds as they would receive the executionFee refund, but not the input token amount for deposits, withdrawals, or orders.Recommendation
Document this behavior so integrators and users can build accordingly.
Resolution
GMX Team: Acknowledged.
-
L-05 Low Incorrect Oracle Price Estimate Logical Error Resolved
Description
createDeposit()callsestimatedWithdrawalOraclePriceCount(). The logic is the same asestimatedDepositOraclePriceCount(), so there is no impact, however the naming convention is wrong.Recommendation
Switch
estimatedWithdrawalOraclePriceCount()toestimatedDepositOraclePriceCount()increateDeposit().Resolution
GMX Team: Resolved.
-
L-06 Low Inconsistent AutoCancel Validation Unexpected Behavior Resolved
Description
To verify update the auto cancel list an order must either be a
LimitDecreaseorStopLossDecreasewith the new change toupdateAutoCancelList(). However,isDecrease()is still used increateOrder. This will causeMarketDecreaseorders to callvalidateTotalCallbackGasLimitForAutoCancelOrders()when it is unnecessary.Recommendation
Use the same check from
updateAutoCancelList()increateOrder().Resolution
GMX Team: Resolved.
-
L-07 Low Optimal Usage Borrowing Can Remain Constant Configuration Acknowledged
Description
The
additionalBorrowingFactorPerSecondin thegetKinkBorrowingFactorfunction is initialized to 0 and is only changed ifaboveOptimalUsageBorrowingFactoris less than or equal tobaseBorrowingFactor. Therefore theborrowingFactorPerSecondwill not grow since multiplication by 0 will causeadditionalBorrowingFactorPerSecond * diff / divisorto be 0.Recommendation
Consider verifying that the
aboveOptimalUsageBorrowingFactoris always be greater thanbaseBorrowingFactorupon configuration.Resolution
GMX Team: Acknowledged.
-
L-08 Low Incorrect Estimated Price Counts Logical Error Acknowledged
Description
In the
GasUtilsfile the oracle price estimation functions accept aswapCountand add this value to the resulting estimated oracle prices necessary. However the estimation does not accurately account for the prices required for swaps. For example:- Consider a deposit to the USDC/WETH market
- longTokenSwapPath = [USDT/ATOM, ATOM/DAI, DAI/WETH]
- shortTokenSwapPath = [SOL/WBTC, WBTC/ARB, ARB/USDC]
In the worst case, all 6 of these markets in the swap path have a different index token, and the deposit market has a unique index token as well. Yielding 7 prices necessary just for index tokens. Then all tokens used in the swapPaths are necessary: [USDT, ATOM, DAI, WETH, SOL, WBTC, ARB, USDC] which adds 8 more potential prices.
While the existing validation assumes that the worst case is 8 prices as mentioned in the comments, and 9 prices as the maximum returnable by the
estimatedDepositOraclePriceCountandestimatedWithdrawalOraclePriceCountfunctions — the actual worst case is 15 total prices.Recommendation
Multiply the
swapCountby 2 in each of theestimatedDepositOraclePriceCount,estimatedWithdrawalOraclePriceCount, andestimateOrderOraclePriceCountfunctions in order to accurately represent the worst case amount of oracle prices required for the action.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.
