Skip to content

Commit

Permalink
feat: add optimism sepolia testnet [APE-1488] (ApeWorX#105)
Browse files Browse the repository at this point in the history
Co-authored-by: Juliya Smith <[email protected]>
  • Loading branch information
fubuloubu and antazoey committed Dec 13, 2023
1 parent 649ed50 commit 258889a
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 64 deletions.
4 changes: 2 additions & 2 deletions ape_etherscan/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def get_etherscan_uri(ecosystem_name: str, network_name: str):
return (
"https://optimistic.etherscan.io"
if network_name == "mainnet"
else "https://goerli-optimism.etherscan.io"
else f"https://{network_name}-optimism.etherscan.io"
)
elif ecosystem_name == "polygon-zkevm":
return (
Expand Down Expand Up @@ -105,7 +105,7 @@ def get_etherscan_api_uri(ecosystem_name: str, network_name: str):
return (
"https://api-optimistic.etherscan.io/api"
if network_name == "mainnet"
else "https://api-goerli-optimistic.etherscan.io/api"
else f"https://api-{network_name}-optimistic.etherscan.io/api"
)
elif ecosystem_name == "polygon-zkevm":
return (
Expand Down
35 changes: 18 additions & 17 deletions ape_etherscan/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,42 +11,43 @@
"gnosis": "GNOSISSCAN_API_KEY",
}
NETWORKS = {
"ethereum": [
"arbitrum": [
"mainnet",
"goerli",
"sepolia",
],
"arbitrum": [
"avalanche": [
"mainnet",
"fuji",
],
"base": [
"mainnet",
"goerli",
],
"fantom": [
"opera",
"bsc": [
"mainnet",
"testnet",
],
"optimism": [
"ethereum": [
"mainnet",
"goerli",
"sepolia",
],
"base": [
"mainnet",
"goerli",
"fantom": [
"opera",
"testnet",
],
"polygon-zkevm": [
"gnosis": ["mainnet"],
"optimism": [
"mainnet",
"goerli",
"sepolia",
],
"polygon": [
"mainnet",
"mumbai",
],
"avalanche": [
"mainnet",
"fuji",
],
"bsc": [
"polygon-zkevm": [
"mainnet",
"testnet",
"goerli",
],
"gnosis": ["mainnet"],
}
35 changes: 19 additions & 16 deletions ape_etherscan/verify.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,26 +182,24 @@ def constructor_arguments(self) -> str:
deploy_receipt = None
while checks_done <= timeout:
# If was just deployed, it takes a few seconds to show up in API response
if deploy_receipt := next(self._account_client.get_all_normal_transactions(), None):
break

try:
deploy_receipt = next(self._account_client.get_all_normal_transactions())
except StopIteration:
continue

logger.debug("Waiting for deploy receipt in Etherscan...")
checks_done += 1
time.sleep(2.5)
else:
logger.debug("Waiting for deploy receipt in Etherscan...")
checks_done += 1
time.sleep(2.5)

if not deploy_receipt:
raise ContractVerificationError(
f"Failed to find to deploy receipt for '{self.address}'"
)

runtime_bytecode = self._contract_type.runtime_bytecode
if runtime_bytecode:
return extract_constructor_arguments(
deploy_receipt["input"], runtime_bytecode.bytecode or ""
)
if code := self._contract_type.runtime_bytecode:
runtime_code = code.bytecode or ""
deployment_code = deploy_receipt["input"]
ctor_args = extract_constructor_arguments(deployment_code, runtime_code)
return ctor_args
else:
raise ContractVerificationError("Failed to find runtime bytecode.")

Expand Down Expand Up @@ -250,7 +248,13 @@ def attempt_verification(self):
all_settings = compiler_plugin.get_compiler_settings(
[self._source_path], base_path=self._base_path
)
settings = all_settings[version]

# Hack to allow any Version object work.
# TODO: Replace with all_settings[version] on 0.7 upgrade
settings = {str(v): s for v, s in all_settings.items() if str(v) == str(version)}[
str(version)
]

optimizer = settings.get("optimizer", {})
optimized = optimizer.get("enabled", False)
runs = optimizer.get("runs", 200)
Expand Down Expand Up @@ -411,7 +415,6 @@ def extract_constructor_arguments(deployment_bytecode: str, runtime_bytecode: st
runtime_bytecode = (
runtime_bytecode[2:] if runtime_bytecode.startswith("0x") else runtime_bytecode
)

if deployment_bytecode.endswith(runtime_bytecode):
# If the runtime bytecode is at the end of the deployment bytecode,
# there are no constructor arguments
Expand All @@ -421,7 +424,7 @@ def extract_constructor_arguments(deployment_bytecode: str, runtime_bytecode: st
start_index = deployment_bytecode.find(runtime_bytecode)

# If the runtime bytecode is not found within the deployment bytecode,
# return an error message
# return an error message.
if start_index == -1:
raise ContractVerificationError("Runtime bytecode not found within deployment bytecode.")

Expand Down
5 changes: 3 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@
"lint": [
"black>=23.12.0,<24", # Auto-formatter and linter
"mypy>=1.7.1,<2", # Static type analyzer
"types-setuptools", # Needed for mypy type shed
"types-requests", # Needed for mypy type shed
"types-requests>=2.28.7", # Needed due to mypy typeshed
"types-setuptools", # Needed due to mypy typeshed
"flake8>=6.1.0,<7", # Style linter
"flake8-breakpoint>=1.1.0,<2", # Detect breakpoints left in code
"flake8-print>=5.0.0,<6", # Detect print statements left in code
Expand Down Expand Up @@ -81,6 +81,7 @@
"eth-ape>=0.7.0,<0.8",
"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",
extras_require=extras_require,
Expand Down
68 changes: 45 additions & 23 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ def get_url_f(testnet: bool = False, tld: str = "io"):
"optimism": {
"mainnet": testnet_url("optimistic", "etherscan"),
"goerli": testnet_url("goerli-optimistic", "etherscan"),
"sepolia": testnet_url("sepolia-optimistic", "etherscan"),
},
"polygon": {
"mainnet": com_url("polygonscan"),
Expand Down Expand Up @@ -390,24 +391,26 @@ def _get_contract_type_response(self, file_name: str) -> Any:
def _expected_get_ct_params(self, address: str) -> Dict:
return {"module": "contract", "action": "getsourcecode", "address": address}

def setup_mock_account_transactions_response(self, address: Optional[AddressType] = None):
def setup_mock_account_transactions_response(
self, address: Optional[AddressType] = None, **overrides
):
file_name = "get_account_transactions.json"
test_data_path = MOCK_RESPONSES_PATH / file_name

if address:
params = EXPECTED_ACCOUNT_TXNS_PARAMS.copy()
params["address"] = address
else:
params = EXPECTED_ACCOUNT_TXNS_PARAMS

with open(test_data_path) as response_data_file:
response = self.get_mock_response(response_data_file, file_name=file_name)
self.add_handler("GET", "account", params, return_value=response)
self.set_network("ethereum", "mainnet")
return response
response = self.get_mock_response(
response_data_file, file_name=file_name, response_overrides=overrides
)

return self._setup_account_response(params, response)

def setup_mock_account_transactions_with_ctor_args_response(
self, address: Optional[AddressType] = None
self, address: Optional[AddressType] = None, **overrides
):
file_name = "get_account_transactions_with_ctor_args.json"
test_data_path = MOCK_RESPONSES_PATH / file_name
Expand All @@ -419,10 +422,16 @@ def setup_mock_account_transactions_with_ctor_args_response(
params = EXPECTED_ACCOUNT_TXNS_PARAMS

with open(test_data_path) as response_data_file:
response = self.get_mock_response(response_data_file, file_name=file_name)
self.add_handler("GET", "account", params, return_value=response)
self.set_network("ethereum", "mainnet")
return response
response = self.get_mock_response(
response_data_file, file_name=file_name, response_overrides=overrides
)

return self._setup_account_response(params, response)

def _setup_account_response(self, params, response):
self.add_handler("GET", "account", params, return_value=response)
self.set_network("ethereum", "mainnet")
return response

def get_mock_response(
self, response_data: Optional[Union[IO, Dict, str, MagicMock]] = None, **kwargs
Expand All @@ -438,7 +447,9 @@ def get_mock_response(
response_data = {}

response = self._mocker.MagicMock(spec=Response)
response.json.return_value = response_data
assert isinstance(response_data, dict) # For mypy
overrides: Dict = kwargs.get("response_overrides", {})
response.json.return_value = {**response_data, **overrides}
response.text = json.dumps(response_data or {})

response.status_code = 200
Expand Down Expand Up @@ -469,19 +480,22 @@ def verification_params(address_to_verify, standard_input_json):


@pytest.fixture(scope="session")
def verification_params_with_ctor_args(
address_to_verify_with_ctor_args, library, standard_input_json
):
def constructor_arguments():
# abi-encoded representation of uint256 value 42
ctor_args = "000000000000000000000000000000000000000000000000000000000000002a" # noqa: E501
return "000000000000000000000000000000000000000000000000000000000000002a" # noqa: E501


@pytest.fixture(scope="session")
def verification_params_with_ctor_args(
address_to_verify_with_ctor_args, library, standard_input_json, constructor_arguments
):
json_data = standard_input_json.copy()
json_data["libraryaddress1"] = "0xF2Df0b975c0C9eFa2f8CA0491C2d1685104d2488"

return {
"action": "verifysourcecode",
"codeformat": "solidity-standard-json-input",
"constructorArguements": ctor_args,
"constructorArguements": constructor_arguments,
"contractaddress": address_to_verify_with_ctor_args,
"contractname": "foo.sol:fooWithConstructor",
"evmversion": None,
Expand Down Expand Up @@ -512,15 +526,18 @@ def library(account, project, chain, solidity):


@pytest.fixture(scope="session")
def address_to_verify(fake_connection, library, project, account):
def contract_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
return project.foo.deploy(sender=account)


@pytest.fixture(scope="session")
def address_to_verify_with_ctor_args(fake_connection, project, account):
def address_to_verify(contract_to_verify):
return contract_to_verify


@pytest.fixture(scope="session")
def contract_to_verify_with_ctor_args(fake_connection, project, account):
# Deploy the library first.
library = account.deploy(project.MyLib)
ape.chain.contracts._local_contract_types[library.address] = library.contract_type
Expand All @@ -531,7 +548,12 @@ def address_to_verify_with_ctor_args(fake_connection, project, account):

foo = project.fooWithConstructor.deploy(42, sender=account)
ape.chain.contracts._local_contract_types[address] = foo.contract_type
return foo.address
return foo


@pytest.fixture(scope="session")
def address_to_verify_with_ctor_args(contract_to_verify_with_ctor_args):
return contract_to_verify_with_ctor_args.address


@pytest.fixture(scope="session")
Expand Down
29 changes: 25 additions & 4 deletions tests/test_etherscan.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@
("optimism", "mainnet-fork", "optimistic.etherscan.io"),
("optimism", "goerli", "goerli-optimism.etherscan.io"),
("optimism", "goerli-fork", "goerli-optimism.etherscan.io"),
("optimism", "sepolia", "sepolia-optimism.etherscan.io"),
("optimism", "sepolia-fork", "sepolia-optimism.etherscan.io"),
("polygon", "mainnet", "polygonscan.com"),
("polygon", "mainnet-fork", "polygonscan.com"),
("polygon", "mumbai", "mumbai.polygonscan.com"),
Expand Down Expand Up @@ -85,10 +87,13 @@ def sim(self):

@pytest.fixture
def setup_verification_test(
mock_backend, verification_params, verification_tester_cls, address_to_verify
mock_backend, verification_params, verification_tester_cls, contract_to_verify
):
def setup(found_handler: Callable, threshold: int = 2):
mock_backend.setup_mock_account_transactions_response(address=address_to_verify)
overrides = _acct_tx_overrides(contract_to_verify)
mock_backend.setup_mock_account_transactions_response(
address=contract_to_verify.address, **overrides
)
mock_backend.add_handler("POST", "contract", verification_params, return_value=PUBLISH_GUID)
verification_tester = verification_tester_cls(found_handler, threshold=threshold)
mock_backend.add_handler(
Expand All @@ -107,11 +112,15 @@ def setup_verification_test_with_ctor_args(
mock_backend,
verification_params_with_ctor_args,
verification_tester_cls,
address_to_verify_with_ctor_args,
contract_to_verify_with_ctor_args,
constructor_arguments,
):
def setup(found_handler: Callable, threshold: int = 2):
overrides = _acct_tx_overrides(
contract_to_verify_with_ctor_args, args=constructor_arguments
)
mock_backend.setup_mock_account_transactions_with_ctor_args_response(
address=address_to_verify_with_ctor_args
address=contract_to_verify_with_ctor_args.address, **overrides
)
mock_backend.add_handler(
"POST", "contract", verification_params_with_ctor_args, return_value=PUBLISH_GUID
Expand Down Expand Up @@ -251,3 +260,15 @@ def test_publish_contract_with_ctor_args(
setup_verification_test_with_ctor_args(lambda: "Pass - You made it!")
explorer.publish_contract(address_to_verify_with_ctor_args)
assert caplog.records[-1].message == expected_verification_log_with_ctor_args


def _acct_tx_overrides(contract, args=None):
suffix = args or ""
if suffix.startswith("0x"):
suffix = suffix[2:]

# Include construcor aguments!
ct = contract.contract_type
prefix = ct.deployment_bytecode.bytecode
code = f"{prefix}{suffix}"
return {"result": [{"input": code}]}

0 comments on commit 258889a

Please sign in to comment.