Skip to content

Commit

Permalink
Merge pull request #86 from ton-studio/hotfix/tvl_calculation
Browse files Browse the repository at this point in the history
Fix pools TVL estimation + Adding AquaUSD to the list of stablecoins
  • Loading branch information
shuva10v authored Feb 4, 2025
2 parents 851a3ed + 6c4562e commit a937445
Show file tree
Hide file tree
Showing 4 changed files with 184 additions and 53 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
.venv
__pycache__/
.vscode
122 changes: 69 additions & 53 deletions parser/parsers/message/swap_volume.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@
tsTON = Parser.uf2raw('EQC98_qAmNEptUtPc7W6xdHh_ZHrBUFpw5Ft_IzNU20QAJav')
oUSDT = Parser.uf2raw('EQC_1YoM8RBixN95lz7odcF3Vrkc_N8Ne7gQi7Abtlet_Efi')
oUSDC = Parser.uf2raw('EQC61IQRl0_la95t27xhIpjxZt32vl1QQVF2UgTNuvD18W-4')
AquaUSD = Parser.uf2raw('EQAWDyxARSl3ol2G1RMLMwepr3v6Ter5ls3jiAlheKshgg0K')

STABLES = [USDT, jUSDT, jUSDC]

STABLES = [USDT, jUSDT, jUSDC, AquaUSD]
TONS = [pTON, TON, pTONv2, WTON_Megaton, WTON_Stonfi, wTTon_TONCO]
LSDS = [stTON, tsTON]
ORBIT_STABLES = [oUSDT, oUSDC]
Expand Down Expand Up @@ -121,61 +123,75 @@ def normalize_addr(a):
Updates swap pool inplace
"""
def estimate_tvl(pool: DexPool, db: DB):
tvl_usd, tvl_ton = None, None
ton_price = db.get_core_price(USDT, pool.last_updated)
if ton_price is None:
logger.warning(f"No TON price found for {pool.last_updated}")
return
ton_price = ton_price * 1e3 # normalize on decimals difference
def normalize_addr(a):
if type(a) == Address:
if type(a) is Address:
return a.to_str(is_user_friendly=False).upper()
else:
return a
jetton_left = normalize_addr(pool.jetton_left)
jetton_right = normalize_addr(pool.jetton_right)
if jetton_left in STABLES or (jetton_left in ORBIT_STABLES and pool.last_updated < ORBIT_HACK_TIMESTAMP):
tvl_usd = pool.reserves_left / 1e6 * 2
tvl_ton = tvl_usd / ton_price
elif jetton_right in STABLES or (jetton_right in ORBIT_STABLES and pool.last_updated < ORBIT_HACK_TIMESTAMP):
tvl_usd = pool.reserves_right / 1e6 * 2
tvl_ton = tvl_usd / ton_price

elif jetton_left in TONS:
tvl_ton = pool.reserves_left / 1e9 * 2
tvl_usd = tvl_ton * ton_price
elif jetton_right in TONS:
tvl_ton = pool.reserves_right / 1e9 * 2
tvl_usd = tvl_ton * ton_price

elif jetton_left in LSDS:
lsd_price = db.get_core_price(jetton_left, pool.last_updated)
if not lsd_price:
logger.warning(f"No price for {jetton_left} for {pool.last_updated}")
return
tvl_ton = pool.reserves_left / 1e9 * lsd_price * 2
tvl_usd = tvl_ton * ton_price
elif jetton_right in LSDS:
lsd_price = db.get_core_price(jetton_right, pool.last_updated)
if not lsd_price:
logger.warning(f"No price for {jetton_right} for {pool.last_updated}")
return
tvl_ton = pool.reserves_right / 1e9 * lsd_price * 2
tvl_usd = tvl_ton * ton_price
else:
if NON_LIQUID_POOLS_TVL:
left_price = db.get_agg_price(jetton_left, pool.last_updated)
right_price = db.get_agg_price(jetton_right, pool.last_updated)
if not left_price :
logger.warning(f"No price for {jetton_left} for {pool.last_updated}")
return
if not right_price :
logger.warning(f"No price for {right_price} for {pool.last_updated}")
return
tvl_ton = (pool.reserves_left * left_price + pool.reserves_right * right_price) / 1e9

def estimate_jetton_tvl(jetton, reserves, last_updated, ton_price):
tvl_usd, tvl_ton, is_liquid = None, None, True
jetton = normalize_addr(jetton)

if jetton in STABLES:
tvl_usd = reserves / 1e6
tvl_ton = tvl_usd / ton_price

elif jetton in ORBIT_STABLES:
if last_updated < ORBIT_HACK_TIMESTAMP:
tvl_usd = reserves / 1e6
tvl_ton = tvl_usd / ton_price
else:
is_liquid = False

elif jetton in TONS:
tvl_ton = reserves / 1e9
tvl_usd = tvl_ton * ton_price
pool.is_liquid = False

elif jetton in LSDS:
lsd_price = db.get_core_price(jetton, last_updated)
if lsd_price:
tvl_ton = reserves * lsd_price / 1e9
tvl_usd = tvl_ton * ton_price
else:
logger.warning(f"No price for {jetton} for {last_updated}")

else:
if NON_LIQUID_POOLS_TVL:
price = db.get_agg_price(jetton, last_updated)
if price :
tvl_ton = reserves * price / 1e9
tvl_usd = tvl_ton * ton_price
else:
logger.warning(f"No price for {jetton} for {last_updated}")
is_liquid = False

return tvl_usd, tvl_ton, is_liquid

if tvl_ton is not None:
pool.tvl_ton = tvl_ton
pool.tvl_usd = tvl_usd
ton_price = db.get_core_price(USDT, pool.last_updated)
if not ton_price:
logger.warning(f"No TON price found or TON price = 0 for {pool.last_updated}")
return
ton_price = ton_price * 1e3 # normalize on decimals difference

tvl_usd_left, tvl_ton_left, is_liquid_left = estimate_jetton_tvl(pool.jetton_left, pool.reserves_left, pool.last_updated, ton_price)
tvl_usd_right, tvl_ton_right, is_liquid_right = estimate_jetton_tvl(pool.jetton_right, pool.reserves_right, pool.last_updated, ton_price)

if is_liquid_left and is_liquid_right and tvl_ton_left is not None and tvl_ton_right is not None:
pool.tvl_ton = tvl_ton_left + tvl_ton_right
pool.tvl_usd = tvl_usd_left + tvl_usd_right

elif is_liquid_left and not is_liquid_right and tvl_ton_left is not None:
pool.tvl_ton = tvl_ton_left * 2
pool.tvl_usd = tvl_usd_left * 2

elif is_liquid_right and not is_liquid_left and tvl_ton_right is not None:
pool.tvl_ton = tvl_ton_right * 2
pool.tvl_usd = tvl_usd_right * 2

elif not is_liquid_left and not is_liquid_right:
pool.is_liquid = False

if tvl_ton_left is not None and tvl_ton_right is not None:
pool.tvl_ton = tvl_ton_left + tvl_ton_right
pool.tvl_usd = tvl_usd_left + tvl_usd_right
112 changes: 112 additions & 0 deletions parser/parsers/message/swap_volume_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import importlib
import pytest
from unittest.mock import Mock
from pytoniq_core import Address

from parser.parsers.message.swap_volume import estimate_tvl
from parser.model.dexpool import DexPool


TON = Address("EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c")
USDT = Address("EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs")
oUSDT = Address("EQC_1YoM8RBixN95lz7odcF3Vrkc_N8Ne7gQi7Abtlet_Efi")
tsTON = Address("EQC98_qAmNEptUtPc7W6xdHh_ZHrBUFpw5Ft_IzNU20QAJav")
NOT = Address("EQAvlWFDxGF2lXm67y4yzC17wYKD9A0guwPkMs1gOsM__NOT")
STORM = Address("EQBsosmcZrD6FHijA7qWGLw5wo_aH8UN435hi935jJ_STORM")
AquaUSD = Address("EQAWDyxARSl3ol2G1RMLMwepr3v6Ter5ls3jiAlheKshgg0K")


@pytest.mark.parametrize(
"jetton_left, jetton_right, reserves_left, reserves_right, last_updated, "
"usdt_core_price, tston_core_price, not_agg_price, storm_agg_price, non_liquid_pools_tvl, "
"res_tvl_usd, res_tvl_ton, res_is_liquid",
[
# TONS-STABLES pool, usual case
(TON, USDT, 2e15, 1.1e13, 1738000000, 0.005, 1.05, 0.001, 0.005, "0", 2.1e7, 4.2e6, True),
# TONS-STABLES pool, no TON price found
(TON, USDT, 2e15, 1.1e13, 1738000000, None, 1.05, 0.001, 0.005, "0", None, None, True),
# TONS-STABLES pool, TON price = 0
(TON, USDT, 2e15, 1.1e13, 1738000000, 0, 1.05, 0.001, 0.005, "0", None, None, True),
# TONS-ORBIT_STABLES pool, time after ORBIT_HACK_TIMESTAMP
(TON, oUSDT, 2e13, 1.1e11, 1738000000, 0.005, 1.05, 0.001, 0.005, "0", 2e5, 4e4, True),
# TONS-ORBIT_STABLES pool, time befor ORBIT_HACK_TIMESTAMP
(TON, oUSDT, 2e13, 1.1e11, 1703900000, 0.005, 1.05, 0.001, 0.005, "0", 2.1e5, 4.2e4, True),
# LSDS-STABLES pool, usual case
(tsTON, USDT, 2e15, 1e13, 1738000000, 0.005, 1.05, 0.001, 0.005, "0", 2.05e7, 4.1e6, True),
# LSDS-STABLES pool, no price for LSD
(tsTON, USDT, 2e15, 1e13, 1738000000, 0.005, None, 0.001, 0.005, "0", None, None, True),
# TONS-JETTONS pool, usual case
(TON, NOT, 1e12, 1.1e15, 1738000000, 0.005, 1.05, 0.001, 0.005, "0", 1e4, 2e3, True),
# JETTONS-TONS pool, usual case
(NOT, TON, 1.1e15, 1e12, 1738000000, 0.005, 1.05, 0.001, 0.005, "0", 1e4, 2e3, True),
# TONS-JETTONS pool, usual case, NON_LIQUID_POOLS_TVL is set to '1'
(TON, NOT, 1e12, 1.1e15, 1738000000, 0.005, 1.05, 0.001, 0.005, "1", 1e4, 2e3, True),
# TONS-JETTONS pool, no price for jetton, NON_LIQUID_POOLS_TVL is set to '1'
(TON, NOT, 1e12, 1.1e15, 1738000000, 0.005, 1.05, None, 0.005, "1", 1e4, 2e3, True),
# JETTONS-JETTONS pool, usual case, NON_LIQUID_POOLS_TVL is set to '0'
(NOT, STORM, 5e15, 1.1e15, 1738000000, 0.005, 1.05, 0.001, 0.005, "0", None, None, False),
# JETTONS-JETTONS pool, usual case, NON_LIQUID_POOLS_TVL is set to '1'
(NOT, STORM, 5e15, 1.1e15, 1738000000, 0.005, 1.05, 0.001, 0.005, "1", 5.25e4, 1.05e4, False),
# JETTONS-JETTONS pool, no price for jetton, NON_LIQUID_POOLS_TVL is set to '1'
(NOT, STORM, 5e15, 1.1e15, 1738000000, 0.005, 1.05, 0.001, None, "1", None, None, False),
# JETTONS-JETTONS pool, no price for both jettons, NON_LIQUID_POOLS_TVL is set to '1'
(NOT, STORM, 5e15, 1.1e15, 1738000000, 0.005, 1.05, None, None, "1", None, None, False),
# USDT-AquaUSD stable pool, usual case
(USDT, AquaUSD, 2e11, 4e11, 1738000000, 0.005, 1.05, 0.001, 0.005, "0", 6e5, 1.2e5, True),
# ORBIT_STABLES-JETTONS pool, time after ORBIT_HACK_TIMESTAMP
(oUSDT, NOT, 1e10, 1.1e10, 1738000000, 0.005, 1.05, 0.001, 0.005, "0", None, None, False),
# ORBIT_STABLES-JETTONS pool, time befor ORBIT_HACK_TIMESTAMP
(oUSDT, NOT, 1e10, 1.1e10, 1703900000, 0.005, 1.05, 0.001, 0.005, "0", 2e4, 4e3, True),
],
)
def test_estimate_tvl(
monkeypatch,
jetton_left,
jetton_right,
reserves_left,
reserves_right,
last_updated,
usdt_core_price,
tston_core_price,
not_agg_price,
storm_agg_price,
non_liquid_pools_tvl,
res_tvl_usd,
res_tvl_ton,
res_is_liquid,
):
monkeypatch.setenv("NON_LIQUID_POOLS_TVL", non_liquid_pools_tvl)
import parser.parsers.message.swap_volume

importlib.reload(parser.parsers.message.swap_volume)

core_price_mapping = {
(USDT.to_str(False).upper(), last_updated): usdt_core_price,
(tsTON.to_str(False).upper(), last_updated): tston_core_price,
}
agg_price_mapping = {
(NOT.to_str(False).upper(), last_updated): not_agg_price,
(STORM.to_str(False).upper(), last_updated): storm_agg_price,
}
db = Mock()
db.get_core_price.side_effect = lambda jetton, last_updated: core_price_mapping.get((jetton, last_updated), None)
db.get_agg_price.side_effect = lambda jetton, last_updated: agg_price_mapping.get((jetton, last_updated), None)

pool = DexPool(
pool="some_pool_address",
platform="some_platform",
jetton_left=jetton_left,
jetton_right=jetton_right,
reserves_left=reserves_left,
reserves_right=reserves_right,
last_updated=last_updated,
tvl_usd=None,
tvl_ton=None,
is_liquid=True,
)

estimate_tvl(pool, db)

assert pool.tvl_usd == res_tvl_usd
assert pool.tvl_ton == res_tvl_ton
assert pool.is_liquid == res_is_liquid
2 changes: 2 additions & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[pytest]
pythonpath = .

0 comments on commit a937445

Please sign in to comment.