From cf3c82435652a6af0668f889dd137ab7742dba8b Mon Sep 17 00:00:00 2001 From: Ben Hauser Date: Mon, 16 Aug 2021 20:14:26 +0000 Subject: [PATCH 1/5] feat: `max_fee` and `priority_fee` for defaults --- brownie/network/__init__.py | 11 +++++++++- brownie/network/account.py | 8 +++++++ brownie/network/main.py | 42 +++++++++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 1 deletion(-) diff --git a/brownie/network/__init__.py b/brownie/network/__init__.py index 8a184125f..ae0dce134 100644 --- a/brownie/network/__init__.py +++ b/brownie/network/__init__.py @@ -2,7 +2,16 @@ from .account import Accounts -from .main import connect, disconnect, gas_limit, gas_price, is_connected, show_active # NOQA 401 +from .main import ( # NOQA 401 + connect, + disconnect, + gas_limit, + gas_price, + is_connected, + max_fee, + priority_fee, + show_active, +) from .rpc import Rpc from .state import Chain, TxHistory from .web3 import web3 diff --git a/brownie/network/account.py b/brownie/network/account.py index 3fc82e147..5246b917f 100644 --- a/brownie/network/account.py +++ b/brownie/network/account.py @@ -702,7 +702,15 @@ def _make_transaction( if silent is None: silent = bool(CONFIG.mode == "test" or CONFIG.argv["silent"]) + if gas_price is None: + # if gas price is not explicitly set, load the default max fee and priority fee + if max_fee is None: + max_fee = CONFIG.active_network["settings"]["max_fee"] or None + if priority_fee is None: + priority_fee = CONFIG.active_network["settings"]["priority_fee"] or None + try: + # if max fee and priority fee are not set, use gas price if max_fee is None and priority_fee is None: gas_price, gas_strategy, gas_iter = self._gas_price(gas_price) else: diff --git a/brownie/network/main.py b/brownie/network/main.py index 2cdb81b27..dd7432ff4 100644 --- a/brownie/network/main.py +++ b/brownie/network/main.py @@ -139,3 +139,45 @@ def gas_buffer(*args: Tuple[float, None]) -> Union[float, None]: else: raise TypeError("Invalid gas buffer - must be given as a float, int or None") return CONFIG.active_network["settings"]["gas_buffer"] + + +def max_fee(*args: Tuple[Union[int, str, bool, None]]) -> Union[int, bool]: + """ + Gets and optionally sets the default max fee per gas. + + * If a Wei value is given, this will be the default max fee. + * If set to None or False, transactions will default to using gas price. + """ + if not is_connected(): + raise ConnectionError("Not connected to any network") + if args: + if args[0] in (None, False): + CONFIG.active_network["settings"]["max_fee"] = None + else: + try: + price = Wei(args[0]) + except ValueError: + raise TypeError(f"Invalid max fee '{args[0]}'") + CONFIG.active_network["settings"]["max_fee"] = price + return CONFIG.active_network["settings"]["max_fee"] + + +def priority_fee(*args: Tuple[Union[int, str, bool, None]]) -> Union[int, bool]: + """ + Gets and optionally sets the default max priority fee per gas. + + * If a Wei value is given, this will be the default max fee. + * If set to None or False, transactions will default to using gas price. + """ + if not is_connected(): + raise ConnectionError("Not connected to any network") + if args: + if args[0] is None: + CONFIG.active_network["settings"]["priority_fee"] = None + else: + try: + price = Wei(args[0]) + except ValueError: + raise TypeError(f"Invalid priority fee '{args[0]}'") + CONFIG.active_network["settings"]["priority_fee"] = price + return CONFIG.active_network["settings"]["priority_fee"] From ffc2f8a3e1a84bc5a20568a63537fd37e6f62cf3 Mon Sep 17 00:00:00 2001 From: Ben Hauser Date: Mon, 16 Aug 2021 20:14:51 +0000 Subject: [PATCH 2/5] chore: add `max_fee` and `priority_fee` to network settings --- brownie/data/default-config.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/brownie/data/default-config.yaml b/brownie/data/default-config.yaml index d474f1316..fb997062b 100644 --- a/brownie/data/default-config.yaml +++ b/brownie/data/default-config.yaml @@ -19,6 +19,8 @@ networks: gas_limit: max gas_buffer: 1 gas_price: 0 + max_fee: null + priority_fee: null reverting_tx_gas_limit: max default_contract_owner: true cmd_settings: null @@ -26,6 +28,8 @@ networks: gas_limit: auto gas_buffer: 1.1 gas_price: auto + max_fee: null + priority_fee: null reverting_tx_gas_limit: false default_contract_owner: false From e98eb79357761c61682289adae6cee9945632d00 Mon Sep 17 00:00:00 2001 From: Ben Hauser Date: Mon, 16 Aug 2021 21:44:39 +0000 Subject: [PATCH 3/5] chore: bump dependencies --- requirements-windows.txt | 14 +++++++------- requirements.txt | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/requirements-windows.txt b/requirements-windows.txt index 4b16eb3cd..16f317caf 100644 --- a/requirements-windows.txt +++ b/requirements-windows.txt @@ -45,7 +45,7 @@ chardet==4.0.0 # via # -r requirements.txt # aiohttp -charset-normalizer==2.0.3 +charset-normalizer==2.0.4 # via # -r requirements.txt # requests @@ -131,7 +131,7 @@ hexbytes==0.2.1 # eth-event # eth-rlp # web3 -hypothesis==6.14.3 +hypothesis==6.14.7 # via -r requirements.txt idna==3.2 # via @@ -225,7 +225,7 @@ pycryptodome==3.10.1 # vyper pygments-lexer-solidity==0.7.0 # via -r requirements.txt -pygments==2.9.0 +pygments==2.10.0 # via # -r requirements.txt # pygments-lexer-solidity @@ -263,7 +263,7 @@ pythx==1.6.1 # via -r requirements.txt pyyaml==5.4.1 # via -r requirements.txt -regex==2021.7.6 +regex==2021.8.3 # via # -r requirements.txt # black @@ -304,7 +304,7 @@ toml==0.10.2 # via # -r requirements.txt # pytest -tomli==1.0.4 +tomli==1.2.1 # via # -r requirements.txt # black @@ -312,7 +312,7 @@ toolz==0.11.1 # via # -r requirements.txt # cytoolz -tqdm==4.61.2 +tqdm==4.62.1 # via -r requirements.txt typing-extensions==3.10.0.0 # via @@ -334,7 +334,7 @@ wcwidth==0.2.5 # via # -r requirements.txt # prompt-toolkit -web3==5.22.0 +web3==5.23.0 # via -r requirements.txt websockets==9.1 # via diff --git a/requirements.txt b/requirements.txt index 74354d4e7..4bb625ef1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -28,7 +28,7 @@ certifi==2021.5.30 # via requests chardet==4.0.0 # via aiohttp -charset-normalizer==2.0.3 +charset-normalizer==2.0.4 # via requests click==8.0.1 # via black @@ -97,7 +97,7 @@ hexbytes==0.2.1 # eth-event # eth-rlp # web3 -hypothesis==6.14.3 +hypothesis==6.14.7 # via -r requirements.in idna==3.2 # via @@ -162,7 +162,7 @@ pycryptodome==3.10.1 # vyper pygments-lexer-solidity==0.7.0 # via -r requirements.in -pygments==2.9.0 +pygments==2.10.0 # via # -r requirements.in # pygments-lexer-solidity @@ -191,7 +191,7 @@ pythx==1.6.1 # via -r requirements.in pyyaml==5.4.1 # via -r requirements.in -regex==2021.7.6 +regex==2021.8.3 # via black requests==2.26.0 # via @@ -225,11 +225,11 @@ sortedcontainers==2.4.0 # via hypothesis toml==0.10.2 # via pytest -tomli==1.0.4 +tomli==1.2.1 # via black toolz==0.11.1 # via cytoolz -tqdm==4.61.2 +tqdm==4.62.1 # via -r requirements.in typing-extensions==3.10.0.0 # via aiohttp @@ -243,7 +243,7 @@ vyper==0.2.15 # via -r requirements.in wcwidth==0.2.5 # via prompt-toolkit -web3==5.22.0 +web3==5.23.0 # via -r requirements.in websockets==9.1 # via web3 From 9881cec5f0d14183e2f9642aed633b8fc110cd52 Mon Sep 17 00:00:00 2001 From: Ben Hauser Date: Mon, 16 Aug 2021 22:20:31 +0000 Subject: [PATCH 4/5] feat: set priority fee via `eth_maxPriorityFeePerGas` --- brownie/network/account.py | 3 +++ brownie/network/main.py | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/brownie/network/account.py b/brownie/network/account.py index 5246b917f..135e9bec0 100644 --- a/brownie/network/account.py +++ b/brownie/network/account.py @@ -709,6 +709,9 @@ def _make_transaction( if priority_fee is None: priority_fee = CONFIG.active_network["settings"]["priority_fee"] or None + if priority_fee == "auto": + priority_fee = Chain().priority_fee + try: # if max fee and priority fee are not set, use gas price if max_fee is None and priority_fee is None: diff --git a/brownie/network/main.py b/brownie/network/main.py index dd7432ff4..ff3d6f53c 100644 --- a/brownie/network/main.py +++ b/brownie/network/main.py @@ -166,14 +166,17 @@ def priority_fee(*args: Tuple[Union[int, str, bool, None]]) -> Union[int, bool]: """ Gets and optionally sets the default max priority fee per gas. + * If set to 'auto', the fee is set using `eth_maxPriorityFeePerGas`. * If a Wei value is given, this will be the default max fee. * If set to None or False, transactions will default to using gas price. """ if not is_connected(): raise ConnectionError("Not connected to any network") if args: - if args[0] is None: + if args[0] in (None, False): CONFIG.active_network["settings"]["priority_fee"] = None + elif args[0] == "auto": + CONFIG.active_network["settings"]["priority_fee"] = "auto" else: try: price = Wei(args[0]) From 11a30e01fad34b6183ff0b04e9af9cfe2dc19e22 Mon Sep 17 00:00:00 2001 From: Ben Hauser Date: Mon, 16 Aug 2021 20:38:47 +0000 Subject: [PATCH 5/5] docs: add info on `max_fee` and `priority_fee` --- docs/api-network.rst | 43 +++++++++++++++++-- docs/config.rst | 14 +++++++ docs/core-gas.rst | 98 +++++++++++++++++++++++++------------------- docs/toctree.rst | 2 +- 4 files changed, 110 insertions(+), 47 deletions(-) diff --git a/docs/api-network.rst b/docs/api-network.rst index f066fcd64..d07b53554 100644 --- a/docs/api-network.rst +++ b/docs/api-network.rst @@ -62,7 +62,7 @@ The ``main`` module contains methods for conncting to or disconnecting from the * If no argument is given, the current default is displayed. * If an integer value is given, this will be the default gas limit. - * If set to ``auto``, the gas limit is determined automatically via :meth:`web3.eth.estimate_gas `. + * If set to ``"auto"``, the gas limit is determined automatically via :meth:`web3.eth.estimate_gas `. Returns ``False`` if the gas limit is set automatically, or an ``int`` if it is set to a fixed value. @@ -99,7 +99,7 @@ The ``main`` module contains methods for conncting to or disconnecting from the Gets and optionally sets the default gas price. * If an integer value is given, this will be the default gas price. - * If set to ``auto``, the gas price is determined automatically via :attr:`web3.eth.gas_price `. + * If set to ``"auto"``, the gas price is determined automatically via :attr:`web3.eth.gas_price `. Returns ``False`` if the gas price is set automatically, or an ``int`` if it is set to a fixed value. @@ -115,6 +115,41 @@ The ``main`` module contains methods for conncting to or disconnecting from the >>> network.gas_price("auto") False +.. py:method:: main.max_fee(*args) + + Gets and optionally sets the default max fee per gas. + + * If an integer value is given, this will be the default max fee. + * If set to ``None`` or ``False``, transactions will instead default to using a legacy-style ``gas_price``. + + .. code-block:: python + + >>> from brownie import network + >>> network.max_fee() + None + >>> network.max_fee(10000000000) + 10000000000 + >>> network.max_fee("45 gwei") + 45000000000 + +.. py:method:: main.priority_fee(*args) + + Gets and optionally sets the default max priority fee per gas. + + * If an integer value is given, this will be the default priority fee. + * If set to ``"auto"``, the fee is determined automatically via :attr:`web3.eth.max_priority_fee `. + * If set to ``None`` or ``False``, transactions will instead default to using a legacy-style ``gas_price``. + + .. code-block:: python + + >>> from brownie import network + >>> network.priority_fee() + None + >>> network.priority_fee(4000000000) + 4000000000 + >>> network.priority_fee("2 gwei") + 2000000000 + ``brownie.network.account`` =========================== @@ -1690,7 +1725,7 @@ Multicall 2. Auto-deployment on development networks (on first use). 3. Uses ``multicall2`` key in network-config as pre-defined multicall contract address 4. Can specify/modify block number to make calls at particular block heights - 5. Calls which fail return ``None`` instad of causing all calls to fail + 5. Calls which fail return ``None`` instad of causing all calls to fail .. code-block:: python @@ -1725,7 +1760,7 @@ Multicall Attributes .. note:: ``Multicall`` relies on an instance of ``Multicall2`` being available for aggregating results. If you set the block_height before the ``Multicall2`` instance you are using was deployed a ``ContractNotFound`` error will be raised. - + .. code-block:: python >>> with brownie.multicall(block_identifier=12733683): diff --git a/docs/config.rst b/docs/config.rst index c5b5875f3..532f4f6c5 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -113,6 +113,8 @@ Networks gas_limit: max gas_buffer: 1 gas_price: 0 + max_fee: null + priority_fee: null reverting_tx_gas_limit: max default_contract_owner: true cmd_settings: @@ -157,6 +159,18 @@ Networks live default: ``auto`` + .. py:attribute:: max_fee + + The default max fee per gas for all transactions. If set to ``null``, transactions will default to legacy-style (using ``gas_price``). + + default: ``null`` + + .. py:attribute:: priority_fee + + The default max priority fee per gas for all transactions. If set to ``null``, transactions will default to legacy-style (using ``gas_price``). + + default: ``null`` + .. py:attribute:: default_contract_owner If ``false``, deployed contracts will not remember the account that they were created by. Every transaction will require a ``from`` kwarg. diff --git a/docs/core-gas.rst b/docs/core-gas.rst index 274626ed3..f1c70621f 100644 --- a/docs/core-gas.rst +++ b/docs/core-gas.rst @@ -1,27 +1,41 @@ -.. _core-accounts: +.. _core-gas: + +============================== +Setting Transaction Gas Prices +============================== -======================== Dynamic Fee Transactions ======================== If the network you are interacting with implements EIP-1559, you can use a better fee model when sending transactions. Instead of specifying ``gas_price``, you specify ``priority_fee`` and an optional ``max_fee``. -* ``priority_fee`` detrmines ``maxPriorityFeePerGas``, which is tipped to the miner. The recommended priority fee can be read from ``chain.priority_fee``. +* ``priority_fee`` determines ``maxPriorityFeePerGas``, which is tipped to the miner. The recommended priority fee can be read from ``chain.priority_fee``. * ``max_fee`` determines ``maxFeePerGas``, which includes the base fee, which is the same for all transactions in the block and is burned, and your priority fee. The current base fee can be read from ``chain.base_fee``. Brownie uses ``base_fee * 2 + priority_fee`` as ``max_fee`` if you only specify the priority fee. -.. code-block:: python + .. code-block:: python - >>> accounts[0].transfer(accounts[1], priority_fee='2 gwei') - Transaction sent: 0x090755e0b75648d12b1ada31fa5957a07aadcbe8a34b8f9af59098f1890d1063 - Max fee: 4.0 gwei Priority fee: 2.0 gwei Gas limit: 30000000 Nonce: 0 - Transaction confirmed Block: 1 Gas used: 21000 (0.07%) Gas price: 2.875 gwei + >>> accounts[0].transfer(accounts[1], priority_fee="2 gwei") + Transaction sent: 0x090755e0b75648d12b1ada31fa5957a07aadcbe8a34b8f9af59098f1890d1063 + Max fee: 4.0 gwei Priority fee: 2.0 gwei Gas limit: 30000000 Nonce: 0 + Transaction confirmed Block: 1 Gas used: 21000 (0.07%) Gas price: 2.875 gwei -Dynamic fee transactions do not support (and arguably don't need) gas strategies. The section below only applies to legacy transactions which use ``gas_price``. +Dynamic fee transactions do not support (and arguably don't need) gas strategies. The section below only applies to legacy transactions which use ``gas_price``. + +Setting Default Dynamic Fees +---------------------------- + +You can use :func:`network.priority_fee ` to set a default priority fee for all transactions: + + .. code-block:: python + + >>> from brownie.network import gas_price + >>> priority_fee("2 gwei") + +Setting the default to ``"auto"`` will dynamically determine the priority fee using :attr:`web3.eth.max_priority_fee `. Seting to ``None`` will return to legacy-style transactions. -============== Gas Strategies ============== @@ -34,37 +48,37 @@ Gas strategies come in three basic types: * **Time** strategies provide an initial price, and optionally replace pending transactions based on the amount of time that has passed since the first transaction was broadcast. Using a Gas Strategy -==================== +-------------------- To use a gas strategy, first import it from ``brownie.network.gas.strategies``: -.. code-block:: python + .. code-block:: python - >>> from brownie.network.gas.strategies import GasNowStrategy - >>> gas_strategy = GasNowStrategy("fast") + >>> from brownie.network.gas.strategies import GasNowStrategy + >>> gas_strategy = GasNowStrategy("fast") You can then provide the object in the ``gas_price`` field when making a transaction: -.. code-block:: python + .. code-block:: python - >>> accounts[0].transfer(accounts[1], "1 ether", gas_price=gas_strategy) + >>> accounts[0].transfer(accounts[1], "1 ether", gas_price=gas_strategy) When the strategy replaces a pending transaction, the returned :func:`TransactionReceipt ` object will be for the transaction that confirms. During :ref:`non-blocking transactions `, all pending transactions are available within the :func:`history ` object. As soon as one transaction confirms, the remaining dropped transactions are removed. Setting a Default Gas Strategy -============================== +------------------------------ You can use :func:`network.gas_price ` to set a gas strategy as the default for all transactions: -.. code-block:: python + .. code-block:: python - >>> from brownie.network import gas_price - >>> gas_price(gas_strategy) + >>> from brownie.network import gas_price + >>> gas_price(gas_strategy) Available Gas Strategies -======================== +------------------------ .. py:class:: brownie.network.gas.strategies.LinearScalingStrategy(initial_gas_price, max_gas_price, increment=1.125, time_duration=30) @@ -75,12 +89,12 @@ Available Gas Strategies * ``increment``: Multiplier applied to the previous gas price in order to determine the new gas price * ``time_duration``: Number of seconds between transactions - .. code-block:: python + .. code-block:: python - >>> from brownie.network.gas.strategies import LinearScalingStrategy - >>> gas_strategy = LinearScalingStrategy("10 gwei", "50 gwei", 1.1) + >>> from brownie.network.gas.strategies import LinearScalingStrategy + >>> gas_strategy = LinearScalingStrategy("10 gwei", "50 gwei", 1.1) - >>> accounts[0].transfer(accounts[1], "1 ether", gas_price=gas_strategy) + >>> accounts[0].transfer(accounts[1], "1 ether", gas_price=gas_strategy) .. py:class:: brownie.network.gas.strategies.ExponentialScalingStrategy(initial_gas_price, max_gas_price, time_duration=30) @@ -92,12 +106,12 @@ Available Gas Strategies * ``max_gas_price``: The maximum gas price to use * ``time_duration``: Number of seconds between transactions - .. code-block:: python + .. code-block:: python - >>> from brownie.network.gas.strategies import ExponentialScalingStrategy - >>> gas_strategy = ExponentialScalingStrategy("10 gwei", "50 gwei") + >>> from brownie.network.gas.strategies import ExponentialScalingStrategy + >>> gas_strategy = ExponentialScalingStrategy("10 gwei", "50 gwei") - >>> accounts[0].transfer(accounts[1], "1 ether", gas_price=gas_strategy) + >>> accounts[0].transfer(accounts[1], "1 ether", gas_price=gas_strategy) .. py:class:: brownie.network.gas.strategies.GasNowStrategy(speed="fast") @@ -105,12 +119,12 @@ Available Gas Strategies * ``speed``: The gas price to use based on the API call. Options are rapid, fast, standard and slow. - .. code-block:: python + .. code-block:: python - >>> from brownie.network.gas.strategies import GasNowStrategy - >>> gas_strategy = GasNowStrategy("fast") + >>> from brownie.network.gas.strategies import GasNowStrategy + >>> gas_strategy = GasNowStrategy("fast") - >>> accounts[0].transfer(accounts[1], "1 ether", gas_price=gas_strategy) + >>> accounts[0].transfer(accounts[1], "1 ether", gas_price=gas_strategy) .. py:class:: brownie.network.gas.strategies.GasNowScalingStrategy(initial_speed="standard", max_speed="rapid", increment=1.125, block_duration=2) @@ -121,12 +135,12 @@ Available Gas Strategies * ``increment``: A multiplier applied to the most recently used gas price in order to determine the new gas price. If the incremented value is less than or equal to the current ``max_speed`` rate, a new transaction is broadcasted. If the current rate for ``initial_speed`` is greater than the incremented rate, it is used instead. * ``block_duration``: The number of blocks to wait between broadcasting new transactions. - .. code-block:: python + .. code-block:: python - >>> from brownie.network.gas.strategies import GasNowScalingStrategy - >>> gas_strategy = GasNowScalingStrategy("fast", increment=1.2) + >>> from brownie.network.gas.strategies import GasNowScalingStrategy + >>> gas_strategy = GasNowScalingStrategy("fast", increment=1.2) - >>> accounts[0].transfer(accounts[1], "1 ether", gas_price=gas_strategy) + >>> accounts[0].transfer(accounts[1], "1 ether", gas_price=gas_strategy) .. py:class:: brownie.network.gas.strategies.GethMempoolStrategy(position=500, graphql_endpoint=None, block_duration=2) @@ -139,14 +153,14 @@ Available Gas Strategies * A position of 200 or less usually places a transaction within the mining block. * A position of 500 usually places a transaction within the 2nd pending block. - .. code-block:: python + .. code-block:: python - >>> from brownie.network.gas.strategies import GethMempoolStrategy - >>> gas_strategy = GethMempoolStrategy(200) + >>> from brownie.network.gas.strategies import GethMempoolStrategy + >>> gas_strategy = GethMempoolStrategy(200) - >>> accounts[0].transfer(accounts[1], "1 ether", gas_price=gas_strategy) + >>> accounts[0].transfer(accounts[1], "1 ether", gas_price=gas_strategy) Building your own Gas Strategy -============================== +------------------------------ To implement your own gas strategy you must subclass from one of the :ref:`gas strategy abstract base classes `. diff --git a/docs/toctree.rst b/docs/toctree.rst index 0918bb115..107094d17 100644 --- a/docs/toctree.rst +++ b/docs/toctree.rst @@ -28,10 +28,10 @@ Brownie core-accounts.rst core-contracts.rst + core-gas.rst core-chain.rst core-transactions.rst core-types.rst - core-gas.rst .. toctree::