220 lines
7.9 KiB
Python
220 lines
7.9 KiB
Python
"""
|
|
python -m dexorder.bin.mirror creates new pools on the rpc_url chain that mimic the tokens and prices of original
|
|
pools on a different mirror_source_rpc_url chain.
|
|
|
|
[config]
|
|
rpc_url=<destination for mock pools and tokens>
|
|
mirror_source_rpc_url = <source for original pools and tokens to copy>
|
|
mirror_pools = <required list of pool addresses to copy from mirror_source_rpc_url>
|
|
mirror_env = [address on rpc_url chain where MirrorEnv contract is deployed. If None, then discovered from deployment]
|
|
metadata = [if set, then a metadata.json file for the mock chain is written to this location]
|
|
polling = [seconds between price updates. If zero or negative, prices are updated once and the process exits]
|
|
"""
|
|
|
|
|
|
import asyncio
|
|
import logging
|
|
import os
|
|
from datetime import timedelta
|
|
|
|
from dexorder import config, blockchain, current_w3, now, ADDRESS_0
|
|
from dexorder.bin.executable import execute
|
|
from dexorder.blockchain.connection import create_w3
|
|
from dexorder.contract import get_deployment_address, ContractProxy, ERC20
|
|
from dexorder.metadata import generate_metadata, init_generating_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())
|
|
log.debug(f'original token: {name} {symbol} .{decimals} {token}')
|
|
_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(p.token0(), p.token1())
|
|
amount0, amount1, (price,*_), fee = \
|
|
await asyncio.gather(ERC20(t0).balanceOf(pool), ERC20(t1).balanceOf(pool), p.slot0(), p.fee())
|
|
|
|
token0 = await get_token(t0)
|
|
token1 = await get_token(t1)
|
|
log.debug(f'pool has {amount0/10**token0["decimals"]} {token0["name"]} and {amount1/10**token1["decimals"]} {token1["name"]}')
|
|
|
|
return [pool, t0, t1, fee, price, amount0, amount1]
|
|
|
|
async def write_metadata( pools, mirror_pools ):
|
|
filename = config.metadata
|
|
if filename is None:
|
|
return
|
|
pool_dicts = [get_pool(addr) for (addr,_inverted) in mirror_pools]
|
|
pool_dicts = await asyncio.gather(*pool_dicts)
|
|
for data, addr, (_,inverted) in zip(pool_dicts, pools, mirror_pools):
|
|
data['x'] = dict(data=dict(uri=f'https://app.dexorder.trade/ohlc/', chain=42161, symbol=addr, inverted=inverted))
|
|
tokens = set(p['base'] for p in pool_dicts)
|
|
tokens.update(p['quote'] for p in pool_dicts)
|
|
tokens = await asyncio.gather(*[get_token(t) for t in tokens])
|
|
for token in tokens:
|
|
token['x'] = {'mock':True}
|
|
with open(filename, 'w') as f:
|
|
generate_metadata(tokens, pool_dicts, file=f, include_unapproved=True)
|
|
log.info(f'wrote {filename}')
|
|
|
|
|
|
last_prices = {}
|
|
|
|
async def main():
|
|
init_generating_metadata()
|
|
if config.metadata is None:
|
|
log.error('Must configure metadata (filename to write)')
|
|
return
|
|
if config.mirror_source_rpc_url is None:
|
|
log.error('Must configure mirror_source_rpc_url')
|
|
return
|
|
delay = max(0.010, config.polling)
|
|
update_once = config.polling <= 0
|
|
global source_w3
|
|
source_w3 = await create_w3(config.mirror_source_rpc_url, name='source', autosign=False, archive_url=[])
|
|
pools = (config.mirror_pools or [])
|
|
if not pools:
|
|
log.error('must configure mirror_pools')
|
|
return
|
|
if not config.accounts:
|
|
# Dev Account #6 0x976EA74026E726554dB657fA54763abd0C3a0aa9
|
|
config.accounts = ['0x92db14e403b83dfe3df233f83dfa3a0d7096f21ca9b0d6d6b8d88b2b4ec1564e']
|
|
await blockchain.connect(name='target')
|
|
|
|
mirror_addr = config.mirror_env
|
|
if mirror_addr is None:
|
|
mirror_addr = os.environ.get('MIRRORENV')
|
|
if mirror_addr is None:
|
|
mirror_addr = get_deployment_address('DeployMirror', 'MirrorEnv')
|
|
if mirror_addr is None:
|
|
log.error('must configure mirror_env or set envioronment MIRRORENV')
|
|
return
|
|
log.debug(f'Using MirrorEnv at {mirror_addr}')
|
|
mirrorenv = ContractProxy(mirror_addr, 'MirrorEnv')
|
|
|
|
pool_infos = [await get_pool_info(pool) for pool in pools]
|
|
tokens = set(i[1] for i in pool_infos).union(i[2] for i in pool_infos)
|
|
|
|
log.debug(f'Mirroring tokens')
|
|
for t in tokens:
|
|
# noinspection PyBroadException
|
|
try:
|
|
info = await get_token_info(t)
|
|
# anvil had trouble estimating the gas, so we hardcode it.
|
|
tx = await mirrorenv.transact.mirrorToken(info, gas=1_000_000)
|
|
await tx.wait()
|
|
except Exception:
|
|
log.exception(f'Failed to mirror token {t}')
|
|
exit(1)
|
|
log.info(f'Tokens deployed')
|
|
|
|
log.debug(f'Mirroring pools {", ".join(pools)}')
|
|
for pool, info in zip(pools, pool_infos):
|
|
# noinspection PyBroadException
|
|
try:
|
|
# anvil had trouble estimating the gas, so we hardcode it.
|
|
tx = await mirrorenv.transact.mirrorPool(info, gas=5_500_000)
|
|
await tx.wait()
|
|
except Exception:
|
|
log.exception(f'Failed to mirror pool {pool}')
|
|
exit(1)
|
|
log.info('Pools deployed')
|
|
|
|
mirror_pool_list = []
|
|
# log.debug(f'Getting pool info {" ".join(pools)}')
|
|
for pool in pools:
|
|
mirror_addr, mirror_inverted = await mirrorenv.pools(pool)
|
|
if mirror_addr == ADDRESS_0:
|
|
raise ValueError(f'Pool {pool} was not successfully mirrored')
|
|
log.debug(f'\t{pool} => {mirror_addr} inverted={mirror_inverted}')
|
|
mirror_pool_list.append((mirror_addr, mirror_inverted))
|
|
await write_metadata(pools, mirror_pool_list)
|
|
mirror_pools = dict(zip(pools, mirror_pool_list))
|
|
|
|
if update_once:
|
|
log.info(f'Updating pools once')
|
|
else:
|
|
log.info(f'Updating a pool every {delay} seconds')
|
|
delay = timedelta(seconds=delay)
|
|
to_update = pools
|
|
pool_iter = iter(to_update)
|
|
pool = next(pool_iter)
|
|
while True:
|
|
wake_up = now() + delay
|
|
# log.debug(f'querying {pool}')
|
|
try:
|
|
price = await get_pool_price(pool)
|
|
if price != last_prices.get(pool):
|
|
# anvil had trouble estimating the gas, so we hardcode it.
|
|
tx = await mirrorenv.transact.updatePool(pool, price, gas=1_000_000) # this is a B.S. gas number
|
|
await tx.wait()
|
|
last_prices[pool] = price
|
|
addr, inverted = mirror_pools[pool]
|
|
log.debug(f'Mirrored {addr} {price}')
|
|
except Exception as x:
|
|
log.debug(f'Could not update {pool}: {x}')
|
|
continue
|
|
try:
|
|
pool = next(pool_iter)
|
|
except StopIteration:
|
|
if update_once:
|
|
log.info('mirror completed')
|
|
break
|
|
pool_iter = iter(to_update)
|
|
pool = next(pool_iter)
|
|
sleep = (wake_up - now()).total_seconds()
|
|
if sleep > 0:
|
|
await asyncio.sleep(sleep)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
execute(main)
|