{ "cells": [ { "cell_type": "markdown", "id": "narrow-stranger", "metadata": {}, "source": [ "Reproducing [Path-dependence problem with EIP-1559 and a possible solution to this problem](https://mtefagh.github.io/fee/) with different demand assumptions: either the demand rate is equal to the supply rate, or the demand rate is higher. When both rates are equal, the multiplicative rule with patient users does make basefee tend to zero. However, small deviations from equality between demand and supply mean basefee will settle to its equilibrium level, as other users will always fill the gap. With an additive rule, basefee does not tend to zero when demand and supply rates are equal.\n", "\n", "## Assumptions\n", "\n", "- Block target = 12.5M gas, block limit = 25M gas\n", "- All users send transactions of size 25000 gas, i.e., target = 500 txs, limit = 1000 txs.\n", "- User values (in Gwei) drawn from Pareto distribution `2 + self.rng.pareto(2) * 20`. No cost per time unit.\n", "- 100 users are drawn each round, with the number following a Poisson distribution of mean 100.\n", "- In some experiments, in expectation 5% of users are patient: wait at most 10 blocks for a block that is undertarget, otherwise send the transaction.\n", "- Other users are strategic 1559: set `max_fee` to their value and the premium to 1 Gwei if the previous block wasn't close to full (< 90% filled), otherwise set premium to min premium in the previous block + 0.1 Gwei.\n", "- Transaction pool keeps in memory only 500 transactions, resorted each step to keep highest fee paying transactions first.\n", "- Simulation runs for 20,000 blocks. Basefee updated with either a multiplicative rule, or an additive rule." ] }, { "cell_type": "code", "execution_count": 1, "id": "accepted-running", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "100.0\n" ] } ], "source": [ "%config InlineBackend.figure_format = 'svg'\n", "\n", "import os, sys\n", "sys.path.insert(1, os.path.realpath(os.path.pardir))\n", "\n", "from typing import Sequence, Dict\n", "\n", "from abm1559.utils import (\n", " constants,\n", " basefee_from_csv_history,\n", " get_basefee_bounds,\n", " flatten\n", ")\n", "constants[\"SIMPLE_TRANSACTION_GAS\"] = 125000\n", "demand_equal_target = constants[\"TARGET_GAS_USED\"] / constants[\"SIMPLE_TRANSACTION_GAS\"]\n", "print(demand_equal_target)\n", "\n", "from abm1559.txpool import TxPool\n", "\n", "from abm1559.users import (\n", " User1559,\n", " AffineUser,\n", " User\n", ")\n", "\n", "from abm1559.config import rng\n", "\n", "from abm1559.txs import Transaction, Tx1559, TxLegacy\n", "\n", "from abm1559.userpool import UserPool\n", "\n", "from abm1559.chain import (\n", " Chain,\n", " Block1559,\n", " Block\n", ")\n", "\n", "from abm1559.simulator import (\n", " spawn_fixed_heterogeneous_demand,\n", " update_basefee,\n", " generate_gbm,\n", ")\n", "\n", "import matplotlib.pyplot as plt\n", "import pandas as pd\n", "pd.set_option('display.max_rows', 50)\n", "import numpy as np\n", "import time\n", "import seaborn as sns\n", "from tqdm import tqdm" ] }, { "cell_type": "code", "execution_count": 2, "id": "figured-craft", "metadata": {}, "outputs": [], "source": [ "class StrategicUser(User1559):\n", " \n", " epsilon = 0.1 # how much the user overbids by\n", "\n", " def __init__(self, wakeup_block, **kwargs):\n", " super().__init__(wakeup_block, cost_per_unit = 0, **kwargs)\n", " self.value = (2 + self.rng.pareto(2) * 20) * (10 ** 9)\n", " self.transacted = False\n", "\n", " def decide_parameters(self, env):\n", " if env[\"min_premium\"] is None:\n", " min_premium = 1 * (10 ** 9)\n", " else:\n", " if env[\"close_to_full\"]:\n", " min_premium = env[\"min_premium\"] + self.epsilon * (10 ** 9)\n", " else:\n", " min_premium = 1e9\n", "\n", " gas_premium = min_premium\n", " max_fee = self.value\n", "\n", " return {\n", " \"max_fee\": max_fee, # in wei\n", " \"gas_premium\": gas_premium, # in wei\n", " \"start_block\": self.wakeup_block\n", " }\n", " \n", " def create_transaction(self, env):\n", " if self.transacted:\n", " return None\n", " \n", " tx = super().create_transaction(env)\n", " if not tx is None:\n", " tx.gas_used = constants[\"SIMPLE_TRANSACTION_GAS\"]\n", " self.transacted = True\n", " return tx\n", " \n", " def export(self):\n", " return {\n", " **super().export(),\n", " \"user_type\": \"strategic_user_1559\",\n", " }\n", "\n", " def __str__(self):\n", " return f\"1559 strategic affine user with value {self.value} and cost {self.cost_per_unit}\"" ] }, { "cell_type": "code", "execution_count": 3, "id": "dying-funeral", "metadata": {}, "outputs": [], "source": [ "class PatientUser(User1559):\n", " def __init__(self, wakeup_block, **kwargs):\n", " super().__init__(wakeup_block, cost_per_unit = 0, **kwargs)\n", " self.value = (2 + self.rng.pareto(2) * 20) * (10 ** 9)\n", " self.patience = 10\n", " self.transacted = False\n", " \n", " def update_patience(self):\n", " self.patience = self.patience - 1\n", " \n", " def create_transaction(self, env):\n", " if self.transacted:\n", " return None\n", " \n", " if self.patience == 0 or env[\"is_full\"]:\n", " tx = super().create_transaction(env)\n", " if tx is not None:\n", " tx.gas_used = constants[\"SIMPLE_TRANSACTION_GAS\"]\n", " self.transacted = True\n", " return tx\n", " else:\n", " self.update_patience()\n", " return None" ] }, { "cell_type": "code", "execution_count": 4, "id": "sitting-volume", "metadata": {}, "outputs": [], "source": [ "class PatientUserPool(UserPool):\n", " \n", " def decide_transactions(self, users: Sequence[User], env: Dict, query_all: bool = False) -> Sequence[Transaction]:\n", " txs = []\n", " for user in users:\n", " self.users[user.pub_key] = user\n", " \n", " self.users = { pub_key: user for pub_key, user in self.users.items() if user.wakeup_block >= env[\"current_block\"] - 20 }\n", " \n", " # We first ask non-patient users if they'd like to transact\n", " for user in self.users.values():\n", " if type(user) is StrategicUser:\n", " tx = user.transact(env)\n", " if not tx is None:\n", " txs.append(tx)\n", " \n", " # Simulate a block being built by a miner to determine the gas used by the next block\n", " max_tx_in_block = int(constants[\"MAX_GAS_EIP1559\"] / constants[\"SIMPLE_TRANSACTION_GAS\"])\n", "\n", " valid_txs = [tx for tx in txs if tx.is_valid(env)]\n", " rng.shuffle(valid_txs)\n", "\n", " sorted_valid_demand = sorted(\n", " valid_txs,\n", " key = lambda tx: -tx.tip(env)\n", " )\n", " selected_txs = sorted_valid_demand[0:max_tx_in_block]\n", " \n", " # If gas used is more than half of the block, patient users transact\n", " env[\"is_full\"] = len(selected_txs) >= max_tx_in_block // 2\n", " \n", " for user in self.users.values():\n", " if type(user) is PatientUser:\n", " tx = user.transact(env)\n", " if not tx is None:\n", " txs.append(tx)\n", " \n", " return txs" ] }, { "cell_type": "code", "execution_count": 5, "id": "marine-physiology", "metadata": {}, "outputs": [], "source": [ "MAX_TRANSACTIONS_IN_POOL = 500\n", "MIN_ACCEPTABLE_TIP = 1e9\n", "\n", "class MixedTxPool(TxPool):\n", " \n", " def add_txs(self, txs: Sequence[Transaction], env: dict) -> Sequence[Transaction]:\n", " for tx in txs:\n", " self.txs[tx.tx_hash] = tx\n", " \n", " if self.pool_length() > MAX_TRANSACTIONS_IN_POOL:\n", " sorted_txs = sorted(self.txs.values(), key = lambda tx: -tx.tip(env))\n", " self.empty_pool()\n", " self.add_txs(sorted_txs[0:MAX_TRANSACTIONS_IN_POOL], env)\n", " return sorted_txs[MAX_TRANSACTIONS_IN_POOL:]\n", " \n", " return []\n", " \n", " def select_transactions(self, env, user_pool=None, rng=rng) -> Sequence[Transaction]:\n", " # Miner side\n", " max_tx_in_block = int(constants[\"MAX_GAS_EIP1559\"] / constants[\"SIMPLE_TRANSACTION_GAS\"])\n", " \n", " valid_txs = [tx for tx in self.txs.values() if tx.is_valid(env) and tx.tip(env) >= MIN_ACCEPTABLE_TIP]\n", " rng.shuffle(valid_txs)\n", "\n", " sorted_valid_demand = sorted(\n", " valid_txs,\n", " key = lambda tx: -tx.tip(env)\n", " )\n", " selected_txs = sorted_valid_demand[0:max_tx_in_block]\n", " \n", " return selected_txs" ] }, { "cell_type": "code", "execution_count": 6, "id": "included-theory", "metadata": {}, "outputs": [], "source": [ "def update_basefee_additive(block: Block, basefee: int) -> int:\n", " \"\"\"\n", " Basefee update rule\n", "\n", " Args:\n", " block (Block): The previous block\n", " basefee (int): The current basefee\n", "\n", " Returns:\n", " int: The new basefee\n", " \"\"\"\n", "\n", " gas_used = sum([tx.gas_used for tx in block.txs])\n", " delta = gas_used - constants[\"TARGET_GAS_USED\"]\n", " new_basefee = max(0, basefee + (2 ** 30) * delta // constants[\"TARGET_GAS_USED\"] // constants[\"BASEFEE_MAX_CHANGE_DENOMINATOR\"])\n", " return new_basefee" ] }, { "cell_type": "code", "execution_count": 7, "id": "illegal-sociology", "metadata": {}, "outputs": [], "source": [ "def simulate(demand_scenario, shares_scenario, extra_metrics = None, rng = rng, additive_rule = False):\n", " # Instantiate a couple of things\n", " txpool = MixedTxPool()\n", " chain = Chain()\n", " metrics = []\n", " user_pool = PatientUserPool()\n", " start_time = time.time()\n", " block_target = int(constants[\"MAX_GAS_EIP1559\"] / constants[\"SIMPLE_TRANSACTION_GAS\"] / 2.0)\n", " \n", " # `env` is the \"environment\" of the simulation\n", " env = {\n", " \"basefee\": constants[\"INITIAL_BASEFEE\"],\n", " \"current_block\": None,\n", " \"min_premium\": 1 * (10 ** 9),\n", " \"is_full\": False,\n", " \"close_to_full\": False\n", " }\n", "\n", " for t in tqdm(range(len(demand_scenario))):\n", " # Sets current block\n", " env[\"current_block\"] = t\n", " \n", " # Reset the random number generator with new seed to generate users with same values across runs\n", " rng = np.random.default_rng(t)\n", " \n", " ### SIMULATION ###\n", "\n", " # We return some demand which on expectation yields `demand_scenario[t]` new users per round\n", " users = spawn_fixed_heterogeneous_demand(t, demand_scenario[t], shares_scenario[t], rng=rng)\n", "\n", " # Add new users to the pool\n", " # We query each new user with the current basefee value\n", " # Users either return a transaction or None if they prefer to balk\n", " decided_txs = user_pool.decide_transactions(users, env, query_all=True)\n", " \n", " patient_sent = len([tx for tx in decided_txs if type(user_pool.get_user(tx.sender)) is PatientUser])\n", "\n", " txpool.add_txs(decided_txs, env)\n", "\n", " # The best valid transactions are taken out of the pool for inclusion\n", " selected_txs = txpool.select_transactions(env)\n", " txpool.remove_txs([tx.tx_hash for tx in selected_txs])\n", "\n", " # We create a block with these transactions\n", " block = Block1559(\n", " txs = selected_txs, parent_hash = chain.current_head,\n", " height = t, basefee = env[\"basefee\"]\n", " )\n", " \n", " # Record the min premium in the block\n", " env[\"min_premium\"] = block.min_premium()\n", " # If block is 90% full, strategic users kick in\n", " env[\"close_to_full\"] = len(selected_txs) >= demand_equal_target * 2 * 0.9\n", " \n", " # The block is added to the chain\n", " chain.add_block(block)\n", " \n", " ### METRICS ###\n", " \n", " row_metrics = {\n", " \"block\": t,\n", " \"users\": len(users),\n", " \"decided_txs\": len(decided_txs),\n", " \"included_txs\": len(selected_txs),\n", " \"basefee\": env[\"basefee\"] / (10 ** 9), # to Gwei\n", " \"blk_min_premium\": block.min_premium() / (10 ** 9), # to Gwei\n", " \"blk_max_premium\": block.max_premium() / (10 ** 9), # to Gwei\n", " \"blk_min_tip\": block.min_tip(env) / (10 ** 9), # to Gwei\n", " \"blk_max_tip\": block.max_tip(env) / (10 ** 9), # to Gwei\n", " \"patient_sent\": patient_sent,\n", " }\n", " \n", " if not extra_metrics is None:\n", " row_metrics = {\n", " **row_metrics,\n", " **extra_metrics(env, users, user_pool, txpool),\n", " }\n", " \n", " metrics.append(row_metrics)\n", " \n", " # Finally, basefee is updated and a new round starts\n", " if additive_rule:\n", " env[\"basefee\"] = update_basefee_additive(block, env[\"basefee\"])\n", " else:\n", " env[\"basefee\"] = update_basefee(block, env[\"basefee\"])\n", "\n", " return (pd.DataFrame(metrics), user_pool, chain)" ] }, { "cell_type": "markdown", "id": "enclosed-entry", "metadata": {}, "source": [ "## Demand equal to target, no patient users, multiplicative rule" ] }, { "cell_type": "code", "execution_count": 8, "id": "imported-killing", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "100%|██████████| 20000/20000 [01:53<00:00, 176.89it/s]\n" ] } ], "source": [ "blocks = 20000\n", "\n", "# Number of new users per time step\n", "demand_scenario = [int(demand_equal_target) for t in range(blocks)]\n", "\n", "# Shares of new users per time step\n", "shares_scenario = [{\n", " StrategicUser: 1.0,\n", "} for t in range(blocks)]\n", "\n", "(df, user_pool, chain) = simulate(demand_scenario, shares_scenario)" ] }, { "cell_type": "code", "execution_count": 9, "id": "animated-links", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Text(0, 0.5, 'Gas price (Gwei)')" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", " \n", " \n", " \n", " \n", " 2021-04-07T13:42:29.988890\n", " image/svg+xml\n", " \n", " \n", " Matplotlib v3.3.3, https://matplotlib.org/\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "\n" ], "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "ax = df.plot(\"block\", [\"basefee\"])\n", "ax.set_xlabel(\"Block height\")\n", "ax.set_ylabel(\"Gas price (Gwei)\")" ] }, { "cell_type": "markdown", "id": "empirical-joshua", "metadata": {}, "source": [ "There is exactly as many users to fill the block to target, so basefee stays constant at 1 Gwei." ] }, { "cell_type": "markdown", "id": "going-moses", "metadata": {}, "source": [ "## Demand equal to target, 5% patient users, multiplicative rule" ] }, { "cell_type": "code", "execution_count": 10, "id": "forbidden-california", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "100%|██████████| 20000/20000 [01:56<00:00, 172.39it/s]\n" ] } ], "source": [ "rng = np.random.default_rng()\n", "\n", "# Number of new users per time step\n", "demand_scenario = [int(demand_equal_target) for t in range(blocks)]\n", "\n", "# Shares of new users per time step\n", "shares_scenario = []\n", "for t in range(blocks):\n", " share_patient = rng.binomial(demand_scenario[t], 0.05) / 100\n", " share_strat = 1 - share_patient\n", " shares_scenario += [{ PatientUser: share_patient, StrategicUser: share_strat }]\n", "\n", "(df2, user_pool, chain) = simulate(demand_scenario, shares_scenario)" ] }, { "cell_type": "code", "execution_count": 11, "id": "verbal-database", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Text(0, 0.5, 'Gas price (Gwei)')" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", " \n", " \n", " \n", " \n", " 2021-04-07T13:44:26.542097\n", " image/svg+xml\n", " \n", " \n", " Matplotlib v3.3.3, https://matplotlib.org/\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "\n" ], "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "ax = df2.plot(\"block\", [\"basefee\"])\n", "ax.set_xlabel(\"Block height\")\n", "ax.set_ylabel(\"Gas price (Gwei)\")" ] }, { "cell_type": "markdown", "id": "eligible-immunology", "metadata": {}, "source": [ "With patient users drawn randomly each round (following a binomial of size \"target number of transactions\" and probability 5%), basefee decreases to zero, underscoring the imbalance produced by the additive rule (7/8 * 9/8 < 1)." ] }, { "cell_type": "markdown", "id": "sticky-eclipse", "metadata": {}, "source": [ "## Demand larger than target, 5% patient users, multiplicative rule" ] }, { "cell_type": "code", "execution_count": 12, "id": "placed-break", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "100%|██████████| 20000/20000 [05:06<00:00, 65.24it/s]\n" ] } ], "source": [ "# Number of new users per time step\n", "demand_scenario = [int(demand_equal_target * 1.2) for t in range(blocks)]\n", "\n", "# Shares of new users per time step\n", "shares_scenario = []\n", "for t in range(blocks):\n", " share_patient = rng.binomial(demand_scenario[t], 0.05) / 100\n", " share_strat = 1 - share_patient\n", " shares_scenario += [{ PatientUser: share_patient, StrategicUser: share_strat }]\n", "\n", "(df3, user_pool, chain) = simulate(demand_scenario, shares_scenario)" ] }, { "cell_type": "code", "execution_count": 13, "id": "theoretical-talent", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Text(0, 0.5, 'Gas price (Gwei)')" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", " \n", " \n", " \n", " \n", " 2021-04-07T13:49:33.699017\n", " image/svg+xml\n", " \n", " \n", " Matplotlib v3.3.3, https://matplotlib.org/\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "\n" ], "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "ax = df3.plot(\"block\", [\"basefee\"])\n", "ax.set_xlabel(\"Block height\")\n", "ax.set_ylabel(\"Gas price (Gwei)\")" ] }, { "cell_type": "markdown", "id": "imperial-halloween", "metadata": {}, "source": [ "However, as soon as there is sufficiently more users than the target supply rate, basefee will stabilise at a constant level." ] }, { "cell_type": "markdown", "id": "infrared-boating", "metadata": {}, "source": [ "## Demand equal to target, 5% patient users, additive rule" ] }, { "cell_type": "code", "execution_count": 14, "id": "first-pavilion", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "100%|██████████| 20000/20000 [02:25<00:00, 137.56it/s]\n" ] } ], "source": [ "# Number of new users per time step\n", "demand_scenario = [int(demand_equal_target) for t in range(blocks)]\n", "\n", "# Shares of new users per time step\n", "shares_scenario = []\n", "for t in range(blocks):\n", " share_patient = rng.binomial(demand_scenario[t], 0.05) / 100\n", " share_strat = 1 - share_patient\n", " shares_scenario += [{ PatientUser: share_patient, StrategicUser: share_strat }]\n", "\n", "(df4, user_pool, chain) = simulate(demand_scenario, shares_scenario, additive_rule = True)" ] }, { "cell_type": "code", "execution_count": 15, "id": "perceived-peripheral", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Text(0, 0.5, 'Gas price (Gwei)')" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", " \n", " \n", " \n", " \n", " 2021-04-07T13:51:59.721686\n", " image/svg+xml\n", " \n", " \n", " Matplotlib v3.3.3, https://matplotlib.org/\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "\n" ], "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "ax = df4.plot(\"block\", [\"basefee\"])\n", "ax.set_xlabel(\"Block height\")\n", "ax.set_ylabel(\"Gas price (Gwei)\")" ] }, { "cell_type": "markdown", "id": "third-accessory", "metadata": {}, "source": [ "Moving back to the case where the demand rate is equal to the target supply, the additive rule removes the imbalance observed with the multiplicative rule." ] }, { "cell_type": "raw", "id": "beautiful-chicago", "metadata": {}, "source": [ "Patient users in additive and multiplicative rules" ] }, { "cell_type": "raw", "id": "exclusive-taste", "metadata": {}, "source": [] }, { "cell_type": "raw", "id": "heavy-affiliation", "metadata": {}, "source": [ "// References + footnotes\n", "\n", "// Authors\n", "let authorData = [\"barnabe\"];" ] }, { "cell_type": "raw", "id": "complicated-championship", "metadata": {}, "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.2" } }, "nbformat": 4, "nbformat_minor": 5 }