complete rework of pool and token metadata into the address metadata blockdict
This commit is contained in:
@@ -74,6 +74,13 @@ def upgrade() -> None:
|
|||||||
sa.Column('state', sa.Enum('Open', 'Canceled', 'Filled', 'Expired', 'Underfunded', name='swaporderstate'), nullable=False),
|
sa.Column('state', sa.Enum('Open', 'Canceled', 'Filled', 'Expired', 'Underfunded', name='swaporderstate'), nullable=False),
|
||||||
sa.PrimaryKeyConstraint('chain', 'vault', 'order_index')
|
sa.PrimaryKeyConstraint('chain', 'vault', 'order_index')
|
||||||
)
|
)
|
||||||
|
op.create_table('token',
|
||||||
|
sa.Column('chain', dexorder.database.column_types.Blockchain(), nullable=False),
|
||||||
|
sa.Column('address', dexorder.database.column_types.Address(), nullable=False),
|
||||||
|
sa.Column('symbol', sa.String(), nullable=False),
|
||||||
|
sa.Column('decimals', sa.SMALLINT(), nullable=False),
|
||||||
|
sa.PrimaryKeyConstraint('chain', 'address')
|
||||||
|
)
|
||||||
op.create_table('pool',
|
op.create_table('pool',
|
||||||
sa.Column('chain', dexorder.database.column_types.Blockchain(), nullable=False),
|
sa.Column('chain', dexorder.database.column_types.Blockchain(), nullable=False),
|
||||||
sa.Column('address', dexorder.database.column_types.Address(), nullable=False),
|
sa.Column('address', dexorder.database.column_types.Address(), nullable=False),
|
||||||
@@ -81,11 +88,17 @@ def upgrade() -> None:
|
|||||||
sa.Column('base', dexorder.database.column_types.Address(), nullable=False),
|
sa.Column('base', dexorder.database.column_types.Address(), nullable=False),
|
||||||
sa.Column('quote', dexorder.database.column_types.Address(), nullable=False),
|
sa.Column('quote', dexorder.database.column_types.Address(), nullable=False),
|
||||||
sa.Column('fee', sa.Integer(), nullable=False),
|
sa.Column('fee', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('decimals', sa.Integer(), nullable=False),
|
||||||
sa.PrimaryKeyConstraint('chain', 'address')
|
sa.PrimaryKeyConstraint('chain', 'address')
|
||||||
)
|
)
|
||||||
|
op.create_index(op.f('ix_pool_base'), 'pool', ['base'], unique=False)
|
||||||
|
op.create_index(op.f('ix_pool_quote'), 'pool', ['quote'], unique=False)
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
def downgrade() -> None:
|
||||||
|
op.drop_table('token')
|
||||||
|
op.drop_index(op.f('ix_pool_quote'), table_name='pool')
|
||||||
|
op.drop_index(op.f('ix_pool_base'), table_name='pool')
|
||||||
op.drop_table('pool')
|
op.drop_table('pool')
|
||||||
op.drop_table('orderindex')
|
op.drop_table('orderindex')
|
||||||
op.drop_table('seriesset')
|
op.drop_table('seriesset')
|
||||||
|
|||||||
@@ -54,5 +54,4 @@ from .util import async_yield
|
|||||||
from .base.fixed import Fixed2, FixedDecimals, Dec18
|
from .base.fixed import Fixed2, FixedDecimals, Dec18
|
||||||
from .configuration import config
|
from .configuration import config
|
||||||
from .base.account import Account
|
from .base.account import Account
|
||||||
from .base.token import Token, tokens
|
|
||||||
from .database import db
|
from .database import db
|
||||||
|
|||||||
31
src/dexorder/addrmeta.py
Normal file
31
src/dexorder/addrmeta.py
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import logging
|
||||||
|
from typing import TypedDict
|
||||||
|
|
||||||
|
from dexorder import db
|
||||||
|
from dexorder.blockstate import BlockDict
|
||||||
|
from dexorder.database.model import Pool
|
||||||
|
from dexorder.database.model.pool import PoolDict
|
||||||
|
from dexorder.database.model.token import Token, TokenDict
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# address_metadata is a polymorphic BlockDict which maps address keys to a dict of metadata describing the address
|
||||||
|
# used for Tokens and Pools
|
||||||
|
|
||||||
|
|
||||||
|
class AddressMetadata (TypedDict):
|
||||||
|
type: str
|
||||||
|
|
||||||
|
|
||||||
|
def save_addrmeta(address: str, meta: AddressMetadata):
|
||||||
|
if meta['type'] == 'Token':
|
||||||
|
meta: TokenDict
|
||||||
|
db.session.add(Token.load(meta))
|
||||||
|
elif meta['type'] == 'Pool':
|
||||||
|
meta: PoolDict
|
||||||
|
db.session.add(Pool.load(meta))
|
||||||
|
else:
|
||||||
|
log.warning(f'Address {address} had unknown metadata type {meta["type"]}')
|
||||||
|
|
||||||
|
|
||||||
|
address_metadata: BlockDict[str,AddressMetadata] = BlockDict('a', redis=True, db=True, savecb=save_addrmeta)
|
||||||
@@ -1,121 +0,0 @@
|
|||||||
from collections import defaultdict
|
|
||||||
from decimal import Decimal
|
|
||||||
|
|
||||||
from sqlalchemy.orm import Mapped
|
|
||||||
from web3 import Web3
|
|
||||||
|
|
||||||
from dexorder import config, Blockchain, NARG, FixedDecimals, ADDRESS_0, current_w3
|
|
||||||
from dexorder.base.account import current_account
|
|
||||||
from dexorder.blockchain import ByBlockchainDict
|
|
||||||
from dexorder.base.chain import current_chain
|
|
||||||
from dexorder.contract import ContractProxy, abis
|
|
||||||
import dexorder.database.column as col
|
|
||||||
|
|
||||||
|
|
||||||
class Token (ContractProxy, FixedDecimals):
|
|
||||||
chain: Mapped[col.Blockchain]
|
|
||||||
address: Mapped[col.Address]
|
|
||||||
decimals: Mapped[col.Uint8]
|
|
||||||
name: Mapped[str]
|
|
||||||
symbol: Mapped[str]
|
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get(name_or_address:str, *, chain_id=None) -> 'Token':
|
|
||||||
try:
|
|
||||||
return tokens.get(name_or_address, default=NARG, chain_id=chain_id)
|
|
||||||
except KeyError:
|
|
||||||
try:
|
|
||||||
# noinspection PyTypeChecker
|
|
||||||
return Web3.to_checksum_address(name_or_address)
|
|
||||||
except ValueError:
|
|
||||||
raise ValueError(f'Could not resolve token {name_or_address} for chain {current_chain.get().chain_id}')
|
|
||||||
|
|
||||||
def __init__(self, chain_id, address, decimals, symbol, name, *, abi=None):
|
|
||||||
FixedDecimals.__init__(self, decimals)
|
|
||||||
if abi is None:
|
|
||||||
load = 'ERC20'
|
|
||||||
else:
|
|
||||||
load = None
|
|
||||||
abi = abis.get(abi,abi)
|
|
||||||
ContractProxy.__init__(self, address, load, abi=abi)
|
|
||||||
self.chain_id = chain_id
|
|
||||||
# noinspection PyTypeChecker
|
|
||||||
self.address = address
|
|
||||||
self.decimals = decimals
|
|
||||||
self.symbol = symbol
|
|
||||||
self.name = name
|
|
||||||
|
|
||||||
def balance(self, address: str = None) -> int:
|
|
||||||
if address is None:
|
|
||||||
address = current_account.get().address
|
|
||||||
return self.balanceOf(address)
|
|
||||||
|
|
||||||
def balance_dec(self, address: str = None) -> Decimal:
|
|
||||||
return self.dec(self.balance(address))
|
|
||||||
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.symbol
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return f'{self.symbol}({self.address},{self.decimals})'
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
return self.chain_id == other.chain_id and self.address == other.address
|
|
||||||
|
|
||||||
def __hash__(self):
|
|
||||||
return hash((self.chain_id,self.address))
|
|
||||||
|
|
||||||
|
|
||||||
class NativeToken (FixedDecimals):
|
|
||||||
""" Token-like but not a contract. """
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get( chain_id = None) -> 'NativeToken':
|
|
||||||
if chain_id is None:
|
|
||||||
chain_id = current_chain.get().chain_id
|
|
||||||
return _native_tokens[chain_id]
|
|
||||||
|
|
||||||
def __init__(self, chain_id, decimals, symbol, name, *, wrapper_token = None):
|
|
||||||
self.chain_id = chain_id
|
|
||||||
self.address = ADDRESS_0 # todo i think there's actually an address? like 0x11 or something?
|
|
||||||
super().__init__(decimals)
|
|
||||||
self.symbol = symbol
|
|
||||||
self.name = name
|
|
||||||
self._wrapper_token = wrapper_token if wrapper_token is not None else _tokens_by_chain[chain_id]['W'+symbol]
|
|
||||||
|
|
||||||
def balance(self, address: str = None) -> int:
|
|
||||||
if address is None:
|
|
||||||
address = current_account.get()
|
|
||||||
assert current_chain.get().chain_id == self.chain_id
|
|
||||||
return current_w3.get().eth.get_balance(address)
|
|
||||||
|
|
||||||
def balance_dec(self, address: str = None) -> Decimal:
|
|
||||||
return self.dec(self.balance(address))
|
|
||||||
|
|
||||||
@property
|
|
||||||
def wrapper(self) -> Token:
|
|
||||||
return self._wrapper_token
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return self.symbol
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# convert TokenConfigs to Tokens
|
|
||||||
_tokens_by_chain:dict[int,dict[str,Token]] = defaultdict(dict)
|
|
||||||
for _c in config.tokens:
|
|
||||||
_chain_id = Blockchain.get(_c.chain).chain_id
|
|
||||||
_tokens_by_chain[_chain_id][_c.symbol] = Token(_chain_id, _c.address, _c.decimals, _c.symbol, _c.name, abi=_c.abi)
|
|
||||||
|
|
||||||
_native_tokens: dict[int, NativeToken] = {
|
|
||||||
# Ethereum.chain_id: NativeToken(Ethereum.chain_id, 18, 'ETH', 'Ether'), # todo need WETH on Ethereum
|
|
||||||
# Polygon.chain_id: NativeToken(Polygon.chain_id, 18, 'MATIC', 'Polygon'),
|
|
||||||
}
|
|
||||||
|
|
||||||
for _chain_id, _native in _native_tokens.items():
|
|
||||||
# noinspection PyTypeChecker
|
|
||||||
_tokens_by_chain[_chain_id][_native.symbol] = _native
|
|
||||||
|
|
||||||
tokens = ByBlockchainDict[Token](_tokens_by_chain)
|
|
||||||
@@ -41,13 +41,13 @@ async def main():
|
|||||||
config.ohlc_dir = './ohlc'
|
config.ohlc_dir = './ohlc'
|
||||||
ohlcs.dir = config.ohlc_dir
|
ohlcs.dir = config.ohlc_dir
|
||||||
await blockchain.connect()
|
await blockchain.connect()
|
||||||
redis_state = None
|
|
||||||
state = None
|
state = None
|
||||||
if memcache:
|
if not memcache:
|
||||||
await memcache.connect()
|
log.error('backfill requires a memcache server')
|
||||||
redis_state = RedisState([recent_ohlcs]) # NOTE: ONLY the ohlc's are pushed to Redis. We do not want to touch anything else.
|
await memcache.connect()
|
||||||
|
redis_state = RedisState([recent_ohlcs]) # NOTE: ONLY the ohlc's are pushed to Redis. We do not want to touch anything else.
|
||||||
if db:
|
if db:
|
||||||
db.connect(url=config.datadb_url) # our main database is the data db
|
db.connect()
|
||||||
db_state = DbState(BlockData.by_opt('db'))
|
db_state = DbState(BlockData.by_opt('db'))
|
||||||
with db.session:
|
with db.session:
|
||||||
state = db_state.load()
|
state = db_state.load()
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ class Config:
|
|||||||
ws_url: Optional[str] = 'ws://localhost:8545'
|
ws_url: Optional[str] = 'ws://localhost:8545'
|
||||||
rpc_urls: Optional[dict[str,str]] = field(default_factory=dict)
|
rpc_urls: Optional[dict[str,str]] = field(default_factory=dict)
|
||||||
db_url: Optional[str] = 'postgresql://dexorder:redroxed@localhost/dexorder'
|
db_url: Optional[str] = 'postgresql://dexorder:redroxed@localhost/dexorder'
|
||||||
datadb_url: Optional[str] = 'postgresql://dexorder:redroxed@localhost/dexorderdata'
|
|
||||||
ohlc_dir: Optional[str] = None # if empty string or None, then OHLC's are not saved to disk
|
ohlc_dir: Optional[str] = None # if empty string or None, then OHLC's are not saved to disk
|
||||||
dump_sql: bool = False
|
dump_sql: bool = False
|
||||||
redis_url: Optional[str] = 'redis://localhost:6379'
|
redis_url: Optional[str] = 'redis://localhost:6379'
|
||||||
|
|||||||
@@ -99,4 +99,3 @@ class Db:
|
|||||||
raise Exception(f'{url} database version not found')
|
raise Exception(f'{url} database version not found')
|
||||||
|
|
||||||
db = Db()
|
db = Db()
|
||||||
datadb = Db('datadb_url')
|
|
||||||
|
|||||||
@@ -5,3 +5,4 @@ from .series import SeriesSet, SeriesDict
|
|||||||
from .transaction import Transaction, TransactionJob
|
from .transaction import Transaction, TransactionJob
|
||||||
from .orderindex import OrderIndex
|
from .orderindex import OrderIndex
|
||||||
from .pool import Pool
|
from .pool import Pool
|
||||||
|
from .token import Token
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from sqlalchemy import SMALLINT
|
|
||||||
from sqlalchemy.orm import Mapped, mapped_column
|
from sqlalchemy.orm import Mapped, mapped_column
|
||||||
|
|
||||||
from dexorder.database.column import Blockchain
|
from dexorder.database.column import Blockchain
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import logging
|
import logging
|
||||||
|
from typing import TypedDict
|
||||||
|
|
||||||
from sqlalchemy.orm import Mapped, mapped_column
|
from sqlalchemy.orm import Mapped, mapped_column
|
||||||
|
|
||||||
@@ -8,12 +9,40 @@ from dexorder.database.model import Base
|
|||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class PoolDict (TypedDict):
|
||||||
|
type: str
|
||||||
|
chain: int
|
||||||
|
address: str
|
||||||
|
exchange: int
|
||||||
|
base: str
|
||||||
|
quote: str
|
||||||
|
fee: int
|
||||||
|
decimals: int
|
||||||
|
|
||||||
|
|
||||||
class Pool (Base):
|
class Pool (Base):
|
||||||
__tablename__ = 'pool'
|
__tablename__ = 'pool'
|
||||||
|
|
||||||
chain: Mapped[Blockchain] = mapped_column(primary_key=True)
|
chain: Mapped[Blockchain] = mapped_column(primary_key=True)
|
||||||
address: Mapped[Address] = mapped_column(primary_key=True)
|
address: Mapped[Address] = mapped_column(primary_key=True)
|
||||||
exchange: Mapped[Exchange]
|
exchange: Mapped[Exchange]
|
||||||
base: Mapped[Address]
|
base: Mapped[Address] = mapped_column(index=True) # index for searching by token addr
|
||||||
quote: Mapped[Address]
|
quote: Mapped[Address] = mapped_column(index=True) # index for searching by token addr
|
||||||
fee: Mapped[int] # in millionths aka 100ths of a bip
|
fee: Mapped[int] # in millionths aka 100ths of a bip
|
||||||
|
decimals: Mapped[int]
|
||||||
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def load(pool_dict: PoolDict) -> 'Pool':
|
||||||
|
return Pool(chain=Blockchain.get(pool_dict['chain']), address=pool_dict['address'],
|
||||||
|
exchange=Exchange(pool_dict['exchange']),
|
||||||
|
base=pool_dict['base'], quote=pool_dict['quote'],
|
||||||
|
fee=pool_dict['fee'], decimals=pool_dict['decimals'])
|
||||||
|
|
||||||
|
|
||||||
|
def dump(self):
|
||||||
|
return PoolDict(chain=self.chain.chain_id, address=self.address,
|
||||||
|
exchange=self.exchange.value,
|
||||||
|
base=self.base.address, quote=self.quote,
|
||||||
|
fee=self.fee, decimals=self.decimals)
|
||||||
|
|||||||
41
src/dexorder/database/model/token.py
Normal file
41
src/dexorder/database/model/token.py
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import logging
|
||||||
|
from typing import TypedDict
|
||||||
|
|
||||||
|
from sqlalchemy.orm import Mapped, mapped_column
|
||||||
|
|
||||||
|
from dexorder.database.column import Address, Blockchain, Uint8
|
||||||
|
from dexorder.database.model import Base
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# TokenDict is the primary dict we use in-memory, with basic JSON-able types
|
||||||
|
|
||||||
|
class TokenDict (TypedDict):
|
||||||
|
type: str
|
||||||
|
chain: int
|
||||||
|
address: str
|
||||||
|
symbol: str
|
||||||
|
decimals: int
|
||||||
|
|
||||||
|
|
||||||
|
# the database object is primarily write-only so we are able to index queries for pools-by-token from the nodejs server
|
||||||
|
|
||||||
|
class Token (Base):
|
||||||
|
__tablename__ = 'token'
|
||||||
|
|
||||||
|
chain: Mapped[Blockchain] = mapped_column(primary_key=True)
|
||||||
|
address: Mapped[Address] = mapped_column(primary_key=True)
|
||||||
|
symbol: Mapped[str]
|
||||||
|
decimals: Mapped[Uint8]
|
||||||
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def load(token_dict: TokenDict) -> 'Token':
|
||||||
|
return Token(chain=Blockchain.get(token_dict['chain']), address=token_dict['address'],
|
||||||
|
symbol=token_dict['symbol'], decimals=token_dict['decimals'])
|
||||||
|
|
||||||
|
|
||||||
|
def dump(self):
|
||||||
|
return TokenDict(type='Token', chain=self.chain.chain_id, address=self.address,
|
||||||
|
symbol=self.symbol, decimals=self.decimals)
|
||||||
|
|
||||||
@@ -10,10 +10,10 @@ from dexorder.base.chain import current_chain, current_clock, get_block_timestam
|
|||||||
from dexorder.base.order import TrancheExecutionRequest, TrancheKey, ExecutionRequest, new_tranche_execution_request, OrderKey
|
from dexorder.base.order import TrancheExecutionRequest, TrancheKey, ExecutionRequest, new_tranche_execution_request, OrderKey
|
||||||
from dexorder.ohlc import ohlcs, recent_ohlcs
|
from dexorder.ohlc import ohlcs, recent_ohlcs
|
||||||
from dexorder.transaction import submit_transaction_request
|
from dexorder.transaction import submit_transaction_request
|
||||||
from dexorder.pools import uniswap_price, new_pool_prices, pool_prices, Pools
|
from dexorder.pools import uniswap_price, new_pool_prices, pool_prices, get_pool
|
||||||
from dexorder.contract.dexorder import vault_address, VaultContract
|
from dexorder.contract.dexorder import vault_address, VaultContract
|
||||||
from dexorder.contract import ERC20
|
from dexorder.contract import ERC20
|
||||||
from dexorder.data import vault_owners, vault_balances
|
from dexorder.vault_blockdata import vault_owners, vault_balances
|
||||||
from dexorder.database.model.block import current_block
|
from dexorder.database.model.block import current_block
|
||||||
from dexorder.database.model.transaction import TransactionJob
|
from dexorder.database.model.transaction import TransactionJob
|
||||||
from dexorder.base.orderlib import SwapOrderState, Exchange
|
from dexorder.base.orderlib import SwapOrderState, Exchange
|
||||||
@@ -158,7 +158,7 @@ async def handle_uniswap_swap_old(swap: EventData):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
return
|
return
|
||||||
addr = swap['address']
|
addr = swap['address']
|
||||||
price: dec = await uniswap_price(await Pools.get(addr), sqrt_price)
|
price: dec = await uniswap_price(await get_pool(addr), sqrt_price)
|
||||||
pool_prices[addr] = price
|
pool_prices[addr] = price
|
||||||
|
|
||||||
|
|
||||||
@@ -168,11 +168,9 @@ async def handle_uniswap_swap(swap: EventData):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
return
|
return
|
||||||
addr = swap['address']
|
addr = swap['address']
|
||||||
pool = await Pools.get(addr)
|
pool = await get_pool(addr)
|
||||||
if pool is None:
|
if pool['exchange'] != Exchange.UniswapV3.value:
|
||||||
return
|
log.debug(f'Ignoring {Exchange(pool["exchange"])} pool {addr}')
|
||||||
if pool.exchange != Exchange.UniswapV3:
|
|
||||||
log.debug(f'Ignoring {pool.exchange} pool {addr}')
|
|
||||||
return
|
return
|
||||||
price: dec = await uniswap_price(pool, sqrt_price)
|
price: dec = await uniswap_price(pool, sqrt_price)
|
||||||
timestamp = await get_block_timestamp(swap['blockHash'])
|
timestamp = await get_block_timestamp(swap['blockHash'])
|
||||||
|
|||||||
@@ -115,5 +115,5 @@ async def publish_all(pubs: list[tuple[str,str,list[Any]]]):
|
|||||||
r: Pipeline
|
r: Pipeline
|
||||||
io = Emitter(dict(client=r))
|
io = Emitter(dict(client=r))
|
||||||
for room, event, args in pubs:
|
for room, event, args in pubs:
|
||||||
log.debug(f'publishing {room} {event} {args}')
|
# log.debug(f'publishing {room} {event} {args}')
|
||||||
io.To(room).Emit(event, *args)
|
io.To(room).Emit(event, *args)
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ from dexorder import DELETE, db
|
|||||||
from dexorder.base.chain import current_chain
|
from dexorder.base.chain import current_chain
|
||||||
from dexorder.base.order import OrderKey, TrancheKey
|
from dexorder.base.order import OrderKey, TrancheKey
|
||||||
from dexorder.blockstate import BlockDict, BlockSet
|
from dexorder.blockstate import BlockDict, BlockSet
|
||||||
from dexorder.data import vault_owners
|
from dexorder.vault_blockdata import vault_owners
|
||||||
from dexorder.database.model.orderindex import OrderIndex
|
from dexorder.database.model.orderindex import OrderIndex
|
||||||
from dexorder.base.orderlib import SwapOrderStatus, SwapOrderState
|
from dexorder.base.orderlib import SwapOrderStatus, SwapOrderState
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ from .orderstate import Order
|
|||||||
from .. import dec
|
from .. import dec
|
||||||
from ..base.chain import current_clock
|
from ..base.chain import current_clock
|
||||||
from ..base.order import OrderKey, TrancheKey, ExecutionRequest
|
from ..base.order import OrderKey, TrancheKey, ExecutionRequest
|
||||||
from ..pools import ensure_pool_price, Pools, pool_decimals, pool_prices
|
from ..pools import ensure_pool_price, pool_prices, get_pool
|
||||||
from ..routing import pool_address
|
from ..routing import pool_address
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
@@ -36,7 +36,7 @@ async def activate_order(order: Order):
|
|||||||
Call this to enable triggers on an order which is already in the state.
|
Call this to enable triggers on an order which is already in the state.
|
||||||
"""
|
"""
|
||||||
address = pool_address(order.status.order)
|
address = pool_address(order.status.order)
|
||||||
pool = await Pools.get(address)
|
pool = await get_pool(address)
|
||||||
await ensure_pool_price(pool)
|
await ensure_pool_price(pool)
|
||||||
triggers = OrderTriggers(order)
|
triggers = OrderTriggers(order)
|
||||||
if triggers.closed:
|
if triggers.closed:
|
||||||
@@ -161,14 +161,14 @@ class TrancheTrigger:
|
|||||||
log.debug(f'price trigger ignored because trigger status is {self.status}')
|
log.debug(f'price trigger ignored because trigger status is {self.status}')
|
||||||
return
|
return
|
||||||
log.debug(f'price trigger {cur}')
|
log.debug(f'price trigger {cur}')
|
||||||
|
addr = pool_address(self.order.order)
|
||||||
|
pool = await get_pool(addr)
|
||||||
if cur is None and self.has_line_constraint:
|
if cur is None and self.has_line_constraint:
|
||||||
await ensure_pool_price(self.order.pool_address)
|
await ensure_pool_price(pool)
|
||||||
cur = pool_prices[self.order.pool_address]
|
cur = pool_prices[addr]
|
||||||
if cur is not None:
|
if cur is not None:
|
||||||
if self.pool_price_multiplier is None:
|
if self.pool_price_multiplier is None:
|
||||||
pool = await Pools.get(pool_address(self.order.order))
|
self.pool_price_multiplier = dec(10) ** dec(-pool['decimals'])
|
||||||
pool_dec = await pool_decimals(pool)
|
|
||||||
self.pool_price_multiplier = dec(10) ** dec(-pool_dec)
|
|
||||||
log.debug(f'adjusted cur price from {cur} => {cur*self.pool_price_multiplier}')
|
log.debug(f'adjusted cur price from {cur} => {cur*self.pool_price_multiplier}')
|
||||||
cur *= self.pool_price_multiplier
|
cur *= self.pool_price_multiplier
|
||||||
if cur is None or not self.has_line_constraint or all(await asyncio.gather(
|
if cur is None or not self.has_line_constraint or all(await asyncio.gather(
|
||||||
|
|||||||
@@ -1,55 +1,55 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from web3.exceptions import ContractLogicError
|
from web3.exceptions import ContractLogicError
|
||||||
|
|
||||||
from dexorder import dec, db, ADDRESS_0
|
from dexorder import dec, ADDRESS_0
|
||||||
|
from dexorder.addrmeta import address_metadata
|
||||||
from dexorder.base.chain import current_chain
|
from dexorder.base.chain import current_chain
|
||||||
from dexorder.base.orderlib import Exchange
|
from dexorder.base.orderlib import Exchange
|
||||||
from dexorder.blockstate import BlockDict
|
from dexorder.blockstate import BlockDict
|
||||||
from dexorder.blockstate.blockdata import K, V
|
from dexorder.blockstate.blockdata import K, V
|
||||||
from dexorder.contract.decimals import token_decimals
|
from dexorder.database.model.pool import PoolDict
|
||||||
from dexorder.database.model.pool import Pool
|
from dexorder.tokens import get_token
|
||||||
from dexorder.uniswap import UniswapV3Pool, uniswapV3_pool_address
|
from dexorder.uniswap import UniswapV3Pool, uniswapV3_pool_address
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
class Pools:
|
|
||||||
instances: dict[tuple[int,str], Pool] = {}
|
|
||||||
|
|
||||||
@staticmethod
|
async def get_pool(address: str) -> PoolDict:
|
||||||
async def get(address, *, chain=None) -> Optional[Pool]:
|
try:
|
||||||
if not chain:
|
return address_metadata[address]
|
||||||
chain = current_chain.get()
|
except KeyError:
|
||||||
key = (chain, address)
|
result = address_metadata[address] = await load_pool(address)
|
||||||
found = Pools.instances.get(key)
|
return result
|
||||||
if not found:
|
|
||||||
found = db.session.get(Pool, key)
|
|
||||||
if not found:
|
async def load_pool(address: str) -> PoolDict:
|
||||||
# todo other exchanges
|
found = None
|
||||||
try:
|
chain_id = current_chain.get().chain_id
|
||||||
v3 = UniswapV3Pool(address)
|
# todo other exchanges
|
||||||
t0, t1, fee = await asyncio.gather(v3.token0(), v3.token1(), v3.fee())
|
try:
|
||||||
if uniswapV3_pool_address(t0, t1, fee) == address: # VALIDATE don't just trust
|
v3 = UniswapV3Pool(address)
|
||||||
log.debug(f'new UniswapV3 pool at {address}')
|
t0, t1, fee = await asyncio.gather(v3.token0(), v3.token1(), v3.fee())
|
||||||
found = Pool(chain=chain, address=address, exchange=Exchange.UniswapV3, base=t0, quote=t1, fee=fee)
|
if uniswapV3_pool_address(t0, t1, fee) == address: # VALIDATE don't just trust that it's a Uniswap pool
|
||||||
db.session.add(found)
|
log.debug(f'new UniswapV3 pool at {address}')
|
||||||
else: # NOT a genuine Uniswap V3 pool if the address test doesn't pass
|
token0, token1 = await asyncio.gather(get_token(t0), get_token(t1))
|
||||||
log.debug(f'new Unknown pool at {address}')
|
decimals = token0['decimals'] - token1['decimals']
|
||||||
found = Pool(chain=chain, address=address, exchange=Exchange.Unknown, base=ADDRESS_0, quote=ADDRESS_0, fee=0)
|
found = PoolDict(type='Pool', chain=chain_id, address=address, exchange=Exchange.UniswapV3.value,
|
||||||
except ContractLogicError:
|
base=t0, quote=t1, fee=fee, decimals=decimals)
|
||||||
log.debug(f'new Unknown pool at {address}')
|
else: # NOT a genuine Uniswap V3 pool if the address test doesn't pass
|
||||||
found = Pool(chain=chain, address=address, exchange=Exchange.Unknown, base=ADDRESS_0, quote=ADDRESS_0, fee=0)
|
log.debug(f'new Unknown pool at {address}')
|
||||||
except ValueError as v:
|
except ContractLogicError:
|
||||||
if v.args[0].get('code') == -32000:
|
log.debug(f'new Unknown pool at {address}')
|
||||||
log.debug(f'new Unknown pool at {address}')
|
except ValueError as v:
|
||||||
found = Pool(chain=chain, address=address, exchange=Exchange.Unknown, base=ADDRESS_0, quote=ADDRESS_0, fee=0)
|
if v.args[0].get('code') == -32000:
|
||||||
else:
|
log.debug(f'new Unknown pool at {address}')
|
||||||
raise
|
else:
|
||||||
db.session.add(found)
|
raise
|
||||||
Pools.instances[key] = found
|
if found is None:
|
||||||
return None if found.exchange == Exchange.Unknown else found
|
found = PoolDict(type='Pool', chain=chain_id, address=address, exchange=Exchange.Unknown.value,
|
||||||
|
base=ADDRESS_0, quote=ADDRESS_0, fee=0, decimals=0)
|
||||||
|
return found
|
||||||
|
|
||||||
|
|
||||||
class PoolPrices (BlockDict[str, dec]):
|
class PoolPrices (BlockDict[str, dec]):
|
||||||
@@ -67,38 +67,22 @@ new_pool_prices: dict[str, dec] = {} # tracks which prices were set during the c
|
|||||||
pool_prices: PoolPrices = PoolPrices('p', db=True, redis=True, pub=pub_pool_price, value2str=lambda d: f'{d:f}', str2value=dec)
|
pool_prices: PoolPrices = PoolPrices('p', db=True, redis=True, pub=pub_pool_price, value2str=lambda d: f'{d:f}', str2value=dec)
|
||||||
|
|
||||||
|
|
||||||
|
async def uniswap_price(pool: PoolDict, sqrt_price=None) -> dec:
|
||||||
async def uniswap_price(pool, sqrt_price=None) -> dec:
|
|
||||||
if sqrt_price is None:
|
if sqrt_price is None:
|
||||||
sqrt_price = (await UniswapV3Pool(pool.address).slot0())[0]
|
sqrt_price = (await UniswapV3Pool(pool['address']).slot0())[0]
|
||||||
pool_dec = await pool_decimals(pool)
|
pool_dec = pool['decimals']
|
||||||
price = dec(sqrt_price*sqrt_price) / 2 ** (96 * 2)
|
price = dec(sqrt_price*sqrt_price) / 2 ** (96 * 2)
|
||||||
result = price * dec(10) ** dec(pool_dec)
|
result = price * dec(10) ** dec(pool_dec)
|
||||||
log.debug(f'pool sqrtX96 {sqrt_price} with {pool_dec} decimals = {result}')
|
log.debug(f'pool sqrtX96 {sqrt_price} with {pool_dec} decimals = {result}')
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
async def ensure_pool_price(pool):
|
async def ensure_pool_price(pool: PoolDict):
|
||||||
# todo refactor to accept a Pool and switch on exchange type
|
addr = pool['address']
|
||||||
if pool not in pool_prices:
|
if addr not in pool_prices:
|
||||||
log.debug(f'querying price for pool {pool.address}')
|
log.debug(f'querying price for pool {addr}')
|
||||||
if pool.exchange == Exchange.UniswapV3:
|
if pool['exchange'] == Exchange.UniswapV3.value:
|
||||||
pool_prices[pool.address] = await uniswap_price(pool)
|
pool_prices[addr] = await uniswap_price(pool)
|
||||||
else:
|
else:
|
||||||
raise ValueError
|
raise ValueError(f'Unsupported exchange type {pool["exchange"]}')
|
||||||
|
# todo other exchanges
|
||||||
|
|
||||||
_pool_decimals = {}
|
|
||||||
async def pool_decimals(pool):
|
|
||||||
if pool.exchange != Exchange.UniswapV3:
|
|
||||||
raise ValueError
|
|
||||||
found = _pool_decimals.get(pool)
|
|
||||||
if found is None:
|
|
||||||
key = f'pd|{pool.address}'
|
|
||||||
try:
|
|
||||||
found = db.kv[key]
|
|
||||||
except KeyError:
|
|
||||||
decimals0 = await token_decimals(pool.base)
|
|
||||||
decimals1 = await token_decimals(pool.quote)
|
|
||||||
found = _pool_decimals[pool] = db.kv[key] = decimals0 - decimals1
|
|
||||||
return found
|
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ from dexorder.database.model import Block
|
|||||||
from dexorder.database.model.block import current_block, latest_block
|
from dexorder.database.model.block import current_block, latest_block
|
||||||
from dexorder.util import hexstr, topic
|
from dexorder.util import hexstr, topic
|
||||||
from dexorder.util.async_util import maywait, Maywaitable
|
from dexorder.util.async_util import maywait, Maywaitable
|
||||||
from dexorder.util.shutdown import fatal
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -343,7 +342,9 @@ class BlockStateRunner:
|
|||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
if e.args[0].get('code') == -32602:
|
if e.args[0].get('code') == -32602:
|
||||||
# too many logs were returned in the batch, so decrease the batch size.
|
# too many logs were returned in the batch, so decrease the batch size.
|
||||||
fatal(f'Decrease batch size for {chain}')
|
# fatal(f'Decrease batch size for {chain}')
|
||||||
|
log.warning(f'Decrease batch size for {chain}')
|
||||||
|
return
|
||||||
raise
|
raise
|
||||||
for log_event in log_events:
|
for log_event in log_events:
|
||||||
try:
|
try:
|
||||||
|
|||||||
33
src/dexorder/tokens.py
Normal file
33
src/dexorder/tokens.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
from eth_abi.exceptions import InsufficientDataBytes
|
||||||
|
from web3.exceptions import ContractLogicError, BadFunctionCallOutput
|
||||||
|
|
||||||
|
from dexorder.addrmeta import address_metadata
|
||||||
|
from dexorder.base.chain import current_chain
|
||||||
|
from dexorder.contract import ERC20
|
||||||
|
from dexorder.database.model.token import TokenDict
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
async def get_token(address):
|
||||||
|
try:
|
||||||
|
return address_metadata[address]
|
||||||
|
except KeyError:
|
||||||
|
result = address_metadata[address] = await load_token(address)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
async def load_token(address: str) -> TokenDict:
|
||||||
|
contract = ERC20(address)
|
||||||
|
symbolProm = contract.symbol()
|
||||||
|
try:
|
||||||
|
decimals = await contract.decimals()
|
||||||
|
except (InsufficientDataBytes, ContractLogicError, BadFunctionCallOutput):
|
||||||
|
log.warning(f'token {address} has no decimals()')
|
||||||
|
decimals = 0
|
||||||
|
symbol = await symbolProm
|
||||||
|
log.debug(f'new token {symbol} {address}')
|
||||||
|
return TokenDict(type='Token', chain=current_chain.get().chain_id, address=address,
|
||||||
|
symbol=symbol, decimals=decimals)
|
||||||
Reference in New Issue
Block a user