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

Loss of execution fee refund during gmx withdrawal

Title

Loss of execution fee refund during gmx withdrawal

Summary

A bug in the afterOrderExecution function prevents users from receiving execution fee refunds when exiting GMX positions with leverage greater than one. This issue occurs because the refundFee parameter is incorrectly set to false, causing the refund process to fail.

Vulnerability Details

The problem arises in the afterOrderExecution function when handling MarketDecrease orders during withdrawals. The function incorrectly sets refundFee to false in the encoded nextAction.data. This prevents the refund from being processed in the _handleReturn function. Here's how it happens:

  1. A user calls withdraw and pays the execution fee upfront.

  2. The Keeper executes runNextAction with WITHDRAW_ACTION, creating a GMX order to reduce the position size if it's open.

  3. The afterOrderExecution callback is triggered with MarketDecrease, but sets refundFee to false in nextAction.data.

  4. The Keeper runs FINALIZE with the incorrect refundFee value, resulting in no refund for the user.

Impact

Users lose their execution fee refunds when withdrawing from positions with leverage greater than one. The _payExecutionFee function allows users to send extra funds without restrictions, but these fees get stuck in the GmxProxy contract instead of being refunded. This issue is severe and directly affects user funds.

Tools Used

Manual Review

Recommendations

Modify the afterOrderExecution function to set refundFee to true when encoding nextAction.data during MarketDecrease orders in withdrawal flow. This ensures the refund is processed correctly.

function afterOrderExecution(...) external nonReentrant {
...
} else if (orderResultData.orderType == Order.OrderType.MarketDecrease) {
uint256 sizeInUsd = vaultReader.getPositionSizeInUsd(curPositionKey);
if (sizeInUsd == 0) {
delete curPositionKey;
}
if (flow == FLOW.WITHDRAW) {
nextAction.selector = NextActionSelector.FINALIZE;
uint256 prevCollateralBalance = collateralToken.balanceOf(address(this)) - orderResultData.outputAmount;
- nextAction.data = abi.encode(prevCollateralBalance, sizeInUsd == 0, false);
+ nextAction.data = abi.encode(prevCollateralBalance, sizeInUsd == 0, true);
Updates

Lead Judging Commences

n0kto Lead Judge 3 months ago
Submission Judgement Published
Invalidated
Reason: Design choice
Assigned finding tags:

invalid_no_refund

According the sponsor, here is the design choice: - when there's gmx interaction, no refund. - when there's no gmx interaction, refund fee. The remaining fees are used for gas cost of the keepers.

Support

FAQs

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