fixes and cleanups
This commit is contained in:
parent
90d500302f
commit
809856c07d
7 changed files with 59 additions and 54 deletions
|
|
@ -48,7 +48,7 @@ class CoinAmt(Decimal, Hilite, InitErrors): # abstract class
|
|||
try:
|
||||
if from_unit:
|
||||
assert from_unit in cls.units, f'{from_unit!r}: unrecognized coin unit for {cls.__name__}'
|
||||
assert type(num) is int, 'value is not an integer'
|
||||
assert isinstance(num, int), 'value is not an integer'
|
||||
me = Decimal.__new__(cls, num * getattr(cls, from_unit))
|
||||
elif from_decimal:
|
||||
assert isinstance(num, Decimal), f'number must be of type Decimal, not {type(num).__name__})'
|
||||
|
|
@ -157,6 +157,7 @@ class CoinAmt(Decimal, Hilite, InitErrors): # abstract class
|
|||
self.method_not_implemented()
|
||||
|
||||
def is_coin_amt(proto, num, from_unit=None, from_decimal=False):
|
||||
assert proto.coin_amt, 'proto.coin_amt is None! Did you call init_proto() with ‘need_amt’?'
|
||||
return get_obj(proto.coin_amt, num=num, from_unit=from_unit, from_decimal=from_decimal, silent=True, return_bool=True)
|
||||
|
||||
class BTCAmt(CoinAmt):
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@
|
|||
swap.proto.thorchain.memo: THORChain swap protocol memo class
|
||||
"""
|
||||
|
||||
from ....util import die
|
||||
|
||||
from . import name as proto_name
|
||||
|
||||
class Memo:
|
||||
|
|
@ -65,14 +67,13 @@ class Memo:
|
|||
All fields are validated, excluding address (cannot validate, since network is unknown)
|
||||
"""
|
||||
from collections import namedtuple
|
||||
from ....exception import SwapMemoParseError
|
||||
from ....util import is_int
|
||||
|
||||
def get_item(desc):
|
||||
try:
|
||||
return fields.pop(0)
|
||||
except IndexError as e:
|
||||
raise SwapMemoParseError(f'malformed {proto_name} memo (missing {desc} field)') from e
|
||||
except IndexError:
|
||||
die('SwapMemoParseError', f'malformed {proto_name} memo (missing {desc} field)')
|
||||
|
||||
def get_id(data, item, desc):
|
||||
if item in data:
|
||||
|
|
@ -80,12 +81,12 @@ class Memo:
|
|||
rev_data = {v:k for k,v in data.items()}
|
||||
if item in rev_data:
|
||||
return rev_data[item]
|
||||
raise SwapMemoParseError(f'{item!r}: unrecognized {proto_name} {desc} abbreviation')
|
||||
die('SwapMemoParseError', f'{item!r}: unrecognized {proto_name} {desc} abbreviation')
|
||||
|
||||
fields = str(s).split(':')
|
||||
|
||||
if len(fields) < 4:
|
||||
raise SwapMemoParseError('memo must contain at least 4 comma-separated fields')
|
||||
die('SwapMemoParseError', 'memo must contain at least 4 comma-separated fields')
|
||||
|
||||
function = get_id(cls.function_abbrevs, get_item('function'), 'function')
|
||||
|
||||
|
|
@ -98,15 +99,15 @@ class Memo:
|
|||
|
||||
try:
|
||||
limit, interval, quantity = lsq.split('/')
|
||||
except ValueError as e:
|
||||
raise SwapMemoParseError(f'malformed memo (failed to parse {desc} field) [{lsq}]') from e
|
||||
except ValueError:
|
||||
die('SwapMemoParseError', f'malformed memo (failed to parse {desc} field) [{lsq}]')
|
||||
|
||||
for n in (limit, interval, quantity):
|
||||
if not is_int(n):
|
||||
raise SwapMemoParseError(f'malformed memo (non-integer in {desc} field [{lsq}])')
|
||||
die('SwapMemoParseError', f'malformed memo (non-integer in {desc} field [{lsq}])')
|
||||
|
||||
if fields:
|
||||
raise SwapMemoParseError('malformed memo (unrecognized extra data)')
|
||||
die('SwapMemoParseError', 'malformed memo (unrecognized extra data)')
|
||||
|
||||
ret = namedtuple(
|
||||
'parsed_memo',
|
||||
|
|
|
|||
|
|
@ -58,20 +58,23 @@ class Midgard:
|
|||
c = self.in_amt.to_unit('satoshi'))
|
||||
self.result = self.rpc.get(self.get_str)
|
||||
self.data = json.loads(self.result.content)
|
||||
if not 'expiry' in self.data:
|
||||
from ....util import pp_fmt, die
|
||||
die(2, pp_fmt(self.data))
|
||||
|
||||
def format_quote(self):
|
||||
from ....util import make_timestr, pp_fmt, die
|
||||
from ....util import make_timestr
|
||||
from ....util2 import format_elapsed_hr
|
||||
from ....color import blue, cyan, pink, orange
|
||||
from . import name
|
||||
|
||||
d = self.data
|
||||
if not 'expiry' in d:
|
||||
die(2, pp_fmt(d))
|
||||
tx = self.tx
|
||||
in_coin = tx.send_proto.coin
|
||||
out_coin = tx.recv_proto.coin
|
||||
in_amt = self.in_amt
|
||||
out_amt = tx.recv_proto.coin_amt(int(d['expected_amount_out']), from_unit='satoshi')
|
||||
|
||||
min_in_amt = tx.send_proto.coin_amt(int(d['recommended_min_amount_in']), from_unit='satoshi')
|
||||
gas_unit = {
|
||||
'satsperbyte': 'sat/byte',
|
||||
|
|
@ -82,16 +85,17 @@ class Midgard:
|
|||
fees_pct_disp = str(fees['total_bps'] / 100) + '%'
|
||||
slip_pct_disp = str(fees['slippage_bps'] / 100) + '%'
|
||||
hdr = f'SWAP QUOTE (source: {self.rpc.host})'
|
||||
|
||||
return f"""
|
||||
{cyan(hdr)}
|
||||
Protocol: {blue(name)}
|
||||
Direction: {orange(f'{in_coin} => {out_coin}')}
|
||||
Vault address: {cyan(d['inbound_address'])}
|
||||
Quote expires: {pink(elapsed_disp)} [{make_timestr(d['expiry'])}]
|
||||
Amount in: {self.in_amt.hl()} {in_coin}
|
||||
Amount in: {in_amt.hl()} {in_coin}
|
||||
Expected amount out: {out_amt.hl()} {out_coin}
|
||||
Rate: {(out_amt / self.in_amt).hl()} {out_coin}/{in_coin}
|
||||
Reverse rate: {(self.in_amt / out_amt).hl()} {in_coin}/{out_coin}
|
||||
Rate: {(out_amt / in_amt).hl()} {out_coin}/{in_coin}
|
||||
Reverse rate: {(in_amt / out_amt).hl()} {in_coin}/{out_coin}
|
||||
Recommended minimum in amount: {min_in_amt.hl()} {in_coin}
|
||||
Recommended fee: {pink(d['recommended_gas_rate'])} {pink(gas_unit)}
|
||||
Fees:
|
||||
|
|
|
|||
|
|
@ -17,9 +17,6 @@ from .new import New
|
|||
class NewSwap(New):
|
||||
desc = 'swap transaction'
|
||||
|
||||
async def process_swap_cmdline_args(self, cmd_args, addrfiles):
|
||||
raise NotImplementedError(f'Swap not implemented for protocol {self.proto.__name__}')
|
||||
|
||||
def update_vault_output(self, amt):
|
||||
import importlib
|
||||
sp = importlib.import_module(f'mmgen.swap.proto.{self.swap_proto}')
|
||||
|
|
|
|||
|
|
@ -317,10 +317,10 @@ class CmdTestSwap(CmdTestRegtest, CmdTestAutosignThreaded):
|
|||
return self.addrimport('bob', mmtypes=['C'], proto=self.protos[2])
|
||||
|
||||
def fund_bob_send(self):
|
||||
return self._fund_bob(2, 'C', '500')
|
||||
return self._fund_bob(2, 'C', '5')
|
||||
|
||||
def bob_bal_send(self):
|
||||
return self._bob_bal(2, '500')
|
||||
return self._bob_bal(2, '5')
|
||||
|
||||
def setup_recv_coin(self):
|
||||
return self._setup(proto=self.protos[1], remove_datadir=False)
|
||||
|
|
@ -332,10 +332,10 @@ class CmdTestSwap(CmdTestRegtest, CmdTestAutosignThreaded):
|
|||
return self._addrimport_bob(1)
|
||||
|
||||
def fund_bob_recv1(self):
|
||||
return self._fund_bob(1, 'S', '500')
|
||||
return self._fund_bob(1, 'S', '5')
|
||||
|
||||
def fund_bob_recv2(self):
|
||||
return self._fund_bob(1, 'B', '500')
|
||||
return self._fund_bob(1, 'B', '5')
|
||||
|
||||
def addrgen_bob_recv_subwallet(self):
|
||||
return self._addrgen_bob(1, ['C', 'B'], subseed_idx='29L')
|
||||
|
|
@ -343,7 +343,7 @@ class CmdTestSwap(CmdTestRegtest, CmdTestAutosignThreaded):
|
|||
def addrimport_bob_recv_subwallet(self):
|
||||
return self._subwallet_addrimport('bob', '29L', ['C', 'B'], proto=self.protos[1])
|
||||
|
||||
def fund_bob_recv_subwallet(self, proto_idx=1, amt='500'):
|
||||
def fund_bob_recv_subwallet(self, proto_idx=1, amt='5'):
|
||||
coin_arg = f'--coin={self.protos[proto_idx].coin}'
|
||||
t = self.spawn('mmgen-tool', ['--bob', coin_arg, 'listaddresses'])
|
||||
addr = [s for s in strip_ansi_escapes(t.read()).splitlines() if 'C:1 No' in s][0].split()[3]
|
||||
|
|
@ -351,7 +351,7 @@ class CmdTestSwap(CmdTestRegtest, CmdTestAutosignThreaded):
|
|||
return t
|
||||
|
||||
def bob_bal_recv(self):
|
||||
return self._bob_bal(1, '1500')
|
||||
return self._bob_bal(1, '15')
|
||||
|
||||
def _swaptxcreate_ui_common(
|
||||
self,
|
||||
|
|
@ -487,7 +487,7 @@ class CmdTestSwap(CmdTestRegtest, CmdTestAutosignThreaded):
|
|||
def swaptxsign1_create(self):
|
||||
self.get_file_with_ext('rawtx', delete_all=True)
|
||||
return self._swaptxcreate_ui_common(
|
||||
self._swaptxcreate(['LTC', '5.4321', f'{self.sid}:S:2', 'BCH', f'{self.sid}:C:2']))
|
||||
self._swaptxcreate(['LTC', '4.321', f'{self.sid}:S:2', 'BCH', f'{self.sid}:C:2']))
|
||||
|
||||
def swaptxsign1(self):
|
||||
return self._swaptxsign()
|
||||
|
|
@ -605,17 +605,17 @@ class CmdTestSwap(CmdTestRegtest, CmdTestAutosignThreaded):
|
|||
return self._generate_for_proto(2)
|
||||
|
||||
def swap_bal1(self):
|
||||
return self._bob_bal(1, '1494.56784238')
|
||||
return self._bob_bal(1, '10.67894238')
|
||||
|
||||
def swap_bal2(self):
|
||||
return self._bob_bal(1, '1382.79038152')
|
||||
return self._bob_bal(1, '8.90148152')
|
||||
|
||||
def swap_bal3(self):
|
||||
return self._bob_bal(0, '999.99990407')
|
||||
|
||||
def swaptxsign1_do(self):
|
||||
return self._swaptxcreate_ui_common(
|
||||
self._swaptxcreate(['LTC', '111.777444', f'{self.sid}:B:2', 'BCH', f'{self.sid}:C:2'], action='txdo'),
|
||||
self._swaptxcreate(['LTC', '1.777444', f'{self.sid}:B:2', 'BCH', f'{self.sid}:C:2'], action='txdo'),
|
||||
sign_and_send = True,
|
||||
file_desc = 'Sent transaction')
|
||||
|
||||
|
|
|
|||
|
|
@ -144,7 +144,7 @@ class UnitTestHelpers:
|
|||
asyncio.run(ret)
|
||||
except Exception as e:
|
||||
exc = type(e).__name__
|
||||
emsg = e.args[0]
|
||||
emsg = e.args[0] if e.args else '(unspecified error)'
|
||||
cfg._util.vmsg(f' {exc:{exc_w}} [{emsg}]')
|
||||
assert exc == exc_chk, m_exc.format(exc, exc_chk)
|
||||
assert re.search(emsg_chk, emsg), m_err.format(emsg, emsg_chk)
|
||||
|
|
|
|||
|
|
@ -180,23 +180,25 @@ class unit_tests:
|
|||
proto = init_proto(cfg, coin)
|
||||
addr = make_burn_addr(proto, addrtype)
|
||||
|
||||
vmsg('\nTesting memo initialization:')
|
||||
m = Memo(proto, addr)
|
||||
vmsg(f'str(memo): {m}')
|
||||
vmsg(f'repr(memo): {m!r}')
|
||||
if True:
|
||||
vmsg('\nTesting memo initialization:')
|
||||
m = Memo(proto, addr)
|
||||
vmsg(f'str(memo): {m}')
|
||||
vmsg(f'repr(memo): {m!r}')
|
||||
|
||||
vmsg('\nTesting memo parsing:')
|
||||
p = Memo.parse(m)
|
||||
from pprint import pformat
|
||||
vmsg(pformat(p._asdict()))
|
||||
assert p.proto == 'THORChain'
|
||||
assert p.function == 'SWAP'
|
||||
assert p.chain == coin.upper()
|
||||
assert p.asset == coin.upper()
|
||||
assert p.address == addr.views[addr.view_pref]
|
||||
assert p.trade_limit == 0
|
||||
assert p.stream_interval == 1
|
||||
assert p.stream_quantity == 0 # auto
|
||||
p = Memo.parse(m)
|
||||
|
||||
vmsg('\nTesting memo parsing:')
|
||||
from pprint import pformat
|
||||
vmsg(pformat(p._asdict()))
|
||||
assert p.proto == 'THORChain'
|
||||
assert p.function == 'SWAP'
|
||||
assert p.chain == coin.upper()
|
||||
assert p.asset == coin.upper()
|
||||
assert p.address == addr.views[addr.view_pref]
|
||||
assert p.trade_limit == 0
|
||||
assert p.stream_interval == 1
|
||||
assert p.stream_quantity == 0 # auto
|
||||
|
||||
vmsg('\nTesting is_partial_memo():')
|
||||
for vec in (
|
||||
|
|
@ -230,13 +232,13 @@ class unit_tests:
|
|||
return lambda: Memo.parse(s)
|
||||
|
||||
ut.process_bad_data((
|
||||
('bad1', 'SwapMemoParseError', 'must contain', bad('x')),
|
||||
('bad2', 'SwapMemoParseError', 'must contain', bad('y:z:x')),
|
||||
('bad3', 'SwapMemoParseError', 'function abbrev', bad('z:l:foobar:0/1/0')),
|
||||
('bad4', 'SwapMemoParseError', 'asset abbrev', bad('=:x:foobar:0/1/0')),
|
||||
('bad5', 'SwapMemoParseError', 'failed to parse', bad('=:l:foobar:n')),
|
||||
('bad6', 'SwapMemoParseError', 'non-integer', bad('=:l:foobar:x/1/0')),
|
||||
('bad7', 'SwapMemoParseError', 'extra', bad('=:l:foobar:0/1/0:x')),
|
||||
('bad1', 'SwapMemoParseError', 'must contain', bad('x')),
|
||||
('bad2', 'SwapMemoParseError', 'must contain', bad('y:z:x')),
|
||||
('bad3', 'SwapMemoParseError', 'function abbrev', bad('z:l:foobar:0/1/0')),
|
||||
('bad4', 'SwapMemoParseError', 'asset abbrev', bad('=:x:foobar:0/1/0')),
|
||||
('bad5', 'SwapMemoParseError', 'failed to parse', bad('=:l:foobar:n')),
|
||||
('bad6', 'SwapMemoParseError', 'non-integer', bad('=:l:foobar:x/1/0')),
|
||||
('bad7', 'SwapMemoParseError', 'extra', bad('=:l:foobar:0/1/0:x')),
|
||||
), pfx='')
|
||||
|
||||
return True
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue