Front Running Smart Contracts: How the Attacks Work
Quick answer. Front running is when an attacker sees your pending transaction in the public mempool and pays a higher priority fee to execute first. That lets them capture value, such as a reward jump, or make your transaction fail.
Every front running smart contract attack comes down to one thing: transaction order. When two transactions touch the same system, whichever runs first can change the outcome of the other.
Front running is one of the most common attack vectors in smart contract systems. Understand how it works and you will find more bugs in any codebase you review.
This guide covers the mempool, how an attacker gets ahead of you in a block, two real findings, and a framework for uncovering these attacks in your own reviews.
How the Mempool Works
When you send a transaction, it does not go straight into a block. First it waits in the mempool, the pool of transactions waiting to be recorded.
Validators take a subset of those transactions and put them into a block in some order.
Three pending transactions A, B and C could land as B, A, C, or as A, B, C, or in any other permutation.
If the three touch unrelated systems, the order is arbitrary.
A Uniswap swap from DAI to ether, a DAI borrow on Aave and a yield optimizer's reward auto-compound do not affect each other.
If A and B interact with the same contract system, the outcome can depend on which one comes first. That difference is what an attacker exploits.
At the most abstract level, front running (and its counterpart, back running) is manipulating transaction order.
The attacker either griefs a user, causing a loss or a revert, or gets a better outcome for themselves.
How Attackers Get Ahead
So how do you get your transaction recorded before someone else's? You pay more for gas.
Each transaction pays a base fee plus a priority fee.
The priority fee goes to the validator, and it is the part you raise to be included higher in the block.
The Etherscan gas tracker shows this. At the time of recording in 2023, it showed a base fee of 37 gwei for every speed.
Only the priority fee changed between slow and fast.
Take a Uniswap V3 swap, which Etherscan estimated at around 180,000 gas.
At a total gas price of 40 gwei, it costs 40 × 109 wei × 180,000, or 7.2 × 1015 wei.
Divide by 1018 to get 0.0072 ether, about $14 if ether trades at $2,000. Raise the gas price and you pay more, but you are recorded earlier.
That is all an attacker needs. They read the victim's base fee plus priority fee in the mempool, outbid it, and their transaction A lands before the victim's transaction B.
One caveat: this requires a public mempool. At the time of recording, Arbitrum, a layer 2 with a sequencer, had none.
Its sequencer ordered transactions first come, first served, so there was no gas price to observe and outbid.
On Ethereum mainnet, front running is very real, and it happens far more than you might imagine.
Finding STK-1: Stepwise Rewards
The first finding, STK-1, comes from a system where users stake a token to share one pool of value.
Separate transactions add value to that pool, and stakers unstake to collect their rewards.
While you are staked, you receive whatever change in value happens over your stake. Say you are the first and only staker, and the pool holds $0.
A few days later it holds $100, and you claim all of it.
What matters is how that $100 arrived. It did not accrue a cent per second. A single transaction added the full $100 in one block.
Graph the pool's value over time and it does not slope upward. It sits at zero, then jumps straight to $100 at one moment.
A stepwise jump like this is a prime candidate for front running. An attacker stakes right before it, even in the same block, and unstakes right after.
They collect the same rewards as someone who has been staking since the start.
In transaction order terms, B is the transaction that adds $100 and A is the attacker's stake.
If B runs first, the attacker gets none of the $100. So the attacker reverses the order: A, then B, then an immediate withdrawal.
They never stake for any real length of time, yet they reap rewards that belong to the long-term stakers.
The finding reads, in short: there is no fee or lock-up period on staking.
A malicious actor can buy the token and stake right before the reward compounding function runs, accruing rewards meant for other stakers.
The attacker can then claim the rewards, unstake and sell right after the compound, stealing rewards from other stakers.
Nothing disincentivizes the attack. There is no fee for staking, claiming or unstaking, and no lock-up, so the full value of each step can be extracted.
Finding M-03: Copied Order IDs
The second finding, M-03, is a good Medium from an audit contest. Because the order ID is a value chosen by the user, a bad actor can grief them.
The attacker front runs the user's transaction with their own, using the same order ID, and the user's transaction reverts.
That hurts most when the order is time sensitive, such as adding collateral to avoid liquidation.
In the code, createOrder takes an order ID that is used to build the order and, later, to execute it.
If an order with that ID already exists, the new one cannot be created. (Overwriting an existing order would be a far worse bug.)
Here is the attack. The victim's transaction B sits in the mempool with an order ID of 7, and it adds collateral they need to avoid liquidation.
The attacker may even profit from that liquidation.
The attacker reads B's data, submits transaction A with the same order ID of 7, and raises the priority fee. A lands first and creates order 7.
B then tries to create order 7 and reverts. The victim cannot add collateral and may be liquidated.
Read more on how those flows fail in our guide to liquidation vulnerabilities.
The recommendation is to use a monotonically increasing nonce, or to make the order ID a hash that includes msg.sender.
Then the ID depends on who sends the transaction, and no one can copy it from the mempool.
struct Order { address account; uint256 collateralDelta; }
mapping(bytes32 => Order) public orders;
mapping(address => uint256) public nonces;
// Vulnerable: the caller picks the ID, so anyone can claim it first
function createOrder(bytes32 orderId, uint256 collateralDelta) external {
require(orders[orderId].account == address(0), "order exists");
orders[orderId] = Order(msg.sender, collateralDelta);
}
// Fixed: the ID depends on the sender and a nonce
function createOrder(uint256 collateralDelta) external returns (bytes32 orderId) {
orderId = keccak256(abi.encode(msg.sender, nonces[msg.sender]++));
orders[orderId] = Order(msg.sender, collateralDelta);
}
The two findings look nothing alike. Front running is a class of attacks, and it plays out differently depending on the system and on what the attacker wants.
How to Uncover Front Running
Two questions catch most of these bugs in a security review.
1. Check stepwise changes in shared value
Almost every DeFi protocol has some kind of shared pool.
Given how Solidity and Ethereum are designed, it makes sense: a vault accrues value, and everyone owns a piece of the vault.
Pay close attention whenever a single transaction adds value to something shared in one instant.
That could be a jump in a token's worth or in the assets sitting in a vault.
In any protocol with a reward distribution mechanism, these steps are often sandwichable: deposit right before, withdraw right after.
The sandwich attack combines front running and back running in exactly this way.
2. Check state that changes another user's outcome
The M-03 kind of attack changes a piece of state right before another user's transaction. It might change the price they execute at, or whether their transaction reverts.
Either way, the result is a negative outcome for the user, a positive one for the attacker, or both.
Walk through the fundamental goals of the system, such as creating an order, opening a position or swapping. For each, list the variables that could impact how the transaction executes.
Then ask how someone could front run to change those variables and harm the user.
Ask the same of keeper and admin actions.
Which variables affect them, and how could a user profit by front running and then back running the owner's change, for example to arbitrage a new parameter?
In short, for every external function that users or keepers call, ask what an attacker could manipulate or profit from by front running it, back running it, or both.
For more attack classes to check alongside this one, see common smart contract vulnerabilities.
How Guardian Helps
Guardian's smart contract audits pair a design review and an invariant suite with senior researchers who attack protocol logic, integrations, privileged flows and economic assumptions.
With Vanguard, two independent teams review the same scope and cross-check each other's findings.
Frequently Asked Questions
What is a front running attack in a smart contract?
It is manipulating transaction order so the attacker's transaction executes before the victim's. On Ethereum mainnet, the attacker watches the public mempool and pays a higher priority fee, to grief the victim or to profit.
Can front running happen on Arbitrum?
At the time of recording in 2023, front running by outbidding gas was not possible on Arbitrum. It had no public mempool, and its sequencer ordered transactions first come, first served. On Ethereum mainnet, front running is common.
How do you prevent order ID front running?
Do not let users choose identifiers that an attacker can copy from the mempool. Use a monotonically increasing nonce, or derive the ID from a hash that includes msg.sender, so another account cannot claim the same ID first.
What is the difference between front running and back running?
Front running lands a transaction just before a target transaction, and back running lands one just after. Attackers often combine them, such as staking right before a reward deposit and unstaking right after.
Sources
Catch front running before attackers do
Guardian researchers attack protocol logic, integrations and privileged flows before your code holds value.