diff --git a/boa/verifiers.py b/boa/verifiers.py index 0d27d6dc..cb998735 100644 --- a/boa/verifiers.py +++ b/boa/verifiers.py @@ -6,6 +6,7 @@ from typing import Optional import requests +from cached_property import cached_property from boa.util.abi import Address from boa.util.open_ctx import Open @@ -56,21 +57,16 @@ def verify( url = f"{self.uri}/api/v2/smart-contracts/{address}/" url += f"verification/via/vyper-standard-input?apikey={api_key}" - data = { - "compiler_version": solc_json["compiler_version"], - "license_type": license_type, - } - files = { - "files[0]": ( - contract_name, - json.dumps(solc_json).encode("utf-8"), - "application/json", - ) - } - - response = requests.post(url, data=data, files=files) + + version = self._get_compiler_version(solc_json["compiler_version"]) + solc_json = {**solc_json, "compiler_version": version} + data = {"compiler_version": version, "license_type": license_type} + file = (contract_name, json.dumps(solc_json), "application/json") + + response = requests.post(url, data=data, files={"files[0]": file}) response.raise_for_status() print(response.json().get("message")) # usually verification started + # print(f"Sent {data} to {url} with {file}") if not wait: return VerificationResult(address, self) @@ -78,6 +74,27 @@ def verify( self.wait_for_verification(address) return None + def _get_compiler_version(self, compiler_version) -> str: + """ + Runs a partial match based on the compiler version. + Blockscout only accepts exact matches, but vyper can have a different + commit hash length depending on the version and installation method. + + Raises a ValueError if no match is found or if multiple matches are found. + """ + supported = self.supported_versions + match [v for v in supported if compiler_version in v]: + case [version]: + return version + case []: + err = "Could not find a matching compiler version on Blockscout. " + err += f"Given: {compiler_version}, supported: {supported}." + raise Exception(err) + case multiple: + err = "Ambiguous compiler version for Blockscout verification. " + err += f"Given: {compiler_version}, found: {multiple}." + raise Exception(err) + def wait_for_verification(self, address: Address) -> None: """ Waits for the contract to be verified on Blockscout. @@ -94,7 +111,7 @@ def wait_for_verification(self, address: Address) -> None: time.sleep(wait_time.total_seconds()) wait_time *= self.backoff_factor - raise TimeoutError("Timeout waiting for verification to complete") + raise TimeoutError(f"Timeout waiting for verification of {address}") def is_verified(self, address: Address) -> bool: api_key = self.api_key or "" @@ -106,6 +123,14 @@ def is_verified(self, address: Address) -> bool: response.raise_for_status() return response.json().get("is_verified", False) + @cached_property + def supported_versions(self) -> list[str]: + response = requests.get( + "https://http.sc-verifier.services.blockscout.com/api/v2/verifier/vyper/versions" + ) + response.raise_for_status() + return response.json()["compilerVersions"] + _verifier = Blockscout() diff --git a/tests/integration/network/sepolia/test_sepolia_env.py b/tests/integration/network/sepolia/test_sepolia_env.py index 0d00e631..81016c87 100644 --- a/tests/integration/network/sepolia/test_sepolia_env.py +++ b/tests/integration/network/sepolia/test_sepolia_env.py @@ -1,4 +1,6 @@ import os +from random import randint, sample +from string import ascii_lowercase import pytest @@ -38,13 +40,23 @@ def simple_contract(): return boa.loads(code, STARTING_SUPPLY) -def test_verify(simple_contract): +def test_verify(): + # generate a random contract so the verification will actually be done again + name = "".join(sample(ascii_lowercase, 10)) + contract = boa.loads( + f""" +@external +def {name}() -> uint256: + return {randint(0, 2**256 - 1)} + """, + name=name, + ) + api_key = os.getenv("BLOCKSCOUT_API_KEY") blockscout = Blockscout("https://eth-sepolia.blockscout.com", api_key) - with boa.set_verifier(blockscout): - result = boa.verify(simple_contract) - result.wait_for_verification() - assert result.is_verified() + result = boa.verify(contract, blockscout) + result.wait_for_verification() + assert result.is_verified() def test_env_type():