From 1da4cf6a9384765e066a81b70220c928f391f08b Mon Sep 17 00:00:00 2001 From: Tim Olson <> Date: Sun, 29 Oct 2023 16:53:45 -0400 Subject: [PATCH] TRANCHE EXECUTION WORKS --- .../versions/db62e7db828d_initial_schema.py | 2 +- src/dexorder/base/account.py | 8 ++++++ src/dexorder/contract/contract_proxy.py | 13 +++++----- src/dexorder/data/__init__.py | 7 +++--- src/dexorder/event_handler.py | 25 +++++++++---------- src/dexorder/order/executionhandler.py | 6 +++++ src/dexorder/order/orderstate.py | 6 ++++- src/dexorder/runner.py | 4 +++ 8 files changed, 47 insertions(+), 24 deletions(-) diff --git a/alembic/versions/db62e7db828d_initial_schema.py b/alembic/versions/db62e7db828d_initial_schema.py index cea645a..375fe5f 100644 --- a/alembic/versions/db62e7db828d_initial_schema.py +++ b/alembic/versions/db62e7db828d_initial_schema.py @@ -60,7 +60,7 @@ def upgrade() -> None: op.create_index(op.f('ix_transactionjob_state'), 'transactionjob', ['state'], unique=False) op.create_table('tx', sa.Column('id', postgresql.BYTEA(), nullable=False), - sa.Column('data', postgresql.JSONB(astext_type=sa.Text()), nullable=False), + sa.Column('data', postgresql.BYTEA(), nullable=False), sa.Column('job_id', sa.UUID(), nullable=False), sa.Column('receipt', postgresql.JSONB(astext_type=sa.Text()), nullable=True), sa.ForeignKeyConstraint(['job_id'], ['transactionjob.id'], ), diff --git a/src/dexorder/base/account.py b/src/dexorder/base/account.py index 201c5ef..2620a8b 100644 --- a/src/dexorder/base/account.py +++ b/src/dexorder/base/account.py @@ -49,6 +49,14 @@ class Account (LocalAccount): self.name = name self.key_str = key_str self.signing_middleware = construct_sign_and_send_raw_middleware(self) + self._nonce: Optional[int] = None + + async def next_nonce(self): + if self._nonce is None: + self._nonce = await current_w3.get().eth.get_transaction_count(self.address, 'pending') + else: + self._nonce += 1 + return self._nonce def attach(self, w3): w3.eth.default_account = self.address diff --git a/src/dexorder/contract/contract_proxy.py b/src/dexorder/contract/contract_proxy.py index 7479206..3d2f099 100644 --- a/src/dexorder/contract/contract_proxy.py +++ b/src/dexorder/contract/contract_proxy.py @@ -1,6 +1,7 @@ import json from typing import Optional +import eth_account from eth_utils import keccak from web3.types import TxReceipt @@ -19,15 +20,15 @@ def call_wrapper(func): def transact_wrapper(func): async def f(*args, **kwargs): - w3 = current_w3.get() try: account = current_account.get() except LookupError: raise RuntimeError('Cannot invoke a transaction without setting an Account.') tx = await func(*args, **kwargs).build_transaction() tx['from'] = account.address - signed = w3.eth.account.sign_transaction(tx, private_key=account.key) - return ContractTransaction(signed) + tx['nonce'] = await account.next_nonce() + signed = eth_account.Account.sign_transaction(tx, private_key=account.key) + return ContractTransaction(signed['hash'], signed['rawTransaction']) return f @@ -88,10 +89,10 @@ class ContractProxy: class ContractTransaction: - def __init__(self, rawtx: bytes): - self.data = rawtx - self.id_bytes = keccak(rawtx) + def __init__(self, id_bytes: bytes, rawtx: bytes): + self.id_bytes = id_bytes self.id = hexstr(self.id_bytes) + self.data = rawtx self.receipt: Optional[TxReceipt] = None def __repr__(self): diff --git a/src/dexorder/data/__init__.py b/src/dexorder/data/__init__.py index 78d501f..4871156 100644 --- a/src/dexorder/data/__init__.py +++ b/src/dexorder/data/__init__.py @@ -7,6 +7,7 @@ from dexorder.util import defaultdictk, hexstr # values of DELETE are serialized as nulls vault_owners: BlockDict[str,str] = BlockDict('v', db=True, redis=True) -vault_tokens: dict[str, BlockSet[str]] = defaultdictk(lambda vault: BlockSet(f'vt|{vault}', db=True, redis=True, pub=lambda k,v: ('vt', vault_owners[vault], [k]))) -pool_prices: BlockDict[str,dec] = BlockDict('p', db=True, redis=True, pub=True, value2str=lambda d:f'{d:f}', str2value=dec) -underfunded_vaults: BlockDict[str, list[str]] = BlockDict('uv', db=True, redis=True, value2str=lambda v:','.join(v), str2value=lambda s: s.split(',')) +vault_balances: dict[str, BlockDict[str,int]] = defaultdictk(lambda vault: BlockDict(f'vb|{vault}', db=True, redis=True, + pub=lambda k,v: (vault_owners[vault], 'vb', (vault,k,v)))) +pool_prices: BlockDict[str,dec] = BlockDict('p', db=True, redis=True, value2str=lambda d:f'{d:f}', str2value=dec, + pub=lambda k,v: (f'p|{k}', 'p', (k,str(v)))) diff --git a/src/dexorder/event_handler.py b/src/dexorder/event_handler.py index 7852c19..7bed007 100644 --- a/src/dexorder/event_handler.py +++ b/src/dexorder/event_handler.py @@ -3,14 +3,14 @@ from uuid import UUID from web3.types import EventData -from dexorder import current_pub, db +from dexorder import current_pub, db, dec from dexorder.base.chain import current_chain from dexorder.base.order import TrancheExecutionRequest, TrancheKey, ExecutionRequest, new_tranche_execution_request -from dexorder.transaction import handle_create_transactions, submit_transaction_request, handle_transaction_receipts +from dexorder.transaction import handle_create_transactions, submit_transaction_request, handle_transaction_receipts, handle_send_transactions from dexorder.blockchain.uniswap import uniswap_price from dexorder.contract.dexorder import get_factory_contract, vault_address, VaultContract, get_dexorder_contract from dexorder.contract import UniswapV3Pool, get_contract_event -from dexorder.data import pool_prices, vault_owners, vault_tokens, underfunded_vaults +from dexorder.data import pool_prices, vault_owners, vault_balances from dexorder.database.model.block import current_block from dexorder.database.model.transaction import TransactionJob from dexorder.order.orderlib import SwapOrderState, SwapOrderStatus @@ -73,6 +73,7 @@ def setup_logevent_triggers(runner): runner.add_event_trigger(activate_price_triggers) runner.add_event_trigger(process_execution_requests) runner.add_event_trigger(handle_create_transactions) + runner.add_event_trigger(handle_send_transactions) async def handle_order_placed(event: EventData): @@ -111,18 +112,17 @@ def handle_order_error(event: EventData): log.debug(f'DexorderError {event}') def handle_transfer(transfer: EventData): + # todo handle native transfers incl gas for token transfers from_address = transfer['args']['from'] to_address = transfer['args']['to'] + amount = int(transfer['args']['value']) log.debug(f'transfer {to_address}') - if to_address in vault_owners: + if to_address in vault_owners and to_address != from_address: token_address = transfer['address'] - vault_tokens[to_address].add(token_address) - if to_address in underfunded_vaults: - # todo possibly funded now - pass - if from_address in Order.open_keys: - # todo possibly underfunded now - pass + vault_balances[to_address].add(token_address, amount, 0) + if from_address in vault_owners and to_address != from_address: + token_address = transfer['address'] + vault_balances[to_address].add(token_address, -amount, 0) new_pool_prices: dict[str, int] = {} @@ -224,9 +224,8 @@ def finish_execution_request(req: TrancheExecutionRequest, error: str): log.debug(f'execution request for tranche {tk} was successful!') elif error in ('IIA', 'STF'): # todo not STF # Insufficient Input Amount or Safe Transfer Failure: suspend execution until new funds are sent - # todo replace with vault balance checks + # todo vault balance checks token = order.order.tokenIn - # underfunded_vaults[req.vault] = underfunded_vaults.get(req.vault, []) + [token] log.debug(f'insufficient funds {req.vault} {token} ') else: log.error(f'Unhandled execution error for transaction request {req} ERROR: "{error}"') diff --git a/src/dexorder/order/executionhandler.py b/src/dexorder/order/executionhandler.py index 46782d7..85d92ea 100644 --- a/src/dexorder/order/executionhandler.py +++ b/src/dexorder/order/executionhandler.py @@ -1,6 +1,9 @@ import logging from uuid import UUID +from web3.exceptions import ContractPanicError + +from dexorder import db from dexorder.base.order import TrancheExecutionRequest, TrancheKey from dexorder.transaction import TransactionHandler from dexorder.contract.dexorder import get_dexorder_contract @@ -17,6 +20,9 @@ class TrancheExecutionHandler (TransactionHandler): # noinspection PyBroadException try: return await get_dexorder_contract().transact.execute(job_id.bytes, (req.vault, req.order_index, req.tranche_index, req.price_proof)) + except ContractPanicError as px: + log.error(f'While executing job {job_id}: {px}') + await self.complete_transaction(db.session.get(TransactionJob, job_id)) except Exception: log.exception(f'Could not send execution request {req}') diff --git a/src/dexorder/order/orderstate.py b/src/dexorder/order/orderstate.py index 8b6790f..9b2f197 100644 --- a/src/dexorder/order/orderstate.py +++ b/src/dexorder/order/orderstate.py @@ -42,7 +42,11 @@ class Order: @staticmethod def of(a, b=None): - return Order.instances[a if b is None else OrderKey(a, b)] + key = a if b is None else OrderKey(a, b) + try: + return Order.instances[key] + except KeyError: + log.error(f'Could not find {key} among:\n{", ".join(str(k) for k in Order.instances.keys())}') @staticmethod diff --git a/src/dexorder/runner.py b/src/dexorder/runner.py index cae3b67..b6adbfc 100644 --- a/src/dexorder/runner.py +++ b/src/dexorder/runner.py @@ -229,6 +229,7 @@ class BlockStateRunner: if pubs and self.publish_all: await maywait(self.publish_all(pubs)) except: # legitimately catch EVERYTHING because we re-raise + log.debug('rolling back session') if session is not None: session.rollback() if blockhash is not None and self.state is not None: @@ -238,3 +239,6 @@ class BlockStateRunner: if session is not None: session.commit() log.info(f'completed block {block}') + finally: + if session is not None: + session.close()