Guardian's review of PerpetualVault Mitigation Review for Gamma Strategies, published May 2025. The report records 24 findings, including 7 medium and 17 low.
- Published
- Review window
- May 3 to 9, 2025
- Language
- Solidity
- Chains
- Ethereum, Arbitrum, Optimism, Base, Polygon, BNB Chain
- Sector
- Yield and vaults
- 0 Critical
- 0 High
- 7 Medium
- 17 Low
- 0 Informational
Scope
Findings 24
-
M-01 Medium Incorrect Fee Amount May Be Calculated Logical Error Acknowledged
Description
GMX is moving to a
balanceWasImprovedmodel instead of ahasPositiveImpactapproach for the position fees. This upcoming change is listed here: https://github.com/gmx-io/gmx-synthetics/pull/93However the Gamma contracts assume that the hasPositiveImpact approach is still being used. As a result, for markets with no price impact configured the fee calculated by Gamma will not match the fee calculated by GMX.
Additionally, when an order flips the skew, moving the absolute skew closer to balanced the order may improve the skew but still receive negative impact due to the negative impact factor being larger than the positive impact factor. Orders which meet this criteria will have the fee overestimated for the user.
Recommendation
The balanceWasImproved value for the order is not easily accessible without comparing the pool state directly before and after an order’s execution. Therefore the best course of action may be to either ensure that no zero slippage GM markets are used with the Gamma system, or to acknowledge the imperfection in the fee calculations for these markets.
-
M-02 Medium Lacking
setGovernanceFeeImplementation Logical Error AcknowledgedDescription
The
setGovernanceFeefunction lacks an implementation to assign the relatedgovernanceFeevalue.Recommendation
Implement the logic to assign the
governanceFeevalue in thesetGovernanceFeefunction. -
M-03 Medium Position Fees May Be Paid Twice Logical Error Acknowledged
Description
In the
_withdrawfunction thefeeAmountis removed from thecollateralDeltaAmountregardless of the sign and magnitude of the pnl.If the pnl is positive and the
swapPnlToCollateraltoken swap succeeds, the profit will be used to cover the fee amount in the decrease order execution. As a result the withdrawer would pay the position fee by having it deducted from thecollateralDeltaAmountspecified by Gamma and also by having it deducted by the pnl amount returned by GMX.Recommendation
If the position pnl is positive then account for the fact that the positive pnl amount can be used to cover the feeAmount.
However be aware that the positive pnl amount cannot be used to cover the feeAmount if the current position is long and the
swapPnlToCollateralswap fails. If this swap is assumed to always succeed in the system, then consider carefully monitoring for the failure of this swap, denoted by theSwapRevertedevent emitted by GMX, and taking manual action. -
M-04 Medium gmxLock Errantly Set To False Warning Acknowledged
Description
In the
afterLiquidationExecutionfunction thegmxLockvariable is assigned tofalse, however it is possible that an order is still pending in GMX for the vault.This may lead to unintended consequences such as un-cancellable orders through the
cancelOrderkeeper function or accidental order creation by the keeper when one is already pending in GMX.Recommendation
Remove the
gmxLockassignment in theafterLiquidationExecution. -
M-05 Medium Excessive Refund During Liquidations Logical Error Acknowledged
Description
In the
_withdrawfunction, if a liquidation occurred and thecurPositionKeyhas been assigned to zero thenrefundFeeis assigned as true in the_handleReturninvocation.As a result, a refund may be granted to the user when the settle action in GMX has already taken place for their withdrawal.
Consider the following scenario:
- User A initiates a withdrawal
- The settle decrease order is submitted and is executed in GMX
- The
nextAction.selectoris a withdrawal action - Before the Gamma keeper executes the next action, the vault position is liquidated and the
curPositionKeyis set to 0. - The Gamma keeper executes the next action and the
_withdrawinvocation invokes a_handleReturnwithrefundFeeas true. - The user is refunded for the majority of their executionFee, such that refund + executionFee paid to GMX for the settle action is greater than the executionFee initially paid by the user.
Recommendation
When the
_withdrawfunction is invoked inrunNextActionand the vault position has been liquidated consider not refunding the fee in this case, since the settle action will have already taken place. -
M-06 Medium Liquidations During Withdrawal Settlement Loop Warning Acknowledged
Description
When a liquidation occurs during the withdrawal flow, it is possible for the settlement action to get stuck and continue to fail and be retried in a cycle.
Consider the following order of events:
- A user initiates a withdrawal, the settlement action is created in GMX.
- The vault position is liquidated in GMX, the
afterLiquidationExecutionsets thenextActionto beNextActionSelector.WITHDRAW_ACTIONto restart the withdrawal and allow the user to exit. - The settlement action is executed after the liquidation and is therefore cancelled.
- In the
afterOrderCancellationcallback thenextActionis reset toNextActionSelector.SETTLE_ACTION, therefore overwriting thenextActionthat was set by the liquidation callback. - The settlement order is submitted again, and will again fail and continue the cycle, preventing the user from withdrawing.
Recommendation
Consider adding a check in the
afterOrderCancellationcallback such that if the order was a settlement order and thecurPositionKeyhas been set to zero then the next action is set toNextActionSelector.WITHDRAW_ACTIONso that the withdrawal flow can be reset and the user can receive their funds. -
M-07 Medium totalShares Accounting Perturbed By CancelFlow Logical Error Acknowledged
Description
In the
_cancelFlowfunction during the final step of a deposit flow the deposit object can have a nonzero shares amount associated which is not removed from the total upon cancellation.As a result, if a deposit is cancelled at the last step before the
FINALIZEaction, thetotalSharesaccounting will be perturbed.Recommendation
Consider explicitly disallowing the
_cancelFlowfunction from being called for deposits which have already been minted a nonzero amount of shares. -
L-01 Low Outdated Reader Used Warning Acknowledged
Description
The tests and PositionInfo interface relies on an outdated Reader contract that was deployed almost a year ago. This Reader may become deprecated with future releases and increases risk of an interface mismatch bug.
Recommendation
Consider updating to the most recent Reader deployment seen here: https://github.com/gmx-io/gmx-synthetics/blob/updates/deployments/arbitrum/Reader.json
And ensure that the interfaces in that Reader deployment match the interfaces used by Gamma, namely the inclusion of a positionKey bytes32 variable at the start of the PositionInfo struct: https://github.com/gmx-io/gmx-synthetics/blob/cb47fb783b017cfa815a353f6f39d415f60646e8/contracts/reader/ReaderPositionUtils.sol#L20
-
L-02 Low Outdated NatSpec Documentation Acknowledged
Description
The NatSpec across many functions in the Gamma codebase is outdated.
Recommendation
Consider updating the NatSpec documentation for each function in the Gamma PerpetualVault codebase.
-
L-03 Low _swapOutIndexToken DoS DoS Acknowledged
Description
In the
runNextActionfunction the_swapOutIndexTokenfunction is used to swap any remaining index tokens from the contract balance to the collateral token.When submitting the
runNextActiontransaction to the mempool, the keeper may not supply anymetadata[1]entry if it doesn’t observe any index token contract balance.However a malicious actor could frontrun the
runNextActiontransaction and supply index tokens to the contract in order to cause the_swapOutIndexTokenlogic to occur and therefore revert when themetadata[1]entry is decoded, thus censoring the runNextAction invocation.Recommendation
Be aware of this DoS vector, on Arbitrum this is not currently a concern, however for any Gamma vaults on Avalanche this is noteworthy. Consider ensuring the keeper always provides a
metadata[1]entry to handle even maliciousindexTokentransfers. -
L-04 Low Zero Position Key Used For totalAmount Warning Acknowledged
Description
In the
_totalAmountfunction when thepositionIsClosedvalue isfalsethepositionDatais always queried from GMX for thecurPositionKey.However there are cases such as liquidation where the
curPositionKeyis reset to 0, whilepositionIsClosedis intentionally not marked astrue.Recommendation
No cases have been identified where the curPositionKey is zero and positionIsClosed is false for a leveraged position during an invocation of the
_totalAmountfunction. However out of an abundance of caution, consider handling the zerocurPositionKeycase to avoid any unintended consequences of future updates.When the curPositionKey is 0 the
_totalAmountfunction should not consult GMX for the position value with thegetPositionInfofunction. -
L-05 Low Misleading Comment Documentation Acknowledged
Description
The documentation for the
fundEthfunction mentions that theGmxProxyattempts to avoid native ether from GMX. However theGmxProxyspecifically wants to receive native Ether from GMX and does so by implementing therefundExecutionFeefunction.Recommendation
Remove the misleading
fundEthcomment. -
L-06 Low Misleading Variable Name Naming Acknowledged
Description
In the
getPositionInfo,getNegativeFundingFeeAmount, andgetPnlfunctions thesizeInTokensvariable is assigned as the result of thegetPositionSizeInUsdfunction.Recommendation
Correct the
sizeInTokensvariables to be the result of thegetPositionSizeInTokensfunction. -
L-07 Low Fee Discounts Are Not Accounted For Warning Acknowledged
Description
When computing the position fee during a withdrawal there is no consideration for discounts that may be granted to the vault position due to pro tiers or in the case that a nonzero referral code is used.
As a result withdrawers may be charged a slightly higher fee than what is actually levied against the vault position.
Recommendation
Be aware of this behavior in the event that the vault receives a pro tier discount or if a referral code is used. This inaccuracy goes in the favor of the vault so it is acceptable.
-
L-08 Low Lacking Withdrawal Execution Fee Refunds Warning Acknowledged
Description
In the withdrawal flow for perpetual positions the
refundFeevalue in the_nextAction.datais always assigned tofalse. As a result users will never receive refunds for the overestimated portion of the execution fee they paid up front.Recommendation
Consider if this is the expected behavior, if it is not consider assigning the refundFee value to true.
-
L-09 Low Unused COMPOUND Action Superfluous Code Acknowledged
Description
The
COMPOUND_ACTIONNextActionSelectoris unused in the codebase.Recommendation
Consider removing the
COMPOUND_ACTIONfrom theNextActionSelectorenum. -
L-10 Low Signal Change Flow Left Incomplete Warning Acknowledged
Description
During the signal change flow it is possible for a switch from 1x long to 1x short to be left incomplete if a GMX swap is used to close the existing position and the GMX swap is cancelled.
When the GMX Market Swap action is cancelled the
nextAction.selectoris assigned as a swap action, overwriting the increase action. Therefore when the MarketSwap is retried it will be the final action in the flow and the signal change will complete without opening the new short position.Recommendation
Be aware of this edge case when the MarketSwap action fails during a signal change. And consider only using dex swaps to avoid this failure when changing from long 1x to short 1x.
-
L-11 Low Zero Position Key Used For flowData Warning Acknowledged
Description
When a liquidation occurs directly after a user initiates a deposit the
flowDatais assigned as the result of thegetPositionSizeInTokensfor acurPositionKeyof 0.It is unlikely that a position may exist in GMX with a
positionKeyof 0, however in the event that such a position does exist the flowData would be errantly assigned to a value that is not accurate to the Gamma vault position.Recommendation
Out of an abundance of caution in the
getPositionSizeInTokensVaultReaderfunction, return 0 if thepositionKeyprovided is zero. Additionally, consider adopting the same behavior for other view functions which accept a position key as a parameter. -
L-12 Low Incorrect Price Impact Rounding Rounding Acknowledged
Description
The rounding used in the
getPriceImpactInCollateralfunction does not match the rounding performed by GMX when determining the sizeDeltaInTokens for a position. As a result thegetPriceImpactInCollateralfunction will slightly miscalculate the price impact result.The
expectedSizeInTokensDeltais calculated as:uint256 expectedSizeInTokensDelta = isLong ? sizeDeltaInUsd / prices.indexTokenPrice.max : sizeDeltaInUsd / prices.indexTokenPrice.min;Meanwhile GMX calculates the
sizeInTokensDeltaas:if (params.position.isLong()) { // round the number of tokens for long positions down cache.baseSizeDeltaInTokens = params.order.sizeDeltaUsd() / indexTokenPrice.max; } else { // round the number of tokens for short positions up cache.baseSizeDeltaInTokens = Calc.roundUpDivision(params.order.sizeDeltaUsd(), indexTokenPrice.min); }Recommendation
Use round up division for shorts to match GMX’s calculation.
-
L-13 Low Arbitrary Price Impact Conversion Unexpected Behavior Acknowledged
Description
The
priceImpactInCollateralTokensvalue is computed using the max or minindexTokenPriceandshortTokenprice depending on if the positionisLong.This may seem correlated to the way that the
expectedSizeInTokensDeltais computed, however it is not. TheexpectedSizeInTokensDeltamax and min price usage is simply necessary to compute the actual amount of price impact which was experienced in the GMX system.Therefore the translation to a collateral token value of the price impact index token amount is arbitrarily assigned by the Gamma system. The calculation currently decides to use a maximum and minimum price interchangeably based upon isLong.
However, technically the most resilient method would be to use the price combination that rounds the amount of price impact experienced towards negative infinity. This follows the best practice of rounding in favor of the protocol over the favor of the user.
Recommendation
Consider implementing rounding in the favor of the protocol by assigning the minimum value of both calculations as the resulting
priceImpactInCollateralTokens. -
L-14 Low Execution Fee Charged with No GMX Call Unexpected Behavior Acknowledged
Description
When a liquidation reduces the GMX position to zero, the curPositionKey is deleted and deposits are paused. However, since positionIsClosed remains false, users who withdraw are still charged an execution fee under the assumption that GMX calls like settle and withdrawGMX will be executed.
Since the
curPositionKeyis empty, these GMX calls are skipped. The_handleReturnfunction does issue a refund, but because no callback occurred, the refunded amount is incorrect. This causes users to be overchargedRecommendation
Update the positionIsClosed flag after the GMX position is liquidated and the
curPositionKeyis cleared, or alternatively, deposit funds back into GMX immediately after liquidation so that future withdrawals trigger the expected callback and execution fee logic behaves correctly. -
L-15 Low Deposit Reverts After Full Liquidation DoS Acknowledged
Description
When a liquidation occurs, the vault processes it in
afterLiquidationExecutionand allows the keeper to create a new order using any remaining funds. However, if the vault is fully liquidated and no collateral is recovered, deposits become blocked.This is due to a division-by-zero condition in the
_mintfunction: totalAmountBefore becomes zero, while totalShares remains greater than zero.As a result, the share calculation:
_shares = amount * totalShares / totalAmountBeforeWill revert due to a division by zero error, preventing any new deposits.
Recommendation
Add a case to handle the scenario where totalAmountBefore is zero but totalShares is non-zero. Depending on the desired behavior the depositor can receive the entire vault share or a diluted amount of it.
-
L-16 Low ADLs May Cause Excessive Fees Warning Acknowledged
Description
When an ADL occurs during the last step of a withdrawal action the entire withdrawal flow is re-started to account for any tokens which might have been sent to the
PerpetualVaultas a result of the ADL.This however can mean that a withdrawal action can expend even more than the executionFee allocated for the settle and decrease actions.
Recommendation
Be aware of this behavior and be sure to top up the
GmxProxycontract accordingly in the event that this occurs. -
L-17 Low totalDepositAmount Not Decremented In Insolvency Logical Error Acknowledged
Description
In the
_handleReturnfunction the_transferTokenfunction is only invoked when the withdrawal amount is nonzero. If a withdrawal amount is zero e.g. due to an insolvent liquidation of the vault, then the totalDepositAmount decrement inside of the_transferTokenfunction will not be reached.Recommendation
Consider refactoring the
_transferTokenfunction such that it handles the 0 amount case gracefully, decrementing thetotalDepositAmountin all cases and early returning if the specified amount is 0.
No findings match.
More from Gamma Strategies
All 7 reports-
Unilaunch Launchpad and Limit Order Book
28 findings8 high 28 findings: 8 high, 8 medium, 5 low, 7 informational -
MultiPositionManager
83 findings1 high 83 findings: 1 high, 25 medium, 22 low, 35 informational -
Limit Order Manager
19 findings2 high 19 findings: 2 high, 17 low -
Position Managers
58 findings5 high 58 findings: 5 high, 10 medium, 32 low, 11 informational
Put 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.
