The _payExecutionFee()
function calculates the required execution fee using gas price at execution time (tx.gasprice)
. Since tx.gasprice
can be manipulated by front-running attacks, an attacker can artificially increases gas prices right before a user's transaction executes. This results in a higher required execution fee than the user originally sent, causing _payExecutionFee()
to revert the deposit transaction.
This attack does not require direct contract interaction an attacker can simply manipulate gas fees at the mempool level, making it hard to trace and difficult for regular users to prevent
HOW THE ATTACK WORKS
Normal Execution:
Assume getExecutionGasLimit(isDeposit) = 50,000
gas
Current gas price: tx.gasprice = 2 gwei
Required execution fee calculation:
User submits a deposit transaction with msg.value = 0.0001 ETH
Deposit executes succesfully.
Attack Process:
Victim submits a deposit transaction with msg.value = minExecutionFee,
based on the current gas price (2 gwei
).
Attacker monitors the pending transaction in the mempool and submits a transaction with an artificially high gas price (10 gwei
).
Ethereum's fee mechanism adjusts to the new higher gas price.
When the victim's transaction executes, tx.gasprice is now 10 gwei instead of 2 gwei.
The new required execution fee is now:
Since the user only sent 0.0001
ETH, _payExecutionFee()
reverts, causing the deposits to fail.
Attacker's gas-spike transaction gets mined first, but they do not lose any ETH because they never interact with the contract.
Attacker successful >>
Deposit fails without direct contract interaction.
ROOT CAUSE
The contract relies on tx.gasprice
, which is variable and attackable.
Users are locked out of depositing
Attackers can target specific users and prevent their deposits from going through (DoS)
Manual Review
Use block.basefee
instead of tx.gasprice
Ethereum introduced EIP-1559, which seperate base fee (block.basefee) from priority fees (tx.gasprice
).
why it works it is constant for an entire block, so it cannot be manipulated per transaction.
Unlike tx.gasprice
, it remains stable even if atackers front-run.
Prevents deposit failures due to gas spikes.
The same issue in other function that users tx.gasprice
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.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.