diff --git a/src/fetch/payouts.py b/src/fetch/payouts.py index 6e3b48cd..644fd3b1 100644 --- a/src/fetch/payouts.py +++ b/src/fetch/payouts.py @@ -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 @@ -261,7 +262,6 @@ class TokenConversion: """ eth_to_token: Callable - token_to_eth: Callable def extend_payment_df(pdf: DataFrame, converter: TokenConversion) -> DataFrame: @@ -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"]] @@ -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 + ) + * 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, diff --git a/src/fetch/prices.py b/src/fetch/prices.py index 97442d87..e910124c 100644 --- a/src/fetch/prices.py +++ b/src/fetch/prices.py @@ -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 @@ -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 diff --git a/tests/e2e/test_prices.py b/tests/e2e/test_prices.py index b3bc8b9b..13cf7cc5 100644 --- a/tests/e2e/test_prices.py +++ b/tests/e2e/test_prices.py @@ -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, ) @@ -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): diff --git a/tests/unit/test_payouts.py b/tests/unit/test_payouts.py index 1d4a07cc..5bfad642 100644 --- a/tests/unit/test_payouts.py +++ b/tests/unit/test_payouts.py @@ -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 = {