From 151548bebd61d647bfd82656f78cc6119f8d251a Mon Sep 17 00:00:00 2001 From: antazoey Date: Fri, 23 Aug 2024 11:01:22 -0500 Subject: [PATCH] fix: issue where custom-networks defaulted to forked-providers when they were not forked networks. (#2239) --- src/ape/api/networks.py | 22 ++++++++++++--- tests/functional/test_cli.py | 6 +++- tests/functional/test_network_api.py | 41 ++++++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 5 deletions(-) diff --git a/src/ape/api/networks.py b/src/ape/api/networks.py index c1efd3c286..52bde2ff03 100644 --- a/src/ape/api/networks.py +++ b/src/ape/api/networks.py @@ -1017,15 +1017,26 @@ def providers(self): # -> dict[str, Partial[ProviderAPI]] from ape.plugins._utils import clean_plugin_name providers = {} - for _, plugin_tuple in self.plugin_manager.providers: + for _, plugin_tuple in self._get_plugin_providers(): ecosystem_name, network_name, provider_class = plugin_tuple provider_name = clean_plugin_name(provider_class.__module__.split(".")[0]) - + is_custom_with_config = self._is_custom and self.default_provider_name == provider_name # NOTE: Custom networks that are NOT from config must work with any provider. + # Also, ensure we are only adding forked providers for forked networks and + # non-forking providers for non-forked networks. For custom networks, it + # can be trickier (see last condition). + # TODO: In 0.9, add a better way for class-level ForkedProviders to define + # themselves as "Fork" providers. if ( self.is_adhoc or (self.ecosystem.name == ecosystem_name and self.name == network_name) - or (self._is_custom and self.default_provider_name == provider_name) + or ( + is_custom_with_config + and ( + (self.is_fork and "Fork" in provider_class.__name__) + or (not self.is_fork and "Fork" not in provider_class.__name__) + ) + ) ): # NOTE: Lazily load provider config providers[provider_name] = partial( @@ -1037,6 +1048,10 @@ def providers(self): # -> dict[str, Partial[ProviderAPI]] return providers + def _get_plugin_providers(self): + # NOTE: Abstracted for testing purposes. + return self.plugin_manager.providers + def get_provider( self, provider_name: Optional[str] = None, @@ -1080,7 +1095,6 @@ def get_provider( # If it can fork Ethereum (and we are asking for it) assume it can fork this one. # TODO: Refactor this approach to work for custom-forked non-EVM networks. common_forking_providers = self.network_manager.ethereum.mainnet_fork.providers - if provider_name in self.providers: provider = self.providers[provider_name](provider_settings=provider_settings) return _set_provider(provider) diff --git a/tests/functional/test_cli.py b/tests/functional/test_cli.py index 27ec983f5d..cf8a4e0806 100644 --- a/tests/functional/test_cli.py +++ b/tests/functional/test_cli.py @@ -315,9 +315,13 @@ def cmd(network): click.echo(f"Value is '{getattr(network, 'name', network)}'") result = runner.invoke(cmd, ("--network", f"ethereum:{network_name}:node")) + assert result.exit_code == 0 assert f"Value is '{network_name}'" in result.output + + # Fails because node is not a fork provider. result = runner.invoke(cmd, ("--network", f"ethereum:{network_name}-fork:node")) - assert f"Value is '{network_name}-fork'" in result.output + assert result.exit_code != 0 + assert f"No provider named 'node' in network '{network_name}-fork'" in result.output def test_account_option(runner, keyfile_account): diff --git a/tests/functional/test_network_api.py b/tests/functional/test_network_api.py index d2bdcf6310..62d32e60b1 100644 --- a/tests/functional/test_network_api.py +++ b/tests/functional/test_network_api.py @@ -195,3 +195,44 @@ def test_create_network_type_fork(): actual = create_network_type(chain_id, chain_id, is_fork=True) assert issubclass(actual, NetworkAPI) assert issubclass(actual, ForkedNetworkAPI) + + +def test_providers(ethereum): + network = ethereum.local + providers = network.providers + assert "test" in providers + assert "node" in providers + + +def test_providers_custom_network(project, custom_networks_config_dict, ethereum): + with project.temp_config(**custom_networks_config_dict): + network = ethereum.apenet + actual = network.providers + assert "node" in actual + + +def test_providers_custom_non_fork_network_does_not_use_fork_provider( + mocker, project, custom_networks_config_dict, ethereum +): + # NOTE: Have to a mock a Fork provider since none ship with Ape core. + with project.temp_config(**custom_networks_config_dict): + network = ethereum.apenet + network.__dict__.pop("providers", None) # de-cache + + # Setup mock fork provider. + orig = network._get_plugin_providers + network._get_plugin_providers = mocker.MagicMock() + name = "foobar" + + class MyForkProvider: + __module__ = "foobar.test" + + network._get_plugin_providers.return_value = iter( + [(name, ("ethereum", "local", MyForkProvider))] + ) + try: + actual = network.providers + assert name not in actual + finally: + network._get_plugin_providers = orig + network.__dict__.pop("providers", None) # de-cache