From 3aa70c41cba75cbe7ee5a8219b5e7582028a51b3 Mon Sep 17 00:00:00 2001 From: Uxio Fuentefria Date: Tue, 20 Sep 2022 16:47:41 +0200 Subject: [PATCH] Add method to check if Oracle is available for network - This way it can be queried and no knowledge about the Oracle is required from the user --- gnosis/eth/oracles/__init__.py | 2 - gnosis/eth/oracles/kyber.py | 11 ++ gnosis/eth/oracles/oracles.py | 118 +++++++++++++++++++-- gnosis/eth/tests/oracles/test_kyber.py | 2 + gnosis/eth/tests/oracles/test_sushiswap.py | 2 + gnosis/eth/tests/test_oracles.py | 20 ++++ 6 files changed, 142 insertions(+), 13 deletions(-) diff --git a/gnosis/eth/oracles/__init__.py b/gnosis/eth/oracles/__init__.py index ee2b94310..8b48b0911 100644 --- a/gnosis/eth/oracles/__init__.py +++ b/gnosis/eth/oracles/__init__.py @@ -20,7 +20,6 @@ UnderlyingToken, UniswapOracle, UniswapV2Oracle, - UsdPricePoolOracle, YearnOracle, ZerionComposedOracle, ) @@ -45,7 +44,6 @@ "UniswapOracle", "UniswapV2Oracle", "UniswapV3Oracle", - "UsdPricePoolOracle", "YearnOracle", "ZerionComposedOracle", ] diff --git a/gnosis/eth/oracles/kyber.py b/gnosis/eth/oracles/kyber.py index db265fcef..d8491d046 100644 --- a/gnosis/eth/oracles/kyber.py +++ b/gnosis/eth/oracles/kyber.py @@ -44,6 +44,17 @@ def __init__( self.w3 = ethereum_client.w3 self._kyber_network_proxy_address = kyber_network_proxy_address + @classmethod + def is_available( + cls, + ethereum_client: EthereumClient, + ) -> bool: + """ + :param ethereum_client: + :return: `True` if Oracle is available for the EthereumClient provided, `False` otherwise + """ + return ethereum_client.get_network() in cls.ADDRESSES + @cached_property def kyber_network_proxy_address(self): if self._kyber_network_proxy_address: diff --git a/gnosis/eth/oracles/oracles.py b/gnosis/eth/oracles/oracles.py index 00370d135..501b7c140 100644 --- a/gnosis/eth/oracles/oracles.py +++ b/gnosis/eth/oracles/oracles.py @@ -42,31 +42,45 @@ class UnderlyingToken: quantity: int -class PriceOracle(ABC): +class BaseOracle(ABC): + @classmethod @abstractmethod - def get_price(self, *args) -> float: - pass + def is_available( + cls, + ethereum_client: EthereumClient, + ) -> bool: + """ + :param ethereum_client: + :return: `True` if Oracle is available for the EthereumClient provided, `False` otherwise + """ + raise NotImplementedError -class PricePoolOracle(ABC): +class PriceOracle(BaseOracle): @abstractmethod - def get_pool_token_price(self, pool_token_address: ChecksumAddress) -> float: - pass + def get_price(self, *args) -> float: + raise NotImplementedError -class UsdPricePoolOracle(ABC): +class PricePoolOracle(BaseOracle): @abstractmethod - def get_pool_usd_token_price(self, pool_token_address: ChecksumAddress) -> float: - pass + def get_pool_token_price(self, pool_token_address: ChecksumAddress) -> float: + raise NotImplementedError -class ComposedPriceOracle(ABC): +class ComposedPriceOracle(BaseOracle): @abstractmethod def get_underlying_tokens(self, *args) -> List[Tuple[UnderlyingToken]]: - pass + raise NotImplementedError class UniswapOracle(PriceOracle): + """ + Uniswap V1 Oracle + + https://docs.uniswap.org/protocol/V1/guides/connect-to-uniswap + """ + ADDRESSES = { EthereumNetwork.MAINNET: "0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95", EthereumNetwork.RINKEBY: "0xf5D915570BC477f9B8D6C0E980aA81757A3AaC36", @@ -88,6 +102,17 @@ def __init__( self.w3 = ethereum_client.w3 self._uniswap_factory_address = uniswap_factory_address + @classmethod + def is_available( + cls, + ethereum_client: EthereumClient, + ) -> bool: + """ + :param ethereum_client: + :return: `True` if Oracle is available for the EthereumClient provided, `False` otherwise + """ + return ethereum_client.get_network() in cls.ADDRESSES + @cached_property def uniswap_factory_address(self): if self._uniswap_factory_address: @@ -231,6 +256,22 @@ def __init__( ethereum_client.w3, self.router_address ) + @classmethod + def is_available( + cls, + ethereum_client: EthereumClient, + ) -> bool: + """ + :param ethereum_client: + :return: `True` if Oracle is available for the EthereumClient provided, `False` otherwise + """ + return ethereum_client.is_contract( + cls.ROUTER_ADDRESSES.get( + ethereum_client.get_network(), + cls.ROUTER_ADDRESSES[EthereumNetwork.MAINNET], + ) + ) + @cached_property def factory(self): return get_uniswap_v2_factory_contract( @@ -436,6 +477,17 @@ def __init__(self, ethereum_client: EthereumClient, price_oracle: PriceOracle): self.w3 = ethereum_client.w3 self.price_oracle = price_oracle + @classmethod + def is_available( + cls, + ethereum_client: EthereumClient, + ) -> bool: + """ + :param ethereum_client: + :return: `True` if Oracle is available for the EthereumClient provided, `False` otherwise + """ + return ethereum_client.get_network() == EthereumNetwork.MAINNET + def get_price(self, token_address: str) -> float: if ( token_address == "0x4da27a545c0c5B758a6BA100e3a049001de870f5" @@ -467,6 +519,17 @@ def __init__(self, ethereum_client: EthereumClient, price_oracle: PriceOracle): self.w3 = ethereum_client.w3 self.price_oracle = price_oracle + @classmethod + def is_available( + cls, + ethereum_client: EthereumClient, + ) -> bool: + """ + :param ethereum_client: + :return: `True` if Oracle is available for the EthereumClient provided, `False` otherwise + """ + return ethereum_client.get_network() == EthereumNetwork.MAINNET + def get_price(self, token_address: str) -> float: try: underlying_token = ( @@ -504,6 +567,17 @@ def __init__( self.ethereum_client = ethereum_client self.w3 = ethereum_client.w3 + @classmethod + def is_available( + cls, + ethereum_client: EthereumClient, + ) -> bool: + """ + :param ethereum_client: + :return: `True` if Oracle is available for the EthereumClient provided, `False` otherwise + """ + return ethereum_client.get_network() == EthereumNetwork.MAINNET + @cached_property def zerion_adapter_contract(self) -> Optional[Contract]: """ @@ -626,6 +700,17 @@ def __init__( ethereum_client, iearn_token_adapter ) + @classmethod + def is_available( + cls, + ethereum_client: EthereumClient, + ) -> bool: + """ + :param ethereum_client: + :return: `True` if Oracle is available for the EthereumClient provided, `False` otherwise + """ + return ethereum_client.get_network() == EthereumNetwork.MAINNET + def get_underlying_tokens( self, token_address: ChecksumAddress ) -> List[Tuple[float, ChecksumAddress]]: @@ -661,6 +746,17 @@ def __init__(self, ethereum_client: EthereumClient, price_oracle: PriceOracle): self.w3 = ethereum_client.w3 self.price_oracle = price_oracle + @classmethod + def is_available( + cls, + ethereum_client: EthereumClient, + ) -> bool: + """ + :param ethereum_client: + :return: `True` if Oracle is available for the EthereumClient provided, `False` otherwise + """ + return ethereum_client.get_network() == EthereumNetwork.MAINNET + def get_pool_token_price(self, pool_token_address: ChecksumAddress) -> float: """ Estimate balancer pool token price based on its components diff --git a/gnosis/eth/tests/oracles/test_kyber.py b/gnosis/eth/tests/oracles/test_kyber.py index 7adbc7b3f..b418a4f21 100644 --- a/gnosis/eth/tests/oracles/test_kyber.py +++ b/gnosis/eth/tests/oracles/test_kyber.py @@ -18,6 +18,8 @@ class TestKyberOracle(EthereumTestCaseMixin, TestCase): def test_kyber_oracle(self): mainnet_node = just_test_if_mainnet_node() ethereum_client = EthereumClient(mainnet_node) + + self.assertTrue(KyberOracle.is_available(ethereum_client)) kyber_oracle = KyberOracle(ethereum_client) price = kyber_oracle.get_price( gno_token_mainnet_address, weth_token_mainnet_address diff --git a/gnosis/eth/tests/oracles/test_sushiswap.py b/gnosis/eth/tests/oracles/test_sushiswap.py index 66d98f9b6..01f2ba293 100644 --- a/gnosis/eth/tests/oracles/test_sushiswap.py +++ b/gnosis/eth/tests/oracles/test_sushiswap.py @@ -18,6 +18,8 @@ def test_get_price(self): oracles_get_decimals.cache_clear() mainnet_node = just_test_if_mainnet_node() ethereum_client = EthereumClient(mainnet_node) + + self.assertTrue(SushiswapOracle.is_available(ethereum_client)) sushiswap_oracle = SushiswapOracle(ethereum_client) price = sushiswap_oracle.get_price( diff --git a/gnosis/eth/tests/test_oracles.py b/gnosis/eth/tests/test_oracles.py index 8a035279a..6127b0ae3 100644 --- a/gnosis/eth/tests/test_oracles.py +++ b/gnosis/eth/tests/test_oracles.py @@ -40,6 +40,8 @@ class TestOracles(EthereumTestCaseMixin, TestCase): def test_uniswap_oracle(self): mainnet_node = just_test_if_mainnet_node() ethereum_client = EthereumClient(mainnet_node) + + self.assertTrue(UniswapOracle.is_available(ethereum_client)) uniswap_oracle = UniswapOracle(ethereum_client) token_address = dai_token_mainnet_address price = uniswap_oracle.get_price(token_address) @@ -100,6 +102,8 @@ def test_get_price(self): oracles_get_decimals.cache_clear() mainnet_node = just_test_if_mainnet_node() ethereum_client = EthereumClient(mainnet_node) + + self.assertTrue(UniswapV2Oracle.is_available(ethereum_client)) uniswap_v2_oracle = UniswapV2Oracle(ethereum_client) self.assertEqual(oracles_get_decimals.cache_info().currsize, 0) @@ -195,6 +199,8 @@ class TestAaveOracle(EthereumTestCaseMixin, TestCase): def test_get_token_price(self): mainnet_node = just_test_if_mainnet_node() ethereum_client = EthereumClient(mainnet_node) + + self.assertTrue(AaveOracle.is_available(ethereum_client)) uniswap_oracle = UniswapV2Oracle(ethereum_client) aave_oracle = AaveOracle(ethereum_client, uniswap_oracle) @@ -218,6 +224,8 @@ class TestCreamOracle(EthereumTestCaseMixin, TestCase): def test_get_price(self): mainnet_node = just_test_if_mainnet_node() ethereum_client = EthereumClient(mainnet_node) + + self.assertTrue(CreamOracle.is_available(ethereum_client)) sushi_oracle = SushiswapOracle(ethereum_client) cream_oracle = CreamOracle(ethereum_client, sushi_oracle) @@ -249,6 +257,8 @@ def test_get_underlying_tokens(self): mainnet_node = just_test_if_mainnet_node() ethereum_client = EthereumClient(mainnet_node) + + self.assertTrue(CurveOracle.is_available(ethereum_client)) curve_oracle = CurveOracle(ethereum_client) # Curve.fi ETH/stETH (steCRV) is working with the updated adapter @@ -321,6 +331,8 @@ def test_get_underlying_token(self): mainnet_node = just_test_if_mainnet_node() ethereum_client = EthereumClient(mainnet_node) + + self.assertTrue(PoolTogetherOracle.is_available(ethereum_client)) pool_together_oracle = PoolTogetherOracle(ethereum_client) underlying_tokens = pool_together_oracle.get_underlying_tokens( @@ -345,6 +357,8 @@ class TestYearnOracle(EthereumTestCaseMixin, TestCase): def test_get_underlying_tokens(self): mainnet_node = just_test_if_mainnet_node() ethereum_client = EthereumClient(mainnet_node) + + self.assertTrue(YearnOracle.is_available(ethereum_client)) yearn_oracle = YearnOracle(ethereum_client) yearn_token_address = "0x5533ed0a3b83F70c3c4a1f69Ef5546D3D4713E44" # Yearn Curve.fi DAI/USDC/USDT/sUSD yearn_underlying_token_address = ( @@ -400,6 +414,8 @@ class TestBalancerOracle(EthereumTestCaseMixin, TestCase): def test_get_pool_token_price(self): mainnet_node = just_test_if_mainnet_node() ethereum_client = EthereumClient(mainnet_node) + + self.assertTrue(BalancerOracle.is_available(ethereum_client)) uniswap_oracle = UniswapV2Oracle(ethereum_client) balancer_oracle = BalancerOracle(ethereum_client, uniswap_oracle) balancer_token_address = ( @@ -424,6 +440,8 @@ class TestMooniswapOracle(EthereumTestCaseMixin, TestCase): def test_get_pool_token_price(self): mainnet_node = just_test_if_mainnet_node() ethereum_client = EthereumClient(mainnet_node) + + self.assertTrue(MooniswapOracle.is_available(ethereum_client)) uniswap_oracle = UniswapV2Oracle(ethereum_client) mooniswap_oracle = MooniswapOracle(ethereum_client, uniswap_oracle) mooniswap_pool_address = "0x6a11F3E5a01D129e566d783A7b6E8862bFD66CcA" # 1inch Liquidity Pool (ETH-WBTC) @@ -455,6 +473,8 @@ class TestEnzymeOracle(EthereumTestCaseMixin, TestCase): def test_get_underlying_tokens(self): mainnet_node = just_test_if_mainnet_node() ethereum_client = EthereumClient(mainnet_node) + + self.assertTrue(EnzymeOracle.is_available(ethereum_client)) enzyme_oracle = EnzymeOracle(ethereum_client) mln_vault_token_address = "0x45c45799Bcf6C7Eb2Df0DA1240BE04cE1D18CC69" mln_vault_underlying_token = "0xec67005c4E498Ec7f55E092bd1d35cbC47C91892"