diff --git a/docs/source/gnosis.eth.clients.rst b/docs/source/gnosis.eth.clients.rst index 1ed6cd788..8c8574420 100644 --- a/docs/source/gnosis.eth.clients.rst +++ b/docs/source/gnosis.eth.clients.rst @@ -28,10 +28,10 @@ gnosis.eth.clients.etherscan\_client module :undoc-members: :show-inheritance: -gnosis.eth.clients.sourcify module +gnosis.eth.clients.sourcify_client module ---------------------------------- -.. automodule:: gnosis.eth.clients.sourcify +.. automodule:: gnosis.eth.clients.sourcify_client :members: :undoc-members: :show-inheritance: diff --git a/gnosis/eth/clients/__init__.py b/gnosis/eth/clients/__init__.py index 1d5175977..dccbf61e2 100644 --- a/gnosis/eth/clients/__init__.py +++ b/gnosis/eth/clients/__init__.py @@ -11,16 +11,22 @@ EtherscanClientException, EtherscanRateLimitError, ) -from .sourcify import Sourcify +from .sourcify_client import ( + SourcifyClient, + SourcifyClientConfigurationProblem, + SourcifyClientException, +) __all__ = [ + "BlockScoutConfigurationProblem", "BlockscoutClient", "BlockscoutClientException", - "BlockScoutConfigurationProblem", "ContractMetadata", "EtherscanClient", "EtherscanClientConfigurationProblem", "EtherscanClientException", "EtherscanRateLimitError", - "Sourcify", + "SourcifyClient", + "SourcifyClientConfigurationProblem", + "SourcifyClientException", ] diff --git a/gnosis/eth/clients/sourcify.py b/gnosis/eth/clients/sourcify_client.py similarity index 76% rename from gnosis/eth/clients/sourcify.py rename to gnosis/eth/clients/sourcify_client.py index bba39a0dd..ab3e65b71 100644 --- a/gnosis/eth/clients/sourcify.py +++ b/gnosis/eth/clients/sourcify_client.py @@ -3,12 +3,22 @@ import requests +from gnosis.util import cache + from .. import EthereumNetwork from ..utils import fast_is_checksum_address from .contract_metadata import ContractMetadata -class Sourcify: +class SourcifyClientException(Exception): + pass + + +class SourcifyClientConfigurationProblem(Exception): + pass + + +class SourcifyClient: """ Get contract metadata from Sourcify. Matches can be full or partial: @@ -23,14 +33,21 @@ class Sourcify: def __init__( self, network: EthereumNetwork = EthereumNetwork.MAINNET, - base_url: str = "https://repo.sourcify.dev/", + base_url_api: str = "https://sourcify.dev", + base_url_repo: str = "https://repo.sourcify.dev/", request_timeout: int = 10, ): self.network = network - self.base_url = base_url + self.base_url_api = base_url_api + self.base_url_repo = base_url_repo self.http_session = self._prepare_http_session() self.request_timeout = request_timeout + if not self.is_chain_supported(network.value): + raise SourcifyClientConfigurationProblem( + f"Network {network.name} - {network.value} not supported" + ) + def _prepare_http_session(self) -> requests.Session: """ Prepare http session with custom pooling. See: @@ -63,6 +80,14 @@ def _do_request(self, url: str) -> Optional[Dict[str, Any]]: return response.json() + def is_chain_supported(self, chain_id: int) -> bool: + return chain_id in (int(chain["chainId"]) for chain in self.get_chains()) + + @cache + def get_chains(self) -> Dict[str, Any]: + url = urljoin(self.base_url_api, "/server/chains") + return self._do_request(url) + def get_contract_metadata( self, contract_address: str ) -> Optional[ContractMetadata]: @@ -72,7 +97,7 @@ def get_contract_metadata( for match_type in ("full_match", "partial_match"): url = urljoin( - self.base_url, + self.base_url_repo, f"/contracts/{match_type}/{self.network.value}/{contract_address}/metadata.json", ) metadata = self._do_request(url) diff --git a/gnosis/eth/tests/clients/test_sourcify.py b/gnosis/eth/tests/clients/test_sourcify_client.py similarity index 57% rename from gnosis/eth/tests/clients/test_sourcify.py rename to gnosis/eth/tests/clients/test_sourcify_client.py index 102f3c5d3..df9f3eba0 100644 --- a/gnosis/eth/tests/clients/test_sourcify.py +++ b/gnosis/eth/tests/clients/test_sourcify_client.py @@ -3,12 +3,27 @@ from django.test import TestCase from ... import EthereumNetwork -from ...clients import Sourcify +from ...clients import SourcifyClient +from ...clients.sourcify_client import SourcifyClientConfigurationProblem -class TestSourcify(TestCase): - def test_sourcify_get_contract_metadata(self): - sourcify = Sourcify() +class TestSourcifyClient(TestCase): + def test_init(self): + with self.assertRaises(SourcifyClientConfigurationProblem): + SourcifyClient(EthereumNetwork.OLYMPIC) + + self.assertIsInstance(SourcifyClient(), SourcifyClient) + self.assertIsInstance(SourcifyClient(EthereumNetwork.GNOSIS), SourcifyClient) + + def test_is_chain_supported(self): + sourcify = SourcifyClient() + + self.assertTrue(sourcify.is_chain_supported(EthereumNetwork.MAINNET.value)) + self.assertTrue(sourcify.is_chain_supported(EthereumNetwork.GNOSIS.value)) + self.assertFalse(sourcify.is_chain_supported(2)) + + def test_get_contract_metadata(self): + sourcify = SourcifyClient() safe_contract_address = "0x6851D6fDFAfD08c0295C392436245E5bc78B0185" try: contract_metadata = sourcify.get_contract_metadata(safe_contract_address) @@ -18,7 +33,7 @@ def test_sourcify_get_contract_metadata(self): self.assertIsInstance(contract_metadata.abi, List) self.assertTrue(contract_metadata.abi) self.assertFalse(contract_metadata.partial_match) - contract_metadata_rinkeby = Sourcify( + contract_metadata_rinkeby = SourcifyClient( EthereumNetwork.RINKEBY ).get_contract_metadata(safe_contract_address) self.assertEqual(contract_metadata, contract_metadata_rinkeby)