Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add gas_buffer #739

Merged
merged 6 commits into from
Sep 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions brownie/data/default-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@ networks:
default: development
development:
gas_limit: max
gas_buffer: 1
gas_price: 0
reverting_tx_gas_limit: max
default_contract_owner: true
cmd_settings: null
live:
gas_limit: auto
gas_buffer: 1.1
gas_price: auto
reverting_tx_gas_limit: false
default_contract_owner: false
Expand Down
44 changes: 36 additions & 8 deletions brownie/network/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -321,14 +321,24 @@ def _pending_nonce(self) -> int:

return nonce

def _gas_limit(self, to: Optional["Accounts"], amount: int, data: Optional[str] = None) -> int:
def _gas_limit(
self,
to: Optional["Accounts"],
amount: int,
gas_buffer: Optional[float],
data: Optional[str] = None,
) -> int:
gas_limit = CONFIG.active_network["settings"]["gas_limit"]
if gas_limit == "max":
gas_limit = web3.eth.getBlock("latest")["gasLimit"]
CONFIG.active_network["settings"]["gas_limit"] = gas_limit
return Chain().block_gas_limit

if isinstance(gas_limit, bool) or gas_limit in (None, "auto"):
return self.estimate_gas(to, amount, data or "")
gas_buffer = gas_buffer or CONFIG.active_network["settings"]["gas_buffer"]
gas_limit = self.estimate_gas(to, amount, data or "")
if gas_buffer != 1:
gas_limit = Wei(gas_limit * gas_buffer)
return min(gas_limit, Chain().block_gas_limit)

return Wei(gas_limit)

def _gas_price(self) -> Wei:
Expand All @@ -350,6 +360,7 @@ def deploy(
*args: Tuple,
amount: int = 0,
gas_limit: Optional[int] = None,
gas_buffer: Optional[float] = None,
gas_price: Optional[int] = None,
nonce: Optional[int] = None,
required_confs: int = 1,
Expand All @@ -365,13 +376,16 @@ def deploy(
Kwargs:
amount: Amount of ether to send with transaction, in wei.
gas_limit: Gas limit of the transaction.
gas_buffer: Multiplier to apply to gas limit.
gas_price: Gas price of the transaction.
nonce: Nonce to use for the transaction.

Returns:
* Contract instance if the transaction confirms and the contract exists
* TransactionReceipt if the transaction is pending or reverts
"""
if gas_limit and gas_buffer:
raise ValueError("Cannot set gas_limit and gas_buffer together")

evm = contract._build["compiler"]["evm_version"]
if rpc.is_active() and not rpc.evm_compatible(evm):
Expand All @@ -389,7 +403,7 @@ def deploy(
"value": Wei(amount),
"nonce": nonce if nonce is not None else self._pending_nonce(),
"gasPrice": Wei(gas_price) or self._gas_price(),
"gas": Wei(gas_limit) or self._gas_limit(None, amount, data),
"gas": Wei(gas_limit) or self._gas_limit(None, amount, gas_buffer, data),
"data": HexBytes(data),
}
)
Expand Down Expand Up @@ -420,7 +434,12 @@ def deploy(
receipt,
self.deploy,
(contract, *args),
{"amount": amount, "gas_limit": gas_limit, "gas_price": gas_price},
{
"amount": amount,
"gas_limit": gas_limit,
"gas_buffer": gas_buffer,
"gas_price": gas_price,
},
),
daemon=True,
)
Expand Down Expand Up @@ -471,6 +490,7 @@ def transfer(
to: "Accounts" = None,
amount: int = 0,
gas_limit: Optional[int] = None,
gas_buffer: Optional[float] = None,
gas_price: Optional[int] = None,
data: str = None,
nonce: Optional[int] = None,
Expand All @@ -484,6 +504,7 @@ def transfer(
to: Account instance or address string to transfer to.
amount: Amount of ether to send, in wei.
gas_limit: Gas limit of the transaction.
gas_buffer: Multiplier to apply to gas limit.
gas_price: Gas price of the transaction.
nonce: Nonce to use for the transaction.
data: Hexstring of data to include in transaction.
Expand All @@ -492,6 +513,8 @@ def transfer(
Returns:
TransactionReceipt object
"""
if gas_limit and gas_buffer:
raise ValueError("Cannot set gas_limit and gas_buffer together")
if silent is None:
silent = bool(CONFIG.mode == "test" or CONFIG.argv["silent"])
with self._lock:
Expand All @@ -500,7 +523,7 @@ def transfer(
"value": Wei(amount),
"nonce": nonce if nonce is not None else self._pending_nonce(),
"gasPrice": Wei(gas_price) if gas_price is not None else self._gas_price(),
"gas": Wei(gas_limit) or self._gas_limit(to, amount, data),
"gas": Wei(gas_limit) or self._gas_limit(to, amount, gas_buffer, data),
"data": HexBytes(data or ""),
}
if to:
Expand All @@ -522,7 +545,12 @@ def transfer(
if rpc.is_active():
undo_thread = threading.Thread(
target=Chain()._add_to_undo_buffer,
args=(receipt, self.transfer, (to, amount, gas_limit, gas_price, data, None), {}),
args=(
receipt,
self.transfer,
(to, amount, gas_limit, gas_buffer, gas_price, data, None),
{},
),
daemon=True,
)
undo_thread.start()
Expand Down
2 changes: 2 additions & 0 deletions brownie/network/contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -1014,6 +1014,7 @@ def transact(self, *args: Tuple) -> TransactionReceiptType:
self._address,
tx["value"],
gas_limit=tx["gas"],
gas_buffer=tx["gas_buffer"],
gas_price=tx["gasPrice"],
nonce=tx["nonce"],
required_confs=tx["required_confs"],
Expand Down Expand Up @@ -1177,6 +1178,7 @@ def _get_tx(owner: Optional[AccountsType], args: Tuple) -> Tuple:
"from": owner,
"value": 0,
"gas": None,
"gas_buffer": None,
"gasPrice": None,
"nonce": None,
"required_confs": 1,
Expand Down
13 changes: 13 additions & 0 deletions brownie/network/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,3 +123,16 @@ def gas_price(*args: Tuple[Union[int, str, bool, None]]) -> Union[int, bool]:
raise TypeError(f"Invalid gas price '{args[0]}'")
CONFIG.active_network["settings"]["gas_price"] = price
return CONFIG.active_network["settings"]["gas_price"]


def gas_buffer(*args: Tuple[float, None]) -> Union[float, None]:
if not is_connected():
raise ConnectionError("Not connected to any network")
if args:
if args[0] is None:
CONFIG.active_network["settings"]["gas_buffer"] = 1
elif isinstance(args[0], (float, int)):
CONFIG.active_network["settings"]["gas_buffer"] = args[0]
else:
raise TypeError("Invalid gas buffer - must be given as a float, int or None")
return CONFIG.active_network["settings"]["gas_buffer"]
17 changes: 16 additions & 1 deletion brownie/network/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

from brownie._config import CONFIG, _get_data_folder
from brownie._singleton import _Singleton
from brownie.convert import Wei
from brownie.exceptions import BrownieEnvironmentError, RPCRequestError
from brownie.project.build import DEPLOYMENT_KEYS
from brownie.utils.sql import Cursor
Expand Down Expand Up @@ -123,6 +124,8 @@ def __init__(self) -> None:
self._undo_buffer: List = []
self._redo_buffer: List = []
self._chainid: Optional[int] = None
self._block_gas_time: int = -1
self._block_gas_limit: int = 0

def __repr__(self) -> str:
try:
Expand Down Expand Up @@ -156,7 +159,11 @@ def __getitem__(self, block_number: int) -> BlockData:
raise TypeError("Block height must be given as an integer")
if block_number < 0:
block_number = web3.eth.blockNumber + 1 + block_number
return web3.eth.getBlock(block_number)
block = web3.eth.getBlock(block_number)
if block["timestamp"] > self._block_gas_time:
self._block_gas_limit = block["gasLimit"]
self._block_gas_time = block["timestamp"]
return block

@property
def height(self) -> int:
Expand All @@ -168,6 +175,14 @@ def id(self) -> int:
self._chainid = web3.eth.chainId
return self._chainid

@property
def block_gas_limit(self) -> Wei:
if time.time() > self._block_gas_time + 3600:
block = web3.eth.getBlock("latest")
self._block_gas_limit = block["gasLimit"]
self._block_gas_time = block["timestamp"]
return Wei(self._block_gas_limit)

def _request(self, method: str, args: List) -> int:
try:
response = web3.provider.make_request(method, args) # type: ignore
Expand Down
32 changes: 26 additions & 6 deletions docs/api-network.rst
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,24 @@ The ``main`` module contains methods for conncting to or disconnecting from the
>>> network.gas_limit("auto")
False

.. py:method:: main.gas_buffer(*args)

Gets and optionally sets the default gas buffer.

* If no argument is given, the current default is displayed.
* If an integer or float value is given, this will be the default gas buffer.
* If ``None`` is given, the gas buffer is set to ``1`` (disabled).

.. code-block:: python

>>> from brownie import network
>>> network.gas_buffer()
1.1
>>> network.gas_buffer(1.25)
1.25
>>> network.gas_buffer(None)
1

.. py:method:: main.gas_price(*args)

Gets and optionally sets the default gas price.
Expand Down Expand Up @@ -287,9 +305,10 @@ Account Methods
* ``contract``: A :func:`ContractContainer <brownie.network.contract.ContractContainer>` instance of the contract to be deployed.
* ``*args``: Contract constructor arguments.
* ``amount``: Amount of ether to send with the transaction. The given value is converted to :func:`Wei <brownie.convert.datatypes.Wei>`.
* ``gas_limit``: Gas limit for the transaction. The given value is converted to :func:`Wei <brownie.convert.datatypes.Wei>`. If none is given, the price is set using ``eth_estimateGas``.
* ``gas_price``: Gas price for the transaction. The given value is converted to :func:`Wei <brownie.convert.datatypes.Wei>`. If none is given, the price is set using ``eth_gasPrice``.
* ``nonce``: Nonce for the transaction. If none is given, the nonce is set using ``eth_getTransactionCount`` while also considering any pending transactions of the Account.
* ``gas_limit``: Gas limit for the transaction. The given value is converted to :func:`Wei <brownie.convert.datatypes.Wei>`. If none is given, the price is set using :meth:`web3.eth.estimateGas <web3.eth.Eth.estimateGas>`.
* ``gas_buffer``: A multiplier applied to :meth:`web3.eth.estimateGas <web3.eth.Eth.estimateGas>` when setting gas limit automatically. ``gas_limit`` and ``gas_buffer`` cannot be given at the same time.
* ``gas_price``: Gas price for the transaction. The given value is converted to :func:`Wei <brownie.convert.datatypes.Wei>`. If none is given, the price is set using :attr:`web3.eth.gasPrice <web3.eth.Eth.gasPrice>`.
* ``nonce``: Nonce for the transaction. If none is given, the nonce is set using :meth:`web3.eth.getTransactionCount <web3.eth.Eth.getTransactionCount>` while also considering any pending transactions of the Account.
* ``required_confs``: The required :attr:`confirmations<TransactionReceipt.confirmations>` before the :func:`TransactionReceipt <brownie.network.transaction.TransactionReceipt>` is processed. If none is given, defaults to 1 confirmation. If 0 is given, immediately returns a pending :func:`TransactionReceipt <brownie.network.transaction.TransactionReceipt>` instead of a :func:`Contract <brownie.network.contract.Contract>` instance, while waiting for a confirmation in a separate thread.

Returns a :func:`Contract <brownie.network.contract.Contract>` instance upon success. If the transaction reverts or you do not wait for a confirmation, a :func:`TransactionReceipt <brownie.network.transaction.TransactionReceipt>` is returned instead.
Expand Down Expand Up @@ -333,10 +352,11 @@ Account Methods

* ``to``: Recipient address. Can be an :func:`Account <brownie.network.account.Account>` instance or string.
* ``amount``: Amount of ether to send. The given value is converted to :func:`Wei <brownie.convert.datatypes.Wei>`.
* ``gas_limit``: Gas limit for the transaction. The given value is converted to :func:`Wei <brownie.convert.datatypes.Wei>`. If none is given, the price is set using ``eth_estimateGas``.
* ``gas_price``: Gas price for the transaction. The given value is converted to :func:`Wei <brownie.convert.datatypes.Wei>`. If none is given, the price is set using ``eth_gasPrice``.
* ``gas_limit``: Gas limit for the transaction. The given value is converted to :func:`Wei <brownie.convert.datatypes.Wei>`. If none is given, the price is set using :meth:`web3.eth.estimateGas <web3.eth.Eth.estimateGas>`.
* ``gas_buffer``: A multiplier applied to :meth:`web3.eth.estimateGas <web3.eth.Eth.estimateGas>` when setting gas limit automatically. ``gas_limit`` and ``gas_buffer`` cannot be given at the same time.
* ``gas_price``: Gas price for the transaction. The given value is converted to :func:`Wei <brownie.convert.datatypes.Wei>`. If none is given, the price is set using :attr:`web3.eth.gasPrice <web3.eth.Eth.gasPrice>`.
* ``data``: Transaction data hexstring.
* ``nonce``: Nonce for the transaction. If none is given, the nonce is set using ``eth_getTransactionCount`` while also considering any pending transactions of the Account..
* ``nonce``: Nonce for the transaction. If none is given, the nonce is set using :meth:`web3.eth.getTransactionCount <web3.eth.Eth.getTransactionCount>` while also considering any pending transactions of the Account.
* ``required_confs``: The required :attr:`confirmations<TransactionReceipt.confirmations>` before the :func:`TransactionReceipt <brownie.network.transaction.TransactionReceipt>` is processed. If none is given, defaults to 1 confirmation. If 0 is given, immediately returns a pending :func:`TransactionReceipt <brownie.network.transaction.TransactionReceipt>`, while waiting for a confirmation in a separate thread.
* ``silent``: Toggles console verbosity. If ``True`` is given, suppresses all console output for this transaction.

Expand Down
25 changes: 17 additions & 8 deletions docs/config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,10 @@ Networks

networks:
development:
gas_limit: 6721975
gas_limit: max
gas_buffer: 1
gas_price: 0
reverting_tx_gas_limit: 6721975
reverting_tx_gas_limit: max
default_contract_owner: true
cmd_settings:
port: 8545
Expand All @@ -111,19 +112,27 @@ Networks

Default settings for development and live environments.

.. py:attribute:: gas_price
.. py:attribute:: gas_limit

The default gas price for all transactions. If set to ``auto`` the gas price is determined using ``web3.eth.gasPrice``.
The default gas limit for all transactions. If set to ``auto`` the gas limit is determined using ``web3.eth.estimateGas``. If set to ``max``, the block gas limit is used.

development default: ``0``
development default: ``max``

live default: ``auto``

.. py:attribute:: gas_limit
.. py:attribute:: gas_buffer

The default gas limit for all transactions. If set to ``auto`` the gas limit is determined using ``web3.eth.estimateGas``. If set to ``max``, the block gas limit is used.
A modifier applied to ``web3.eth.estimateGas`` when determining gas price automatically.

development default: ``max``
development default: ``1``

live default: ``1.1``

.. py:attribute:: gas_price

The default gas price for all transactions. If set to ``auto`` the gas price is determined using ``web3.eth.gasPrice``.

development default: ``0``

live default: ``auto``

Expand Down
1 change: 1 addition & 0 deletions docs/core-contracts.rst
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ When executing a transaction to a contract, you can optionally include a :py:cla

* ``from``: the :func:`Account <brownie.network.account.Account>` that the transaction it sent from. If not given, the transaction is sent from the account that deployed the contract.
* ``gas_limit``: The amount of gas provided for transaction execution, in wei. If not given, the gas limit is determined using :meth:`web3.eth.estimateGas <web3.eth.Eth.estimateGas>`.
* ``gas_buffer``: A multiplier applied to :meth:`web3.eth.estimateGas <web3.eth.Eth.estimateGas>` when setting gas limit automatically. ``gas_limit`` and ``gas_buffer`` cannot be given at the same time.
* ``gas_price``: The gas price for the transaction, in wei. If not given, the gas price is set according to :attr:`web3.eth.gasPrice <web3.eth.Eth.gasPrice>`.
* ``amount``: The amount of Ether to include with the transaction, in wei.
* ``nonce``: The nonce for the transaction. If not given, the nonce is set according to :meth:`web3.eth.getTransactionCount <web3.eth.Eth.getTransactionCount>` while taking pending transactions from the sender into account.
Expand Down
23 changes: 19 additions & 4 deletions tests/network/account/test_account_transfer.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,12 +139,22 @@ def test_gas_limit_manual(accounts):
assert tx.gas_used == 21000


@pytest.mark.parametrize("auto", (True, False, None, "auto"))
def test_gas_limit_automatic(accounts, config, auto):
def test_gas_buffer_manual(accounts, config):
"""gas limit is set correctly when specified in the call"""
config.active_network["settings"]["gas_limit"] = None
tx = accounts[0].transfer(accounts[1], 1000, gas_buffer=1.337)
assert tx.gas_limit == int(21000 * 1.337)
assert tx.gas_used == 21000


@pytest.mark.parametrize("gas_limit", (True, False, None, "auto"))
@pytest.mark.parametrize("gas_buffer", (1, 1.25))
def test_gas_limit_automatic(accounts, config, gas_limit, gas_buffer):
"""gas limit is set correctly using web3.eth.estimateGas"""
config.active_network["settings"]["gas_limit"] = auto
config.active_network["settings"]["gas_limit"] = gas_limit
config.active_network["settings"]["gas_buffer"] = gas_buffer
tx = accounts[0].transfer(accounts[1], 1000)
assert tx.gas_limit == 21000
assert tx.gas_limit == int(21000 * gas_buffer)


def test_gas_limit_config(accounts, config):
Expand Down Expand Up @@ -197,3 +207,8 @@ def test_deploy_via_transfer(accounts, web3):
tx = accounts[0].transfer(data=bytecode)
assert tx.contract_name == "UnknownContract"
assert web3.eth.getCode(tx.contract_address)


def test_gas_limit_and_buffer(accounts):
with pytest.raises(ValueError):
accounts[0].transfer(accounts[1], 1000, gas_limit=21000, gas_buffer=1.3)
15 changes: 11 additions & 4 deletions tests/network/contract/test_contracttx.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,12 +126,14 @@ def test_gas_limit_manual(tester, accounts):
assert tx.gas_limit == 100000


@pytest.mark.parametrize("auto", (True, False, None, "auto"))
def test_gas_limit_automatic(tester, accounts, config, auto):
@pytest.mark.parametrize("gas_limit", (True, False, None, "auto"))
@pytest.mark.parametrize("gas_buffer", (1, 1.25))
def test_gas_limit_automatic(tester, accounts, config, gas_limit, gas_buffer):
"""gas limit is set correctly using web3.eth.estimateGas"""
config.active_network["settings"]["gas_limit"] = auto
config.active_network["settings"]["gas_limit"] = gas_limit
config.active_network["settings"]["gas_buffer"] = gas_buffer
tx = tester.doNothing({"from": accounts[0]})
assert tx.gas_limit == tx.gas_used
assert int(tx.gas_used * gas_buffer) == tx.gas_limit


def test_gas_limit_config(tester, accounts, config):
Expand Down Expand Up @@ -175,3 +177,8 @@ def test_tuples(tester, accounts):
assert tx.status == 1
assert tx.return_value == value
assert tx.return_value["nested"]["a"] == "yesyesyes"


def test_gas_limit_and_buffer(tester, accounts):
with pytest.raises(ValueError):
tester.doNothing({"from": accounts[0], "gas_limit": 100000, "gas_buffer": 1.2})