.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "api/gallery/contracts/payments/plot_01_king_of_the_ether.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_payments_plot_01_king_of_the_ether.py: King of the Ether: unchecked send and the pull-payment pattern (2016) ===================================================================== *King of the Ether* sold a throne. Each claimant paid more than the current price, the deposed king received the payment less a 1% fee, and the price rose by half. The contract paid with ``send``, which forwards only 2,300 gas and returns False on failure. In February 2016 some kings used Mist's contract wallets, whose code for receiving ether needed more than 2,300 gas; their payments failed, the contract ignored the False, crowned the next king, and kept the money. Checking the result only moves the problem: a king who *refuses* every payment then reigns forever, since nobody can be crowned without paying him. The lasting fix is the *pull-payment* pattern: record what each deposed king is owed and let each withdraw it, with as much gas as they like, in their own transaction. A failure then harms only the one who causes it. .. GENERATED FROM PYTHON SOURCE LINES 22-26 .. code-block:: Python import matplotlib.pyplot as plt import blockchainkit as bk .. GENERATED FROM PYTHON SOURCE LINES 27-29 A contract wallet becomes king, then is deposed ----------------------------------------------- .. GENERATED FROM PYTHON SOURCE LINES 29-60 .. code-block:: Python def reign(throne_code): world = bk.contracts.World() for account in ("owner", "alice", "bob"): world.fund(account, 10_000) throne = world.deploy("owner", throne_code, 100, name="throne") wallet = world.deploy("alice", bk.contracts.ContractWallet, name="alice's wallet") world.fund(wallet, 1_000) world.transact("alice", wallet, "forward", throne, "claim_throne", 100) deposed = world.transact("bob", throne, "claim_throne", value=1_000) if throne_code is bk.contracts.PullKingOfTheEther: world.transact("alice", wallet, "forward", throne, "withdraw", 0) return world, throne, wallet, deposed summary = {} for code in ( bk.contracts.KingOfTheEther, bk.contracts.CheckedKingOfTheEther, bk.contracts.PullKingOfTheEther, ): world, throne, wallet, deposed = reign(code) compensation = world.balance(wallet) - 900 summary[code.__name__] = (deposed.success, compensation, world.view(throne, "king")) print(f"{code.__name__:22s} Bob crowned: {deposed.success}, Alice compensated: {compensation}") assert summary["KingOfTheEther"][:2] == (True, 0) # Crowned, but Alice lost her 990. assert summary["CheckedKingOfTheEther"][0] is False # Nobody can dethrone her. assert summary["PullKingOfTheEther"][:2] == (True, 990) .. rst-class:: sphx-glr-script-out .. code-block:: none KingOfTheEther Bob crowned: True, Alice compensated: 0 CheckedKingOfTheEther Bob crowned: False, Alice compensated: 0 PullKingOfTheEther Bob crowned: True, Alice compensated: 990 .. GENERATED FROM PYTHON SOURCE LINES 61-63 A king who refuses payment -------------------------- .. GENERATED FROM PYTHON SOURCE LINES 63-87 .. code-block:: Python world = bk.contracts.World() world.fund("mallory", 1_000) world.fund("carol", 100_000) throne = world.deploy("owner", bk.contracts.CheckedKingOfTheEther, 100) usurper = world.deploy("mallory", bk.contracts.Usurper) world.transact("mallory", usurper, "claim", throne, value=100) offers = [150, 1_000, 10_000, 90_000] blocked = [not world.transact("carol", throne, "claim_throne", value=v).success for v in offers] assert all(blocked) and world.view(throne, "king") == usurper fig, ax = plt.subplots(figsize=(8, 4)) names = list(summary) ax.bar(names, [summary[n][1] for n in names], color=["#dc2626", "#d97706", "#16a34a"]) for i, name in enumerate(names): note = "Bob crowned" if summary[name][0] else "Bob refused" ax.text(i, 30, note, ha="center", color="white") ax.axhline(990, color="#64748b", linestyle=":", label="what Alice was owed") ax.set(ylabel="ether reaching the deposed king", title="Push, checked push, and pull") ax.legend() fig.tight_layout() plt.show() .. image-sg:: /api/gallery/contracts/payments/images/sphx_glr_plot_01_king_of_the_ether_001.png :alt: Push, checked push, and pull :srcset: /api/gallery/contracts/payments/images/sphx_glr_plot_01_king_of_the_ether_001.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 88-93 Exercise -------- In the unchecked version, where did Alice's 990 go? Read the throne's balance, and the call tree of Bob's claim with ``plot_call_tree``: which call failed, and with what error? .. rst-class:: sphx-glr-timing **Total running time of the script:** (0 minutes 0.038 seconds) .. _sphx_glr_download_api_gallery_contracts_payments_plot_01_king_of_the_ether.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/payments/plot_01_king_of_the_ether.ipynb :alt: Launch JupyterLite :width: 150 px .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: plot_01_king_of_the_ether.ipynb ` .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: plot_01_king_of_the_ether.py ` .. container:: sphx-glr-download sphx-glr-download-zip :download:`Download zipped: plot_01_king_of_the_ether.zip ` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery `_