diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 14cb032..9c1c803 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -75,7 +75,6 @@ jobs: - name: Run Tests env: TEST_TYPE: ${{ matrix.type }} - GITHUB_ETHERSCAN: ${{ secrets.GITHUB_ETHERSCAN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} shell: bash run: | diff --git a/.github/workflows/etherscan.yml b/.github/workflows/etherscan.yml index 98a0bf7..680630c 100644 --- a/.github/workflows/etherscan.yml +++ b/.github/workflows/etherscan.yml @@ -42,7 +42,7 @@ jobs: - name: Run Tests env: TEST_TYPE: ${{ matrix.type }} - GITHUB_ETHERSCAN: ${{ secrets.GITHUB_ETHERSCAN }} + ETHERSCAN_API_KEY: ${{ secrets.ETHERSCAN_API_KEY }} shell: bash run: | bash "scripts/ci_test_${TEST_TYPE}.sh" diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 482bb9c..be6eea2 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -41,5 +41,7 @@ jobs: solc-select use latest --always-install - name: Run Tests + env: + ETHERSCAN_API_KEY: ${{ secrets.ETHERSCAN_API_KEY }} run: | pytest tests diff --git a/crytic_compile/cryticparser/cryticparser.py b/crytic_compile/cryticparser/cryticparser.py index 1646a93..682cfec 100755 --- a/crytic_compile/cryticparser/cryticparser.py +++ b/crytic_compile/cryticparser/cryticparser.py @@ -329,30 +329,6 @@ def _init_etherscan(parser: ArgumentParser) -> None: default=DEFAULTS_FLAG_IN_CONFIG["etherscan_api_key"], ) - group_etherscan.add_argument( - "--arbiscan-apikey", - help="Etherscan API key.", - action="store", - dest="arbiscan_api_key", - default=DEFAULTS_FLAG_IN_CONFIG["etherscan_api_key"], - ) - - group_etherscan.add_argument( - "--polygonscan-apikey", - help="Etherscan API key.", - action="store", - dest="polygonscan_api_key", - default=DEFAULTS_FLAG_IN_CONFIG["etherscan_api_key"], - ) - - group_etherscan.add_argument( - "--test-polygonscan-apikey", - help="Etherscan API key.", - action="store", - dest="test_polygonscan_api_key", - default=DEFAULTS_FLAG_IN_CONFIG["etherscan_api_key"], - ) - group_etherscan.add_argument( "--avax-apikey", help="Etherscan API key.", @@ -361,62 +337,6 @@ def _init_etherscan(parser: ArgumentParser) -> None: default=DEFAULTS_FLAG_IN_CONFIG["etherscan_api_key"], ) - group_etherscan.add_argument( - "--ftmscan-apikey", - help="Etherscan API key.", - action="store", - dest="ftmscan_api_key", - default=DEFAULTS_FLAG_IN_CONFIG["etherscan_api_key"], - ) - - group_etherscan.add_argument( - "--bscan-apikey", - help="Etherscan API key.", - action="store", - dest="bscan_api_key", - default=DEFAULTS_FLAG_IN_CONFIG["etherscan_api_key"], - ) - - group_etherscan.add_argument( - "--optim-apikey", - help="Optimistic API key.", - action="store", - dest="optim_api_key", - default=DEFAULTS_FLAG_IN_CONFIG["etherscan_api_key"], - ) - - group_etherscan.add_argument( - "--base-apikey", - help="Basescan API key.", - action="store", - dest="base_api_key", - default=DEFAULTS_FLAG_IN_CONFIG["etherscan_api_key"], - ) - - group_etherscan.add_argument( - "--gno-apikey", - help="Gnosisscan API key.", - action="store", - dest="gno_api_key", - default=DEFAULTS_FLAG_IN_CONFIG["etherscan_api_key"], - ) - - group_etherscan.add_argument( - "--polyzk-apikey", - help="zkEVM Polygonscan API key.", - action="store", - dest="polyzk_api_key", - default=DEFAULTS_FLAG_IN_CONFIG["etherscan_api_key"], - ) - - group_etherscan.add_argument( - "--blast-apikey", - help="Blastscan API key.", - action="store", - dest="blast_api_key", - default=DEFAULTS_FLAG_IN_CONFIG["etherscan_api_key"], - ) - group_etherscan.add_argument( "--etherscan-export-directory", help="Directory in which to save the analyzed contracts.", diff --git a/crytic_compile/platform/etherscan.py b/crytic_compile/platform/etherscan.py index bcb2491..e042d37 100644 --- a/crytic_compile/platform/etherscan.py +++ b/crytic_compile/platform/etherscan.py @@ -27,33 +27,102 @@ LOGGER = logging.getLogger("CryticCompile") -ETHERSCAN_BASE = "https://api%s/api?module=contract&action=getsourcecode&address=%s" +# Etherscan v1 API style (per-scanner URL) +ETHERSCAN_BASE_V1 = "https://api%s/api?module=contract&action=getsourcecode&address=%s" +# Etherscan v2 API style (unified) +ETHERSCAN_BASE_V2 = ( + "https://api.etherscan.io/v2/api?chainid=%s&module=contract&action=getsourcecode&address=%s" +) + +# Bytecode URL style (for scraping) ETHERSCAN_BASE_BYTECODE = "https://%s/address/%s#code" -SUPPORTED_NETWORK = { - # Key, (prefix_base, perfix_bytecode) - "mainet:": (".etherscan.io", "etherscan.io"), - "optim:": ("-optimistic.etherscan.io", "optimistic.etherscan.io"), - "goerli:": ("-goerli.etherscan.io", "goerli.etherscan.io"), - "sepolia:": ("-sepolia.etherscan.io", "sepolia.etherscan.io"), - "tobalaba:": ("-tobalaba.etherscan.io", "tobalaba.etherscan.io"), - "bsc:": (".bscscan.com", "bscscan.com"), - "testnet.bsc:": ("-testnet.bscscan.com", "testnet.bscscan.com"), - "arbi:": (".arbiscan.io", "arbiscan.io"), - "testnet.arbi:": ("-testnet.arbiscan.io", "testnet.arbiscan.io"), - "poly:": (".polygonscan.com", "polygonscan.com"), - "mumbai:": ("-testnet.polygonscan.com", "testnet.polygonscan.com"), - "avax:": (".snowtrace.io", "snowtrace.io"), - "testnet.avax:": ("-testnet.snowtrace.io", "testnet.snowtrace.io"), - "ftm:": (".ftmscan.com", "ftmscan.com"), - "goerli.base:": ("-goerli.basescan.org", "goerli.basescan.org"), - "base:": (".basescan.org", "basescan.org"), - "gno:": (".gnosisscan.io", "gnosisscan.io"), - "polyzk:": ("-zkevm.polygonscan.com", "zkevm.polygonscan.com"), - "blast:": (".blastscan.io", "blastscan.io"), +# v1 style scanners +SUPPORTED_NETWORK_V1: Dict[str, Tuple[str, str]] = { + # None at this time. External tracer instances not operated by Etherscan would be here +} + +# v2 style scanners +SUPPORTED_NETWORK_V2: Dict[str, Tuple[str, str]] = { + # Key, (chainid, perfix_bytecode) + "mainnet": ("1", "etherscan.io"), + "sepolia": ("11155111", "sepolia.etherscan.io"), + "holesky": ("17000", "holesky.etherscan.io"), + "bsc": ("56", "bscscan.com"), + "testnet.bsc": ("97", "testnet.bscscan.com"), + "poly": ("137", "polygonscan.com"), + "amoy.poly": ("80002", "amoy.polygonscan.com"), + "polyzk": ("1101", "zkevm.polygonscan.com"), + "cardona.polyzk": ("2442", "cardona-zkevm.polygonscan.com"), + "base": ("8453", "basescan.org"), + "sepolia.base": ("84532", "sepolia.basescan.org"), + "arbi": ("42161", "arbiscan.io"), + "nova.arbi": ("42170", "nova.arbiscan.io"), + "sepolia.arbi": ("421614", "sepolia.arbiscan.io"), + "linea": ("59144", "lineascan.build"), + "sepolia.linea": ("59141", "sepolia.lineascan.build"), + "ftm": ("250", "ftmscan.com"), + "testnet.ftm": ("4002", "testnet.ftmscan.com"), + "blast": ("81457", "blastscan.io"), + "sepolia.blast": ("168587773", "sepolia.blastscan.io"), + "optim": ("10", "optimistic.etherscan.io"), + "sepolia.optim": ("11155420", "sepolia-optimism.etherscan.io"), + "avax": ("43114", "snowscan.xyz"), + "testnet.avax": ("43113", "testnet.snowscan.xyz"), + "bttc": ("199", "bttcscan.com"), + "testnet.bttc": ("1028", "testnet.bttcscan.com"), + "celo": ("42220", "celoscan.io"), + "alfajores.celo": ("44787", "alfajores.celoscan.io"), + "cronos": ("25", "cronoscan.com"), + "frax": ("252", "fraxscan.com"), + "holesky.frax": ("2522", "holesky.fraxscan.com"), + "gno": ("100", "gnosisscan.io"), + "kroma": ("255", "kromascan.com"), + "sepolia.kroma": ("2358", "sepolia.kromascan.com"), + "mantle": ("5000", "mantlescan.xyz"), + "sepolia.mantle": ("5003", "sepolia.mantlescan.xyz"), + "moonbeam": ("1284", "moonbeam.moonscan.io"), + "moonriver": ("1285", "moonriver.moonscan.io"), + "moonbase": ("1287", "moonbase.moonscan.io"), + "opbnb": ("204", "opbnb.bscscan.com"), + "testnet.opbnb": ("5611", "opbnb-testnet.bscscan.com"), + "scroll": ("534352", "scrollscan.com"), + "sepolia.scroll": ("534351", "sepolia.scrollscan.com"), + "taiko": ("167000", "taikoscan.io"), + "hekla.taiko": ("167009", "hekla.taikoscan.io"), + "wemix": ("1111", "wemixscan.com"), + "testnet.wemix": ("1112", "testnet.wemixscan.com"), + "era.zksync": ("324", "era.zksync.network"), + "sepoliaera.zksync": ("300", "sepolia-era.zksync.network"), + "xai": ("660279", "xaiscan.io"), + "sepolia.xai": ("37714555429", "sepolia.xaiscan.io"), } +SUPPORTED_NETWORK = {**SUPPORTED_NETWORK_V1, **SUPPORTED_NETWORK_V2} + + +def generate_supported_network_v2_list() -> None: + """Manual function to generate a dictionary for updating the SUPPORTED_NETWORK_V2 array""" + + with urllib.request.urlopen("https://api.etherscan.io/v2/chainlist") as response: + items = response.read() + networks = json.loads(items) + + id2name = {} + for name, (chainid, _) in SUPPORTED_NETWORK_V2.items(): + id2name[chainid] = name + + results = {} + for network in networks["result"]: + name = id2name.get(network["chainid"], f"{network['chainid']}") + results[name] = ( + network["chainid"], + network["blockexplorer"].replace("https://", "").strip("/"), + ) + + print(results) + def _handle_bytecode(crytic_compile: "CryticCompile", target: str, result_b: bytes) -> None: """Parse the bytecode and populate CryticCompile info @@ -215,15 +284,24 @@ def compile(self, crytic_compile: "CryticCompile", **kwargs: str) -> None: target = self._target - if target.startswith(tuple(SUPPORTED_NETWORK)): - prefix: Union[None, str] = SUPPORTED_NETWORK[target[: target.find(":") + 1]][0] - prefix_bytecode = SUPPORTED_NETWORK[target[: target.find(":") + 1]][1] + api_key_required = None + + if target.startswith(tuple(SUPPORTED_NETWORK_V2)): + api_key_required = 2 + prefix, addr = target.split(":", 2) + chainid, prefix_bytecode = SUPPORTED_NETWORK_V2[prefix] + etherscan_url = ETHERSCAN_BASE_V2 % (chainid, addr) + etherscan_bytecode_url = ETHERSCAN_BASE_BYTECODE % (prefix_bytecode, addr) + elif target.startswith(tuple(SUPPORTED_NETWORK_V1)): + api_key_required = 1 + prefix = SUPPORTED_NETWORK_V1[target[: target.find(":") + 1]][0] + prefix_bytecode = SUPPORTED_NETWORK_V1[target[: target.find(":") + 1]][1] addr = target[target.find(":") + 1 :] - etherscan_url = ETHERSCAN_BASE % (prefix, addr) + etherscan_url = ETHERSCAN_BASE_V1 % (prefix, addr) etherscan_bytecode_url = ETHERSCAN_BASE_BYTECODE % (prefix_bytecode, addr) - else: - etherscan_url = ETHERSCAN_BASE % (".etherscan.io", target) + api_key_required = 2 + etherscan_url = ETHERSCAN_BASE_V2 % ("1", target) etherscan_bytecode_url = ETHERSCAN_BASE_BYTECODE % ("etherscan.io", target) addr = target prefix = None @@ -232,75 +310,36 @@ def compile(self, crytic_compile: "CryticCompile", **kwargs: str) -> None: only_bytecode = kwargs.get("etherscan_only_bytecode", False) etherscan_api_key = kwargs.get("etherscan_api_key", None) - arbiscan_api_key = kwargs.get("arbiscan_api_key", None) - polygonscan_api_key = kwargs.get("polygonscan_api_key", None) - test_polygonscan_api_key = kwargs.get("test_polygonscan_api_key", None) - avax_api_key = kwargs.get("avax_api_key", None) - ftmscan_api_key = kwargs.get("ftmscan_api_key", None) - bscan_api_key = kwargs.get("bscan_api_key", None) - optim_api_key = kwargs.get("optim_api_key", None) - base_api_key = kwargs.get("base_api_key", None) - gno_api_key = kwargs.get("gno_api_key", None) - polyzk_api_key = kwargs.get("polyzk_api_key", None) - blast_api_key = kwargs.get("blast_api_key", None) + if etherscan_api_key is None: + etherscan_api_key = os.getenv("ETHERSCAN_API_KEY") export_dir = kwargs.get("export_dir", "crytic-export") export_dir = os.path.join( export_dir, kwargs.get("etherscan_export_dir", "etherscan-contracts") ) - if etherscan_api_key and "etherscan" in etherscan_url: + if api_key_required == 2 and etherscan_api_key: etherscan_url += f"&apikey={etherscan_api_key}" etherscan_bytecode_url += f"&apikey={etherscan_api_key}" - if arbiscan_api_key and "arbiscan" in etherscan_url: - etherscan_url += f"&apikey={arbiscan_api_key}" - etherscan_bytecode_url += f"&apikey={arbiscan_api_key}" - if polygonscan_api_key and "polygonscan" in etherscan_url: - etherscan_url += f"&apikey={polygonscan_api_key}" - etherscan_bytecode_url += f"&apikey={polygonscan_api_key}" - if test_polygonscan_api_key and "polygonscan" in etherscan_url: - etherscan_url += f"&apikey={test_polygonscan_api_key}" - etherscan_bytecode_url += f"&apikey={test_polygonscan_api_key}" - if avax_api_key and "snowtrace" in etherscan_url: - etherscan_url += f"&apikey={avax_api_key}" - etherscan_bytecode_url += f"&apikey={avax_api_key}" - if ftmscan_api_key and "ftmscan" in etherscan_url: - etherscan_url += f"&apikey={ftmscan_api_key}" - etherscan_bytecode_url += f"&apikey={ftmscan_api_key}" - if bscan_api_key and "bscscan" in etherscan_url: - etherscan_url += f"&apikey={bscan_api_key}" - etherscan_bytecode_url += f"&apikey={bscan_api_key}" - if optim_api_key and "optim" in etherscan_url: - etherscan_url += f"&apikey={optim_api_key}" - etherscan_bytecode_url += f"&apikey={optim_api_key}" - if base_api_key and "base" in etherscan_url: - etherscan_url += f"&apikey={base_api_key}" - etherscan_bytecode_url += f"&apikey={base_api_key}" - if gno_api_key and "gno" in etherscan_url: - etherscan_url += f"&apikey={gno_api_key}" - etherscan_bytecode_url += f"&apikey={gno_api_key}" - if polyzk_api_key and "zkevm" in etherscan_url: - etherscan_url += f"&apikey={polyzk_api_key}" - etherscan_bytecode_url += f"&apikey={polyzk_api_key}" - if blast_api_key and "blast" in etherscan_url: - etherscan_url += f"&apikey={blast_api_key}" - etherscan_bytecode_url += f"&apikey={blast_api_key}" + # API key handling for external tracers would be here e.g. + # elif api_key_required == 1 and avax_api_key and "snowtrace" in etherscan_url: + # etherscan_url += f"&apikey={avax_api_key}" + # etherscan_bytecode_url += f"&apikey={avax_api_key}" source_code: str = "" result: Dict[str, Union[bool, str, int]] = {} contract_name: str = "" if not only_bytecode: - if "polygon" in etherscan_url or "basescan" in etherscan_url: - # build object with headers, then send request - new_etherscan_url = urllib.request.Request( - etherscan_url, headers={"User-Agent": "Mozilla/5.0"} - ) - with urllib.request.urlopen(new_etherscan_url) as response: - html = response.read() - else: - with urllib.request.urlopen(etherscan_url) as response: - html = response.read() + # build object with headers, then send request + new_etherscan_url = urllib.request.Request( + etherscan_url, + headers={ + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36 crytic-compile/0" + }, + ) + with urllib.request.urlopen(new_etherscan_url) as response: + html = response.read() info = json.loads(html) diff --git a/scripts/ci_test_etherscan.sh b/scripts/ci_test_etherscan.sh index d4ddc5a..631b1f9 100755 --- a/scripts/ci_test_etherscan.sh +++ b/scripts/ci_test_etherscan.sh @@ -7,19 +7,14 @@ cd "$DIR" || exit 255 solc-select use 0.4.25 --always-install -delay_etherscan () { - # Perform a small sleep when API key is not available (e.g. on PR CI from external contributor) - if [ "$GITHUB_ETHERSCAN" = "" ]; then - sleep 5s - else - # Always sleep 2 second in the CI - # We have a lot of concurrent github action so this is needed - sleep 2s - fi -} +# Etherscan now _requires_ an API key, so skip the tests if it's not available +if [ "$ETHERSCAN_API_KEY" = "" ]; then + echo "API key not available, skipping etherscan tests" + exit 0 +fi echo "::group::Etherscan mainnet" -crytic-compile 0x7F37f78cBD74481E593F9C737776F7113d76B315 --compile-remove-metadata --etherscan-apikey "$GITHUB_ETHERSCAN" +crytic-compile 0x7F37f78cBD74481E593F9C737776F7113d76B315 --compile-remove-metadata --etherscan-apikey "$ETHERSCAN_API_KEY" if [ $? -ne 0 ] then @@ -28,11 +23,10 @@ then fi echo "::endgroup::" -delay_etherscan - # From crytic/slither#1154 +# Try without an explicit API key argument, to verify reading `ETHERSCAN_API_KEY` from env works fine echo "::group::Etherscan #3" -crytic-compile 0xcfc1E0968CA08aEe88CbF664D4A1f8B881d90f37 --compile-remove-metadata --etherscan-apikey "$GITHUB_ETHERSCAN" +crytic-compile 0xcfc1E0968CA08aEe88CbF664D4A1f8B881d90f37 --compile-remove-metadata if [ $? -ne 0 ] then @@ -41,11 +35,9 @@ then fi echo "::endgroup::" -delay_etherscan - # From crytic/crytic-compile#415 echo "::group::Etherscan #4" -crytic-compile 0x19c7d0fbf906c282dedb5543d098f43dfe9f856f --compile-remove-metadata --etherscan-apikey "$GITHUB_ETHERSCAN" +crytic-compile 0x19c7d0fbf906c282dedb5543d098f43dfe9f856f --compile-remove-metadata --etherscan-apikey "$ETHERSCAN_API_KEY" if [ $? -ne 0 ] then @@ -54,11 +46,9 @@ then fi echo "::endgroup::" -delay_etherscan - # From crytic/crytic-compile#150 echo "::group::Etherscan #5" -crytic-compile 0x2a311e451491091d2a1d3c43f4f5744bdb4e773a --compile-remove-metadata --etherscan-apikey "$GITHUB_ETHERSCAN" +crytic-compile 0x2a311e451491091d2a1d3c43f4f5744bdb4e773a --compile-remove-metadata --etherscan-apikey "$ETHERSCAN_API_KEY" if [ $? -ne 0 ] then @@ -74,11 +64,9 @@ then fi echo "::endgroup::" -delay_etherscan - # From crytic/crytic-compile#151 echo "::group::Etherscan #6" -crytic-compile 0x4c808e3c011514d5016536af11218eec537eb6f5 --compile-remove-metadata --etherscan-apikey "$GITHUB_ETHERSCAN" +crytic-compile 0x4c808e3c011514d5016536af11218eec537eb6f5 --compile-remove-metadata --etherscan-apikey "$ETHERSCAN_API_KEY" if [ $? -ne 0 ] then @@ -87,11 +75,9 @@ then fi echo "::endgroup::" -delay_etherscan - # via-ir test for crytic/crytic-compile#517 echo "::group::Etherscan #7" -crytic-compile 0x9AB6b21cDF116f611110b048987E58894786C244 --compile-remove-metadata --etherscan-apikey "$GITHUB_ETHERSCAN" +crytic-compile 0x9AB6b21cDF116f611110b048987E58894786C244 --compile-remove-metadata --etherscan-apikey "$ETHERSCAN_API_KEY" if [ $? -ne 0 ] then @@ -102,7 +88,7 @@ echo "::endgroup::" # From crytic/crytic-compile#544 echo "::group::Etherscan #8" -crytic-compile 0x9AB6b21cDF116f611110b048987E58894786C244 --etherscan-apikey "$GITHUB_ETHERSCAN" +crytic-compile 0x9AB6b21cDF116f611110b048987E58894786C244 --etherscan-apikey "$ETHERSCAN_API_KEY" if [ $? -ne 0 ] then diff --git a/scripts/ci_test_vyper.sh b/scripts/ci_test_vyper.sh index 9be8fa3..6afe62d 100755 --- a/scripts/ci_test_vyper.sh +++ b/scripts/ci_test_vyper.sh @@ -2,7 +2,7 @@ ### Test vyper integration -pip install vyper +pip install 'vyper>=0.3.7,<0.4' echo "Testing vyper integration of $(realpath "$(which crytic-compile)")" diff --git a/tests/test_metadata.py b/tests/test_metadata.py index 460be92..753c65c 100644 --- a/tests/test_metadata.py +++ b/tests/test_metadata.py @@ -1,6 +1,9 @@ """ Test file """ +import os +import pytest + from crytic_compile import CryticCompile from crytic_compile.source_unit import SourceUnit @@ -8,6 +11,7 @@ DAI_BYTECODE = """608060405234801561001057600080fd5b506040516120d33803806120d38339818101604052602081101561003357600080fd5b810190808051906020019092919050505060016000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550604051808061208160529139605201905060405180910390206040518060400160405280600e81526020017f44616920537461626c65636f696e000000000000000000000000000000000000815250805190602001206040518060400160405280600181526020017f3100000000000000000000000000000000000000000000000000000000000000815250805190602001208330604051602001808681526020018581526020018481526020018381526020018273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001955050505050506040516020818303038152906040528051906020012060058190555050611ee0806101a16000396000f3fe608060405234801561001057600080fd5b50600436106101425760003560e01c80637ecebe00116100b8578063a9059cbb1161007c578063a9059cbb146106b4578063b753a98c1461071a578063bb35783b14610768578063bf353dbb146107d6578063dd62ed3e1461082e578063f2d5d56b146108a657610142565b80637ecebe00146104a15780638fcbaf0c146104f957806395d89b411461059f5780639c52a7f1146106225780639dc29fac1461066657610142565b8063313ce5671161010a578063313ce567146102f25780633644e5151461031657806340c10f191461033457806354fd4d501461038257806365fae35e1461040557806370a082311461044957610142565b806306fdde0314610147578063095ea7b3146101ca57806318160ddd1461023057806323b872dd1461024e57806330adf81f146102d4575b600080fd5b61014f6108f4565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561018f578082015181840152602081019050610174565b50505050905090810190601f1680156101bc5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b610216600480360360408110156101e057600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019092919050505061092d565b604051808215151515815260200191505060405180910390f35b610238610a1f565b6040518082815260200191505060405180910390f35b6102ba6004803603606081101561026457600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610a25565b604051808215151515815260200191505060405180910390f35b6102dc610f3a565b6040518082815260200191505060405180910390f35b6102fa610f61565b604051808260ff1660ff16815260200191505060405180910390f35b61031e610f66565b6040518082815260200191505060405180910390f35b6103806004803603604081101561034a57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610f6c565b005b61038a611128565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156103ca5780820151818401526020810190506103af565b50505050905090810190601f1680156103f75780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6104476004803603602081101561041b57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050611161565b005b61048b6004803603602081101561045f57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919050505061128f565b6040518082815260200191505060405180910390f35b6104e3600480360360208110156104b757600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506112a7565b6040518082815260200191505060405180910390f35b61059d600480360361010081101561051057600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019092919080359060200190929190803515159060200190929190803560ff16906020019092919080359060200190929190803590602001909291905050506112bf565b005b6105a76117fa565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156105e75780820151818401526020810190506105cc565b50505050905090810190601f1680156106145780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6106646004803603602081101561063857600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050611833565b005b6106b26004803603604081101561067c57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050611961565b005b610700600480360360408110156106ca57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050611df4565b604051808215151515815260200191505060405180910390f35b6107666004803603604081101561073057600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050611e09565b005b6107d46004803603606081101561077e57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050611e19565b005b610818600480360360208110156107ec57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050611e2a565b6040518082815260200191505060405180910390f35b6108906004803603604081101561084457600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050611e42565b6040518082815260200191505060405180910390f35b6108f2600480360360408110156108bc57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050611e67565b005b6040518060400160405280600e81526020017f44616920537461626c65636f696e00000000000000000000000000000000000081525081565b600081600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040518082815260200191505060405180910390a36001905092915050565b60015481565b600081600260008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541015610adc576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260188152602001807f4461692f696e73756666696369656e742d62616c616e6365000000000000000081525060200191505060405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff1614158015610bb457507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600360008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205414155b15610db25781600360008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541015610cab576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601a8152602001807f4461692f696e73756666696369656e742d616c6c6f77616e636500000000000081525060200191505060405180910390fd5b610d31600360008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205483611e77565b600360008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055505b610dfb600260008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205483611e77565b600260008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550610e87600260008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205483611e91565b600260008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040518082815260200191505060405180910390a3600190509392505050565b7fea2aa0a1be11a07ed86d755c93467f4f82362b452371d1ba94d1715123511acb60001b81565b601281565b60055481565b60016000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205414611020576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260128152602001807f4461692f6e6f742d617574686f72697a6564000000000000000000000000000081525060200191505060405180910390fd5b611069600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205482611e91565b600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055506110b860015482611e91565b6001819055508173ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040518082815260200191505060405180910390a35050565b6040518060400160405280600181526020017f310000000000000000000000000000000000000000000000000000000000000081525081565b60016000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205414611215576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260128152602001807f4461692f6e6f742d617574686f72697a6564000000000000000000000000000081525060200191505060405180910390fd5b60016000808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055505961012081016040526020815260e0602082015260e0600060408301376024356004353360003560e01c60e01b61012085a45050565b60026020528060005260406000206000915090505481565b60046020528060005260406000206000915090505481565b60006005547fea2aa0a1be11a07ed86d755c93467f4f82362b452371d1ba94d1715123511acb60001b8a8a8a8a8a604051602001808781526020018673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018481526020018381526020018215151515815260200196505050505050506040516020818303038152906040528051906020012060405160200180807f190100000000000000000000000000000000000000000000000000000000000081525060020183815260200182815260200192505050604051602081830303815290604052805190602001209050600073ffffffffffffffffffffffffffffffffffffffff168973ffffffffffffffffffffffffffffffffffffffff16141561148c576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260158152602001807f4461692f696e76616c69642d616464726573732d30000000000000000000000081525060200191505060405180910390fd5b60018185858560405160008152602001604052604051808581526020018460ff1660ff1681526020018381526020018281526020019450505050506020604051602081039080840390855afa1580156114e9573d6000803e3d6000fd5b5050506020604051035173ffffffffffffffffffffffffffffffffffffffff168973ffffffffffffffffffffffffffffffffffffffff1614611593576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260128152602001807f4461692f696e76616c69642d7065726d6974000000000000000000000000000081525060200191505060405180910390fd5b60008614806115a25750854211155b611614576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260128152602001807f4461692f7065726d69742d65787069726564000000000000000000000000000081525060200191505060405180910390fd5b600460008a73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008154809291906001019190505587146116d6576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260118152602001807f4461692f696e76616c69642d6e6f6e636500000000000000000000000000000081525060200191505060405180910390fd5b6000856116e4576000611706565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5b905080600360008c73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508873ffffffffffffffffffffffffffffffffffffffff168a73ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925836040518082815260200191505060405180910390a350505050505050505050565b6040518060400160405280600381526020017f444149000000000000000000000000000000000000000000000000000000000081525081565b60016000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054146118e7576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260128152602001807f4461692f6e6f742d617574686f72697a6564000000000000000000000000000081525060200191505060405180910390fd5b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055505961012081016040526020815260e0602082015260e0600060408301376024356004353360003560e01c60e01b61012085a45050565b80600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541015611a16576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260188152602001807f4461692f696e73756666696369656e742d62616c616e6365000000000000000081525060200191505060405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614158015611aee57507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600360008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205414155b15611cec5780600360008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541015611be5576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601a8152602001807f4461692f696e73756666696369656e742d616c6c6f77616e636500000000000081525060200191505060405180910390fd5b611c6b600360008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205482611e77565b600360008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055505b611d35600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205482611e77565b600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550611d8460015482611e77565b600181905550600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040518082815260200191505060405180910390a35050565b6000611e01338484610a25565b905092915050565b611e14338383610a25565b505050565b611e24838383610a25565b50505050565b60006020528060005260406000206000915090505481565b6003602052816000526040600020602052806000526040600020600091509150505481565b611e72823383610a25565b505050565b6000828284039150811115611e8b57600080fd5b92915050565b6000828284019150811015611ea557600080fd5b9291505056fe454950373132446f6d61696e28737472696e67206e616d652c737472696e672076657273696f6e2c75696e7432353620636861696e49642c6164647265737320766572696679696e67436f6e747261637429""" +@pytest.mark.skipif(not os.getenv("ETHERSCAN_API_KEY"), reason="Etherscan requires an API key") # type: ignore def test_metadata() -> None: """ Test the metadata