Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: asyncify yeth #700

Merged
merged 7 commits into from
Apr 8, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 75 additions & 62 deletions yearn/yeth.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,32 @@
import asyncio
import logging
import os
import re
import logging
from datetime import datetime, timezone, timedelta
from pprint import pformat
from typing import Optional, AsyncIterator

import eth_retry

from brownie import chain
from pprint import pformat
from brownie.network.event import _EventItem

from y import Contract, magic, Network
from y.time import get_block_timestamp, 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 y.utils.events import Events
from y.utils.dank_mids import dank_w3

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")

Expand Down Expand Up @@ -49,24 +55,20 @@ 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 float(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:
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)
Expand All @@ -83,14 +85,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)
Expand All @@ -100,8 +101,8 @@ async def describe(self, block=None):
boost = 0

if block:
block_timestamp = get_block_timestamp(block)
samples = get_samples(datetime.fromtimestamp(block_timestamp))
block_timestamp = await get_block_timestamp_async(block)
samples = get_samples(datetime.fromtimestamp(block_timestamp, tz=timezone.utc))
else:
samples = get_samples()

Expand All @@ -118,7 +119,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


Expand All @@ -140,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,
Expand All @@ -156,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)
Expand All @@ -166,16 +175,18 @@ 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 = get_block_timestamp(block)
block_timestamp = await get_block_timestamp_async(block)
samples = get_samples(datetime.fromtimestamp(block_timestamp))
else:
samples = get_samples()
Expand All @@ -201,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

Expand All @@ -211,33 +222,34 @@ 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]

weth_price = await magic.get_price(weth, block=from_block, sync=False)
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):
volume_in_usd[i] = value * weth_price

Expand All @@ -252,26 +264,27 @@ 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)
block_timestamp = await get_block_timestamp_async(block)
now_time = datetime.fromtimestamp(block_timestamp, tz=timezone.utc)
else:
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)}

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)}

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])
Expand Down
Loading