From 094b76dfc89ed46aa1aefbc034ac6e48262b6761 Mon Sep 17 00:00:00 2001 From: antazoey Date: Sat, 1 Jun 2024 00:11:09 -0500 Subject: [PATCH] feat!: update to 0.8 (#133) Co-authored-by: Dalena --- .github/workflows/test.yaml | 2 +- .pre-commit-config.yaml | 2 +- README.md | 2 +- ape-config.yaml | 7 ++ ape_etherscan/client.py | 29 +++--- ape_etherscan/dependency.py | 18 +++- ape_etherscan/explorer.py | 6 +- ape_etherscan/query.py | 3 +- ape_etherscan/types.py | 6 +- ape_etherscan/verify.py | 53 +++++------ pyproject.toml | 2 +- setup.py | 5 +- tests/conftest.py | 136 +++++++-------------------- tests/contracts/subcontracts/foo.sol | 23 +++++ tests/dependency/contracts/bar.sol | 5 + tests/test_config.py | 58 +++++------- tests/test_dependency.py | 10 +- tests/test_etherscan.py | 2 +- 18 files changed, 166 insertions(+), 203 deletions(-) create mode 100644 ape-config.yaml create mode 100644 tests/contracts/subcontracts/foo.sol create mode 100644 tests/dependency/contracts/bar.sol diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index ba71a02..a6ef55f 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -65,7 +65,7 @@ jobs: # TODO: Replace with macos-latest when works again. # https://github.com/actions/setup-python/issues/808 os: [ubuntu-latest, macos-12] # eventually add `windows-latest` - python-version: [3.8, 3.9, "3.10", "3.11", "3.12"] + python-version: [3.9, "3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v4 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 32df4fe..d7770d4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -30,7 +30,7 @@ repos: rev: 0.7.17 hooks: - id: mdformat - additional_dependencies: [mdformat-gfm, mdformat-frontmatter] + additional_dependencies: [mdformat-gfm, mdformat-frontmatter, mdformat-pyproject] default_language_version: python: python3 diff --git a/README.md b/README.md index 692587d..d60119f 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ The following blockchain explorers are supported in this plugin: ## Dependencies -- [python3](https://www.python.org/downloads) version 3.8 up to 3.12. +- [python3](https://www.python.org/downloads) version 3.9 up to 3.12. ## Installation diff --git a/ape-config.yaml b/ape-config.yaml new file mode 100644 index 0000000..c29e575 --- /dev/null +++ b/ape-config.yaml @@ -0,0 +1,7 @@ +name: ape-etherscan-tests + +contracts_folder: tests/contracts + +dependencies: + - name: bar + local: ./tests/dependency diff --git a/ape_etherscan/client.py b/ape_etherscan/client.py index 0a4a901..e5dab4f 100644 --- a/ape_etherscan/client.py +++ b/ape_etherscan/client.py @@ -2,8 +2,9 @@ import os import random import time +from collections.abc import Iterator from io import StringIO -from typing import Dict, Iterator, List, Optional +from typing import Optional from ape.api import PluginConfig from ape.logging import logger @@ -201,7 +202,7 @@ def base_uri(self) -> str: return self._instance.api_uri @property - def base_params(self) -> Dict: + def base_params(self) -> dict: return {"module": self._module_name} @property @@ -225,8 +226,8 @@ def _clean_uri(self) -> str: def _get( self, - params: Optional[Dict] = None, - headers: Optional[Dict[str, str]] = None, + params: Optional[dict] = None, + headers: Optional[dict[str, str]] = None, raise_on_exceptions: bool = True, ) -> EtherscanResponse: params = self.__authorize(params) @@ -248,7 +249,7 @@ def _get( ) def _post( - self, json_dict: Optional[Dict] = None, headers: Optional[Dict[str, str]] = None + self, json_dict: Optional[dict] = None, headers: Optional[dict[str, str]] = None ) -> EtherscanResponse: data = self.__authorize(json_dict) return self._request("POST", data=data, headers=headers) @@ -257,9 +258,9 @@ def _request( self, method: str, raise_on_exceptions: bool = True, - headers: Optional[Dict] = None, - params: Optional[Dict] = None, - data: Optional[Dict] = None, + headers: Optional[dict] = None, + params: Optional[dict] = None, + data: Optional[dict] = None, ) -> EtherscanResponse: headers = headers or self.DEFAULT_HEADERS for i in range(self._retries): @@ -288,7 +289,7 @@ def _request( return EtherscanResponse(response, self._instance.ecosystem_name, raise_on_exceptions) - def __authorize(self, params_or_data: Optional[Dict] = None) -> Optional[Dict]: + def __authorize(self, params_or_data: Optional[dict] = None) -> Optional[dict]: env_var_key = API_KEY_ENV_KEY_MAP.get(self._instance.ecosystem_name) if not env_var_key: return params_or_data @@ -332,7 +333,7 @@ def get_source_code(self) -> SourceCodeResponse: def verify_source_code( self, - standard_json_output: Dict, + standard_json_output: dict, compiler_version: str, contract_name: Optional[str] = None, optimization_used: bool = False, @@ -340,7 +341,7 @@ def verify_source_code( constructor_arguments: Optional[str] = None, evm_version: Optional[str] = None, license_type: Optional[int] = None, - libraries: Optional[Dict[str, str]] = None, + libraries: Optional[dict[str, str]] = None, ) -> str: libraries = libraries or {} if len(libraries) > 10: @@ -384,7 +385,7 @@ def check_verify_status(self, guid: str) -> str: response = self._get(params=json_dict, raise_on_exceptions=False) return str(response.value) - def get_creation_data(self) -> List[ContractCreationResponse]: + def get_creation_data(self) -> list[ContractCreationResponse]: params = { **self.base_params, "action": "getcontractcreation", @@ -409,7 +410,7 @@ def get_all_normal_transactions( end_block: Optional[int] = None, offset: int = 100, sort: str = "asc", - ) -> Iterator[Dict]: + ) -> Iterator[dict]: page_num = 1 last_page_results = offset # Start at offset to trigger iteration while last_page_results == offset: @@ -430,7 +431,7 @@ def _get_page_of_normal_transactions( end_block: Optional[int] = None, offset: int = 100, sort: str = "asc", - ) -> List[Dict]: + ) -> list[dict]: params = { **self.base_params, "action": "txlist", diff --git a/ape_etherscan/dependency.py b/ape_etherscan/dependency.py index ed62f6e..8fc6f94 100644 --- a/ape_etherscan/dependency.py +++ b/ape_etherscan/dependency.py @@ -1,9 +1,11 @@ +from pathlib import Path + from ape.api.projects import DependencyAPI from ape.exceptions import ProjectError from ape.types import AddressType from ethpm_types import PackageManifest from hexbytes import HexBytes -from pydantic import AnyUrl, HttpUrl, field_validator +from pydantic import field_validator from .explorer import Etherscan @@ -27,8 +29,12 @@ def address(self) -> AddressType: return self.network_manager.ethereum.decode_address(self.etherscan) @property - def uri(self) -> AnyUrl: - return HttpUrl(f"{self.explorer.get_address_url(self.address)}#code") + def package_id(self) -> str: + return self.address + + @property + def uri(self) -> str: + return f"{self.explorer.get_address_url(self.address)}#code" @property def explorer(self) -> Etherscan: @@ -43,7 +49,11 @@ def explorer(self) -> Etherscan: # Assume Ethereum return self.network_manager.ethereum.mainnet.explorer - def extract_manifest(self, use_cache: bool = True) -> PackageManifest: + def fetch(self, destination: Path): + manifest = self._get_manifest() + manifest.unpack_sources(destination) + + def _get_manifest(self) -> PackageManifest: ecosystem = self.network_manager.get_ecosystem(self.ecosystem) network = ecosystem.get_network(self.network) diff --git a/ape_etherscan/explorer.py b/ape_etherscan/explorer.py index 4ff1eb9..04ff391 100644 --- a/ape_etherscan/explorer.py +++ b/ape_etherscan/explorer.py @@ -4,6 +4,7 @@ from ape.api import ExplorerAPI, PluginConfig from ape.contracts import ContractInstance from ape.exceptions import ProviderNotConnectedError +from ape.managers.project import ProjectManager from ape.types import AddressType, ContractType from ethpm_types import Compiler, PackageManifest from ethpm_types.source import Source @@ -120,5 +121,8 @@ def get_contract_type(self, address: AddressType) -> Optional[ContractType]: return contract_type def publish_contract(self, address: AddressType): - verifier = SourceVerifier(address, self._client_factory) + return self._publish_contract(address) + + def _publish_contract(self, address: AddressType, project: Optional["ProjectManager"] = None): + verifier = SourceVerifier(address, self._client_factory, project=project) return verifier.attempt_verification() diff --git a/ape_etherscan/query.py b/ape_etherscan/query.py index 1a76165..0559297 100644 --- a/ape_etherscan/query.py +++ b/ape_etherscan/query.py @@ -1,4 +1,5 @@ -from typing import Iterator, Optional +from collections.abc import Iterator +from typing import Optional from ape.api import PluginConfig, QueryAPI, QueryType, ReceiptAPI from ape.api.query import AccountTransactionQuery, ContractCreationQuery diff --git a/ape_etherscan/types.py b/ape_etherscan/types.py index deb06ad..84ce877 100644 --- a/ape_etherscan/types.py +++ b/ape_etherscan/types.py @@ -1,7 +1,7 @@ import json import re from dataclasses import dataclass -from typing import Dict, List, Union +from typing import Union from ape.utils import cached_property from ethpm_types import BaseModel @@ -21,7 +21,7 @@ class EtherscanInstance: class SourceCodeResponse(BaseModel): - abi: List = Field([], alias="ABI") + abi: list = Field([], alias="ABI") name: str = Field("unknown", alias="ContractName") source_code: str = Field("", alias="SourceCode") compiler_version: str = Field("", alias="CompilerVersion") @@ -67,7 +67,7 @@ class ContractCreationResponse: txHash: str -ResponseValue = Union[List, Dict, str] +ResponseValue = Union[list, dict, str] class EtherscanResponse: diff --git a/ape_etherscan/verify.py b/ape_etherscan/verify.py index 94a0523..db8daa8 100644 --- a/ape_etherscan/verify.py +++ b/ape_etherscan/verify.py @@ -2,11 +2,12 @@ import time from enum import Enum from pathlib import Path -from typing import Dict, Optional +from typing import Optional from ape.api import CompilerAPI from ape.contracts import ContractInstance from ape.logging import LogLevel, logger +from ape.managers.project import ProjectManager from ape.types import AddressType from ape.utils import ManagerAccessMixin, cached_property from ethpm_types import Compiler, ContractType @@ -169,9 +170,15 @@ def from_spdx_id(cls, spdx_id: str) -> "LicenseType": class SourceVerifier(ManagerAccessMixin): - def __init__(self, address: AddressType, client_factory: ClientFactory): + def __init__( + self, + address: AddressType, + client_factory: ClientFactory, + project: Optional[ProjectManager] = None, + ): self.address = address self.client_factory = client_factory + self.project = project or self.local_project @cached_property def account_client(self) -> AccountClient: @@ -193,13 +200,9 @@ def contract_type(self) -> ContractType: def contract_name(self) -> str: return self.contract.contract_type.name or "" - @property - def _base_path(self) -> Path: - return self.project_manager.contracts_folder - @property def source_path(self) -> Path: - return self._base_path / (self.contract_type.source_id or "") + return self.project.path / (self.contract_type.source_id or "") @property def ext(self) -> str: @@ -242,8 +245,7 @@ def license_code(self) -> LicenseType: """ The license type used in the code. """ - - spdx_id = self.source_path.read_text().split("\n")[0] + spdx_id = self.source_path.read_text().splitlines()[0] return LicenseType.from_spdx_id(spdx_id) @property @@ -262,17 +264,17 @@ def compiler_name(self) -> str: @property def compiler(self) -> Compiler: # Check the cached manifest for the compiler artifacts. - if manifest := self.project_manager.local_project.cached_manifest: + if manifest := self.local_project.manifest: if compiler := manifest.get_contract_compiler(self.contract_name): return compiler # Look in the publishable manifest, as Ape includes these there. - manifest = self.project_manager.extract_manifest() + manifest = self.local_project.extract_manifest() if compiler := manifest.get_contract_compiler(self.contract_name): return compiler # Build a default one and hope for the best. - return Compiler(name=self.compiler_name, contractType=[self.contract_name], version=None) + return Compiler(name=self.compiler_name, contractType=[self.contract_name], version="") def attempt_verification(self): """ @@ -314,8 +316,7 @@ def attempt_verification(self): optimized = optimizer.get("enabled", False) runs = optimizer.get("runs", DEFAULT_OPTIMIZATION_RUNS) source_id = self.contract_type.source_id - base_folder = self.project_manager.contracts_folder - standard_input_json = self._get_standard_input_json(source_id, base_folder, **settings) + standard_input_json = self._get_standard_input_json(source_id, **settings) evm_version = settings.get("evmVersion") license_code = self.license_code license_code_value = license_code.value if license_code else None @@ -355,7 +356,7 @@ def attempt_verification(self): self._wait_for_verification(guid) - def _get_new_settings(self, version: str) -> Dict: + def _get_new_settings(self, version: str) -> dict: logger.warning( "Settings missing from cached manifest. " "Attempting to re-calculate find settings." ) @@ -363,31 +364,27 @@ def _get_new_settings(self, version: str) -> Dict: # Attempt to re-calculate settings. compiler_plugin = self.compiler_manager.registered_compilers[self.ext] all_settings = compiler_plugin.get_compiler_settings( - [self.source_path], base_path=self._base_path + [self.source_path], project=self.project ) # Hack to allow any Version object work. return {str(v): s for v, s in all_settings.items() if str(v) == version}[version] - def _get_standard_input_json( - self, source_id: str, base_folder: Optional[Path] = None, **settings - ) -> Dict: - base_dir = base_folder or self.project_manager.contracts_folder - source_path = base_dir / source_id + def _get_standard_input_json(self, source_id: str, **settings) -> dict: + source_path = self.local_project.sources.lookup(source_id) compiler = self.compiler_manager.registered_compilers[source_path.suffix] - sources = {self.source_path.name: {"content": source_path.read_text()}} + sources = {source_id: {"content": source_path.read_text()}} def build_map(_source_id: str): - _source_path = base_dir / _source_id + _source_path = self.local_project.sources.lookup(_source_id) source_imports = compiler.get_imports([_source_path]).get(_source_id, []) for imported_source_id in source_imports: - sources[imported_source_id] = { - "content": (base_dir / imported_source_id).read_text() - } - build_map(imported_source_id) + if imp_path := self.local_project.sources.lookup(imported_source_id): + sources[imported_source_id] = {"content": imp_path.read_text()} + build_map(imported_source_id) def flatten_source(_source_id: str) -> str: - _source_path = base_dir / _source_id + _source_path = self.local_project.sources.lookup(_source_id) flattened_source = str(compiler.flatten_contract(_source_path)) return flattened_source diff --git a/pyproject.toml b/pyproject.toml index 484f184..71104a8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,7 @@ write_to = "ape_etherscan/version.py" [tool.black] line-length = 100 -target-version = ['py38', 'py39', 'py310', 'py311', 'py312'] +target-version = ['py39', 'py310', 'py311', 'py312'] include = '\.pyi?$' [tool.pytest.ini_options] diff --git a/setup.py b/setup.py index 9e36abd..7da62b9 100644 --- a/setup.py +++ b/setup.py @@ -80,12 +80,12 @@ url="https://github.com/ApeWorX/ape-etherscan", include_package_data=True, install_requires=[ - "eth-ape>=0.7.6,<0.8", + "eth-ape>=0.8.2,<0.9", "ethpm_types", # Use same version as eth-ape "requests", # Use same version as eth-ape "yarl", # Use same version as eth-ape ], - python_requires=">=3.8,<4", + python_requires=">=3.9,<4", extras_require=extras_require, py_modules=["ape_etherscan"], license="Apache-2.0", @@ -101,7 +101,6 @@ "Operating System :: MacOS", "Operating System :: POSIX", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", diff --git a/tests/conftest.py b/tests/conftest.py index 056f0d8..2dd9d33 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,22 +1,21 @@ import json import os -import tempfile +import shutil +from collections.abc import Callable from contextlib import contextmanager from io import StringIO from json import JSONDecodeError from pathlib import Path from tempfile import mkdtemp -from typing import IO, Any, Callable, Dict, Optional, Union +from typing import IO, Any, Optional, Union from unittest.mock import MagicMock import _io # type: ignore import ape import pytest -import yaml from ape.api import ExplorerAPI from ape.exceptions import NetworkError from ape.logging import logger -from ape.managers.config import CONFIG_FILE_NAME from ape.types import AddressType from ape.utils import cached_property from ape_solidity._utils import OUTPUT_SELECTION @@ -25,52 +24,21 @@ from ape_etherscan import Etherscan from ape_etherscan.client import _APIClient from ape_etherscan.types import EtherscanResponse +from ape_etherscan.verify import LicenseType -ape.config.DATA_FOLDER = Path(mkdtemp()).resolve() -ape.config.PROJECT_FOLDER = Path(mkdtemp()).resolve() +DATA_FOLDER = Path(mkdtemp()).resolve() +ape.config.DATA_FOLDER = DATA_FOLDER -MOCK_RESPONSES_PATH = Path(__file__).parent / "mock_responses" -FOO_SOURCE_CODE = """ -// SPDX-License-Identifier: AGPL-3.0 -pragma solidity ^0.8.20; +HERE = Path(__file__).parent +MOCK_RESPONSES_PATH = HERE / "mock_responses" +FOO_SOURCE_CODE = (HERE / "contracts" / "subcontracts" / "foo.sol").read_text() +BAR_SOURCE_CODE = (HERE / "dependency" / "contracts" / "bar.sol").read_text() -import "@bar/bar.sol"; -library MyLib { - function insert(uint value) public returns (bool) { - return true; - } -} - -contract foo { - function register(uint value) public { - require(MyLib.insert(value)); - } -} - -contract fooWithConstructor { - uint public value; - constructor(uint _value) { - value = _value; - } -} -""" -BAR_SOURCE_CODE = r""" -// SPDX-License-Identifier: AGPL-3.0 -pragma solidity ^0.8.20; - -contract bar { -} -""" -APE_CONFIG_FILE = r""" -dependencies: - - name: bar - local: ./bar - -solidity: - import_remapping: - - "@bar=bar" -""" +@pytest.fixture(scope="session", autouse=True) +def clean_datafolder(): + yield # Run all collected tests. + shutil.rmtree(DATA_FOLDER, ignore_errors=True) @pytest.fixture(scope="session") @@ -78,16 +46,19 @@ def standard_input_json(library): return { "language": "Solidity", "sources": { - "foo.sol": {"content": FOO_SOURCE_CODE}, - ".cache/bar/local/bar.sol": {"content": BAR_SOURCE_CODE}, + "tests/contracts/.cache/bar/local/contracts/bar.sol": {"content": BAR_SOURCE_CODE}, + "tests/contracts/subcontracts/foo.sol": {"content": FOO_SOURCE_CODE}, }, "settings": { "optimizer": {"enabled": True, "runs": 200}, "outputSelection": { - ".cache/bar/local/bar.sol": {"": ["ast"], "*": OUTPUT_SELECTION}, - "subcontracts/foo.sol": {"": ["ast"], "*": OUTPUT_SELECTION}, + "tests/contracts/.cache/bar/local/contracts/bar.sol": { + "": ["ast"], + "*": OUTPUT_SELECTION, + }, + "tests/contracts/subcontracts/foo.sol": {"": ["ast"], "*": OUTPUT_SELECTION}, }, - "remappings": ["@bar=.cache/bar/local"], + "remappings": ["@bar=tests/contracts/.cache/bar/local"], }, "libraryname1": "MyLib", "libraryaddress1": library.address, @@ -129,24 +100,9 @@ def make_source(base_dir: Path, name: str, content: str): source_file.write_text(content) -@pytest.fixture(scope="session", autouse=True) +@pytest.fixture(scope="session") def project(): - base_dir = ape.config.PROJECT_FOLDER - contracts_dir = base_dir / "contracts" - dependency_contracts_dir = base_dir / "bar" / "contracts" - sub_contracts_dir = contracts_dir / "subcontracts" - sub_contracts_dir.mkdir(exist_ok=True, parents=True) - dependency_contracts_dir.mkdir(exist_ok=True, parents=True) - - make_source(sub_contracts_dir, "foo", FOO_SOURCE_CODE) - make_source(dependency_contracts_dir, "bar", BAR_SOURCE_CODE) - - config_file = base_dir / "ape-config.yaml" - config_file.unlink(missing_ok=True) - config_file.write_text(APE_CONFIG_FILE) - - with ape.config.using_project(base_dir) as project: - yield project + return ape.project @pytest.fixture(scope="session") @@ -265,7 +221,7 @@ def __init__(self, mocker, session, get_expected_account_txns_params, contract_a @cached_property def expected_uri_map( self, - ) -> Dict[str, Dict[str, str]]: + ) -> dict[str, dict[str, str]]: def get_url_f(testnet: bool = False, tld: str = "io"): f_str = f"https://api-{{}}.{{}}.{tld}/api" if testnet else f"https://api.{{}}.{tld}/api" return f_str.format @@ -327,7 +283,7 @@ def add_handler( self, method: str, module: str, - expected_params: Dict, + expected_params: dict, return_value: Optional[Any] = None, side_effect: Optional[Callable] = None, ): @@ -443,7 +399,7 @@ def _get_contract_type_response(self, file_name: str) -> Any: data["SourceCode"] = content return self.get_mock_response(data, file_name=file_name) - def _expected_get_ct_params(self, address: str) -> Dict: + def _expected_get_ct_params(self, address: str) -> dict: return {"module": "contract", "action": "getsourcecode", "address": address} def setup_mock_account_transactions_response(self, address: AddressType, **overrides): @@ -480,7 +436,7 @@ def _setup_account_response(self, params, response): return response def get_mock_response( - self, response_data: Optional[Union[IO, Dict, str, MagicMock]] = None, **kwargs + self, response_data: Optional[Union[IO, dict, str, MagicMock]] = None, **kwargs ): if isinstance(response_data, str): return self.get_mock_response({"result": response_data, **kwargs}) @@ -497,7 +453,7 @@ def get_mock_response( def _get_mock_response( self, - response_data: Optional[Dict] = None, + response_data: Optional[dict] = None, response_text: Optional[str] = None, *args, **kwargs, @@ -505,7 +461,7 @@ def _get_mock_response( response = self.mocker.MagicMock(spec=Response) if response_data: assert isinstance(response_data, dict) # For mypy - overrides: Dict = kwargs.get("response_overrides", {}) + overrides: dict = kwargs.get("response_overrides", {}) response.json.return_value = {**response_data, **overrides} if not response_text: response_text = json.dumps(response_data or {}) @@ -531,7 +487,7 @@ def verification_params(address_to_verify, standard_input_json): "contractaddress": address_to_verify, "contractname": "foo.sol:foo", "evmversion": None, - "licenseType": 1, + "licenseType": LicenseType.AGLP_3.value, "module": "contract", "optimizationUsed": 1, "runs": 200, @@ -559,7 +515,7 @@ def verification_params_with_ctor_args( "contractaddress": address_to_verify_with_ctor_args, "contractname": "foo.sol:fooWithConstructor", "evmversion": None, - "licenseType": 1, + "licenseType": LicenseType.AGLP_3.value, "module": "contract", "optimizationUsed": 1, "runs": 200, @@ -630,31 +586,3 @@ def expected_verification_log_with_ctor_args(address_to_verify_with_ctor_args): "Contract verification successful!\n" f"https://etherscan.io/address/{address_to_verify_with_ctor_args}#code" ) - - -@pytest.fixture(scope="session") -def temp_config(): - config = ape.config - - @contextmanager - def func(data: Dict, package_json: Optional[Dict] = None): - with tempfile.TemporaryDirectory() as temp_dir_str: - temp_dir = Path(temp_dir_str) - - config._cached_configs = {} - config_file = temp_dir / CONFIG_FILE_NAME - config_file.touch() - config_file.write_text(yaml.dump(data)) - config.load(force_reload=True) - - if package_json: - package_json_file = temp_dir / "package.json" - package_json_file.write_text(json.dumps(package_json)) - - with config.using_project(temp_dir): - yield temp_dir - - config_file.unlink() - config._cached_configs = {} - - return func diff --git a/tests/contracts/subcontracts/foo.sol b/tests/contracts/subcontracts/foo.sol new file mode 100644 index 0000000..9b858a6 --- /dev/null +++ b/tests/contracts/subcontracts/foo.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity ^0.8.20; + +import "@bar/contracts/bar.sol"; + +library MyLib { + function insert(uint value) public returns (bool) { + return true; + } +} + +contract foo { + function register(uint value) public { + require(MyLib.insert(value)); + } +} + +contract fooWithConstructor { + uint public value; + constructor(uint _value) { + value = _value; + } +} diff --git a/tests/dependency/contracts/bar.sol b/tests/dependency/contracts/bar.sol new file mode 100644 index 0000000..f2c7811 --- /dev/null +++ b/tests/dependency/contracts/bar.sol @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity ^0.8.20; + +contract bar { +} diff --git a/tests/test_config.py b/tests/test_config.py index 4a13f68..2f0c6d5 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -7,67 +7,53 @@ ecosystems_and_networks, ) -DEFAULT_CONFIG = {"name": "ape-etherscan-test"} - @network_ecosystems -def test_no_config(account, ecosystem, network, get_explorer, temp_config): +def test_no_config(account, ecosystem, network, get_explorer, project): """Test default behavior""" - conf = DEFAULT_CONFIG - with temp_config(conf): + with project.temp_config(name="ape-etherscan-test"): explorer = get_explorer(ecosystem, network) assert explorer.network.name == network assert explorer.network.ecosystem.name == ecosystem assert account.query_manager.engines["etherscan"].rate_limit == 5 - client = account.query_manager.engines["etherscan"]._client_factory.get_account_client( - account - ) + engine = account.query_manager.engines["etherscan"] + client = engine._client_factory.get_account_client(account) assert client._retries == 5 -def test_config_rate_limit(account, get_explorer, temp_config): +def test_config_rate_limit(account, project): """Test that rate limit config is set""" - conf = {**DEFAULT_CONFIG, **{"etherscan": {"ethereum": {"rate_limit": 123}}}} - with temp_config(conf): - assert account.query_manager.engines["etherscan"].rate_limit == 123 + with project.temp_config(etherscan={"ethereum": {"rate_limit": 123}}): + engine = account.query_manager.engines["etherscan"] + assert engine.rate_limit == 123 -def test_config_retries(account, get_explorer, temp_config): +def test_config_retries(account, project): """Test that rate limit config is set""" - conf = {**DEFAULT_CONFIG, **{"etherscan": {"ethereum": {"retries": 321}}}} - with temp_config(conf): - assert account.query_manager.engines["etherscan"].rate_limit == 5 - client = account.query_manager.engines["etherscan"]._client_factory.get_account_client( - account - ) + with project.temp_config(etherscan={"ethereum": {"retries": 321}}): + engine = account.query_manager.engines["etherscan"] + assert engine.rate_limit == 5 + client = engine._client_factory.get_account_client(account) assert client._retries == 321 -def test_config_uri(account, get_explorer, mock_provider, temp_config): +def test_config_uri(account, mock_provider, project): """ Make sure URI parameter is used when configured """ expected_uri = "https://monke.chain/" expected_api_uri = "https://api.monke.chain/api" - custon_network_name = "monkechain" - conf = { - **DEFAULT_CONFIG, - **{ - "networks": { - "custom": [ - {"name": custon_network_name, "chain_id": 31337, "ecosystem": "ethereum"} - ] - }, - "etherscan": { - "ethereum": { - custon_network_name: {"uri": expected_uri, "api_uri": expected_api_uri} - } - }, - }, + custom_network_name = "monkechain" + networks_conf = ( + {"custom": [{"name": custom_network_name, "chain_id": 31337, "ecosystem": "ethereum"}]}, + ) + + explorer_confg = { + "ethereum": {custom_network_name: {"uri": expected_uri, "api_uri": expected_api_uri}} } - with temp_config(conf): + with project.temp_config(networks=networks_conf, etherscan=explorer_confg): with mock_provider("ethereum", "monkechain"): assert account.query_manager.engines["etherscan"].etherscan_uri == expected_uri assert account.query_manager.engines["etherscan"].etherscan_api_uri == expected_api_uri diff --git a/tests/test_dependency.py b/tests/test_dependency.py index 317ee6a..cac3c27 100644 --- a/tests/test_dependency.py +++ b/tests/test_dependency.py @@ -1,5 +1,6 @@ import pytest from ape.exceptions import ProjectError +from ape.utils import create_tempdir from ape_etherscan.dependency import EtherscanDependency @@ -23,7 +24,7 @@ def test_dependency(mock_backend, verification_type, expected_name, contract_add ecosystem=ecosystem, network=network, ) - actual = dependency.extract_manifest() + actual = dependency._get_manifest() assert dependency.version_id == f"{ecosystem}_{network}" assert f"{expected_name}.sol" in actual.sources assert actual.compilers[0].name == "Solidity" @@ -40,6 +41,7 @@ def test_dependency_not_verified(mock_backend): ecosystem="ethereum", network="mainnet", ) - expected = "Etherscan dependency 'Apes' not verified." - with pytest.raises(ProjectError, match=expected): - dependency.extract_manifest() + expected = "Etherscan dependency 'apes' not verified." + with create_tempdir() as temp_dir: + with pytest.raises(ProjectError, match=expected): + dependency.fetch(temp_dir) diff --git a/tests/test_etherscan.py b/tests/test_etherscan.py index 1a10ffb..eb31243 100644 --- a/tests/test_etherscan.py +++ b/tests/test_etherscan.py @@ -1,4 +1,4 @@ -from typing import Callable +from collections.abc import Callable import pytest from ape.api.query import AccountTransactionQuery