From 48bc9cf7488ea8580a165b5c4e2333f8a31f04db Mon Sep 17 00:00:00 2001 From: BobTheBuidler Date: Mon, 8 Apr 2024 23:23:08 +0000 Subject: [PATCH 1/7] fix: yeth decimal type err --- yearn/yeth.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/yearn/yeth.py b/yearn/yeth.py index dcfcaf8b2..bc14903b7 100644 --- a/yearn/yeth.py +++ b/yearn/yeth.py @@ -237,7 +237,7 @@ async def _get_swap_volumes(self, from_block, to_block): volume_in_eth[asset_in] += amount_in * rates[asset_in] volume_out_eth[asset_out] += amount_out * rates[asset_out] - weth_price = await magic.get_price(weth, block=from_block, sync=False) + weth_price = float(await magic.get_price(weth, block=from_block, sync=False)) for i, value in enumerate(volume_in_eth): volume_in_usd[i] = value * weth_price @@ -271,7 +271,7 @@ async def total_value_at(self, block=None): tvls = await asyncio.gather(*[product.total_value_at(block=block) for product in products]) return {product.name: tvl for product, tvl in zip(products, tvls)} - async def active_products_at(self, block=None): + async def active_vaults_at(self, block=None): products = [self.st_yeth] + self.st_yeth.lsts if block: blocks = await asyncio.gather(*[contract_creation_block_async(str(product.address)) for product in products]) From 0439ef7cbc57c2e680a708334a09e8b5e7fd5f44 Mon Sep 17 00:00:00 2001 From: BobTheBuidler Date: Mon, 8 Apr 2024 23:23:08 +0000 Subject: [PATCH 2/7] fix: active_vaults_at --- yearn/yeth.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/yearn/yeth.py b/yearn/yeth.py index bc14903b7..93c58572b 100644 --- a/yearn/yeth.py +++ b/yearn/yeth.py @@ -10,7 +10,7 @@ from pprint import pformat from y import Contract, magic, Network -from y.time import get_block_timestamp, closest_block_after_timestamp +from y.time import get_block_timestamp_async, closest_block_after_timestamp from y.contracts import contract_creation_block_async from y.exceptions import PriceError, yPriceMagicError @@ -66,7 +66,7 @@ async def _get_supply_price(self, block=None): @eth_retry.auto_retry async def apy(self, samples: ApySamples) -> Apy: block = samples.now - now = get_block_timestamp(block) + now = await get_block_timestamp_async(block) seconds_til_eow = SECONDS_PER_WEEK - now % SECONDS_PER_WEEK data = STAKING_CONTRACT.get_amounts(block_identifier=block) @@ -252,12 +252,13 @@ async def _get_swap_volumes(self, from_block, to_block): } async def describe(self, block=None): - to_block = chain.height - now_time = datetime.today() if block: to_block = block block_timestamp = get_block_timestamp(block) now_time = datetime.fromtimestamp(block_timestamp) + else: + to_block = chain.height + now_time = datetime.today() from_block = self._get_from_block(now_time) @@ -267,7 +268,7 @@ async def describe(self, block=None): return {product.name: desc for product, desc in zip(products, data)} async def total_value_at(self, block=None): - products = await self.active_products_at(block) + products = await self.active_vaults_at(block) tvls = await asyncio.gather(*[product.total_value_at(block=block) for product in products]) return {product.name: tvl for product, tvl in zip(products, tvls)} From e539c9401f4173882774e9fc09cae56152b13685 Mon Sep 17 00:00:00 2001 From: BobTheBuidler Date: Mon, 8 Apr 2024 23:23:08 +0000 Subject: [PATCH 3/7] fix: yeth type err --- yearn/yeth.py | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/yearn/yeth.py b/yearn/yeth.py index 93c58572b..9ced2e222 100644 --- a/yearn/yeth.py +++ b/yearn/yeth.py @@ -49,19 +49,15 @@ def decimals(self): def symbol(self): return 'st-yETH' + async def get_supply(self, block: Optional[Block] = None) -> float: + return (await YETH_POOL.vb_prod_sum.coroutine(block_identifier=block))[1] / 10 ** 18 - async def _get_supply_price(self, block=None): - data = YETH_POOL.vb_prod_sum(block_identifier=block) - supply = data[1] / 1e18 + async def get_price(self, block: Optional[Block] = None) -> Optional[float]: try: - price = await magic.get_price(YETH_TOKEN, block=block, sync=False) + return await magic.get_price(YETH_TOKEN, block=block, sync=False) except yPriceMagicError as e: if not isinstance(e.exception, PriceError): raise e - price = None - - return supply, price - @eth_retry.auto_retry async def apy(self, samples: ApySamples) -> Apy: @@ -83,14 +79,13 @@ async def apy(self, samples: ApySamples) -> Apy: @eth_retry.auto_retry async def tvl(self, block=None) -> Tvl: - supply, price = await self._get_supply_price(block=block) + supply, price = await asyncio.gather(self.get_supply(block), self.get_price(block)) tvl = supply * price if price else None - return Tvl(supply, price, tvl) async def describe(self, block=None): - supply, price = await self._get_supply_price(block=block) + supply, price = await asyncio.gather(self.get_supply(block), self.get_price(block)) try: pool_supply = YETH_POOL.supply(block_identifier=block) total_assets = STAKING_CONTRACT.totalAssets(block_identifier=block) @@ -118,7 +113,7 @@ async def describe(self, block=None): async def total_value_at(self, block=None): - supply, price = await self._get_supply_price(block=block) + supply, price = await asyncio.gather(self.get_supply(block), self.get_price(block)) return supply * price From 0cde11fc269e842a395313661a459277cb9bed32 Mon Sep 17 00:00:00 2001 From: BobTheBuidler Date: Mon, 8 Apr 2024 23:23:08 +0000 Subject: [PATCH 4/7] fix: broken import --- yearn/yeth.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/yearn/yeth.py b/yearn/yeth.py index 9ced2e222..1d5e0ef8d 100644 --- a/yearn/yeth.py +++ b/yearn/yeth.py @@ -1,26 +1,29 @@ import asyncio +import logging import os import re import logging from datetime import datetime, timezone, timedelta +from pprint import pformat +from typing import Optional import eth_retry - from brownie import chain -from pprint import pformat -from y import Contract, magic, Network -from y.time import get_block_timestamp_async, closest_block_after_timestamp +from y import Contract, Network, magic from y.contracts import contract_creation_block_async +from y.datatypes import Block from y.exceptions import PriceError, yPriceMagicError +from y.time import get_block_timestamp_async, closest_block_after_timestamp -from yearn.apy.common import (Apy, ApyFees, - ApySamples, SECONDS_PER_YEAR, SECONDS_PER_WEEK, SharePricePoint, calculate_roi, get_samples) +from yearn.apy.common import (SECONDS_PER_WEEK, SECONDS_PER_YEAR, Apy, ApyFees, + ApySamples, SharePricePoint, calculate_roi, + get_samples) from yearn.common import Tvl +from yearn.debug import Debug from yearn.events import decode_logs, get_logs_asap -from yearn.utils import Singleton from yearn.prices.constants import weth -from yearn.debug import Debug +from yearn.utils import Singleton logger = logging.getLogger("yearn.yeth") @@ -96,7 +99,7 @@ async def describe(self, block=None): if block: block_timestamp = get_block_timestamp(block) - samples = get_samples(datetime.fromtimestamp(block_timestamp)) + samples = get_samples(datetime.fromtimestamp(block_timestamp, tz=timezone.utc)) else: samples = get_samples() @@ -170,7 +173,7 @@ async def describe(self, block=None): data = self._get_lst_data(block=block) if block: - block_timestamp = get_block_timestamp(block) + block_timestamp = await get_block_timestamp_async(block) samples = get_samples(datetime.fromtimestamp(block_timestamp)) else: samples = get_samples() @@ -250,7 +253,7 @@ async def describe(self, block=None): if block: to_block = block block_timestamp = get_block_timestamp(block) - now_time = datetime.fromtimestamp(block_timestamp) + now_time = datetime.fromtimestamp(block_timestamp, tz=timezone.utc) else: to_block = chain.height now_time = datetime.today() From 043273043bc66a8c1bb2ddfc327e3af13cbfc69e Mon Sep 17 00:00:00 2001 From: BobTheBuidler Date: Mon, 8 Apr 2024 23:23:08 +0000 Subject: [PATCH 5/7] fix: yeth type err --- yearn/yeth.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yearn/yeth.py b/yearn/yeth.py index 1d5e0ef8d..8f5147285 100644 --- a/yearn/yeth.py +++ b/yearn/yeth.py @@ -57,7 +57,7 @@ async def get_supply(self, block: Optional[Block] = None) -> float: async def get_price(self, block: Optional[Block] = None) -> Optional[float]: try: - return await magic.get_price(YETH_TOKEN, block=block, sync=False) + return float(await magic.get_price(YETH_TOKEN, block=block, sync=False)) except yPriceMagicError as e: if not isinstance(e.exception, PriceError): raise e From 9b46e6981e6db0eedb2fefb4937394aa12efea10 Mon Sep 17 00:00:00 2001 From: BobTheBuidler Date: Mon, 8 Apr 2024 23:23:08 +0000 Subject: [PATCH 6/7] fix: yeth --- yearn/yeth.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/yearn/yeth.py b/yearn/yeth.py index 8f5147285..b747f790a 100644 --- a/yearn/yeth.py +++ b/yearn/yeth.py @@ -15,6 +15,7 @@ from y.datatypes import Block from y.exceptions import PriceError, yPriceMagicError from y.time import get_block_timestamp_async, closest_block_after_timestamp +from y.utils.dank_mids import dank_w3 from yearn.apy.common import (SECONDS_PER_WEEK, SECONDS_PER_YEAR, Apy, ApyFees, ApySamples, SharePricePoint, calculate_roi, @@ -98,7 +99,7 @@ async def describe(self, block=None): boost = 0 if block: - block_timestamp = get_block_timestamp(block) + block_timestamp = await get_block_timestamp_async(block) samples = get_samples(datetime.fromtimestamp(block_timestamp, tz=timezone.utc)) else: samples = get_samples() @@ -252,16 +253,16 @@ async def _get_swap_volumes(self, from_block, to_block): async def describe(self, block=None): if block: to_block = block - block_timestamp = get_block_timestamp(block) + block_timestamp = await get_block_timestamp_async(block) now_time = datetime.fromtimestamp(block_timestamp, tz=timezone.utc) else: - to_block = chain.height + to_block = await dank_w3.eth.block_number now_time = datetime.today() from_block = self._get_from_block(now_time) self.swap_volumes = await self._get_swap_volumes(from_block, to_block) - products = await self.active_products_at(block) + products = await self.active_vaults_at(block) data = await asyncio.gather(*[product.describe(block=block) for product in products]) return {product.name: desc for product, desc in zip(products, data)} From 0ff008cc8fa1841f061ef338b3a3279fd1825a67 Mon Sep 17 00:00:00 2001 From: BobTheBuidler Date: Mon, 8 Apr 2024 23:23:08 +0000 Subject: [PATCH 7/7] feat: asyncify yeth --- yearn/yeth.py | 73 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 43 insertions(+), 30 deletions(-) diff --git a/yearn/yeth.py b/yearn/yeth.py index b747f790a..98e50d885 100644 --- a/yearn/yeth.py +++ b/yearn/yeth.py @@ -5,16 +5,18 @@ import logging from datetime import datetime, timezone, timedelta from pprint import pformat -from typing import Optional +from typing import Optional, AsyncIterator import eth_retry from brownie import chain +from brownie.network.event import _EventItem from y import Contract, Network, magic from y.contracts import contract_creation_block_async from y.datatypes import Block from y.exceptions import PriceError, yPriceMagicError from y.time import get_block_timestamp_async, closest_block_after_timestamp +from y.utils.events import Events from y.utils.dank_mids import dank_w3 from yearn.apy.common import (SECONDS_PER_WEEK, SECONDS_PER_YEAR, Apy, ApyFees, @@ -139,12 +141,16 @@ def decimals(self): def _sanitize(self, name): return re.sub(r"([\d]+)\.[\d]*", r"\1", name) - def _get_lst_data(self, block=None): - virtual_balance = YETH_POOL.virtual_balance(self.asset_id, block_identifier=block) / 1e18 - weights = YETH_POOL.weight(self.asset_id, block_identifier=block) + async def _get_lst_data(self, block=None): + virtual_balance, weights, rate = await asyncio.gather( + YETH_POOL.virtual_balance.coroutine(self.asset_id, block_identifier=block), + YETH_POOL.weight.coroutine(self.asset_id, block_identifier=block), + RATE_PROVIDER.rate.coroutine(str(self.lst), block_identifier=block) + ) + virtual_balance /= 1e18 weight = weights[0] / 1e18 target = weights[1] / 1e18 - rate = RATE_PROVIDER.rate(str(self.lst), block_identifier=block) / 1e18 + rate /= 1e18 return { "virtual_balance": virtual_balance, @@ -155,8 +161,12 @@ def _get_lst_data(self, block=None): @eth_retry.auto_retry async def apy(self, samples: ApySamples) -> Apy: - now_rate = RATE_PROVIDER.rate(str(self.lst), block_identifier=samples.now) / 1e18 - week_ago_rate = RATE_PROVIDER.rate(str(self.lst), block_identifier=samples.week_ago) / 1e18 + now_rate, week_ago_rate = await asyncio.gather( + RATE_PROVIDER.rate.coroutine(str(self.lst), block_identifier=samples.now), + RATE_PROVIDER.rate(str(self.lst), block_identifier=samples.week_ago), + ) + now_rate /= 1e18 + week_ago_rate /= 1e18 now_point = SharePricePoint(samples.now, now_rate) week_ago_point = SharePricePoint(samples.week_ago, week_ago_rate) apy = calculate_roi(now_point, week_ago_point) @@ -165,13 +175,15 @@ async def apy(self, samples: ApySamples) -> Apy: @eth_retry.auto_retry async def tvl(self, block=None) -> Tvl: - data = self._get_lst_data(block=block) + data = await self._get_lst_data(block=block) tvl = data["virtual_balance"] * data["rate"] return Tvl(data["virtual_balance"], data["rate"], tvl) async def describe(self, block=None): - weth_price = await magic.get_price(weth, block=block, sync=False) - data = self._get_lst_data(block=block) + weth_price, data = await asyncio.gather( + magic.get_price(weth, block=block, sync=False), + self._get_lst_data(block=block), + ) if block: block_timestamp = await get_block_timestamp_async(block) @@ -200,7 +212,7 @@ async def describe(self, block=None): } async def total_value_at(self, block=None): - data = self._get_lst_data(block=block) + data = await self._get_lst_data(block=block) tvl = data["virtual_balance"] * data["rate"] return tvl @@ -210,31 +222,32 @@ def __init__(self) -> None: self.st_yeth = StYETH() self.swap_volumes = {} self.resolution = os.environ.get('RESOLUTION', '1h') # Default: 1 hour - + self.swap_events = Events(addresses=[str(YETH_POOL)]) + + async def _get_swaps(self, from_block, to_block) -> AsyncIterator[_EventItem]: + async for event in YETH_POOL.events.Swap.events(to_block): + if event.block_number < from_block: + continue + elif event.block_number > to_block: + return + yield event + async def _get_swap_volumes(self, from_block, to_block): - logs = get_logs_asap([str(YETH_POOL)], None, from_block=from_block, to_block=to_block) - events = decode_logs(logs) - - num_assets = YETH_POOL.num_assets(block_identifier=from_block) + num_assets = await YETH_POOL.num_assets.coroutine(block_identifier=from_block) volume_in_eth = [0] * num_assets volume_out_eth = [0] * num_assets volume_in_usd = [0] * num_assets volume_out_usd = [0] * num_assets - rates = [] - for i in range(num_assets): - lst = self.st_yeth.lsts[i] - address = str(lst.lst) - rates.append(RATE_PROVIDER.rate(address, block_identifier=from_block) / 1e18) - - for e in events: - if e.name == "Swap": - asset_in = e["asset_in"] - asset_out = e["asset_out"] - amount_in = e["amount_in"] / 1e18 - amount_out = e["amount_out"] / 1e18 - volume_in_eth[asset_in] += amount_in * rates[asset_in] - volume_out_eth[asset_out] += amount_out * rates[asset_out] + rates = [r / 1e18 for r in await asyncio.gather(*[RATE_PROVIDER.rate.coroutine(str(self.st_yeth.lsts[i].lst), block_identifier=from_block) for i in range(num_assets)])] + + async for swap in self._get_swaps(from_block, to_block): + asset_in = swap["asset_in"] + asset_out = swap["asset_out"] + amount_in = swap["amount_in"] / 1e18 + amount_out = swap["amount_out"] / 1e18 + volume_in_eth[asset_in] += amount_in * rates[asset_in] + volume_out_eth[asset_out] += amount_out * rates[asset_out] weth_price = float(await magic.get_price(weth, block=from_block, sync=False)) for i, value in enumerate(volume_in_eth):