Merge remote-tracking branch 'origin/master'

This commit is contained in:
Tim Olson
2023-10-10 16:01:01 -04:00
7 changed files with 134 additions and 17 deletions

View File

@@ -43,6 +43,6 @@ Goerli = Blockchain(5, 'Goerli')
Polygon = Blockchain(137, 'Polygon') # POS not zkEVM Polygon = Blockchain(137, 'Polygon') # POS not zkEVM
Mumbai = Blockchain(80001, 'Mumbai') Mumbai = Blockchain(80001, 'Mumbai')
BSC = Blockchain(56, 'BSC') BSC = Blockchain(56, 'BSC')
Arbitrum = Blockchain(42161, 'Arbitrum', 10) Arbitrum = Blockchain(42161, 'Arbitrum', 10, batch_size=1000) # todo configure batch size... does it depend on log count? :(
current_chain = ContextVar[Blockchain]('current_chain') current_chain = ContextVar[Blockchain]('current_chain')

View File

@@ -8,7 +8,7 @@ from dexorder.blockchain.util import vault_address, get_contract_event, get_fact
from dexorder.contract import VaultContract from dexorder.contract import VaultContract
from dexorder.data import pool_prices, vault_addresses, vault_tokens, underfunded_vaults from dexorder.data import pool_prices, vault_addresses, vault_tokens, underfunded_vaults
from dexorder.database.model.block import current_block from dexorder.database.model.block import current_block
from dexorder.orderlib.orders import SwapOrderStatus from dexorder.orderlib.orderlib import SwapOrderStatus
log = logging.getLogger(__name__) log = logging.getLogger(__name__)

View File

@@ -0,0 +1,4 @@
def order_key(vault:str, ):
return f'{vault}'

View File

@@ -62,6 +62,8 @@ class SwapOrderStatus:
ocoGroup: Optional[int] ocoGroup: Optional[int]
filledIn: int filledIn: int
filledOut: int filledOut: int
trancheFilledIn: list[int]
trancheFilledOut: list[int]
@staticmethod @staticmethod
def load(obj): def load(obj):
@@ -71,10 +73,12 @@ class SwapOrderStatus:
ocoGroup = None if obj[3] == NO_OCO else obj[3] ocoGroup = None if obj[3] == NO_OCO else obj[3]
filledIn = obj[4] filledIn = obj[4]
filledOut = obj[5] filledOut = obj[5]
return SwapOrderStatus(order, state, start, ocoGroup, filledIn, filledOut) trancheFilledIn = obj[6]
trancheFilledOut = obj[7]
return SwapOrderStatus(order, state, start, ocoGroup, filledIn, filledOut, trancheFilledIn, trancheFilledOut)
def dump(self): def dump(self):
return self.order.dump(), self.state.value, self.start, self.ocoGroup, self.filledIn, self.filledOut return self.order.dump(), self.state.value, self.start, self.ocoGroup, self.filledIn, self.filledOut, self.trancheFilledIn, self.trancheFilledOut
NO_OCO = 18446744073709551615 # max uint64 NO_OCO = 18446744073709551615 # max uint64
@@ -88,6 +92,8 @@ class ConstraintMode (Enum):
@dataclass @dataclass
class Constraint (ABC): class Constraint (ABC):
mode: ConstraintMode
@staticmethod @staticmethod
def load(obj): def load(obj):
mode = ConstraintMode(obj[0]) mode = ConstraintMode(obj[0])
@@ -99,9 +105,8 @@ class Constraint (ABC):
@abstractmethod @abstractmethod
def dump(self): ... def dump(self): ...
@staticmethod def _dump(self, types, values):
def _dump(mode, types, values): return self.mode, abi_encoder.encode(types, values)
return mode, abi_encoder.encode(types, values)
@dataclass @dataclass
class PriceConstraint (Constraint): class PriceConstraint (Constraint):
@@ -109,8 +114,14 @@ class PriceConstraint (Constraint):
isRatio: bool isRatio: bool
valueSqrtX96: int valueSqrtX96: int
TYPES = 'bool','bool','uint160'
def load(self, obj):
isAbove, isRatio, valueSqrtX96 = abi_decoder.decode(PriceConstraint.TYPES, obj)
return PriceConstraint(ConstraintMode.Limit, isAbove, isRatio, valueSqrtX96)
def dump(self): def dump(self):
return Constraint._dump(ConstraintMode.Limit, ('bool','bool','uint160'), (self.isAbove, self.isRatio, self.valueSqrtX96)) return self._dump(PriceConstraint.TYPES, (self.isAbove, self.isRatio, self.valueSqrtX96))
@dataclass @dataclass
@@ -147,7 +158,7 @@ class TimeConstraint (Constraint):
@staticmethod @staticmethod
def load(obj: bytes): def load(obj: bytes):
earliest_mode, earliest_time, latest_mode, latest_time = abi_decoder.decode(TimeConstraint.TYPES, obj) earliest_mode, earliest_time, latest_mode, latest_time = abi_decoder.decode(TimeConstraint.TYPES, obj)
return TimeConstraint(Time(TimeMode(earliest_mode),earliest_time), Time(TimeMode(latest_mode),latest_time)) return TimeConstraint(ConstraintMode.Time, Time(TimeMode(earliest_mode),earliest_time), Time(TimeMode(latest_mode),latest_time))
def dump(self): def dump(self):
return Constraint._dump(ConstraintMode.Time, TimeConstraint.TYPES, return Constraint._dump(ConstraintMode.Time, TimeConstraint.TYPES,

View File

@@ -0,0 +1,30 @@
import logging
from dexorder.blockstate import BlockSet, BlockDict
from dexorder.order.orderlib import SwapOrderStatus
from dexorder.util import keystr
log = logging.getLogger(__name__)
def order_key( vault:str, order_index:int ):
return keystr(vault, str(order_index))
def tranche_key( vault:str, order_index:int, tranche_index:int ):
return keystr(vault,str(order_index),str(tranche_index))
active_orders = BlockSet('ao') # unfilled, not-canceled orders whose triggers have been loaded/set
order_remaining = BlockDict('or') # by order key
tranche_remaining = BlockDict('tr') # by tranche key
# todo forcibly dispose of entire series
class OrderState:
def __init__(self, vault:str, order_index:int, status: SwapOrderStatus):
self.vault = vault
self.order_index = order_index
self.key = f'{vault}|{order_index}'
self.tranche_keys = [f'{self.key}|{i}' for i in range(len(status.trancheFilledIn))]
###### TODO TODO TODO vault state needs to be a dict pointing to owner addr

View File

@@ -0,0 +1,55 @@
import logging
from typing import Callable
from dexorder.blockstate import BlockSet
from .orderlib import SwapOrderStatus, TimeConstraint, PriceConstraint, ConstraintMode
from dexorder.util import defaultdictk
log = logging.getLogger(__name__)
# todo time and price triggers should be BlockSortedSets that support range queries
TimeTrigger = Callable[[int, int], None] # func(start_timestamp, end_timestamp)
time_triggers:BlockSet[TimeTrigger] = BlockSet('tt')
PriceTrigger = Callable[[int, int], None] # pool previous price, pool new price
price_triggers:dict[str, BlockSet[PriceTrigger]] = defaultdictk(BlockSet) # different BlockSet per pool address
def intersect_ranges( a_low, a_high, b_low, b_high):
low, high = max(a_low,b_low), min(a_high,b_high)
if high <= low:
low, high = None, None
return low, high
class TrancheTrigger:
def __init__(self, vault: str, order_index:int, tranche_index: int):
self.series = f'{vault}|{order_index}|{tranche_index}|'
self.vault = vault
self.order_index = order_index
self.tranche_index = tranche_index
# todo refactor so we have things like tranche amount filled as blockstate, order amount remaining
def enable(self, status: SwapOrderStatus):
tranche = status.order.tranches[self.tranche_index]
tranche_amount = status.order.amount * tranche.fraction // 10**18
tranche_filled = status.trancheFilledIn[self.tranche_index] if status.order.amountIsInput else status.trancheFilledOut[self.tranche_index]
order_filled = status.filledIn if status.order.amountIsInput else status.filledOut
remaining = min(tranche_amount - tranche_filled, status.order.amount - order_filled)
if remaining <= 0: # todo dust?
return
time_constraint = None
price_constraints = []
if status.filledOut:
...
for c in tranche.constraints:
if c.mode == ConstraintMode.Time:
c: TimeConstraint
time_constraint = (c.earliest, c.latest) if time_constraint is None else intersect_ranges(*time_constraint, c.earliest, c.latest)
elif c.mode == ConstraintMode.Limit:
c: PriceConstraint
raise NotImplementedError
else:
raise NotImplementedError

View File

@@ -1,4 +1,5 @@
import re import re
from typing import Callable, TypeVar, Generic
from eth_utils import keccak from eth_utils import keccak
from hexbytes import HexBytes from hexbytes import HexBytes
@@ -23,6 +24,7 @@ def hexstr(value: bytes):
elif type(value) is bytes: elif type(value) is bytes:
return '0x' + value.hex() return '0x' + value.hex()
elif type(value) is str: elif type(value) is str:
# noinspection PyTypeChecker
return value if value.startswith('0x') else '0x' + value return value if value.startswith('0x') else '0x' + value
else: else:
raise ValueError raise ValueError
@@ -32,14 +34,14 @@ def hexbytes(value: str):
""" converts an optionally 0x-prefixed hex string into bytes """ """ converts an optionally 0x-prefixed hex string into bytes """
return bytes.fromhex(value[2:] if value.startswith('0x') else value) return bytes.fromhex(value[2:] if value.startswith('0x') else value)
def keystr(value): def keystr(*keys):
if type(value) is str: return '|'.join(
return value value if type(value) is str else
if type(value) is HexBytes: value.hex() if type(value) is HexBytes else
return value.hex() '0x' + value.hex() if type(value) is bytes else
if type(value) is bytes: str(value)
return '0x' + value.hex() for value in keys
return str(value) )
def strkey(s): def strkey(s):
if s.startswith('0x'): if s.startswith('0x'):
@@ -51,3 +53,18 @@ def topic(event_abi):
result = '0x' + keccak(text=event_name).hex() result = '0x' + keccak(text=event_name).hex()
print(event_name, result) print(event_name, result)
return result return result
K = TypeVar('K')
V = TypeVar('V')
class defaultdictk (Generic[K,V], dict[K,V]):
def __init__(self, default_factory: Callable[[K],V]):
super().__init__()
self.default_factory = default_factory
def __getitem__(self, item: K) -> V:
try:
return super().__getitem__(item)
except KeyError:
default = self[item] = self.default_factory(item)
return default