all tranches of TWAP WORK

This commit is contained in:
Tim Olson
2023-10-29 20:37:18 -04:00
parent f846120c1a
commit 064f1a4d82
4 changed files with 91 additions and 22 deletions

View File

@@ -175,7 +175,7 @@ class BlockState:
for d in block_diffs: for d in block_diffs:
if d.key == BlockState._DELETE_SERIES_KEY and dead.hash in new_root_fork: if d.key == BlockState._DELETE_SERIES_KEY and dead.hash in new_root_fork:
series_deletions.append(d.series) series_deletions.append(d.series)
elif d.value is UNLOAD and dead.hash in new_root_fork: elif d.value is UNLOAD and dead in new_root_fork:
key_unloads.append((d.series, d.key)) key_unloads.append((d.series, d.key))
else: else:
updated_keys.add((d.series, d.key)) updated_keys.add((d.series, d.key))

View File

@@ -5,7 +5,7 @@ from web3.types import EventData
from dexorder import current_pub, db, dec from dexorder import current_pub, db, dec
from dexorder.base.chain import current_chain from dexorder.base.chain import current_chain
from dexorder.base.order import TrancheExecutionRequest, TrancheKey, ExecutionRequest, new_tranche_execution_request from dexorder.base.order import TrancheExecutionRequest, TrancheKey, ExecutionRequest, new_tranche_execution_request, OrderKey
from dexorder.transaction import handle_create_transactions, submit_transaction_request, handle_transaction_receipts, handle_send_transactions from dexorder.transaction import handle_create_transactions, submit_transaction_request, handle_transaction_receipts, handle_send_transactions
from dexorder.blockchain.uniswap import uniswap_price from dexorder.blockchain.uniswap import uniswap_price
from dexorder.contract.dexorder import get_factory_contract, vault_address, VaultContract, get_dexorder_contract from dexorder.contract.dexorder import get_factory_contract, vault_address, VaultContract, get_dexorder_contract
@@ -16,7 +16,7 @@ from dexorder.database.model.transaction import TransactionJob
from dexorder.order.orderlib import SwapOrderState, SwapOrderStatus from dexorder.order.orderlib import SwapOrderState, SwapOrderStatus
from dexorder.order.orderstate import Order from dexorder.order.orderstate import Order
from dexorder.order.triggers import OrderTriggers, close_order_and_disable_triggers, price_triggers, time_triggers, \ from dexorder.order.triggers import OrderTriggers, close_order_and_disable_triggers, price_triggers, time_triggers, \
unconstrained_price_triggers, execution_requests, inflight_execution_requests unconstrained_price_triggers, execution_requests, inflight_execution_requests, TrancheStatus
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@@ -97,15 +97,40 @@ async def handle_order_placed(event: EventData):
log.debug(f'created order {order_status}') log.debug(f'created order {order_status}')
if triggers.closed: if triggers.closed:
log.warning(f'order {order.key} was immediately closed') log.warning(f'order {order.key} was immediately closed')
close_order_and_disable_triggers(order.key, SwapOrderState.Filled if not order.remaining else SwapOrderState.Expired) close_order_and_disable_triggers(order, SwapOrderState.Filled if order.remaining <= 0 else SwapOrderState.Expired)
def handle_swap_filled(event: EventData): def handle_swap_filled(event: EventData):
# event DexorderSwapFilled (uint64 orderIndex, uint8 trancheIndex, uint256 amountIn, uint256 amountOut); # event DexorderSwapFilled (uint64 orderIndex, uint8 trancheIndex, uint256 amountIn, uint256 amountOut);
log.debug(f'DexorderSwapFilled {event}') log.debug(f'DexorderSwapFilled {event}')
args = event['args']
vault = event['address']
order_index = args['orderIndex']
tranche_index = args['trancheIndex']
amount_in = args['amountIn']
amount_out = args['amountOut']
try:
order: Order = Order.of(vault, order_index)
except KeyError:
log.warning(f'DexorderSwapFilled IGNORED due to missing order {vault} {order_index}')
return
triggers = OrderTriggers.instances[order.key]
triggers.fill(tranche_index, amount_in, amount_out)
# check for fill
if order.remaining <= 0:
close_order_and_disable_triggers(order.key, SwapOrderState.Filled)
def handle_order_completed(event: EventData): async def handle_order_completed(event: EventData):
# event DexorderCompleted (uint64 orderIndex); // todo remove? # event DexorderCompleted (uint64 orderIndex); // todo remove?
log.debug(f'DexorderCompleted {event}') log.debug(f'DexorderCompleted {event}')
vault = event['address']
order_index = event['args']['orderIndex']
try:
order: Order = Order.of(vault, order_index)
except KeyError:
log.warning(f'DexorderCompleted IGNORED due to missing order {vault} {order_index}')
status = await VaultContract(vault).swapOrderStatus(order_index)
log.debug(f'SwapOrderStatus #todo {status}')
# todo
def handle_order_error(event: EventData): def handle_order_error(event: EventData):
# event DexorderError (uint64 orderIndex, string reason); # event DexorderError (uint64 orderIndex, string reason);
@@ -211,12 +236,11 @@ def handle_dexorderexecutions(event: EventData):
if job is None: if job is None:
log.warning(f'Job {exe_id} not found!') log.warning(f'Job {exe_id} not found!')
return return
finish_execution_request(job.request, errors[0]) finish_execution_request(job.request, errors[0])
def finish_execution_request(req: TrancheExecutionRequest, error: str): def finish_execution_request(req: TrancheExecutionRequest, error: str):
order = Order.of(req.vault, req.order_index) order: Order = Order.of(req.vault, req.order_index)
tk = TrancheKey(req.vault, req.order_index, req.tranche_index) tk = TrancheKey(req.vault, req.order_index, req.tranche_index)
if error != '': if error != '':
log.debug(f'execution request for tranche {tk} had error "{error}"') log.debug(f'execution request for tranche {tk} had error "{error}"')
@@ -227,5 +251,15 @@ def finish_execution_request(req: TrancheExecutionRequest, error: str):
# todo vault balance checks # todo vault balance checks
token = order.order.tokenIn token = order.order.tokenIn
log.debug(f'insufficient funds {req.vault} {token} ') log.debug(f'insufficient funds {req.vault} {token} ')
elif error == 'TF':
# Tranche Filled
log.warning(f'tranche already filled {tk}')
triggers = OrderTriggers.instances[order.key]
tranche_trigger = triggers.triggers[tk.tranche_index]
tranche_trigger.status = TrancheStatus.Filled
tranche_trigger.disable()
else: else:
log.error(f'Unhandled execution error for transaction request {req} ERROR: "{error}"') log.error(f'Unhandled execution error for transaction request {req} ERROR: "{error}"')
er = execution_requests[tk]
if er.height < current_block.get().height:
del execution_requests[tk]

View File

@@ -41,7 +41,7 @@ class Order:
def of(vault: str, order_index: int):... def of(vault: str, order_index: int):...
@staticmethod @staticmethod
def of(a, b=None): def of(a, b=None) -> 'Order':
key = a if b is None else OrderKey(a, b) key = a if b is None else OrderKey(a, b)
try: try:
return Order.instances[key] return Order.instances[key]
@@ -108,6 +108,9 @@ class Order:
def tranche_filled(self, tk: TrancheKey): def tranche_filled(self, tk: TrancheKey):
return self.tranche_filled_in(tk) if self.amount_is_input else self.tranche_filled_out(tk) return self.tranche_filled_in(tk) if self.amount_is_input else self.tranche_filled_out(tk)
def tranche_remaining(self, tk: TrancheKey):
return self.tranche_amounts[tk.tranche_index] - self.tranche_filled(tk)
@property @property
def filled(self): def filled(self):
return self.filled_in if self.amount_is_input else self.filled_out return self.filled_in if self.amount_is_input else self.filled_out
@@ -130,7 +133,6 @@ class Order:
fin = old.filled_in + filled_in fin = old.filled_in + filled_in
fout = old.filled_out + filled_out fout = old.filled_out + filled_out
Order._tranche_filled[tk] = Filled(fin, fout) Order._tranche_filled[tk] = Filled(fin, fout)
# todo check for completion
def complete(self, final_state: SwapOrderState): def complete(self, final_state: SwapOrderState):
""" updates the static order record with its final values, then deletes all its dynamic blockstate and removes the Order from the actives list """ """ updates the static order record with its final values, then deletes all its dynamic blockstate and removes the Order from the actives list """

View File

@@ -1,5 +1,5 @@
import logging import logging
from enum import Enum from enum import Enum, auto
from typing import Callable from typing import Callable
from dexorder.blockstate import BlockSet, BlockDict from dexorder.blockstate import BlockSet, BlockDict
@@ -18,7 +18,7 @@ time_triggers:BlockSet[TimeTrigger] = BlockSet('tt')
PriceTrigger = Callable[[int], None] # func(pool_price) PriceTrigger = Callable[[int], None] # func(pool_price)
price_triggers:dict[str, BlockSet[PriceTrigger]] = defaultdictk(lambda addr:BlockSet(f'pt|{addr}')) # different BlockSet per pool address price_triggers:dict[str, BlockSet[PriceTrigger]] = defaultdictk(lambda addr:BlockSet(f'pt|{addr}')) # different BlockSet per pool address
unconstrained_price_triggers: BlockSet[PriceTrigger] = BlockSet('upt') # tranches with no price constraints, whose time constraint is fulfilled unconstrained_price_triggers: BlockSet[PriceTrigger] = BlockSet('upt') # tranches with no price constraints, whose time constraint is fulfilled
execution_requests:BlockDict[TrancheKey, ExecutionRequest] = BlockDict('e') # value is block height when the request was placed execution_requests:BlockDict[TrancheKey, ExecutionRequest] = BlockDict('e')
# todo should this really be blockdata? # todo should this really be blockdata?
inflight_execution_requests:BlockDict[TrancheKey, int] = BlockDict('ei') # value is block height when the request was sent inflight_execution_requests:BlockDict[TrancheKey, int] = BlockDict('ei') # value is block height when the request was sent
@@ -30,10 +30,10 @@ def intersect_ranges( a_low, a_high, b_low, b_high):
return low, high return low, high
class TrancheStatus (Enum): class TrancheStatus (Enum):
Early = 0 # first time trigger hasnt happened yet Early = auto() # first time trigger hasnt happened yet
Pricing = 1 # we are inside the time window and checking prices Pricing = auto() # we are inside the time window and checking prices
Filled = 1 # tranche has no more available amount Filled = auto() # tranche has no more available amount
Expired = 2 # time deadline has past and this tranche cannot be filled Expired = auto() # time deadline has past and this tranche cannot be filled
class TrancheTrigger: class TrancheTrigger:
def __init__(self, order: Order, tranche_key: TrancheKey): def __init__(self, order: Order, tranche_key: TrancheKey):
@@ -52,7 +52,7 @@ class TrancheTrigger:
self.status = TrancheStatus.Filled self.status = TrancheStatus.Filled
return return
self.time_constraint = time_constraint = None # stored as a tuple of two ints for earliest and latest absolute timestamps time_constraint = None # stored as a tuple of two ints for earliest and latest absolute timestamps
self.price_constraints = [] self.price_constraints = []
for c in tranche.constraints: for c in tranche.constraints:
if c.mode == ConstraintMode.Time: if c.mode == ConstraintMode.Time:
@@ -65,6 +65,7 @@ class TrancheTrigger:
raise NotImplementedError raise NotImplementedError
else: else:
raise NotImplementedError raise NotImplementedError
self.time_constraint = time_constraint
if time_constraint is None: if time_constraint is None:
self.status = TrancheStatus.Pricing self.status = TrancheStatus.Pricing
else: else:
@@ -85,11 +86,18 @@ class TrancheTrigger:
time_triggers.remove(self.time_trigger) time_triggers.remove(self.time_trigger)
def time_trigger(self, now): def time_trigger(self, now):
log.debug(f'time_trigger {now}') log.debug(f'time_trigger {now} {self.status} {self.time_constraint}')
if self.closed:
log.debug(f'price trigger ignored because trigger status is {self.status}')
return
if now >= self.time_constraint[1]: if now >= self.time_constraint[1]:
log.debug(f'tranche expired {self.tk}')
self.disable() self.disable()
self.status = TrancheStatus.Expired self.status = TrancheStatus.Expired
if self.status == TrancheStatus.Early and now >= self.time_constraint[0]: # check for all tranches expired
OrderTriggers.instances[self.order.key].check_complete()
elif self.status == TrancheStatus.Early and now >= self.time_constraint[0]:
log.debug(f'tranche time enabled {self.tk}')
self.status = TrancheStatus.Pricing self.status = TrancheStatus.Pricing
self.enable_price_trigger() self.enable_price_trigger()
@@ -100,9 +108,15 @@ class TrancheTrigger:
unconstrained_price_triggers.add(self.price_trigger) unconstrained_price_triggers.add(self.price_trigger)
def disable_price_trigger(self): def disable_price_trigger(self):
if self.price_constraints:
price_triggers[self.order.pool_address].remove(self.price_trigger) price_triggers[self.order.pool_address].remove(self.price_trigger)
else:
unconstrained_price_triggers.remove(self.price_trigger)
def price_trigger(self, cur): def price_trigger(self, cur):
if self.closed:
log.debug(f'price trigger ignored because trigger status is {self.status}')
return
if not self.price_constraints or all(pc.passes(cur) for pc in self.price_constraints): if not self.price_constraints or all(pc.passes(cur) for pc in self.price_constraints):
self.execute() self.execute()
@@ -113,6 +127,15 @@ class TrancheTrigger:
log.info(f'execution request for {self.tk}') log.info(f'execution request for {self.tk}')
execution_requests[self.tk] = ExecutionRequest(height, proof) execution_requests[self.tk] = ExecutionRequest(height, proof)
def fill(self, _amount_in, _amount_out ):
remaining = self.order.tranche_remaining(self.tk)
filled = remaining <= 0
if filled:
log.debug(f'tranche filled {self.tk}')
self.status = TrancheStatus.Filled
self.disable()
return filled
def disable(self): def disable(self):
self.disable_time_trigger() self.disable_time_trigger()
self.disable_price_trigger() self.disable_price_trigger()
@@ -149,11 +172,21 @@ class OrderTriggers:
def open(self): def open(self):
return not self.closed return not self.closed
def check_complete(self):
if all(t.closed for t in self.triggers):
final_state = SwapOrderState.Filled if self.order.remaining <= 0 else SwapOrderState.Expired
close_order_and_disable_triggers(self.order, final_state)
def close_order_and_disable_triggers(order_key: OrderKey, final_state: SwapOrderState): def fill(self, tranche_index, amount_in, amount_out):
Order.instances[order_key].complete(final_state) self.order.add_fill(tranche_index, amount_in, amount_out)
if self.triggers[tranche_index].fill(amount_in, amount_out):
self.check_complete()
def close_order_and_disable_triggers(order: Order, final_state: SwapOrderState):
order.complete(final_state)
try: try:
triggers = OrderTriggers.instances[order_key] triggers = OrderTriggers.instances[order.key]
except KeyError: except KeyError:
pass pass
else: else: