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

A user loses their funds if the `refundExecutionFee` fails

Summary

Users lose their funds if the refundExecutionFee function fails in the try-catch block.

Vulnerability Details

In the _cancelFlow functions with is meant to cancel the flow of the vault, if the current flow is WITHDRAW, then users are supposed to be refunded their execution fee via the refundExecutionFee function in the try-catch block. as shown below

function _cancelFlow() internal {
//Skipped for simplicity
} else if (flow == FLOW.WITHDRAW) {
@> try IGmxProxy(gmxProxy).refundExecutionFee(depositInfo[counter].owner, depositInfo[counter].executionFee) {}
catch {}
}
flow = FLOW.LIQUIDATION;
nextAction.selector = NextActionSelector.FINALIZE;

The try section calls the refundExecutionFee function in the GMX proxy contract, which uses the payable(receipient).transfer(amount) line to refund the execution fees, since the transfer has limited gas and is likely to fail under certain circumstances, if it fails the _cancelFlow moves on with the execution of the rest of the functions thus leading to the user losing their funds. The refund has a high likelyhood of failing doue to the use of .transfer() to handle the eth transfer.

Impact

The issue in the _cancelFlow function causes users to permanently lose their execution fee refunds if the refundExecutionFee call to the GmxProxy contract fails. The try-catch block will silently ignores failures of the payable().transfer() operation, which is prone to failure due to its fixed 2300 gas stipend—insufficient for recipients with complex fallback logic or under gas-intensive conditions. When this happens during a WITHDRAW flow, the function proceeds to set flow = FLOW.LIQUIDATION without reverting or retrying, effectively erasing the refund opportunity. This results in a direct financial loss for users, as their entitled funds are neither returned nor held for later recovery, undermining the contract’s reliability and user trust.

Tools Used

Manual Review

Recommendations

  1. Revert on Refund Failure in _cancelFlow
    Remove the empty catch {} block and let the transaction revert if refundExecutionFee fails. This ensures that the flow cancellation halts if the refund cannot be processed, preventing the loss of user funds by keeping the contract state intact for retry or manual intervention.

function _cancelFlow() internal {
//Skipped for simplicity
} else if (flow == FLOW.WITHDRAW) {
- try IGmxProxy(gmxProxy).refundExecutionFee(depositInfo[counter].owner, depositInfo[counter].executionFee) {}
- catch {}
+ if (flow == FLOW.WITHDRAW) {
+ IGmxProxy(gmxProxy).refundExecutionFee(depositInfo[counter].owner, depositInfo[counter].executionFee);
+ }
}
flow = FLOW.LIQUIDATION;
nextAction.selector = NextActionSelector.FINALIZE;
}
  1. Replace .transfer() with .call() in refundExecutionFee to reduce the likelihood of a transfer Failing.

Modify the IGmxProxy contract’s refundExecutionFee function to use payable(recipient).call{value: amount}("") instead of payable(recipient).transfer(amount). The .call() method forwards all available gas (configurable if needed) and is less likely to fail due to gas limitations, unlike .transfer()’s fixed 2300 gas stipend.

Updates

Lead Judging Commences

n0kto Lead Judge 5 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

Suppositions

There is no real proof, concrete root cause, specific impact, or enough details in those submissions. Examples include: "It could happen" without specifying when, "If this impossible case happens," "Unexpected behavior," etc. Make a Proof of Concept (PoC) using external functions and realistic parameters. Do not test only the internal function where you think you found something.

Support

FAQs

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