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:
if d.key == BlockState._DELETE_SERIES_KEY and dead.hash in new_root_fork:
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))
else:
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.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.blockchain.uniswap import uniswap_price
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.orderstate import Order
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__)
@@ -97,15 +97,40 @@ async def handle_order_placed(event: EventData):
log.debug(f'created order {order_status}')
if triggers.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):
# event DexorderSwapFilled (uint64 orderIndex, uint8 trancheIndex, uint256 amountIn, uint256 amountOut);
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?
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):
# event DexorderError (uint64 orderIndex, string reason);
@@ -211,12 +236,11 @@ def handle_dexorderexecutions(event: EventData):
if job is None:
log.warning(f'Job {exe_id} not found!')
return
finish_execution_request(job.request, errors[0])
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)
if 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
token = order.order.tokenIn
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:
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):...
@staticmethod
def of(a, b=None):
def of(a, b=None) -> 'Order':
key = a if b is None else OrderKey(a, b)
try:
return Order.instances[key]
@@ -108,6 +108,9 @@ class Order:
def tranche_filled(self, tk: TrancheKey):
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
def filled(self):
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
fout = old.filled_out + filled_out
Order._tranche_filled[tk] = Filled(fin, fout)
# todo check for completion
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 """

View File

@@ -1,5 +1,5 @@
import logging
from enum import Enum
from enum import Enum, auto
from typing import Callable
from dexorder.blockstate import BlockSet, BlockDict
@@ -18,7 +18,7 @@ time_triggers:BlockSet[TimeTrigger] = BlockSet('tt')
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
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?
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
class TrancheStatus (Enum):
Early = 0 # first time trigger hasnt happened yet
Pricing = 1 # we are inside the time window and checking prices
Filled = 1 # tranche has no more available amount
Expired = 2 # time deadline has past and this tranche cannot be filled
Early = auto() # first time trigger hasnt happened yet
Pricing = auto() # we are inside the time window and checking prices
Filled = auto() # tranche has no more available amount
Expired = auto() # time deadline has past and this tranche cannot be filled
class TrancheTrigger:
def __init__(self, order: Order, tranche_key: TrancheKey):
@@ -52,7 +52,7 @@ class TrancheTrigger:
self.status = TrancheStatus.Filled
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 = []
for c in tranche.constraints:
if c.mode == ConstraintMode.Time:
@@ -65,6 +65,7 @@ class TrancheTrigger:
raise NotImplementedError
else:
raise NotImplementedError
self.time_constraint = time_constraint
if time_constraint is None:
self.status = TrancheStatus.Pricing
else:
@@ -85,11 +86,18 @@ class TrancheTrigger:
time_triggers.remove(self.time_trigger)
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]:
log.debug(f'tranche expired {self.tk}')
self.disable()
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.enable_price_trigger()
@@ -100,9 +108,15 @@ class TrancheTrigger:
unconstrained_price_triggers.add(self.price_trigger)
def disable_price_trigger(self):
price_triggers[self.order.pool_address].remove(self.price_trigger)
if self.price_constraints:
price_triggers[self.order.pool_address].remove(self.price_trigger)
else:
unconstrained_price_triggers.remove(self.price_trigger)
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):
self.execute()
@@ -113,6 +127,15 @@ class TrancheTrigger:
log.info(f'execution request for {self.tk}')
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):
self.disable_time_trigger()
self.disable_price_trigger()
@@ -149,11 +172,21 @@ class OrderTriggers:
def open(self):
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):
Order.instances[order_key].complete(final_state)
def fill(self, tranche_index, amount_in, amount_out):
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:
triggers = OrderTriggers.instances[order_key]
triggers = OrderTriggers.instances[order.key]
except KeyError:
pass
else: