diff --git a/src/dexorder/bin/mirror.py b/src/dexorder/bin/mirror.py index f1e203e..eac67ed 100644 --- a/src/dexorder/bin/mirror.py +++ b/src/dexorder/bin/mirror.py @@ -3,18 +3,73 @@ import logging import os import sys -from dexorder import config, blockchain +from dexorder import config, blockchain, current_w3 from dexorder.bin.executable import execute +from dexorder.blockchain.connection import create_w3 from dexorder.blockstate import current_blockstate from dexorder.blockstate.state import FinalizedBlockState -from dexorder.contract import get_deployment_address, ContractProxy +from dexorder.contract import get_deployment_address, ContractProxy, ERC20 from dexorder.metadata import generate_metadata from dexorder.pools import get_pool from dexorder.tokens import get_token +from dexorder.uniswap import UniswapV3Pool +from dexorder.util.async_util import maywait log = logging.getLogger('dexorder') +_token_infos = {} +source_w3 = None + +def use_source(func): + async def wrapper(*args, **kwargs): + old_w3 = current_w3.get() + current_w3.set(source_w3) + result = await func(*args, **kwargs) + current_w3.set(old_w3) + return result + return wrapper + + +@use_source +async def get_token_info( token ): + # struct TokenInfo { + # IERC20Metadata addr; + # string name; + # string symbol; + # uint8 decimals; + # } + token = await maywait(token) + if token not in _token_infos: + t = ERC20(token) + name, symbol, decimals = await asyncio.gather(t.name(), t.symbol(), t.decimals()) + _token_infos[token] = [token, name, symbol, decimals] + return _token_infos[token] + + +@use_source +async def get_pool_price(pool): + slot0 = await UniswapV3Pool(pool).slot0() + return slot0[0] + + +@use_source +async def get_pool_info( pool ): + # struct PoolInfo { + # IUniswapV3Pool pool; + # TokenInfo token0; + # TokenInfo token1; + # uint24 fee; + # uint160 sqrtPriceX96; + # uint256 amount0; + # uint256 amount1; + # } + p = UniswapV3Pool(pool) + t0, t1 = await asyncio.gather(get_token_info(p.token0()), get_token_info(p.token1())) + amount0, amount1, (price,*_), fee = \ + await asyncio.gather(ERC20(t0[0]).balanceOf(pool), ERC20(t1[0]).balanceOf(pool), p.slot0(), p.fee()) + return [pool, t0, t1, fee, price, amount0, amount1] + async def write_metadata( pools, mirror_pools ): filename = config.mirror_metadata if filename is None: @@ -37,6 +92,11 @@ async def await_mirror(tx, pool_addr, mirror_addr, mirror_inverted ): async def main(): + if config.mirror_source_rpc_url is None: + log.error('Must configure mirror_source_rpc_url') + return + global source_w3 + source_w3 = await create_w3(config.mirror_source_rpc_url) pools = (config.mirror_pools or []) if not pools: log.error('must configure mirror_pools') @@ -56,12 +116,11 @@ async def main(): return log.info(f'Initializing with MirrorEnv {mirror_addr}') mirrorenv = ContractProxy(mirror_addr, 'MirrorEnv') - proms = [] + log.debug(f'Mirroring pools {", ".join(pools)}') + pool_infos = await asyncio.gather(*[get_pool_info(pool) for pool in pools]) + tx = await mirrorenv.transact.mirrorPools(pool_infos) + await tx.wait() mirror_pools = [] - for pool in pools: - log.debug(f'Mirroring pool {pool}') - proms.append(await mirrorenv.transact.mirrorPool(pool)) - await asyncio.gather(*[p.wait() for p in proms]) for pool in pools: mirror_addr, mirror_inverted = await mirrorenv.pools(pool) log.debug(f'\tmirror result {mirror_addr} {mirror_inverted}') @@ -70,18 +129,12 @@ async def main(): delay = config.polling if config.polling > 0 else 1 log.info(f'Mirroring pools every {delay} seconds') while True: - proms = [] - for pool_addr, (mirror_addr, mirror_inverted) in zip(pools, mirror_pools): - try: - log.info(f'mirroring {pool_addr}') - tx = await mirrorenv.transact.updatePool(pool_addr) - proms.append((tx, pool_addr, mirror_addr, mirror_inverted)) - except Exception as x: - log.exception(x) + prices = await asyncio.gather(*[get_pool_price(pool) for pool in pools]) try: - await asyncio.gather(*[await_mirror(*args) for args in proms], return_exceptions=True) + await mirrorenv.transact.updatePools(list(zip(pools,prices))) except Exception as x: log.exception(x) + log.debug('Mirrored'+''.join(f'\n\t{pool} {price}' for pool, price in zip(pools,prices))) await asyncio.sleep(delay) diff --git a/src/dexorder/configuration/schema.py b/src/dexorder/configuration/schema.py index 1bc202b..38a0e40 100644 --- a/src/dexorder/configuration/schema.py +++ b/src/dexorder/configuration/schema.py @@ -31,6 +31,7 @@ class Config: walker_flush_interval: float = 300 + mirror_source_rpc_url: Optional[str] = None # source RPC for original pools mirror_env: Optional[str] = None mirror_pools: Optional[list[str]] = field(default_factory=list) mirror_metadata: str = field(default='metadata.json') diff --git a/src/dexorder/contract/contract_proxy.py b/src/dexorder/contract/contract_proxy.py index 15287bc..1118b0e 100644 --- a/src/dexorder/contract/contract_proxy.py +++ b/src/dexorder/contract/contract_proxy.py @@ -122,7 +122,13 @@ class ContractProxy: return ContractProxy(self.address, self._interface_name, _contracts=self._contracts, _wrapper=build_wrapper, abi=self._abi) def __getattr__(self, item): - return self._wrapper(self.contract.constructor if item == 'constructor' else self.contract.functions[item]) + if item == 'constructor': + found = self.contract.constructor + elif item in self.contract.functions: + found = self.contract.functions[item] + else: + raise AttributeError(item) + return self._wrapper(found) def __repr__(self): addr = self.contract.address