.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "api/gallery/economics/mev/plot_02_sandwich_attacks.py" .. LINE NUMBERS ARE GIVEN BELOW. .. only:: html .. note:: :class: sphx-glr-download-link-note :ref:`Go to the end ` to download the full example code or to run this example in your browser via JupyterLite. .. rst-class:: sphx-glr-example-title .. _sphx_glr_api_gallery_economics_mev_plot_02_sandwich_attacks.py: Sandwich attacks on decentralized exchanges (Zhou et al. 2021) ============================================================== A swap on a constant-product pool waits in the public mempool before it is mined. An attacker who sees it can *sandwich* it: buy just before it, which raises the price the victim pays, and sell just after, at the price the victim's own purchase pushed up. The victim's *slippage limit*, the least output it accepts, is all that stops the attacker: the victim's swap reverts below it, so the attacker picks the most profitable front-run :math:`a` among those that keep .. math:: \text{out}_{\text{victim}}(a) \ge (1 - s)\, \text{out}_{\text{victim}}(0). For a large trade, that is the largest one allowed. Zhou et al. derived this optimal attack, showed how a looser tolerance hands more of the trade to the attacker, and measured sandwiches on Uniswap. .. GENERATED FROM PYTHON SOURCE LINES 23-27 .. code-block:: Python import matplotlib.pyplot as plt import blockchainkit as bk .. GENERATED FROM PYTHON SOURCE LINES 28-30 One sandwich ------------ .. GENERATED FROM PYTHON SOURCE LINES 30-59 .. code-block:: Python RESERVES = (10_000_000, 10_000_000) VICTIM = 100_000 attack = bk.economics.sandwich_attack(*RESERVES, VICTIM, slippage_bps=200) print( f"front-run {attack.front_run:,}; victim gets {attack.victim_out:,} " f"instead of {attack.victim_out_alone:,}; attacker profit {attack.profit:,}" ) assert attack.profit > 0 and attack.victim_loss > attack.profit # Fees take the difference. # The pool contract agrees with the formula. world = bk.contracts.World() tok = world.deploy("lp", bk.contracts.ERC20, 10**9, name="TOK") usd = world.deploy("lp", bk.contracts.ERC20, 10**9, name="USD") pool = world.deploy("lp", bk.economics.ConstantProductPool, usd, tok, name="pool") for who in ("lp", "victim", "attacker"): if who != "lp": world.transact("lp", usd, "transfer", who, 10**7) for token in (tok, usd): world.transact(who, token, "approve", pool, 10**9) world.transact("lp", pool, "add_liquidity", *RESERVES) minimum = attack.victim_out_alone * 9_800 // 10_000 bought = world.transact("attacker", pool, "swap", usd, attack.front_run, 0).result victim = world.transact("victim", pool, "swap", usd, VICTIM, minimum) sold = world.transact("attacker", pool, "swap", tok, bought, 0).result assert victim.result == attack.victim_out and sold - attack.front_run == attack.profit greedy = bk.economics.sandwich_profit(*RESERVES, VICTIM, attack.front_run + 10_000) assert greedy.victim_out < minimum # Any larger, and the victim's swap would revert. .. rst-class:: sphx-glr-script-out .. code-block:: none front-run 102,223; victim gets 96,740 instead of 98,715; attacker profit 1,402 .. GENERATED FROM PYTHON SOURCE LINES 60-62 Profit against the front-run, and against the tolerance ------------------------------------------------------- .. GENERATED FROM PYTHON SOURCE LINES 62-103 .. code-block:: Python fig, (left, right) = plt.subplots(1, 2, figsize=(12, 4.5)) sizes = range(0, 1_500_001, 25_000) for slippage, color in ((50, "#16a34a"), (200, "#2563eb"), (500, "#dc2626")): best = bk.economics.sandwich_attack(*RESERVES, VICTIM, slippage_bps=slippage) floor = best.victim_out_alone * (10_000 - slippage) // 10_000 feasible = [ a for a in sizes if bk.economics.sandwich_profit(*RESERVES, VICTIM, a).victim_out >= floor ] left.plot( feasible, [bk.economics.sandwich_profit(*RESERVES, VICTIM, a).profit for a in feasible], color=color, label=f"slippage {slippage / 100}%", ) left.plot(best.front_run, best.profit, "o", color=color) left.axhline(0, color="black", linewidth=0.8) left.set(xlabel="front-run size", ylabel="attacker profit") left.set_title("The best front-run is the largest the victim tolerates") left.legend() tolerances = [10, 25, 50, 100, 200, 300, 500, 800] results = [bk.economics.sandwich_attack(*RESERVES, VICTIM, slippage_bps=s) for s in tolerances] right.plot( [s / 100 for s in tolerances], [r.profit for r in results], "o-", label="attacker profit" ) right.plot( [s / 100 for s in tolerances], [r.victim_loss for r in results], "s-", label="victim loss" ) right.set(xlabel="victim's slippage tolerance (%)", ylabel="tokens") right.set_title("A loose tolerance invites the sandwich") right.legend() fig.tight_layout() # The victim moves the price by about 2%, more than the attacker's two fees of 0.3%, # so even a tight tolerance leaves a profit; a loose one multiplies it. assert results[0].profit > 0 and all( a.profit < b.profit for a, b in zip(results, results[1:], strict=False) ) plt.show() .. image-sg:: /api/gallery/economics/mev/images/sphx_glr_plot_02_sandwich_attacks_001.png :alt: The best front-run is the largest the victim tolerates, A loose tolerance invites the sandwich :srcset: /api/gallery/economics/mev/images/sphx_glr_plot_02_sandwich_attacks_001.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 104-110 Exercise -------- The victim could split its trade into ten swaps of 10,000. Assuming each is sandwiched with the same tolerance, does the attacker earn more or less in total? What about the 0.3% fee the attacker pays twice? A worked solution is in :doc:`/exercises/economics`. .. rst-class:: sphx-glr-timing **Total running time of the script:** (0 minutes 0.080 seconds) .. _sphx_glr_download_api_gallery_economics_mev_plot_02_sandwich_attacks.py: .. only:: html .. container:: sphx-glr-footer sphx-glr-footer-example .. container:: lite-badge .. image:: images/jupyterlite_badge_logo.svg :target: ../../../../lite/lab/index.html?path=api/gallery/economics/mev/plot_02_sandwich_attacks.ipynb :alt: Launch JupyterLite :width: 150 px .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: plot_02_sandwich_attacks.ipynb ` .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: plot_02_sandwich_attacks.py ` .. container:: sphx-glr-download sphx-glr-download-zip :download:`Download zipped: plot_02_sandwich_attacks.zip ` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery `_