← Duc V. Le
IEEE S&P 2021

High-Frequency Trading on Decentralized On-Chain Exchanges

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.

Liyi Zhou1, Kaihua Qin1, Christof Ferreira Torres2, Duc V. Le3, Arthur Gervais1

1Imperial College London, United Kingdom  ·  2University of Luxembourg, Luxembourg  ·  3Purdue University, United States

In three numbers

What a sandwich is worth

Average daily revenue
3,414 USD / day

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.

Blocks ordered by gas price
78.53%

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%.

Profit lost to 10 rival bots
−91.5%

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

A transparent exchange with a delay is a front-runner's dream

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.

Table I: example price slippages on an AMM DEX
AMM state 1AMM state 2
Liquidity $X$ ($x$)1001,000
Liquidity $Y$ ($y$)10100
Product ($k = xy$)1,000100,000
Purchase amount X110110
AMM price Y/X0.10000.10000.10000.1000
$\mathbb{E}[P]$ Y/X0.10100.11110.10010.1010
Expected slippage0.00100.01110.00010.0010
Slippage rate1.01%11.11%0.10%1.01%

Interactive · attack 1

Sandwich a trade: the liquidity-taker attack

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.

Market (pool at block 9M)
Victim's unexpected-slippage limit
Adversary's gas cost (both txs)

Adversary's revenue vs the victim's trade size

  • Revenue, before gas
  • Break-even (gas cost)
Table view
Front-run $T_{A1}$
–
Victim $T_V$ receives
–
Back-run $T_{A2}$
–
Profit after gas
–
Smallest profitable victim
–
for this market, limit and gas cost

Walk the block

Pool before the attack.

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)

Pull the rug, then put it back: the liquidity-provider attack

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.

Market (pool at block 9M)
Victim's unexpected-slippage limit

Provider's revenue and forgone commission vs the victim's trade size

  • Attack revenue, before gas
  • Commission forgone (passive LP)
  • 0.01 ETH gas
Table view
Withdraw before $T_V$
–
Victim pays
–
Attack revenue
–
A passive LP with the same share earns
–
Revenue turns positive at
–

Walk the block

Pool before the attack.

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

Can you land in the sandwich?

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.

Miner's ordering policy

A block, top = executed first (fees shown relative to the victim's)

Share of the 2,372,084 blocks by ordering policy (Table V)

Table view
StrategyBlocksRatio
Empty block55,5450.0234
Order per gas price1,862,8000.7853
Order per Parity default384,1500.1620
Unknown ordering69,5890.0293
Total2,372,0841.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

158 days of Uniswap, replayed

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.

Measure

  • Liquidity taker attacks taker
  • Liquidity provider attacks taker
Table view (Table III)
Profitable / total txsRevenue (ETH)Revenue (USD)
Liquidity taker attacks taker
ETH → Token878 / 25,20498.1514,621.41
Token → ETH5,657 / 602,85 *2,643.84393,852.46
Token → Token1,258 / 196,72 *216.6632,275.16
Total7,793 / 105,1612,958.64440,749.02
Liquidity provider attacks taker
ETH → Token444 / 25,20452.557,829.05
Token → ETH3,254 / 60,285520.6177,555.62
Token → Token721 / 19,67289.1613,281.49
Total4,419 / 105,161662.3298,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

When the bots compete

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%.

Competing adversaries

Victim trades 20 ETH → DAI; pending 10 s before it is mined

Table view
AdversariesProfit reductionExpected profit / attackerUnprofitable after pending
251.0%0.45 ETH (67 USD)27.7 s
581.4%0.17 ETH (25 USD)20.3 s
1091.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

Two ways to make a sandwich

Liquidity taker attacks takerLiquidity provider attacks taker
Transactions2: buy before, sell after3: remove liquidity, add liquidity, rebalancing swap
How the victim is hurtThe price is pushed toward the victim's slippage limitThe pool is thinned, so the victim's own trade moves the price further
Cost beyond gasTwo AMM fees on its own tradesForgoes 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 failed20/20 full
Profitable targets, blocks 8M–9M7,793 of 105,161 (7.4%)4,419 of 105,161 (4.2%)
Estimated revenue, 158 days2,958.64 ETH · 440,749.02 USD662.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

Results

Live attacks on Ethereum mainnet (Table II)

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 · meanTaker · stdProvider · meanProvider · std
$T_V$ broadcast duration0.45 s0.270.36 s0.29
A find-strategy duration0.03 s0.000.03 s0.00
A execute-strategy duration0.16 s0.600.04 s0.00
$T_V$ duration in mempool35.84 s33.3123.09 s10.52
$T_{A1}$ duration in mempool35.88 s33.1923.03 s10.52
$T_{A2}$ duration in mempool48.87 s51.2523.03 s10.52
$T_{A3}$ duration in mempoolN/AN/A23.03 s10.52
A1 block relative position0.050.220.000.00
A1 index relative position−10.4210.26−2.952.34
A2 block relative position0.700.920.000.00
A2 index relative position5.456.474.504.90
A3 block relative positionN/AN/A0.000.00
A3 index relative positionN/AN/A5.504.90
Success11/2020/20
Partial success8/200/20
Failure1/200/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.

Top 10 miners by blocks mined, blocks 6,627,917 to 9M (Table IV)

MinerEmptyGas priceParity defaultUnknownTotal

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.

Gas prices, 189,951,899 transactions over the same 388 days

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%).

Notes on the numbers

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.

  1. ETH/SAI pool size. §IV-A first gives the pool as 7,377.53 ETH and 521,468.62 SAI, then as 7,377.53 ETH and 1,099,040.91 SAI. Only the latter reproduces Fig. 5 (2,754.32 SAI for 18.59 ETH) and the stated rate of 148.97 USD per ETH, so the calculators use it.
  2. Taker success rate. §V-A says the attack succeeded in “19 out of 20” attempts; Table II counts 11 full and 8 partial successes. The two agree only if partial successes count.
  3. Provider mempool time. §V-B says $T_V$ stayed in the mempool “on average less than 10 seconds”; Table II reports a mean of 23.09 s.
  4. Table III denominators. Two taker rows print 602,85 and 196,72. The provider rows and the column total show these are 60,285 and 19,672.
  5. ETH/USD conversions. 0.01 ETH is given as 1.97 USD and 0.001 ETH as 0.2 USD. At the paper's own 148.97 USD per ETH these would be 1.49 and 0.15 USD.
  6. Provider minimum victim. The Fig. 9 caption says “adversary break even at 0.01 ETH tx fees”, yet its 43.93 ETH (SAI), and the appendix's 27.8 ETH (DAI), match where the model's revenue first turns positive (43.85 and 27.70 ETH). At a 0.01 ETH cost the model gives 45.84 and 29.70 ETH. Fig. 13's curves also begin where revenue turns positive. The model is real-valued, so the token's number of decimal places cannot affect it. The paper's own computation is sensitive to decimals: Fig. 9 reports that 17 instead of 18 decimals moves the SAI minimum from 43.93 to 44.54 ETH. The remaining ~0.1 ETH gap is therefore something this model cannot reproduce.
  7. Simulation scale. §VII reports 0.45 ETH expected profit per attacker with two attackers on a 20 ETH → DAI victim at 0.5% slippage, and Fig. 11's colour bar reaches 3 ETH. The §IV model gives a single attacker 0.030 ETH revenue for that same trade. The paper does not explain the gap, so the §VII numbers are shown as stated and not mixed with the calculators.
  8. Taker minimum on DAI. §VII gives 14.75 ETH; Fig. 7 gives about 16.07 ETH at 0.5%. §VII uses simulated gas costs rather than a flat 0.01 ETH, so these need not agree.
  9. Fig. 13, middle panel. The axis is labelled in percent (0.10%–0.60%), but the values are fractions of the pool (0.2658 = 26.58%, as stated in §IV-B).
  10. Congestion threshold. The introduction says break-even gets harder once the victim waits longer than the average block interval (13.5 s); §VII says more than 15 s.
  11. Number of exchanges. §V's experimental setup mentions “the 78 Uniswap exchanges on the Uniswap UI as of block 9M”; §V-C and the Table III caption use 79 exchanges.

Reference

Cite this work

@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.