diff --git a/src/erc7730/common/client.py b/src/erc7730/common/client.py index 86aca94..7012535 100644 --- a/src/erc7730/common/client.py +++ b/src/erc7730/common/client.py @@ -15,27 +15,54 @@ class ScanSite: host: str api_key: str + url: str SCAN_SITES = { - 1: ScanSite(host="api.etherscan.io", api_key="ETHERSCAN_API_KEY"), - 56: ScanSite(host="api.bscscan.com", api_key="BSCSCAN_API_KEY"), - 137: ScanSite(host="api.polygonscan.com", api_key="POLYGONSCAN_API_KEY"), - 1101: ScanSite(host="api-zkevm.polygonscan.com", api_key="POLYGONSKEVMSCAN_API_KEY"), - 42161: ScanSite(host="api.arbiscan.io", api_key="ARBISCAN_API_KEY"), - 8453: ScanSite(host="api.basescan.io", api_key="BASESCAN_API_KEY"), - 10: ScanSite(host="api-optimistic.etherscan.io", api_key="OPTIMISMSCAN_API_KEY"), - 25: ScanSite(host="api.cronoscan.com", api_key="CRONOSCAN_API_KEY"), - 250: ScanSite(host="api.ftmscan.com", api_key="FANTOMSCAN_API_KEY"), - 284: ScanSite(host="api-moonbeam.moonscan.io", api_key="MOONSCAN_API_KEY"), - 199: ScanSite(host="api.bttcscan.com", api_key="BTTCSCAN_API_KEY"), - 59144: ScanSite(host="api.lineascan.build", api_key="LINEASCAN_API_KEY"), - 534352: ScanSite(host="api.scrollscan.com", api_key="SCROLLSCAN_API_KEY"), - 421614: ScanSite(host="api-sepolia.arbiscan.io", api_key="ARBISCAN_SEPOLIA_API_KEY"), - 84532: ScanSite(host="api-sepolia.basescan.org", api_key="BASESCAN_SEPOLIA_API_KEY"), - 11155111: ScanSite(host="api-sepolia.etherscan.io", api_key="ETHERSCAN_SEPOLIA_API_KEY"), - 11155420: ScanSite(host="api-sepolia-optimistic.etherscan.io", api_key="OPTIMISMSCAN_SEPOLIA_API_KEY"), - 534351: ScanSite(host="api-sepolia.scrollscan.com", api_key="SCROLLSCAN_SEPOLIA_API_KEY"), + 1: ScanSite(host="api.etherscan.io", api_key="ETHERSCAN_API_KEY", url="https://etherscan.io"), + 56: ScanSite(host="api.bscscan.com", api_key="BSCSCAN_API_KEY", url="https://bscscan.com"), + 137: ScanSite(host="api.polygonscan.com", api_key="POLYGONSCAN_API_KEY", url="https://polygonscan.com"), + 1101: ScanSite( + host="api-zkevm.polygonscan.com", + api_key="POLYGONSKEVMSCAN_API_KEY", + url="https://zkevm.polygonscan.com", + ), + 42161: ScanSite(host="api.arbiscan.io", api_key="ARBISCAN_API_KEY", url="https://arbiscan.io"), + 8453: ScanSite(host="api.basescan.io", api_key="BASESCAN_API_KEY", url="https://basescan.io"), + 10: ScanSite( + host="api-optimistic.etherscan.io", + api_key="OPTIMISMSCAN_API_KEY", + url="https://optimistic.etherscan.io", + ), + 25: ScanSite(host="api.cronoscan.com", api_key="CRONOSCAN_API_KEY", url="https://cronoscan.com"), + 250: ScanSite(host="api.ftmscan.com", api_key="FANTOMSCAN_API_KEY", url="https://ftmscan.com"), + 284: ScanSite(host="api-moonbeam.moonscan.io", api_key="MOONSCAN_API_KEY", url="https://moonbeam.moonscan.io"), + 199: ScanSite(host="api.bttcscan.com", api_key="BTTCSCAN_API_KEY", url="https://bttcscan.com"), + 59144: ScanSite(host="api.lineascan.build", api_key="LINEASCAN_API_KEY", url="https://lineascan.build"), + 534352: ScanSite(host="api.scrollscan.com", api_key="SCROLLSCAN_API_KEY", url="https://scrollscan.com"), + 421614: ScanSite( + host="api-sepolia.arbiscan.io", api_key="ARBISCAN_SEPOLIA_API_KEY", url="https://sepolia.arbiscan.io" + ), + 84532: ScanSite( + host="api-sepolia.basescan.org", + api_key="BASESCAN_SEPOLIA_API_KEY", + url="https://sepolia.basescan.org", + ), + 11155111: ScanSite( + host="api-sepolia.etherscan.io", + api_key="ETHERSCAN_SEPOLIA_API_KEY", + url="https://sepolia.etherscan.io", + ), + 11155420: ScanSite( + host="api-sepolia-optimistic.etherscan.io", + api_key="OPTIMISMSCAN_SEPOLIA_API_KEY", + url="https://sepolia.optimistic.etherscan.io", + ), + 534351: ScanSite( + host="api-sepolia.scrollscan.com", + api_key="SCROLLSCAN_SEPOLIA_API_KEY", + url="https://sepolia.scrollscan.com", + ), } @@ -58,6 +85,14 @@ def get_contract_abis(chain_id: int, contract_address: str) -> list[ABI] | None: ).root +def get_contract_url(chain_id: int, contract_address: str) -> str: + if (site := SCAN_SITES.get(chain_id)) is None: + raise UnsupportedOperation( + f"Chain ID {chain_id} is not supported, please report this to authors of " f"python-erc7730 library" + ) + return f"{site.url}/address/{contract_address}#code" + + def get(url: FileUrl | HttpUrl, model: type[_BaseModel]) -> _BaseModel: """ Fetch data from a file or an HTTP URL and deserialize it. diff --git a/src/erc7730/lint/lint_validate_abi.py b/src/erc7730/lint/lint_validate_abi.py index 90f78e7..5d06840 100644 --- a/src/erc7730/lint/lint_validate_abi.py +++ b/src/erc7730/lint/lint_validate_abi.py @@ -50,21 +50,22 @@ def _validate_contract_abis(cls, context: ResolvedContractContext, out: OutputAd reference_abis = get_functions(abis) descriptor_abis = get_functions(context.contract.abi) + url = client.get_contract_url(deployment.chainId, deployment.address) if reference_abis.proxy: return out.info( title="Proxy contract", - message="Contract ABI on Etherscan is likely to be a proxy, validation skipped", + message=f"Contract ABI {url} is likely to be a proxy, validation skipped", ) for selector, abi in descriptor_abis.functions.items(): if selector not in reference_abis.functions: out.error( title="Missing function", - message=f"Function `{selector}/{compute_signature(abi)}` is not defined in Etherscan ABI", + message=f"Function `{selector}/{compute_signature(abi)}` is not defined on {url}", ) elif descriptor_abis.functions[selector] != reference_abis.functions[selector]: out.warning( title="Function mismatch", - message=f"Function `{selector}/{compute_signature(abi)}` does not match Etherscan ABI", + message=f"Function `{selector}/{compute_signature(abi)}` does not match {url}", )