order placement doesnt crash
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
from typing import Generic, TypeVar, Any, Iterator
|
from typing import Generic, TypeVar, Any, Iterator
|
||||||
|
|
||||||
from dexorder import NARG
|
from dexorder import NARG
|
||||||
|
from dexorder.base.chain import current_chain
|
||||||
|
|
||||||
_T = TypeVar('_T')
|
_T = TypeVar('_T')
|
||||||
|
|
||||||
@@ -18,18 +19,18 @@ class ByBlockchainCollection (Generic[_T]):
|
|||||||
self.by_blockchain = by_blockchain if by_blockchain is not None else {}
|
self.by_blockchain = by_blockchain if by_blockchain is not None else {}
|
||||||
|
|
||||||
def __getitem__(self, item) -> _T:
|
def __getitem__(self, item) -> _T:
|
||||||
return self.by_blockchain[ctx.chain_id][item]
|
return self.by_blockchain[current_chain.get().chain_id][item]
|
||||||
|
|
||||||
|
|
||||||
class ByBlockchainDict (ByBlockchainCollection[_T], Generic[_T]):
|
class ByBlockchainDict (ByBlockchainCollection[_T], Generic[_T]):
|
||||||
|
|
||||||
def __getattr__(self, name: str) -> _T:
|
def __getattr__(self, name: str) -> _T:
|
||||||
return self.by_blockchain[ctx.chain_id][name]
|
return self.by_blockchain[current_chain.get().chain_id][name]
|
||||||
|
|
||||||
def get(self, item, default=None, *, chain_id=None) -> _T:
|
def get(self, item, default=None, *, chain_id=None) -> _T:
|
||||||
# will raise if default is NARG
|
# will raise if default is NARG
|
||||||
if chain_id is None:
|
if chain_id is None:
|
||||||
chain_id = ctx.chain_id
|
chain_id = current_chain.get().chain_id
|
||||||
if chain_id is None:
|
if chain_id is None:
|
||||||
raise KeyError('no ctx.chain_id set')
|
raise KeyError('no ctx.chain_id set')
|
||||||
found = self.by_blockchain.get(chain_id, {}).get(item, default)
|
found = self.by_blockchain.get(chain_id, {}).get(item, default)
|
||||||
@@ -40,16 +41,16 @@ class ByBlockchainDict (ByBlockchainCollection[_T], Generic[_T]):
|
|||||||
|
|
||||||
class ByBlockchainList (ByBlockchainCollection[_T], Generic[_T]):
|
class ByBlockchainList (ByBlockchainCollection[_T], Generic[_T]):
|
||||||
def __iter__(self) -> Iterator[_T]:
|
def __iter__(self) -> Iterator[_T]:
|
||||||
return iter(self.by_blockchain[ctx.chain_id])
|
return iter(self.by_blockchain[current_chain.get().chain_id])
|
||||||
|
|
||||||
def iter(self, *, chain_id=None) -> Iterator[_T]:
|
def iter(self, *, chain_id=None) -> Iterator[_T]:
|
||||||
if chain_id is None:
|
if chain_id is None:
|
||||||
chain_id = ctx.chain_id
|
chain_id = current_chain.get().chain_id
|
||||||
return iter(self.by_blockchain[chain_id])
|
return iter(self.by_blockchain[chain_id])
|
||||||
|
|
||||||
def get(self, index, *, chain_id=None) -> _T:
|
def get(self, index, *, chain_id=None) -> _T:
|
||||||
if chain_id is None:
|
if chain_id is None:
|
||||||
chain_id = ctx.chain_id
|
chain_id = current_chain.get().chain_id
|
||||||
if chain_id is None:
|
if chain_id is None:
|
||||||
raise KeyError('no ctx.chain_id set')
|
raise KeyError('no ctx.chain_id set')
|
||||||
return self.by_blockchain[chain_id][index]
|
return self.by_blockchain[chain_id][index]
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from hexbytes import HexBytes
|
from hexbytes import HexBytes
|
||||||
from web3 import WebsocketProviderV2, AsyncWeb3, AsyncHTTPProvider
|
from web3 import WebsocketProviderV2, AsyncWeb3, AsyncHTTPProvider
|
||||||
|
|
||||||
from dexorder.blockchain.util import get_contract_data
|
from dexorder.util.uniswap_util import get_contract_data
|
||||||
from .. import current_w3
|
from .. import current_w3
|
||||||
from ..configuration import resolve_rpc_url
|
from ..configuration import resolve_rpc_url
|
||||||
from ..configuration.resolve import resolve_ws_url
|
from ..configuration.resolve import resolve_ws_url
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
|
from charset_normalizer.md import getLogger
|
||||||
from eth_abi.packed import encode_packed
|
from eth_abi.packed import encode_packed
|
||||||
from eth_utils import keccak, to_bytes, to_checksum_address
|
from eth_utils import keccak, to_bytes, to_checksum_address
|
||||||
|
|
||||||
from dexorder import dec
|
from dexorder import dec
|
||||||
from dexorder.contract import uniswapV3
|
from dexorder.contract import abi_encoder
|
||||||
from dexorder.util import hexbytes
|
from dexorder.util import hexbytes
|
||||||
|
|
||||||
UNISWAPV3_POOL_INIT_CODE_HASH = hexbytes('0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54')
|
UNISWAPV3_POOL_INIT_CODE_HASH = hexbytes('0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54')
|
||||||
|
|
||||||
|
log = getLogger(__name__)
|
||||||
|
|
||||||
class Fee:
|
class Fee:
|
||||||
LOWEST = 100
|
LOWEST = 100
|
||||||
@@ -18,19 +20,19 @@ class Fee:
|
|||||||
def ordered_addresses(addr_a:str, addr_b:str):
|
def ordered_addresses(addr_a:str, addr_b:str):
|
||||||
return (addr_a, addr_b) if addr_a.lower() <= addr_b.lower() else (addr_b, addr_a)
|
return (addr_a, addr_b) if addr_a.lower() <= addr_b.lower() else (addr_b, addr_a)
|
||||||
|
|
||||||
def uniswapV3_pool_address( addr_a: str, addr_b: str, fee: int):
|
|
||||||
return uniswap_pool_address(uniswapV3['factory'], addr_a, addr_b, fee)
|
|
||||||
|
|
||||||
def uniswap_pool_address(factory_addr: str, addr_a: str, addr_b: str, fee: int) -> str:
|
def uniswap_pool_address(factory_addr: str, addr_a: str, addr_b: str, fee: int) -> str:
|
||||||
token0, token1 = ordered_addresses(addr_a, addr_b)
|
token0, token1 = ordered_addresses(addr_a, addr_b)
|
||||||
salt = keccak(encode_packed(['address','address','uint24'],[token0, token1, fee]))
|
salt = keccak(abi_encoder.encode(['address','address','uint24'],[token0, token1, fee]))
|
||||||
contract_address = keccak(
|
contract_address = keccak(
|
||||||
b"\xff"
|
b"\xff"
|
||||||
+ to_bytes(hexstr=factory_addr)
|
+ to_bytes(hexstr=factory_addr)
|
||||||
+ salt
|
+ salt
|
||||||
+ UNISWAPV3_POOL_INIT_CODE_HASH
|
+ UNISWAPV3_POOL_INIT_CODE_HASH
|
||||||
).hex()[-40:]
|
).hex()[-40:]
|
||||||
return to_checksum_address(contract_address)
|
result = to_checksum_address(contract_address)
|
||||||
|
log.debug(f'uniswap pool address {factory_addr} {addr_a} {addr_b} {fee} => {result}')
|
||||||
|
return result
|
||||||
|
|
||||||
def uniswap_price(sqrt_price):
|
def uniswap_price(sqrt_price):
|
||||||
d = dec(sqrt_price)
|
d = dec(sqrt_price)
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ class BlockData:
|
|||||||
def __init__(self, data_type: DataType, series: Any, *,
|
def __init__(self, data_type: DataType, series: Any, *,
|
||||||
series2str=None, series2key=None, # defaults to key2str and str2key
|
series2str=None, series2key=None, # defaults to key2str and str2key
|
||||||
key2str=util_key2str, str2key=util_str2key,
|
key2str=util_key2str, str2key=util_str2key,
|
||||||
value2basic=lambda x:x, basic2value=lambda x:x, # serialize/deserialize value to something JSON-able
|
value2str=lambda x:x, str2value=lambda x:x, # serialize/deserialize value to something JSON-able
|
||||||
**opts):
|
**opts):
|
||||||
assert series not in BlockData.registry
|
assert series not in BlockData.registry
|
||||||
BlockData.registry[series] = self
|
BlockData.registry[series] = self
|
||||||
@@ -39,8 +39,8 @@ class BlockData:
|
|||||||
self.str2key = str2key
|
self.str2key = str2key
|
||||||
self.series2str = series2str or self.key2str
|
self.series2str = series2str or self.key2str
|
||||||
self.series2key = series2key or self.str2key
|
self.series2key = series2key or self.str2key
|
||||||
self.value2basic = value2basic
|
self.value2str = value2str
|
||||||
self.basic2value = basic2value
|
self.str2value = str2value
|
||||||
self.lazy_getitem = None
|
self.lazy_getitem = None
|
||||||
|
|
||||||
def setitem(self, item, value, overwrite=True):
|
def setitem(self, item, value, overwrite=True):
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ class DbState(SeriesCollection):
|
|||||||
elif t == DataType.DICT:
|
elif t == DataType.DICT:
|
||||||
found = db.session.get(SeriesDict, key)
|
found = db.session.get(SeriesDict, key)
|
||||||
if found is None:
|
if found is None:
|
||||||
db.session.add(SeriesDict(**key, value=d.value2basic(diff.value)))
|
db.session.add(SeriesDict(**key, value=d.value2str(diff.value)))
|
||||||
else:
|
else:
|
||||||
found.value = diff.value
|
found.value = diff.value
|
||||||
else:
|
else:
|
||||||
@@ -97,6 +97,6 @@ class DbState(SeriesCollection):
|
|||||||
# noinspection PyTypeChecker
|
# noinspection PyTypeChecker
|
||||||
var: BlockDict = BlockData.registry[series]
|
var: BlockDict = BlockData.registry[series]
|
||||||
for row in db.session.query(SeriesDict).where(SeriesDict.series == data.series2str(series)):
|
for row in db.session.query(SeriesDict).where(SeriesDict.series == data.series2str(series)):
|
||||||
var[data.str2key(row.key)] = data.basic2value(row.value)
|
var[data.str2key(row.key)] = data.str2value(row.value)
|
||||||
completed_block.set(root_block)
|
completed_block.set(root_block)
|
||||||
return state
|
return state
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
from .abi import abis
|
|
||||||
from .contract_proxy import ContractProxy, Transaction
|
|
||||||
from .pool_contract import UniswapV3Pool
|
|
||||||
from .uniswap_contracts import uniswapV3
|
|
||||||
|
|
||||||
from eth_abi.codec import ABIDecoder, ABIEncoder
|
from eth_abi.codec import ABIDecoder, ABIEncoder
|
||||||
from eth_abi.registry import registry as default_registry
|
from eth_abi.registry import registry as default_registry
|
||||||
|
|
||||||
abi_decoder = ABIDecoder(default_registry)
|
abi_decoder = ABIDecoder(default_registry)
|
||||||
abi_encoder = ABIEncoder(default_registry)
|
abi_encoder = ABIEncoder(default_registry)
|
||||||
|
|
||||||
|
from .abi import abis
|
||||||
|
from .contract_proxy import ContractProxy, Transaction
|
||||||
|
from .pool_contract import UniswapV3Pool
|
||||||
|
from .uniswap_contracts import uniswapV3
|
||||||
|
|
||||||
|
|
||||||
def VaultContract(addr):
|
def VaultContract(addr):
|
||||||
return ContractProxy(addr, 'Vault')
|
return ContractProxy(addr, 'Vault')
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
from .contract_proxy import ContractProxy
|
from .contract_proxy import ContractProxy
|
||||||
|
from ..blockchain.uniswap import uniswap_price
|
||||||
|
|
||||||
|
|
||||||
class UniswapV3Pool (ContractProxy):
|
class UniswapV3Pool (ContractProxy):
|
||||||
def __init__(self, address: str = None):
|
def __init__(self, address: str = None):
|
||||||
super().__init__(address, 'IUniswapV3Pool')
|
super().__init__(address, 'IUniswapV3Pool')
|
||||||
|
|
||||||
|
async def price(self):
|
||||||
|
return uniswap_price((await self.slot0())[0])
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from dexorder.base.chain import Ethereum, Goerli, Polygon, Mumbai, Arbitrum, Mock
|
from dexorder.base.chain import Ethereum, Goerli, Polygon, Mumbai, Arbitrum, Mock
|
||||||
|
from dexorder.blockchain.uniswap import uniswap_pool_address
|
||||||
from dexorder.contract.contract_proxy import ContractProxy
|
from dexorder.contract.contract_proxy import ContractProxy
|
||||||
from dexorder.blockchain import ByBlockchainDict
|
from dexorder.blockchain import ByBlockchainDict
|
||||||
|
|
||||||
@@ -17,3 +18,6 @@ class _UniswapContracts (ByBlockchainDict[ContractProxy]):
|
|||||||
|
|
||||||
uniswapV3 = _UniswapContracts()
|
uniswapV3 = _UniswapContracts()
|
||||||
|
|
||||||
|
|
||||||
|
def uniswapV3_pool_address( addr_a: str, addr_b: str, fee: int):
|
||||||
|
return uniswap_pool_address(uniswapV3['factory'].address, addr_a, addr_b, fee)
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
|
from dexorder import dec
|
||||||
from dexorder.blockstate import BlockSet, BlockDict
|
from dexorder.blockstate import BlockSet, BlockDict
|
||||||
|
|
||||||
# pub=... publishes to a channel for web clients to consume. argument is (key,value) and return must be (event,room,value)
|
# pub=... publishes to a channel for web clients to consume. argument is (key,value) and return must be (event,room,args)
|
||||||
# if pub is True, then event is the current series name, room is the key, and value is passed through
|
# if pub is True, then event is the current series name, room is the key, and args is [value]
|
||||||
# values of DELETE are serialized as nulls
|
# values of DELETE are serialized as nulls
|
||||||
|
|
||||||
vault_owners = BlockDict('v', db=True, redis=True)
|
vault_owners: BlockDict[str,str] = BlockDict('v', db=True, redis=True)
|
||||||
vault_tokens = BlockDict('vt', db=True, redis=True, pub=True)
|
vault_tokens: BlockDict[str,str] = BlockDict('vt', db=True, redis=True, pub=True)
|
||||||
pool_prices = BlockDict('p', db=True, redis=True, pub=True)
|
pool_prices: BlockDict[str,dec] = BlockDict('p', db=True, redis=True, pub=True, value2str=lambda d:f'{d:f}', str2value=dec)
|
||||||
underfunded_vaults = BlockSet('uv', db=True)
|
|
||||||
active_orders = BlockSet('a', db=True)
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ from sqlalchemy.dialects.postgresql import JSONB
|
|||||||
from sqlalchemy.orm import Mapped, mapped_column
|
from sqlalchemy.orm import Mapped, mapped_column
|
||||||
|
|
||||||
from dexorder.database.model import Base
|
from dexorder.database.model import Base
|
||||||
|
from dexorder.util import hexint
|
||||||
|
|
||||||
|
|
||||||
class Block(Base):
|
class Block(Base):
|
||||||
@@ -13,6 +14,11 @@ class Block(Base):
|
|||||||
parent: Mapped[bytes]
|
parent: Mapped[bytes]
|
||||||
data: Mapped[dict] = mapped_column(JSONB)
|
data: Mapped[dict] = mapped_column(JSONB)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def timestamp(self) -> int:
|
||||||
|
# noinspection PyTypeChecker
|
||||||
|
return hexint(self.data['timestamp'])
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f'{self.height}_{self.hash.hex()[:5]}'
|
return f'{self.height}_{self.hash.hex()[:5]}'
|
||||||
|
|
||||||
|
|||||||
@@ -5,15 +5,26 @@ from web3.types import EventData
|
|||||||
from dexorder import current_pub, current_w3
|
from dexorder import current_pub, current_w3
|
||||||
from dexorder.base.chain import current_chain
|
from dexorder.base.chain import current_chain
|
||||||
from dexorder.blockchain.uniswap import uniswap_price
|
from dexorder.blockchain.uniswap import uniswap_price
|
||||||
from dexorder.blockchain.util import vault_address, get_contract_event, get_factory, get_contract_data
|
from dexorder.util.uniswap_util import vault_address, get_contract_event, get_factory, get_contract_data
|
||||||
from dexorder.contract import VaultContract
|
from dexorder.contract import VaultContract, UniswapV3Pool
|
||||||
from dexorder.data import pool_prices, vault_owners, vault_tokens, underfunded_vaults
|
from dexorder.data import pool_prices, vault_owners, vault_tokens
|
||||||
from dexorder.database.model.block import current_block
|
from dexorder.database.model.block import current_block
|
||||||
from dexorder.orderlib.orderlib import SwapOrderStatus
|
|
||||||
|
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
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
async def ensure_pool_price(pool_addr):
|
||||||
|
if pool_addr not in pool_prices:
|
||||||
|
log.debug(f'querying price for pool {pool_addr}')
|
||||||
|
pool_prices[pool_addr] = await UniswapV3Pool(pool_addr).price()
|
||||||
|
|
||||||
|
def dump_log(eventlog):
|
||||||
|
log.debug(f'eventlog {eventlog}')
|
||||||
|
|
||||||
def setup_logevent_triggers(runner):
|
def setup_logevent_triggers(runner):
|
||||||
runner.events.clear()
|
runner.events.clear()
|
||||||
|
|
||||||
@@ -21,17 +32,28 @@ def setup_logevent_triggers(runner):
|
|||||||
# code ordering here is also the trigger order: e.g. we process all vault creation events
|
# code ordering here is also the trigger order: e.g. we process all vault creation events
|
||||||
# before any order creations
|
# before any order creations
|
||||||
|
|
||||||
vault_created = current_w3.get().eth.contract(get_factory().address, abi=get_contract_data('Factory')['abi']).events.VaultCreated()
|
# DEBUG
|
||||||
|
runner.add_event_trigger(dump_log, None, {})
|
||||||
|
|
||||||
|
factory = get_factory()
|
||||||
|
if factory is None:
|
||||||
|
log.warning(f'No Factory for {current_chain.get()}')
|
||||||
|
vault_created = get_contract_event('Factory', 'VaultCreated')
|
||||||
|
else:
|
||||||
|
vault_created = current_w3.get().eth.contract(factory.address, abi=get_contract_data('Factory')['abi']).events.VaultCreated()
|
||||||
runner.add_event_trigger(handle_vault_created, vault_created)
|
runner.add_event_trigger(handle_vault_created, vault_created)
|
||||||
runner.add_event_trigger(handle_transfer, get_contract_event('ERC20', 'Transfer'))
|
|
||||||
runner.add_event_trigger(handle_swap, get_contract_event('IUniswapV3PoolEvents', 'Swap'))
|
|
||||||
runner.add_event_trigger(handle_order_placed, get_contract_event('OrderLib', 'DexorderSwapPlaced'))
|
runner.add_event_trigger(handle_order_placed, get_contract_event('OrderLib', 'DexorderSwapPlaced'))
|
||||||
|
runner.add_event_trigger(activate_time_triggers)
|
||||||
|
runner.add_event_trigger(handle_transfer, get_contract_event('ERC20', 'Transfer'))
|
||||||
|
runner.add_event_trigger(handle_uniswap_swap, get_contract_event('IUniswapV3PoolEvents', 'Swap'))
|
||||||
runner.add_event_trigger(handle_swap_filled, get_contract_event('OrderLib', 'DexorderSwapFilled'))
|
runner.add_event_trigger(handle_swap_filled, get_contract_event('OrderLib', 'DexorderSwapFilled'))
|
||||||
runner.add_event_trigger(handle_order_completed, get_contract_event('OrderLib', 'DexorderSwapCompleted'))
|
runner.add_event_trigger(handle_order_completed, get_contract_event('OrderLib', 'DexorderSwapCompleted'))
|
||||||
runner.add_event_trigger(handle_order_error, get_contract_event('OrderLib', 'DexorderSwapError'))
|
runner.add_event_trigger(handle_order_error, get_contract_event('OrderLib', 'DexorderSwapError'))
|
||||||
|
runner.add_event_trigger(activate_price_triggers)
|
||||||
|
|
||||||
|
|
||||||
async def handle_order_placed(event: EventData):
|
async def handle_order_placed(event: EventData):
|
||||||
|
log.debug(f'handle order placed {event}')
|
||||||
# event DexorderPlaced (uint64 startOrderIndex, uint8 numOrders);
|
# event DexorderPlaced (uint64 startOrderIndex, uint8 numOrders);
|
||||||
addr = event['address']
|
addr = event['address']
|
||||||
start_index = int(event['args']['startOrderIndex'])
|
start_index = int(event['args']['startOrderIndex'])
|
||||||
@@ -46,10 +68,13 @@ async def handle_order_placed(event: EventData):
|
|||||||
obj = await vault.swapOrderStatus(index)
|
obj = await vault.swapOrderStatus(index)
|
||||||
log.debug(f'raw order status {obj}')
|
log.debug(f'raw order status {obj}')
|
||||||
order_status = SwapOrderStatus.load(obj)
|
order_status = SwapOrderStatus.load(obj)
|
||||||
log.debug(f'order status {order_status}')
|
order = Order.create(vault.address, index, order_status)
|
||||||
assert order_status == SwapOrderStatus.load(order_status.dump())
|
await ensure_pool_price(order.pool_address)
|
||||||
log.debug('assert ok')
|
triggers = OrderTriggers(order)
|
||||||
# todo record order
|
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)
|
||||||
|
|
||||||
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);
|
||||||
@@ -63,8 +88,6 @@ def handle_order_error(event: EventData):
|
|||||||
# event DexorderError (uint64 orderIndex, string reason);
|
# event DexorderError (uint64 orderIndex, string reason);
|
||||||
log.debug(f'DexorderError {event}')
|
log.debug(f'DexorderError {event}')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def handle_transfer(transfer: EventData):
|
def handle_transfer(transfer: EventData):
|
||||||
to_address = transfer['args']['to']
|
to_address = transfer['args']['to']
|
||||||
log.debug(f'transfer {to_address}')
|
log.debug(f'transfer {to_address}')
|
||||||
@@ -76,7 +99,9 @@ def handle_transfer(transfer: EventData):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def handle_swap(swap: EventData):
|
new_pool_prices: dict[str, int] = {}
|
||||||
|
|
||||||
|
def handle_uniswap_swap(swap: EventData):
|
||||||
try:
|
try:
|
||||||
sqrt_price = swap['args']['sqrtPriceX96']
|
sqrt_price = swap['args']['sqrtPriceX96']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
@@ -84,10 +109,11 @@ def handle_swap(swap: EventData):
|
|||||||
addr = swap['address']
|
addr = swap['address']
|
||||||
price = uniswap_price(sqrt_price)
|
price = uniswap_price(sqrt_price)
|
||||||
log.debug(f'pool {addr} {price}')
|
log.debug(f'pool {addr} {price}')
|
||||||
pool_prices[addr] = price
|
new_pool_prices[addr] = price
|
||||||
|
|
||||||
|
|
||||||
def handle_vault_created(created: EventData):
|
def handle_vault_created(created: EventData):
|
||||||
|
log.debug(f'VaultCreated {created}')
|
||||||
try:
|
try:
|
||||||
owner = created['args']['owner']
|
owner = created['args']['owner']
|
||||||
num = created['args']['num']
|
num = created['args']['num']
|
||||||
@@ -108,6 +134,19 @@ def handle_vault_created(created: EventData):
|
|||||||
# log.debug(f'updated vaults: {vaults}')
|
# log.debug(f'updated vaults: {vaults}')
|
||||||
current_pub.get()(f'{current_chain.get().chain_id}|{owner}', 'vaults', vaults)
|
current_pub.get()(f'{current_chain.get().chain_id}|{owner}', 'vaults', vaults)
|
||||||
|
|
||||||
def handle_order_created(event:EventData):
|
|
||||||
print('order', event)
|
def activate_time_triggers():
|
||||||
|
now = current_block.get().timestamp
|
||||||
|
# time triggers
|
||||||
|
for tt in time_triggers:
|
||||||
|
tt(now)
|
||||||
|
|
||||||
|
def activate_price_triggers():
|
||||||
|
for pool, price in new_pool_prices.items():
|
||||||
|
for pt in price_triggers[pool]:
|
||||||
|
pt(price)
|
||||||
|
|
||||||
|
def execute_requests():
|
||||||
|
log.info('execute requests: todo')
|
||||||
|
pass # todo
|
||||||
|
|
||||||
|
|||||||
@@ -58,17 +58,17 @@ class RedisState (SeriesCollection):
|
|||||||
t = d.type
|
t = d.type
|
||||||
series = f'{chain_id}|{d.series2str(diff.series)}'
|
series = f'{chain_id}|{d.series2str(diff.series)}'
|
||||||
key = d.key2str(diff.key)
|
key = d.key2str(diff.key)
|
||||||
value = diff.value
|
value = d.value2str(diff.value)
|
||||||
# pub/sub socketio/redis
|
# pub/sub socketio/redis
|
||||||
pub_kv = d.opts.get('pub')
|
pub_era = d.opts.get('pub') # event, room, args
|
||||||
if pub_kv is True:
|
if pub_era is True:
|
||||||
pub_kv = key, value
|
pub_era = series, key, [value]
|
||||||
elif callable(pub_kv):
|
elif callable(pub_era):
|
||||||
pub_kv = pub_kv((key,value))
|
pub_era = pub_era(diff.key, diff.value)
|
||||||
if pub_kv is not None:
|
if pub_era is not None:
|
||||||
k, v = pub_kv
|
e, r, a = pub_era
|
||||||
# noinspection PyTypeChecker
|
# noinspection PyTypeChecker
|
||||||
pubs.append((series,k,[v]))
|
pubs.append((e,r,a))
|
||||||
if diff.value is DELETE:
|
if diff.value is DELETE:
|
||||||
if t == DataType.SET:
|
if t == DataType.SET:
|
||||||
sdels[series].add(key)
|
sdels[series].add(key)
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ from enum import Enum
|
|||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from dexorder import dec
|
from dexorder import dec
|
||||||
from dexorder.blockchain.uniswap import uniswap_pool_address, uniswap_price, uniswapV3_pool_address
|
from dexorder.blockchain.uniswap import uniswap_pool_address, uniswap_price
|
||||||
|
from dexorder.contract.uniswap_contracts import uniswapV3_pool_address
|
||||||
from dexorder.contract import abi_decoder, abi_encoder, uniswapV3
|
from dexorder.contract import abi_decoder, abi_encoder, uniswapV3
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
@@ -131,7 +132,7 @@ class Constraint (ABC):
|
|||||||
|
|
||||||
class PriceConstraint (Constraint, ABC):
|
class PriceConstraint (Constraint, ABC):
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def passes(self, old_price: dec, new_price: dec) -> bool:...
|
def passes(self, price: dec) -> bool:...
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -153,8 +154,8 @@ class LimitConstraint (PriceConstraint):
|
|||||||
def dump(self):
|
def dump(self):
|
||||||
return self._dump(LimitConstraint.TYPES, (self.isAbove, self.isRatio, self.valueSqrtX96))
|
return self._dump(LimitConstraint.TYPES, (self.isAbove, self.isRatio, self.valueSqrtX96))
|
||||||
|
|
||||||
def passes(self, old_price: dec, new_price: dec) -> bool:
|
def passes(self, price: dec) -> bool:
|
||||||
return self.isAbove and new_price >= self.limit or not self.isAbove and new_price <= self.limit
|
return self.isAbove and price >= self.limit or not self.isAbove and price <= self.limit
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -176,6 +177,9 @@ class Time:
|
|||||||
mode: TimeMode
|
mode: TimeMode
|
||||||
time: int
|
time: int
|
||||||
|
|
||||||
|
def timestamp(self, order_start: int):
|
||||||
|
return self.time if self.mode is TimeMode.Timestamp else order_start + self.time
|
||||||
|
|
||||||
|
|
||||||
DISTANT_PAST = 0
|
DISTANT_PAST = 0
|
||||||
DISTANT_FUTURE = 4294967295 # max uint32
|
DISTANT_FUTURE = 4294967295 # max uint32
|
||||||
@@ -194,8 +198,7 @@ class TimeConstraint (Constraint):
|
|||||||
return TimeConstraint(ConstraintMode.Time, 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 self._dump(TimeConstraint.TYPES, (self.earliest.mode.value, self.earliest.time, self.latest.mode.value, self.latest.time))
|
||||||
(self.earliest.mode.value, self.earliest.time, self.latest.mode.value, self.latest.time))
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -203,6 +206,9 @@ class Tranche:
|
|||||||
fraction: int # 18-decimal fraction of the order amount which is available to this tranche. must be <= 1
|
fraction: int # 18-decimal fraction of the order amount which is available to this tranche. must be <= 1
|
||||||
constraints: list[Constraint]
|
constraints: list[Constraint]
|
||||||
|
|
||||||
|
def fraction_of(self, amount):
|
||||||
|
return amount * self.fraction // 65535
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def load(obj):
|
def load(obj):
|
||||||
return Tranche(obj[0], [Constraint.load(c) for c in obj[1]])
|
return Tranche(obj[0], [Constraint.load(c) for c in obj[1]])
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ from dexorder.order.orderlib import SwapOrderStatus, SwapOrderState
|
|||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
@dataclass
|
@dataclass(frozen=True, eq=True)
|
||||||
class OrderKey:
|
class OrderKey:
|
||||||
vault: str
|
vault: str
|
||||||
order_index: int
|
order_index: int
|
||||||
@@ -20,7 +20,7 @@ class OrderKey:
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f'{self.vault}|{self.order_index}'
|
return f'{self.vault}|{self.order_index}'
|
||||||
|
|
||||||
@dataclass
|
@dataclass(frozen=True, eq=True)
|
||||||
class TrancheKey (OrderKey):
|
class TrancheKey (OrderKey):
|
||||||
tranche_index: int
|
tranche_index: int
|
||||||
|
|
||||||
@@ -39,11 +39,11 @@ class Filled:
|
|||||||
filled_out: int
|
filled_out: int
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def basic2remaining(basic):
|
def str2remaining(basic):
|
||||||
return Filled(*basic)
|
return Filled(*map(int,basic.split(','))) if basic else Filled(0,0)
|
||||||
|
|
||||||
def remaining2basic(self):
|
def remaining2str(self):
|
||||||
return self.filled_in, self.filled_out
|
return f'{self.filled_in},{self.filled_out}'
|
||||||
|
|
||||||
|
|
||||||
# todo oco groups
|
# todo oco groups
|
||||||
@@ -96,19 +96,16 @@ class Order:
|
|||||||
self.status: SwapOrderStatus = Order._statuses[key].copy()
|
self.status: SwapOrderStatus = Order._statuses[key].copy()
|
||||||
self.pool_address: str = self.status.order.pool_address
|
self.pool_address: str = self.status.order.pool_address
|
||||||
self.tranche_keys = [TrancheKey(key.vault, key.order_index, i) for i in range(len(self.status.trancheFilledIn))]
|
self.tranche_keys = [TrancheKey(key.vault, key.order_index, i) for i in range(len(self.status.trancheFilledIn))]
|
||||||
|
# various flattenings
|
||||||
|
self.order = self.status.order
|
||||||
|
self.amount = self.status.order.amount
|
||||||
|
self.amount_is_input = self.status.order.amountIsInput
|
||||||
|
self.tranche_amounts = [t.fraction_of(self.amount) for t in self.order.tranches]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state(self):
|
def state(self):
|
||||||
return self.status.state
|
return self.status.state
|
||||||
|
|
||||||
@property
|
|
||||||
def order(self):
|
|
||||||
return self.status.order
|
|
||||||
|
|
||||||
@property
|
|
||||||
def amount(self):
|
|
||||||
return self.order.amount
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def remaining(self):
|
def remaining(self):
|
||||||
return self.amount - self.filled
|
return self.amount - self.filled
|
||||||
@@ -134,10 +131,6 @@ class Order:
|
|||||||
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
|
||||||
|
|
||||||
@property
|
|
||||||
def amount_is_input(self):
|
|
||||||
return self.order.amountIsInput
|
|
||||||
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_open(self):
|
def is_open(self):
|
||||||
@@ -156,6 +149,7 @@ 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 """
|
||||||
@@ -163,16 +157,23 @@ class Order:
|
|||||||
status = self.status
|
status = self.status
|
||||||
status.state = final_state
|
status.state = final_state
|
||||||
if self.is_open:
|
if self.is_open:
|
||||||
del Order._open_keys[self.key]
|
Order._open_keys.remove(self.key)
|
||||||
|
# set final fill values in the status
|
||||||
filled = Order._order_filled[self.key]
|
filled = Order._order_filled[self.key]
|
||||||
del Order._order_filled[self.key]
|
try:
|
||||||
|
del Order._order_filled[self.key]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
status.filledIn = filled.filled_in
|
status.filledIn = filled.filled_in
|
||||||
status.filledOut = filled.filled_out
|
status.filledOut = filled.filled_out
|
||||||
for i, tk in enumerate(self.tranche_keys):
|
for i, tk in enumerate(self.tranche_keys):
|
||||||
filled = Order._tranche_filled[tk]
|
try:
|
||||||
del Order._tranche_filled[tk]
|
filled = Order._tranche_filled[tk]
|
||||||
status.trancheFilledIn[i] = filled.filled_in
|
del Order._tranche_filled[tk]
|
||||||
status.trancheFilledOut[i] = filled.filled_out
|
status.trancheFilledIn[i] = filled.filled_in
|
||||||
|
status.trancheFilledOut[i] = filled.filled_out
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
final_status = status.copy()
|
final_status = status.copy()
|
||||||
Order._statuses[self.key] = final_status # set the status in order to save it
|
Order._statuses[self.key] = final_status # set the status in order to save it
|
||||||
Order._statuses.unload(self.key) # but then unload from memory after root promotion
|
Order._statuses.unload(self.key) # but then unload from memory after root promotion
|
||||||
@@ -187,13 +188,16 @@ class Order:
|
|||||||
# open orders = the set of unfilled, not-canceled orders
|
# open orders = the set of unfilled, not-canceled orders
|
||||||
_open_keys: BlockSet[OrderKey] = BlockSet('oo', db=True, redis=True, str2key=OrderKey.str2key)
|
_open_keys: BlockSet[OrderKey] = BlockSet('oo', db=True, redis=True, str2key=OrderKey.str2key)
|
||||||
|
|
||||||
|
# underfunded vaults
|
||||||
|
_underfunded: BlockSet[str] = BlockSet('uv', db=True, redis=True)
|
||||||
|
|
||||||
# total remaining amount per order, for all unfilled, not-canceled orders
|
# total remaining amount per order, for all unfilled, not-canceled orders
|
||||||
_order_filled: BlockDict[OrderKey, Filled] = BlockDict(
|
_order_filled: BlockDict[OrderKey, Filled] = BlockDict(
|
||||||
'of', db=True, redis=True, str2key=OrderKey.str2key, value2basic=Filled.remaining2basic, basic2value=Filled.basic2remaining)
|
'of', db=True, redis=True, str2key=OrderKey.str2key, value2str=Filled.remaining2str, str2value=Filled.str2remaining)
|
||||||
|
|
||||||
# total remaining amount per tranche
|
# total remaining amount per tranche
|
||||||
_tranche_filled: BlockDict[TrancheKey, Filled] = BlockDict(
|
_tranche_filled: BlockDict[TrancheKey, Filled] = BlockDict(
|
||||||
'tf', db=True, redis=True, str2key=TrancheKey.str2key, value2basic=Filled.remaining2basic, basic2value=Filled.basic2remaining)
|
'tf', db=True, redis=True, str2key=TrancheKey.str2key, value2str=Filled.remaining2str, str2value=Filled.str2remaining)
|
||||||
|
|
||||||
|
|
||||||
active_orders: dict[OrderKey,Order] = {}
|
active_orders: dict[OrderKey,Order] = {}
|
||||||
|
|||||||
@@ -3,18 +3,18 @@ from enum import Enum
|
|||||||
from typing import Callable
|
from typing import Callable
|
||||||
|
|
||||||
from dexorder.blockstate import BlockSet, BlockDict
|
from dexorder.blockstate import BlockSet, BlockDict
|
||||||
from .orderlib import TimeConstraint, LimitConstraint, ConstraintMode
|
from .orderlib import TimeConstraint, LimitConstraint, ConstraintMode, SwapOrderState
|
||||||
from dexorder.util import defaultdictk
|
from dexorder.util import defaultdictk
|
||||||
from .orderstate import TrancheKey, Order
|
from .orderstate import TrancheKey, Order, OrderKey
|
||||||
from ..database.model.block import current_block
|
from ..database.model.block import current_block
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
# todo time and price triggers should be BlockSortedSets that support range queries
|
# todo time and price triggers should be BlockSortedSets that support range queries, for efficient lookup of triggers
|
||||||
TimeTrigger = Callable[[int, int], None] # func(previous_timestamp, current_timestamp)
|
TimeTrigger = Callable[[int], None] # func(timestamp)
|
||||||
time_triggers:BlockSet[TimeTrigger] = BlockSet('tt')
|
time_triggers:BlockSet[TimeTrigger] = BlockSet('tt')
|
||||||
|
|
||||||
PriceTrigger = Callable[[int, int], None] # pool previous price, pool new 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
|
||||||
|
|
||||||
execution_requests:BlockDict[TrancheKey,int] = BlockDict('te') # value is block height of the request
|
execution_requests:BlockDict[TrancheKey,int] = BlockDict('te') # value is block height of the request
|
||||||
@@ -37,9 +37,10 @@ class TrancheTrigger:
|
|||||||
self.order = order
|
self.order = order
|
||||||
self.tk = tranche_key
|
self.tk = tranche_key
|
||||||
self.status = TrancheStatus.Early
|
self.status = TrancheStatus.Early
|
||||||
|
start = self.order.status.start
|
||||||
|
|
||||||
tranche = order.order.tranches[self.tk.tranche_index]
|
tranche = order.order.tranches[self.tk.tranche_index]
|
||||||
tranche_amount = order.amount * tranche.fraction // 10**18
|
tranche_amount = tranche.fraction_of(order.amount)
|
||||||
tranche_filled = order.tranche_filled(self.tk)
|
tranche_filled = order.tranche_filled(self.tk)
|
||||||
tranche_remaining = tranche_amount - tranche_filled
|
tranche_remaining = tranche_amount - tranche_filled
|
||||||
|
|
||||||
@@ -47,12 +48,14 @@ class TrancheTrigger:
|
|||||||
self.status = TrancheStatus.Filled
|
self.status = TrancheStatus.Filled
|
||||||
return
|
return
|
||||||
|
|
||||||
self.time_constraint = time_constraint = None
|
self.time_constraint = 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:
|
||||||
c: TimeConstraint
|
c: TimeConstraint
|
||||||
time_constraint = (c.earliest, c.latest) if time_constraint is None else intersect_ranges(*time_constraint, c.earliest, c.latest)
|
earliest = c.earliest.timestamp(start)
|
||||||
|
latest = c.latest.timestamp(start)
|
||||||
|
time_constraint = (earliest, latest) if time_constraint is None else intersect_ranges(*time_constraint, earliest, latest)
|
||||||
elif c.mode == ConstraintMode.Limit:
|
elif c.mode == ConstraintMode.Limit:
|
||||||
c: LimitConstraint
|
c: LimitConstraint
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
@@ -70,13 +73,15 @@ class TrancheTrigger:
|
|||||||
|
|
||||||
def enable_time_trigger(self):
|
def enable_time_trigger(self):
|
||||||
if self.time_constraint:
|
if self.time_constraint:
|
||||||
|
log.debug(f'enable_time_trigger')
|
||||||
time_triggers.add(self.time_trigger)
|
time_triggers.add(self.time_trigger)
|
||||||
|
|
||||||
def disable_time_trigger(self):
|
def disable_time_trigger(self):
|
||||||
if self.time_constraint:
|
if self.time_constraint:
|
||||||
time_triggers.remove(self.time_trigger)
|
time_triggers.remove(self.time_trigger)
|
||||||
|
|
||||||
def time_trigger(self, _prev, now):
|
def time_trigger(self, now):
|
||||||
|
log.debug(f'time_trigger {now}')
|
||||||
if now >= self.time_constraint[1]:
|
if now >= self.time_constraint[1]:
|
||||||
self.disable()
|
self.disable()
|
||||||
self.status = TrancheStatus.Expired
|
self.status = TrancheStatus.Expired
|
||||||
@@ -90,8 +95,8 @@ class TrancheTrigger:
|
|||||||
def disable_price_trigger(self):
|
def disable_price_trigger(self):
|
||||||
price_triggers[self.order.pool_address].remove(self.price_trigger)
|
price_triggers[self.order.pool_address].remove(self.price_trigger)
|
||||||
|
|
||||||
def price_trigger(self, prev, cur):
|
def price_trigger(self, cur):
|
||||||
if all(pc.passes(prev,cur) for pc in self.price_constraints):
|
if all(pc.passes(cur) for pc in self.price_constraints):
|
||||||
self.execute()
|
self.execute()
|
||||||
|
|
||||||
def execute(self):
|
def execute(self):
|
||||||
@@ -102,13 +107,44 @@ class TrancheTrigger:
|
|||||||
self.disable_time_trigger()
|
self.disable_time_trigger()
|
||||||
self.disable_price_trigger()
|
self.disable_price_trigger()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def closed(self):
|
||||||
|
return self.status in (TrancheStatus.Filled, TrancheStatus.Expired)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def open(self):
|
||||||
|
return not self.closed
|
||||||
|
|
||||||
|
|
||||||
class OrderTriggers:
|
class OrderTriggers:
|
||||||
|
instances: dict[OrderKey, 'OrderTriggers'] = {}
|
||||||
|
|
||||||
def __init__(self, order: Order):
|
def __init__(self, order: Order):
|
||||||
|
assert order.key not in OrderTriggers.instances
|
||||||
self.order = order
|
self.order = order
|
||||||
self.triggers = [TrancheTrigger(order, tk) for tk in self.order.tranche_keys]
|
self.triggers = [TrancheTrigger(order, tk) for tk in self.order.tranche_keys]
|
||||||
|
OrderTriggers.instances[order.key] = self
|
||||||
|
|
||||||
def disable(self):
|
def disable(self):
|
||||||
for t in self.triggers:
|
for t in self.triggers:
|
||||||
t.disable()
|
t.disable()
|
||||||
|
del OrderTriggers.instances[self.order.key]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def closed(self):
|
||||||
|
return all(t.closed for t in self.triggers)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def open(self):
|
||||||
|
return not self.closed
|
||||||
|
|
||||||
|
|
||||||
|
def close_order_and_disable_triggers(order_key: OrderKey, final_state: SwapOrderState):
|
||||||
|
Order.instances[order_key].complete(final_state)
|
||||||
|
try:
|
||||||
|
triggers = OrderTriggers.instances[order_key]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
triggers.disable()
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import logging
|
import logging
|
||||||
from typing import Callable, Union, Any, Iterable
|
from typing import Callable, Union, Any, Iterable, Optional
|
||||||
|
|
||||||
from web3.contract.contract import ContractEvents
|
from web3.contract.contract import ContractEvents
|
||||||
from web3.exceptions import LogTopicError, MismatchedABI
|
from web3.exceptions import LogTopicError, MismatchedABI
|
||||||
@@ -13,6 +13,7 @@ from dexorder.blockstate.diff import DiffEntryItem
|
|||||||
from dexorder.database.model import Block
|
from dexorder.database.model import Block
|
||||||
from dexorder.database.model.block import current_block, latest_block
|
from dexorder.database.model.block import current_block, latest_block
|
||||||
from dexorder.event_handler import setup_logevent_triggers
|
from dexorder.event_handler import setup_logevent_triggers
|
||||||
|
from dexorder.order.triggers import time_triggers
|
||||||
from dexorder.util import hexstr, topic
|
from dexorder.util import hexstr, topic
|
||||||
from dexorder.util.async_util import maywait
|
from dexorder.util.async_util import maywait
|
||||||
|
|
||||||
@@ -103,23 +104,30 @@ class BlockStateRunner:
|
|||||||
if fork.disjoint:
|
if fork.disjoint:
|
||||||
# backfill batches
|
# backfill batches
|
||||||
for callback, event, log_filter in self.events:
|
for callback, event, log_filter in self.events:
|
||||||
from_height = state.root_block.height + 1
|
if event is None:
|
||||||
end_height = block.height
|
batches.append(None)
|
||||||
while from_height <= end_height:
|
else:
|
||||||
to_height = min(end_height, from_height + chain.batch_size - 1)
|
from_height = state.root_block.height + 1
|
||||||
lf = dict(log_filter)
|
end_height = block.height
|
||||||
lf['fromBlock'] = from_height
|
while from_height <= end_height:
|
||||||
lf['toBlock'] = to_height
|
to_height = min(end_height, from_height + chain.batch_size - 1)
|
||||||
log.debug(f'batch backfill {from_height} - {to_height}')
|
lf = dict(log_filter)
|
||||||
batches.append((w3.eth.get_logs(lf), callback, event, lf))
|
lf['fromBlock'] = from_height
|
||||||
from_height += chain.batch_size
|
lf['toBlock'] = to_height
|
||||||
|
log.debug(f'batch backfill {from_height} - {to_height}')
|
||||||
|
batches.append((w3.eth.get_logs(lf), callback, event, lf))
|
||||||
|
from_height += chain.batch_size
|
||||||
else:
|
else:
|
||||||
# event callbacks are triggered in the order in which they're registered. the events passed to
|
# event callbacks are triggered in the order in which they're registered. the events passed to
|
||||||
# each callback are in block transaction order
|
# each callback are in block transaction order
|
||||||
for callback, event, log_filter in self.events:
|
for callback, event, log_filter in self.events:
|
||||||
lf = dict(log_filter)
|
if log_filter is None:
|
||||||
lf['blockHash'] = hexstr(block.hash)
|
batches.append((None, callback, event, None))
|
||||||
batches.append((w3.eth.get_logs(lf), callback, event, log_filter))
|
else:
|
||||||
|
lf = dict(log_filter)
|
||||||
|
lf['blockHash'] = hexstr(block.hash)
|
||||||
|
print(lf)
|
||||||
|
batches.append((w3.eth.get_logs(lf), callback, event, log_filter))
|
||||||
|
|
||||||
# set up for callbacks
|
# set up for callbacks
|
||||||
current_block.set(block)
|
current_block.set(block)
|
||||||
@@ -129,17 +137,20 @@ class BlockStateRunner:
|
|||||||
session.add(block)
|
session.add(block)
|
||||||
pubs = []
|
pubs = []
|
||||||
current_pub.set(lambda room, evnt, *args: pubs.append((room, evnt, args)))
|
current_pub.set(lambda room, evnt, *args: pubs.append((room, evnt, args)))
|
||||||
# callbacks
|
# logevent callbacks
|
||||||
for future,callback,event,filter_args in batches:
|
for future,callback,event,filter_args in batches:
|
||||||
log_events = await future
|
if future is None:
|
||||||
for log_event in log_events:
|
await maywait(callback()) # non-log callback
|
||||||
try:
|
else:
|
||||||
parsed = event.process_log(log_event)
|
log_events = await future
|
||||||
except (LogTopicError, MismatchedABI):
|
for log_event in log_events:
|
||||||
pass
|
try:
|
||||||
else:
|
parsed = event.process_log(log_event) if event is not None else log_event
|
||||||
# todo try/except for known retryable errors
|
except (LogTopicError, MismatchedABI):
|
||||||
await maywait(callback(parsed))
|
pass
|
||||||
|
else:
|
||||||
|
# todo try/except for known retryable errors
|
||||||
|
await maywait(callback(parsed))
|
||||||
|
|
||||||
# todo check for reorg and generate a reorg diff list
|
# todo check for reorg and generate a reorg diff list
|
||||||
diff_items = state.diffs_by_hash[block.hash]
|
diff_items = state.diffs_by_hash[block.hash]
|
||||||
@@ -167,9 +178,10 @@ class BlockStateRunner:
|
|||||||
session.commit()
|
session.commit()
|
||||||
log.info(f'completed block {block}')
|
log.info(f'completed block {block}')
|
||||||
|
|
||||||
|
def add_event_trigger(self, callback: Callable[[dict], None], event: ContractEvents = None, log_filter: Union[dict, str] = None):
|
||||||
|
"""
|
||||||
def add_event_trigger(self, callback:Callable[[dict],None], event: ContractEvents, log_filter: Union[dict,str]=None):
|
if event is None, the callback is still invoked in the series of log handlers but with no argument instead of logs
|
||||||
if log_filter is None:
|
"""
|
||||||
log_filter = {'topics':[topic(event.abi)]}
|
if log_filter is None and event is not None:
|
||||||
|
log_filter = {'topics': [topic(event.abi)]}
|
||||||
self.events.append((callback, event, log_filter))
|
self.events.append((callback, event, log_filter))
|
||||||
|
|||||||
@@ -35,6 +35,10 @@ def hexbytes(value: str):
|
|||||||
return bytes.fromhex(value[2:] if value.startswith('0x') else value)
|
return bytes.fromhex(value[2:] if value.startswith('0x') else value)
|
||||||
|
|
||||||
|
|
||||||
|
def hexint(value: str):
|
||||||
|
return int(value[2:] if value.startswith('0x') else value, 16)
|
||||||
|
|
||||||
|
|
||||||
def _keystr1(value):
|
def _keystr1(value):
|
||||||
t = type(value)
|
t = type(value)
|
||||||
return value if t is str else value.hex() if t is HexBytes else '0x' + value.hex() if t is bytes else str(value)
|
return value if t is str else value.hex() if t is HexBytes else '0x' + value.hex() if t is bytes else str(value)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from eth_utils import keccak, to_bytes, to_checksum_address
|
|||||||
from dexorder import config, current_w3
|
from dexorder import config, current_w3
|
||||||
from dexorder.base.chain import current_chain
|
from dexorder.base.chain import current_chain
|
||||||
from dexorder.contract import ContractProxy
|
from dexorder.contract import ContractProxy
|
||||||
|
from dexorder.util import hexstr
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -24,6 +25,7 @@ def get_factory() -> ContractProxy:
|
|||||||
if tx['contractName'] == 'Factory':
|
if tx['contractName'] == 'Factory':
|
||||||
addr = tx['contractAddress']
|
addr = tx['contractAddress']
|
||||||
found = factory[chain_id] = ContractProxy(addr, 'Factory')
|
found = factory[chain_id] = ContractProxy(addr, 'Factory')
|
||||||
|
log.info(f'Factory {addr}')
|
||||||
break
|
break
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
log.warning(f'Could not find deployment for chain {chain_id} "{deployment_tag}"')
|
log.warning(f'Could not find deployment for chain {chain_id} "{deployment_tag}"')
|
||||||
@@ -46,6 +48,7 @@ def vault_address(owner, num):
|
|||||||
with open('../contract/out/Vault.sol/Vault.json', 'rt') as _file:
|
with open('../contract/out/Vault.sol/Vault.json', 'rt') as _file:
|
||||||
vault_info = json.load(_file)
|
vault_info = json.load(_file)
|
||||||
VAULT_INIT_CODE_HASH = keccak(to_bytes(hexstr=vault_info['bytecode']['object']))
|
VAULT_INIT_CODE_HASH = keccak(to_bytes(hexstr=vault_info['bytecode']['object']))
|
||||||
|
log.info(f'VAULT_INIT_CODE_HASH {hexstr(VAULT_INIT_CODE_HASH)}')
|
||||||
salt = keccak(encode_packed(['address','uint8'],[owner,num]))
|
salt = keccak(encode_packed(['address','uint8'],[owner,num]))
|
||||||
contract_address = keccak(
|
contract_address = keccak(
|
||||||
b"\xff"
|
b"\xff"
|
||||||
Reference in New Issue
Block a user