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

Incorrect Execution Fee Handling in `PerpetualVault._payExecutionFee`

Summary

The execution fee in the PerpetualVault._payExecutionFee function is calculated using tx.gasprice, which can vary significantly. This can lead to insufficient fees if gas prices spike, causing transactions to fail or be delayed.

Vulnerability Details

The _payExecutionFee function calculates the minimum execution fee using the current transaction's gas price (tx.gasprice). Gas prices can fluctuate significantly, especially during periods of high network congestion. If the gas price spikes after the execution fee is calculated but before the transaction is mined, the calculated fee may become insufficient, leading to failed transactions.

Root Cause:
The execution fee is calculated using tx.gasprice, which can vary significantly, leading to insufficient fees if gas prices spike.

Proof of Concept:

  1. Code Analysis:

    function _payExecutionFee(uint256 depositId, bool isDeposit) internal {
    uint256 minExecutionFee = getExecutionGasLimit(isDeposit) * tx.gasprice;
    if (msg.value < minExecutionFee) {
    revert Error.InsufficientAmount();
    }
    if (msg.value > 0) {
    payable(address(gmxProxy)).transfer(msg.value);
    depositInfo[depositId].executionFee = msg.value;
    }
    }
  2. Example Scenario:

    • Execution Fee Calculation:

      • Assume the gas price at the time of calculation is 50 gwei.

      • The execution fee is calculated as getExecutionGasLimit(isDeposit) * 50 gwei.

    • Gas Price Spike:

      • Before the transaction is mined, the gas price spikes to 100 gwei.

      • The calculated execution fee is now insufficient to cover the increased gas cost.

    • Transaction Failure:

      • The transaction fails due to insufficient execution fees.

      • The user needs to resubmit the transaction with a higher fee, causing delays and additional costs.

  3. ** Unit Test: copy and paste to** PerpetualVault.t.sol

    function test_InsufficientExecutionFee() external {
    address keeper = PerpetualVault(vault).keeper();
    address alice = makeAddr("alice");
    depositFixture(alice, 1e10);
    // Simulate a gas price spike
    uint256 initialGasPrice = tx.gasprice;
    uint256 spikedGasPrice = initialGasPrice * 2;
    // Set the gas price to the spiked value
    vm.txGasPrice(spikedGasPrice);
    // Attempt to deposit with the initial execution fee
    uint256 executionFee = PerpetualVault(vault).getExecutionGasLimit(true) * initialGasPrice;
    vm.startPrank(alice);
    IERC20 collateralToken = PerpetualVault(vault).collateralToken();
    collateralToken.approve(vault, 1e10);
    vm.expectRevert(Error.InsufficientAmount.selector);
    PerpetualVault(vault).deposit{value: executionFee}(1e10);
    vm.stopPrank();
    }

Impact

Failed transactions due to insufficient execution fees can cause delays in processing deposits and withdrawals, leading to stuck operations and a poor user experience. Users may need to resubmit transactions with higher fees, incurring additional costs and delays.

Tools Used

Manual

Recommendations

To prevent insufficient execution fees due to gas price fluctuations, consider implementing a buffer or using a more stable mechanism for calculating the execution fee. One approach is to add a buffer to the calculated fee:

function _payExecutionFee(uint256 depositId, bool isDeposit) internal {
uint256 minExecutionFee = getExecutionGasLimit(isDeposit) * tx.gasprice;
uint256 buffer = minExecutionFee / 10; // Add a 10% buffer to the execution fee
if (msg.value < minExecutionFee + buffer) {
revert Error.InsufficientAmount();
}
if (msg.value > 0) {
payable(address(gmxProxy)).transfer(msg.value);
depositInfo[depositId].executionFee = msg.value;
}
}
Updates

Lead Judging Commences

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

invalid_tx-gasprice_instable

The frontrunner won’t trigger "congestion" without a huge amount of transactions, and it will cost a lot. Moreover, the execution gas limit is overestimated to prevent such cases: ``` executionGasLimit = baseGasLimit + ((estimatedGasLimit + _callbackGasLimit) * multiplierFactor) / PRECISION; ``` The keeper won’t wait long to execute the order; otherwise, GMX would not be competitive.

Support

FAQs

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

Give us feedback!