Skip to content

Commit

Permalink
update for kodo mainnet
Browse files Browse the repository at this point in the history
  • Loading branch information
drummaster98 committed Jul 12, 2024
1 parent 146982f commit 1b0e8c1
Show file tree
Hide file tree
Showing 14 changed files with 230 additions and 47 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ __pycache__/
build
dist
env
env.bash
run.sh
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
FROM pypy:3.9-slim-bullseye
FROM python:3.9-slim

RUN apt-get update
RUN apt-get install -y --no-install-recommends gcc g++ libssl-dev libev-dev
RUN apt-get install -y --no-install-recommends gcc g++ libssl-dev libev-dev git
RUN apt-get clean

WORKDIR /app
Expand Down
2 changes: 2 additions & 0 deletions app/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from app.settings import LOGGER, honeybadger_handler
from app.venfts import Accounts
from app.supply import Supply
from app.stats import Stats

app = falcon.App(cors_enable=True, middleware=[CompressionMiddleware()])
app.add_error_handler(Exception, honeybadger_handler)
Expand All @@ -25,6 +26,7 @@
app.add_route('/api/v1/configuration', Configuration())
app.add_route('/api/v1/pairs', Pairs())
app.add_route('/api/v1/supply', Supply())
app.add_route('/api/v1/stats', Stats())

# TODO: Remove when no longer needed for backward-compat...
app.add_route('/api/v1/baseAssets', Assets())
Expand Down
65 changes: 62 additions & 3 deletions app/assets/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,10 @@
from web3.exceptions import ContractLogicError

from app.settings import (
LOGGER, CACHE, TOKENLISTS, ROUTER_ADDRESS, STABLE_TOKEN_ADDRESS,
LOGGER, CACHE, TOKENLISTS, ROUTER_ADDRESS, STABLE_TOKEN_ADDRESS, WETH_ADDRESS,
IGNORED_TOKEN_ADDRESSES
)


class Token(Model):
"""ERC20 token model."""
__database__ = CACHE
Expand Down Expand Up @@ -165,6 +164,62 @@ def aggregated_price_in_stables(self):
requests.exceptions.JSONDecodeError
):
return price

def mock_eth_price(self):
'''Returns the price of ETH on Optimism.'''
weth_optimism_token = 'optimism:0x4200000000000000000000000000000000000006'

res = requests.get(self.DEFILLAMA_ENDPOINT + weth_optimism_token).json()
coins = res.get('coins', {})

for (_, coin) in coins.items():
return coin.get('price', 0)

return 0


def mock_aggregated_price_in_stables(self):
"""Returns the price quoted from an aggregator in stables/USDC."""
# Peg it forever.
if self.address == STABLE_TOKEN_ADDRESS:
return 1.0

if self.address == WETH_ADDRESS:
return self.mock_eth_price()

weth = Token.find(WETH_ADDRESS)
try:
amount, is_stable = Call(
ROUTER_ADDRESS,
[
'getAmountOut(uint256,address,address)(uint256,bool)',
1 * 10**self.decimals,
self.address,
weth.address
]
)()
except ContractLogicError:
return 0

return amount * weth.price / 10**weth.decimals

# import os

# PRICE_MAP = {
# os.getenv('WETH_ADDRESS').lower(): 3590.03, # WETH
# os.getenv('TKO_ADDRESS').lower(): 3.6075, # TKO
# os.getenv('LRC_ADDRESS').lower(): 0.229513, # LRC
# os.getenv('USDC_ADDRESS').lower(): 1.022, # USDC
# os.getenv('MIM_ADDRESS').lower(): 0.999039, # MIM
# os.getenv('KDO_ADDRESS').lower(): 0.0943, # KDO
# os.getenv('HORSE_ADDRESS').lower(): 0.03, # WETH
# os.getenv('NOTCH_ADDRESS').lower(): 0.003, # NOTCH
# os.getenv('WSXETH_ADDRESS').lower(): 3590, #
# os.getenv('KITTY_ADDRESS').lower(): 0.001, # KITTY
# }

# return PRICE_MAP[self.address.lower()]


def chain_price_in_stables(self):
"""Returns the price quoted from our router in stables/USDC."""
Expand Down Expand Up @@ -201,7 +256,8 @@ def find(cls, address):

def _update_price(self):
"""Updates the token price in USD from different sources."""
self.price = self.aggregated_price_in_stables()
# self.price = self.aggregated_price_in_stables()
self.price = self.mock_aggregated_price_in_stables() # TODO: for test

if self.price == 0:
self.price = self.chain_price_in_stables()
Expand Down Expand Up @@ -237,9 +293,11 @@ def from_tokenlists(cls):
"""Fetches and merges all the tokens from available tokenlists."""
our_chain_id = w3.eth.chain_id

import os
for tlist in TOKENLISTS:
try:
res = requests.get(tlist).json()

for token_data in res['tokens']:
# Skip tokens from other chains...
if token_data.get('chainId', None) != our_chain_id:
Expand All @@ -261,6 +319,7 @@ def from_tokenlists(cls):
LOGGER.debug(
'Loaded %s:%s.', cls.__name__, token_data['address']
)

except Exception as error:
LOGGER.error(error)
continue
2 changes: 1 addition & 1 deletion app/configuration/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def recache(cls):
conf = json.dumps(
dict(
data=[
default_token._data,
# default_token._data,
stable_token._data,
*route_token_data
],
Expand Down
58 changes: 40 additions & 18 deletions app/gauges/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from app.settings import (
LOGGER, CACHE, VOTER_ADDRESS,
DEFAULT_TOKEN_ADDRESS, WRAPPED_BRIBE_FACTORY_ADDRESS, VE_ADDRESS
DEFAULT_TOKEN_ADDRESS, VE_ADDRESS
)
from app.assets import Token

Expand All @@ -23,7 +23,6 @@ class Gauge(Model):
total_supply = FloatField()
bribe_address = TextField(index=True)
fees_address = TextField(index=True)
wrapped_bribe_address = TextField(index=True)
# Per epoch...
reward = FloatField()

Expand All @@ -35,8 +34,15 @@ class Gauge(Model):
tbv = FloatField(default=0.0)
# Voting APR
votes = FloatField(default=0.0)
apr_rebase = FloatField(default=0.0)
apr = FloatField(default=0.0)

# Trading fees for the upcoming voting epoch
# Also used to estimate trading volume
fee_claimable0 = FloatField(default=0.0)
fee_claimable1 = FloatField(default=0.0)
fee_claimable_value = FloatField(default=0.0)

# TODO: Backwards compat. Remove once no longer needed...
bribeAddress = TextField()
feesAddress = TextField()
Expand All @@ -58,6 +64,10 @@ def from_chain(cls, address):
"""Fetches pair/pool gauge data from chain."""
address = address.lower()

# Avoid circular import...
from app.pairs.model import Pair
pair = Pair.get(Pair.gauge_address == address)

pair_gauge_multi = Multicall([
Call(
address,
Expand All @@ -78,13 +88,29 @@ def from_chain(cls, address):
VOTER_ADDRESS,
['internal_bribes(address)(address)', address],
[['fees_address', None]]
),
Call(
pair.address,
['claimable0(address)(uint256)', address],
[['fee_claimable0', None]]
),
Call(
pair.address,
['claimable1(address)(uint256)', address],
[['fee_claimable1', None]]
)
])

data = pair_gauge_multi()
data['decimals'] = cls.DEFAULT_DECIMALS
data['total_supply'] = data['total_supply'] / data['decimals']

token0 = Token.find(pair.token0_address)
token1 = Token.find(pair.token1_address)
data['fee_claimable0'] = data['fee_claimable0'] / (10**token0.decimals)
data['fee_claimable1'] = data['fee_claimable1'] / (10**token1.decimals)
data['fee_claimable_value'] = (data['fee_claimable0'] * token0.price) + (data['fee_claimable1'] * token1.price)

token = Token.find(DEFAULT_TOKEN_ADDRESS)
data['reward'] = (
data['reward_rate'] / 10**token.decimals * cls.DAY_IN_SECONDS
Expand All @@ -95,22 +121,13 @@ def from_chain(cls, address):
data['feesAddress'] = data['fees_address']
data['totalSupply'] = data['total_supply']

if data.get('bribe_address') not in (ADDRESS_ZERO, None):
data['wrapped_bribe_address'] = Call(
WRAPPED_BRIBE_FACTORY_ADDRESS,
['oldBribeToNew(address)(address)', data['bribe_address']]
)()

if data.get('wrapped_bribe_address') in (ADDRESS_ZERO, ''):
del data['wrapped_bribe_address']

# Cleanup old data
cls.query_delete(cls.address == address.lower())

gauge = cls.create(address=address, **data)
gauge = cls.create(address=address.lower(), **data)
LOGGER.debug('Fetched %s:%s.', cls.__name__, address)

if data.get('wrapped_bribe_address') not in (ADDRESS_ZERO, None):
if data.get('bribe_address') not in (ADDRESS_ZERO, None):
cls._fetch_external_rewards(gauge)

cls._fetch_internal_rewards(gauge)
Expand All @@ -129,7 +146,10 @@ def rebase_apr(cls):
['calculate_growth(uint256)(uint256)', weekly]
)()

return ((growth * 52) / supply) * 100
if supply == 0: # prevent pre-launch 0 division
return 0
else:
return ((growth * 52) / supply) * 100

@classmethod
def _update_apr(cls, gauge):
Expand All @@ -147,32 +167,34 @@ def _update_apr(cls, gauge):
token = Token.find(DEFAULT_TOKEN_ADDRESS)
votes = votes / 10**token.decimals

gauge.apr_rebase = cls.rebase_apr()
gauge.apr = cls.rebase_apr()

if token.price and votes * token.price > 0:
gauge.votes = votes
gauge.apr += ((gauge.tbv * 52) / (votes * token.price)) * 100
gauge.save()

gauge.save()

@classmethod
def _fetch_external_rewards(cls, gauge):
"""Fetches gauge external rewards (bribes) data from chain."""
tokens_len = Call(
gauge.wrapped_bribe_address,
gauge.bribe_address,
'rewardsListLength()(uint256)'
)()

reward_calls = []

for idx in range(0, tokens_len):
bribe_token_address = Call(
gauge.wrapped_bribe_address,
gauge.bribe_address,
['rewards(uint256)(address)', idx]
)()

reward_calls.append(
Call(
gauge.wrapped_bribe_address,
gauge.bribe_address,
['left(address)(uint256)', bribe_token_address],
[[bribe_token_address, None]]
)
Expand Down
5 changes: 3 additions & 2 deletions app/pairs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,17 @@ def serialize(cls):

if pair.gauge_address:
gauge = Gauge.find(pair.gauge_address)

data['gauge'] = gauge._data
data['gauge']['bribes'] = []

for (token_addr, reward_ammount) in gauge.rewards:
data['gauge']['bribes'].append(
dict(
token=Token.find(token_addr)._data,
reward_ammount=float(reward_ammount),
reward_amount=float(reward_ammount),
# TODO: Backwards compat...
rewardAmmount=float(reward_ammount)
rewardAmount=float(reward_ammount)
)
)

Expand Down
9 changes: 5 additions & 4 deletions app/pairs/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
LOGGER, CACHE, FACTORY_ADDRESS, VOTER_ADDRESS, DEFAULT_TOKEN_ADDRESS
)


class Pair(Model):
"""Liquidity pool pairs model."""
__database__ = CACHE
Expand Down Expand Up @@ -66,10 +65,12 @@ def _update_apr(self, gauge):
return

token = Token.find(DEFAULT_TOKEN_ADDRESS)
token_price = token.chain_price_in_stables()

daily_apr = (gauge.reward * token_price) / self.tvl * 100
# token_price = token.chain_price_in_stables()

daily_apr = 0
if token.price:
daily_apr = (gauge.reward * token.price) / self.tvl * 100

self.apr = daily_apr * 365
self.save()

Expand Down
2 changes: 1 addition & 1 deletion app/rewards/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ def prepare_chain_calls(cls, pair, gauge, token_id):

calls.append(
Call(
gauge.wrapped_bribe_address,
gauge.bribe_address,
[
'earned(address,uint256)(uint256)',
bribe_token_addr.decode('utf-8'),
Expand Down
2 changes: 1 addition & 1 deletion app/settings/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ def honeybadger_handler(req, resp, exc, params):
TOKENLISTS = os.getenv('TOKENLISTS', '').split('|')
DEFAULT_TOKEN_ADDRESS = os.getenv('DEFAULT_TOKEN_ADDRESS').lower()
STABLE_TOKEN_ADDRESS = os.getenv('STABLE_TOKEN_ADDRESS').lower()
WETH_ADDRESS = os.getenv('WETH_ADDRESS').lower()
ROUTE_TOKEN_ADDRESSES = \
os.getenv('ROUTE_TOKEN_ADDRESSES', '').lower().split(',')
IGNORED_TOKEN_ADDRESSES = \
Expand All @@ -54,7 +55,6 @@ def honeybadger_handler(req, resp, exc, params):
ROUTER_ADDRESS = os.getenv('ROUTER_ADDRESS')
VE_ADDRESS = os.getenv('VE_ADDRESS')
REWARDS_DIST_ADDRESS = os.getenv('REWARDS_DIST_ADDRESS')
WRAPPED_BRIBE_FACTORY_ADDRESS = os.getenv('WRAPPED_BRIBE_FACTORY_ADDRESS')

# Seconds to wait before running the chain syncup. `0` disables it!
SYNC_WAIT_SECONDS = int(os.getenv('SYNC_WAIT_SECONDS', 0))
Expand Down
Loading

0 comments on commit 1b0e8c1

Please sign in to comment.