diff --git a/setup.cfg b/setup.cfg index 6492cbd8be..664d5c307e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -5,6 +5,7 @@ exclude = docs build .eggs + tests/integration/cli/projects per-file-ignores = # Need signal handler before imports src/ape/__init__.py: E402 diff --git a/src/ape/api/address.py b/src/ape/api/address.py index ae4e460e3b..9a14195bac 100644 --- a/src/ape/api/address.py +++ b/src/ape/api/address.py @@ -5,7 +5,6 @@ from ape.exceptions import ConversionError from ape.types import AddressType, ContractCode from ape.utils import BaseInterface, abstractmethod, cached_property -from ape.utils.abi import _convert_kwargs if TYPE_CHECKING: from ape.api.transactions import ReceiptAPI, TransactionAPI @@ -167,7 +166,7 @@ def history(self) -> "AccountHistory": return self.chain_manager.history[self.address] def as_transaction(self, **kwargs) -> "TransactionAPI": - converted_kwargs = _convert_kwargs(kwargs, self.conversion_manager.convert) + converted_kwargs = self.conversion_manager.convert_method_kwargs(kwargs) return self.provider.network.ecosystem.create_transaction( receiver=self.address, **converted_kwargs ) diff --git a/src/ape/api/networks.py b/src/ape/api/networks.py index e78d614922..afcb5232e7 100644 --- a/src/ape/api/networks.py +++ b/src/ape/api/networks.py @@ -115,7 +115,7 @@ def encode_contract_blueprint( # type: ignore[empty-body] or Starknet's ``Declare`` transaction type. Args: - contract (``ContractType``): The type of contract to create a blueprint for. + contract_type (``ContractType``): The type of contract to create a blueprint for. This is the type of contract that will get created by factory contracts. *args: Calldata, if applicable. **kwargs: Transaction specifications, such as ``value``. @@ -686,7 +686,7 @@ def create_adhoc_network(cls) -> "NetworkAPI": return cls( name="adhoc", ecosystem=ethereum, - data_folder=data_folder, + data_folder=Path(data_folder), request_header=request_header, _default_provider="geth", ) diff --git a/src/ape/api/projects.py b/src/ape/api/projects.py index 9c6b99cb45..f41524858b 100644 --- a/src/ape/api/projects.py +++ b/src/ape/api/projects.py @@ -6,7 +6,7 @@ from ethpm_types import Checksum, ContractType, PackageManifest, Source from ethpm_types.manifest import PackageName -from ethpm_types.utils import AnyUrl, compute_checksum +from ethpm_types.utils import Algorithm, AnyUrl, compute_checksum from packaging.version import InvalidVersion, Version from pydantic import ValidationError @@ -210,7 +210,7 @@ def _create_source_dict( source_dict[key] = Source( checksum=Checksum( - algorithm="md5", + algorithm=Algorithm.MD5, hash=compute_checksum(source_path.read_bytes()), ), urls=[], diff --git a/src/ape/api/providers.py b/src/ape/api/providers.py index 21b251e743..57abd9d6e9 100644 --- a/src/ape/api/providers.py +++ b/src/ape/api/providers.py @@ -104,7 +104,7 @@ def convert_parent_hash(cls, data): @validator("hash", "parent_hash", pre=True) def validate_hexbytes(cls, value): # NOTE: pydantic treats these values as bytes and throws an error - if value and not isinstance(value, HexBytes): + if value and not isinstance(value, bytes): return HexBytes(value) return value diff --git a/src/ape/api/query.py b/src/ape/api/query.py index d9f9272764..19e1a98de5 100644 --- a/src/ape/api/query.py +++ b/src/ape/api/query.py @@ -94,7 +94,7 @@ def extract_fields(item, columns): class _BaseQuery(BaseModel): - columns: List[str] + columns: Sequence[str] # TODO: Support "*" from getting the EcosystemAPI fields diff --git a/src/ape/api/transactions.py b/src/ape/api/transactions.py index 2b85df8d2e..eac25a2db9 100644 --- a/src/ape/api/transactions.py +++ b/src/ape/api/transactions.py @@ -118,7 +118,7 @@ def total_transfer_value(self) -> int: to submit the transaction. """ if self.max_fee is None: - raise TransactionError("Max fee must not be null.") + raise TransactionError("`self.max_fee` must not be None.") return self.value + self.max_fee diff --git a/src/ape/contracts/base.py b/src/ape/contracts/base.py index 1a1462c9bc..d9f4c04ae8 100644 --- a/src/ape/contracts/base.py +++ b/src/ape/contracts/base.py @@ -25,7 +25,7 @@ from ape.logging import logger from ape.types import AddressType, ContractLog, LogFilter, MockContractLog from ape.utils import ManagerAccessMixin, cached_property, singledispatchmethod -from ape.utils.abi import StructParser, _convert_args, _convert_kwargs +from ape.utils.abi import StructParser class ContractConstructor(ManagerAccessMixin): @@ -54,10 +54,10 @@ def decode_input(self, calldata: bytes) -> Tuple[str, Dict[str, Any]]: return self.abi.selector, decoded_inputs def serialize_transaction(self, *args, **kwargs) -> TransactionAPI: - arguments = _convert_args(args, self.conversion_manager.convert, self.abi) - kwargs = _convert_kwargs(kwargs, self.conversion_manager.convert) + arguments = self.conversion_manager.convert_method_args(self.abi, args) + converted_kwargs = self.conversion_manager.convert_method_kwargs(kwargs) return self.provider.network.ecosystem.encode_deployment( - self.deployment_bytecode, self.abi, *arguments, **kwargs + self.deployment_bytecode, self.abi, *arguments, **converted_kwargs ) def __call__(self, private: bool = False, *args, **kwargs) -> ReceiptAPI: @@ -86,9 +86,9 @@ def __repr__(self) -> str: return self.abi.signature def serialize_transaction(self, *args, **kwargs) -> TransactionAPI: - kwargs = _convert_kwargs(kwargs, self.conversion_manager.convert) + converted_kwargs = self.conversion_manager.convert_method_kwargs(kwargs) return self.provider.network.ecosystem.encode_transaction( - self.address, self.abi, *args, **kwargs + self.address, self.abi, *args, **converted_kwargs ) def __call__(self, *args, **kwargs) -> Any: @@ -130,9 +130,9 @@ def __str__(self) -> str: def encode_input(self, *args) -> HexBytes: selected_abi = _select_method_abi(self.abis, args) - args = self._convert_tuple(args, selected_abi) + arguments = self.conversion_manager.convert_method_args(selected_abi, args) ecosystem = self.provider.network.ecosystem - encoded_calldata = ecosystem.encode_calldata(selected_abi, *args) + encoded_calldata = ecosystem.encode_calldata(selected_abi, *arguments) method_id = ecosystem.get_method_selector(selected_abi) return HexBytes(method_id + encoded_calldata) @@ -179,9 +179,6 @@ def decode_input(self, calldata: bytes) -> Tuple[str, Dict[str, Any]]: raise err - def _convert_tuple(self, v: tuple, abi) -> tuple: - return _convert_args(v, self.conversion_manager.convert, abi) - class ContractCallHandler(ContractMethodHandler): def __call__(self, *args, **kwargs) -> Any: @@ -190,12 +187,12 @@ def __call__(self, *args, **kwargs) -> Any: raise _get_non_contract_error(self.contract.address, network) selected_abi = _select_method_abi(self.abis, args) - args = self._convert_tuple(args, selected_abi) + arguments = self.conversion_manager.convert_method_args(selected_abi, args) return ContractCall( abi=selected_abi, address=self.contract.address, - )(*args, **kwargs) + )(*arguments, **kwargs) def as_transaction(self, *args, **kwargs): """ @@ -236,7 +233,7 @@ def estimate_gas_cost(self, *args, **kwargs) -> int: """ selected_abi = _select_method_abi(self.abis, args) - arguments = _convert_args(args, self.conversion_manager.convert, selected_abi) + arguments = self.conversion_manager.convert_method_args(selected_abi, args) return self.transact.estimate_gas_cost(*arguments, **kwargs) @@ -271,10 +268,10 @@ def serialize_transaction(self, *args, **kwargs) -> TransactionAPI: # Automatically impersonate contracts (if API available) when sender kwargs["sender"] = self.account_manager.test_accounts[kwargs["sender"].address] - arguments = _convert_args(args, self.conversion_manager.convert, self.abi) - kwargs = _convert_kwargs(kwargs, self.conversion_manager.convert) + arguments = self.conversion_manager.convert_method_args(self.abi, args) + converted_kwargs = self.conversion_manager.convert_method_kwargs(kwargs) return self.provider.network.ecosystem.encode_transaction( - self.address, self.abi, *arguments, **kwargs + self.address, self.abi, *arguments, **converted_kwargs ) def __call__(self, *args, **kwargs) -> ReceiptAPI: @@ -328,7 +325,7 @@ def estimate_gas_cost(self, *args, **kwargs) -> int: reported in the fee-currency's smallest unit, e.g. Wei. """ selected_abi = _select_method_abi(self.abis, args) - arguments = _convert_args(args, self.conversion_manager.convert, selected_abi) + arguments = self.conversion_manager.convert_method_args(selected_abi, args) txn = self.as_transaction(*arguments, **kwargs) return self.provider.estimate_gas_cost(txn) @@ -356,7 +353,6 @@ def _as_transaction(self, *args) -> ContractTransaction: raise _get_non_contract_error(self.contract.address, network) selected_abi = _select_method_abi(self.abis, args) - args = self._convert_tuple(args, selected_abi) return ContractTransaction( abi=selected_abi, @@ -500,7 +496,7 @@ def __call__(self, *args: Any, **kwargs: Any) -> MockContractLog: else: converted_args[key] = self.conversion_manager.convert(value, py_type) - properties = {"event_arguments": converted_args, "event_name": self.abi.name} + properties: Dict = {"event_arguments": converted_args, "event_name": self.abi.name} if hasattr(self.contract, "address"): # Only address if this is off an instance. properties["contract_address"] = self.contract.address @@ -551,7 +547,7 @@ def query( if columns[0] == "*": columns = list(ContractLog.__fields__) # type: ignore - query = { + query: Dict = { "columns": columns, "event": self.abi, "start_block": start_block, @@ -595,7 +591,7 @@ def range( Iterator[:class:`~ape.contracts.base.ContractLog`] """ - if not hasattr(self.contract, "address"): + if not (contract_address := getattr(self.contract, "address", None)): return start_block = None @@ -604,16 +600,16 @@ def range( if stop is None: contract = None try: - contract = self.chain_manager.contracts.instance_at(self.contract.address) + contract = self.chain_manager.contracts.instance_at(contract_address) except Exception: pass if contract: start_block = contract.receipt.block_number else: - start_block = self.chain_manager.contracts.get_creation_receipt( - self.contract.address - ).block_number + cache = self.chain_manager.contracts + receipt = cache.get_creation_receipt(contract_address) + start_block = receipt.block_number stop_block = start_or_stop elif start_or_stop is not None and stop is not None: @@ -622,7 +618,7 @@ def range( stop_block = min(stop_block, self.chain_manager.blocks.height) - addresses = set([self.contract.address] + (extra_addresses or [])) + addresses = list(set([contract_address] + (extra_addresses or []))) contract_event_query = ContractEventQuery( columns=list(ContractLog.__fields__.keys()), contract=addresses, @@ -1144,7 +1140,7 @@ def __getattr__(self, attr_name: str) -> Any: if attr_name in set(super(BaseAddress, self).__dir__()): return super(BaseAddress, self).__getattribute__(attr_name) - if attr_name not in { + elif attr_name not in { *self._view_methods_, *self._mutable_methods_, *self._events_, diff --git a/src/ape/managers/chain.py b/src/ape/managers/chain.py index 09a8d437a8..537aaafc83 100644 --- a/src/ape/managers/chain.py +++ b/src/ape/managers/chain.py @@ -113,7 +113,7 @@ def __iter__(self) -> Iterator[BlockAPI]: def query( self, - *columns: List[str], + *columns: str, start_block: int = 0, stop_block: Optional[int] = None, step: int = 1, @@ -130,7 +130,7 @@ def query( than the chain length. Args: - columns (List[str]): columns in the DataFrame to return + *columns (str): columns in the DataFrame to return start_block (int): The first block, by number, to include in the query. Defaults to 0. stop_block (Optional[int]): The last block, by number, to include @@ -438,12 +438,12 @@ def outgoing(self) -> Iterator[ReceiptAPI]: start_nonce = receipt.nonce + 1 # start next loop on the next item if start_nonce != stop_nonce: - # NOTE: there is no more sessional history, so just return query engine iterator + # NOTE: there is no more session history, so just return query engine iterator yield from iter(self[start_nonce : stop_nonce + 1]) # noqa: E203 def query( self, - *columns: List[str], + *columns: str, start_nonce: int = 0, stop_nonce: Optional[int] = None, engine_to_use: Optional[str] = None, @@ -459,7 +459,7 @@ def query( than the account's current nonce. Args: - columns (List[str]): columns in the DataFrame to return + *columns (str): columns in the DataFrame to return start_nonce (int): The first transaction, by nonce, to include in the query. Defaults to 0. stop_nonce (Optional[int]): The last transaction, by nonce, to include diff --git a/src/ape/managers/config.py b/src/ape/managers/config.py index 2384b7fef2..8512447474 100644 --- a/src/ape/managers/config.py +++ b/src/ape/managers/config.py @@ -13,7 +13,7 @@ if TYPE_CHECKING: from .project import ProjectManager -from ethpm_types import PackageMeta +from ethpm_types import BaseModel, PackageMeta CONFIG_FILE_NAME = "ape-config.yaml" @@ -28,9 +28,16 @@ class CompilerConfig(PluginConfig): """List of globular files to ignore""" -class DeploymentConfigCollection(dict): - def __init__(self, data: Dict, valid_ecosystems: Dict, valid_networks: List[str]): - for ecosystem_name, networks in data.items(): +class DeploymentConfigCollection(BaseModel): + __root__: Dict + + @root_validator(pre=True) + def validate_deployments(cls, data: Dict): + root_data = data.get("__root__", data) + valid_ecosystems = root_data.pop("valid_ecosystems", {}) + valid_networks = root_data.pop("valid_networks", {}) + valid_data: Dict = {} + for ecosystem_name, networks in root_data.items(): if ecosystem_name not in valid_ecosystems: logger.warning(f"Invalid ecosystem '{ecosystem_name}' in deployments config.") continue @@ -41,21 +48,29 @@ def __init__(self, data: Dict, valid_ecosystems: Dict, valid_networks: List[str] logger.warning(f"Invalid network '{network_name}' in deployments config.") continue + valid_deployments = [] for deployment in [d for d in contract_deployments]: - address = deployment.get("address", None) - if "address" not in deployment: + if not (address := deployment.get("address")): logger.warning( f"Missing 'address' field in deployment " f"(ecosystem={ecosystem_name}, network={network_name})" ) continue + valid_deployment = {**deployment} try: - deployment["address"] = ecosystem.decode_address(address) + valid_deployment["address"] = ecosystem.decode_address(address) except ValueError as err: logger.warning(str(err)) - super().__init__(data) + valid_deployments.append(valid_deployment) + + valid_data[ecosystem_name] = { + **valid_data.get(ecosystem_name, {}), + network_name: valid_deployments, + } + + return {"__root__": valid_data} class ConfigManager(BaseInterfaceModel): @@ -194,7 +209,11 @@ def _plugin_configs(self) -> Dict[str, PluginConfig]: valid_ecosystems = dict(self.plugin_manager.ecosystems) valid_network_names = [n[1] for n in [e[1] for e in self.plugin_manager.networks]] self.deployments = configs["deployments"] = DeploymentConfigCollection( - deployments, valid_ecosystems, valid_network_names + __root__={ + **deployments, + "valid_ecosystems": valid_ecosystems, + "valid_networks": valid_network_names, + } ) for plugin_name, config_class in self.plugin_manager.config_class: diff --git a/src/ape/managers/converters.py b/src/ape/managers/converters.py index 89ca408b4a..199c70ca9f 100644 --- a/src/ape/managers/converters.py +++ b/src/ape/managers/converters.py @@ -1,6 +1,6 @@ from datetime import datetime, timedelta, timezone from decimal import Decimal -from typing import Any, Dict, List, Tuple, Type, Union +from typing import Any, Dict, List, Sequence, Tuple, Type, Union from dateutil.parser import parse # type: ignore from eth_utils import ( @@ -12,9 +12,9 @@ to_hex, to_int, ) -from ethpm_types import HexBytes +from ethpm_types import ConstructorABI, EventABI, HexBytes, MethodABI -from ape.api import ConverterAPI +from ape.api import ConverterAPI, TransactionAPI from ape.api.address import BaseAddress from ape.exceptions import ConversionError from ape.types import AddressType @@ -291,11 +291,7 @@ def is_type(self, value: Any, type: Type) -> bool: bool: ``True`` when we consider the given value to be the given type. """ - if type is AddressType: - return is_checksum_address(value) - - else: - return isinstance(value, type) + return is_checksum_address(value) if type is AddressType else isinstance(value, type) def convert(self, value: Any, type: Union[Type, Tuple, List]) -> Any: """ @@ -356,3 +352,31 @@ def convert(self, value: Any, type: Union[Type, Tuple, List]) -> Any: raise ConversionError(message) from err raise ConversionError(f"No conversion registered to handle '{value}'.") + + def convert_method_args( + self, + abi: Union[MethodABI, ConstructorABI, EventABI], + arguments: Sequence[Any], + ): + input_types = [i.canonical_type for i in abi.inputs] + pre_processed_args = [] + for ipt, argument in zip(input_types, arguments): + # Handle primitive-addresses separately since they may not occur + # on the tuple-conversion if they are integers or bytes. + if str(ipt) == "address": + converted_value = self.convert(argument, AddressType) + pre_processed_args.append(converted_value) + else: + pre_processed_args.append(argument) + + return self.convert(pre_processed_args, tuple) + + def convert_method_kwargs(self, kwargs) -> Dict: + fields = TransactionAPI.__fields__ + + kwargs_to_convert = {k: v for k, v in kwargs.items() if k == "sender" or k in fields} + converted_fields = { + k: self.convert(v, AddressType if k == "sender" else fields[k].type_) + for k, v in kwargs_to_convert.items() + } + return {**kwargs, **converted_fields} diff --git a/src/ape/managers/project/dependency.py b/src/ape/managers/project/dependency.py index bc995a6d37..803064035d 100644 --- a/src/ape/managers/project/dependency.py +++ b/src/ape/managers/project/dependency.py @@ -287,7 +287,7 @@ class LocalDependency(DependencyAPI): """ local: str - version = "local" + version: str = "local" @root_validator() def validate_contracts_folder(cls, value): diff --git a/src/ape/managers/project/manager.py b/src/ape/managers/project/manager.py index 9cd69a6441..cb77f5b9f5 100644 --- a/src/ape/managers/project/manager.py +++ b/src/ape/managers/project/manager.py @@ -1,13 +1,13 @@ import shutil from pathlib import Path -from typing import Any, Dict, Iterable, List, Optional, Type, Union +from typing import Any, Dict, Iterable, List, Optional, Type, Union, cast from ethpm_types import ContractInstance as EthPMContractInstance from ethpm_types import ContractType, PackageManifest, PackageMeta, Source from ethpm_types.contract_type import BIP122_URI from ethpm_types.manifest import PackageName from ethpm_types.source import Compiler, ContractSource -from ethpm_types.utils import AnyUrl +from ethpm_types.utils import AnyUrl, Hex from ape.api import DependencyAPI, ProjectAPI from ape.api.networks import LOCAL_NETWORK_NAME @@ -196,7 +196,7 @@ def _get_compiler_data(self, compile_if_needed: bool = True): filtered_contract_types = [ ct for ct in contract_types if ct.source_id in source_ids ] - contract_type_names = [ct.name for ct in filtered_contract_types] + contract_type_names = [ct.name for ct in filtered_contract_types if ct.name] compiler_list.append( Compiler( name=compiler.name, @@ -703,6 +703,9 @@ def track_deployment(self, contract: ContractInstance): raise ProjectError("Can only publish deployments on a live network.") contract_name = contract.contract_type.name + if not (contract_name := contract.contract_type.name): + raise ProjectError("Contract name required when publishing.") + try: receipt = contract.receipt except ChainError as err: @@ -721,10 +724,10 @@ def track_deployment(self, contract: ContractInstance): block_hash = block_hash_bytes.hex() artifact = EthPMContractInstance( - address=contract.address, + address=cast(Hex, contract.address), block=block_hash, contractType=contract_name, - transaction=contract.txn_hash, + transaction=cast(Hex, contract.txn_hash), runtimeBytecode=contract.contract_type.runtime_bytecode, ) diff --git a/src/ape/managers/project/types.py b/src/ape/managers/project/types.py index 66541cb1cc..705b1a46ad 100644 --- a/src/ape/managers/project/types.py +++ b/src/ape/managers/project/types.py @@ -230,7 +230,7 @@ class ApeProject(BaseProject): class BrownieProject(BaseProject): - config_file_name = "brownie-config.yaml" + config_file_name: str = "brownie-config.yaml" @property def brownie_config_path(self) -> Path: diff --git a/src/ape/types/__init__.py b/src/ape/types/__init__.py index ba6776d556..3e3123328e 100644 --- a/src/ape/types/__init__.py +++ b/src/ape/types/__init__.py @@ -1,10 +1,10 @@ from dataclasses import dataclass -from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional, Union +from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional, Sequence, Union, cast from eth_abi.abi import encode from eth_abi.packed import encode_packed -from eth_typing import HexStr -from eth_utils import encode_hex, keccak +from eth_typing import Hash32, HexStr +from eth_utils import encode_hex, keccak, to_hex from ethpm_types import ( ABI, Bytecode, @@ -88,7 +88,7 @@ def validate_multiplier(cls, value): """ -TopicFilter = List[Union[Optional[HexStr], List[Optional[HexStr]]]] +TopicFilter = Sequence[Union[Optional[HexStr], List[Optional[HexStr]]]] @dataclass @@ -142,11 +142,13 @@ def validate_addresses(cls, value): return convert(value, AddressType) def dict(self, client=None): + _Hash32 = Union[Hash32, HexBytes, HexStr] + topics = cast(Sequence[Optional[Union[_Hash32, Sequence[_Hash32]]]], self.topic_filter) return FilterParams( address=self.addresses, - fromBlock=hex(self.start_block), # type: ignore - toBlock=hex(self.stop_block), # type: ignore - topics=self.topic_filter, # type: ignore + fromBlock=to_hex(self.start_block), + toBlock=to_hex(self.stop_block), + topics=topics, ) @classmethod diff --git a/src/ape/types/signatures.py b/src/ape/types/signatures.py index 979f971286..fdd182aa28 100644 --- a/src/ape/types/signatures.py +++ b/src/ape/types/signatures.py @@ -9,10 +9,7 @@ def _left_pad_bytes(val: bytes, num_bytes: int) -> bytes: - if len(val) < num_bytes: - return b"\x00" * (num_bytes - len(val)) + val - - return val + return b"\x00" * (num_bytes - len(val)) + val if len(val) < num_bytes else val @dataclass(frozen=True) diff --git a/src/ape/utils/abi.py b/src/ape/utils/abi.py index 9e1bdca651..cd0f4a3fb9 100644 --- a/src/ape/utils/abi.py +++ b/src/ape/utils/abi.py @@ -1,6 +1,6 @@ import re from dataclasses import make_dataclass -from typing import Any, Dict, List, Optional, Tuple, Union +from typing import Any, Dict, List, Optional, Sequence, Tuple, Union from eth_abi import decode, grammar from eth_abi.exceptions import DecodingError, InsufficientDataBytes @@ -9,7 +9,6 @@ from ethpm_types.abi import ABIType, ConstructorABI, EventABI, EventABIType, MethodABI from ape.logging import logger -from ape.types import AddressType ARRAY_PATTERN = re.compile(r"[(*\w,? )]*\[\d*]") @@ -42,7 +41,7 @@ def returns_array(abi: MethodABI) -> bool: return _is_array_return(abi.outputs) -def _is_array_return(outputs: List[ABIType]): +def _is_array_return(outputs: Sequence[ABIType]): return len(outputs) == 1 and is_array(outputs[0].type) @@ -77,7 +76,7 @@ def encode_input(self, values: Union[List, Tuple, Dict]) -> Any: return [self._encode(ipt, v) for ipt, v in zip(self.abi.inputs, values)] - def decode_input(self, values: Union[List, Tuple, Dict[str, Any]]) -> Any: + def decode_input(self, values: Union[Sequence, Dict[str, Any]]) -> Any: return self._decode(self.abi.inputs, values) if isinstance(self.abi, EventABI) else None def _encode(self, _type: ABIType, value: Any): @@ -121,7 +120,7 @@ def decode_output(self, values: Union[List, Tuple]) -> Any: return self._decode(self.abi.outputs, values) if isinstance(self.abi, MethodABI) else None - def _decode(self, _types: List[ABIType], values: Union[List, Tuple, Dict[str, Any]]): + def _decode(self, _types: Sequence[ABIType], values: Union[Sequence, Dict[str, Any]]): if is_struct(_types): return self._create_struct(_types[0], values) @@ -223,23 +222,21 @@ def _parse_components(self, components: List[ABIType], values) -> List: return parsed_values -def is_struct(outputs: Union[ABIType, List[ABIType]]) -> bool: +def is_struct(outputs: Union[ABIType, Sequence[ABIType]]) -> bool: """ Returns ``True`` if the given output is a struct. """ - if not isinstance(outputs, (tuple, list)): - outputs = [outputs] - + outputs_seq = outputs if isinstance(outputs, (tuple, list)) else [outputs] return ( - len(outputs) == 1 - and "[" not in outputs[0].type - and outputs[0].components not in (None, []) - and all(c.name != "" for c in outputs[0].components or []) + len(outputs_seq) == 1 + and "[" not in outputs_seq[0].type + and outputs_seq[0].components not in (None, []) + and all(c.name != "" for c in outputs_seq[0].components or []) ) -def is_named_tuple(outputs: List[ABIType], output_values: Union[List, Tuple]) -> bool: +def is_named_tuple(outputs: Sequence[ABIType], output_values: Sequence) -> bool: """ Returns ``True`` if the given output is a tuple where every item is named. """ @@ -261,9 +258,7 @@ def __setitem__(self, key, value): pass -def create_struct( - name: str, types: List[ABIType], output_values: Union[List[Any], Tuple[Any, ...]] -) -> Any: +def create_struct(name: str, types: Sequence[ABIType], output_values: Sequence) -> Any: """ Create a dataclass representing an ABI struct that can be used as inputs or outputs. The struct properties can be accessed via ``.`` notation, as keys in a dictionary, or @@ -440,31 +435,3 @@ def decode_value(self, abi_type: str, value: Any) -> Any: # ecosystem API through the calling function. return value - - -def _convert_args(arguments, converter, abi: Union[MethodABI, ConstructorABI]) -> Tuple: - input_types = [i.canonical_type for i in abi.inputs] - pre_processed_args = [] - for ipt, argument in zip(input_types, arguments): - # Handle primitive-addresses separately since they may not occur - # on the tuple-conversion if they are integers or bytes. - if str(ipt) == "address": - converted_value = converter(argument, AddressType) - pre_processed_args.append(converted_value) - else: - pre_processed_args.append(argument) - - return converter(pre_processed_args, tuple) - - -def _convert_kwargs(kwargs, converter) -> Dict: - from ape.api.transactions import TransactionAPI - - fields = TransactionAPI.__fields__ - - kwargs_to_convert = {k: v for k, v in kwargs.items() if k == "sender" or k in fields} - converted_fields = { - k: converter(v, AddressType if k == "sender" else fields[k].type_) - for k, v in kwargs_to_convert.items() - } - return {**kwargs, **converted_fields} diff --git a/src/ape/utils/basemodel.py b/src/ape/utils/basemodel.py index d3e590449a..e10d8fe49f 100644 --- a/src/ape/utils/basemodel.py +++ b/src/ape/utils/basemodel.py @@ -1,5 +1,5 @@ from abc import ABC -from typing import TYPE_CHECKING, ClassVar, Dict, List, Optional, cast +from typing import TYPE_CHECKING, ClassVar, List, Optional, cast from ethpm_types import BaseModel @@ -88,10 +88,10 @@ def provider(self) -> "ProviderAPI": Returns: :class:`~ape.api.providers.ProviderAPI` """ - if self.network_manager.active_provider is None: - raise ProviderNotConnectedError() + if provider := self.network_manager.active_provider: + return provider - return self.network_manager.active_provider + raise ProviderNotConnectedError() class BaseInterface(ManagerAccessMixin, ABC): @@ -123,27 +123,3 @@ def __dir__(self) -> List[str]: """ # Filter out protected/private members return [member for member in super().__dir__() if not member.startswith("_")] - - def dict(self, *args, **kwargs) -> Dict: - if "by_alias" not in kwargs: - kwargs["by_alias"] = True - - if "exclude_none" not in kwargs: - kwargs["exclude_none"] = True - - return super().dict(*args, **kwargs) - - def json(self, *args, **kwargs) -> str: - if "separators" not in kwargs: - kwargs["separators"] = (",", ":") - - if "sort_keys" not in kwargs: - kwargs["sort_keys"] = True - - if "by_alias" not in kwargs: - kwargs["by_alias"] = True - - if "exclude_none" not in kwargs: - kwargs["exclude_none"] = True - - return super().json(*args, **kwargs) diff --git a/src/ape/utils/misc.py b/src/ape/utils/misc.py index fefa6fff94..3b6ea1b2c8 100644 --- a/src/ape/utils/misc.py +++ b/src/ape/utils/misc.py @@ -5,7 +5,7 @@ from datetime import datetime from functools import cached_property, lru_cache, singledispatchmethod, wraps from pathlib import Path -from typing import Any, Callable, Coroutine, Dict, List, Mapping, Optional, cast +from typing import TYPE_CHECKING, Any, Callable, Coroutine, Dict, List, Mapping, Optional, cast import requests import yaml @@ -17,11 +17,14 @@ from ape.exceptions import APINotImplementedError, ProviderNotConnectedError from ape.logging import logger -from ape.types import AddressType from ape.utils.os import expand_environment_variables +if TYPE_CHECKING: + from ape.types import AddressType + + EMPTY_BYTES32 = HexBytes("0x0000000000000000000000000000000000000000000000000000000000000000") -ZERO_ADDRESS: AddressType = cast(AddressType, "0x0000000000000000000000000000000000000000") +ZERO_ADDRESS: "AddressType" = cast("AddressType", "0x0000000000000000000000000000000000000000") DEFAULT_TRANSACTION_ACCEPTANCE_TIMEOUT = 120 DEFAULT_LOCAL_TRANSACTION_ACCEPTANCE_TIMEOUT = 20 DEFAULT_LIVE_NETWORK_BASE_FEE_MULTIPLIER = 1.4 diff --git a/src/ape_ethereum/ecosystem.py b/src/ape_ethereum/ecosystem.py index 162e6d2dba..18b4a80efd 100644 --- a/src/ape_ethereum/ecosystem.py +++ b/src/ape_ethereum/ecosystem.py @@ -50,7 +50,6 @@ returns_array, to_int, ) -from ape.utils.abi import _convert_kwargs from ape.utils.misc import DEFAULT_MAX_RETRIES_TX from ape_ethereum.proxies import ( IMPLEMENTATION_ABI, @@ -91,7 +90,7 @@ class NetworkConfig(PluginConfig): block_time: int = 0 transaction_acceptance_timeout: int = DEFAULT_TRANSACTION_ACCEPTANCE_TIMEOUT default_transaction_type: TransactionType = TransactionType.DYNAMIC - max_receipt_retries = DEFAULT_MAX_RETRIES_TX + max_receipt_retries: int = DEFAULT_MAX_RETRIES_TX gas_limit: GasLimit = "auto" """ @@ -237,7 +236,7 @@ def encode_contract_blueprint( deploy_bytecode = HexBytes( return_data_size + len_bytes + return_instructions + blueprint_bytecode ) - converted_kwargs = _convert_kwargs(kwargs, self.conversion_manager.convert) + converted_kwargs = self.conversion_manager.convert_method_kwargs(kwargs) return self.encode_deployment( deploy_bytecode, contract_type.constructor, **converted_kwargs ) @@ -264,11 +263,10 @@ def get_proxy_info(self, address: AddressType) -> Optional[ProxyInfo]: ProxyType.SplitsCWIA: r"36602f57343d527f9e4ac34f21c619cefc926c8bd93b54bf5a39c7ab2127a895af1cc0691d7e3dff60203da13d3df35b3d3d3d3d363d3d3761.{4}606736393661.{4}013d73(.{40})5af43d3d93803e606557fd5bf3.*", # noqa: E501 ProxyType.SoladyPush0: r"^5f5f365f5f37365f73(.{40})5af43d5f5f3e6029573d5ffd5b3d5ff3", } - for type, pattern in patterns.items(): - match = re.match(pattern, code) - if match: + for type_, pattern in patterns.items(): + if match := re.match(pattern, code): target = self.conversion_manager.convert(match.group(1), AddressType) - return ProxyInfo(type=type, target=target) + return ProxyInfo(type=type_, target=target) sequence_pattern = r"363d3d373d3d3d363d30545af43d82803e903d91601857fd5bf3" if re.match(sequence_pattern, code): @@ -350,7 +348,7 @@ def decode_receipt(self, data: dict) -> ReceiptAPI: break if txn_hash: - txn_hash = txn_hash.hex() if isinstance(txn_hash, HexBytes) else txn_hash + txn_hash = txn_hash.hex() if isinstance(txn_hash, bytes) else txn_hash data_bytes = data.get("data", b"") if data_bytes and isinstance(data_bytes, str): @@ -359,8 +357,12 @@ def decode_receipt(self, data: dict) -> ReceiptAPI: elif "input" in data and isinstance(data["input"], str): data["input"] = HexBytes(data["input"]) + block_number = data.get("block_number") or data.get("blockNumber") + if block_number is None: + raise ValueError("Missing block number.") + receipt = Receipt( - block_number=data.get("block_number") or data.get("blockNumber"), + block_number=block_number, contract_address=data.get("contract_address") or data.get("contractAddress"), gas_limit=data.get("gas", data.get("gas_limit", data.get("gasLimit"))) or 0, gas_price=data.get("gas_price", data.get("gasPrice")) or 0, @@ -491,7 +493,7 @@ def decode_returndata(self, abi: MethodABI, raw_data: bytes) -> Tuple[Any, ...]: return tuple(output_values) def _enrich_value(self, value: Any, **kwargs) -> Any: - if isinstance(value, HexBytes): + if isinstance(value, bytes): try: string_value = value.strip(b"\x00").decode("utf8") return f'"{string_value}"' @@ -554,13 +556,14 @@ def encode_deployment( self, deployment_bytecode: HexBytes, abi: ConstructorABI, *args, **kwargs ) -> BaseTransaction: txn = self.create_transaction(**kwargs) - txn.data = deployment_bytecode + data = HexBytes(deployment_bytecode) # Encode args, if there are any if abi and args: - txn.data += self.encode_calldata(abi, *args) + data = HexBytes(data + self.encode_calldata(abi, *args)) - return txn # type: ignore + txn.data = data + return cast(BaseTransaction, txn) def encode_transaction( self, @@ -573,9 +576,9 @@ def encode_transaction( # Add method ID txn.data = self.get_method_selector(abi) - txn.data += self.encode_calldata(abi, *args) + txn.data = HexBytes(txn.data + self.encode_calldata(abi, *args)) - return txn # type: ignore + return cast(BaseTransaction, txn) def create_transaction(self, **kwargs) -> TransactionAPI: """ @@ -837,10 +840,8 @@ def _enrich_calldata( **kwargs, ) -> CallTreeNode: calldata = call.inputs - if isinstance(calldata, str): + if isinstance(calldata, (str, bytes, int)): calldata_arg = HexBytes(calldata) - elif isinstance(calldata, HexBytes): - calldata_arg = calldata else: # Not sure if we can get here. # Mostly for mypy's sake. @@ -873,10 +874,10 @@ def _enrich_returndata( return call default_return_value = "" - if isinstance(call.outputs, str) and is_0x_prefixed(call.outputs): + if (isinstance(call.outputs, str) and is_0x_prefixed(call.outputs)) or isinstance( + call.outputs, (int, bytes) + ): return_value_bytes = HexBytes(call.outputs) - elif isinstance(call.outputs, HexBytes): - return_value_bytes = call.outputs else: return_value_bytes = None @@ -918,9 +919,9 @@ def get_python_types(self, abi_type: ABIType) -> Union[Type, Tuple, List]: return self._python_type_for_abi_type(abi_type) -def parse_type(type: Dict[str, Any]) -> Union[str, Tuple, List]: - if "tuple" not in type["type"]: - return type["type"] +def parse_type(type_: Dict[str, Any]) -> Union[str, Tuple, List]: + if "tuple" not in type_["type"]: + return type_["type"] - result = tuple([parse_type(c) for c in type["components"]]) - return [result] if is_array(type["type"]) else result + result = tuple([parse_type(c) for c in type_["components"]]) + return [result] if is_array(type_["type"]) else result diff --git a/src/ape_ethereum/proxies.py b/src/ape_ethereum/proxies.py index 36ed36be19..40fd31fa8e 100644 --- a/src/ape_ethereum/proxies.py +++ b/src/ape_ethereum/proxies.py @@ -1,7 +1,9 @@ from enum import IntEnum, auto +from typing import cast from ethpm_types import ContractType, MethodABI from ethpm_types.abi import ABIType +from ethpm_types.utils import Hex from lazyasd import LazyObject # type: ignore from ape.api.networks import ProxyInfoAPI @@ -90,7 +92,7 @@ class ProxyInfo(ProxyInfoAPI): def _make_minimal_proxy(address: str = MINIMAL_PROXY_TARGET_PLACEHOLDER) -> ContractContainer: address = address.replace("0x", "") - code = MINIMAL_PROXY_BYTES.replace(MINIMAL_PROXY_TARGET_PLACEHOLDER, address) + code = cast(Hex, MINIMAL_PROXY_BYTES.replace(MINIMAL_PROXY_TARGET_PLACEHOLDER, address)) bytecode = {"bytecode": code} contract_type = ContractType(abi=[], deploymentBytecode=bytecode) return ContractContainer(contract_type=contract_type) diff --git a/src/ape_ethereum/transactions.py b/src/ape_ethereum/transactions.py index b430319396..51a67cea55 100644 --- a/src/ape_ethereum/transactions.py +++ b/src/ape_ethereum/transactions.py @@ -107,10 +107,7 @@ class DynamicFeeTransaction(BaseTransaction): @validator("type", allow_reuse=True) def check_type(cls, value): - if isinstance(value, TransactionType): - return value.value - - return value + return value.value if isinstance(value, TransactionType) else value class AccessListTransaction(BaseTransaction): diff --git a/src/ape_geth/provider.py b/src/ape_geth/provider.py index dad84fd0ed..f214e7fff6 100644 --- a/src/ape_geth/provider.py +++ b/src/ape_geth/provider.py @@ -246,8 +246,8 @@ class BaseGethProvider(Web3Provider, ABC): _client_version: Optional[str] = None # optimal values for geth - block_page_size = 5000 - concurrency = 16 + block_page_size: int = 5000 + concurrency: int = 16 name: str = "geth" @@ -450,10 +450,10 @@ def __repr__(self): def connect(self): self._set_web3() - if not self.is_connected: - self.start() - else: + if self.is_connected: self._complete_connect() + else: + self.start() def start(self, timeout: int = 20): test_config = self.config_manager.get_config("test").dict() diff --git a/src/ape_test/__init__.py b/src/ape_test/__init__.py index dd5be81151..6a4507aecc 100644 --- a/src/ape_test/__init__.py +++ b/src/ape_test/__init__.py @@ -6,9 +6,8 @@ from ape.api import PluginConfig from ape.api.networks import LOCAL_NETWORK_NAME from ape.utils import DEFAULT_HD_PATH, DEFAULT_NUMBER_OF_TEST_ACCOUNTS, DEFAULT_TEST_MNEMONIC - -from .accounts import TestAccount, TestAccountContainer -from .provider import EthTesterProviderConfig, LocalProvider +from ape_test.accounts import TestAccount, TestAccountContainer +from ape_test.provider import EthTesterProviderConfig, LocalProvider class GasExclusion(PluginConfig): @@ -79,7 +78,7 @@ class CoverageConfig(PluginConfig): the ``--coverage`` flag. """ - reports = CoverageReportsConfig() + reports: CoverageReportsConfig = CoverageReportsConfig() """ Enable reports. """ diff --git a/src/ape_test/provider.py b/src/ape_test/provider.py index b4bd2b81a7..6ee20ad7b9 100644 --- a/src/ape_test/provider.py +++ b/src/ape_test/provider.py @@ -109,16 +109,16 @@ def estimate_gas_cost(self, txn: TransactionAPI, **kwargs) -> int: @property def chain_id(self) -> int: - try: - if self.cached_chain_id: - return self.cached_chain_id + if self.cached_chain_id: + return self.cached_chain_id + try: result = self._make_request("eth_chainId", []) - self.cached_chain_id = result - return result - except ProviderNotConnectedError: - return self.provider_settings.get("chain_id", self.config.provider.chain_id) + result = self.provider_settings.get("chain_id", self.config.provider.chain_id) + + self.cached_chain_id = result + return result @property def gas_price(self) -> int: diff --git a/tests/functional/conftest.py b/tests/functional/conftest.py index e0a75bc790..58d2378111 100644 --- a/tests/functional/conftest.py +++ b/tests/functional/conftest.py @@ -2,7 +2,7 @@ import time from distutils.dir_util import copy_tree from pathlib import Path -from typing import Optional +from typing import Optional, cast import pytest from ethpm_types import ContractType, HexBytes @@ -30,7 +30,7 @@ def fn(name: str) -> ContractType: ALIAS_2 = "__FUNCTIONAL_TESTS_ALIAS_2__" -TEST_ADDRESS = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" +TEST_ADDRESS = cast(AddressType, "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045") BASE_PROJECTS_DIRECTORY = (Path(__file__).parent / "data" / "projects").absolute() PROJECT_WITH_LONG_CONTRACTS_FOLDER = BASE_PROJECTS_DIRECTORY / "LongContractsFolder" APE_PROJECT_FOLDER = BASE_PROJECTS_DIRECTORY / "ApeProject" @@ -378,7 +378,7 @@ def _assert_log_values( address: Optional[AddressType] = None, ): assert log.contract_address == address or contract_instance.address - assert isinstance(log.b, HexBytes) + assert isinstance(log.b, bytes) expected_previous_number = number - 1 if previous_number is None else previous_number assert log.prevNum == expected_previous_number, "Event param 'prevNum' has unexpected value" assert log.newNum == number, "Event param 'newNum' has unexpected value" diff --git a/tests/functional/conversion/test_encode_structs.py b/tests/functional/conversion/test_encode_structs.py index 4df9cdc011..d2709ccaac 100644 --- a/tests/functional/conversion/test_encode_structs.py +++ b/tests/functional/conversion/test_encode_structs.py @@ -1,4 +1,4 @@ -from typing import Dict, Tuple +from typing import Dict, Tuple, cast import pytest from ethpm_types import HexBytes @@ -48,7 +48,7 @@ class SimilarStruct(Struct): "0000000000000000000000000000000000000000000000000000000000000001" "000000000000000000000000d9b7fdb3fc0a0aa3a507dcf0976bc23d49a9c7a3" ) -ADDRESS = "0xD9b7fdb3FC0A0Aa3A507dCf0976bc23D49a9C7A3" +ADDRESS = cast(AddressType, "0xD9b7fdb3FC0A0Aa3A507dCf0976bc23D49a9C7A3") DATA_BY_TYPE_KEY = { "tuple": (1, HexBytes("0x02"), True, ADDRESS), "dict": {"a": 1, "b": HexBytes("0x02"), "c": True, "d": ADDRESS}, diff --git a/tests/functional/geth/conftest.py b/tests/functional/geth/conftest.py index a5540030b8..6bf6c3c771 100644 --- a/tests/functional/geth/conftest.py +++ b/tests/functional/geth/conftest.py @@ -66,7 +66,7 @@ def mock_geth(geth_provider, mock_web3): network=geth_provider.network, provider_settings={}, data_folder=Path("."), - request_header="", + request_header={}, ) original_web3 = provider._web3 provider._web3 = mock_web3 diff --git a/tests/functional/test_accounts.py b/tests/functional/test_accounts.py index 84b424d6c9..09ea66eac9 100644 --- a/tests/functional/test_accounts.py +++ b/tests/functional/test_accounts.py @@ -293,18 +293,16 @@ def test_autosign_transactions(runner, keyfile_account, receiver): assert keyfile_account.transfer(receiver, "1 gwei") -def test_impersonate_not_implemented(accounts): - test_address = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" +def test_impersonate_not_implemented(accounts, address): expected_err_msg = ( "Your provider does not support impersonating accounts:\n" - f"No account with address '{test_address}'." + f"No account with address '{address}'." ) with pytest.raises(IndexError, match=expected_err_msg): - _ = accounts[test_address] + _ = accounts[address] -def test_impersonated_account_ignores_signature_check_on_txn(accounts): - address = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" +def test_impersonated_account_ignores_signature_check_on_txn(accounts, address): account = ImpersonatedAccount(raw_address=address) # Impersonate hack, since no providers in core actually support it. diff --git a/tests/functional/test_config.py b/tests/functional/test_config.py index d69d2266cf..d3648c9268 100644 --- a/tests/functional/test_config.py +++ b/tests/functional/test_config.py @@ -3,6 +3,7 @@ import pytest from ape.api import PluginConfig +from ape.api.networks import LOCAL_NETWORK_NAME from ape.managers.config import CONFIG_FILE_NAME, DeploymentConfigCollection, merge_configs from ape.types import GasLimit from ape_ethereum.ecosystem import NetworkConfig @@ -10,11 +11,16 @@ def test_integer_deployment_addresses(networks): - deployments_data = _create_deployments() - config = DeploymentConfigCollection( - deployments_data, {"ethereum": networks.ethereum}, ["local"] + data = { + **_create_deployments(), + "valid_ecosystems": {"ethereum": networks.ethereum}, + "valid_networks": [LOCAL_NETWORK_NAME], + } + config = DeploymentConfigCollection(__root__=data) + assert ( + config.__root__["ethereum"]["local"][0]["address"] + == "0x0c25212c557d00024b7Ca3df3238683A35541354" ) - assert config["ethereum"]["local"][0]["address"] == "0x0c25212c557d00024b7Ca3df3238683A35541354" @pytest.mark.parametrize( @@ -25,9 +31,9 @@ def test_bad_value_in_deployments(ecosystems, networks, err_part, ape_caplog, pl deployments = _create_deployments() all_ecosystems = dict(plugin_manager.ecosystems) ecosystem_dict = {e: all_ecosystems[e] for e in ecosystems if e in all_ecosystems} - DeploymentConfigCollection(deployments, ecosystem_dict, networks) + data = {**deployments, "valid_ecosystems": ecosystem_dict, "valid_networks": networks} ape_caplog.assert_last_log_with_retries( - lambda: DeploymentConfigCollection(deployments, ecosystem_dict, networks), + lambda: DeploymentConfigCollection(__root__=data), f"Invalid {err_part}", ) diff --git a/tests/functional/test_contract_event.py b/tests/functional/test_contract_event.py index a0a6536801..6b9d08dae6 100644 --- a/tests/functional/test_contract_event.py +++ b/tests/functional/test_contract_event.py @@ -14,7 +14,7 @@ @pytest.fixture def assert_log_values(owner, chain): def _assert_log_values(log: ContractLog, number: int, previous_number: Optional[int] = None): - assert isinstance(log.b, HexBytes) + assert isinstance(log.b, bytes) expected_previous_number = number - 1 if previous_number is None else previous_number assert log.prevNum == expected_previous_number, "Event param 'prevNum' has unexpected value" assert log.newNum == number, "Event param 'newNum' has unexpected value" diff --git a/tests/functional/test_coverage.py b/tests/functional/test_coverage.py index 388d013c08..aeb6d510c4 100644 --- a/tests/functional/test_coverage.py +++ b/tests/functional/test_coverage.py @@ -75,7 +75,7 @@ def coverage_project(source_contract, second_source_contract): @pytest.fixture def coverage_report(coverage_project): - return CoverageReport(source_folders=[Path.cwd()], projects=[coverage_project], timestamp=None) + return CoverageReport(source_folders=[Path.cwd()], projects=[coverage_project], timestamp=0) class TestFunctionCoverage: diff --git a/tests/functional/test_ecosystem.py b/tests/functional/test_ecosystem.py index b7ebcc54dc..00ed82ff11 100644 --- a/tests/functional/test_ecosystem.py +++ b/tests/functional/test_ecosystem.py @@ -54,7 +54,7 @@ def test_encode_address(ethereum): assert actual == raw_address -def test_encode_calldata(ethereum): +def test_encode_calldata(ethereum, address): abi = MethodABI( type="function", name="callMe", @@ -65,7 +65,6 @@ def test_encode_calldata(ethereum): ABIType(name="d", type="bytes4[]"), ], ) - address = "0xd8da6bf26964af9d7eed9e03e53415d37aa96045" byte_array = ["0x456", "0x678"] values = ("0x123", address, HexBytes(55), byte_array) diff --git a/tests/functional/test_project.py b/tests/functional/test_project.py index 62144e5667..9687c9e8a0 100644 --- a/tests/functional/test_project.py +++ b/tests/functional/test_project.py @@ -1,5 +1,6 @@ import shutil from pathlib import Path +from urllib.parse import urlparse import pytest import yaml @@ -183,7 +184,10 @@ def test_meta(temp_config, project): assert project.meta.license == "MIT" assert project.meta.description == "test" assert project.meta.keywords == ["testing"] - assert "https://apeworx.io" in project.meta.links["apeworx.io"] + + actual_url = urlparse(project.meta.links["apeworx.io"]) + assert actual_url.hostname == "apeworx.io" + assert actual_url.scheme == "https" def test_brownie_project_configure(config, base_projects_directory): @@ -193,7 +197,7 @@ def test_brownie_project_configure(config, base_projects_directory): # Left from previous run expected_config_file.unlink() - project = BrownieProject(path=project_path, contracts_folder="contracts") + project = BrownieProject(path=project_path, contracts_folder=Path("contracts")) project.process_config_file() assert expected_config_file.is_file() diff --git a/tests/functional/test_types.py b/tests/functional/test_types.py index 6d5014f672..2c0ec7d620 100644 --- a/tests/functional/test_types.py +++ b/tests/functional/test_types.py @@ -1,3 +1,5 @@ +from typing import Dict + import pytest from eth_utils import to_hex from ethpm_types.abi import EventABI @@ -11,7 +13,7 @@ EVENT_NAME = "MyEvent" LOG_INDEX = 7 TXN_INDEX = 2 -RAW_LOG = { +RAW_LOG: Dict = { "block_hash": BLOCK_HASH, "block_number": BLOCK_NUMBER, "contract_address": ZERO_ADDRESS,