Skip to content

Commit

Permalink
feat: gas limit multpliers
Browse files Browse the repository at this point in the history
  • Loading branch information
antazoey committed Aug 3, 2023
1 parent 26e9247 commit e4f14fb
Show file tree
Hide file tree
Showing 8 changed files with 98 additions and 10 deletions.
13 changes: 12 additions & 1 deletion docs/userguides/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,19 @@ You may use one of:
- `"auto"` - gas limit is estimated for each transaction
- `"max"` - the maximum block gas limit is used
- A number or numeric string, base 10 or 16 (e.g. `1234`, `"1234"`, `0x1234`, `"0x1234"`)
- An object with key `"auto"` for specifying an estimate-multiplier for transaction insurance

For the local network configuration, the default is `"max"`. Otherwise it is `"auto"`.
To use the auto-multiplier, make your config like this:

```yaml
ethereum:
mainnet:
gas_limit:
auto:
multiplier: 1.2 # Multiply 1.2 times the result of eth_estimateGas
```

For the local network configuration, the default is `"max"`. Otherwise, it is `"auto"`.

## Plugins

Expand Down
2 changes: 1 addition & 1 deletion src/ape/api/accounts.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ def call(
raise TransactionError("Transaction not prepared.")

# The conditions below should never reached but are here for mypy's sake.
# The `max_fee` was either set manaully or from `prepare_transaction()`.
# The `max_fee` was either set manually or from `prepare_transaction()`.
# The `gas_limit` was either set manually or from `prepare_transaction()`.
if max_fee is None:
raise TransactionError("`max_fee` failed to get set in transaction preparation.")
Expand Down
9 changes: 8 additions & 1 deletion src/ape/api/networks.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
SignatureError,
)
from ape.logging import logger
from ape.types import AddressType, CallTreeNode, ContractLog, GasLimit, RawAddress
from ape.types import AddressType, AutoGasLimit, CallTreeNode, ContractLog, GasLimit, RawAddress
from ape.utils import (
DEFAULT_TRANSACTION_ACCEPTANCE_TIMEOUT,
BaseInterfaceModel,
Expand Down Expand Up @@ -710,6 +710,13 @@ def _network_config(self) -> Dict:
def gas_limit(self) -> GasLimit:
return self._network_config.get("gas_limit", "auto")

@cached_property
def auto_gas_multiplier(self) -> float:
"""
The value to multiply estimated gas by for tx-insurance.
"""
return self.gas_limit.multiplier if isinstance(self.gas_limit, AutoGasLimit) else 1.0

@property
def chain_id(self) -> int:
"""
Expand Down
15 changes: 12 additions & 3 deletions src/ape/api/providers.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
from ape.logging import LogLevel, logger
from ape.types import (
AddressType,
AutoGasLimit,
BlockID,
CallTreeNode,
ContractCode,
Expand Down Expand Up @@ -813,9 +814,11 @@ def estimate_gas_cost(self, txn: TransactionAPI, **kwargs) -> int:
txn_dict["type"] = HexBytes(txn_dict["type"]).hex()

# NOTE: "auto" means to enter this method, so remove it from dict
if "gas" in txn_dict and txn_dict["gas"] == "auto":
if "gas" in txn_dict and (
txn_dict["gas"] == "auto" or isinstance(txn_dict["auto"], AutoGasLimit)
):
txn_dict.pop("gas")
# Also pop these, they are overriden by "auto"
# Also pop these, they are overridden by "auto"
txn_dict.pop("maxFeePerGas", None)
txn_dict.pop("maxPriorityFeePerGas", None)

Expand Down Expand Up @@ -1309,7 +1312,13 @@ def prepare_transaction(self, txn: TransactionAPI) -> TransactionAPI:
# else: Assume user specified the correct amount or txn will fail and waste gas

if txn.gas_limit is None:
txn.gas_limit = self.estimate_gas_cost(txn)
multiplier = self.network.auto_gas_multiplier
if multiplier != 1.0:
gas = min(int(self.estimate_gas_cost(txn) * multiplier), self.max_gas)
else:
gas = self.estimate_gas_cost(txn)

txn.gas_limit = gas

if txn.required_confirmations is None:
txn.required_confirmations = self.network.required_confirmations
Expand Down
3 changes: 2 additions & 1 deletion src/ape/api/transactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from ape.logging import logger
from ape.types import (
AddressType,
AutoGasLimit,
ContractLogContainer,
SourceTraceback,
TraceFrame,
Expand Down Expand Up @@ -68,7 +69,7 @@ def validate_gas_limit(cls, value):

value = cls.network_manager.active_provider.network.gas_limit

if value == "auto":
if value == "auto" or isinstance(value, AutoGasLimit):
return None # Delegate to `ProviderAPI.estimate_gas_cost`

elif value == "max":
Expand Down
20 changes: 19 additions & 1 deletion src/ape/types/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,25 @@
"""


GasLimit = Union[Literal["auto", "max"], int, str]
class AutoGasLimit(BaseModel):
"""
Additional settings for ``gas_limit: auto``.
"""

multiplier: float = 1.0
"""
A multiplier to estimated gas.
"""

@validator("multiplier", pre=True)
def validate_multiplier(cls, value):
if isinstance(value, str):
return float(value)

return value


GasLimit = Union[Literal["auto", "max"], int, str, AutoGasLimit]
"""
A value you can give to Ape for handling gas-limit calculations.
``"auto"`` refers to automatically figuring out the gas,
Expand Down
8 changes: 6 additions & 2 deletions src/ape_ethereum/ecosystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
from ape.logging import logger
from ape.types import (
AddressType,
AutoGasLimit,
CallTreeNode,
ContractLog,
GasLimit,
Expand Down Expand Up @@ -101,7 +102,10 @@ class Config:

@validator("gas_limit", pre=True, allow_reuse=True)
def validate_gas_limit(cls, value):
if value in ("auto", "max"):
if isinstance(value, dict) and "auto" in value:
return AutoGasLimit.parse_obj(value["auto"])

elif value in ("auto", "max") or isinstance(value, AutoGasLimit):
return value

elif isinstance(value, int):
Expand All @@ -110,7 +114,7 @@ def validate_gas_limit(cls, value):
elif isinstance(value, str) and value.isnumeric():
return int(value)

elif is_hex(value) and is_0x_prefixed(value):
elif isinstance(value, str) and is_hex(value) and is_0x_prefixed(value):
return to_int(HexBytes(value))

elif is_hex(value):
Expand Down
38 changes: 38 additions & 0 deletions tests/functional/test_accounts.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import ape
from ape.api import ImpersonatedAccount
from ape.exceptions import AccountsError, NetworkError, ProjectError, SignatureError
from ape.types import AutoGasLimit
from ape.types.signatures import recover_signer
from ape.utils.testing import DEFAULT_NUMBER_OF_TEST_ACCOUNTS
from ape_ethereum.ecosystem import ProxyType
Expand Down Expand Up @@ -499,3 +500,40 @@ def test_iter_test_accounts(test_accounts):
def test_declare(contract_container, sender):
receipt = sender.declare(contract_container)
assert not receipt.failed


@pytest.mark.parametrize(
"tx_type,params", [(0, ["gas_price"]), (2, ["max_fee", "max_priority_fee"])]
)
def test_prepare_transaction(sender, ethereum, tx_type, params):
# Create a test tx and estimate gas.
tx0 = ethereum.create_transaction(type=tx_type, gas="auto")
tx1 = ethereum.create_transaction(type=tx_type, gas=AutoGasLimit(multiplier=1.1))

tx0_gas = None
for tx in (tx0, tx1):
# Show tx doesn't have these by default.
assert tx.nonce is None
for param in params:
# Custom fields depending on type.
assert getattr(tx, param) is None

# Gas should NOT yet be estimated, as that happens closer to sending.
assert tx.gas_limit is None

# Sets fields.
tx = sender.prepare_transaction(tx)

# We expect these fields to have been set.
assert tx.nonce is not None
assert tx.gas_limit is not None # Gas was estimated (using eth_estimateGas).

if tx0_gas is None:
# Set tx0 gas for tx1 check.
tx0_gas = tx.gas_limit
else:
# Check that that multiplier causes higher gas limit
assert tx.gas_limit > tx0_gas

for param in params:
assert getattr(tx, param) is not None

0 comments on commit e4f14fb

Please sign in to comment.