From 08a11d901aeeb515832f13c3a0b167b370ceac73 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Wed, 23 Oct 2024 14:47:06 -0500 Subject: [PATCH] refactor: split up types modules --- src/ape/api/accounts.py | 7 +- src/ape/api/address.py | 9 +- src/ape/api/config.py | 4 +- src/ape/api/explorers.py | 9 +- src/ape/api/networks.py | 8 +- src/ape/api/providers.py | 10 +- src/ape/api/query.py | 8 +- src/ape/api/trace.py | 3 +- src/ape/api/transactions.py | 27 +- src/ape/cli/choices.py | 2 +- src/ape/contracts/base.py | 20 +- src/ape/exceptions.py | 4 +- src/ape/managers/accounts.py | 6 +- src/ape/managers/chain.py | 16 +- src/ape/managers/converters.py | 8 +- src/ape/pytest/config.py | 2 +- src/ape/pytest/coverage.py | 9 +- src/ape/pytest/fixtures.py | 2 +- src/ape/pytest/gas.py | 3 +- src/ape/types/__init__.py | 562 +----------------- src/ape/types/basic.py | 73 +++ src/ape/types/events.py | 328 ++++++++++ src/ape/types/gas.py | 31 + src/ape/types/signatures.py | 4 +- src/ape/types/trace.py | 26 + src/ape/types/units.py | 80 +++ src/ape/types/vm.py | 25 + src/ape/utils/misc.py | 2 +- src/ape/utils/trace.py | 3 +- src/ape_accounts/accounts.py | 6 +- src/ape_cache/query.py | 2 +- src/ape_console/plugin.py | 5 +- src/ape_ethereum/_converters.py | 2 +- src/ape_ethereum/ecosystem.py | 16 +- src/ape_ethereum/multicall/constants.py | 4 +- src/ape_ethereum/multicall/handlers.py | 8 +- src/ape_ethereum/provider.py | 23 +- src/ape_ethereum/query.py | 2 +- src/ape_ethereum/trace.py | 9 +- src/ape_ethereum/transactions.py | 9 +- src/ape_networks/_cli.py | 7 +- src/ape_node/provider.py | 6 +- src/ape_test/accounts.py | 8 +- src/ape_test/provider.py | 12 +- tests/conftest.py | 6 +- tests/functional/conftest.py | 3 +- tests/functional/conversion/test_address.py | 2 +- .../conversion/test_encode_structs.py | 2 +- tests/functional/test_accounts.py | 4 +- tests/functional/test_address.py | 2 +- tests/functional/test_chain.py | 2 +- tests/functional/test_compilers.py | 2 +- tests/functional/test_config.py | 4 +- tests/functional/test_contract_event.py | 5 +- tests/functional/test_contract_instance.py | 2 +- tests/functional/test_coverage.py | 2 +- tests/functional/test_ecosystem.py | 5 +- tests/functional/test_exceptions.py | 2 +- tests/functional/test_explorer.py | 4 +- tests/functional/test_provider.py | 2 +- tests/functional/test_types.py | 7 +- 61 files changed, 763 insertions(+), 703 deletions(-) create mode 100644 src/ape/types/basic.py create mode 100644 src/ape/types/events.py create mode 100644 src/ape/types/gas.py create mode 100644 src/ape/types/units.py create mode 100644 src/ape/types/vm.py diff --git a/src/ape/api/accounts.py b/src/ape/api/accounts.py index 1c74b85bde..9320bfa5f1 100644 --- a/src/ape/api/accounts.py +++ b/src/ape/api/accounts.py @@ -1,4 +1,5 @@ import os +from abc import abstractmethod from collections.abc import Iterator from functools import cached_property from pathlib import Path @@ -24,8 +25,10 @@ TransactionError, ) from ape.logging import logger -from ape.types import AddressType, MessageSignature, SignableMessage -from ape.utils import BaseInterfaceModel, abstractmethod, raises_not_implemented +from ape.types.address import AddressType +from ape.types.signatures import MessageSignature, SignableMessage +from ape.utils.basemodel import BaseInterfaceModel +from ape.utils.misc import raises_not_implemented if TYPE_CHECKING: from ape.contracts import ContractContainer, ContractInstance diff --git a/src/ape/api/address.py b/src/ape/api/address.py index 14f06e46b3..cd62661c41 100644 --- a/src/ape/api/address.py +++ b/src/ape/api/address.py @@ -1,10 +1,15 @@ +from abc import abstractmethod +from functools import cached_property from typing import TYPE_CHECKING, Any from eth_pydantic_types import HexBytes from ape.exceptions import ConversionError -from ape.types import AddressType, ContractCode, CurrencyValue -from ape.utils import BaseInterface, abstractmethod, cached_property, log_instead_of_fail +from ape.types.address import AddressType +from ape.types.units import CurrencyValue +from ape.types.vm import ContractCode +from ape.utils.basemodel import BaseInterface +from ape.utils.misc import log_instead_of_fail if TYPE_CHECKING: from ape.api.transactions import ReceiptAPI, TransactionAPI diff --git a/src/ape/api/config.py b/src/ape/api/config.py index f1cb66f51e..8673f7bb33 100644 --- a/src/ape/api/config.py +++ b/src/ape/api/config.py @@ -12,8 +12,7 @@ from ape.exceptions import ConfigError from ape.logging import logger -from ape.types import AddressType -from ape.utils import clean_path +from ape.types.address import AddressType from ape.utils.basemodel import ( ExtraAttributesMixin, ExtraModelAttributes, @@ -23,6 +22,7 @@ only_raise_attribute_error, ) from ape.utils.misc import load_config +from ape.utils.os import clean_path ConfigItemType = TypeVar("ConfigItemType") diff --git a/src/ape/api/explorers.py b/src/ape/api/explorers.py index f79fed6668..2344c06d05 100644 --- a/src/ape/api/explorers.py +++ b/src/ape/api/explorers.py @@ -1,10 +1,11 @@ +from abc import abstractmethod from typing import Optional from ethpm_types import ContractType -from ape.api import networks -from ape.types import AddressType -from ape.utils import BaseInterfaceModel, abstractmethod +from ape.api.networks import NetworkAPI +from ape.types.address import AddressType +from ape.utils.basemodel import BaseInterfaceModel class ExplorerAPI(BaseInterfaceModel): @@ -14,7 +15,7 @@ class ExplorerAPI(BaseInterfaceModel): """ name: str # Plugin name - network: networks.NetworkAPI + network: NetworkAPI @abstractmethod def get_address_url(self, address: AddressType) -> str: diff --git a/src/ape/api/networks.py b/src/ape/api/networks.py index a3cf0d81bf..92145dbbf6 100644 --- a/src/ape/api/networks.py +++ b/src/ape/api/networks.py @@ -12,7 +12,7 @@ ) from eth_pydantic_types import HexBytes from eth_utils import keccak, to_int -from ethpm_types import BaseModel, ContractType +from ethpm_types import ContractType from ethpm_types.abi import ABIType, ConstructorABI, EventABI, MethodABI from pydantic import model_validator @@ -26,10 +26,12 @@ SignatureError, ) from ape.logging import logger -from ape.types import AutoGasLimit, ContractLog, GasLimit from ape.types.address import AddressType, RawAddress +from ape.types.events import ContractLog +from ape.types.gas import AutoGasLimit, GasLimit from ape.utils.basemodel import ( BaseInterfaceModel, + BaseModel, ExtraAttributesMixin, ExtraModelAttributes, ManagerAccessMixin, @@ -419,7 +421,7 @@ def encode_transaction( """ @abstractmethod - def decode_logs(self, logs: Sequence[dict], *events: EventABI) -> Iterator["ContractLog"]: + def decode_logs(self, logs: Sequence[dict], *events: EventABI) -> Iterator[ContractLog]: """ Decode any contract logs that match the given event ABI from the raw log data. diff --git a/src/ape/api/providers.py b/src/ape/api/providers.py index bc8ba5c041..dd5bc51c15 100644 --- a/src/ape/api/providers.py +++ b/src/ape/api/providers.py @@ -7,7 +7,9 @@ import sys import time import warnings +from abc import abstractmethod from collections.abc import Iterable, Iterator +from functools import cached_property from logging import FileHandler, Formatter, Logger, getLogger from pathlib import Path from signal import SIGINT, SIGTERM, signal @@ -33,8 +35,11 @@ VirtualMachineError, ) from ape.logging import LogLevel, logger -from ape.types import AddressType, BlockID, ContractCode, ContractLog, HexInt, LogFilter, SnapshotID -from ape.utils import BaseInterfaceModel, JoinableQueue, abstractmethod, cached_property, spawn +from ape.types.address import AddressType +from ape.types.basic import HexInt +from ape.types.events import ContractLog, LogFilter +from ape.types.vm import BlockID, ContractCode, SnapshotID +from ape.utils.basemodel import BaseInterfaceModel from ape.utils.misc import ( EMPTY_BYTES32, _create_raises_not_implemented_error, @@ -42,6 +47,7 @@ raises_not_implemented, to_int, ) +from ape.utils.process import JoinableQueue, spawn from ape.utils.rpc import RPCHeaders if TYPE_CHECKING: diff --git a/src/ape/api/query.py b/src/ape/api/query.py index b5c6c7f388..8732a3d404 100644 --- a/src/ape/api/query.py +++ b/src/ape/api/query.py @@ -1,5 +1,6 @@ +from abc import abstractmethod from collections.abc import Iterator, Sequence -from functools import cache +from functools import cache, cached_property from typing import Any, Optional, Union from ethpm_types.abi import EventABI, MethodABI @@ -7,9 +8,8 @@ from ape.api.transactions import ReceiptAPI, TransactionAPI from ape.logging import logger -from ape.types import AddressType -from ape.utils import BaseInterface, BaseInterfaceModel, abstractmethod, cached_property -from ape.utils.basemodel import BaseModel +from ape.types.address import AddressType +from ape.utils.basemodel import BaseInterface, BaseInterfaceModel, BaseModel QueryType = Union[ "BlockQuery", diff --git a/src/ape/api/trace.py b/src/ape/api/trace.py index 4250757dde..f4398fb58b 100644 --- a/src/ape/api/trace.py +++ b/src/ape/api/trace.py @@ -3,8 +3,7 @@ from collections.abc import Iterator, Sequence from typing import IO, Any, Optional -from ape.types import ContractFunctionPath -from ape.types.trace import GasReport +from ape.types.trace import ContractFunctionPath, GasReport from ape.utils.basemodel import BaseInterfaceModel diff --git a/src/ape/api/transactions.py b/src/ape/api/transactions.py index 926c26a50e..8a68da6163 100644 --- a/src/ape/api/transactions.py +++ b/src/ape/api/transactions.py @@ -1,7 +1,9 @@ import sys import time +from abc import abstractmethod from collections.abc import Iterator from datetime import datetime +from functools import cached_property from typing import IO, TYPE_CHECKING, Any, NoReturn, Optional, Union from eth_pydantic_types import HexBytes, HexStr @@ -20,23 +22,14 @@ TransactionNotFoundError, ) from ape.logging import logger -from ape.types import ( - AddressType, - AutoGasLimit, - ContractLogContainer, - HexInt, - SourceTraceback, - TransactionSignature, -) -from ape.utils import ( - BaseInterfaceModel, - ExtraAttributesMixin, - ExtraModelAttributes, - abstractmethod, - cached_property, - log_instead_of_fail, - raises_not_implemented, -) +from ape.types.address import AddressType +from ape.types.basic import HexInt +from ape.types.events import ContractLogContainer +from ape.types.gas import AutoGasLimit +from ape.types.signatures import TransactionSignature +from ape.types.trace import SourceTraceback +from ape.utils.basemodel import BaseInterfaceModel, ExtraAttributesMixin, ExtraModelAttributes +from ape.utils.misc import log_instead_of_fail, raises_not_implemented if TYPE_CHECKING: from ape.api.providers import BlockAPI diff --git a/src/ape/cli/choices.py b/src/ape/cli/choices.py index 3c4782fefb..b37894acc3 100644 --- a/src/ape/cli/choices.py +++ b/src/ape/cli/choices.py @@ -15,7 +15,7 @@ NetworkNotFoundError, ProviderNotFoundError, ) -from ape.types import _LazySequence +from ape.types.basic import _LazySequence from ape.utils.basemodel import ManagerAccessMixin _ACCOUNT_TYPE_FILTER = Union[ diff --git a/src/ape/contracts/base.py b/src/ape/contracts/base.py index 486dd89d50..f5e834c0ce 100644 --- a/src/ape/contracts/base.py +++ b/src/ape/contracts/base.py @@ -1,7 +1,7 @@ import difflib import types from collections.abc import Callable, Iterator -from functools import partial +from functools import cached_property, partial, singledispatchmethod from itertools import islice from pathlib import Path from typing import Any, Optional, Union @@ -14,14 +14,15 @@ from ethpm_types.contract_type import ABI_W_SELECTOR_T, ContractType from IPython.lib.pretty import for_type -from ape.api import AccountAPI, Address, ReceiptAPI, TransactionAPI -from ape.api.address import BaseAddress +from ape.api.accounts import AccountAPI +from ape.api.address import Address, BaseAddress from ape.api.query import ( ContractCreation, ContractEventQuery, extract_fields, validate_and_expand_columns, ) +from ape.api.transactions import ReceiptAPI, TransactionAPI from ape.exceptions import ( ApeAttributeError, ArgumentsLengthError, @@ -34,20 +35,17 @@ MissingDeploymentBytecodeError, ) from ape.logging import get_rich_console, logger -from ape.types import AddressType, ContractLog, LogFilter, MockContractLog -from ape.utils import ( - BaseInterfaceModel, - ManagerAccessMixin, - cached_property, - log_instead_of_fail, - singledispatchmethod, -) +from ape.types.address import AddressType +from ape.types.events import ContractLog, LogFilter, MockContractLog from ape.utils.abi import StructParser, _enrich_natspec from ape.utils.basemodel import ( + BaseInterfaceModel, ExtraAttributesMixin, ExtraModelAttributes, + ManagerAccessMixin, _assert_not_ipython_check, get_attribute_with_extras, + log_instead_of_fail, only_raise_attribute_error, ) diff --git a/src/ape/exceptions.py b/src/ape/exceptions.py index d3af388a67..b6f1db0dcd 100644 --- a/src/ape/exceptions.py +++ b/src/ape/exceptions.py @@ -25,7 +25,9 @@ from ape.api.trace import TraceAPI from ape.api.transactions import ReceiptAPI, TransactionAPI from ape.managers.project import ProjectManager - from ape.types import AddressType, BlockID, SnapshotID, SourceTraceback + from ape.types.address import AddressType + from ape.types.trace import SourceTraceback + from ape.types.vm import BlockID, SnapshotID FailedTxn = Union["TransactionAPI", "ReceiptAPI"] diff --git a/src/ape/managers/accounts.py b/src/ape/managers/accounts.py index 1815d263a6..858cf0de8a 100644 --- a/src/ape/managers/accounts.py +++ b/src/ape/managers/accounts.py @@ -1,6 +1,7 @@ import contextlib from collections.abc import Generator, Iterator from contextlib import AbstractContextManager as ContextManager +from functools import cached_property, singledispatchmethod from typing import Optional, Union from eth_utils import is_hex @@ -14,8 +15,9 @@ ) from ape.exceptions import AccountsError, ConversionError from ape.managers.base import BaseManager -from ape.types import AddressType -from ape.utils import ManagerAccessMixin, cached_property, log_instead_of_fail, singledispatchmethod +from ape.types.address import AddressType +from ape.utils.basemodel import ManagerAccessMixin +from ape.utils.misc import log_instead_of_fail _DEFAULT_SENDERS: list[AccountAPI] = [] diff --git a/src/ape/managers/chain.py b/src/ape/managers/chain.py index 93a525bb42..45682de78e 100644 --- a/src/ape/managers/chain.py +++ b/src/ape/managers/chain.py @@ -3,7 +3,7 @@ from collections.abc import Collection, Iterator from concurrent.futures import ThreadPoolExecutor from contextlib import contextmanager -from functools import partial +from functools import partial, singledispatchmethod from pathlib import Path from statistics import mean, median from typing import IO, Optional, Union, cast @@ -40,15 +40,11 @@ ) from ape.logging import get_rich_console, logger from ape.managers.base import BaseManager -from ape.types import AddressType, GasReport, SnapshotID, SourceTraceback -from ape.utils import ( - BaseInterfaceModel, - is_evm_precompile, - is_zero_hex, - log_instead_of_fail, - nonreentrant, - singledispatchmethod, -) +from ape.types.address import AddressType +from ape.types.trace import GasReport, SourceTraceback +from ape.types.vm import SnapshotID +from ape.utils.basemodel import BaseInterfaceModel +from ape.utils.misc import is_evm_precompile, is_zero_hex, log_instead_of_fail, nonreentrant class BlockContainer(BaseManager): diff --git a/src/ape/managers/converters.py b/src/ape/managers/converters.py index f2ec2a24ab..ca141b53ff 100644 --- a/src/ape/managers/converters.py +++ b/src/ape/managers/converters.py @@ -2,6 +2,7 @@ from collections.abc import Iterable, Sequence from datetime import datetime, timedelta, timezone from decimal import Decimal +from functools import cached_property from typing import Any, Union from dateutil.parser import parse @@ -17,12 +18,13 @@ ) from ethpm_types import ConstructorABI, EventABI, MethodABI -from ape.api import ConverterAPI, TransactionAPI from ape.api.address import BaseAddress +from ape.api.convert import ConverterAPI +from ape.api.transactions import TransactionAPI from ape.exceptions import ConversionError from ape.logging import logger -from ape.types import AddressType -from ape.utils import cached_property, log_instead_of_fail +from ape.types.address import AddressType +from ape.utils.misc import log_instead_of_fail from .base import BaseManager diff --git a/src/ape/pytest/config.py b/src/ape/pytest/config.py index d21e2cf619..99f6db77c7 100644 --- a/src/ape/pytest/config.py +++ b/src/ape/pytest/config.py @@ -3,7 +3,7 @@ from _pytest.config import Config as PytestConfig -from ape.types import ContractFunctionPath +from ape.types.trace import ContractFunctionPath from ape.utils.basemodel import ManagerAccessMixin diff --git a/src/ape/pytest/coverage.py b/src/ape/pytest/coverage.py index 9df48b8227..9adee6fa31 100644 --- a/src/ape/pytest/coverage.py +++ b/src/ape/pytest/coverage.py @@ -9,13 +9,8 @@ from ape.logging import logger from ape.managers.project import ProjectManager from ape.pytest.config import ConfigWrapper -from ape.types import ( - ContractFunctionPath, - ControlFlow, - CoverageProject, - CoverageReport, - SourceTraceback, -) +from ape.types.coverage import CoverageProject, CoverageReport +from ape.types.trace import ContractFunctionPath, ControlFlow, SourceTraceback from ape.utils.basemodel import ManagerAccessMixin from ape.utils.misc import get_current_timestamp_ms from ape.utils.os import get_full_extension, get_relative_path diff --git a/src/ape/pytest/fixtures.py b/src/ape/pytest/fixtures.py index fe3b997542..80a77b0279 100644 --- a/src/ape/pytest/fixtures.py +++ b/src/ape/pytest/fixtures.py @@ -14,7 +14,7 @@ from ape.managers.networks import NetworkManager from ape.managers.project import ProjectManager from ape.pytest.config import ConfigWrapper -from ape.types import SnapshotID +from ape.types.vm import SnapshotID from ape.utils.basemodel import ManagerAccessMixin from ape.utils.rpc import allow_disconnected diff --git a/src/ape/pytest/gas.py b/src/ape/pytest/gas.py index 87f6ffca41..3b8e0e63ce 100644 --- a/src/ape/pytest/gas.py +++ b/src/ape/pytest/gas.py @@ -6,7 +6,8 @@ from ape.api.trace import TraceAPI from ape.pytest.config import ConfigWrapper -from ape.types import AddressType, ContractFunctionPath, GasReport +from ape.types.address import AddressType +from ape.types.trace import ContractFunctionPath, GasReport from ape.utils.basemodel import ManagerAccessMixin from ape.utils.trace import _exclude_gas, parse_gas_table diff --git a/src/ape/types/__init__.py b/src/ape/types/__init__.py index ea6581c50a..3893f7bcb5 100644 --- a/src/ape/types/__init__.py +++ b/src/ape/types/__init__.py @@ -1,12 +1,4 @@ -from collections.abc import Callable, Iterable, Iterator, Sequence -from dataclasses import dataclass -from typing import TYPE_CHECKING, Annotated, Any, Literal, Optional, TypeVar, Union, cast, overload - -from eth_abi.abi import encode -from eth_abi.packed import encode_packed from eth_pydantic_types import HexBytes -from eth_typing import Hash32, HexStr -from eth_utils import encode_hex, keccak, to_hex from ethpm_types import ( ABI, Bytecode, @@ -17,21 +9,10 @@ PackageMeta, Source, ) -from ethpm_types.abi import EventABI from ethpm_types.source import Closure -from pydantic import BaseModel, BeforeValidator, field_serializer, field_validator, model_validator -from pydantic_core.core_schema import ( - CoreSchema, - ValidationInfo, - int_schema, - no_info_plain_validator_function, - plain_serializer_function_ser_schema, -) -from typing_extensions import TypeAlias -from web3.types import FilterParams -from ape.exceptions import ConversionError from ape.types.address import AddressType, RawAddress +from ape.types.basic import HexInt, _LazySequence from ape.types.coverage import ( ContractCoverage, ContractSourceCoverage, @@ -39,546 +20,26 @@ CoverageReport, CoverageStatement, ) +from ape.types.events import ContractLog, ContractLogContainer, LogFilter, MockContractLog +from ape.types.gas import AutoGasLimit, GasLimit from ape.types.signatures import MessageSignature, SignableMessage, TransactionSignature -from ape.types.trace import ControlFlow, GasReport, SourceTraceback -from ape.utils import ( - BaseInterfaceModel, - ExtraAttributesMixin, - ExtraModelAttributes, - ManagerAccessMixin, - cached_property, -) -from ape.utils.misc import ZERO_ADDRESS, log_instead_of_fail - -if TYPE_CHECKING: - from ape.api.providers import BlockAPI - from ape.contracts import ContractEvent - -BlockID = Union[int, HexStr, HexBytes, Literal["earliest", "latest", "pending"]] -""" -An ID that can match a block, such as the literals ``"earliest"``, ``"latest"``, or ``"pending"`` -as well as a block number or hash (HexBytes). -""" - -ContractCode = Union[str, bytes, HexBytes] -""" -A type that represents contract code, which can be represented in string, bytes, or HexBytes. -""" - -SnapshotID = Union[str, int, bytes] -""" -An ID representing a point in time on a blockchain, as used in the -:meth:`~ape.managers.chain.ChainManager.snapshot` and -:meth:`~ape.managers.chain.ChainManager.snapshot` methods. Can be a ``str``, ``int``, or ``bytes``. -Providers will expect and handle snapshot IDs differently. There shouldn't be a need to change -providers when using this feature, so there should not be confusion over this type in practical use -cases. -""" - -HexInt = Annotated[ - int, - BeforeValidator( - lambda v, info: None if v is None else ManagerAccessMixin.conversion_manager.convert(v, int) - ), -] -""" -Validate any hex-str or bytes into an integer. -To be used on pydantic-fields. -""" - - -class AutoGasLimit(BaseModel): - """ - Additional settings for ``gas_limit: auto``. - """ - - multiplier: float = 1.0 - """ - A multiplier to estimated gas. - """ - - @field_validator("multiplier", mode="before") - @classmethod - 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, -``"max"`` refers to using the maximum block gas limit, -and otherwise you can provide a numeric value. -""" - - -TopicFilter = Sequence[Union[Optional[HexStr], Sequence[Optional[HexStr]]]] - - -@dataclass -class ContractFunctionPath: - """ - Useful for identifying a method in a contract. - """ - - contract_name: str - method_name: Optional[str] = None - - @classmethod - def from_str(cls, value: str) -> "ContractFunctionPath": - if ":" in value: - contract_name, method_name = value.split(":") - return cls(contract_name=contract_name, method_name=method_name) - - return cls(contract_name=value) - - def __str__(self) -> str: - return f"{self.contract_name}:{self.method_name}" - - @log_instead_of_fail(default="") - def __repr__(self) -> str: - return f"<{self}>" - - -class LogFilter(BaseModel): - addresses: list[AddressType] = [] - events: list[EventABI] = [] - topic_filter: TopicFilter = [] - start_block: int = 0 - stop_block: Optional[int] = None # Use block height - selectors: dict[str, EventABI] = {} - - @model_validator(mode="before") - @classmethod - def compute_selectors(cls, values): - values["selectors"] = { - encode_hex(keccak(text=event.selector)): event for event in values.get("events", []) - } - - return values - - @field_validator("start_block", mode="before") - @classmethod - def validate_start_block(cls, value): - return value or 0 - - def model_dump(self, *args, **kwargs): - _Hash32 = Union[Hash32, HexBytes, HexStr] - topics = cast(Sequence[Optional[Union[_Hash32, Sequence[_Hash32]]]], self.topic_filter) - return FilterParams( - address=self.addresses, - fromBlock=to_hex(self.start_block), - toBlock=to_hex(self.stop_block or self.start_block), - topics=topics, - ) - - @classmethod - def from_event( - cls, - event: Union[EventABI, "ContractEvent"], - search_topics: Optional[dict[str, Any]] = None, - addresses: Optional[list[AddressType]] = None, - start_block=None, - stop_block=None, - ): - """ - Construct a log filter from an event topic query. - """ - from ape import convert - from ape.utils.abi import LogInputABICollection, is_dynamic_sized_type - - event_abi: EventABI = getattr(event, "abi", event) # type: ignore - search_topics = search_topics or {} - topic_filter: list[Optional[HexStr]] = [encode_hex(keccak(text=event_abi.selector))] - abi_inputs = LogInputABICollection(event_abi) - - def encode_topic_value(abi_type, value): - if isinstance(value, (list, tuple)): - return [encode_topic_value(abi_type, v) for v in value] - elif is_dynamic_sized_type(abi_type): - return encode_hex(keccak(encode_packed([str(abi_type)], [value]))) - elif abi_type == "address": - value = convert(value, AddressType) - - return encode_hex(encode([abi_type], [value])) - - for topic in abi_inputs.topic_abi_types: - if topic.name in search_topics: - encoded_value = encode_topic_value(topic.type, search_topics[topic.name]) - topic_filter.append(encoded_value) - else: - topic_filter.append(None) - - topic_names = [i.name for i in abi_inputs.topic_abi_types if i.name] - invalid_topics = set(search_topics) - set(topic_names) - if invalid_topics: - raise ValueError( - f"{event_abi.name} defines {', '.join(topic_names)} as indexed topics, " - f"but you provided {', '.join(invalid_topics)}" - ) - - # remove trailing wildcards since they have no effect - while topic_filter[-1] is None: - topic_filter.pop() - - return cls( - addresses=addresses or [], - events=[event_abi], - topic_filter=topic_filter, - start_block=start_block, - stop_block=stop_block, - ) - - -class BaseContractLog(BaseInterfaceModel): - """ - Base class representing information relevant to an event instance. - """ - - event_name: str - """The name of the event.""" - - contract_address: AddressType = ZERO_ADDRESS - """The contract responsible for emitting the log.""" - - event_arguments: dict[str, Any] = {} - """The arguments to the event, including both indexed and non-indexed data.""" - - def __eq__(self, other: Any) -> bool: - if self.contract_address != other.contract_address or self.event_name != other.event_name: - return False - - for k, v in self.event_arguments.items(): - other_v = other.event_arguments.get(k) - if v != other_v: - return False - - return True - - @field_serializer("event_arguments") - def _serialize_event_arguments(self, event_arguments, info): - """ - Because of an issue with BigInt in Pydantic, - (https://github.com/pydantic/pydantic/issues/10152) - we have to ensure these are regular ints. - """ - return self._serialize_value(event_arguments, info) - - def _serialize_value(self, value: Any, info) -> Any: - if isinstance(value, int): - # Handle custom ints. - return int(value) - - elif isinstance(value, HexBytes): - return to_hex(value) if info.mode == "json" else value - - elif isinstance(value, str): - # Avoiding str triggering iterable condition. - return value - - elif isinstance(value, dict): - # Also, avoid handling dict in the iterable case. - return {k: self._serialize_value(v, info) for k, v in value.items()} - - elif isinstance(value, Iterable): - return [self._serialize_value(v, info) for v in value] - - return value - - -class ContractLog(ExtraAttributesMixin, BaseContractLog): - """ - An instance of a log from a contract. - """ - - transaction_hash: Any - """The hash of the transaction containing this log.""" - - block_number: HexInt - """The number of the block containing the transaction that produced this log.""" - - block_hash: Any - """The hash of the block containing the transaction that produced this log.""" - - log_index: HexInt - """The index of the log on the transaction.""" - - transaction_index: Optional[HexInt] = None - """ - The index of the transaction's position when the log was created. - Is `None` when from the pending block. - """ - - @field_serializer("transaction_hash", "block_hash") - def _serialize_hashes(self, value, info): - return self._serialize_value(value, info) - - # NOTE: This class has an overridden `__getattr__` method, but `block` is a reserved keyword - # in most smart contract languages, so it is safe to use. Purposely avoid adding - # `.datetime` and `.timestamp` in case they are used as event arg names. - @cached_property - def block(self) -> "BlockAPI": - return self.chain_manager.blocks[self.block_number] - - @property - def timestamp(self) -> int: - """ - The UNIX timestamp of when the event was emitted. - - NOTE: This performs a block lookup. - """ - return self.block.timestamp - - @property - def _event_args_str(self) -> str: - return " ".join(f"{key}={val}" for key, val in self.event_arguments.items()) - - def __str__(self) -> str: - return f"{self.event_name}({self._event_args_str})" - - @log_instead_of_fail(default="") - def __repr__(self) -> str: - event_arg_str = self._event_args_str - suffix = f" {event_arg_str}" if event_arg_str else "" - return f"<{self.event_name}{suffix}>" - - def __ape_extra_attributes__(self) -> Iterator[ExtraModelAttributes]: - yield ExtraModelAttributes( - name=self.event_name, - attributes=lambda: self.event_arguments or {}, - include_getattr=True, - include_getitem=True, - ) - - def __contains__(self, item: str) -> bool: - return item in self.event_arguments - - def __eq__(self, other: Any) -> bool: - """ - Check for equality between this instance and another ContractLog instance. - - If the other object is not an instance of ContractLog, this method returns - NotImplemented. This triggers the Python interpreter to call the __eq__ method - on the other object (i.e., y.__eq__(x)) if it is defined, allowing for a custom - comparison. This behavior is leveraged by the MockContractLog class to handle - custom comparison logic between ContractLog and MockContractLog instances. - - Args: - other (Any): The object to compare with this instance. - - Returns: - bool: True if the two instances are equal, False otherwise. - """ - - if not isinstance(other, ContractLog): - return NotImplemented - - # call __eq__ on parent class - return super().__eq__(other) - - def get(self, item: str, default: Optional[Any] = None) -> Any: - return self.event_arguments.get(item, default) - - -def _equal_event_inputs(mock_input: Any, real_input: Any) -> bool: - if mock_input is None: - # Check is skipped. - return True - - elif isinstance(mock_input, (list, tuple)): - if not isinstance(real_input, (list, tuple)) or len(real_input) != len(mock_input): - return False - - return all(_equal_event_inputs(m, r) for m, r in zip(mock_input, real_input)) - - else: - return mock_input == real_input - - -class MockContractLog(BaseContractLog): - """ - A mock version of the ContractLog class used for testing purposes. - This class is designed to match a subset of event arguments in a ContractLog instance - by only comparing those event arguments that the user explicitly provides. - - Inherits from :class:`~ape.types.BaseContractLog`, and overrides the - equality method for custom comparison - of event arguments between a MockContractLog and a ContractLog instance. - """ - - def __eq__(self, other: Any) -> bool: - if ( - not hasattr(other, "contract_address") - or not hasattr(other, "event_name") - or self.contract_address != other.contract_address - or self.event_name != other.event_name - ): - return False - - # NOTE: `self.event_arguments` contains a subset of items from `other.event_arguments`, - # but we skip those the user doesn't care to check - for name, value in self.event_arguments.items(): - other_input = other.event_arguments.get(name) - if not _equal_event_inputs(value, other_input): - # Only exit on False; Else, keep checking. - return False - - return True - - -class ContractLogContainer(list): - """ - Container for ContractLogs which is adding capability of filtering logs - """ - - def filter(self, event: "ContractEvent", **kwargs) -> list[ContractLog]: - return [ - x - for x in self - if x.event_name == event.name - and x.contract_address == event.contract - and all(v == x.event_arguments.get(k) and v is not None for k, v in kwargs.items()) - ] - - def __contains__(self, val: Any) -> bool: - return any(log == val for log in self) - - -_T = TypeVar("_T") # _LazySequence generic. - - -class _LazySequence(Sequence[_T]): - def __init__(self, generator: Union[Iterator[_T], Callable[[], Iterator[_T]]]): - self._generator = generator - self.cache: list = [] - - @overload - def __getitem__(self, index: int) -> _T: ... - - @overload - def __getitem__(self, index: slice) -> Sequence[_T]: ... - - def __getitem__(self, index: Union[int, slice]) -> Union[_T, Sequence[_T]]: - if isinstance(index, int): - while len(self.cache) <= index: - # Catch up the cache. - if value := next(self.generator, None): - self.cache.append(value) - - return self.cache[index] - - elif isinstance(index, slice): - # TODO: Make slices lazier. Right now, it deqeues all. - for item in self.generator: - self.cache.append(item) - - return self.cache[index] - - else: - raise TypeError("Index must be int or slice.") - - def __len__(self) -> int: - # NOTE: This will deque everything. - - for value in self.generator: - self.cache.append(value) - - return len(self.cache) - - def __iter__(self) -> Iterator[_T]: - yield from self.cache - for value in self.generator: - yield value - self.cache.append(value) - - @property - def generator(self) -> Iterator: - if callable(self._generator): - self._generator = self._generator() - - assert isinstance(self._generator, Iterator) # For type-checking. - yield from self._generator - - -class CurrencyValueComparable(int): - """ - An integer you can compare with currency-value - strings, such as ``"1 ether"``. - """ - - def __eq__(self, other: Any) -> bool: - if isinstance(other, int): - return super().__eq__(other) - - elif isinstance(other, str): - try: - other_value = ManagerAccessMixin.conversion_manager.convert(other, int) - except ConversionError: - # Not a currency-value, it's ok. - return False - - return super().__eq__(other_value) - - # Try from the other end, if hasn't already. - return NotImplemented - - def __hash__(self) -> int: - return hash(int(self)) - - @classmethod - def __get_pydantic_core_schema__(cls, value, handler=None) -> CoreSchema: - return no_info_plain_validator_function( - cls._validate, - serialization=plain_serializer_function_ser_schema( - cls._serialize, - info_arg=False, - return_schema=int_schema(), - ), - ) - - @staticmethod - def _validate(value: Any, info: Optional[ValidationInfo] = None) -> "CurrencyValueComparable": - # NOTE: For some reason, for this to work, it has to happen - # in an "after" validator, or else it always only `int` type on the model. - if value is None: - # Will fail if not optional. - # Type ignore because this is an hacky and unlikely situation. - return None # type: ignore - - elif isinstance(value, str) and " " in value: - return ManagerAccessMixin.conversion_manager.convert(value, int) - - # For models annotating with this type, we validate all integers into it. - return CurrencyValueComparable(value) - - @staticmethod - def _serialize(value): - return int(value) - - -CurrencyValueComparable.__name__ = int.__name__ - - -CurrencyValue: TypeAlias = CurrencyValueComparable -""" -An alias to :class:`~ape.types.CurrencyValueComparable` for -situations when you know for sure the type is a currency-value -(and not just comparable to one). -""" - +from ape.types.trace import ContractFunctionPath, ControlFlow, GasReport, SourceTraceback +from ape.types.units import CurrencyValue, CurrencyValueComparable +from ape.types.vm import BlockID, ContractCode, SnapshotID __all__ = [ + "_LazySequence", "ABI", "AddressType", + "AutoGasLimit", "BlockID", "Bytecode", "Checksum", "Closure", "Compiler", + "ContractCode", "ContractCoverage", + "ContractFunctionPath", "ContractSourceCoverage", "ContractLog", "ContractLogContainer", @@ -589,10 +50,13 @@ def _serialize(value): "CoverageStatement", "CurrencyValue", "CurrencyValueComparable", + "GasLimit", "GasReport", "HexInt", "HexBytes", + "LogFilter", "MessageSignature", + "MockContractLog", "PackageManifest", "PackageMeta", "RawAddress", diff --git a/src/ape/types/basic.py b/src/ape/types/basic.py new file mode 100644 index 0000000000..920c52e53b --- /dev/null +++ b/src/ape/types/basic.py @@ -0,0 +1,73 @@ +from collections.abc import Callable, Iterator, Sequence +from importlib import import_module +from typing import Annotated, TypeVar, Union, overload + +from pydantic import BeforeValidator + + +def _hex_int_validator(value, info): + basemodel = import_module("ape.utils.basemodel") + convert = basemodel.ManagerAccessMixin.conversion_manager.convert + return convert(value, int) + + +HexInt = Annotated[int, BeforeValidator(_hex_int_validator)] +""" +Validate any hex-str or bytes into an integer. +To be used on pydantic-fields. +""" + +_T = TypeVar("_T") # _LazySequence generic. + + +class _LazySequence(Sequence[_T]): + def __init__(self, generator: Union[Iterator[_T], Callable[[], Iterator[_T]]]): + self._generator = generator + self.cache: list = [] + + @overload + def __getitem__(self, index: int) -> _T: ... + + @overload + def __getitem__(self, index: slice) -> Sequence[_T]: ... + + def __getitem__(self, index: Union[int, slice]) -> Union[_T, Sequence[_T]]: + if isinstance(index, int): + while len(self.cache) <= index: + # Catch up the cache. + if value := next(self.generator, None): + self.cache.append(value) + + return self.cache[index] + + elif isinstance(index, slice): + # TODO: Make slices lazier. Right now, it deqeues all. + for item in self.generator: + self.cache.append(item) + + return self.cache[index] + + else: + raise TypeError("Index must be int or slice.") + + def __len__(self) -> int: + # NOTE: This will deque everything. + + for value in self.generator: + self.cache.append(value) + + return len(self.cache) + + def __iter__(self) -> Iterator[_T]: + yield from self.cache + for value in self.generator: + yield value + self.cache.append(value) + + @property + def generator(self) -> Iterator: + if callable(self._generator): + self._generator = self._generator() + + assert isinstance(self._generator, Iterator) # For type-checking. + yield from self._generator diff --git a/src/ape/types/events.py b/src/ape/types/events.py new file mode 100644 index 0000000000..8eb6d106df --- /dev/null +++ b/src/ape/types/events.py @@ -0,0 +1,328 @@ +from collections.abc import Iterable, Iterator, Sequence +from functools import cached_property +from typing import TYPE_CHECKING, Any, Optional, Union, cast + +from eth_abi.abi import encode +from eth_abi.packed import encode_packed +from eth_pydantic_types import HexBytes +from eth_typing import Hash32, HexStr +from eth_utils import encode_hex, keccak, to_hex +from ethpm_types.abi import EventABI +from pydantic import BaseModel, field_serializer, field_validator, model_validator +from web3.types import FilterParams + +from ape.types.address import AddressType +from ape.types.basic import HexInt +from ape.utils.basemodel import BaseInterfaceModel, ExtraAttributesMixin, ExtraModelAttributes +from ape.utils.misc import ZERO_ADDRESS, log_instead_of_fail + +if TYPE_CHECKING: + from ape.api.providers import BlockAPI + from ape.contracts import ContractEvent + + +TopicFilter = Sequence[Union[Optional[HexStr], Sequence[Optional[HexStr]]]] + + +class LogFilter(BaseModel): + addresses: list[AddressType] = [] + events: list[EventABI] = [] + topic_filter: TopicFilter = [] + start_block: int = 0 + stop_block: Optional[int] = None # Use block height + selectors: dict[str, EventABI] = {} + + @model_validator(mode="before") + @classmethod + def compute_selectors(cls, values): + values["selectors"] = { + encode_hex(keccak(text=event.selector)): event for event in values.get("events", []) + } + + return values + + @field_validator("start_block", mode="before") + @classmethod + def validate_start_block(cls, value): + return value or 0 + + def model_dump(self, *args, **kwargs): + _Hash32 = Union[Hash32, HexBytes, HexStr] + topics = cast(Sequence[Optional[Union[_Hash32, Sequence[_Hash32]]]], self.topic_filter) + return FilterParams( + address=self.addresses, + fromBlock=to_hex(self.start_block), + toBlock=to_hex(self.stop_block or self.start_block), + topics=topics, + ) + + @classmethod + def from_event( + cls, + event: Union[EventABI, "ContractEvent"], + search_topics: Optional[dict[str, Any]] = None, + addresses: Optional[list[AddressType]] = None, + start_block=None, + stop_block=None, + ): + """ + Construct a log filter from an event topic query. + """ + from ape import convert + from ape.utils.abi import LogInputABICollection, is_dynamic_sized_type + + event_abi: EventABI = getattr(event, "abi", event) # type: ignore + search_topics = search_topics or {} + topic_filter: list[Optional[HexStr]] = [encode_hex(keccak(text=event_abi.selector))] + abi_inputs = LogInputABICollection(event_abi) + + def encode_topic_value(abi_type, value): + if isinstance(value, (list, tuple)): + return [encode_topic_value(abi_type, v) for v in value] + elif is_dynamic_sized_type(abi_type): + return encode_hex(keccak(encode_packed([str(abi_type)], [value]))) + elif abi_type == "address": + value = convert(value, AddressType) + + return encode_hex(encode([abi_type], [value])) + + for topic in abi_inputs.topic_abi_types: + if topic.name in search_topics: + encoded_value = encode_topic_value(topic.type, search_topics[topic.name]) + topic_filter.append(encoded_value) + else: + topic_filter.append(None) + + topic_names = [i.name for i in abi_inputs.topic_abi_types if i.name] + invalid_topics = set(search_topics) - set(topic_names) + if invalid_topics: + raise ValueError( + f"{event_abi.name} defines {', '.join(topic_names)} as indexed topics, " + f"but you provided {', '.join(invalid_topics)}" + ) + + # remove trailing wildcards since they have no effect + while topic_filter[-1] is None: + topic_filter.pop() + + return cls( + addresses=addresses or [], + events=[event_abi], + topic_filter=topic_filter, + start_block=start_block, + stop_block=stop_block, + ) + + +class BaseContractLog(BaseInterfaceModel): + """ + Base class representing information relevant to an event instance. + """ + + event_name: str + """The name of the event.""" + + contract_address: AddressType = ZERO_ADDRESS + """The contract responsible for emitting the log.""" + + event_arguments: dict[str, Any] = {} + """The arguments to the event, including both indexed and non-indexed data.""" + + def __eq__(self, other: Any) -> bool: + if self.contract_address != other.contract_address or self.event_name != other.event_name: + return False + + for k, v in self.event_arguments.items(): + other_v = other.event_arguments.get(k) + if v != other_v: + return False + + return True + + @field_serializer("event_arguments") + def _serialize_event_arguments(self, event_arguments, info): + """ + Because of an issue with BigInt in Pydantic, + (https://github.com/pydantic/pydantic/issues/10152) + we have to ensure these are regular ints. + """ + return self._serialize_value(event_arguments, info) + + def _serialize_value(self, value: Any, info) -> Any: + if isinstance(value, int): + # Handle custom ints. + return int(value) + + elif isinstance(value, HexBytes): + return to_hex(value) if info.mode == "json" else value + + elif isinstance(value, str): + # Avoiding str triggering iterable condition. + return value + + elif isinstance(value, dict): + # Also, avoid handling dict in the iterable case. + return {k: self._serialize_value(v, info) for k, v in value.items()} + + elif isinstance(value, Iterable): + return [self._serialize_value(v, info) for v in value] + + return value + + +class ContractLog(ExtraAttributesMixin, BaseContractLog): + """ + An instance of a log from a contract. + """ + + transaction_hash: Any + """The hash of the transaction containing this log.""" + + block_number: HexInt + """The number of the block containing the transaction that produced this log.""" + + block_hash: Any + """The hash of the block containing the transaction that produced this log.""" + + log_index: HexInt + """The index of the log on the transaction.""" + + transaction_index: Optional[HexInt] = None + """ + The index of the transaction's position when the log was created. + Is `None` when from the pending block. + """ + + @field_serializer("transaction_hash", "block_hash") + def _serialize_hashes(self, value, info): + return self._serialize_value(value, info) + + # NOTE: This class has an overridden `__getattr__` method, but `block` is a reserved keyword + # in most smart contract languages, so it is safe to use. Purposely avoid adding + # `.datetime` and `.timestamp` in case they are used as event arg names. + @cached_property + def block(self) -> "BlockAPI": + return self.chain_manager.blocks[self.block_number] + + @property + def timestamp(self) -> int: + """ + The UNIX timestamp of when the event was emitted. + + NOTE: This performs a block lookup. + """ + return self.block.timestamp + + @property + def _event_args_str(self) -> str: + return " ".join(f"{key}={val}" for key, val in self.event_arguments.items()) + + def __str__(self) -> str: + return f"{self.event_name}({self._event_args_str})" + + @log_instead_of_fail(default="") + def __repr__(self) -> str: + event_arg_str = self._event_args_str + suffix = f" {event_arg_str}" if event_arg_str else "" + return f"<{self.event_name}{suffix}>" + + def __ape_extra_attributes__(self) -> Iterator[ExtraModelAttributes]: + yield ExtraModelAttributes( + name=self.event_name, + attributes=lambda: self.event_arguments or {}, + include_getattr=True, + include_getitem=True, + ) + + def __contains__(self, item: str) -> bool: + return item in self.event_arguments + + def __eq__(self, other: Any) -> bool: + """ + Check for equality between this instance and another ContractLog instance. + + If the other object is not an instance of ContractLog, this method returns + NotImplemented. This triggers the Python interpreter to call the __eq__ method + on the other object (i.e., y.__eq__(x)) if it is defined, allowing for a custom + comparison. This behavior is leveraged by the MockContractLog class to handle + custom comparison logic between ContractLog and MockContractLog instances. + + Args: + other (Any): The object to compare with this instance. + + Returns: + bool: True if the two instances are equal, False otherwise. + """ + + if not isinstance(other, ContractLog): + return NotImplemented + + # call __eq__ on parent class + return super().__eq__(other) + + def get(self, item: str, default: Optional[Any] = None) -> Any: + return self.event_arguments.get(item, default) + + +def _equal_event_inputs(mock_input: Any, real_input: Any) -> bool: + if mock_input is None: + # Check is skipped. + return True + + elif isinstance(mock_input, (list, tuple)): + if not isinstance(real_input, (list, tuple)) or len(real_input) != len(mock_input): + return False + + return all(_equal_event_inputs(m, r) for m, r in zip(mock_input, real_input)) + + else: + return mock_input == real_input + + +class MockContractLog(BaseContractLog): + """ + A mock version of the ContractLog class used for testing purposes. + This class is designed to match a subset of event arguments in a ContractLog instance + by only comparing those event arguments that the user explicitly provides. + + Inherits from :class:`~ape.types.BaseContractLog`, and overrides the + equality method for custom comparison + of event arguments between a MockContractLog and a ContractLog instance. + """ + + def __eq__(self, other: Any) -> bool: + if ( + not hasattr(other, "contract_address") + or not hasattr(other, "event_name") + or self.contract_address != other.contract_address + or self.event_name != other.event_name + ): + return False + + # NOTE: `self.event_arguments` contains a subset of items from `other.event_arguments`, + # but we skip those the user doesn't care to check + for name, value in self.event_arguments.items(): + other_input = other.event_arguments.get(name) + if not _equal_event_inputs(value, other_input): + # Only exit on False; Else, keep checking. + return False + + return True + + +class ContractLogContainer(list): + """ + Container for ContractLogs which is adding capability of filtering logs + """ + + def filter(self, event: "ContractEvent", **kwargs) -> list[ContractLog]: + return [ + x + for x in self + if x.event_name == event.name + and x.contract_address == event.contract + and all(v == x.event_arguments.get(k) and v is not None for k, v in kwargs.items()) + ] + + def __contains__(self, val: Any) -> bool: + return any(log == val for log in self) diff --git a/src/ape/types/gas.py b/src/ape/types/gas.py new file mode 100644 index 0000000000..f7c87244df --- /dev/null +++ b/src/ape/types/gas.py @@ -0,0 +1,31 @@ +from typing import Literal, Union + +from pydantic import BaseModel, field_validator + + +class AutoGasLimit(BaseModel): + """ + Additional settings for ``gas_limit: auto``. + """ + + multiplier: float = 1.0 + """ + A multiplier to estimated gas. + """ + + @field_validator("multiplier", mode="before") + @classmethod + 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, +``"max"`` refers to using the maximum block gas limit, +and otherwise you can provide a numeric value. +""" diff --git a/src/ape/types/signatures.py b/src/ape/types/signatures.py index cec7811ef2..60db85fcb1 100644 --- a/src/ape/types/signatures.py +++ b/src/ape/types/signatures.py @@ -7,7 +7,7 @@ from eth_utils import to_bytes, to_hex from pydantic.dataclasses import dataclass -from ape.utils import as_our_module, log_instead_of_fail +from ape.utils.misc import as_our_module, log_instead_of_fail try: # Only on Python 3.11 @@ -15,7 +15,7 @@ except ImportError: from typing_extensions import Self # type: ignore -from ape.types import AddressType +from ape.types.address import AddressType # Fix 404 in doc link. as_our_module( diff --git a/src/ape/types/trace.py b/src/ape/types/trace.py index f314648055..dfa65ddeb7 100644 --- a/src/ape/types/trace.py +++ b/src/ape/types/trace.py @@ -15,6 +15,7 @@ Statement, ) from pydantic import RootModel +from pydantic.dataclasses import dataclass from ape.utils.misc import log_instead_of_fail @@ -533,3 +534,28 @@ def _add( statements=[statement], source_path=source_path, closure=function, depth=depth ) self.append(exec_sequence) + + +@dataclass +class ContractFunctionPath: + """ + Useful for identifying a method in a contract. + """ + + contract_name: str + method_name: Optional[str] = None + + @classmethod + def from_str(cls, value: str) -> "ContractFunctionPath": + if ":" in value: + contract_name, method_name = value.split(":") + return cls(contract_name=contract_name, method_name=method_name) + + return cls(contract_name=value) + + def __str__(self) -> str: + return f"{self.contract_name}:{self.method_name}" + + @log_instead_of_fail(default="") + def __repr__(self) -> str: + return f"<{self}>" diff --git a/src/ape/types/units.py b/src/ape/types/units.py new file mode 100644 index 0000000000..c81d122a4c --- /dev/null +++ b/src/ape/types/units.py @@ -0,0 +1,80 @@ +from typing import Any, Optional + +from pydantic_core.core_schema import ( + CoreSchema, + ValidationInfo, + int_schema, + no_info_plain_validator_function, + plain_serializer_function_ser_schema, +) +from typing_extensions import TypeAlias + +from ape.exceptions import ConversionError +from ape.utils.basemodel import ManagerAccessMixin + + +class CurrencyValueComparable(int): + """ + An integer you can compare with currency-value + strings, such as ``"1 ether"``. + """ + + def __eq__(self, other: Any) -> bool: + if isinstance(other, int): + return super().__eq__(other) + + elif isinstance(other, str): + try: + other_value = ManagerAccessMixin.conversion_manager.convert(other, int) + except ConversionError: + # Not a currency-value, it's ok. + return False + + return super().__eq__(other_value) + + # Try from the other end, if hasn't already. + return NotImplemented + + def __hash__(self) -> int: + return hash(int(self)) + + @classmethod + def __get_pydantic_core_schema__(cls, value, handler=None) -> CoreSchema: + return no_info_plain_validator_function( + cls._validate, + serialization=plain_serializer_function_ser_schema( + cls._serialize, + info_arg=False, + return_schema=int_schema(), + ), + ) + + @staticmethod + def _validate(value: Any, info: Optional[ValidationInfo] = None) -> "CurrencyValueComparable": + # NOTE: For some reason, for this to work, it has to happen + # in an "after" validator, or else it always only `int` type on the model. + if value is None: + # Will fail if not optional. + # Type ignore because this is an hacky and unlikely situation. + return None # type: ignore + + elif isinstance(value, str) and " " in value: + return ManagerAccessMixin.conversion_manager.convert(value, int) + + # For models annotating with this type, we validate all integers into it. + return CurrencyValueComparable(value) + + @staticmethod + def _serialize(value): + return int(value) + + +CurrencyValueComparable.__name__ = int.__name__ + + +CurrencyValue: TypeAlias = CurrencyValueComparable +""" +An alias to :class:`~ape.types.CurrencyValueComparable` for +situations when you know for sure the type is a currency-value +(and not just comparable to one). +""" diff --git a/src/ape/types/vm.py b/src/ape/types/vm.py new file mode 100644 index 0000000000..95556ed916 --- /dev/null +++ b/src/ape/types/vm.py @@ -0,0 +1,25 @@ +from typing import Literal, Union + +from eth_typing import HexStr +from hexbytes import HexBytes + +BlockID = Union[int, HexStr, HexBytes, Literal["earliest", "latest", "pending"]] +""" +An ID that can match a block, such as the literals ``"earliest"``, ``"latest"``, or ``"pending"`` +as well as a block number or hash (HexBytes). +""" + +ContractCode = Union[str, bytes, HexBytes] +""" +A type that represents contract code, which can be represented in string, bytes, or HexBytes. +""" + +SnapshotID = Union[str, int, bytes] +""" +An ID representing a point in time on a blockchain, as used in the +:meth:`~ape.managers.chain.ChainManager.snapshot` and +:meth:`~ape.managers.chain.ChainManager.snapshot` methods. Can be a ``str``, ``int``, or ``bytes``. +Providers will expect and handle snapshot IDs differently. There shouldn't be a need to change +providers when using this feature, so there should not be confusion over this type in practical use +cases. +""" diff --git a/src/ape/utils/misc.py b/src/ape/utils/misc.py index 281900bf79..ee99d6f761 100644 --- a/src/ape/utils/misc.py +++ b/src/ape/utils/misc.py @@ -30,7 +30,7 @@ from ape.utils.os import expand_environment_variables if TYPE_CHECKING: - from ape.types import AddressType + from ape.types.address import AddressType EMPTY_BYTES32 = HexBytes("0x0000000000000000000000000000000000000000000000000000000000000000") diff --git a/src/ape/utils/trace.py b/src/ape/utils/trace.py index a896c3664e..70ad46ab3a 100644 --- a/src/ape/utils/trace.py +++ b/src/ape/utils/trace.py @@ -9,7 +9,8 @@ from ape.utils import is_evm_precompile, is_zero_hex if TYPE_CHECKING: - from ape.types import ContractFunctionPath, CoverageReport, GasReport + from ape.types.coverage import CoverageReport + from ape.types.trace import ContractFunctionPath, GasReport USER_ASSERT_TAG = "USER_ASSERT" diff --git a/src/ape_accounts/accounts.py b/src/ape_accounts/accounts.py index b07ff301a3..aea2f66f60 100644 --- a/src/ape_accounts/accounts.py +++ b/src/ape_accounts/accounts.py @@ -15,10 +15,12 @@ from eth_pydantic_types import HexBytes from eth_utils import to_bytes, to_hex -from ape.api import AccountAPI, AccountContainerAPI, TransactionAPI +from ape.api.accounts import AccountAPI, AccountContainerAPI +from ape.api.transactions import TransactionAPI from ape.exceptions import AccountsError from ape.logging import logger -from ape.types import AddressType, MessageSignature, SignableMessage, TransactionSignature +from ape.types.address import AddressType +from ape.types.signatures import MessageSignature, SignableMessage, TransactionSignature from ape.utils.basemodel import ManagerAccessMixin from ape.utils.misc import log_instead_of_fail from ape.utils.validators import _validate_account_alias, _validate_account_passphrase diff --git a/src/ape_cache/query.py b/src/ape_cache/query.py index 81da0fe87a..30e32aeab7 100644 --- a/src/ape_cache/query.py +++ b/src/ape_cache/query.py @@ -20,7 +20,7 @@ from ape.api.transactions import TransactionAPI from ape.exceptions import QueryEngineError from ape.logging import logger -from ape.types import ContractLog +from ape.types.events import ContractLog from ape.utils.misc import LOCAL_NETWORK_NAME from . import models diff --git a/src/ape_console/plugin.py b/src/ape_console/plugin.py index 30802abfb8..532d4c4b72 100644 --- a/src/ape_console/plugin.py +++ b/src/ape_console/plugin.py @@ -1,4 +1,5 @@ import shlex +from functools import cached_property from pathlib import Path import click @@ -13,8 +14,8 @@ from ape.exceptions import Abort, ApeException, handle_ape_exception from ape.logging import logger from ape.managers.project import LocalProject -from ape.types import AddressType -from ape.utils import ManagerAccessMixin, cached_property +from ape.types.address import AddressType +from ape.utils.basemodel import ManagerAccessMixin from ape.utils.os import clean_path diff --git a/src/ape_ethereum/_converters.py b/src/ape_ethereum/_converters.py index 9e0ad6572a..25c2d1639a 100644 --- a/src/ape_ethereum/_converters.py +++ b/src/ape_ethereum/_converters.py @@ -2,7 +2,7 @@ from decimal import Decimal from ape.api.convert import ConverterAPI -from ape.types import CurrencyValue +from ape.types.units import CurrencyValue ETHER_UNITS = { "eth": int(1e18), diff --git a/src/ape_ethereum/ecosystem.py b/src/ape_ethereum/ecosystem.py index 1176f49041..f08c7a980d 100644 --- a/src/ape_ethereum/ecosystem.py +++ b/src/ape_ethereum/ecosystem.py @@ -42,16 +42,12 @@ ) from ape.logging import logger from ape.managers.config import merge_configs -from ape.types import ( - AddressType, - AutoGasLimit, - ContractLog, - CurrencyValueComparable, - GasLimit, - HexInt, - RawAddress, - TransactionSignature, -) +from ape.types.address import AddressType, RawAddress +from ape.types.basic import HexInt +from ape.types.events import ContractLog +from ape.types.gas import AutoGasLimit, GasLimit +from ape.types.signatures import TransactionSignature +from ape.types.units import CurrencyValueComparable from ape.utils.abi import LogInputABICollection, Struct, StructParser, is_array, returns_array from ape.utils.basemodel import _assert_not_ipython_check, only_raise_attribute_error from ape.utils.misc import ( diff --git a/src/ape_ethereum/multicall/constants.py b/src/ape_ethereum/multicall/constants.py index d2c82a8a1b..dacb7f7e55 100644 --- a/src/ape_ethereum/multicall/constants.py +++ b/src/ape_ethereum/multicall/constants.py @@ -1,7 +1,9 @@ from copy import deepcopy from typing import cast -from ape.types import AddressType, HexBytes +from eth_pydantic_types import HexBytes + +from ape.types.address import AddressType SUPPORTED_CHAINS = [ 1, diff --git a/src/ape_ethereum/multicall/handlers.py b/src/ape_ethereum/multicall/handlers.py index 62490a6002..ae6ed6ec96 100644 --- a/src/ape_ethereum/multicall/handlers.py +++ b/src/ape_ethereum/multicall/handlers.py @@ -1,7 +1,11 @@ from collections.abc import Iterator +from functools import cached_property from types import ModuleType from typing import Any, Optional, Union +from eth_pydantic_types import HexBytes +from ethpm_types import ContractType + from ape.api import ReceiptAPI, TransactionAPI from ape.contracts.base import ( ContractCallHandler, @@ -12,9 +16,9 @@ ) from ape.exceptions import ChainError, DecodingError from ape.logging import logger -from ape.types import AddressType, ContractType, HexBytes -from ape.utils import ManagerAccessMixin, cached_property +from ape.types.address import AddressType from ape.utils.abi import MethodABI +from ape.utils.basemodel import ManagerAccessMixin from .constants import ( MULTICALL3_ADDRESS, diff --git a/src/ape_ethereum/provider.py b/src/ape_ethereum/provider.py index 2f5f2af284..63d4e16ba0 100644 --- a/src/ape_ethereum/provider.py +++ b/src/ape_ethereum/provider.py @@ -37,7 +37,10 @@ from web3.providers.auto import load_provider_from_environment from web3.types import FeeHistory, RPCEndpoint, TxParams -from ape.api import Address, BlockAPI, ProviderAPI, ReceiptAPI, TraceAPI, TransactionAPI +from ape.api.address import Address +from ape.api.providers import BlockAPI, ProviderAPI +from ape.api.trace import TraceAPI +from ape.api.transactions import ReceiptAPI, TransactionAPI from ape.exceptions import ( ApeException, APINotImplementedError, @@ -53,17 +56,13 @@ VirtualMachineError, ) from ape.logging import logger, sanitize_url -from ape.types import ( - AddressType, - AutoGasLimit, - BlockID, - ContractCode, - ContractLog, - LogFilter, - SourceTraceback, -) -from ape.utils import ManagerAccessMixin, gas_estimation_error_message, to_int -from ape.utils.misc import DEFAULT_MAX_RETRIES_TX +from ape.types.address import AddressType +from ape.types.events import ContractLog, LogFilter +from ape.types.gas import AutoGasLimit +from ape.types.trace import SourceTraceback +from ape.types.vm import BlockID, ContractCode +from ape.utils.basemodel import ManagerAccessMixin +from ape.utils.misc import DEFAULT_MAX_RETRIES_TX, gas_estimation_error_message, to_int from ape_ethereum._print import CONSOLE_ADDRESS, console_contract from ape_ethereum.trace import CallTrace, TraceApproach, TransactionTrace from ape_ethereum.transactions import AccessList, AccessListTransaction, TransactionStatusEnum diff --git a/src/ape_ethereum/query.py b/src/ape_ethereum/query.py index fd5d8b046c..0cece4e740 100644 --- a/src/ape_ethereum/query.py +++ b/src/ape_ethereum/query.py @@ -4,7 +4,7 @@ from ape.api.query import ContractCreation, ContractCreationQuery, QueryAPI, QueryType from ape.exceptions import APINotImplementedError, ProviderError, QueryEngineError -from ape.types import AddressType +from ape.types.address import AddressType class EthereumQueryProvider(QueryAPI): diff --git a/src/ape_ethereum/trace.py b/src/ape_ethereum/trace.py index ee9aa7a184..51e8cf3565 100644 --- a/src/ape_ethereum/trace.py +++ b/src/ape_ethereum/trace.py @@ -24,11 +24,14 @@ from pydantic import field_validator from rich.tree import Tree -from ape.api import EcosystemAPI, TraceAPI, TransactionAPI +from ape.api.networks import EcosystemAPI +from ape.api.trace import TraceAPI +from ape.api.transactions import TransactionAPI from ape.exceptions import ContractLogicError, ProviderError, TransactionNotFoundError from ape.logging import get_rich_console, logger -from ape.types import AddressType, ContractFunctionPath, GasReport -from ape.utils import ZERO_ADDRESS, is_evm_precompile, is_zero_hex, log_instead_of_fail +from ape.types.address import AddressType +from ape.types.trace import ContractFunctionPath, GasReport +from ape.utils.misc import ZERO_ADDRESS, is_evm_precompile, is_zero_hex, log_instead_of_fail from ape.utils.trace import TraceStyles, _exclude_gas from ape_ethereum._print import extract_debug_logs diff --git a/src/ape_ethereum/transactions.py b/src/ape_ethereum/transactions.py index 652088c9e6..4385973469 100644 --- a/src/ape_ethereum/transactions.py +++ b/src/ape_ethereum/transactions.py @@ -15,12 +15,15 @@ from ethpm_types.abi import EventABI, MethodABI from pydantic import BaseModel, Field, field_validator, model_validator -from ape.api import ReceiptAPI, TransactionAPI +from ape.api.transactions import ReceiptAPI, TransactionAPI from ape.contracts import ContractEvent from ape.exceptions import OutOfGasError, SignatureError, TransactionError from ape.logging import logger -from ape.types import AddressType, ContractLog, ContractLogContainer, HexInt, SourceTraceback -from ape.utils import ZERO_ADDRESS +from ape.types.address import AddressType +from ape.types.basic import HexInt +from ape.types.events import ContractLog, ContractLogContainer +from ape.types.trace import SourceTraceback +from ape.utils.misc import ZERO_ADDRESS from ape_ethereum.trace import Trace, _events_to_trees diff --git a/src/ape_networks/_cli.py b/src/ape_networks/_cli.py index 803f339428..68e9beb092 100644 --- a/src/ape_networks/_cli.py +++ b/src/ape_networks/_cli.py @@ -6,13 +6,12 @@ from rich import print as echo_rich_text from rich.tree import Tree -from ape.api import SubprocessProvider -from ape.cli import ape_cli_context, network_option +from ape.api.providers import SubprocessProvider from ape.cli.choices import OutputFormat -from ape.cli.options import output_format_option +from ape.cli.options import ape_cli_context, network_option, output_format_option from ape.exceptions import NetworkError from ape.logging import LogLevel -from ape.types import _LazySequence +from ape.types.basic import _LazySequence from ape.utils.basemodel import ManagerAccessMixin diff --git a/src/ape_node/provider.py b/src/ape_node/provider.py index 68067359f7..6f459324ff 100644 --- a/src/ape_node/provider.py +++ b/src/ape_node/provider.py @@ -16,9 +16,11 @@ from web3.middleware import geth_poa_middleware as ExtraDataToPOAMiddleware from yarl import URL -from ape.api import PluginConfig, SubprocessProvider, TestAccountAPI, TestProviderAPI +from ape.api.accounts import TestAccountAPI +from ape.api.config import PluginConfig +from ape.api.providers import SubprocessProvider, TestProviderAPI from ape.logging import LogLevel, logger -from ape.types import SnapshotID +from ape.types.vm import SnapshotID from ape.utils.misc import ZERO_ADDRESS, log_instead_of_fail, raises_not_implemented from ape.utils.process import JoinableQueue, spawn from ape.utils.testing import ( diff --git a/src/ape_test/accounts.py b/src/ape_test/accounts.py index 690fb4f1b7..0dbe558250 100644 --- a/src/ape_test/accounts.py +++ b/src/ape_test/accounts.py @@ -10,10 +10,12 @@ from eth_pydantic_types import HexBytes from eth_utils import to_bytes, to_hex -from ape.api import TestAccountAPI, TestAccountContainerAPI, TransactionAPI +from ape.api.accounts import TestAccountAPI, TestAccountContainerAPI +from ape.api.transactions import TransactionAPI from ape.exceptions import ProviderNotConnectedError, SignatureError -from ape.types import AddressType, MessageSignature, TransactionSignature -from ape.utils import ( +from ape.types.address import AddressType +from ape.types.signatures import MessageSignature, TransactionSignature +from ape.utils.testing import ( DEFAULT_NUMBER_OF_TEST_ACCOUNTS, DEFAULT_TEST_HD_PATH, DEFAULT_TEST_MNEMONIC, diff --git a/src/ape_test/provider.py b/src/ape_test/provider.py index 673f1a38a2..4f9800ac02 100644 --- a/src/ape_test/provider.py +++ b/src/ape_test/provider.py @@ -17,7 +17,10 @@ from web3.providers.eth_tester.defaults import API_ENDPOINTS, static_return from web3.types import TxParams -from ape.api import BlockAPI, PluginConfig, ReceiptAPI, TestProviderAPI, TraceAPI, TransactionAPI +from ape.api.config import PluginConfig +from ape.api.providers import BlockAPI, TestProviderAPI +from ape.api.trace import TraceAPI +from ape.api.transactions import ReceiptAPI, TransactionAPI from ape.exceptions import ( APINotImplementedError, ContractLogicError, @@ -28,8 +31,11 @@ VirtualMachineError, ) from ape.logging import logger -from ape.types import AddressType, BlockID, ContractLog, LogFilter, SnapshotID -from ape.utils import DEFAULT_TEST_CHAIN_ID, DEFAULT_TEST_HD_PATH, gas_estimation_error_message +from ape.types.address import AddressType +from ape.types.events import ContractLog, LogFilter +from ape.types.vm import BlockID, SnapshotID +from ape.utils.misc import gas_estimation_error_message +from ape.utils.testing import DEFAULT_TEST_CHAIN_ID, DEFAULT_TEST_HD_PATH from ape_ethereum.provider import Web3Provider from ape_ethereum.trace import TraceApproach, TransactionTrace diff --git a/tests/conftest.py b/tests/conftest.py index 24f3fcba32..5b5cb641ab 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -19,9 +19,11 @@ from ape.managers.project import Project from ape.pytest.config import ConfigWrapper from ape.pytest.gas import GasTracker -from ape.types import AddressType, CurrencyValue -from ape.utils import DEFAULT_TEST_CHAIN_ID, ZERO_ADDRESS +from ape.types.address import AddressType +from ape.types.units import CurrencyValue from ape.utils.basemodel import only_raise_attribute_error +from ape.utils.misc import ZERO_ADDRESS +from ape.utils.testing import DEFAULT_TEST_CHAIN_ID # Needed to test tracing support in core `ape test` command. pytest_plugins = ["pytester"] diff --git a/tests/functional/conftest.py b/tests/functional/conftest.py index 75528f15ee..d06dd7bee0 100644 --- a/tests/functional/conftest.py +++ b/tests/functional/conftest.py @@ -17,7 +17,8 @@ from ape.exceptions import ChainError, ContractLogicError, ProviderError from ape.logging import LogLevel from ape.logging import logger as _logger -from ape.types import AddressType, ContractLog +from ape.types.address import AddressType +from ape.types.events import ContractLog from ape.utils.misc import LOCAL_NETWORK_NAME from ape_ethereum.proxies import minimal_proxy as _minimal_proxy_container diff --git a/tests/functional/conversion/test_address.py b/tests/functional/conversion/test_address.py index 9a09a9148a..5b2ed07399 100644 --- a/tests/functional/conversion/test_address.py +++ b/tests/functional/conversion/test_address.py @@ -1,7 +1,7 @@ import pytest from ape.managers.converters import HexAddressConverter, IntAddressConverter -from ape.types import AddressType +from ape.types.address import AddressType def test_convert_keyfile_account_to_address(convert, keyfile_account): diff --git a/tests/functional/conversion/test_encode_structs.py b/tests/functional/conversion/test_encode_structs.py index f131a33c9a..b4d1383e1a 100644 --- a/tests/functional/conversion/test_encode_structs.py +++ b/tests/functional/conversion/test_encode_structs.py @@ -5,7 +5,7 @@ from ethpm_types import BaseModel from ethpm_types.abi import MethodABI -from ape.types import AddressType +from ape.types.address import AddressType ABI = MethodABI.model_validate( { diff --git a/tests/functional/test_accounts.py b/tests/functional/test_accounts.py index e7a0a50534..6f023d09d3 100644 --- a/tests/functional/test_accounts.py +++ b/tests/functional/test_accounts.py @@ -18,9 +18,9 @@ ProjectError, SignatureError, ) -from ape.types import AutoGasLimit +from ape.types.gas import AutoGasLimit from ape.types.signatures import recover_signer -from ape_accounts import ( +from ape_accounts.accounts import ( KeyfileAccount, generate_account, import_account_from_mnemonic, diff --git a/tests/functional/test_address.py b/tests/functional/test_address.py index 75d91e5a62..84ffc081ba 100644 --- a/tests/functional/test_address.py +++ b/tests/functional/test_address.py @@ -2,7 +2,7 @@ from pydantic import BaseModel from ape.api.address import Address, BaseAddress -from ape.types import AddressType +from ape.types.address import AddressType @pytest.fixture diff --git a/tests/functional/test_chain.py b/tests/functional/test_chain.py index cae7e2f7d3..95c8ca2b48 100644 --- a/tests/functional/test_chain.py +++ b/tests/functional/test_chain.py @@ -5,7 +5,7 @@ from ape.exceptions import APINotImplementedError, ChainError, UnknownSnapshotError from ape.managers.chain import AccountHistory -from ape.types import AddressType +from ape.types.address import AddressType def test_snapshot_and_restore(chain, owner, receiver, vyper_contract_instance): diff --git a/tests/functional/test_compilers.py b/tests/functional/test_compilers.py index 149d2b119e..48ed938828 100644 --- a/tests/functional/test_compilers.py +++ b/tests/functional/test_compilers.py @@ -7,7 +7,7 @@ from ape.contracts import ContractContainer from ape.exceptions import APINotImplementedError, CompilerError, ContractLogicError, CustomError -from ape.types import AddressType +from ape.types.address import AddressType from ape_compile import Config diff --git a/tests/functional/test_config.py b/tests/functional/test_config.py index 7612446f23..2c90a5ba06 100644 --- a/tests/functional/test_config.py +++ b/tests/functional/test_config.py @@ -10,8 +10,8 @@ from ape.api.config import ApeConfig, ConfigEnum, PluginConfig from ape.exceptions import ConfigError from ape.managers.config import CONFIG_FILE_NAME, merge_configs -from ape.types import GasLimit -from ape.utils import create_tempdir +from ape.types.gas import GasLimit +from ape.utils.os import create_tempdir from ape_ethereum.ecosystem import EthereumConfig, NetworkConfig from ape_networks import CustomNetwork from tests.functional.conftest import PROJECT_WITH_LONG_CONTRACTS_FOLDER diff --git a/tests/functional/test_contract_event.py b/tests/functional/test_contract_event.py index 3bd4977897..531366dedf 100644 --- a/tests/functional/test_contract_event.py +++ b/tests/functional/test_contract_event.py @@ -8,9 +8,10 @@ from eth_utils import to_hex from ethpm_types import ContractType -from ape.api import ReceiptAPI +from ape.api.transactions import ReceiptAPI from ape.exceptions import ProviderError -from ape.types import ContractLog, CurrencyValueComparable +from ape.types.events import ContractLog +from ape.types.units import CurrencyValueComparable @pytest.fixture diff --git a/tests/functional/test_contract_instance.py b/tests/functional/test_contract_instance.py index ba93b9c435..b0e50cb156 100644 --- a/tests/functional/test_contract_instance.py +++ b/tests/functional/test_contract_instance.py @@ -18,7 +18,7 @@ CustomError, MethodNonPayableError, ) -from ape.types import AddressType +from ape.types.address import AddressType from ape_ethereum.transactions import TransactionStatusEnum, TransactionType MATCH_TEST_CONTRACT = re.compile(r"