fixes and cleanups throughout

This commit is contained in:
The MMGen Project 2025-03-21 05:46:31 +03:00
commit 1769b100b5
Signed by: mmgen
GPG key ID: 3F8B1861E32B7DA2
8 changed files with 132 additions and 107 deletions

View file

@ -14,7 +14,6 @@ proto.btc.tx.base: Bitcoin base transaction class
from collections import namedtuple
from ....addr import CoinAddr
from ....tx.base import Base as TxBase
from ....obj import MMGenList, HexStr, ListItemAttr
from ....util import msg, make_chksum_6, die, pp_fmt
@ -175,7 +174,6 @@ class Base(TxBase):
_deserialized = None
class Output(TxBase.Output): # output contains either addr or data, but not both
addr = ListItemAttr(CoinAddr, include_proto=True) # ImmutableAttr in parent cls
data = ListItemAttr(OpReturnData, include_proto=True) # type None in parent cls
class InputList(TxBase.InputList):

View file

@ -14,10 +14,10 @@ proto.eth.tx.base: Ethereum base transaction class
from collections import namedtuple
from ....tx import base as TxBase
from ....obj import HexStr, Int
from ....tx.base import Base as TxBase
from ....obj import Int
class Base(TxBase.Base):
class Base(TxBase):
rel_fee_desc = 'gas price'
rel_fee_disp = 'gas price in Gwei'
@ -25,7 +25,7 @@ class Base(TxBase.Base):
dfl_gas = 21000 # the startGas amt used in the transaction
# for simple sends with no data, startGas = 21000
contract_desc = 'contract'
usr_contract_data = HexStr('')
usr_contract_data = b''
disable_fee_check = False
@property

View file

@ -15,7 +15,7 @@ proto.eth.tx.new: Ethereum new transaction class
import json
from ....tx import new as TxBase
from ....obj import Int, ETHNonce, MMGenTxID, HexStr
from ....obj import Int, ETHNonce, MMGenTxID
from ....util import msg, is_int, is_hex_str, make_chksum_6, suf, die
from ....tw.ctl import TwCtl
from ....addr import is_mmgen_id, is_coin_addr
@ -42,7 +42,7 @@ class New(Base, TxBase.New):
m = "'--contract-data' option may not be used with token transaction"
assert 'Token' not in self.name, m
with open(self.cfg.contract_data) as fp:
self.usr_contract_data = HexStr(fp.read().strip())
self.usr_contract_data = bytes.fromhex(fp.read().strip())
self.disable_fee_check = True
async def get_nonce(self):
@ -58,7 +58,7 @@ class New(Base, TxBase.New):
'startGas': self.gas,
'nonce': await self.get_nonce(),
'chainId': self.rpc.chainID,
'data': self.usr_contract_data}
'data': self.usr_contract_data.hex()}
# Instead of serializing tx data as with BTC, just create a JSON dump.
# This complicates things but means we avoid using the rlp library to deserialize the data,
@ -88,12 +88,12 @@ class New(Base, TxBase.New):
if lc != 1:
die(1, f'{lc} output{suf(lc)} specified, but Ethereum transactions must have exactly one')
arg = self.parse_cmdline_arg(self.proto, cmd_args[0], ad_f, ad_w)
a = self.parse_cmdline_arg(self.proto, cmd_args[0], ad_f, ad_w)
self.add_output(
coinaddr = arg.addr,
amt = self.proto.coin_amt(arg.amt or '0'),
is_chg = not arg.amt)
coinaddr = a.addr,
amt = self.proto.coin_amt(a.amt or '0'),
is_chg = not a.amt)
self.add_mmaddrs_to_outputs(ad_f, ad_w)

View file

@ -13,8 +13,15 @@ swap.proto.thorchain.thornode: THORChain swap protocol network query ops
"""
import json
from collections import namedtuple
from ....amt import UniAmt
_gd = namedtuple('gas_unit_data', ['code', 'disp'])
gas_unit_data = {
'satsperbyte': _gd('s', 'sat/byte'),
'gwei': _gd('G', 'Gwei'),
}
class ThornodeRPCClient:
http_hdrs = {'Content-Type': 'application/json'}
@ -49,7 +56,7 @@ class Thornode:
def __init__(self, tx, amt):
self.tx = tx
self.in_amt = amt
self.in_amt = UniAmt(str(amt))
self.rpc = ThornodeRPCClient(tx)
def get_quote(self):
@ -73,8 +80,9 @@ class Thornode:
tx = self.tx
in_coin = tx.send_proto.coin
out_coin = tx.recv_proto.coin
in_amt = self.in_amt
in_amt = UniAmt(str(self.in_amt))
out_amt = UniAmt(int(d['expected_amount_out']), from_unit='satoshi')
gas_unit = d['gas_rate_units']
if trade_limit:
from . import ExpInt4
@ -94,19 +102,22 @@ class Thornode:
trade_limit_disp = ''
tx_size_adj = 0
def get_estimated_fee():
return tx.feespec2abs(
fee_arg = d['recommended_gas_rate'] + gas_unit_data[gas_unit].code,
tx_size = tx.estimate_size() + tx_size_adj)
_amount_in_label = 'Amount in:'
if deduct_est_fee:
if d['gas_rate_units'] == 'satsperbyte':
in_amt -= tx.feespec2abs(d['recommended_gas_rate'] + 's', tx.estimate_size() + tx_size_adj)
if gas_unit in gas_unit_data:
in_amt -= UniAmt(str(get_estimated_fee()))
out_amt *= (in_amt / self.in_amt)
_amount_in_label = 'Amount in (estimated):'
else:
ymsg('Warning: unknown gas unit ‘{}’, cannot estimate fee'.format(d['gas_rate_units']))
ymsg(f'Warning: unknown gas unit ‘{gas_unit}’, cannot estimate fee')
min_in_amt = UniAmt(int(d['recommended_min_amount_in']), from_unit='satoshi')
gas_unit = {
'satsperbyte': 'sat/byte',
}.get(d['gas_rate_units'], d['gas_rate_units'])
gas_unit_disp = _.disp if (_ := gas_unit_data.get(gas_unit)) else gas_unit
elapsed_disp = format_elapsed_hr(d['expiry'], future_msg='from now')
fees = d['fees']
fees_t = UniAmt(int(fees['total']), from_unit='satoshi')
@ -118,14 +129,14 @@ class Thornode:
{cyan(hdr)}
Protocol: {blue(name)}
Direction: {orange(f'{in_coin} => {out_coin}')}
Vault address: {cyan(d['inbound_address'])}
Vault address: {cyan(self.inbound_address)}
Quote expires: {pink(elapsed_disp)} [{make_timestr(d['expiry'])}]
{_amount_in_label:<22} {in_amt.hl()} {in_coin}
Expected amount out: {out_amt.hl()} {out_coin}{trade_limit_disp}
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)}
Recommended fee: {pink(d['recommended_gas_rate'])} {pink(gas_unit_disp)}
Fees:
Total: {fees_t.hl()} {out_coin} ({pink(fees_pct_disp)})
Slippage: {pink(slip_pct_disp)}
@ -137,8 +148,9 @@ class Thornode:
@property
def rel_fee_hint(self):
if self.data['gas_rate_units'] == 'satsperbyte':
return f'{self.data["recommended_gas_rate"]}s'
gas_unit = self.data['gas_rate_units']
if gas_unit in gas_unit_data:
return self.data['recommended_gas_rate'] + gas_unit_data[gas_unit].code
def __str__(self):
from pprint import pformat

View file

@ -102,6 +102,7 @@ class Base(MMGenObject):
tw_copy_attrs = {'scriptPubKey', 'vout', 'amt', 'comment', 'mmid', 'addr', 'confs', 'txid'}
class Output(MMGenTxIO):
addr = ListItemAttr(CoinAddr, include_proto=True) # ImmutableAttr in parent cls
is_chg = ListItemAttr(bool, typeconv=False)
is_vault = ListItemAttr(bool, typeconv=False)
data = ListItemAttr(None, typeconv=False) # placeholder

View file

@ -118,67 +118,6 @@ def set_vbals(daemon_id):
vbal7 = '1000124.91944498212345678'
vbal9 = '1.226261'
bals = lambda k: {
'1': [ ('98831F3A:E:1', '123.456')],
'2': [ ('98831F3A:E:1', '123.456'), ('98831F3A:E:11', '1.234')],
'3': [ ('98831F3A:E:1', '123.456'), ('98831F3A:E:11', '1.234'), ('98831F3A:E:21', '2.345')],
'4': [ ('98831F3A:E:1', '100'),
('98831F3A:E:2', '23.45495'),
('98831F3A:E:11', '1.234'),
('98831F3A:E:21', '2.345')],
'5': [ ('98831F3A:E:1', '100'),
('98831F3A:E:2', '23.45495'),
('98831F3A:E:11', '1.234'),
('98831F3A:E:21', '2.345'),
(burn_addr + r'\s+non-MMGen', amt1)],
'8': [ ('98831F3A:E:1', '0'),
('98831F3A:E:2', '23.45495'),
('98831F3A:E:11', vbal1),
('98831F3A:E:12', '99.99895'),
('98831F3A:E:21', '2.345'),
(burn_addr + r'\s+non-MMGen', amt1)],
'9': [ ('98831F3A:E:1', '0'),
('98831F3A:E:2', '23.45495'),
('98831F3A:E:11', vbal1),
('98831F3A:E:12', vbal2),
('98831F3A:E:21', '2.345'),
(burn_addr + r'\s+non-MMGen', amt1)],
'10': [ ('98831F3A:E:1', '0'),
('98831F3A:E:2', '23.0218'),
('98831F3A:E:3', '0.4321'),
('98831F3A:E:11', vbal1),
('98831F3A:E:12', vbal2),
('98831F3A:E:21', '2.345'),
(burn_addr + r'\s+non-MMGen', amt1)]
}[k]
token_bals = lambda k: {
'1': [ ('98831F3A:E:11', '1000', '1.234')],
'2': [ ('98831F3A:E:11', '998.76544', vbal3),
('98831F3A:E:12', '1.23456', '0')],
'3': [ ('98831F3A:E:11', '110.654317776666555545', vbal1),
('98831F3A:E:12', '1.23456', '0')],
'4': [ ('98831F3A:E:11', '110.654317776666555545', vbal1),
('98831F3A:E:12', '1.23456', '0'),
(burn_addr + r'\s+non-MMGen', amt2, amt1)],
'5': [ ('98831F3A:E:11', '110.654317776666555545', vbal1),
('98831F3A:E:12', '1.23456', '99.99895'),
(burn_addr + r'\s+non-MMGen', amt2, amt1)],
'6': [ ('98831F3A:E:11', '110.654317776666555545', vbal1),
('98831F3A:E:12', '0', vbal2),
('98831F3A:E:13', '1.23456', '0'),
(burn_addr + r'\s+non-MMGen', amt2, amt1)],
'7': [ ('98831F3A:E:11', '67.444317776666555545', vbal9),
('98831F3A:E:12', '43.21', vbal2),
('98831F3A:E:13', '1.23456', '0'),
(burn_addr + r'\s+non-MMGen', amt2, amt1)]
}[k]
token_bals_getbalance = lambda k: {
'1': (vbal4, '999777.12345689012345678'),
'2': ('111.888877776666555545', '888.111122223333444455')
}[k]
coin = cfg.coin
class CmdTestEthdev(CmdTestBase, CmdTestShared):
@ -187,6 +126,70 @@ class CmdTestEthdev(CmdTestBase, CmdTestShared):
passthru_opts = ('coin', 'daemon_id', 'http_timeout', 'rpc_backend')
tmpdir_nums = [22]
color = True
menu_prompt = 'efresh balance:\b'
input_sels_prompt = 'to spend from: '
bals = lambda self, k: {
'1': [ ('98831F3A:E:1', '123.456')],
'2': [ ('98831F3A:E:1', '123.456'), ('98831F3A:E:11', '1.234')],
'3': [ ('98831F3A:E:1', '123.456'), ('98831F3A:E:11', '1.234'), ('98831F3A:E:21', '2.345')],
'4': [ ('98831F3A:E:1', '100'),
('98831F3A:E:2', '23.45495'),
('98831F3A:E:11', '1.234'),
('98831F3A:E:21', '2.345')],
'5': [ ('98831F3A:E:1', '100'),
('98831F3A:E:2', '23.45495'),
('98831F3A:E:11', '1.234'),
('98831F3A:E:21', '2.345'),
(burn_addr + r'\s+non-MMGen', amt1)],
'8': [ ('98831F3A:E:1', '0'),
('98831F3A:E:2', '23.45495'),
('98831F3A:E:11', vbal1),
('98831F3A:E:12', '99.99895'),
('98831F3A:E:21', '2.345'),
(burn_addr + r'\s+non-MMGen', amt1)],
'9': [ ('98831F3A:E:1', '0'),
('98831F3A:E:2', '23.45495'),
('98831F3A:E:11', vbal1),
('98831F3A:E:12', vbal2),
('98831F3A:E:21', '2.345'),
(burn_addr + r'\s+non-MMGen', amt1)],
'10': [ ('98831F3A:E:1', '0'),
('98831F3A:E:2', '23.0218'),
('98831F3A:E:3', '0.4321'),
('98831F3A:E:11', vbal1),
('98831F3A:E:12', vbal2),
('98831F3A:E:21', '2.345'),
(burn_addr + r'\s+non-MMGen', amt1)]
}[k]
token_bals = lambda self, k: {
'1': [ ('98831F3A:E:11', '1000', '1.234')],
'2': [ ('98831F3A:E:11', '998.76544', vbal3),
('98831F3A:E:12', '1.23456', '0')],
'3': [ ('98831F3A:E:11', '110.654317776666555545', vbal1),
('98831F3A:E:12', '1.23456', '0')],
'4': [ ('98831F3A:E:11', '110.654317776666555545', vbal1),
('98831F3A:E:12', '1.23456', '0'),
(burn_addr + r'\s+non-MMGen', amt2, amt1)],
'5': [ ('98831F3A:E:11', '110.654317776666555545', vbal1),
('98831F3A:E:12', '1.23456', '99.99895'),
(burn_addr + r'\s+non-MMGen', amt2, amt1)],
'6': [ ('98831F3A:E:11', '110.654317776666555545', vbal1),
('98831F3A:E:12', '0', vbal2),
('98831F3A:E:13', '1.23456', '0'),
(burn_addr + r'\s+non-MMGen', amt2, amt1)],
'7': [ ('98831F3A:E:11', '67.444317776666555545', vbal9),
('98831F3A:E:12', '43.21', vbal2),
('98831F3A:E:13', '1.23456', '0'),
(burn_addr + r'\s+non-MMGen', amt2, amt1)]
}[k]
token_bals_getbalance = lambda self, k: {
'1': (vbal4, '999777.12345689012345678'),
'2': ('111.888877776666555545', '888.111122223333444455')
}[k]
cmd_group_in = (
('setup', f'dev mode tests for coin {coin} (start daemon)'),
('subgroup.misc', []),
@ -955,7 +958,7 @@ class CmdTestEthdev(CmdTestBase, CmdTestShared):
self.mining_delay()
t = self.spawn('mmgen-tool', self.eth_args + ['twview', 'wide=1'])
text = t.read(strip_color=True)
for addr, amt in bals(n):
for addr, amt in self.bals(n):
pat = r'\D{}\D.*\D{}\D'.format(addr, amt.replace('.', r'\.'))
assert re.search(pat, text), pat
ss = f'Total {self.proto.coin}:'
@ -966,7 +969,7 @@ class CmdTestEthdev(CmdTestBase, CmdTestShared):
self.mining_delay()
t = self.spawn('mmgen-tool', self.eth_args + ['--token=mm1', 'twview', 'wide=1'])
text = t.read(strip_color=True)
for addr, _amt1, _amt2 in token_bals(n):
for addr, _amt1, _amt2 in self.token_bals(n):
pat = fr'{addr}\b.*\D{_amt1}\D.*\b{_amt2}\D'
assert re.search(pat, text), pat
ss = 'Total MM1:'
@ -974,8 +977,8 @@ class CmdTestEthdev(CmdTestBase, CmdTestShared):
return t
def bal_getbalance(self, sid, idx, etc_adj=False, extra_args=[]):
bal1 = token_bals_getbalance(idx)[0]
bal2 = token_bals_getbalance(idx)[1]
bal1 = self.token_bals_getbalance(idx)[0]
bal2 = self.token_bals_getbalance(idx)[1]
bal1 = Decimal(bal1)
t = self.spawn('mmgen-tool', self.eth_args + extra_args + ['getbalance'])
t.expect(rf'{sid}:.*'+str(bal1), regex=True)
@ -1056,12 +1059,12 @@ class CmdTestEthdev(CmdTestBase, CmdTestShared):
omsg(yellow('Warning: all gas was used!'))
return res
async def token_deploy(self, num, key, gas, mmgen_cmd='txdo', tx_fee='8G'):
async def token_deploy(self, num, key, gas, mmgen_cmd='txdo', gas_price='8G'):
keyfile = joinpath(self.tmpdir, parity_devkey_fn)
fn = joinpath(self.tmpdir, 'mm'+str(num), key+'.bin')
args = [
'-B',
f'--fee={tx_fee}',
f'--fee={gas_price}',
f'--gas={gas}',
f'--contract-data={fn}',
f'--inputs={dfl_devaddr}',
@ -1099,7 +1102,7 @@ class CmdTestEthdev(CmdTestBase, CmdTestShared):
async def token_deploy1b(self):
return await self.token_deploy(num=1, key='Owned', gas=1_000_000)
async def token_deploy1c(self):
return await self.token_deploy(num=1, key='Token', gas=4_000_000, tx_fee='7G')
return await self.token_deploy(num=1, key='Token', gas=4_000_000, gas_price='7G')
def tx_status2(self):
return self.tx_status(
@ -1138,7 +1141,7 @@ class CmdTestEthdev(CmdTestBase, CmdTestShared):
to_addr = usr_addrs[i],
amt = amt,
key = dfl_devkey,
gas = self.proto.coin_amt(60000, from_unit='wei'),
gas = self.proto.coin_amt(120000, from_unit='wei'),
gasPrice = self.proto.coin_amt(8, from_unit='Gwei'))
if (await self.get_tx_receipt(txid)).status == 0:
die(2, 'Transfer of token funds failed. Aborting')
@ -1459,10 +1462,8 @@ class CmdTestEthdev(CmdTestBase, CmdTestShared):
t = self.spawn('mmgen-txcreate', self.eth_args + args + ['-B', '-i'], pexpect_spawn=pexpect_spawn)
menu_prompt = 'efresh balance:\b'
t.expect(menu_prompt, 'M')
t.expect(menu_prompt, action)
t.expect(self.menu_prompt, 'M')
t.expect(self.menu_prompt, action)
t.expect(r'return to main menu): ', out_num+'\n')
for p, r in (
@ -1479,8 +1480,8 @@ class CmdTestEthdev(CmdTestBase, CmdTestShared):
'Label removed from account #{}')
t.expect(m.format(out_num))
t.expect(menu_prompt, 'M')
t.expect(menu_prompt, 'q')
t.expect(self.menu_prompt, 'M')
t.expect(self.menu_prompt, 'q')
t.expect('Total unspent:')

View file

@ -28,6 +28,8 @@ sample1 = gr_uc[:24]
sample2 = '00010203040506'
class CmdTestSwapMethods:
menu_prompt = 'abel:\b'
input_sels_prompt = 'to spend: '
def _addrgen_bob(self, proto_idx, mmtypes, subseed_idx=None):
return self.addrgen('bob', subseed_idx=subseed_idx, mmtypes=mmtypes, proto=self.protos[proto_idx])
@ -107,8 +109,8 @@ class CmdTestSwapMethods:
reload_quote = False,
sign_and_send = False,
expect = None):
t.expect('abel:\b', 'q')
t.expect('to spend: ', f'{inputs}\n')
t.expect(self.menu_prompt, 'q')
t.expect(self.input_sels_prompt, f'{inputs}\n')
if reload_quote:
t.expect('to continue: ', 'r') # reload swap quote
t.expect('to continue: ', '\n') # exit swap quote view
@ -136,7 +138,8 @@ class CmdTestSwapMethods:
['-q', '-d', self.tmpdir, '-B', '--bob']
+ add_opts
+ args,
exit_val = exit_val)
exit_val = exit_val,
no_passthru_opts = ['coin'])
def _swaptxcreate_bad(self, args, *, exit_val=1, expect1=None, expect2=None):
t = self._swaptxcreate(args, exit_val=exit_val)
@ -159,7 +162,10 @@ class CmdTestSwapMethods:
def _swaptxsend(self, *, add_opts=[], spawn_only=False):
fn = self.get_file_with_ext('sigtx')
t = self.spawn('mmgen-txsend', add_opts + ['-q', '-d', self.tmpdir, '--bob', fn])
t = self.spawn(
'mmgen-txsend',
add_opts + ['-q', '-d', self.tmpdir, '--bob', fn],
no_passthru_opts = ['coin'])
if spawn_only:
return t
t.expect('view: ', 'v')
@ -170,7 +176,10 @@ class CmdTestSwapMethods:
def _swaptxsign(self, *, add_opts=[], expect=None):
self.get_file_with_ext('sigtx', delete_all=True)
fn = self.get_file_with_ext('rawtx')
t = self.spawn('mmgen-txsign', add_opts + ['-d', self.tmpdir, '--bob', fn])
t = self.spawn(
'mmgen-txsign',
add_opts + ['-d', self.tmpdir, '--bob', fn],
no_passthru_opts = ['coin'])
t.view_tx('t')
if expect:
t.expect(expect)
@ -475,7 +484,11 @@ class CmdTestSwap(CmdTestRegtest, CmdTestAutosignThreaded, CmdTestSwapMethods):
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]
t = self.spawn('mmgen-regtest', [coin_arg, 'send', addr, str(amt)], no_passthru_opts=True, no_msg=True)
t = self.spawn(
'mmgen-regtest',
[coin_arg, 'send', addr, str(amt)],
no_passthru_opts = ['coin'],
no_msg = True)
return t
def bob_bal_recv(self):

View file

@ -161,7 +161,7 @@ class CmdTestRunner:
if self.logging:
self.log_fd.write('[{}][{}:{}] {}\n'.format(
(self.proto.coin.lower() if 'coin' in passthru_opts else 'NONE'),
(self.proto.coin.lower() if 'coin' in self.tg.passthru_opts else 'NONE'),
self.tg.group_name,
self.tg.test_name,
cmd_disp))