Summary
In the _handleReturn function the depositInfo structure is deleted before executing the refund fee logic. This results in an issue where the executionFee field is always 0, causing incorrect or failed refund calculations.
Code Analysis
Deletion of depositInfo
_handleReturn calls _burn(depositId), which deletes depositInfo[depositId].
The deletion occurs before the refund logic in _handleReturn.
function _handleReturn(uint256 withdrawn, bool positionClosed, bool refundFee) internal {
(uint256 depositId) = flowData;
uint256 shares = depositInfo[depositId].shares;
uint256 amount;
if (positionClosed) {
amount = collateralToken.balanceOf(address(this)) * shares / totalShares;
} else {
uint256 balanceBeforeWithdrawal = collateralToken.balanceOf(address(this)) - withdrawn;
amount = withdrawn + balanceBeforeWithdrawal * shares / totalShares;
}
if (amount > 0) {
_transferToken(depositId, amount);
}
emit Burned(depositId, depositInfo[depositId].recipient, depositInfo[depositId].shares, amount);
@=> _burn(depositId);
if (refundFee) {
uint256 usedFee = callbackGasLimit * tx.gasprice;
@=> if (depositInfo[depositId].executionFee > usedFee) {
try IGmxProxy(gmxProxy).refundExecutionFee(depositInfo[counter].owner, depositInfo[counter].executionFee - usedFee) {} catch {}
}
}
delete swapProgressData;
delete flowData;
delete flow;
}
_burn function
function _burn(uint256 depositId) internal {
EnumerableSet.remove(userDeposits[depositInfo[depositId].owner], depositId);
totalShares = totalShares - depositInfo[depositId].shares;
@=> delete depositInfo[depositId];
}
As a result, any subsequent access to depositInfo[depositId] returns 0 or causes unintended behavior.
Impact
The refund logic attempts to access depositInfo[depositId].executionFee.
Since depositInfo has been deleted, executionFee defaults to 0.
This means the refundExecutionFee function will always receive incorrect values.
It will lead to users not receiving their due refunds.
Recommendation
Move Refund Logic Before _burn
Rearrange the function so that the refund logic executes before deleting depositInfo:
- emit Burned(depositId, depositInfo[depositId].recipient, depositInfo[depositId].shares, amount);
- _burn(depositId);
if (refundFee) {
uint256 usedFee = callbackGasLimit * tx.gasprice;
if (depositInfo[depositId].executionFee > usedFee) {
try IGmxProxy(gmxProxy).refundExecutionFee(depositInfo[depositId].owner, depositInfo[depositId].executionFee - usedFee) {} catch {}
}
}
+ emit Burned(depositId, depositInfo[depositId].recipient, depositInfo[depositId].shares, amount);
+_burn(depositId); // Now deletion happens after refund is processed