DeFiFoundry
50,000 USDC
View results
Submission Details
Severity: medium
Invalid

_gmxLock is not unlocked in afterLiquidationExecution blocking the keeper's future Next Actions.

Summary

_gmxLock is not unlocked in the afterLiquidationExecution function, this will block the keeper's next action calls, causing a loss of gas and disrupting the protocol functionality momentarily.

Vulnerability Details

Every time the PerpetualVault contract creates an order in the GMX contracts it sets the _gmxLock variable to true, so any other action involving the GMX contracts can modify the state in the perpetual vault contract until the GMX keeper executes the order and returns the callback to the gamma contracts so that the flow can be finished correctly.
https://github.com/CodeHawks-Contests/2025-02-gamma/blob/84b9da452fc84762378481fa39b4087b10bab5e0/contracts/PerpetualVault.sol#L885
https://github.com/CodeHawks-Contests/2025-02-gamma/blob/84b9da452fc84762378481fa39b4087b10bab5e0/contracts/PerpetualVault.sol#L935
https://github.com/CodeHawks-Contests/2025-02-gamma/blob/84b9da452fc84762378481fa39b4087b10bab5e0/contracts/PerpetualVault.sol#L963
https://github.com/CodeHawks-Contests/2025-02-gamma/blob/84b9da452fc84762378481fa39b4087b10bab5e0/contracts/PerpetualVault.sol#L1078

function _createIncreasePosition(/* Code Omitted */) internal
{
/* Code Omitted */
_gmxLock = true;
gmxProxy.createOrder(orderType, orderData);
}
function _createDecreasePosition(/* Code Omitted */) internal
{
/* Code Omitted */
_gmxLock = true;
gmxProxy.createOrder(orderType, orderData);
}
function _settle(/* Code Omitted */) internal
{
/* Code Omitted */
_gmxLock = true;
gmxProxy.settle(orderData);
}
function _doGmxSwap(/* Code Omitted */) internal
{
/* Code Omitted */
_gmxLock = true;
gmxProxy.createOrder(orderType, orderData);
}

After the GMX contracts execute an order created by the GmxProxy contract, it returns a callback to this contract GmxProxy.afterOrderExecution this function calls the GmxProxy contract which calls the PerpetualVault contract so the flow can be finished correctly.
https://github.com/CodeHawks-Contests/2025-02-gamma/blob/84b9da452fc84762378481fa39b4087b10bab5e0/contracts/GmxProxy.sol#L206-L283

function afterOrderExecution(/* Code Omitted */)external override validCallback(requestKey, order)
{
/* Code Omitted */
if (order.numbers.orderType == Order.OrderType.Liquidation) {
/* Code Omitted */
IPerpetualVault(perpVault).afterLiquidationExecution();
} else if (msg.sender == address(adlHandler)) {
/* Code Omitted */
if (sizeInUsd == 0) {
IPerpetualVault(perpVault).afterLiquidationExecution();
}
} else {
/* Code Omitted */
IPerpetualVault(perpVault).afterOrderExecution(requestKey, positionKey, orderResultData, prices);
delete queue;
}
}

The functions of the PerpetualVault that receive these callbacks should finish the current flow and set _gmxLock to false so the keepers can execute future actions correctly, currently in the PerpetualVault contract the afterOrderExecution and the afterOrderCancellation set the _gmxLock variable to false correctly allowing the future keeper's calls to run successfully.
https://github.com/CodeHawks-Contests/2025-02-gamma/blob/84b9da452fc84762378481fa39b4087b10bab5e0/contracts/PerpetualVault.sol#L474
https://github.com/CodeHawks-Contests/2025-02-gamma/blob/84b9da452fc84762378481fa39b4087b10bab5e0/contracts/PerpetualVault.sol#L600

function afterOrderExecution(/* Code Omitted */) external nonReentrant {
/* Code Omitted */
_gmxLock = false;
}
function afterOrderCancellation(/* Code Omitted */) external {
/* Code Omitted */
_gmxLock = false;
}

But the afterLiquidationExecution function doesn't set the _gmxLock to false, this will cause that every subsequent call from the keeper to the runNextAction or the cancelFlow will revert in the gmxLock modifier, blocking the keeper's calls, denying the correct finalization of the current flow in the perpetual vault contract and causing gas loss from these calls.
https://github.com/CodeHawks-Contests/2025-02-gamma/blob/84b9da452fc84762378481fa39b4087b10bab5e0/contracts/PerpetualVault.sol#L563-L583

function afterLiquidationExecution() external {
if (msg.sender != address(gmxProxy)) {
revert Error.InvalidCall();
}
depositPaused = true;
uint256 sizeInTokens = vaultReader.getPositionSizeInTokens(curPositionKey);
if (sizeInTokens == 0) {
delete curPositionKey;
}
if (flow == FLOW.NONE) {
flow = FLOW.LIQUIDATION;
nextAction.selector = NextActionSelector.FINALIZE;
} else if (flow == FLOW.DEPOSIT) {
flowData = sizeInTokens;
} else if (flow == FLOW.WITHDRAW) {
// restart the withdraw flow even though current step is FINALIZE.
nextAction.selector = NextActionSelector.WITHDRAW_ACTION;
}
}

To avoid this block, the afterLiquidationExecution function should set the _gmxLock variable to false

Impact

blocking functionality of subsequent keepers nextAction calls, cancelFlow process, and the loss of transaction gas cost.

Tools Used

Manual Review

Recommendations

Set the _gmxLock variable to false in the afterLiquidationExecution function.

Updates

Lead Judging Commences

n0kto Lead Judge 9 months ago
Submission Judgement Published
Validated
Assigned finding tags:

finding_gmxLock_not_reset_during_liquidation

Likelihood: Medium, every liquidation. Impact: Medium, runNextAction and cancelFlow are not usable before a `withdraw` or a `run`

Appeal created

riceee Auditor
9 months ago
mikebello Submitter
9 months ago
n0kto Lead Judge
9 months ago
n0kto Lead Judge 8 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!