Browse Source

fixes and cleanups throughout

The MMGen Project 1 week ago
parent
commit
1769b100b5

+ 0 - 2
mmgen/proto/btc/tx/base.py

@@ -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):

+ 4 - 4
mmgen/proto/eth/tx/base.py

@@ -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

+ 7 - 7
mmgen/proto/eth/tx/new.py

@@ -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)
 

+ 24 - 12
mmgen/swap/proto/thorchain/thornode.py

@@ -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

+ 1 - 0
mmgen/tx/base.py

@@ -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

+ 76 - 75
test/cmdtest_d/ct_ethdev.py

@@ -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:')
 

+ 19 - 6
test/cmdtest_d/ct_swap.py

@@ -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):

+ 1 - 1
test/cmdtest_d/runner.py

@@ -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))