bin/examine.py; readonly state; debug logs for Underfunded
This commit is contained in:
15
bin/examine
Executable file
15
bin/examine
Executable file
@@ -0,0 +1,15 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
kubectl port-forward postgres-0 5431:5432 &
|
||||||
|
PF_PID=$!
|
||||||
|
|
||||||
|
shutdown () {
|
||||||
|
kill $PF_PID
|
||||||
|
wait
|
||||||
|
}
|
||||||
|
|
||||||
|
trap shutdown INT TERM
|
||||||
|
|
||||||
|
PYTHONPATH=src python -m dexorder.bin.examine rpc_url=arbitrum_dxod db_url=postgres://dexorder@localhost:5431/dexorder "$@"
|
||||||
|
|
||||||
|
shutdown
|
||||||
@@ -59,7 +59,7 @@ _cwd() # do this first so that config has the right current working directory
|
|||||||
|
|
||||||
# ordering here is important!
|
# ordering here is important!
|
||||||
from .base.chain import Blockchain # the singletons are loaded into the dexorder.blockchain.* namespace
|
from .base.chain import Blockchain # the singletons are loaded into the dexorder.blockchain.* namespace
|
||||||
from .util import async_yield
|
from .util import async_yield, json
|
||||||
from .base.fixed import Fixed2, FixedDecimals, Dec18
|
from .base.fixed import Fixed2, FixedDecimals, Dec18
|
||||||
from .configuration import config
|
from .configuration import config
|
||||||
from .base.account import Account
|
from .base.account import Account
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from dataclasses import dataclass
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from dexorder import timestamp
|
from dexorder import timestamp, from_timestamp
|
||||||
from dexorder.util import hexbytes
|
from dexorder.util import hexbytes
|
||||||
from dexorder.util.convert import decode_IEEE754
|
from dexorder.util.convert import decode_IEEE754
|
||||||
|
|
||||||
@@ -250,6 +250,26 @@ class ElaboratedSwapOrderStatus:
|
|||||||
def copy(self):
|
def copy(self):
|
||||||
return copy.deepcopy(self)
|
return copy.deepcopy(self)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
msg = f'''
|
||||||
|
SwapOrder
|
||||||
|
status: {self.state.name}
|
||||||
|
in: {self.order.tokenIn}
|
||||||
|
out: {self.order.tokenOut}
|
||||||
|
exchange: {self.order.route.exchange.name, self.order.route.fee}
|
||||||
|
amount: {"input" if self.order.amountIsInput else "output"} {self.filledIn if self.order.amountIsInput else self.filledOut}/{self.order.amount}{" to owner" if self.order.outputDirectlyToOwner else ""}
|
||||||
|
minFill: {self.order.minFillAmount}
|
||||||
|
inverted: {self.order.inverted}
|
||||||
|
tranches:
|
||||||
|
'''
|
||||||
|
for i in range(len(self.trancheStatus)):
|
||||||
|
tranche = self.order.tranches[i]
|
||||||
|
ts = self.trancheStatus[i]
|
||||||
|
msg += f' {tranche}\n'
|
||||||
|
for fill in ts.fills:
|
||||||
|
msg += f' {fill}\n'
|
||||||
|
return msg
|
||||||
|
|
||||||
|
|
||||||
NO_OCO = 18446744073709551615 # max uint64
|
NO_OCO = 18446744073709551615 # max uint64
|
||||||
|
|
||||||
@@ -344,7 +364,7 @@ class Tranche:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
msg = f'{self.fraction/MAX_FRACTION:.1%} {"start+" if self.startTimeIsRelative else ""}{self.startTime} to {"start+" if self.startTimeIsRelative else ""}{self.endTime}'
|
msg = f'{self.fraction/MAX_FRACTION:.1%} {"start+" if self.startTimeIsRelative else ""}{from_timestamp(self.startTime)} to {"start+" if self.startTimeIsRelative else ""}{from_timestamp(self.endTime)}'
|
||||||
if self.marketOrder:
|
if self.marketOrder:
|
||||||
# for marketOrders, minLine.intercept is the slippage
|
# for marketOrders, minLine.intercept is the slippage
|
||||||
msg += f' market order slippage {self.minLine.intercept:.2%}'
|
msg += f' market order slippage {self.minLine.intercept:.2%}'
|
||||||
|
|||||||
77
src/dexorder/bin/examine.py
Normal file
77
src/dexorder/bin/examine.py
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import argparse
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from dexorder import db, blockchain
|
||||||
|
from dexorder.base.order import OrderKey
|
||||||
|
from dexorder.blocks import current_block, get_block
|
||||||
|
from dexorder.blockstate import current_blockstate
|
||||||
|
from dexorder.blockstate.blockdata import BlockData
|
||||||
|
from dexorder.blockstate.db_state import DbState
|
||||||
|
from dexorder.contract.dexorder import VaultContract
|
||||||
|
from dexorder.order.orderstate import Order
|
||||||
|
from dexorder.tokens import adjust_decimals
|
||||||
|
from dexorder.vault_blockdata import vault_balances, pretty_balances
|
||||||
|
from dexorder.bin.executable import execute
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def command_vault_argparse(subparsers):
|
||||||
|
parser = subparsers.add_parser('vault', help='show the vault\'s balances and orders')
|
||||||
|
parser.add_argument('address', help='address of the vault')
|
||||||
|
parser.add_argument('--all', help='show all orders including closed ones', action='store_true')
|
||||||
|
# parser.add_argument('--json', help='output in JSON format', action='store_true')
|
||||||
|
|
||||||
|
async def command_vault(args):
|
||||||
|
balances = vault_balances.get(args.address, {})
|
||||||
|
print(f'Vault {args.address} v{await VaultContract(args.address).version()}')
|
||||||
|
print(f'Balances:')
|
||||||
|
print(pretty_balances({k: (await adjust_decimals(k, v)) for k, v in balances.items()}))
|
||||||
|
print(f'Orders:')
|
||||||
|
i = 0
|
||||||
|
while True:
|
||||||
|
key = OrderKey(args.address, i)
|
||||||
|
try:
|
||||||
|
order = Order.of(key)
|
||||||
|
except KeyError:
|
||||||
|
break
|
||||||
|
if args.all or order.is_open:
|
||||||
|
print(await order.pprint())
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
# for key in Order.open_orders:
|
||||||
|
# order = Order.of(key)
|
||||||
|
# if args.json:
|
||||||
|
# print(json.dumps(order.status.dump()))
|
||||||
|
# else:
|
||||||
|
# print()
|
||||||
|
# print(order)
|
||||||
|
|
||||||
|
async def main(args: list):
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument('--chain-id', default=None)
|
||||||
|
subparsers = parser.add_subparsers(dest='command')
|
||||||
|
for name in globals():
|
||||||
|
if name.startswith('command_') and name.endswith('_argparse'):
|
||||||
|
globals()[name](subparsers)
|
||||||
|
parsed = parser.parse_args(args)
|
||||||
|
print(parsed)
|
||||||
|
try:
|
||||||
|
subcommand = globals()[f'command_{parsed.command}']
|
||||||
|
except KeyError:
|
||||||
|
parser.print_help()
|
||||||
|
exit(1)
|
||||||
|
await blockchain.connect()
|
||||||
|
db.connect()
|
||||||
|
db_state = DbState(BlockData.by_opt('db'))
|
||||||
|
with db.transaction():
|
||||||
|
state = await db_state.load()
|
||||||
|
state.readonly = True
|
||||||
|
current_blockstate.set(state)
|
||||||
|
block = await get_block(state.root_hash)
|
||||||
|
current_block.set(block)
|
||||||
|
await subcommand(parsed)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
execute(main, parse_args=True)
|
||||||
@@ -33,11 +33,11 @@ def split_args():
|
|||||||
omegaconf_args = []
|
omegaconf_args = []
|
||||||
regular_args = []
|
regular_args = []
|
||||||
for arg in sys.argv[1:]:
|
for arg in sys.argv[1:]:
|
||||||
if '=' in arg:
|
if '=' in arg and not arg.startswith('--'):
|
||||||
key, value = arg.split('=', 1)
|
key, value = arg.split('=', 1)
|
||||||
if hasattr(Config, key):
|
if hasattr(Config, key):
|
||||||
omegaconf_args.append(arg)
|
omegaconf_args.append(arg)
|
||||||
else:
|
continue
|
||||||
regular_args.append(arg)
|
regular_args.append(arg)
|
||||||
return omegaconf_args, regular_args
|
return omegaconf_args, regular_args
|
||||||
|
|
||||||
@@ -67,12 +67,10 @@ def execute(main:Callable[...,Coroutine[Any,Any,Any]], shutdown=None, *, parse_l
|
|||||||
log.info('Logging configured to default')
|
log.info('Logging configured to default')
|
||||||
xconf = None
|
xconf = None
|
||||||
if parse_args:
|
if parse_args:
|
||||||
if callable(parse_args) or isinstance(parse_args, type):
|
|
||||||
omegaconf_args, regular_args = split_args()
|
|
||||||
else:
|
|
||||||
omegaconf_args = None
|
|
||||||
# NOTE: there is special command-line argument handling in config/load.py to get a config filename.
|
# NOTE: there is special command-line argument handling in config/load.py to get a config filename.
|
||||||
# The -c/--config flag MUST BE FIRST if present.
|
# The -c/--config flag MUST BE FIRST if present.
|
||||||
|
# The rest of the arguments are split by format into key=value for omegaconf and anything else is "regular args"
|
||||||
|
omegaconf_args, regular_args = split_args()
|
||||||
configuration.parse_args(omegaconf_args)
|
configuration.parse_args(omegaconf_args)
|
||||||
# must check for `type` before `callable`, because types are also callables
|
# must check for `type` before `callable`, because types are also callables
|
||||||
if isinstance(parse_args, type):
|
if isinstance(parse_args, type):
|
||||||
@@ -81,6 +79,9 @@ def execute(main:Callable[...,Coroutine[Any,Any,Any]], shutdown=None, *, parse_l
|
|||||||
elif callable(parse_args):
|
elif callable(parse_args):
|
||||||
# noinspection PyUnboundLocalVariable
|
# noinspection PyUnboundLocalVariable
|
||||||
xconf = parse_args(regular_args)
|
xconf = parse_args(regular_args)
|
||||||
|
else:
|
||||||
|
# just pass the regular args to main
|
||||||
|
xconf = regular_args
|
||||||
|
|
||||||
init_alerts()
|
init_alerts()
|
||||||
|
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ class BlockData (Generic[T]):
|
|||||||
if self.lazy_getitem:
|
if self.lazy_getitem:
|
||||||
lazy = self.lazy_getitem(self, item)
|
lazy = self.lazy_getitem(self, item)
|
||||||
if lazy is not NARG:
|
if lazy is not NARG:
|
||||||
state.set(state.root_fork, self.series, item, lazy)
|
state.set(state.root_fork, self.series, item, lazy, readonly_override=True)
|
||||||
result = lazy
|
result = lazy
|
||||||
if result is NARG:
|
if result is NARG:
|
||||||
raise KeyError
|
raise KeyError
|
||||||
|
|||||||
@@ -53,7 +53,10 @@ class BlockState:
|
|||||||
with a diff height of the root branch or older is always part of the finalized blockchain.
|
with a diff height of the root branch or older is always part of the finalized blockchain.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
class ReadOnlyError(Exception): ...
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
self.readonly = False
|
||||||
self._root_branch: Optional[Branch] = None
|
self._root_branch: Optional[Branch] = None
|
||||||
self._root_fork: Optional[Fork] = None
|
self._root_fork: Optional[Fork] = None
|
||||||
self.height: int = 0 # highest branch seen
|
self.height: int = 0 # highest branch seen
|
||||||
@@ -80,6 +83,8 @@ class BlockState:
|
|||||||
|
|
||||||
@root_branch.setter
|
@root_branch.setter
|
||||||
def root_branch(self, value: Branch):
|
def root_branch(self, value: Branch):
|
||||||
|
if self.readonly:
|
||||||
|
raise self.ReadOnlyError()
|
||||||
self._root_branch = value
|
self._root_branch = value
|
||||||
self._root_fork = Fork([value])
|
self._root_fork = Fork([value])
|
||||||
|
|
||||||
@@ -92,6 +97,8 @@ class BlockState:
|
|||||||
return self._root_branch.head
|
return self._root_branch.head
|
||||||
|
|
||||||
def init_root_block(self, root_block: Block) -> Fork:
|
def init_root_block(self, root_block: Block) -> Fork:
|
||||||
|
if self.readonly:
|
||||||
|
raise self.ReadOnlyError()
|
||||||
assert self.root_branch is None
|
assert self.root_branch is None
|
||||||
return self.add_branch(Branch.from_block(root_block))
|
return self.add_branch(Branch.from_block(root_block))
|
||||||
|
|
||||||
@@ -113,6 +120,8 @@ class BlockState:
|
|||||||
should only be set to False when it is assured that the branch may be joined by height alone, because
|
should only be set to False when it is assured that the branch may be joined by height alone, because
|
||||||
the branch join is known to be at a live-blockchain-finalized height.
|
the branch join is known to be at a live-blockchain-finalized height.
|
||||||
"""
|
"""
|
||||||
|
if self.readonly:
|
||||||
|
raise self.ReadOnlyError()
|
||||||
assert branch.id not in self.branches_by_id
|
assert branch.id not in self.branches_by_id
|
||||||
|
|
||||||
if self.root_branch is None:
|
if self.root_branch is None:
|
||||||
@@ -155,6 +164,8 @@ class BlockState:
|
|||||||
|
|
||||||
|
|
||||||
def remove_branch(self, branch: Branch, *, remove_series_diffs=True):
|
def remove_branch(self, branch: Branch, *, remove_series_diffs=True):
|
||||||
|
if self.readonly:
|
||||||
|
raise self.ReadOnlyError()
|
||||||
if branch.height == self.height and len(self.branches_by_height[branch.height]) == 1:
|
if branch.height == self.height and len(self.branches_by_height[branch.height]) == 1:
|
||||||
# this is the only branch at this height: compute the new lower height
|
# this is the only branch at this height: compute the new lower height
|
||||||
other_heights = [b.height for b in self.branches_by_id.values() if b is not branch]
|
other_heights = [b.height for b in self.branches_by_id.values() if b is not branch]
|
||||||
@@ -210,7 +221,9 @@ class BlockState:
|
|||||||
return DELETE
|
return DELETE
|
||||||
|
|
||||||
|
|
||||||
def set(self, fork: Fork, series, key, value, overwrite=True):
|
def set(self, fork: Fork, series, key, value, overwrite=True, *, readonly_override=False):
|
||||||
|
if not readonly_override and self.readonly:
|
||||||
|
raise self.ReadOnlyError()
|
||||||
# first look for an existing value
|
# first look for an existing value
|
||||||
branch = fork.branch
|
branch = fork.branch
|
||||||
diffs = self.diffs_by_series.get(series,{}).get(key)
|
diffs = self.diffs_by_series.get(series,{}).get(key)
|
||||||
@@ -236,6 +249,8 @@ class BlockState:
|
|||||||
return old_value
|
return old_value
|
||||||
|
|
||||||
def unload(self, fork: Optional[Fork], series, key):
|
def unload(self, fork: Optional[Fork], series, key):
|
||||||
|
if self.readonly:
|
||||||
|
raise self.ReadOnlyError()
|
||||||
self.unloads[fork.branch_id].append((series, key))
|
self.unloads[fork.branch_id].append((series, key))
|
||||||
|
|
||||||
def iteritems(self, fork: Optional[Fork], series):
|
def iteritems(self, fork: Optional[Fork], series):
|
||||||
@@ -285,6 +300,8 @@ class BlockState:
|
|||||||
|
|
||||||
Returns the set of diffs for the promoted fork.
|
Returns the set of diffs for the promoted fork.
|
||||||
"""
|
"""
|
||||||
|
if self.readonly:
|
||||||
|
raise self.ReadOnlyError()
|
||||||
found_root = False
|
found_root = False
|
||||||
promotion_branches = []
|
promotion_branches = []
|
||||||
for branch in reversed(fork.branches):
|
for branch in reversed(fork.branches):
|
||||||
@@ -350,6 +367,7 @@ class FinalizedBlockState:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
self.readonly = False
|
||||||
self.data = {}
|
self.data = {}
|
||||||
self.by_hash = {}
|
self.by_hash = {}
|
||||||
|
|
||||||
@@ -361,6 +379,8 @@ class FinalizedBlockState:
|
|||||||
|
|
||||||
def set(self, _fork: Optional[Fork], series, key, value, overwrite=True):
|
def set(self, _fork: Optional[Fork], series, key, value, overwrite=True):
|
||||||
assert overwrite
|
assert overwrite
|
||||||
|
if self.readonly:
|
||||||
|
raise BlockState.ReadOnlyError()
|
||||||
self.data.setdefault(series, {})[key] = value
|
self.data.setdefault(series, {})[key] = value
|
||||||
|
|
||||||
def iteritems(self, _fork: Optional[Fork], series):
|
def iteritems(self, _fork: Optional[Fork], series):
|
||||||
@@ -373,6 +393,8 @@ class FinalizedBlockState:
|
|||||||
return self.data.get(series,{}).values()
|
return self.data.get(series,{}).values()
|
||||||
|
|
||||||
def delete_series(self, _fork: Optional[Fork], series: str):
|
def delete_series(self, _fork: Optional[Fork], series: str):
|
||||||
|
if self.readonly:
|
||||||
|
raise BlockState.ReadOnlyError()
|
||||||
del self.data[series]
|
del self.data[series]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from omegaconf.errors import OmegaConfBaseException
|
|||||||
|
|
||||||
from .schema import Config
|
from .schema import Config
|
||||||
|
|
||||||
schema = OmegaConf.structured(Config())
|
schema = OmegaConf.structured(Config(), flags={'struct': False})
|
||||||
|
|
||||||
_config_file = 'dexorder.toml'
|
_config_file = 'dexorder.toml'
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ class Config:
|
|||||||
ws_url: Optional[str] = 'ws://localhost:8545'
|
ws_url: Optional[str] = 'ws://localhost:8545'
|
||||||
rpc_urls: Optional[dict[str,str]] = field(default_factory=dict)
|
rpc_urls: Optional[dict[str,str]] = field(default_factory=dict)
|
||||||
db_url: Optional[str] = 'postgresql://dexorder:redroxed@localhost/dexorder'
|
db_url: Optional[str] = 'postgresql://dexorder:redroxed@localhost/dexorder'
|
||||||
|
db_readonly: bool = False
|
||||||
dump_sql: bool = False
|
dump_sql: bool = False
|
||||||
redis_url: Optional[str] = 'redis://localhost:6379'
|
redis_url: Optional[str] = 'redis://localhost:6379'
|
||||||
|
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
import logging
|
|
||||||
|
|
||||||
from dexorder import db
|
|
||||||
from dexorder.contract import ERC20, CONTRACT_ERRORS
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
async def token_decimals(addr):
|
|
||||||
key = f'td|{addr}'
|
|
||||||
try:
|
|
||||||
return db.kv[key]
|
|
||||||
except KeyError:
|
|
||||||
# noinspection PyBroadException
|
|
||||||
try:
|
|
||||||
decimals = await ERC20(addr).decimals()
|
|
||||||
except CONTRACT_ERRORS:
|
|
||||||
log.debug(f'token {addr} has no decimals()')
|
|
||||||
decimals = 0
|
|
||||||
except Exception:
|
|
||||||
log.debug(f'could not get token decimals for {addr}')
|
|
||||||
return None
|
|
||||||
db.kv[key] = decimals
|
|
||||||
return decimals
|
|
||||||
@@ -3,7 +3,7 @@ import logging
|
|||||||
from contextvars import ContextVar
|
from contextvars import ContextVar
|
||||||
|
|
||||||
import sqlalchemy
|
import sqlalchemy
|
||||||
from sqlalchemy import Engine
|
from sqlalchemy import Engine, event
|
||||||
from sqlalchemy.orm import Session, SessionTransaction
|
from sqlalchemy.orm import Session, SessionTransaction
|
||||||
|
|
||||||
from .migrate import migrate_database
|
from .migrate import migrate_database
|
||||||
@@ -99,7 +99,7 @@ class Db:
|
|||||||
_session.set(None)
|
_session.set(None)
|
||||||
|
|
||||||
# noinspection PyShadowingNames
|
# noinspection PyShadowingNames
|
||||||
def connect(self, url=None, migrate=True, reconnect=False, dump_sql=None):
|
def connect(self, url=None, migrate=True, reconnect=False, dump_sql=None, readonly:bool=None):
|
||||||
if _engine.get() is not None and not reconnect:
|
if _engine.get() is not None and not reconnect:
|
||||||
return None
|
return None
|
||||||
if url is None:
|
if url is None:
|
||||||
@@ -114,6 +114,19 @@ class Db:
|
|||||||
if dump_sql is None:
|
if dump_sql is None:
|
||||||
dump_sql = config.dump_sql
|
dump_sql = config.dump_sql
|
||||||
engine = sqlalchemy.create_engine(url, echo=dump_sql, json_serializer=json.dumps, json_deserializer=json.loads)
|
engine = sqlalchemy.create_engine(url, echo=dump_sql, json_serializer=json.dumps, json_deserializer=json.loads)
|
||||||
|
|
||||||
|
if readonly is None:
|
||||||
|
readonly = config.db_readonly
|
||||||
|
if readonly:
|
||||||
|
@event.listens_for(engine, "connect")
|
||||||
|
def set_readonly(dbapi_connection, _connection_record):
|
||||||
|
cursor = dbapi_connection.cursor()
|
||||||
|
try:
|
||||||
|
cursor.execute("SET default_transaction_read_only = on;")
|
||||||
|
log.info('database connection set to READ ONLY')
|
||||||
|
finally:
|
||||||
|
cursor.close()
|
||||||
|
|
||||||
if migrate:
|
if migrate:
|
||||||
migrate_database(url)
|
migrate_database(url)
|
||||||
with engine.connect() as connection:
|
with engine.connect() as connection:
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ async def handle_order_placed(event: EventData):
|
|||||||
log.debug(f'raw order status {obj}')
|
log.debug(f'raw order status {obj}')
|
||||||
order = Order.create(addr, index, event['transactionHash'], obj)
|
order = Order.create(addr, index, event['transactionHash'], obj)
|
||||||
await activate_order(order)
|
await activate_order(order)
|
||||||
log.debug(f'new order {order.key}{order}')
|
log.debug(f'new order {order.key} {await order.pprint()}')
|
||||||
|
|
||||||
|
|
||||||
async def handle_swap_filled(event: EventData):
|
async def handle_swap_filled(event: EventData):
|
||||||
|
|||||||
@@ -3,13 +3,14 @@ import logging
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import overload
|
from typing import overload
|
||||||
|
|
||||||
from dexorder import DELETE, db, order_log
|
from dexorder import DELETE, db, order_log, from_timestamp
|
||||||
from dexorder.base.chain import current_chain
|
from dexorder.base.chain import current_chain
|
||||||
from dexorder.base.order import OrderKey, TrancheKey
|
from dexorder.base.order import OrderKey, TrancheKey
|
||||||
from dexorder.base.orderlib import SwapOrderState, ElaboratedSwapOrderStatus, Fill
|
from dexorder.base.orderlib import SwapOrderState, ElaboratedSwapOrderStatus, Fill
|
||||||
from dexorder.blockstate import BlockDict, BlockSet
|
from dexorder.blockstate import BlockDict, BlockSet
|
||||||
from dexorder.database.model.orderindex import OrderIndex
|
from dexorder.database.model.orderindex import OrderIndex
|
||||||
from dexorder.routing import pool_address
|
from dexorder.routing import pool_address
|
||||||
|
from dexorder.tokens import adjust_decimals
|
||||||
from dexorder.util import json
|
from dexorder.util import json
|
||||||
from dexorder.vault_blockdata import vault_owners
|
from dexorder.vault_blockdata import vault_owners
|
||||||
|
|
||||||
@@ -287,6 +288,29 @@ class Order:
|
|||||||
Order.vault_recently_closed_orders.listremove(key.vault, key.order_index)
|
Order.vault_recently_closed_orders.listremove(key.vault, key.order_index)
|
||||||
|
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return str(self.key)
|
||||||
|
|
||||||
|
|
||||||
|
async def pprint(self):
|
||||||
|
amount_token = self.order.tokenIn if self.order.amountIsInput else self.order.tokenOut
|
||||||
|
msg = f'''
|
||||||
|
SwapOrder {self.key}
|
||||||
|
status: {self.state.name}
|
||||||
|
placed: {from_timestamp(self.status.startTime)}
|
||||||
|
in: {self.order.tokenIn}
|
||||||
|
out: {self.order.tokenOut}
|
||||||
|
exchange: {self.order.route.exchange.name, self.order.route.fee}
|
||||||
|
amount: {"input" if self.order.amountIsInput else "output"} {await adjust_decimals(amount_token, self.filled):f}/{await adjust_decimals(amount_token, self.amount):f}{" to owner" if self.order.outputDirectlyToOwner else ""}
|
||||||
|
minFill: {await adjust_decimals(amount_token, self.min_fill_amount):f}
|
||||||
|
inverted: {self.order.inverted}
|
||||||
|
tranches:
|
||||||
|
'''
|
||||||
|
for i in range(len(self.order.tranches)):
|
||||||
|
tranche = self.order.tranches[i]
|
||||||
|
msg += f' {tranche} filled {await adjust_decimals(amount_token, self.tranche_filled(i))}\n'
|
||||||
|
return msg
|
||||||
|
|
||||||
# ORDER STATE
|
# ORDER STATE
|
||||||
# various blockstate fields hold different aspects of an order's state.
|
# various blockstate fields hold different aspects of an order's state.
|
||||||
|
|
||||||
@@ -318,8 +342,6 @@ class Order:
|
|||||||
'of', db=True, redis=True, pub=pub_order_fills,
|
'of', db=True, redis=True, pub=pub_order_fills,
|
||||||
str2key=OrderKey.str2key, value2str=lambda v: json.dumps(v.dump()), str2value=lambda s:OrderFilled.load(json.loads(s)))
|
str2key=OrderKey.str2key, value2str=lambda v: json.dumps(v.dump()), str2value=lambda s:OrderFilled.load(json.loads(s)))
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return str(self.order)
|
|
||||||
|
|
||||||
# "active" means the order wants to be executed now. this is not BlockData because it's cleared every block
|
# "active" means the order wants to be executed now. this is not BlockData because it's cleared every block
|
||||||
active_orders: dict[OrderKey,Order] = {}
|
active_orders: dict[OrderKey,Order] = {}
|
||||||
|
|||||||
@@ -226,20 +226,20 @@ async def has_funds(tk: TrancheKey):
|
|||||||
|
|
||||||
|
|
||||||
async def input_amount_is_sufficient(order, token_balance):
|
async def input_amount_is_sufficient(order, token_balance):
|
||||||
# log.debug(f'input is sufficient? {order.min_fill_amount}')
|
log.debug(f'input is sufficient? {order.min_fill_amount}')
|
||||||
if order.amount_is_input:
|
if order.amount_is_input:
|
||||||
# log.debug(f'amount is input: {token_balance} >= {order.min_fill_amount}')
|
log.debug(f'amount is input: {token_balance} >= {order.min_fill_amount}')
|
||||||
return token_balance >= order.min_fill_amount
|
return token_balance >= order.min_fill_amount
|
||||||
# amount is an output amount, so we need to know the price
|
# amount is an output amount, so we need to know the price
|
||||||
price = pool_prices.get(order.pool_address)
|
price = pool_prices.get(order.pool_address)
|
||||||
# log.debug(f'amount is output amount. price={price}')
|
log.debug(f'amount is output amount. price={price}')
|
||||||
if price is None:
|
if price is None:
|
||||||
return token_balance > 0 # we don't know the price so we allow any nonzero amount to be sufficient
|
return token_balance > 0 # we don't know the price so we allow any nonzero amount to be sufficient
|
||||||
pool = await get_pool(order.pool_address)
|
pool = await get_pool(order.pool_address)
|
||||||
price *= dec(10) ** -pool['decimals']
|
price *= dec(10) ** -pool['decimals']
|
||||||
inverted = order.order.tokenIn != pool['base']
|
inverted = order.order.tokenIn != pool['base']
|
||||||
minimum = dec(order.min_fill_amount)*price if inverted else dec(order.min_fill_amount)/price
|
minimum = dec(order.min_fill_amount)*price if inverted else dec(order.min_fill_amount)/price
|
||||||
# log.debug(f'order minimum amount is {order.min_fill_amount} '+ ("input" if order.amount_is_input else f"output @ {price} = {minimum} ")+f'< {token_balance} balance')
|
log.debug(f'order minimum amount is {order.min_fill_amount} '+ ("input" if order.amount_is_input else f"output @ {price} = {minimum} ")+f'< {token_balance} balance')
|
||||||
return token_balance >= minimum
|
return token_balance >= minimum
|
||||||
|
|
||||||
|
|
||||||
@@ -261,7 +261,7 @@ class BalanceTrigger (Trigger):
|
|||||||
|
|
||||||
async def update(self, balance):
|
async def update(self, balance):
|
||||||
self.value = await input_amount_is_sufficient(self.order, balance)
|
self.value = await input_amount_is_sufficient(self.order, balance)
|
||||||
# log.debug(f'update balance {balance} was sufficient? {self.value}')
|
log.debug(f'update balance {balance} was sufficient? {self.value} {self.order.key}')
|
||||||
|
|
||||||
def remove(self):
|
def remove(self):
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ import asyncio
|
|||||||
import functools
|
import functools
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from dexorder import current_pub
|
from dexorder import current_pub, dec
|
||||||
from dexorder.base.chain import current_chain
|
from dexorder.base.chain import current_chain
|
||||||
from dexorder.blockstate import BlockDict
|
from dexorder.blockstate import BlockDict
|
||||||
from dexorder.contract import ERC20, CONTRACT_ERRORS
|
from dexorder.contract import ERC20, CONTRACT_ERRORS
|
||||||
from dexorder.contract.dexorder import VaultContract, vault_address
|
from dexorder.contract.dexorder import VaultContract, vault_address
|
||||||
from dexorder.util import json
|
from dexorder.util import json, align_decimal
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -102,3 +102,6 @@ async def refresh_vault_balances(vault, *tokens):
|
|||||||
result[t] = a
|
result[t] = a
|
||||||
return result
|
return result
|
||||||
vault_balances.modify(vault, functools.partial(_adjust, vault, tokens, amounts))
|
vault_balances.modify(vault, functools.partial(_adjust, vault, tokens, amounts))
|
||||||
|
|
||||||
|
def pretty_balances(b: dict[str,dec], padding=8) -> str:
|
||||||
|
return '\n'.join(f'{k:>} {align_decimal(v,padding)}' for k,v in b.items())
|
||||||
|
|||||||
Reference in New Issue
Block a user