From 3dcd64155c9638fb1807f7a711669f4e29be4819 Mon Sep 17 00:00:00 2001 From: akrem Date: Thu, 27 Jul 2023 15:04:57 -0400 Subject: [PATCH 1/5] Add sweth source --- src/telliot_feeds/feeds/sweth_usd_feed.py | 3 + src/telliot_feeds/sources/sweth_source.py | 78 +++++++++++++++++++++++ tests/feeds/test_sweth_usd_feed.py | 2 +- tests/sources/test_spot_price_sources.py | 11 ++++ 4 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 src/telliot_feeds/sources/sweth_source.py diff --git a/src/telliot_feeds/feeds/sweth_usd_feed.py b/src/telliot_feeds/feeds/sweth_usd_feed.py index 26b5c52b..ee19c85e 100644 --- a/src/telliot_feeds/feeds/sweth_usd_feed.py +++ b/src/telliot_feeds/feeds/sweth_usd_feed.py @@ -3,6 +3,8 @@ from telliot_feeds.sources.price.spot.coingecko import CoinGeckoSpotPriceSource from telliot_feeds.sources.price.spot.uniswapV3 import UniswapV3PriceSource from telliot_feeds.sources.price_aggregator import PriceAggregator +from telliot_feeds.sources.sweth_source import swETHSpotPriceSource + sweth_usd_median_feed = DataFeed( query=SpotPrice(asset="SWETH", currency="USD"), @@ -11,6 +13,7 @@ currency="usd", algorithm="median", sources=[ + swETHSpotPriceSource(asset="sweth", currency="usd"), CoinGeckoSpotPriceSource(asset="sweth", currency="usd"), UniswapV3PriceSource(asset="sweth", currency="usd"), ], diff --git a/src/telliot_feeds/sources/sweth_source.py b/src/telliot_feeds/sources/sweth_source.py new file mode 100644 index 00000000..989e0a29 --- /dev/null +++ b/src/telliot_feeds/sources/sweth_source.py @@ -0,0 +1,78 @@ +from dataclasses import dataclass +from dataclasses import field +from typing import Any +from typing import Optional + +from telliot_core.apps.telliot_config import TelliotConfig + +from telliot_feeds.dtypes.datapoint import OptionalDataPoint +from telliot_feeds.pricing.price_service import WebPriceService +from telliot_feeds.pricing.price_source import PriceSource + +from telliot_feeds.dtypes.datapoint import datetime_now_utc +from telliot_feeds.utils.log import get_logger + +logger = get_logger(__name__) + + +class swETHSpotPriceService(WebPriceService): + """Custom swETH Price Service""" + + def __init__(self, **kwargs: Any) -> None: + kwargs["name"] = "Custom swETH Price Service" + kwargs["url"] = "" + super().__init__(**kwargs) + self.cfg = TelliotConfig() + + def get_sweth_eth_ratio(self) -> Optional[float]: + # get endpoint + endpoint = self.cfg.endpoints.find(chain_id=1) + if not endpoint: + logger.error("Endpoint not found for mainnet to get sweth_eth_ratio") + return None + ep = endpoint[0] + if not ep.connect(): + logger.error("Unable to connect endpoint for mainnet to get sweth_eth_ratio") + return None + w3 = ep.web3 + # get ratio + sweth_eth_ratio_bytes = w3.eth.call( + { + "to": "0xf951E335afb289353dc249e82926178EaC7DEd78", + "data": "0xd68b2cb6", + } + ) + sweth_eth_ratio_decoded = w3.toInt(sweth_eth_ratio_bytes) + sweth_eth_ratio = w3.fromWei(sweth_eth_ratio_decoded, "ether") + logger.debug(f"sweth_eth_ratio: {sweth_eth_ratio}") + return float(sweth_eth_ratio) + + async def get_price(self, asset: str, currency: str) -> OptionalDataPoint[float]: + """This implementation gets the median price of eth in usd and + converts the sweth/eth ratio to get sweth/usd price + """ + asset = asset.lower() + currency = currency.lower() + + sweth_eth_ratio = self.get_sweth_eth_ratio() + if asset == "sweth" and currency == "eth": + return sweth_eth_ratio, datetime_now_utc() + + if sweth_eth_ratio is None: + logger.error("Unable to get sweth_eth_ratio") + return None, None + from telliot_feeds.feeds.eth_usd_feed import eth_usd_median_feed + source = eth_usd_median_feed.source + + eth_price, timestamp = await source.fetch_new_datapoint() + if eth_price is None: + logger.error("Unable to get eth/usd price") + return None, None + return sweth_eth_ratio * eth_price, timestamp + + +@dataclass +class swETHSpotPriceSource(PriceSource): + asset: str = "" + currency: str = "" + service: swETHSpotPriceService = field(default_factory=swETHSpotPriceService, init=False) diff --git a/tests/feeds/test_sweth_usd_feed.py b/tests/feeds/test_sweth_usd_feed.py index 5fe18876..6485d0f6 100644 --- a/tests/feeds/test_sweth_usd_feed.py +++ b/tests/feeds/test_sweth_usd_feed.py @@ -12,7 +12,7 @@ async def test_sweth_usd_median_feed(caplog): assert v is not None assert v > 0 - assert "sources used in aggregate: 2" in caplog.text.lower() + assert "sources used in aggregate: 3" in caplog.text.lower() print(f"SWETH/USD Price: {v}") # Get list of data sources from sources dict diff --git a/tests/sources/test_spot_price_sources.py b/tests/sources/test_spot_price_sources.py index 7367e919..ddf0fffd 100644 --- a/tests/sources/test_spot_price_sources.py +++ b/tests/sources/test_spot_price_sources.py @@ -24,6 +24,7 @@ PancakeswapPriceService, ) from telliot_feeds.sources.price.spot.uniswapV3 import UniswapV3PriceService +from telliot_feeds.sources.sweth_source import swETHSpotPriceService service = { @@ -39,6 +40,7 @@ "bitfinex": BitfinexSpotPriceService(), "coinpaprika": CoinpaprikaSpotPriceService(), "curvefi": CurveFinanceSpotPriceService(), + "sweth": swETHSpotPriceService(), } @@ -305,3 +307,12 @@ async def test_curvefi(): validate_price(v, t) assert v is not None assert t is not None + + +@pytest.mark.asyncio +async def test_sweth_source(): + """Test swETH price service""" + v, t = await get_price("sweth", "usd", service["sweth"]) + validate_price(v, t) + assert v is not None + assert t is not None \ No newline at end of file From 3857a23cc53ebae9e79bc3c1e4a3334fede75645 Mon Sep 17 00:00:00 2001 From: akrem Date: Thu, 27 Jul 2023 15:06:32 -0400 Subject: [PATCH 2/5] tox --- src/telliot_feeds/sources/sweth_source.py | 4 ++-- tests/sources/test_spot_price_sources.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/telliot_feeds/sources/sweth_source.py b/src/telliot_feeds/sources/sweth_source.py index 989e0a29..e38a8985 100644 --- a/src/telliot_feeds/sources/sweth_source.py +++ b/src/telliot_feeds/sources/sweth_source.py @@ -5,11 +5,10 @@ from telliot_core.apps.telliot_config import TelliotConfig +from telliot_feeds.dtypes.datapoint import datetime_now_utc from telliot_feeds.dtypes.datapoint import OptionalDataPoint from telliot_feeds.pricing.price_service import WebPriceService from telliot_feeds.pricing.price_source import PriceSource - -from telliot_feeds.dtypes.datapoint import datetime_now_utc from telliot_feeds.utils.log import get_logger logger = get_logger(__name__) @@ -62,6 +61,7 @@ async def get_price(self, asset: str, currency: str) -> OptionalDataPoint[float] logger.error("Unable to get sweth_eth_ratio") return None, None from telliot_feeds.feeds.eth_usd_feed import eth_usd_median_feed + source = eth_usd_median_feed.source eth_price, timestamp = await source.fetch_new_datapoint() diff --git a/tests/sources/test_spot_price_sources.py b/tests/sources/test_spot_price_sources.py index ddf0fffd..35d1eee0 100644 --- a/tests/sources/test_spot_price_sources.py +++ b/tests/sources/test_spot_price_sources.py @@ -315,4 +315,4 @@ async def test_sweth_source(): v, t = await get_price("sweth", "usd", service["sweth"]) validate_price(v, t) assert v is not None - assert t is not None \ No newline at end of file + assert t is not None From 8fb060f9c7f43f0dfb87dc196b1f7bfc31af916f Mon Sep 17 00:00:00 2001 From: akrem Date: Thu, 27 Jul 2023 16:42:10 -0400 Subject: [PATCH 3/5] Add maverick source --- src/telliot_feeds/feeds/sweth_usd_feed.py | 15 ++++++++++++ src/telliot_feeds/sources/sweth_source.py | 30 +++++++++++++++++------ tests/feeds/test_sweth_usd_feed.py | 2 +- 3 files changed, 39 insertions(+), 8 deletions(-) diff --git a/src/telliot_feeds/feeds/sweth_usd_feed.py b/src/telliot_feeds/feeds/sweth_usd_feed.py index ee19c85e..ed01f983 100644 --- a/src/telliot_feeds/feeds/sweth_usd_feed.py +++ b/src/telliot_feeds/feeds/sweth_usd_feed.py @@ -3,6 +3,7 @@ from telliot_feeds.sources.price.spot.coingecko import CoinGeckoSpotPriceSource from telliot_feeds.sources.price.spot.uniswapV3 import UniswapV3PriceSource from telliot_feeds.sources.price_aggregator import PriceAggregator +from telliot_feeds.sources.sweth_source import swETHMaverickSpotPriceSource from telliot_feeds.sources.sweth_source import swETHSpotPriceSource @@ -14,8 +15,22 @@ algorithm="median", sources=[ swETHSpotPriceSource(asset="sweth", currency="usd"), + swETHMaverickSpotPriceSource(asset="sweth", currency="usd"), CoinGeckoSpotPriceSource(asset="sweth", currency="usd"), UniswapV3PriceSource(asset="sweth", currency="usd"), ], ), ) +if __name__ == "__main__": + import asyncio + + async def main() -> None: + source = swETHMaverickSpotPriceSource(asset="sweth", currency="usd") + v, _ = await source.fetch_new_datapoint() + print(v) + + source = swETHSpotPriceSource(asset="sweth", currency="usd") + v, _ = await source.fetch_new_datapoint() + print(v) + + asyncio.run(main()) diff --git a/src/telliot_feeds/sources/sweth_source.py b/src/telliot_feeds/sources/sweth_source.py index e38a8985..9a14c695 100644 --- a/src/telliot_feeds/sources/sweth_source.py +++ b/src/telliot_feeds/sources/sweth_source.py @@ -22,6 +22,8 @@ def __init__(self, **kwargs: Any) -> None: kwargs["url"] = "" super().__init__(**kwargs) self.cfg = TelliotConfig() + self.contract: Optional[str] = None + self.calldata: Optional[str] = None def get_sweth_eth_ratio(self) -> Optional[float]: # get endpoint @@ -33,14 +35,13 @@ def get_sweth_eth_ratio(self) -> Optional[float]: if not ep.connect(): logger.error("Unable to connect endpoint for mainnet to get sweth_eth_ratio") return None - w3 = ep.web3 + w3 = ep._web3 + if w3 is None: + logger.error("Unable to get web3 for mainnet to get sweth_eth_ratio") + return None # get ratio - sweth_eth_ratio_bytes = w3.eth.call( - { - "to": "0xf951E335afb289353dc249e82926178EaC7DEd78", - "data": "0xd68b2cb6", - } - ) + sweth_eth_ratio_bytes = w3.eth.call({"to": self.contract, "data": self.calldata}) + sweth_eth_ratio_decoded = w3.toInt(sweth_eth_ratio_bytes) sweth_eth_ratio = w3.fromWei(sweth_eth_ratio_decoded, "ether") logger.debug(f"sweth_eth_ratio: {sweth_eth_ratio}") @@ -76,3 +77,18 @@ class swETHSpotPriceSource(PriceSource): asset: str = "" currency: str = "" service: swETHSpotPriceService = field(default_factory=swETHSpotPriceService, init=False) + + def __post_init__(self) -> None: + self.service.contract = "0xf951E335afb289353dc249e82926178EaC7DEd78" + self.service.calldata = "0xd68b2cb6" + + +@dataclass +class swETHMaverickSpotPriceSource(PriceSource): + asset: str = "" + currency: str = "" + service: swETHSpotPriceService = field(default_factory=swETHSpotPriceService) + + def __post_init__(self) -> None: + self.service.contract = "0x9980ce3b5570e41324904f46A06cE7B466925E23" + self.service.calldata = "0x91c0914e000000000000000000000000817e8c9a99db98082ca187e4f80498586bf6bc1b" diff --git a/tests/feeds/test_sweth_usd_feed.py b/tests/feeds/test_sweth_usd_feed.py index 6485d0f6..32785c15 100644 --- a/tests/feeds/test_sweth_usd_feed.py +++ b/tests/feeds/test_sweth_usd_feed.py @@ -12,7 +12,7 @@ async def test_sweth_usd_median_feed(caplog): assert v is not None assert v > 0 - assert "sources used in aggregate: 3" in caplog.text.lower() + assert "sources used in aggregate: 4" in caplog.text.lower() print(f"SWETH/USD Price: {v}") # Get list of data sources from sources dict From 34e54d733d2760dabdfc5648264899c4e7404120 Mon Sep 17 00:00:00 2001 From: akrem Date: Thu, 27 Jul 2023 17:51:45 -0400 Subject: [PATCH 4/5] fix maverick source --- src/telliot_feeds/sources/sweth_source.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/telliot_feeds/sources/sweth_source.py b/src/telliot_feeds/sources/sweth_source.py index 9a14c695..1b5bbd42 100644 --- a/src/telliot_feeds/sources/sweth_source.py +++ b/src/telliot_feeds/sources/sweth_source.py @@ -44,6 +44,9 @@ def get_sweth_eth_ratio(self) -> Optional[float]: sweth_eth_ratio_decoded = w3.toInt(sweth_eth_ratio_bytes) sweth_eth_ratio = w3.fromWei(sweth_eth_ratio_decoded, "ether") + # Maverick AMM uses square root of ratio + if self.contract == "0x9980ce3b5570e41324904f46A06cE7B466925E23": + sweth_eth_ratio = sweth_eth_ratio**2 logger.debug(f"sweth_eth_ratio: {sweth_eth_ratio}") return float(sweth_eth_ratio) From 571cee271e785b800ff02f661c7518200f21960e Mon Sep 17 00:00:00 2001 From: akrem Date: Fri, 28 Jul 2023 11:10:04 -0400 Subject: [PATCH 5/5] Add var name --- src/telliot_feeds/sources/sweth_source.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/telliot_feeds/sources/sweth_source.py b/src/telliot_feeds/sources/sweth_source.py index 1b5bbd42..77340641 100644 --- a/src/telliot_feeds/sources/sweth_source.py +++ b/src/telliot_feeds/sources/sweth_source.py @@ -13,6 +13,9 @@ logger = get_logger(__name__) +MAVERICK_CONTRACT = "0x9980ce3b5570e41324904f46A06cE7B466925E23" +SWETH_CONTRACT = "0xf951E335afb289353dc249e82926178EaC7DEd78" + class swETHSpotPriceService(WebPriceService): """Custom swETH Price Service""" @@ -45,7 +48,7 @@ def get_sweth_eth_ratio(self) -> Optional[float]: sweth_eth_ratio_decoded = w3.toInt(sweth_eth_ratio_bytes) sweth_eth_ratio = w3.fromWei(sweth_eth_ratio_decoded, "ether") # Maverick AMM uses square root of ratio - if self.contract == "0x9980ce3b5570e41324904f46A06cE7B466925E23": + if self.contract == MAVERICK_CONTRACT: sweth_eth_ratio = sweth_eth_ratio**2 logger.debug(f"sweth_eth_ratio: {sweth_eth_ratio}") return float(sweth_eth_ratio) @@ -82,7 +85,7 @@ class swETHSpotPriceSource(PriceSource): service: swETHSpotPriceService = field(default_factory=swETHSpotPriceService, init=False) def __post_init__(self) -> None: - self.service.contract = "0xf951E335afb289353dc249e82926178EaC7DEd78" + self.service.contract = SWETH_CONTRACT self.service.calldata = "0xd68b2cb6" @@ -93,5 +96,5 @@ class swETHMaverickSpotPriceSource(PriceSource): service: swETHSpotPriceService = field(default_factory=swETHSpotPriceService) def __post_init__(self) -> None: - self.service.contract = "0x9980ce3b5570e41324904f46A06cE7B466925E23" + self.service.contract = MAVERICK_CONTRACT self.service.calldata = "0x91c0914e000000000000000000000000817e8c9a99db98082ca187e4f80498586bf6bc1b"