From 800b3cef36e494c8145d664a4aed25985e84fc8a Mon Sep 17 00:00:00 2001 From: The MMGen Project Date: Wed, 28 May 2025 11:40:40 +0000 Subject: [PATCH] test suite: `ThornodeServer` -> `ThornodeSwapServer` --- test/cmdtest_d/ethbump.py | 6 +- test/cmdtest_d/ethswap.py | 12 +- test/cmdtest_d/httpd/thornode.py | 153 ----------------------- test/cmdtest_d/httpd/thornode_swap.py | 171 ++++++++++++++++++++++++++ test/cmdtest_d/swap.py | 12 +- 5 files changed, 186 insertions(+), 168 deletions(-) create mode 100755 test/cmdtest_d/httpd/thornode_swap.py diff --git a/test/cmdtest_d/ethbump.py b/test/cmdtest_d/ethbump.py index 8665c00b..cfbde706 100755 --- a/test/cmdtest_d/ethbump.py +++ b/test/cmdtest_d/ethbump.py @@ -22,14 +22,14 @@ from ..include.common import imsg, omsg_r from .include.common import cleanup_env, dfl_words_file, dfl_sid from .include.runner import CmdTestRunner -from .httpd.thornode import ThornodeServer +from .httpd.thornode_swap import ThornodeSwapServer from .ethdev import CmdTestEthdev, CmdTestEthdevMethods from .regtest import CmdTestRegtest from .swap import CmdTestSwapMethods from .ethswap import CmdTestEthSwapMethods -thornode_server = ThornodeServer() +swap_server = ThornodeSwapServer() burn_addr = 'beefcafe22' * 4 method_template = """ def {name}(self): @@ -292,7 +292,7 @@ class CmdTestEthBump(CmdTestEthBumpMethods, CmdTestEthSwapMethods, CmdTestSwapMe ethbump_ltc = CmdTestRunner(cfg, t.repo_root, t.data_dir, t.trash_dir, t.trash_dir2) ethbump_ltc.init_group('ethbump_ltc') - thornode_server.start() + swap_server.start() def txcreate1(self): return self._txcreate(args=[f'{burn_addr},987'], acct='1') diff --git a/test/cmdtest_d/ethswap.py b/test/cmdtest_d/ethswap.py index cd5b73d4..4337f46b 100755 --- a/test/cmdtest_d/ethswap.py +++ b/test/cmdtest_d/ethswap.py @@ -23,13 +23,13 @@ from ..include.common import imsg, chk_equal from .include.runner import CmdTestRunner from .include.common import dfl_sid, eth_inbound_addr, thorchain_router_addr_file -from .httpd.thornode import ThornodeServer +from .httpd.thornode_swap import ThornodeSwapServer from .regtest import CmdTestRegtest from .swap import CmdTestSwapMethods from .ethdev import CmdTestEthdev -thornode_server = ThornodeServer() +swap_server = ThornodeSwapServer() method_template = """ def {name}(self): @@ -150,7 +150,7 @@ class CmdTestEthSwap(CmdTestSwapMethods, CmdTestRegtest): ('subgroup.eth_token_swap', ['fund', 'token_init']), ('stop', 'stopping regtest daemon'), ('eth_stop', 'stopping Ethereum daemon'), - ('thornode_server_stop', 'stopping the Thornode server'), + ('swap_server_stop', 'stopping the Thornode server'), ) cmd_subgroups = { 'init': ( @@ -281,7 +281,7 @@ class CmdTestEthSwap(CmdTestSwapMethods, CmdTestRegtest): ethswap_eth = CmdTestRunner(cfg, t.repo_root, t.data_dir, t.trash_dir, t.trash_dir2) ethswap_eth.init_group(self.eth_group) - thornode_server.start() + swap_server.start() def swaptxcreate1(self): t = self._swaptxcreate(['BTC', '8.765', 'ETH']) @@ -326,13 +326,13 @@ class CmdTestEthSwap(CmdTestSwapMethods, CmdTestRegtest): t.expect('OK? (Y/n): ', 'y') return self._swaptxcreate_ui_common(t) - def thornode_server_stop(self): + def swap_server_stop(self): self.spawn(msg_only=True) if self.cfg.no_daemon_stop: msg_r('(leaving thornode server running by user request)') imsg('') else: - thornode_server.stop() + swap_server.stop() return 'ok' class CmdTestEthSwapEth(CmdTestEthSwapMethods, CmdTestSwapMethods, CmdTestEthdev): diff --git a/test/cmdtest_d/httpd/thornode.py b/test/cmdtest_d/httpd/thornode.py index 8e13fb69..261ecfc6 100755 --- a/test/cmdtest_d/httpd/thornode.py +++ b/test/cmdtest_d/httpd/thornode.py @@ -12,162 +12,9 @@ test.cmdtest_d.httpd.thornode: Thornode WSGI http server """ -import time, re, json - -from mmgen.cfg import Config -from mmgen.amt import UniAmt -from mmgen.protocol import init_proto - -from ..include.common import eth_inbound_addr, thorchain_router_addr_file - from . import HTTPD -cfg = Config() - -# https://thornode.ninerealms.com/thorchain/quote/swap?from_asset=BCH.BCH&to_asset=LTC.LTC&amount=1000000 -sample_request = 'GET /thorchain/quote/swap?from_asset=BCH.BCH&to_asset=LTC.LTC&amount=1000000000' -request_pat = r'/thorchain/quote/swap\?from_asset=(\S+)\.(\S+)&to_asset=(\S+)\.(\S+)&amount=(\d+)' -prices = {'BTC': 97000, 'LTC': 115, 'BCH': 330, 'ETH': 2304, 'MM1': 0.998, 'RUNE': 1.4} -gas_rate_units = {'ETH': 'gwei', 'BTC': 'satsperbyte'} -recommended_gas_rate = {'ETH': '1', 'BTC': '6'} - -data_template_from_rune = { - 'outbound_delay_blocks': 0, - 'outbound_delay_seconds': 0, - 'fees': { - 'asset': 'BTC.BTC', - 'affiliate': '0', - 'outbound': '1182', - 'liquidity': '110', - 'total': '1292', - 'slippage_bps': 7, - 'total_bps': 92 - }, - 'warning': 'Do not cache this response. Do not send funds after the expiry.', - 'notes': 'Broadcast a MsgDeposit to the THORChain network with the appropriate memo. Do not use multi-in, multi-out transactions.', - 'max_streaming_quantity': 0, - 'streaming_swap_blocks': 0 -} - -data_template_to_rune = { - 'inbound_confirmation_blocks': 2, - 'inbound_confirmation_seconds': 24, - 'outbound_delay_blocks': 0, - 'outbound_delay_seconds': 0, - 'fees': { - 'asset': 'THOR.RUNE', - 'affiliate': '0', - 'outbound': '2000000', - 'liquidity': '684966', - 'total': '2684966', - 'slippage_bps': 8, - 'total_bps': 31 - }, - 'router': '0xD37BbE5744D730a1d98d8DC97c42F0Ca46aD7146', - 'warning': 'Do not cache this response. Do not send funds after the expiry.', - 'notes': 'Base Asset: Send the inbound_address the asset with the memo encoded in hex in the data field. Tokens: First approve router to spend tokens from user: asset.approve(router, amount). Then call router.depositWithExpiry(inbound_address, asset, amount, memo, expiry). Asset is the token contract address. Amount should be in native asset decimals (eg 1e18 for most tokens). Do not swap to smart contract addresses.', - 'dust_threshold': '1', - 'recommended_gas_rate': '1', - 'max_streaming_quantity': 0, - 'streaming_swap_blocks': 0, - 'total_swap_seconds': 24 -} - -data_template_btc = { - 'inbound_confirmation_blocks': 4, - 'inbound_confirmation_seconds': 2400, - 'outbound_delay_blocks': 5, - 'outbound_delay_seconds': 30, - 'fees': { - 'asset': 'LTC.LTC', - 'affiliate': '0', - 'outbound': '878656', - 'liquidity': '8945012', - 'total': '9823668', - 'slippage_bps': 31, - 'total_bps': 34 - }, - 'warning': 'Do not cache this response. Do not send funds after the expiry.', - 'notes': 'First output should be to inbound_address, second output should be change back to self, third output should be OP_RETURN, limited to 80 bytes. Do not send below the dust threshold. Do not use exotic spend scripts, locks or address formats.', - 'dust_threshold': '10000', - 'max_streaming_quantity': 0, - 'streaming_swap_blocks': 0, - 'total_swap_seconds': 2430 -} - -data_template_eth = { - 'inbound_confirmation_blocks': 2, - 'inbound_confirmation_seconds': 24, - 'outbound_delay_blocks': 0, - 'outbound_delay_seconds': 0, - 'fees': { - 'asset': 'BTC.BTC', - 'affiliate': '0', - 'outbound': '1097', - 'liquidity': '77', - 'total': '1174', - 'slippage_bps': 15, - 'total_bps': 237 - }, - 'router': '0xD37BbE5744D730a1d98d8DC97c42F0Ca46aD7146', - 'warning': 'Do not cache this response. Do not send funds after the expiry.', - 'notes': 'Base Asset: Send the inbound_address the asset with the memo encoded in hex in the data field. Tokens: First approve router to spend tokens from user: asset.approve(router, amount). Then call router.depositWithExpiry(inbound_address, asset, amount, memo, expiry). Asset is the token contract address. Amount should be in native asset decimals (eg 1e18 for most tokens). Do not swap to smart contract addresses.', - 'recommended_gas_rate': '1', - 'max_streaming_quantity': 0, - 'streaming_swap_blocks': 0, - 'total_swap_seconds': 24 -} - -def make_inbound_addr(proto, mmtype): - if proto.is_evm: - return '0x' + eth_inbound_addr # non-checksummed as per ninerealms thornode - else: - from mmgen.tool.coin import tool_cmd - n = int(time.time()) // (60 * 60 * 24) # increments once every 24 hrs - return tool_cmd( - cfg = cfg, - cmdname = 'pubhash2addr', - proto = proto, - mmtype = mmtype).pubhash2addr(f'{n:040x}') - class ThornodeServer(HTTPD): name = 'thornode server' port = 18800 content_type = 'application/json' - - def make_response_body(self, method, environ): - from wsgiref.util import request_uri - - m = re.search(request_pat, request_uri(environ)) - send_chain, send_asset, recv_chain, recv_asset, amt_atomic = m.groups() - - in_amt = UniAmt(int(amt_atomic), from_unit='satoshi') - out_amt = in_amt * (prices[send_asset] / prices[recv_asset]) - - data_template = ( - data_template_from_rune if send_asset == 'RUNE' else - data_template_to_rune if recv_asset == 'RUNE' else - data_template_eth if send_asset == 'ETH' else - data_template_btc) - - data = data_template | { - 'recommended_min_amount_in': str(int(70 * 10**8 / prices[send_asset])), # $70 - 'expected_amount_out': str(out_amt.to_unit('satoshi')), - 'expiry': int(time.time()) + (10 * 60), - } - - if send_asset != 'RUNE': - send_proto = init_proto(cfg, send_chain, network='regtest', need_amt=True) - data.update({ - 'inbound_address': make_inbound_addr(send_proto, send_proto.preferred_mmtypes[0]), - 'gas_rate_units': gas_rate_units[send_proto.base_proto_coin], - 'recommended_gas_rate': recommended_gas_rate[send_proto.base_proto_coin] - }) - - if send_asset == 'MM1': - eth_proto = init_proto(cfg, 'eth', network='regtest') - with open(thorchain_router_addr_file) as fh: - raw_addr = fh.read().strip() - data['router'] = '0x' + eth_proto.checksummed_addr(raw_addr) - - return json.dumps(data).encode() diff --git a/test/cmdtest_d/httpd/thornode_swap.py b/test/cmdtest_d/httpd/thornode_swap.py new file mode 100755 index 00000000..e55aa15b --- /dev/null +++ b/test/cmdtest_d/httpd/thornode_swap.py @@ -0,0 +1,171 @@ +#!/usr/bin/env python3 +# +# MMGen Wallet, a terminal-based cryptocurrency wallet +# Copyright (C)2013-2025 The MMGen Project +# Licensed under the GNU General Public License, Version 3: +# https://www.gnu.org/licenses +# Public project repositories: +# https://github.com/mmgen/mmgen-wallet +# https://gitlab.com/mmgen/mmgen-wallet + +""" +test.cmdtest_d.httpd.thornode_swap: Thornode swap WSGI http server +""" + +import time, re, json +from wsgiref.util import request_uri + +from mmgen.cfg import Config +from mmgen.amt import UniAmt +from mmgen.protocol import init_proto + +from ..include.common import eth_inbound_addr, thorchain_router_addr_file + +from . import HTTPD +from .thornode import ThornodeServer + +cfg = Config() + +# https://thornode.ninerealms.com/thorchain/quote/swap?from_asset=BCH.BCH&to_asset=LTC.LTC&amount=1000000 +prices = {'BTC': 97000, 'LTC': 115, 'BCH': 330, 'ETH': 2304, 'MM1': 0.998, 'RUNE': 1.4} +gas_rate_units = {'ETH': 'gwei', 'BTC': 'satsperbyte'} +recommended_gas_rate = {'ETH': '1', 'BTC': '6'} + +data_template_from_rune = { + 'outbound_delay_blocks': 0, + 'outbound_delay_seconds': 0, + 'fees': { + 'asset': 'BTC.BTC', + 'affiliate': '0', + 'outbound': '1182', + 'liquidity': '110', + 'total': '1292', + 'slippage_bps': 7, + 'total_bps': 92 + }, + 'warning': 'Do not cache this response. Do not send funds after the expiry.', + 'notes': 'Broadcast a MsgDeposit to the THORChain network with the appropriate memo. Do not use multi-in, multi-out transactions.', + 'max_streaming_quantity': 0, + 'streaming_swap_blocks': 0 +} + +data_template_to_rune = { + 'inbound_confirmation_blocks': 2, + 'inbound_confirmation_seconds': 24, + 'outbound_delay_blocks': 0, + 'outbound_delay_seconds': 0, + 'fees': { + 'asset': 'THOR.RUNE', + 'affiliate': '0', + 'outbound': '2000000', + 'liquidity': '684966', + 'total': '2684966', + 'slippage_bps': 8, + 'total_bps': 31 + }, + 'router': '0xD37BbE5744D730a1d98d8DC97c42F0Ca46aD7146', + 'warning': 'Do not cache this response. Do not send funds after the expiry.', + 'notes': 'Base Asset: Send the inbound_address the asset with the memo encoded in hex in the data field. Tokens: First approve router to spend tokens from user: asset.approve(router, amount). Then call router.depositWithExpiry(inbound_address, asset, amount, memo, expiry). Asset is the token contract address. Amount should be in native asset decimals (eg 1e18 for most tokens). Do not swap to smart contract addresses.', + 'dust_threshold': '1', + 'recommended_gas_rate': '1', + 'max_streaming_quantity': 0, + 'streaming_swap_blocks': 0, + 'total_swap_seconds': 24 +} + +data_template_btc = { + 'inbound_confirmation_blocks': 4, + 'inbound_confirmation_seconds': 2400, + 'outbound_delay_blocks': 5, + 'outbound_delay_seconds': 30, + 'fees': { + 'asset': 'LTC.LTC', + 'affiliate': '0', + 'outbound': '878656', + 'liquidity': '8945012', + 'total': '9823668', + 'slippage_bps': 31, + 'total_bps': 34 + }, + 'warning': 'Do not cache this response. Do not send funds after the expiry.', + 'notes': 'First output should be to inbound_address, second output should be change back to self, third output should be OP_RETURN, limited to 80 bytes. Do not send below the dust threshold. Do not use exotic spend scripts, locks or address formats.', + 'dust_threshold': '10000', + 'max_streaming_quantity': 0, + 'streaming_swap_blocks': 0, + 'total_swap_seconds': 2430 +} + +data_template_eth = { + 'inbound_confirmation_blocks': 2, + 'inbound_confirmation_seconds': 24, + 'outbound_delay_blocks': 0, + 'outbound_delay_seconds': 0, + 'fees': { + 'asset': 'BTC.BTC', + 'affiliate': '0', + 'outbound': '1097', + 'liquidity': '77', + 'total': '1174', + 'slippage_bps': 15, + 'total_bps': 237 + }, + 'router': '0xD37BbE5744D730a1d98d8DC97c42F0Ca46aD7146', + 'warning': 'Do not cache this response. Do not send funds after the expiry.', + 'notes': 'Base Asset: Send the inbound_address the asset with the memo encoded in hex in the data field. Tokens: First approve router to spend tokens from user: asset.approve(router, amount). Then call router.depositWithExpiry(inbound_address, asset, amount, memo, expiry). Asset is the token contract address. Amount should be in native asset decimals (eg 1e18 for most tokens). Do not swap to smart contract addresses.', + 'recommended_gas_rate': '1', + 'max_streaming_quantity': 0, + 'streaming_swap_blocks': 0, + 'total_swap_seconds': 24 +} + +def make_inbound_addr(proto, mmtype): + if proto.is_evm: + return '0x' + eth_inbound_addr # non-checksummed as per ninerealms thornode + else: + from mmgen.tool.coin import tool_cmd + n = int(time.time()) // (60 * 60 * 24) # increments once every 24 hrs + return tool_cmd( + cfg = cfg, + cmdname = 'pubhash2addr', + proto = proto, + mmtype = mmtype).pubhash2addr(f'{n:040x}') + +class ThornodeSwapServer(ThornodeServer): + name = 'thornode swap server' + request_pat = r'/thorchain/quote/swap\?from_asset=(\S+)\.(\S+)&to_asset=(\S+)\.(\S+)&amount=(\d+)' + + def make_response_body(self, method, environ): + + m = re.search(self.request_pat, request_uri(environ)) + send_chain, send_asset, recv_chain, recv_asset, amt_atomic = m.groups() + + in_amt = UniAmt(int(amt_atomic), from_unit='satoshi') + out_amt = in_amt * (prices[send_asset] / prices[recv_asset]) + + data_template = ( + data_template_from_rune if send_asset == 'RUNE' else + data_template_to_rune if recv_asset == 'RUNE' else + data_template_eth if send_asset == 'ETH' else + data_template_btc) + + data = data_template | { + 'recommended_min_amount_in': str(int(70 * 10**8 / prices[send_asset])), # $70 + 'expected_amount_out': str(out_amt.to_unit('satoshi')), + 'expiry': int(time.time()) + (10 * 60), + } + + if send_asset != 'RUNE': + send_proto = init_proto(cfg, send_chain, network='regtest', need_amt=True) + data.update({ + 'inbound_address': make_inbound_addr(send_proto, send_proto.preferred_mmtypes[0]), + 'gas_rate_units': gas_rate_units[send_proto.base_proto_coin], + 'recommended_gas_rate': recommended_gas_rate[send_proto.base_proto_coin] + }) + + if send_asset == 'MM1': + eth_proto = init_proto(cfg, 'eth', network='regtest') + with open(thorchain_router_addr_file) as fh: + raw_addr = fh.read().strip() + data['router'] = '0x' + eth_proto.checksummed_addr(raw_addr) + + return json.dumps(data).encode() diff --git a/test/cmdtest_d/swap.py b/test/cmdtest_d/swap.py index a1a598ee..1993709b 100755 --- a/test/cmdtest_d/swap.py +++ b/test/cmdtest_d/swap.py @@ -19,12 +19,12 @@ from mmgen.wallet.mmgen import wallet as MMGenWallet from ..include.common import make_burn_addr, gr_uc from .include.common import dfl_bip39_file, dfl_words_file -from .httpd.thornode import ThornodeServer +from .httpd.thornode_swap import ThornodeSwapServer from .autosign import CmdTestAutosign, CmdTestAutosignThreaded from .regtest import CmdTestRegtest, rt_data, dfl_wcls, rt_pw, strip_ansi_escapes -thornode_server = ThornodeServer() +swap_server = ThornodeSwapServer() sample1 = gr_uc[:24] sample2 = '00010203040506' @@ -280,7 +280,7 @@ class CmdTestSwap(CmdTestSwapMethods, CmdTestRegtest, CmdTestAutosignThreaded): ('subgroup.signsend', ['init_swap']), ('subgroup.signsend_bad', ['init_swap']), ('subgroup.autosign', ['init_data', 'signsend']), - ('thornode_server_stop', 'stopping the Thornode server'), + ('swap_server_stop', 'stopping the Thornode server'), ('stop', 'stopping regtest daemons'), ) cmd_subgroups = { @@ -408,7 +408,7 @@ class CmdTestSwap(CmdTestSwapMethods, CmdTestRegtest, CmdTestAutosignThreaded): self.protos = [init_proto(cfg, k, network='regtest', need_amt=True) for k in ('btc', 'ltc', 'bch')] - thornode_server.start() + swap_server.start() self.opts.append('--bob') @@ -776,7 +776,7 @@ class CmdTestSwap(CmdTestSwapMethods, CmdTestRegtest, CmdTestAutosignThreaded): def mempool2(self): return self._mempool(2) - def thornode_server_stop(self): + def swap_server_stop(self): self.spawn(msg_only=True) - thornode_server.stop() + swap_server.stop() return 'ok'