Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add optimism sepolia testnet [APE-1488] #105

Merged
merged 13 commits into from
Dec 13, 2023
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ repos:
- id: isort

- repo: https://github.com/psf/black
rev: 23.10.0
rev: 23.12.0
hooks:
- id: black
name: black
Expand All @@ -21,7 +21,7 @@ repos:
- id: flake8

- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.6.1
rev: v1.7.1
hooks:
- id: mypy
additional_dependencies: [types-PyYAML, types-requests, types-setuptools, pydantic]
Expand Down
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",
Copy link
Member

@antazoey antazoey Dec 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

only addition besides ethereum's here, rest of diff is sorting
note: eth also just has sep added

],
"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 @@ -20,8 +20,8 @@
"pytest-mock", # Test mocker
],
"lint": [
"black>=23.10.0,<24", # auto-formatter and linter
"mypy>=1.6.1,<2", # Static type analyzer
"black>=23.12.0,<24", # auto-formatter and linter
"mypy>=1.7.1,<2", # Static type analyzer
"types-requests>=2.28.7", # Needed due to mypy typeshed
"types-setuptools", # Needed due to mypy typeshed
"flake8>=6.1.0,<7", # Style linter
Expand Down Expand Up @@ -78,6 +78,7 @@
install_requires=[
"eth-ape>=0.6.16,<0.7",
"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}]}