Web3 & smart contracts
Partial13 minSandwich Attacks & MEV
Transactions are public in the mempool before they confirm, and whoever orders a block chooses their sequence. MEV — Maximal Extractable Value — is the profit available from that ordering power. A sandwich attack is its signature form: a searcher front-runs a victim’s swap to move the price against them, lets the victim execute at the worse price, then back-runs to sell. The enabling flaw on the contract side is weak slippage protection.
Classification
- Domain
- Web3 & smart contracts
- ROOTVIEL support
- Partial — Core is built; parts are not yet wired end-to-end.
- Analysis family
- RootRaven
- CWE
- CWE-362, CWE-841
- OWASP
- SC02
Definition
Maximal Extractable Value (MEV) is the value a block producer or searcher can extract by including, excluding, or re-ordering transactions within a block, beyond the standard block reward and fees. It arises because transaction ordering is a scarce resource under the control of whoever builds the block, and many on-chain actions are order-sensitive.
A sandwich attack is the canonical order-extraction pattern against a decentralized-exchange trade. Seeing a victim’s pending swap in the public mempool, an attacker places one transaction just before it (front-run) and one just after it (back-run). The front-run pushes the pool price in the direction the victim’s trade will push it, so the victim fills at a worse price; the back-run then unwinds the attacker’s position at the now-elevated price, capturing the difference. No contract is "hacked" — value is extracted purely from position in the ordering.
Context
Sandwiching and broader MEV affect any order-sensitive on-chain action whose intent is visible before it settles.
- ▸AMM swaps with loose or missing slippage limits (amountOutMin set too low, or defaulted to zero).
- ▸Large trades against shallow pools, where a single swap moves the price enough to be worth sandwiching.
- ▸Liquidations and arbitrage, where searchers compete to be first (priority-gas auctions / back-running).
- ▸NFT mints and other first-come actions decided by inclusion order.
- ▸Any flow that leaks intent to a public mempool before execution — the information asymmetry is the raw material.
Solidity & EVM fundamentals
A constant-product AMM prices a swap along the curve x·y = k: the larger the input relative to the reserves, the worse the marginal price, and the more the trade moves the pool. This price impact is deterministic and public, so an attacker can compute exactly how much a victim’s swap will move the price and size a front-run to profit. The contract itself behaves correctly; the "attack" lives in the ordering layer above it.
The only contract-side lever the victim controls is slippage protection — the amountOutMin (or amountInMax) argument that tells the router to revert if the fill is worse than a stated bound, and the deadline that bounds how long the transaction may sit pending. Set tightly, these cap how much a sandwich can extract; set loosely or to zero, they invite unbounded extraction. MEV cannot be eliminated by a contract (ordering is a protocol/mempool property), but its impact on a given trade is bounded by these parameters.
- Mempool
- The public pool of pending, unconfirmed transactions. Broadcasting a swap there reveals its intent to anyone watching before it settles.
- Front-run / back-run
- Placing a transaction immediately before (to move price against the victim) and immediately after (to unwind at the new price) a target transaction.
- Slippage tolerance
- The worst acceptable price for a trade, enforced on-chain via amountOutMin; a revert fires if the fill is worse.
- Price impact
- How much a swap moves the pool price, set by trade size relative to reserves along x·y=k — computable in advance by an attacker.
- Priority-gas auction / PBS
- Mechanisms by which searchers bid for ordering; proposer-builder separation and private order flow reshape, but do not remove, extractable value.
Root cause
There are two distinct roots. The systemic root is protocol-level: transaction ordering is valuable and controllable, and intent is public before settlement — this is MEV, and it is a property of the blockchain and mempool, not of any one contract. No Solidity change removes it.
The contract-side root — the part a developer owns — is inadequate slippage protection: a swap that accepts any output (amountOutMin = 0), computes its minimum off a manipulable spot price, or omits a deadline, hands the attacker an unbounded or stale extraction window. The sandwich is only as profitable as the slippage the victim tolerates. So "a sandwich happened" is systemic; "the loss was unbounded" is a fixable contract/integration defect.
How the attack works, step by step
- 1Observe: the searcher sees a victim swap sitting in the public mempool — say, buy TOKEN with a generous or zero amountOutMin.
- 2Front-run: they submit their own buy of TOKEN first (higher priority fee), pushing the pool price up before the victim’s trade executes.
- 3Victim fills worse: the victim’s swap now executes at the inflated price; because their slippage bound is loose, it does not revert.
- 4Back-run: the searcher immediately sells the TOKEN they just bought into the price the victim’s trade pushed even higher, realising a gain.
- 5Settle: the three transactions land in the order front-run → victim → back-run within the same block; the searcher’s profit is exactly the value the victim lost to price impact beyond fair value.
Vulnerable example
A synthetic router wrapper that swaps with no meaningful slippage bound and no deadline. Setting amountOutMin to zero means the victim accepts any output, so a sandwich can extract an unbounded amount of price impact.
synthetic — do not use; no slippage bound, no deadline// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; interface IRouter { function swapExactTokensForTokens( uint256 amountIn, uint256 amountOutMin, address[] calldata path, address to, uint256 deadline ) external returns (uint256[] memory); } contract NaiveSwapper { IRouter public router; function swap(uint256 amountIn, address[] calldata path) external { // UNSAFE: amountOutMin = 0 accepts ANY output price, so a // front-run can push the price arbitrarily and the trade // still succeeds. deadline = max means it can also sit // pending indefinitely and be executed at a stale moment. router.swapExactTokensForTokens( amountIn, 0, // <-- no slippage protection path, msg.sender, type(uint256).max // <-- no deadline ); } }
Impact
- ▸Direct value loss to the trader: the victim fills at a worse price; the gap is captured by the searcher.
- ▸Unbounded loss with zero slippage: accepting any output turns a bounded nuisance into an open-ended extraction.
- ▸Degraded execution quality across the protocol: users consistently receive worse prices than the quoted mid-price.
- ▸Second-order effects: stale deadlines let a transaction execute far from its intended market conditions; competition for ordering raises fees for everyone.
MEV itself is not eliminable by a contract; what a contract can control is how much of a given trade is extractable. The severity of a sandwich is a function of the victim’s slippage tolerance and the pool’s depth, not of any contract compromise.
Detection — static, dynamic, symbolic, fuzzing, formal
On the contract side, detection asks whether a swap enforces a tight, non-manipulable output bound; the extraction itself is confirmed by simulating an adversarial ordering.
- Static analysis
- Flags swaps with amountOutMin == 0, minimums derived from a spot price fetched in the same call, or missing deadlines. A strong SIGNAL of unbounded slippage exposure, not a measured loss.
- Dynamic (ordering simulation)
- On a local chain/fork, construct the front-run → victim → back-run sequence and measure the extracted value. A profitable reconstruction is concrete evidence the trade is sandwichable as configured.
- Symbolic execution
- Can confirm that a path accepts an output below fair value (e.g. prove amountOutMin is reachable as 0), but cannot by itself model mempool ordering or searcher behaviour; results are partial.
- Fuzzing
- Parameterize the attacker’s front-run size and search for the sizing that maximises extraction against a victim trade; the maximising input is a reproducible worst-case for that pool depth.
- Formal verification
- Can prove a bound like "output ≥ amountOutMin always holds", i.e. that the slippage check is enforced — but it cannot prove the chosen tolerance is economically adequate, which is a policy question.
A reproduced sandwich on a local fork is strong evidence of extractability for that pool state; it is not a claim that a specific searcher will execute it on the live network. SIGNAL (loose slippage) and FINDING (measured extraction in a lab) are different strengths of evidence.
How autonomous agents investigate it
An autonomous investigator reconstructs the adversarial ordering in a sandbox and measures extraction, rather than asserting "this is sandwichable" from the presence of a zero minimum.
- 1Observe: identify swaps and their slippage/deadline arguments; mark those with zero/loose bounds or spot-derived minimums as candidates.
- 2Hypothesise: state the extraction claim ("against a victim swap of size V into this pool, a front-run of size F yields searcher profit P and worsens the victim’s fill by L").
- 3Experiment: on a local fork of real reserves, order front-run → victim → back-run in one block and execute, measuring reserves and balances at each step.
- 4Collect evidence: the realised searcher profit net of gas and the victim’s realised loss versus the fair mid-price — numbers from execution.
- 5Decide: promote to a finding only when a real reconstruction extracted value; a tight-slippage trade where no profitable front-run exists is UNKNOWN or informational.
The model proposes the ordering and front-run sizing; it must not assert that a live searcher will act, nor estimate real-world profit — the lab reconstruction measures extractability for the forked state, which is a narrower, honest claim.
How RootRaven analyzes it
RootRaven proves sandwich extractability only by reconstructing the adversarial ordering on a disposable LOCAL blockchain or a LOCAL fork of real reserves — never by acting on a live network — and confirming the value transfer with a deterministic, AI-free verifier. The model composes the candidate front-run/back-run and sizing; the verifier measures whether the searcher ended ahead and the victim behind, and by how much.
- Observation
- Swaps with amountOutMin == 0, spot-derived minimums, or missing deadlines are marked as candidate slippage exposures — SIGNALS only.
- Hypothesis
- A quantified ordering attack: victim size, front-run size, and the expected searcher profit and victim loss.
- Experiment
- Build the block as front-run → victim → back-run on a local fork of real reserves and execute it; record reserves and balances step by step. No live-network attack mode.
- Evidence
- Measured searcher profit net of gas and the victim’s loss versus fair mid-price — raw numbers from the sandbox.
- Verification
- A deterministic checker confirms the searcher’s net gain and the victim’s net loss, scoped to the forked reserves — no model judgement.
- Finding
- Promoted only on a reproduced, profitable ordering against realistic reserves; a tight-slippage swap with no profitable front-run stays UNKNOWN or informational.
Support is "partial": extraction depends on pool depth, gas, and real ordering dynamics RootRaven does not control, so the honest claim is bounded to the forked state and block. A loose-slippage swap that was not reproduced as profitable is reported UNKNOWN, never "safe", and a lab-reproduced sandwich is not a prediction about live searchers.
Mitigation
- ▸Enforce a tight amountOutMin computed from a trusted, manipulation-resistant reference (not the spot price being traded against), and always set a near-term deadline.
- ▸Compute slippage client-side against a fair quote and pass it in; never default amountOutMin to zero.
- ▸Cap trade size relative to pool depth, or split large orders, to limit the price impact a sandwich can exploit.
- ▸Use private order flow / MEV-protection relays so the transaction is not exposed in the public mempool before inclusion.
- ▸Consider mechanism-level defenses where available: batch auctions, commit-reveal, or encrypted mempools that remove the ordering/intent advantage.
- ▸Accept the residual: a contract cannot abolish MEV; it can only bound per-trade extraction. State the residual explicitly rather than claiming immunity.
Corrected example — Solidity, why, and limits
The corrected swapper requires the caller to pass a real minimum-output bound and a near-term deadline, and reverts if either is missing or stale. This caps how far a front-run can push the fill before the trade fails.
synthetic — enforced slippage bound + deadline// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; interface IRouter { function swapExactTokensForTokens( uint256 amountIn, uint256 amountOutMin, address[] calldata path, address to, uint256 deadline ) external returns (uint256[] memory); } contract BoundedSwapper { IRouter public router; function swap( uint256 amountIn, uint256 amountOutMin, // caller supplies a REAL bound (from a fair quote) address[] calldata path, uint256 deadline ) external { // WHY: reject the dangerous defaults outright. require(amountOutMin > 0, "slippage bound required"); require(deadline >= block.timestamp && deadline <= block.timestamp + 15 minutes, "bad deadline"); // The router reverts if the fill is worse than amountOutMin, so a // front-run can only push the price up to that bound -- extraction // beyond the tolerance causes the victim's tx to fail instead. router.swapExactTokensForTokens(amountIn, amountOutMin, path, msg.sender, deadline); } }
Limits of the fix: slippage bounds cap per-trade extraction but do not remove MEV — a sandwich can still extract up to the tolerance the user accepts, and a too-tight bound just causes failed trades (a liveness cost). The bound is only as good as the fair quote it is derived from; if that quote comes from the manipulable pool, the protection is illusory. Full protection against ordering requires mechanism-level changes (private flow, batch auctions) outside a single contract.
Verifying the fix
Verify by replaying the sandwich against the bounded swapper and confirming the victim transaction reverts (rather than filling at a looted price) once extraction would exceed the tolerance. Conceptual Foundry-style fork tests follow.
conceptual — Foundry fork: sandwich bounded by slippagefunction test_sandwichBoundedBySlippage() public { uint256 minOut = _fairQuote(amountIn); // from a trusted reference uint256 tolerant = minOut * 995 / 1000; // accept at most 0.5% worse _frontRun(largeSize); // push price against victim // With a real bound, if the front-run pushed too far the victim reverts. vm.prank(victim); try swapper.swap(amountIn, tolerant, path, block.timestamp + 60) { // if it filled, confirm it filled within tolerance assertGe(_received(victim), tolerant); } catch { // acceptable: the trade failed rather than being looted } }
A passing test supports "extraction beyond the chosen tolerance caused a revert rather than a looted fill, for this pool and block". It does not prove MEV is eliminated, nor that the tolerance is economically right — that is a policy choice, not a property the test can certify.
Limitations
- ▸MEV is systemic: a contract cannot abolish ordering value, only bound per-trade extraction; "no sandwich possible" is not a claim a contract can make.
- ▸Lab vs. live: a reproduced sandwich on a fork shows extractability for that state, not that a specific searcher will act on the live network.
- ▸State-scoped: results depend on pool depth, gas, and block context, so they are bounded to the forked reserves and block.
- ▸Tolerance is policy: tooling can prove a bound is enforced but not that it is economically adequate; too-tight bounds trade extraction for failed trades.
- ▸Quote dependence: slippage protection derived from the manipulable pool is illusory; the reference quote is part of the attack surface.
- ▸Maturity caveat: this class is "partial" — RootRaven reproduces some sandwiches in the lab but reports un-reproduced loose-slippage swaps as UNKNOWN, never safe.
References
- [1]Ethereum.org — Maximal Extractable Value (MEV)
- [2]Flashbots docs — MEV, PBS, and private order flow
- [3]Uniswap docs — Swaps, price impact and slippage protection
- [4]OWASP Smart Contract Top 10 — Price Oracle Manipulation (ordering-adjacent)
- [5]MITRE CWE-362 (Race Condition), CWE-841 (Improper Enforcement of Behavioral Workflow)
- [6]Foundry Book — fork testing for ordering simulation
References point to public, well-known technical sources for further study.
Related articles
