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