all tranches of TWAP WORK
This commit is contained in:
@@ -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))
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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 """
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user