From 412dc6bd33982ecc3bc2dd02ee39b96653398e54 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Wed, 18 Oct 2023 22:00:22 -0400 Subject: [PATCH] fix: fix --- .pre-commit-config.yaml | 10 +++--- CONTRIBUTING.md | 2 +- ape_etherscan/client.py | 24 +++++++++---- ape_etherscan/explorer.py | 10 +++--- ape_etherscan/query.py | 7 ++-- setup.py | 9 ++--- tests/conftest.py | 74 ++++++++++++++++++++++++--------------- 7 files changed, 83 insertions(+), 53 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4fa5c77..1e0b64e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.2.0 + rev: v4.5.0 hooks: - id: check-yaml @@ -10,24 +10,24 @@ repos: - id: isort - repo: https://github.com/psf/black - rev: 23.3.0 + rev: 23.10.0 hooks: - id: black name: black - repo: https://github.com/pycqa/flake8 - rev: 6.0.0 + rev: 6.1.0 hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.991 + rev: v1.6.1 hooks: - id: mypy additional_dependencies: [types-PyYAML, types-requests, types-setuptools, pydantic] - repo: https://github.com/executablebooks/mdformat - rev: 0.7.14 + rev: 0.7.17 hooks: - id: mdformat additional_dependencies: [mdformat-gfm, mdformat-frontmatter] diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 363e7b6..64d748f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -46,4 +46,4 @@ A pull request represents the start of a discussion, and doesn't necessarily nee If you are opening a work-in-progress pull request to verify that it passes CI tests, please consider [marking it as a draft](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests#draft-pull-requests). -Join the Ethereum Python [Discord](https://discord.gg/PcEJ54yX) if you have any questions. +Join the ApeWorX [Discord](https://discord.gg/apeworx) if you have any questions. diff --git a/ape_etherscan/client.py b/ape_etherscan/client.py index 54829ce..8b4c1b2 100644 --- a/ape_etherscan/client.py +++ b/ape_etherscan/client.py @@ -8,6 +8,7 @@ from ape.logging import logger from ape.utils import USER_AGENT, ManagerAccessMixin from requests import Session +from yarl import URL from ape_etherscan.exceptions import ( UnhandledResultError, @@ -176,6 +177,11 @@ def _retries(self) -> int: def _min_time_between_calls(self) -> float: return 1 / self._rate_limit # seconds / calls per second + @property + def _clean_uri(self) -> str: + url = URL(self.base_uri).with_user(None).with_password(None) + return f"{url.with_path('')}/[hidden]" if url.path else f"{url}" + def _get( self, params: Optional[Dict] = None, @@ -216,6 +222,7 @@ def _request( ) -> EtherscanResponse: headers = headers or self.DEFAULT_HEADERS for i in range(self._retries): + logger.debug(f"Request sent to {self._clean_uri}.") response = self.session.request( method.upper(), self.base_uri, @@ -225,14 +232,16 @@ def _request( timeout=1024, ) if response.status_code == 429: - time.sleep(2**i) + time_to_sleep = 2**i + logger.debug(f"Request was throttled. Retrying in {time_to_sleep} seconds.") + time.sleep(time_to_sleep) continue # Recieved a real response unrelated to rate limiting. if raise_on_exceptions: response.raise_for_status() elif not 200 <= response.status_code < 300: - logger.error(response.text) + logger.error(f"Response was not successful: {response.text}") break @@ -264,9 +273,8 @@ def get_source_code(self) -> SourceCodeResponse: "address": self._address, } result = self._get(params=params) - result_list = result.value or [] - if not result_list: + if not (result_list := result.value or []): return SourceCodeResponse() elif len(result_list) > 1: @@ -341,9 +349,11 @@ def get_creation_data(self) -> List[ContractCreationResponse]: "contractaddresses": [self._address], } result = self._get(params=params) - assert isinstance(result.value, list) - assert all(isinstance(val, dict) for val in result.value) - return [ContractCreationResponse(**item) for item in result.value] + items = result.value or [] + if not isinstance(items, list): + raise ValueError("Expecting list.") + + return [ContractCreationResponse(**item) for item in items] class AccountClient(_APIClient): diff --git a/ape_etherscan/explorer.py b/ape_etherscan/explorer.py index 31c9541..000b032 100644 --- a/ape_etherscan/explorer.py +++ b/ape_etherscan/explorer.py @@ -5,6 +5,7 @@ from ape.api import ExplorerAPI from ape.contracts import ContractInstance from ape.exceptions import ProviderNotConnectedError +from ape.logging import logger from ape.types import AddressType, ContractType from ape_etherscan.client import ClientFactory, get_etherscan_uri @@ -35,20 +36,19 @@ def get_contract_type(self, address: AddressType) -> Optional[ContractType]: client = self._client_factory.get_contract_client(address) source_code = client.get_source_code() - abi_string = source_code.abi - if not abi_string: + if not (abi_string := source_code.abi): return None try: abi = json.loads(abi_string) - except JSONDecodeError: + except JSONDecodeError as err: + logger.error(f"Error with contract ABI: {err}") return None contract_type = ContractType(abi=abi, contractName=source_code.name) if source_code.name == "Vyper_contract" and "symbol" in contract_type.view_methods: try: - checksummed_address = self.provider.network.ecosystem.decode_address(address) - contract = ContractInstance(checksummed_address, contract_type) + contract = ContractInstance(address, contract_type) contract_type.name = contract.symbol() or contract_type.name except ProviderNotConnectedError: pass diff --git a/ape_etherscan/query.py b/ape_etherscan/query.py index 5971887..d4b4384 100644 --- a/ape_etherscan/query.py +++ b/ape_etherscan/query.py @@ -98,8 +98,9 @@ def get_account_transactions(self, query: AccountTransactionQuery) -> Iterator[R def get_contract_creation_receipt(self, query: ContractCreationQuery) -> Iterator[ReceiptAPI]: client = self._client_factory.get_contract_client(query.contract) creation_data = client.get_creation_data() - - if len(creation_data) != 1: - raise + if len(creation_data) == 0: + return + elif len(creation_data) != 1: + raise ValueError("Expecting single creation data.") yield self.chain_manager.get_receipt(creation_data[0].txHash) diff --git a/setup.py b/setup.py index a4c9b25..e12d53c 100644 --- a/setup.py +++ b/setup.py @@ -20,15 +20,16 @@ "pytest-mock", # Test mocker ], "lint": [ - "black>=23.3.0,<24", # auto-formatter and linter - "mypy>=0.991,<1", # Static type analyzer + "black>=23.10.0,<24", # auto-formatter and linter + "mypy>=1.6.1,<2", # Static type analyzer "types-requests>=2.28.7", # Needed due to mypy typeshed "types-setuptools", # Needed due to mypy typeshed - "flake8>=6.0.0,<7", # Style linter + "flake8>=6.1.0,<7", # Style linter "isort>=5.10.1,<6", # Import sorting linter - "mdformat>=0.7.16", # Auto-formatter for markdown + "mdformat>=0.7.17", # Auto-formatter for markdown "mdformat-gfm>=0.3.5", # Needed for formatting GitHub-flavored markdown "mdformat-frontmatter>=0.4.1", # Needed for frontmatters-style headers in issue templates + "pydantic<2", # Needed for successful type check. ], "doc": [ "Sphinx>=3.4.3,<4", # Documentation generator diff --git a/tests/conftest.py b/tests/conftest.py index 2b3ebe9..54bb977 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -83,23 +83,28 @@ import_remapping: - "@bar=bar" """ -STANDARD_INPUT_JSON = { - "language": "Solidity", - "sources": { - "foo.sol": {"content": FOO_SOURCE_CODE}, - ".cache/bar/local/bar.sol": {"content": BAR_SOURCE_CODE}, - }, - "settings": { - "optimizer": {"enabled": True, "runs": 200}, - "outputSelection": { - "subcontracts/foo.sol": {"*": OUTPUT_SELECTION, "": ["ast"]}, - ".cache/bar/local/bar.sol": {"*": OUTPUT_SELECTION, "": ["ast"]}, + + +@pytest.fixture(scope="session") +def standard_input_json(library): + return { + "language": "Solidity", + "sources": { + "foo.sol": {"content": FOO_SOURCE_CODE}, + ".cache/bar/local/bar.sol": {"content": BAR_SOURCE_CODE}, }, - "remappings": ["@bar=.cache/bar/local"], - }, - "libraryname1": "MyLib", - "libraryaddress1": "0x274b028b03A250cA03644E6c578D81f019eE1323", -} + "settings": { + "optimizer": {"enabled": True, "runs": 200}, + "outputSelection": { + ".cache/bar/local/bar.sol": {"": ["ast"], "*": OUTPUT_SELECTION}, + "subcontracts/foo.sol": {"": ["ast"], "*": OUTPUT_SELECTION}, + }, + "remappings": ["@bar=.cache/bar/local"], + "viaIR": False, + }, + "libraryname1": "MyLib", + "libraryaddress1": library.address, + } @pytest.fixture(autouse=True) @@ -445,7 +450,7 @@ def get_mock_response( @pytest.fixture -def verification_params(address_to_verify): +def verification_params(address_to_verify, standard_input_json): ctor_args = "" # noqa: E501 return { @@ -459,16 +464,18 @@ def verification_params(address_to_verify): "module": "contract", "optimizationUsed": 1, "runs": 200, - "sourceCode": StringIO(json.dumps(STANDARD_INPUT_JSON)), + "sourceCode": StringIO(json.dumps(standard_input_json)), } -@pytest.fixture -def verification_params_with_ctor_args(address_to_verify_with_ctor_args): +@pytest.fixture(scope="session") +def verification_params_with_ctor_args( + address_to_verify_with_ctor_args, library, standard_input_json +): # abi-encoded representation of uint256 value 42 ctor_args = "000000000000000000000000000000000000000000000000000000000000002a" # noqa: E501 - json_data = STANDARD_INPUT_JSON.copy() + json_data = standard_input_json.copy() json_data["libraryaddress1"] = "0xF2Df0b975c0C9eFa2f8CA0491C2d1685104d2488" return { @@ -487,15 +494,26 @@ def verification_params_with_ctor_args(address_to_verify_with_ctor_args): @pytest.fixture(scope="session") -def address_to_verify(fake_connection, project, account): - # Deploy the library first. - library = account.deploy(project.MyLib) - ape.chain.contracts._local_contract_types[library.address] = library.contract_type +def chain(): + return ape.chain - # Add the library to recompile contract `foo`. - solidity = project.compiler_manager.solidity - solidity.add_library(library) +@pytest.fixture(scope="session") +def solidity(project): + return project.compiler_manager.solidity + + +@pytest.fixture(scope="session") +def library(account, project, chain, solidity): + lib = account.deploy(project.MyLib) + chain.contracts._local_contract_types[lib.address] = lib.contract_type + solidity.add_library(lib) + return lib + + +@pytest.fixture(scope="session") +def address_to_verify(fake_connection, library, project, account): + _ = library # Ensure library is deployed first. foo = project.foo.deploy(sender=account) ape.chain.contracts._local_contract_types[address] = foo.contract_type return foo.address