A trader who watches the Ethereum peer-to-peer network can see your exchange order before it is mined, buy just before it and sell just after it. This paper is the first to formalise and quantify these sandwich attacks on automated-market-maker exchanges such as Uniswap.
1Imperial College London, United Kingdom · 2University of Luxembourg, Luxembourg · 3Purdue University, United States
In three numbers
What a single adversary could have earned on Uniswap, without colluding with any miner.
= (440,749.02 + 98,666.15) USD ÷ 158 days: the taker plus provider totals in Table III. The paper states the 3,414 figure without showing this division.
Of 2,372,084 blocks, 1,862,800 sort transactions purely by gas price. There, paying 1 Wei more or less than the victim places a transaction just before or just after it.
Table V; the paper rounds this to 79%.
Expected profit per attacker falls to 0.08 ETH (12 USD) when ten adversaries counter-bid for the same 20 ETH → DAI victim pending for 10 s.
§VII simulation, 100,000 runs per setting.
The problem
An automated market maker (AMM) holds two pools of assets, $x$ of asset $X$ and $y$ of asset $Y$, and quotes prices from a fixed formula. Uniswap keeps the product constant, $x \cdot y = k$. A taker who pays in $\delta_x$ moves the pool to
$$ (x,y) \xrightarrow{\ \textsf{TransactXForY}(\delta_x)\ } \Big(x+\delta_x,\ \frac{xy}{x+\delta_x-c_x(\cdot)}+c_y(\cdot)\Big) $$
where $c_x, c_y$ are fees (0.3% on Uniswap). Every price is deterministic and every pending order is public in the mempool for seconds before a miner includes it. So an adversary knows exactly how a victim's trade will move the price, and has time to act.
Traders defend themselves with a slippage limit: “fill me only if the price is at most 0.5% worse than quoted”. The attack exploits exactly that tolerance. The adversary front-runs just enough to push the price to the edge of the victim's limit, lets the victim trade at the worse price, then back-runs to unwind. The victim's trade still succeeds, only more expensively.
The paper studies two variants. The liquidity taker attack is buy, victim, sell. The new liquidity provider attack is withdraw liquidity, victim, re-deposit, rebalance. Both are evaluated at Uniswap's pool states at Ethereum block 9,000,000 (25 November 2019). Preliminary results were disclosed to Uniswap on 18 November 2019, which allowed it to tighten trader protections.
| AMM state 1 | AMM state 2 | |||
|---|---|---|---|---|
| Liquidity $X$ ($x$) | 100 | 1,000 | ||
| Liquidity $Y$ ($y$) | 10 | 100 | ||
| Product ($k = xy$) | 1,000 | 100,000 | ||
| Purchase amount X | 1 | 10 | 1 | 10 |
| AMM price Y/X | 0.1000 | 0.1000 | 0.1000 | 0.1000 |
| $\mathbb{E}[P]$ Y/X | 0.1010 | 0.1111 | 0.1001 | 0.1010 |
| Expected slippage | 0.0010 | 0.0111 | 0.0001 | 0.0010 |
| Slippage rate | 1.01% | 11.11% | 0.10% | 1.01% |
Interactive · attack 1
A victim sells ETH for a token. Pick one of the five largest Uniswap markets, a trade size and the victim's slippage tolerance. The adversary front-runs with the largest trade that still leaves the victim's order inside its limit, then sells the tokens back.
Adversary's revenue vs the victim's trade size
Recomputed in your browser from the paper's constant-product model (0.3% fee, pool reserves at block 9M from §IV-A). At the default settings it reproduces the Fig. 5 example exactly, and across markets it reproduces all 15 minimum-victim points of Fig. 7. The model is continuous; the mainnet bot also searched over Uniswap's integer rounding (§V). Dollar values use the paper's rate of 1 ETH = 148.97 USD.
Interactive · attack 2 (new in this paper)
A liquidity provider can hurt a victim without trading at all. By withdrawing a share $L$ of the pool just before the victim buys ETH, it thins the market so the victim's order moves the price further. Afterwards it re-deposits the same share and rebalances with one swap. The cost is the 0.3% commission its share would have earned on the victim's trade.
Provider's revenue and forgone commission vs the victim's trade size
Derived for this page from the paper's appendix equations: withdraw $L$, victim buys an exact amount of ETH, re-deposit to restore share $L$, then one swap. At the default settings it reproduces the paper's 100 ETH example (26.58%, 0.28 ETH vs 0.08 ETH passive) and its 60 ETH example (37.76%, 0.07 ETH after 0.01 ETH gas). For minimum victim sizes, see note 6 under Notes on the numbers.
Interactive · positioning
The attack works only if the miner places $T_{A1}$ directly before the victim and $T_{A2}$ directly after. The adversary does not collude with miners. It prices $T_{A1}$ at the victim's gas price $+1$ Wei and $T_{A2}$ at $-1$ Wei, then relies on the miner's ordering policy. The authors classified every block from Uniswap's launch (block 6,627,917) to block 9M: 388 days.
A block, top = executed first (fees shown relative to the victim's)
Share of the 2,372,084 blocks by ordering policy (Table V)
| Strategy | Blocks | Ratio |
|---|---|---|
| Empty block | 55,545 | 0.0234 |
| Order per gas price | 1,862,800 | 0.7853 |
| Order per Parity default | 384,150 | 0.1620 |
| Unknown ordering | 69,589 | 0.0293 |
| Total | 2,372,084 | 1.0000 |
Parity-default blocks are a lower bound: a Parity miner with no local transactions looks like pure gas-price ordering. When two transactions pay the same gas price, both Geth and Parity fall back to first-in-first-out.
Interactive · missed revenue
For the 79 exchanges listed in the Uniswap UI (§V-C and Table III; §V's setup paragraph says 78, see note 11), the authors replayed every transaction between blocks 8M and 9M. For each one they asked whether a sandwich would have beaten a 0.01 ETH break-even cost. Out of 105,161 trades, 7.4% were profitable targets for a taker and 4.2% for a provider.
| Profitable / total txs | Revenue (ETH) | Revenue (USD) | |
|---|---|---|---|
| Liquidity taker attacks taker | |||
| ETH → Token | 878 / 25,204 | 98.15 | 14,621.41 |
| Token → ETH | 5,657 / 602,85 * | 2,643.84 | 393,852.46 |
| Token → Token | 1,258 / 196,72 * | 216.66 | 32,275.16 |
| Total | 7,793 / 105,161 | 2,958.64 | 440,749.02 |
| Liquidity provider attacks taker | |||
| ETH → Token | 444 / 25,204 | 52.55 | 7,829.05 |
| Token → ETH | 3,254 / 60,285 | 520.61 | 77,555.62 |
| Token → Token | 721 / 19,672 | 89.16 | 13,281.49 |
| Total | 4,419 / 105,161 | 662.32 | 98,666.15 |
* Printed this way in the paper. The provider rows and the totals (25,204 + 60,285 + 19,672 = 105,161) show these should read 60,285 and 19,672; the chart uses those values.
Transactions were crawled from a full archive Geth node. Most takers traded with about 1% maximum unexpected slippage (the Uniswap default at the time); the estimated averages were 0.58% expected and 1.16% unexpected slippage (Fig. 10).
Interactive · many adversaries
With several attackers watching the same victim, whoever gets their front-run $T_{A1}$ mined first wins. The loser's front-run fails the victim's slippage check. Each bot follows the reactive counter-bidding strategy: when it sees a rival bid, it re-issues with a 10% higher gas price (Geth's replacement bump). The authors simulated this 100,000 times per setting on the Uniswap DAI market at block 9M, with the victim's slippage fixed at 0.5%.
Victim trades 20 ETH → DAI; pending 10 s before it is mined
| Adversaries | Profit reduction | Expected profit / attacker | Unprofitable after pending |
|---|---|---|---|
| 2 | 51.0% | 0.45 ETH (67 USD) | 27.7 s |
| 5 | 81.4% | 0.17 ETH (25 USD) | 20.3 s |
| 10 | 91.5% | 0.08 ETH (12 USD) | 16.3 s |
Who wins a two-bot race? (Table VI)
If a second bot is irrational and ignores the slippage check, the victim's trade fails and both front-runs fail, so both lose their fees.
Simulation inputs from the paper: transaction size 426.27 ± 68.94 bytes; victim gas price 8.76 ± 61.18 GWei; victim pending time uniform on 0–30 s (block interval 13.5 ± 0.12 s); gas used 85,488 ± 34,782; latency and bandwidth interpolated from prior measurements (Table VII). The minimum profitable victim input is 14.75 ETH (2,197.30 USD) for a single taker adversary and 27.8 ETH (4,141.37 USD) for a single provider adversary.
Side by side
| Liquidity taker attacks taker | Liquidity provider attacks taker | |
|---|---|---|
| Transactions | 2: buy before, sell after | 3: remove liquidity, add liquidity, rebalancing swap |
| How the victim is hurt | The price is pushed toward the victim's slippage limit | The pool is thinned, so the victim's own trade moves the price further |
| Cost beyond gas | Two AMM fees on its own trades | Forgoes the commission its share would earn on the victim's trade |
| Smallest profitable victim, ETH/SAI, 0.5% | 24.26 ETH (Fig. 7) | 43.93 ETH (Fig. 9) |
| Mainnet runs (ETH/VERI) | 11/20 full, 8/20 partial, 1/20 failed | 20/20 full |
| Profitable targets, blocks 8M–9M | 7,793 of 105,161 (7.4%) | 4,419 of 105,161 (4.2%) |
| Estimated revenue, 158 days | 2,958.64 ETH · 440,749.02 USD | 662.32 ETH · 98,666.15 USD |
Why this is hard to fix. Set the default slippage limit low and trades fail under high transaction volume, so the DEX does not scale. Set it high and adversaries profit. Cryptographic defences such as commit-reveal or submarine commitments add rounds of interaction or trusted off-chain parts (Appendix C).
Measured
The authors ran a modified Parity client (mempool raised from 1,024 to 2,048 transactions) on AWS in Ireland, with a Python bot subscribed to pending transactions. The only victims were their own transactions, sent through the Uniswap UI and MetaMask to the smallest-liquidity market in the UI: ETH/VERI (0.01 ETH and 0.07 VERI, 3.50 USD in total).
| Taker · mean | Taker · std | Provider · mean | Provider · std | |
|---|---|---|---|---|
| $T_V$ broadcast duration | 0.45 s | 0.27 | 0.36 s | 0.29 |
| A find-strategy duration | 0.03 s | 0.00 | 0.03 s | 0.00 |
| A execute-strategy duration | 0.16 s | 0.60 | 0.04 s | 0.00 |
| $T_V$ duration in mempool | 35.84 s | 33.31 | 23.09 s | 10.52 |
| $T_{A1}$ duration in mempool | 35.88 s | 33.19 | 23.03 s | 10.52 |
| $T_{A2}$ duration in mempool | 48.87 s | 51.25 | 23.03 s | 10.52 |
| $T_{A3}$ duration in mempool | N/A | N/A | 23.03 s | 10.52 |
| A1 block relative position | 0.05 | 0.22 | 0.00 | 0.00 |
| A1 index relative position | −10.42 | 10.26 | −2.95 | 2.34 |
| A2 block relative position | 0.70 | 0.92 | 0.00 | 0.00 |
| A2 index relative position | 5.45 | 6.47 | 4.50 | 4.90 |
| A3 block relative position | N/A | N/A | 0.00 | 0.00 |
| A3 index relative position | N/A | N/A | 5.50 | 4.90 |
| Success | 11/20 | 20/20 | ||
| Partial success | 8/20 | 0/20 | ||
| Failure | 1/20 | 0/20 | ||
Victim: 0.001 ETH at 5 GWei (taker runs), 0.002 ETH at 2 GWei (provider runs). Clock difference between adversary and victim: 8.781 ms ± 6.189 ms. “Partial” means the front- and back-runs both succeeded but $T_{A2}$ was mined in a later block than $T_V$. The one failure had $T_V$ in the mempool for only 1.677 s.
| Miner | Empty | Gas price | Parity default | Unknown | Total |
|---|
Miners seem to switch among strategies. Four of the ten always follow a known strategy, and 0xb293…0347 is the only one that mined no empty blocks.
Mean 17.2 ± 10,520.1 GWei; median 10 GWei. The most common price, 20 GWei, was used by 23,759,990 transactions (12.5%).
Everything on this page comes from the paper's source. Where the paper disagrees with itself, or where our recomputation departs from it, both values are shown here rather than silently reconciled.
Reference
@inproceedings{zhou2021high,
title = {High-Frequency Trading on Decentralized On-Chain Exchanges},
author = {Zhou, Liyi and Qin, Kaihua and Ferreira Torres, Christof and
Le, Duc V. and Gervais, Arthur},
booktitle = {IEEE Symposium on Security and Privacy (S\&P)},
year = {2021}
}
The linked PDF is the arXiv version, arXiv:2009.14021v1.
Code and data. The paper contains no code or data availability statement and points to no public repository, so this page links none. The Uniswap figures come from the authors' crawl of a full archive Geth node; the mainnet experiments attacked only the authors' own transactions.