.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "api/gallery/economics/defi/plot_03_bzx_oracle_manipulation.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_defi_plot_03_bzx_oracle_manipulation.py: Flash loans and oracle manipulation: the bZx attacks (2020) =========================================================== A lender must know what its collateral is worth, and on a blockchain the easiest answer is the spot price of a decentralized exchange. In February 2020, attackers took flash loans, used them to move such a price within a single transaction, and borrowed from the bZx protocol against collateral it then overvalued, walking away with the difference. The mechanism fits in one inequality. Borrowing :math:`U` and buying the collateral token from a pool with reserves :math:`(x, y)` raises its spot price to the point where the tokens bought are valued at :math:`U (y + U) / y`. A lender requiring 150% collateral then lends .. math:: \frac{U (y + U)}{1.5\, y} > U \quad \text{whenever} \quad U > \frac{y}{2}, so the flash loan is repaid from the new loan, with a profit, and the lender keeps collateral worth far less than it lent. A price recorded *before* the transaction, the idea behind time-weighted averages, cannot be moved by a flash loan. .. GENERATED FROM PYTHON SOURCE LINES 27-32 .. code-block:: Python import matplotlib.pyplot as plt import blockchainkit as bk from blockchainkit.contracts.visualizers import plot_call_tree .. GENERATED FROM PYTHON SOURCE LINES 33-35 A pool, a lender, and a flash lender ------------------------------------ .. GENERATED FROM PYTHON SOURCE LINES 35-70 .. code-block:: Python def stage(delayed, loan=300_000): world = bk.contracts.World() tok = world.deploy("dex", bk.contracts.ERC20, 10**6, name="TOK") usd = world.deploy("bank", bk.contracts.ERC20, 10**7, name="USD") world.transact("bank", usd, "transfer", "dex", 100_000) pool = world.deploy("dex", bk.economics.ConstantProductPool, tok, usd, name="pool") for token in (tok, usd): world.transact("dex", token, "approve", pool, 10**6) world.transact("dex", pool, "add_liquidity", 100_000, 100_000) # TOK at 1 USD. lender = world.deploy("bank", bk.economics.OracleLender, tok, usd, pool, delayed, name="lender") world.transact("bank", usd, "transfer", lender, 2_000_000) flash = world.deploy("bank", bk.contracts.FlashLender, usd, name="flash lender") world.transact("bank", usd, "transfer", flash, 2_000_000) world.advance() attacker = world.deploy("eve", bk.economics.OracleAttacker, name="attack contract") receipt = world.transact("eve", attacker, "attack", flash, pool, lender, loan) return world, usd, lender, receipt world, usd, lender, attack = stage(delayed=False) profit = world.view(usd, "balance_of", "eve") print(f"spot-price oracle: {attack.success}, attacker keeps {profit:,} USD") assert attack.success and profit > 400_000 safe_world, _, _, defended = stage(delayed=True) print("price from before the block:", defended.error) assert defended.error == "undercollateralized" fig, ax = plt.subplots(figsize=(10, 6)) plot_call_tree(attack, names=world.name, ax=ax) ax.set_title("One transaction: borrow, pump, over-borrow, repay. " + ax.get_title()) fig.tight_layout() .. image-sg:: /api/gallery/economics/defi/images/sphx_glr_plot_03_bzx_oracle_manipulation_001.png :alt: One transaction: borrow, pump, over-borrow, repay. Call tree: 21 calls, 210,550 gas, succeeded :srcset: /api/gallery/economics/defi/images/sphx_glr_plot_03_bzx_oracle_manipulation_001.png :class: sphx-glr-single-img .. rst-class:: sphx-glr-script-out .. code-block:: none spot-price oracle: True, attacker keeps 497,573 USD price from before the block: undercollateralized .. GENERATED FROM PYTHON SOURCE LINES 71-73 Profit against the size of the flash loan ----------------------------------------- .. GENERATED FROM PYTHON SOURCE LINES 73-93 .. code-block:: Python loans = [25_000 * i for i in range(1, 17)] profits = [] for loan in loans: w, token, _, receipt = stage(delayed=False, loan=loan) profits.append(w.view(token, "balance_of", "eve") if receipt.success else 0) predicted = [max(0, u * (100_000 + u) / 150_000 - u) for u in loans] assert profits[0] == 0 and profits[-1] > 0 # Small loans do not pay for themselves. fig, ax = plt.subplots(figsize=(7, 4.5)) ax.plot(loans, profits, "o", color="#dc2626", label="simulated (0.3% fee)") ax.plot(loans, predicted, color="black", label="U (y + U) / 1.5 y - U, no fee") ax.axvline(50_000, color="#64748b", linestyle=":", label="U = y / 2") ax.set(xlabel="flash loan U (USD)", ylabel="attacker profit (USD)") ax.set_title("A deeper pool makes the attack costlier") ax.legend() fig.tight_layout() plt.show() .. image-sg:: /api/gallery/economics/defi/images/sphx_glr_plot_03_bzx_oracle_manipulation_002.png :alt: A deeper pool makes the attack costlier :srcset: /api/gallery/economics/defi/images/sphx_glr_plot_03_bzx_oracle_manipulation_002.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 94-99 Exercise -------- Double the pool's reserves. How large must the flash loan now be for the attack to pay, and how much would it have to cost to borrow for a profitable attack to remain? .. rst-class:: sphx-glr-timing **Total running time of the script:** (0 minutes 0.139 seconds) .. _sphx_glr_download_api_gallery_economics_defi_plot_03_bzx_oracle_manipulation.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/defi/plot_03_bzx_oracle_manipulation.ipynb :alt: Launch JupyterLite :width: 150 px .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: plot_03_bzx_oracle_manipulation.ipynb ` .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: plot_03_bzx_oracle_manipulation.py ` .. container:: sphx-glr-download sphx-glr-download-zip :download:`Download zipped: plot_03_bzx_oracle_manipulation.zip ` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery `_