Skip to content

Commit

Permalink
refactor!: use provider in contract error instead of network choice s…
Browse files Browse the repository at this point in the history
…tring (#2385)
  • Loading branch information
antazoey committed Dec 11, 2024
1 parent 2962fae commit 22a6550
Show file tree
Hide file tree
Showing 7 changed files with 47 additions and 35 deletions.
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@
# ** Dependencies maintained by Ethereum Foundation **
"eth-abi>=5.1.0,<6",
"eth-account>=0.13.4,<0.14",
"eth-tester>=0.12.0b2,<0.13", # Peer: stricter pin needed for [tester].
"eth-typing>=5.0.1,<6",
"eth-utils>=5.1.0,<6",
"hexbytes>=1.2.1,<2",
Expand Down
6 changes: 1 addition & 5 deletions src/ape/contracts/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,11 +257,7 @@ def decode_input(self, calldata: bytes) -> tuple[str, dict[str, Any]]:

def _validate_is_contract(self):
if not self.contract.is_contract:
raise ContractNotFoundError(
self.contract.address,
self.provider.network.explorer is not None,
self.provider.network_choice,
)
raise ContractNotFoundError(self.contract.address, provider=self.provider)


class ContractCallHandler(ContractMethodHandler):
Expand Down
36 changes: 23 additions & 13 deletions src/ape/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
from ethpm_types.contract_type import ContractType

from ape.api.networks import NetworkAPI
from ape.api.providers import SubprocessProvider
from ape.api.providers import ProviderAPI, SubprocessProvider
from ape.api.trace import TraceAPI
from ape.api.transactions import ReceiptAPI, TransactionAPI
from ape.managers.project import ProjectManager
Expand Down Expand Up @@ -590,29 +590,39 @@ class ContractNotFoundError(ChainError):
Raised when a contract is not found at an address.
"""

# TODO: In 0.9, pass in provider object directly (instead of network choice + name)
def __init__(self, address: "AddressType", has_explorer: bool, network_choice: str):
def __init__(self, address: "AddressType", provider: Optional["ProviderAPI"] = None):
try:
msg = self._create_message(address, provider=provider)
except Exception as err:
# Don't let errors occurring within exception handling to
# ruin the exception completely.
logger.error(f"Failed to create proper error message because of: {err}")
msg = f"Failed to get contract type for address '{address}'."

super().__init__(msg)

@classmethod
def _create_message(
cls, address: "AddressType", provider: Optional["ProviderAPI"] = None
) -> str:
msg = f"Failed to get contract type for address '{address}'."
if not provider:
return msg

# NOTE: Network name is optional to avoid breaking change.
choice_parts = network_choice.split(":")
if len(choice_parts) > 1:
network_name = network_choice.split(":")[1]
else:
network_name = network_choice
network_name = provider.network_choice.split(":")[1]

if has_explorer:
msg += " Contract may need verification."
if provider.network.explorer:
msg += " Contract may need verification (if relying on an explorer)."
elif network_name != "local":
# Only bother mentioning explorer plugins if we are not the local network.
msg += (
f" Current network '{network_choice}' has no associated "
f" Current network '{provider.network_choice}' has no associated "
"explorer plugin. Try installing an explorer plugin using "
f"{click.style(text='ape plugins install etherscan', fg='green')}, "
"or using a network with explorer support."
)

super().__init__(msg)
return msg


class UnknownSnapshotError(ChainError):
Expand Down
10 changes: 2 additions & 8 deletions src/ape/managers/chain.py
Original file line number Diff line number Diff line change
Expand Up @@ -972,9 +972,7 @@ def __getitem__(self, address: AddressType) -> ContractType:
contract_type = self.get(address)
if not contract_type:
# Create error message from custom exception cls.
err = ContractNotFoundError(
address, self.provider.network.explorer is not None, self.provider.network_choice
)
err = ContractNotFoundError(address, provider=self.provider)
# Must raise KeyError.
raise KeyError(str(err))

Expand Down Expand Up @@ -1242,11 +1240,7 @@ def instance_at(
)

if not contract_type:
raise ContractNotFoundError(
contract_address,
self.provider.network.explorer is not None,
self.provider.network_choice,
)
raise ContractNotFoundError(contract_address, provider=self.provider)

elif not isinstance(contract_type, ContractType):
raise TypeError(
Expand Down
5 changes: 3 additions & 2 deletions src/ape_ethereum/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -1593,8 +1593,9 @@ def ots_get_contract_creator(self, address: "AddressType") -> Optional[dict]:

result = self.make_request("ots_getContractCreator", [address])
if result is None:
# NOTE: Skip the explorer part of the error message via `has_explorer=True`.
raise ContractNotFoundError(address, True, self.network_choice)
# Don't pass provider so the error message is simplifer in this case
# (avoids mentioning explorer plugins).
raise ContractNotFoundError(address)

return result

Expand Down
3 changes: 3 additions & 0 deletions tests/functional/test_contract_container.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
ProjectError,
)
from ape_ethereum.ecosystem import ProxyType
from tests.conftest import explorer_test


def test_deploy(
Expand Down Expand Up @@ -166,6 +167,7 @@ def test_at(vyper_contract_instance, vyper_contract_container):
assert instance == vyper_contract_instance


@explorer_test
def test_at_fetch_from_explorer_false(
project_with_contract, mock_explorer, eth_tester_provider, owner
):
Expand All @@ -188,3 +190,4 @@ def test_at_fetch_from_explorer_false(

# Clean up test.
eth_tester_provider.network.explorer = None
assert eth_tester_provider.network.explorer is None
21 changes: 14 additions & 7 deletions tests/functional/test_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
handle_ape_exception,
)
from ape.types.trace import SourceTraceback
from ape.utils.misc import LOCAL_NETWORK_NAME, ZERO_ADDRESS
from ape.utils.misc import ZERO_ADDRESS
from ape_ethereum.transactions import DynamicFeeTransaction, Receipt


Expand Down Expand Up @@ -230,20 +230,27 @@ def revert_type(self) -> Optional[str]:


class TestContractNotFoundError:
def test_local_network(self):
def test_local_network(self, eth_tester_provider):
"""
Testing we are NOT mentioning explorer plugins
for the local-network, as 99.9% of the time it is
confusing.
"""
err = ContractNotFoundError(ZERO_ADDRESS, False, f"ethereum:{LOCAL_NETWORK_NAME}:test")
assert str(err) == f"Failed to get contract type for address '{ZERO_ADDRESS}'."
eth_tester_provider.network.explorer = None # Ensure no explorer is set.
err = ContractNotFoundError(ZERO_ADDRESS, provider=eth_tester_provider)
actual = f"{err}"
expected = f"Failed to get contract type for address '{ZERO_ADDRESS}'."
assert actual == expected

def test_fork_network(self):
err = ContractNotFoundError(ZERO_ADDRESS, False, "ethereum:sepolia-fork:test")
def test_fork_network(self, mocker, mock_sepolia):
provider = mocker.MagicMock()
provider.network = mock_sepolia
mock_sepolia.explorer = None
provider.network_choice = "ethereum:sepolia:node"
err = ContractNotFoundError(ZERO_ADDRESS, provider=provider)
assert str(err) == (
f"Failed to get contract type for address '{ZERO_ADDRESS}'. "
"Current network 'ethereum:sepolia-fork:test' has no associated explorer plugin. "
"Current network 'ethereum:sepolia:node' has no associated explorer plugin. "
"Try installing an explorer plugin using \x1b[32mape plugins install etherscan"
"\x1b[0m, or using a network with explorer support."
)
Expand Down

0 comments on commit 22a6550

Please sign in to comment.