.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "api/gallery/contracts/governance/plot_01_beanstalk_flash_loan.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_contracts_governance_plot_01_beanstalk_flash_loan.py: Governance attacks: Beanstalk's flash-loan vote (2022) ====================================================== A *flash loan* lends any amount without collateral, on one condition: it is repaid before the transaction ends, or the whole transaction, loan included, reverts. For the length of one transaction, anyone can be as rich as the lender. Beanstalk let token holders pass a proposal at once with an ``emergencyCommit``, given two thirds of the voting power and a day after the proposal was made. On 17 April 2022 an attacker who had proposed sending the treasury to itself the day before took flash loans worth about a billion dollars, turned them into voting power, voted, committed the proposal and repaid, netting about 80 million dollars. The vote passed because .. math:: \frac{\text{borrowed} + \text{own}}{\text{supply}} \ge \frac{2}{3} held for the duration of a single transaction. Counting votes at a *snapshot*, the balances at the end of the block before the proposal, defeats the attack: no flash loan can change the past. .. GENERATED FROM PYTHON SOURCE LINES 28-33 .. 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 34-36 A treasury, a lender, and a proposal ------------------------------------ .. GENERATED FROM PYTHON SOURCE LINES 36-70 .. code-block:: Python def stage(snapshot): world = bk.contracts.World() token = world.deploy("dao", bk.contracts.VotesToken, 1_000_000, name="token") governance = world.deploy("dao", bk.contracts.Governance, token, snapshot, name="governance") world.fund("dao", 10_000) world.transact("dao", governance, value=10_000) lender = world.deploy("bank", bk.contracts.FlashLender, token, name="lender") world.transact("dao", token, "transfer", lender, 900_000) attacker = world.deploy("attacker", bk.contracts.GovernanceAttacker, name="attack contract") world.advance() pid = world.transact("attacker", governance, "propose", attacker, 10_000).result world.advance(blocks=7_200, seconds=86_400) # A day later. receipt = world.transact("attacker", attacker, "attack", lender, governance, pid, 900_000) return world, token, lender, receipt world, token, lender, attack = stage(snapshot=False) print("current-balance voting:", attack.success, "attacker gains", world.balance("attacker")) assert attack.success and world.balance("attacker") == 10_000 assert world.view(token, "balance_of", lender) == 900_000 # The loan was repaid. safe_world, _, _, defended = stage(snapshot=True) print("snapshot voting:", defended.error) assert defended.error == "below the two-thirds majority" and safe_world.balance("attacker") == 0 fig, ax = plt.subplots(figsize=(10, 5)) plot_call_tree(attack, names=world.name, ax=ax) ax.set_title("One transaction: borrow, vote, commit, repay. " + ax.get_title()) fig.tight_layout() plt.show() .. image-sg:: /api/gallery/contracts/governance/images/sphx_glr_plot_01_beanstalk_flash_loan_001.png :alt: One transaction: borrow, vote, commit, repay. Call tree: 13 calls, 125,750 gas, succeeded :srcset: /api/gallery/contracts/governance/images/sphx_glr_plot_01_beanstalk_flash_loan_001.png :class: sphx-glr-single-img .. rst-class:: sphx-glr-script-out .. code-block:: none current-balance voting: True attacker gains 10000 snapshot voting: below the two-thirds majority .. GENERATED FROM PYTHON SOURCE LINES 71-76 Exercise -------- With snapshot voting, could the attacker still win by flash-borrowing in the block *before* proposing? What does that require of the lender, and why does a delay between proposal and vote also help? .. rst-class:: sphx-glr-timing **Total running time of the script:** (0 minutes 0.069 seconds) .. _sphx_glr_download_api_gallery_contracts_governance_plot_01_beanstalk_flash_loan.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/contracts/governance/plot_01_beanstalk_flash_loan.ipynb :alt: Launch JupyterLite :width: 150 px .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: plot_01_beanstalk_flash_loan.ipynb ` .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: plot_01_beanstalk_flash_loan.py ` .. container:: sphx-glr-download sphx-glr-download-zip :download:`Download zipped: plot_01_beanstalk_flash_loan.zip ` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery `_