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

Restructure price fetching #421

Merged
merged 5 commits into from
Nov 10, 2024
Merged
Show file tree
Hide file tree
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
22 changes: 13 additions & 9 deletions src/fetch/payouts.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@
from dune_client.types import Address
from pandas import DataFrame, Series

from src.abis.load import WETH9_ADDRESS
from src.constants import COW_TOKEN_ADDRESS, COW_BONDING_POOL
from src.fetch.dune import DuneFetcher
from src.fetch.prices import eth_in_token, TokenId, token_in_eth
from src.fetch.prices import exchange_rate_atoms
from src.models.accounting_period import AccountingPeriod
from src.models.overdraft import Overdraft
from src.models.token import Token
Expand Down Expand Up @@ -261,7 +262,6 @@ class TokenConversion:
"""

eth_to_token: Callable
token_to_eth: Callable


def extend_payment_df(pdf: DataFrame, converter: TokenConversion) -> DataFrame:
Expand Down Expand Up @@ -457,9 +457,6 @@ def construct_payouts(
"""Workflow of solver reward payout logic post-CIP27"""
# pylint: disable-msg=too-many-locals

price_day = dune.period.end - timedelta(days=1)
reward_token = TokenId.COW

quote_rewards_df = orderbook.get_quote_rewards(dune.start_block, dune.end_block)
batch_rewards_df = orderbook.get_solver_rewards(dune.start_block, dune.end_block)
partner_fees_df = batch_rewards_df[["partner_list", "partner_fee_eth"]]
Expand Down Expand Up @@ -495,15 +492,22 @@ def construct_payouts(
# TODO - After CIP-20 phased in, adapt query to return `solver` like all the others
slippage_df = slippage_df.rename(columns={"solver_address": "solver"})

reward_token = COW_TOKEN_ADDRESS
native_token = Address(WETH9_ADDRESS)
price_day = dune.period.end - timedelta(days=1)
converter = TokenConversion(
eth_to_token=lambda t: exchange_rate_atoms(
native_token, reward_token, price_day
fhenneke marked this conversation as resolved.
Show resolved Hide resolved
)
* t,
)

complete_payout_df = construct_payout_dataframe(
# Fetch and extend auction data from orderbook.
payment_df=extend_payment_df(
pdf=merged_df,
# provide token conversion functions (ETH <--> COW)
converter=TokenConversion(
eth_to_token=lambda t: eth_in_token(reward_token, t, price_day),
token_to_eth=lambda t: token_in_eth(reward_token, t, price_day),
),
converter=converter,
),
# Dune: Fetch Solver Slippage & Reward Targets
slippage_df=slippage_df,
Expand Down
41 changes: 18 additions & 23 deletions src/fetch/prices.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@
import logging.config
from datetime import datetime
from enum import Enum
from fractions import Fraction

from coinpaprika import client as cp
from dune_client.types import Address

from src.constants import LOG_CONFIG_FILE

Expand Down Expand Up @@ -41,32 +43,25 @@ def decimals(self) -> int:
return 18


def eth_in_token(quote_token: TokenId, amount: int, day: datetime) -> int:
"""
Compute how much of `token` is equivalent to `amount` ETH on `day`.
Use current price if day not specified.
"""
eth_amount_usd = token_in_usd(TokenId.ETH, amount, day)
quote_price_usd = token_in_usd(quote_token, 10 ** quote_token.decimals(), day)
return int(eth_amount_usd / quote_price_usd * 10 ** quote_token.decimals())


def token_in_eth(token: TokenId, amount: int, day: datetime) -> int:
"""
The inverse of eth_in_token;
how much ETH is equivalent to `amount` of `token` on `day`
"""
token_amount_usd = token_in_usd(token, amount, day)
eth_price_usd = token_in_usd(TokenId.ETH, 10 ** TokenId.ETH.decimals(), day)
TOKEN_ADDRESS_TO_ID = {
Address("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"): TokenId.ETH,
Address("0xDEf1CA1fb7FBcDC777520aa7f396b4E015F497aB"): TokenId.COW,
Address("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"): TokenId.USDC,
}

return int(token_amount_usd / eth_price_usd * 10 ** TokenId.ETH.decimals())


def token_in_usd(token: TokenId, amount_wei: int, day: datetime) -> float:
"""
Converts token amount [wei] to usd amount on given day.
def exchange_rate_atoms(
token_1_address: Address, token_2_address: Address, day: datetime
) -> Fraction:
"""Exchange rate for converting tokens on a given day.
The convention for the exchange rate r is as follows:
x atoms of token 1 have the same value as x * r atoms of token 2.
"""
return float(amount_wei) * usd_price(token, day) / 10.0 ** token.decimals()
token_1 = TOKEN_ADDRESS_TO_ID[token_1_address]
token_2 = TOKEN_ADDRESS_TO_ID[token_2_address]
price_1 = Fraction(usd_price(token_1, day)) / Fraction(10 ** token_1.decimals())
price_2 = Fraction(usd_price(token_2, day)) / Fraction(10 ** token_2.decimals())
return price_1 / price_2


@functools.cache
Expand Down
56 changes: 26 additions & 30 deletions tests/e2e/test_prices.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import unittest
from datetime import datetime, timedelta

from dune_client.types import Address

from src.abis.load import WETH9_ADDRESS
from src.constants import COW_TOKEN_ADDRESS
from src.fetch.prices import (
eth_in_token,
TOKEN_ADDRESS_TO_ID,
TokenId,
token_in_eth,
token_in_usd,
exchange_rate_atoms,
usd_price,
)

Expand All @@ -19,48 +22,41 @@ def setUp(self) -> None:
self.cow_price = usd_price(TokenId.COW, self.some_date)
self.eth_price = usd_price(TokenId.ETH, self.some_date)
self.usdc_price = usd_price(TokenId.USDC, self.some_date)
self.cow_address = COW_TOKEN_ADDRESS
self.weth_address = Address(WETH9_ADDRESS)
self.usdc_address = Address("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48")

def test_usd_price(self):
self.assertEqual(self.usdc_price, 1.001622)
self.assertEqual(self.eth_price, 2481.89)
self.assertEqual(self.cow_price, 0.194899)

def test_token_in_usd(self):
def test_exchange_rate_atoms(self):
with self.assertRaises(AssertionError):
token_in_usd(TokenId.COW, ONE_ETH, datetime.today())
exchange_rate_atoms(self.cow_address, self.weth_address, datetime.today())

self.assertEqual(
token_in_usd(TokenId.ETH, ONE_ETH, self.some_date), self.eth_price
)
self.assertEqual(
token_in_usd(TokenId.COW, ONE_ETH, self.some_date), self.cow_price
exchange_rate_atoms(self.cow_address, self.cow_address, self.some_date), 1
)
self.assertEqual(
token_in_usd(TokenId.USDC, 10**6, self.some_date), self.usdc_price
exchange_rate_atoms(self.cow_address, self.weth_address, self.some_date),
1
/ exchange_rate_atoms(self.weth_address, self.cow_address, self.some_date),
)

def test_eth_in_token(self):
self.assertAlmostEqual(
eth_in_token(TokenId.COW, ONE_ETH, self.some_date) / 10**18,
self.eth_price / self.cow_price,
delta=DELTA,
)
self.assertAlmostEqual(
eth_in_token(TokenId.USDC, ONE_ETH, self.some_date) / 10**6,
self.eth_price / self.usdc_price,
delta=DELTA,
self.assertEqual(
float(
exchange_rate_atoms(self.cow_address, self.weth_address, self.some_date)
),
self.cow_price / self.eth_price,
)

def test_token_in_eth(self):
self.assertAlmostEqual(
token_in_eth(TokenId.COW, ONE_ETH, self.some_date),
10**18 * self.cow_price // self.eth_price,
delta=DELTA,
)
self.assertAlmostEqual(
token_in_eth(TokenId.USDC, 10**6, self.some_date),
10**18 * self.usdc_price // self.eth_price,
delta=DELTA,
self.assertEqual(
float(
exchange_rate_atoms(self.cow_address, self.usdc_address, self.some_date)
)
* 10**18,
self.cow_price / self.usdc_price * 10**6,
)

def test_price_cache(self):
Expand Down
4 changes: 1 addition & 3 deletions tests/unit/test_payouts.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,7 @@ def setUp(self) -> None:
0.0,
]
# Mocking TokenConversion!
self.mock_converter = TokenConversion(
eth_to_token=lambda t: int(t * 1000), token_to_eth=lambda t: t // 1000
)
self.mock_converter = TokenConversion(eth_to_token=lambda t: int(t * 1000))

def test_extend_payment_df(self):
base_data_dict = {
Expand Down
Loading