orderindex
This commit is contained in:
@@ -66,13 +66,22 @@ def upgrade() -> None:
|
||||
sa.ForeignKeyConstraint(['job_id'], ['transactionjob.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_table('orderindex',
|
||||
sa.Column('chain', dexorder.database.column_types.Blockchain(), nullable=False),
|
||||
sa.Column('vault', sa.String(), nullable=False),
|
||||
sa.Column('order_index', sa.Integer(), nullable=False),
|
||||
sa.Column('state', sa.Enum('Open', 'Canceled', 'Filled', 'Expired', 'Underfunded', name='swaporderstate'), nullable=False),
|
||||
sa.PrimaryKeyConstraint('chain', 'vault', 'order_index')
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_table('orderindex')
|
||||
op.drop_table('seriesset')
|
||||
op.drop_table('seriesdict')
|
||||
op.drop_table('keyvalue')
|
||||
op.drop_table('block')
|
||||
op.drop_table('tx')
|
||||
op.drop_table('transactionjob')
|
||||
op.execute('drop type swaporderstate') # enum type
|
||||
op.execute('drop type transactionjobstate') # enum type
|
||||
@@ -15,7 +15,7 @@ class OrderKey:
|
||||
return OrderKey(vault, int(order_index))
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.vault}|{self.order_index}'
|
||||
return f'{self.vault}|{self.order_index:05}'
|
||||
|
||||
|
||||
@dataclass(frozen=True, eq=True)
|
||||
|
||||
@@ -6,8 +6,7 @@ from enum import Enum
|
||||
from typing import Optional, Union
|
||||
|
||||
from dexorder import dec
|
||||
from dexorder.uniswap import uniswapV3_pool_address, uniswap_price
|
||||
from dexorder.contract import abi_decoder, abi_encoder
|
||||
from dexorder.util.abiencode import abi_decoder, abi_encoder
|
||||
from dexorder.util import hexbytes
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -22,6 +21,7 @@ class SwapOrderState (Enum):
|
||||
Canceled = 1
|
||||
Filled = 2
|
||||
Expired = 3
|
||||
Underfunded = 14 # todo this is a pseudostate...
|
||||
|
||||
class Exchange (Enum):
|
||||
UniswapV2 = 0
|
||||
@@ -58,12 +58,6 @@ class SwapOrder:
|
||||
return (self.tokenIn, self.tokenOut, self.route.dump(), str(self.amount), self.amountIsInput,
|
||||
self.outputDirectlyToOwner, self.chainOrder, [t.dump() for t in self.tranches])
|
||||
|
||||
@property
|
||||
def pool_address(self):
|
||||
if self.route.exchange == Exchange.UniswapV3:
|
||||
return uniswapV3_pool_address( self.tokenIn, self.tokenOut, self.route.fee )
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
@dataclass
|
||||
class SwapStatus:
|
||||
@@ -184,13 +178,6 @@ class LineConstraint (Constraint):
|
||||
def dump(self):
|
||||
return self._dump(LineConstraint.TYPES, (self.isAbove, self.isRatio, self.time, self.valueSqrtX96, self.slopeSqrtX96))
|
||||
|
||||
async def passes(self, pool_addr: str, price: dec) -> bool:
|
||||
limit = await uniswap_price(pool_addr, self.valueSqrtX96)
|
||||
# todo slopes
|
||||
# todo ratios
|
||||
# prices AT the limit get zero volume, so we only trigger on >, not >=
|
||||
return self.isAbove and price > limit or not self.isAbove and price < limit
|
||||
|
||||
|
||||
@dataclass
|
||||
class Tranche:
|
||||
@@ -29,6 +29,7 @@ class BlockData:
|
||||
series2str=None, series2key=None, # defaults to key2str and str2key
|
||||
key2str=util_key2str, str2key=util_str2key,
|
||||
value2str=json.dumps, str2value=json.loads, # serialize/deserialize value to something JSON-able
|
||||
savecb:Callable[[Any,Any],None]=None, # callback(key, value) where value may be DELETE
|
||||
**opts):
|
||||
assert series not in BlockData.registry
|
||||
BlockData.registry[series] = self
|
||||
@@ -41,6 +42,7 @@ class BlockData:
|
||||
self.series2key = series2key or self.str2key
|
||||
self.value2str = value2str
|
||||
self.str2value = str2value
|
||||
self.savecb = savecb
|
||||
self.lazy_getitem = None
|
||||
|
||||
@property
|
||||
|
||||
@@ -67,6 +67,9 @@ class DbState(SeriesCollection):
|
||||
found.value = value
|
||||
else:
|
||||
raise NotImplementedError
|
||||
if d.savecb:
|
||||
d.savecb(diff.key, diff.value)
|
||||
# save root block info
|
||||
db.kv[f'root_block|{root_block.chain}'] = [root_block.height, root_block.hash]
|
||||
|
||||
# noinspection PyShadowingBuiltins
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
import json
|
||||
|
||||
from eth_abi.codec import ABIDecoder, ABIEncoder
|
||||
from eth_abi.registry import registry as default_registry
|
||||
|
||||
from .. import current_w3 as _current_w3
|
||||
from .abi import abis
|
||||
from .contract_proxy import ContractProxy
|
||||
|
||||
abi_decoder = ABIDecoder(default_registry)
|
||||
abi_encoder = ABIEncoder(default_registry)
|
||||
|
||||
|
||||
def get_contract_data(name):
|
||||
with open(f'../contract/out/{name}.sol/{name}.json', 'rt') as file:
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
from .base import Base
|
||||
from .kv import KeyValue
|
||||
from .block import Block
|
||||
from .series import SeriesSet, SeriesDict
|
||||
from .transaction import Transaction, TransactionJob
|
||||
from .orderindex import OrderIndex
|
||||
|
||||
23
src/dexorder/database/model/orderindex.py
Normal file
23
src/dexorder/database/model/orderindex.py
Normal file
@@ -0,0 +1,23 @@
|
||||
import logging
|
||||
|
||||
from sqlalchemy import SMALLINT
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from dexorder.database.column import Blockchain
|
||||
from dexorder.database.model import Base
|
||||
from dexorder.base.orderlib import SwapOrderState
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
class OrderIndex (Base):
|
||||
chain: Mapped[Blockchain] = mapped_column(primary_key=True)
|
||||
vault: Mapped[str] = mapped_column(primary_key=True)
|
||||
order_index: Mapped[int] = mapped_column(primary_key=True)
|
||||
state: Mapped[SwapOrderState]
|
||||
|
||||
|
||||
# class FillIndex (Base):
|
||||
# chain: Mapped[Blockchain] = mapped_column(index=True)
|
||||
# vault: Mapped[str] = mapped_column(index=True)
|
||||
# order_index: Mapped[SMALLINT] = mapped_column(index=True)
|
||||
# tranche_index:
|
||||
@@ -15,7 +15,7 @@ from dexorder.contract import get_contract_event, ERC20
|
||||
from dexorder.data import pool_prices, vault_owners, vault_balances, new_pool_prices
|
||||
from dexorder.database.model.block import current_block
|
||||
from dexorder.database.model.transaction import TransactionJob
|
||||
from dexorder.order.orderlib import SwapOrderStatus, SwapOrderState
|
||||
from dexorder.base.orderlib import SwapOrderStatus, SwapOrderState
|
||||
from dexorder.order.orderstate import Order
|
||||
from dexorder.order.triggers import OrderTriggers, price_triggers, time_triggers, \
|
||||
unconstrained_price_triggers, execution_requests, inflight_execution_requests, TrancheStatus, active_tranches, new_price_triggers, activate_order
|
||||
|
||||
@@ -3,12 +3,15 @@ import logging
|
||||
from dataclasses import dataclass
|
||||
from typing import overload
|
||||
|
||||
from dexorder import DELETE
|
||||
from dexorder import DELETE, db
|
||||
from dexorder.base.chain import current_chain
|
||||
from dexorder.base.order import OrderKey, TrancheKey
|
||||
from dexorder.blockstate import BlockDict, BlockSet
|
||||
from dexorder.data import vault_owners
|
||||
from dexorder.order.orderlib import SwapOrderStatus, SwapOrderState
|
||||
from dexorder.database.model.orderindex import OrderIndex
|
||||
from dexorder.base.orderlib import SwapOrderStatus, SwapOrderState
|
||||
|
||||
from dexorder.routing import pool_address
|
||||
from dexorder.util import json
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -96,7 +99,7 @@ class Order:
|
||||
assert key not in Order.instances
|
||||
self.key = key
|
||||
self.status: SwapOrderStatus = Order.order_statuses[key].copy()
|
||||
self.pool_address: str = self.status.order.pool_address
|
||||
self.pool_address: str = pool_address(self.status.order)
|
||||
self.tranche_keys = [TrancheKey(key.vault, key.order_index, i) for i in range(len(self.status.trancheFilledIn))]
|
||||
# flattenings of various static data
|
||||
self.order = self.status.order
|
||||
@@ -203,6 +206,22 @@ class Order:
|
||||
log.warning(f'No vault owner for {k}')
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def save_order_index(key, status):
|
||||
key: OrderKey
|
||||
status: SwapOrderStatus
|
||||
sess = db.session
|
||||
oi = sess.get(OrderIndex, (current_chain.get(), key.vault, key.order_index))
|
||||
if status is DELETE:
|
||||
if oi:
|
||||
oi.delete()
|
||||
else:
|
||||
if oi:
|
||||
oi.state = status.state
|
||||
else:
|
||||
oi = OrderIndex(chain=current_chain.get(), vault=key.vault, order_index=key.order_index, state=status.state)
|
||||
sess.add(oi)
|
||||
|
||||
|
||||
# ORDER STATE
|
||||
# various blockstate fields hold different aspects of an order's state.
|
||||
@@ -212,7 +231,7 @@ class Order:
|
||||
# it holds "everything" about an order in the canonical format specified by the contract orderlib, except that
|
||||
# the filled amount fields for active orders are maintained in the order_remainings and tranche_remainings series.
|
||||
order_statuses: BlockDict[OrderKey, SwapOrderStatus] = BlockDict(
|
||||
'o', db='lazy', redis=True, pub=pub_order_status,
|
||||
'o', db='lazy', redis=True, pub=pub_order_status, savecb=save_order_index,
|
||||
str2key=OrderKey.str2key, value2str=lambda v: json.dumps(v.dump()), str2value=lambda s:SwapOrderStatus.load(json.loads(s)),
|
||||
)
|
||||
|
||||
|
||||
@@ -5,13 +5,15 @@ from enum import Enum, auto
|
||||
from typing import Callable, Optional, Union, Awaitable
|
||||
|
||||
from dexorder.blockstate import BlockSet, BlockDict
|
||||
from .orderlib import TimeConstraint, LineConstraint, ConstraintMode, SwapOrderState, PriceProof
|
||||
from dexorder.base.orderlib import TimeConstraint, LineConstraint, ConstraintMode, SwapOrderState, PriceProof
|
||||
from dexorder.util import defaultdictk
|
||||
from .orderstate import Order
|
||||
from .. import dec
|
||||
from ..base.order import OrderKey, TrancheKey, ExecutionRequest
|
||||
from ..data import ensure_pool_price
|
||||
from ..database.model.block import current_block
|
||||
from ..routing import pool_address
|
||||
from ..uniswap import uniswap_price
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -30,11 +32,11 @@ execution_requests:BlockDict[TrancheKey, ExecutionRequest] = BlockDict('e') # g
|
||||
inflight_execution_requests:BlockDict[TrancheKey, int] = BlockDict('ei') # value is block height when the request was sent
|
||||
|
||||
|
||||
async def activate_order(order):
|
||||
async def activate_order(order: Order):
|
||||
"""
|
||||
Call this to enable triggers on an order which is already in the state.
|
||||
"""
|
||||
await ensure_pool_price(order.pool_address)
|
||||
await ensure_pool_price(pool_address(order.status.order))
|
||||
triggers = OrderTriggers(order)
|
||||
if triggers.closed:
|
||||
log.debug(f'order {order.key} was immediately closed')
|
||||
@@ -47,6 +49,15 @@ def intersect_ranges( a_low, a_high, b_low, b_high):
|
||||
low, high = None, None
|
||||
return low, high
|
||||
|
||||
|
||||
async def line_passes(lc: LineConstraint, pool_addr: str, price: dec) -> bool:
|
||||
limit = await uniswap_price(pool_addr, lc.valueSqrtX96)
|
||||
# todo slopes
|
||||
# todo ratios
|
||||
# prices AT the limit get zero volume, so we only trigger on >, not >=
|
||||
return lc.isAbove and price > limit or not lc.isAbove and price < limit
|
||||
|
||||
|
||||
class TrancheStatus (Enum):
|
||||
Early = auto() # first time trigger hasnt happened yet
|
||||
Pricing = auto() # we are inside the time window and checking prices
|
||||
@@ -137,7 +148,7 @@ class TrancheTrigger:
|
||||
if self.closed:
|
||||
log.debug(f'price trigger ignored because trigger status is {self.status}')
|
||||
return
|
||||
if not self.line_constraints or all(await asyncio.gather(*[pc.passes(self.order.pool_address, cur) for pc in self.line_constraints])):
|
||||
if not self.line_constraints or all(await asyncio.gather(*[line_passes(lc, self.order.pool_address, cur) for lc in self.line_constraints])):
|
||||
active_tranches[self.tk] = None # or PriceProof(...)
|
||||
|
||||
def fill(self, _amount_in, _amount_out ):
|
||||
|
||||
14
src/dexorder/routing.py
Normal file
14
src/dexorder/routing.py
Normal file
@@ -0,0 +1,14 @@
|
||||
import logging
|
||||
|
||||
from dexorder.base.orderlib import Exchange, SwapOrder
|
||||
from dexorder.uniswap import uniswapV3_pool_address
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def pool_address(so: SwapOrder):
|
||||
if so.route.exchange == Exchange.UniswapV3:
|
||||
return uniswapV3_pool_address(so.tokenIn, so.tokenOut, so.route.fee)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
@@ -4,7 +4,8 @@ from eth_utils import keccak, to_bytes, to_checksum_address
|
||||
from dexorder import dec, db
|
||||
from dexorder.base.chain import Ethereum, Polygon, Goerli, Mumbai, Arbitrum, Mock
|
||||
from dexorder.blockchain import ByBlockchainDict
|
||||
from dexorder.contract import abi_encoder, ContractProxy
|
||||
from dexorder.contract import ContractProxy
|
||||
from dexorder.util.abiencode import abi_encoder
|
||||
from dexorder.contract.decimals import token_decimals
|
||||
from dexorder.util import hexbytes
|
||||
|
||||
@@ -50,13 +51,16 @@ def uniswap_pool_address(factory_addr: str, addr_a: str, addr_b: str, fee: int)
|
||||
async def uniswap_price(addr, sqrt_price) -> dec:
|
||||
price = dec(sqrt_price*sqrt_price) / 2 ** (96 * 2)
|
||||
decimals = await pool_decimals(addr)
|
||||
return price * dec(10) ** dec(decimals)
|
||||
result = price * dec(10) ** dec(decimals)
|
||||
log.debug(f'pool sqrtX96 {sqrt_price} with {decimals} decimals = {result}')
|
||||
return result
|
||||
|
||||
|
||||
async def pool_decimals(addr):
|
||||
key = f'pd|{addr}'
|
||||
try:
|
||||
return db.kv[key]
|
||||
decimals = db.kv[key]
|
||||
log.debug('got decimals from db')
|
||||
except KeyError:
|
||||
pool = UniswapV3Pool(addr)
|
||||
token0 = await pool.token0()
|
||||
@@ -65,7 +69,9 @@ async def pool_decimals(addr):
|
||||
decimals1 = await token_decimals(token1)
|
||||
decimals = decimals0 - decimals1
|
||||
db.kv[key] = decimals
|
||||
return decimals
|
||||
log.debug(f'pool decimals: {decimals0} - {decimals1}')
|
||||
log.debug(f'pool decimals {addr} {decimals}')
|
||||
return decimals
|
||||
|
||||
|
||||
class _UniswapContracts (ByBlockchainDict[ContractProxy]):
|
||||
|
||||
8
src/dexorder/util/abiencode.py
Normal file
8
src/dexorder/util/abiencode.py
Normal file
@@ -0,0 +1,8 @@
|
||||
import logging
|
||||
|
||||
from eth_abi.codec import ABIDecoder, ABIEncoder
|
||||
from eth_abi.registry import registry as default_registry
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
abi_decoder = ABIDecoder(default_registry)
|
||||
abi_encoder = ABIEncoder(default_registry)
|
||||
Reference in New Issue
Block a user