GMX engaged Guardian to review the security of its decentralized synthetics perpetuals exchange. From the 23rd of May to the 2nd of June, a team of 2 auditors reviewed the source code in scope.
- Published
- Review window
- May 23 to June 2, 2023
- Language
- Solidity
- Chains
- Arbitrum, Avalanche
- Sector
- Perpetuals
- 0 Critical
- 1 High
- 9 Medium
- 9 Low
- 0 Informational
Scope
Overview
GMX engaged Guardian to review the security of its decentralized synthetics perpetuals exchange. From the 23rd of May to the 2nd of June, a team of 2 auditors reviewed the source code in scope.
Findings 19
-
DPCU-1 High priceImpactDiffUsd Unclaimable For Adjusted PnL Logical Error Resolved
Description
Proof of concept: PoC
If the
positionPnlUsdis positive but smaller than thepriceImpactDiffUsd, theadjustedPositionPnlUsdis set to 0. However the condition for accounting for thepnlDiffAmountand making that amount claimable for the user is dependent onadjustedPositionPnlUsd > 0.Therefore, cases where the
priceImpactDiffUsdcannot be entirely fulfilled by thepositionPnlUsdresult in the user being unable to claim their pnl that was used to cover a portion of thepriceImpactDiffUsd.Recommendation
Change the condition to
adjustedPositionPnlUsd ≥ 0or make theincrementClaimableCollateralAmountcall directly when the PnL is decreased by thepriceImpactDiffUsd.Resolution
GMX Team: The logic in
DecreasePositionCollateralUtils.solwas refactored. -
DPCU-2 Medium Fees May Be Errantly Credited To The Pool Misaccounting Resolved
Description
In the
processForceClosefunction, theamountForPoolis theremainingCollateralminus thefundingFees.However it is possible in some cases that this
amountForPoolincludes amounts that were meant to be subtracted from the collateral for other beneficiaries other than the pool. For example thefeeReceiver,uiFeeReceiver, andaffiliate.This situation can arise when the
pendingCollateralDeductionis only slightly larger than the remaining collateral, and the exact deduction that put the collateral deduction over the remaining collateral threshold is one of these fees that should not be distributed to the pool.Recommendation
Consider decrementing these fees from the
amountForPooland crediting as much as possible to the rightful receivers.Resolution
GMX Team: The logic in
DecreasePositionCollateralUtils.solwas refactored. -
PU-1 Medium Inaccurate Price Impact Formula Logical Error Resolved
Description
Proof of concept: PoC
The comment in the
applyImpactFactorfunction states the following:We divide by 2 here to more easily translate liquidity into the appropriate impactFactor values. For example, if the impactExponentFactor is 2 and we want to have an impact of 0.1% for $2 million of difference we can set the impactFactor to be 0.1% / 2 million, in factor form that would be 0.001 /
2,000,000 * (10 ^ 30)However this additional divisor of 2 is redundant, especially in the given example.
Consider the
diffUsdof2,000,000and animpactExponentFactorof 2 (ignoring units):exponentValue = 2,000,000 * 2,000,000;impactFactor = 0.001 / 2,000,000exponentValue * impactFactor = 2,000,000 * 2,000,000 * .001 / 2,000,000 / 2 = 2,000,000 * .001 / 2
Without the extra division by 2 we already have the result we're looking for,
2,000,000 * .001 = 2,000since2,000,000and1/2,000,000cancelled the additionalx2introduced.This directly contradicts the example given in the
applyImpactFactorfunction.Recommendation
Remove the redundant
1/2.Resolution
GMX Team: The recommendation was implemented.
-
BOU-1 Medium Incongruent Price Impact For Decrease Orders Logical Error Resolved
Description
Proof of concept: PoC
The formula used to compute the
executionPricein theBaseOrderUtils.getExecutionPricefunction does not agree with thepriceImpactAmountcalculation in thePositionPricingUtils.getPriceImpactAmountfunction during decrease orders.When calculating the
executionPrice, thepriceImpactUsdis applied in a fraction with thesizeDeltaUsd. This agrees with thegetPriceImpactAmountcalculations for increase orders, as thesizeDeltaUsdand theexecutionPricedetermine the trader’ssizeInTokensand therefore their immediate PnL.However, when closing a position, the trader realizes PnL based on the
sizeInTokensandexecutionPrice, not thesizeDeltaUsdandexecutionPrice. Therefore the effect that price impact has on the trader’s PnL is not accurately reflected by the calculation for theexecutionPrice. Ultimately because of this, theexecutionPriceand resulting trader’s PnL do not agree with thepriceImpactAmountgenerated by thePositionPricingUtils.getPriceImpactAmountfunction.Recommendation
Consider applying the
priceImpactUsddirectly to the trader’s PnL rather than manipulating theexecutionPricefor decrease orders.Resolution
GMX Team: The
executionPricelogic was refactored. -
MKTU-1 Medium Wrong Impact Pool Maximization Logical Error Resolved
Description
The Impact pool pricing should use
!maximizefor the index token sinceimpactPoolUsdis being deducted, however this calculation usesmaximize.Recommendation
Change
maximizeto!maximizefor the index token valuation.Resolution
GMX Team: The recommendation was implemented.
-
ORDU-1 Medium Read-only Reentrancy Reentrancy Resolved
Description
In the
OrderUtils.cancelOrderfunction, theorderVault.transferOutis executed before the order is removed from thedataStore.Recommendation
Move the removal of the order from the
dataStoreto before theorderVault.transferOutcall.Resolution
GMX Team: The recommendation was implemented.
-
DPCU-3 Medium Capped PnL Leads To Incongruent Accounting Misaccounting Resolved
Description
In the event that a trader’s PnL is capped, the PnL they experience from price impact may not be accurately represented by the change in balance of the position impact pool, therefore perturbing the pool value.
For example: A trader is positively impacted but their PnL is capped. The capping of their PnL essentially changes their
executionPriceand negates the positive impact they received.However this positive impact is still removed from the position impact pool to offset the immediate gain in PnL the trader would have realized from the impact.
Therefore the trader does not actually experience the PnL boost from the price impact amount, but that amount is still credited towards the pool value with the removal from the position impact pool.
Recommendation
Consider computing what ought to be removed from the position impact pool after the trader’s PnL is capped.
Resolution
GMX Team: The logic in
DecreasePositionCollateralUtils.solwas refactored. -
DPCU-4 Medium adjustedPriceImpactDiffAmount Minimized Logical Error Resolved
Description
While converting the
adjustedPriceImpactDiffUsdto a collateral token amount, thecollateralTokenPrice.maxis used. However thecollateralTokenPrice.maxwill result in a smalleradjustedPriceImpactDiffAmount.In scenarios where the max price has a nontrivial difference with the min price, e.g. a depeg event, this can lead to users paying significantly less for this capped price impact amount than they ought to.
Recommendation
Use the
collateralTokenPrice.minwhen translating theadjustedPriceImpactDiffUsdto aadjustedPriceImpactDiffAmount.Resolution
GMX Team: The logic in
DecreasePositionCollateralUtils.solwas refactored. -
DPCU-5 Medium Liquidation Reverts Due To Underflow Underflow Resolved
Description
Although rare, there are cases where the
pendingCollateralDeductionis smaller than thefees.funding.fundingFeeAmount, resulting in a revert upon thecache.remainingCostAmountcalculation in theprocessForceClosefunction.Consider the following:
- Position with 1 token of collateral
- Fees of total 11 tokens
- Funding fees of 3 tokens
- Profit of 9 tokens
In this case, the profit is used to cover 9 tokens of the
fees.collateralCostAmount, so the remainingfees.collateralCostAmountis 2 tokens. Therefore thevalues.pendingCollateralDeductionwill be larger than thevalues.remainingCollateralAmountand the execution will enter theprocessForceClosefunction.However when the
remainingCostAmountis computed, the funding fees (3 tokens) will be subtracted from the pending deduction (2 tokens) and revert.Recommendation
Although this scenario will be rare, the percentage of funding fees that may be covered by position profit should be accounted for to avoid an underflow.
Resolution
GMX Team: The logic in
DecreasePositionCollateralUtils.solwas refactored. -
DPCU-6 Medium Position Price Impact Not Offset Mis-accounting Resolved
Description
During the force closure of a position during a liquidation or ADL order, the accounting for the position impact pool with
applyDeltaToPositionImpactPoolis skipped. However the effects of the price impact were already felt on the position’s resulting PnL.This results in scenarios where a user is significantly negatively/positively impacted during a liquidation/ADL and this amount is not reflected by the position impact pool and so the pool value is asymmetrically effected.
For instance, a user’s PnL is positively impacted by $100 during a force close liquidation. This positive impact is translated to a decrease of the pool value by $100.
The positive impact is not offset by a decrease in the position impact pool, and therefore the pool realizes immediate losses from PI.
Vice-versa for the pool realizing immediate gains on negative price impact, although when a position is negatively impacted, it contributes to the collateral + pnl not being sufficient and therefore the necessary accounting becomes less straightforward. In these scenarios, the position impact pool ought to only be increased by the amount that the position actually experienced, as it wasn’t able to cover it’s entire losses/negative impact — effectively exactly offsetting whatever amount was “payable” (or actually was able to take effect) of the negative impact.
Recommendation
This is somewhat non-trivial to address in the negative impact case as mentioned above, however for the positive impact case, the full impact amount should be applied to the position impact pool, as this full amount is experienced by the trader.
Resolution
GMX Team: The logic in
DecreasePositionCollateralUtils.solwas refactored. -
DPCU-7 Low Invalid priceImpactDiffUsd Emitted Events Resolved
Description
During the
processForceClosefunction, thepriceImpactDiffUsdis assigned to 0 in the returned values, however there may have been a nonzeropriceImpactDiffUsdthat was applied to theadjustedPositionPnlUsd.At present, this
priceImpactDiffUsdwould be misrepresented as 0 in theemitPositionDecreasefunction call.Recommendation
Compute the amount of
priceImpactDiffUsdthat was applied to theadjustedPositionPnlUsdand return that as a part of theDecreasePositionCollateralValuesin theprocessForceClosefunction.Resolution
GMX Team: The logic in
DecreasePositionCollateralUtils.solwas refactored. -
DPCU-8 Low Event Emission For Insufficient Payment Events Resolved
Description
In the event that a position is force closed and the
secondaryOutputorremainingCollateralAmountis insufficient to cover the position costs, it may be helpful to emit an event indicating theremainingCostAmountthat was left uncovered.Recommendation
Emit an event at the end of the
processForceClosefunction if theremainingCostAmountis greater than 0. Similar to the logic foremitInsufficientFundingFeePayment.Resolution
GMX Team: The logic in
DecreasePositionCollateralUtils.solwas refactored. -
POSU-1 Low User’s PnL Differs From Pool PnL Warning Resolved
Description
The PnL of a user’s position is based upon their
executionPrice, which deviates from the index price due to price impact. If we consider the scenario of a single trader in the market, when the user decreases their position, the position’s PnL will not be equal to pool’s PnL obtained fromMarketUtils.getPnl.As a result, this may lead to the pool’s PnL less likely to be capped when +PI is experienced since the pool’s PnL will be smaller. Similarly, this may lead to the pool’s PnL more likely to be capped when -PI is experienced since the pool’s PnL will be larger.
Recommendation
Be aware of this edge case, it may not be necessary to address it directly with a code change but is worth considering when configuring the PnL caps as well as other relevant variables.
Resolution
GMX Team: The
executionPricelogic was refactored. -
OCL-1 Low Inefficient Validation Optimization Resolved
Description
The check
if (!primaryPrices[reportInfo.token].isEmpty())can be performed at the top of theforloop to save gas as it unnecessary to perform all the price processing for this check.Recommendation
Implement the above recommendation.
Resolution
GMX Team: The recommendation was implemented.
-
OCL-2 Low Unnecessary Parameter Superfluous Code Resolved
Description
All calls to
emitOraclePriceUpdatedhave parameterisPrimaryastrue. Thus, the parameter may be removed.Recommendation
Implement the above recommendation.
Resolution
GMX Team: The recommendation was implemented.
-
ERR-1 Low Unnecessary Error Superfluous Code Resolved
Description
The
InvalidPoolAdjustmenterror could be removed as it is never used.Recommendation
Implement the above recommendation.
Resolution
GMX Team: The recommendation was implemented.
-
MKTU-2 Low Unnecessary Cache Attributes Superfluous Code Resolved
Description
The
cache.collateralForLongsandcache.collateralForShortsamounts have been removed from the aggregateminTokenBalancelogic and are now validated individually.However these values are still added in the result for the
getExpectedMinTokenBalancefunction and reside in theGetExpectedMinTokenBalanceCache.Recommendation
Remove the
cache.collateralForLongsandcache.collateralForShortsvalues from the summation in thegetExpectedMinTokenBalancefunction as well as theGetExpectedMinTokenBalanceCachestruct.Resolution
GMX Team: The recommendation was implemented.
-
CON-1 Low Duplicated Key In _initAllowedBaseKeys Superfluous Code Resolved
Description
The
MAX_POSITION_IMPACT_FACTOR_FOR_LIQUIDATIONSkey is duplicated in the_initAllowedBaseKeysfunction.Recommendation
Remove one of the duplicated
allowedBaseKeys[Keys.MAX_POSITION_IMPACT_FACTOR_FOR_LIQUIDATIONS] = true;lines.Resolution
GMX Team: The duplicated key was removed.
-
ADLH-1 Low Crowded Code Style Formatting Resolved
Description
The
msg.senderparameter is crowded on the same line as theoracleParams.Recommendation
Put the
msg.senderparameter on it’s own line.Resolution
GMX Team: The recommendation was removed.
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.
