diff --git a/src/ape_ethereum/ecosystem.py b/src/ape_ethereum/ecosystem.py index c78cb9ab73..f0c4c22fe8 100644 --- a/src/ape_ethereum/ecosystem.py +++ b/src/ape_ethereum/ecosystem.py @@ -1,5 +1,6 @@ import re from copy import deepcopy +from functools import cached_property from typing import Any, ClassVar, Dict, Iterator, List, Optional, Sequence, Tuple, Type, Union, cast from eth_abi import decode, encode @@ -17,13 +18,14 @@ ) from ethpm_types import ContractType from ethpm_types.abi import ABIType, ConstructorABI, EventABI, MethodABI -from pydantic import Field, field_validator, model_validator +from pydantic import Field, computed_field, field_validator, model_validator from pydantic_settings import SettingsConfigDict from ape.api import BlockAPI, EcosystemAPI, PluginConfig, ReceiptAPI, TransactionAPI from ape.api.networks import LOCAL_NETWORK_NAME from ape.contracts.base import ContractCall from ape.exceptions import ApeException, APINotImplementedError, ConversionError, DecodingError +from ape.managers.config import merge_configs from ape.types import ( AddressType, AutoGasLimit, @@ -164,11 +166,9 @@ class BaseEthereumConfig(PluginConfig): L2 plugins should use this as their config base-class. """ - DEFAULT_TRANSACTION_TYPE: ClassVar[TransactionType] = TransactionType.DYNAMIC + DEFAULT_TRANSACTION_TYPE: ClassVar[int] = TransactionType.DYNAMIC.value + NETWORKS: ClassVar[Dict[str, Tuple[int, int]]] = NETWORKS - local: NetworkConfig = create_local_network_config( - default_provider="test", default_transaction_type=DEFAULT_TRANSACTION_TYPE - ) default_network: str = LOCAL_NETWORK_NAME _forked_configs: Dict[str, ForkedNetworkConfig] = {} _custom_networks: Dict[str, NetworkConfig] = {} @@ -191,15 +191,27 @@ def load_network_configs(cls, values): default_fork_model = create_local_network_config( use_fork=True, default_transaction_type=cls.DEFAULT_TRANSACTION_TYPE ).model_dump(mode="json", by_alias=True) - cfg_forks[key] = ForkedNetworkConfig.model_validate({**default_fork_model, **obj}) + data = merge_configs(default_fork_model, obj) + cfg_forks[key] = ForkedNetworkConfig.model_validate(data) - elif key != LOCAL_NETWORK_NAME and key not in NETWORKS and isinstance(obj, dict): + elif key != LOCAL_NETWORK_NAME and key not in cls.NETWORKS and isinstance(obj, dict): # Custom network. - custom_networks[name] = NetworkConfig.model_validate(obj) + default_network_model = create_network_config( + default_transaction_type=cls.DEFAULT_TRANSACTION_TYPE + ).model_dump(mode="json", by_alias=True) + data = merge_configs(default_network_model, obj) + custom_networks[name] = NetworkConfig.model_validate(data) values["_forked_configs"] = {**cfg_forks, **values.get("_forked_configs", {})} return {**values, **custom_networks} + @computed_field # type: ignore[misc] + @cached_property + def local(self) -> NetworkConfig: + return create_local_network_config( + default_provider="test", default_transaction_type=self.DEFAULT_TRANSACTION_TYPE + ) + def __getattr__(self, key: str) -> Any: net_key = key.replace("-", "_") if net_key.endswith("_fork"): @@ -208,7 +220,7 @@ def __getattr__(self, key: str) -> Any: try: return super().__getattr__(key) except AttributeError: - return NetworkConfig() + return NetworkConfig(default_transaction_type=self.DEFAULT_TRANSACTION_TYPE) def __contains__(self, key: str) -> bool: net_key = key.replace("-", "_") @@ -223,17 +235,27 @@ def get(self, key: str, default: Optional[Any] = None) -> Any: if cfg := self._get_forked_config(net_key): return cfg - return super().get(key, default=default) + result: Any + if result := super().get(key, default=default): + return result + + # Handle weird base-class differences. + try: + return self.__getattr__(key) + except AttributeError: + return default def _get_forked_config(self, name: str) -> Optional[ForkedNetworkConfig]: live_key: str = name.replace("_fork", "") - if live_key in self._forked_configs: + if live_key in self._forked_configs and self._forked_configs[live_key]: return self._forked_configs[live_key] live_cfg: Any if live_cfg := self.get(live_key): if isinstance(live_cfg, NetworkConfig): - fork_cfg = create_local_network_config(use_fork=True) + fork_cfg = create_local_network_config( + use_fork=True, default_transaction_type=self.DEFAULT_TRANSACTION_TYPE + ) self._forked_configs[live_key] = fork_cfg return fork_cfg @@ -303,9 +325,9 @@ def default_transaction_type(self) -> TransactionType: for name in networks_to_check: network = self.get_network(name) - result: Optional[int] = network._network_config.get("default_transaction_type") - if result is not None: - return TransactionType(result) + ecosystem_default = network.config.DEFAULT_TRANSACTION_TYPE + result: int = network._network_config.get("default_transaction_type", ecosystem_default) + return TransactionType(result) return TransactionType(DEFAULT_TRANSACTION_TYPE) diff --git a/tests/functional/test_ecosystem.py b/tests/functional/test_ecosystem.py index 080fac334f..47f4840f25 100644 --- a/tests/functional/test_ecosystem.py +++ b/tests/functional/test_ecosystem.py @@ -1,5 +1,5 @@ import copy -from typing import Any, Dict +from typing import Any, ClassVar, Dict import pytest from eth_pydantic_types import HashBytes32, HexBytes @@ -10,7 +10,7 @@ from ape.exceptions import DecodingError, NetworkError, NetworkNotFoundError from ape.types import AddressType from ape.utils import DEFAULT_LOCAL_TRANSACTION_ACCEPTANCE_TIMEOUT -from ape_ethereum.ecosystem import BLUEPRINT_HEADER, Block +from ape_ethereum.ecosystem import BLUEPRINT_HEADER, BaseEthereumConfig, Block from ape_ethereum.transactions import DynamicFeeTransaction, StaticFeeTransaction, TransactionType LOG = { @@ -398,6 +398,20 @@ def test_default_transaction_type_configured_from_local_network( assert ethereum.default_transaction_type == TransactionType.STATIC +def test_default_transaction_type_changed_at_class_level(ethereum): + """ + Simulates an L2 plugin changing the default at the definition-level. + """ + + class Subconfig(BaseEthereumConfig): + DEFAULT_TRANSACTION_TYPE: ClassVar[int] = TransactionType.STATIC.value + + config = Subconfig() + assert config.local.default_transaction_type.value == 0 + assert config.mainnet.default_transaction_type.value == 0 + assert config.mainnet_fork.default_transaction_type.value == 0 + + @pytest.mark.parametrize("network_name", (LOCAL_NETWORK_NAME, "mainnet-fork", "mainnet_fork")) def test_gas_limit_local_networks(ethereum, network_name): network = ethereum.get_network(network_name)