From d1970d1473ffe9e760161fe8a22c8f04402aa663 Mon Sep 17 00:00:00 2001 From: MMGen Date: Sat, 28 Jul 2018 13:52:43 +0000 Subject: [PATCH] tx.process_cmd_args(); ETH: no-change tx support, tx.get_status(), other fixes --- mmgen/altcoins/eth/tx.py | 98 ++++++++++++++++++++++++++++------------ mmgen/main_txdo.py | 2 +- mmgen/main_txsend.py | 2 +- mmgen/obj.py | 10 +++- mmgen/tx.py | 64 ++++++++++++++------------ test/test.py | 77 +++++++++++++++++++++++++++---- 6 files changed, 180 insertions(+), 73 deletions(-) diff --git a/mmgen/altcoins/eth/tx.py b/mmgen/altcoins/eth/tx.py index 7437dcfd..79223639 100755 --- a/mmgen/altcoins/eth/tx.py +++ b/mmgen/altcoins/eth/tx.py @@ -53,6 +53,8 @@ class EthereumMMGenTX(MMGenTX): if hasattr(opt,'tx_gas') and opt.tx_gas: self.tx_gas = self.start_gas = ETHAmt(int(opt.tx_gas),'wei') if hasattr(opt,'contract_data') and opt.contract_data: + m = "'--contract-data' option may not be used with token transaction" + assert not 'Token' in type(self).__name__, m self.data = HexStr(open(opt.contract_data).read().strip()) self.disable_fee_check = True @@ -62,7 +64,10 @@ class EthereumMMGenTX(MMGenTX): @classmethod def get_exec_status(cls,txid): - return int(g.rpch.eth_getTransactionReceipt('0x'+txid)['status'],16) + d = g.rpch.eth_getTransactionReceipt('0x'+txid) + if 'contractAddress' in d and d['contractAddress']: + msg('Contract address: {}'.format(d['contractAddress'].replace('0x',''))) + return int(d['status'],16) def is_replaceable(self): return True @@ -120,6 +125,9 @@ class EthereumMMGenTX(MMGenTX): self.txobj = o return d # 'token_addr','decimals' required by subclass + def get_nonce(self): + return ETHNonce(int(g.rpch.parity_nextNonce('0x'+self.inputs[0].addr),16)) + def make_txobj(self): # create_raw self.txobj = { 'from': self.inputs[0].addr, @@ -127,7 +135,7 @@ class EthereumMMGenTX(MMGenTX): 'amt': self.outputs[0].amt if self.outputs else ETHAmt(0), 'gasPrice': self.usr_rel_fee or self.fee_abs2rel(self.fee,to_unit='eth'), 'startGas': self.start_gas, - 'nonce': ETHNonce(int(g.rpch.parity_nextNonce('0x'+self.inputs[0].addr),16)), + 'nonce': self.get_nonce(), 'chainId': Int(g.rpch.parity_chainId(),16), 'data': self.data, } @@ -145,8 +153,6 @@ class EthereumMMGenTX(MMGenTX): self.update_txid() def del_output(self,idx): pass - def update_output_amt(self,idx,amt): pass - def get_chg_output_idx(self): return None def update_txid(self): assert not is_hex_str(self.hex),'update_txid() must be called only when self.hex is not hex data' @@ -157,26 +163,12 @@ class EthereumMMGenTX(MMGenTX): def process_cmd_args(self,cmd_args,ad_f,ad_w): lc = len(cmd_args) - - if lc == 0 and self.data: - return - elif lc != 1: + if lc == 0 and self.data and not 'Token' in type(self).__name__: return + if lc != 1: fs = '{} output{} specified, but Ethereum transactions must have exactly one' die(1,fs.format(lc,suf(lc))) - a = list(cmd_args)[0] - if ',' in a: - a1,a2 = a.split(',',1) - if is_mmgen_id(a1) or is_coin_addr(a1): - coin_addr = mmaddr2coinaddr(a1,ad_w,ad_f) if is_mmgen_id(a1) else CoinAddr(a1) - self.add_output(coin_addr,ETHAmt(a2)) - else: - die(2,"{}: invalid subargument in command-line argument '{}'".format(a1,a)) - else: - die(2,'{}: invalid command-line argument'.format(a)) - - if not self.outputs: - die(2,'At least one output must be specified on the command line') + for a in cmd_args: self.process_cmd_arg(a,ad_f,ad_w) def select_unspent(self,unspent): prompt = 'Enter an account to spend from: ' @@ -198,7 +190,8 @@ class EthereumMMGenTX(MMGenTX): # given absolute fee in ETH, return gas price in Gwei using tx_gas def fee_abs2rel(self,abs_fee,to_unit='Gwei'): ret = ETHAmt(int(abs_fee.toWei() / self.tx_gas.toWei()),'wei') - return ret if to_unit == 'eth' else ret.to_unit(to_unit) + dmsg('fee_abs2rel() ==> {} ETH'.format(ret)) + return ret if to_unit == 'eth' else ret.to_unit(to_unit,show_decimal=True) # get rel_fee (gas price) from network, return in native wei def get_rel_fee_from_network(self): @@ -234,6 +227,14 @@ class EthereumMMGenTX(MMGenTX): else: return abs_fee + def update_change_output(self,change_amt): + if self.outputs and self.outputs[0].is_chg: + self.update_output_amt(0,ETHAmt(change_amt)) + + def update_send_amt(self,foo): + if self.outputs: + self.send_amt = self.outputs[0].amt + def format_view_body(self,blockcount,nonmm_str,max_mmwid,enl,terse): m = {} for k in ('in','out'): @@ -253,7 +254,7 @@ class EthereumMMGenTX(MMGenTX): return fs.format( *((self.txobj[k] if self.txobj[k] != '' else Str('None')).hl() for k in keys), d='{}... ({} bytes)'.format(self.txobj['data'][:40],ld/2) if ld else Str('None'), c=g.dcoin if len(self.outputs) else '', - g=yellow(str(self.txobj['gasPrice'].toGwei())), + g=yellow(str(self.txobj['gasPrice'].to_unit('Gwei',show_decimal=True))), G=yellow(str(self.txobj['startGas'].toKwei())), t_mmid=m['out'] if len(self.outputs) else '', f_mmid=m['in']) @@ -266,9 +267,13 @@ class EthereumMMGenTX(MMGenTX): def format_view_rel_fee(self,terse): return '' def format_view_verbose_footer(self): return '' # TODO + def set_g_token(self): + die(2,"Not a Token transaction object. Have you omitted the '--token' option?") + def final_inputs_ok_msg(self,change_amt): m = "Transaction leaves {} {} in the sender's account" - return m.format(g.proto.coin_amt(change_amt).hl(),g.coin) + chg = 0 if (self.outputs and self.outputs[0].is_chg) else change_amt + return m.format(ETHAmt(chg).hl(),g.coin) def do_sign(self,d,wif,tx_num_str): @@ -309,7 +314,33 @@ class EthereumMMGenTX(MMGenTX): return self.do_sign(self.txobj,keys[0].sec.wif,tx_num_str) - def get_status(self,status=False): pass # TODO + def is_in_mempool(self): +# pmsg(g.rpch.parity_pendingTransactions()) + return '0x'+self.coin_txid in map(lambda x: x['hash'],g.rpch.parity_pendingTransactions()) + + def is_in_wallet(self): + d = g.rpch.eth_getTransactionReceipt('0x'+self.coin_txid) + if d and 'blockNumber' in d: + return 1 + int(g.rpch.eth_blockNumber(),16) - int(d['blockNumber'],16) + return False + + def get_status(self,status=False): + if self.is_in_mempool(): + msg('Transaction is in mempool' if status else 'Warning: transaction is in mempool!') + return + + confs = self.is_in_wallet() + if confs is not False: + if self.data: + exec_status = type(self).get_exec_status(self.coin_txid) + if exec_status == 0: + msg('Contract failed to execute!') + else: + msg('Contract successfully executed with status {}'.format(exec_status)) + die(0,'Transaction has {} confirmation{}'.format(confs,suf(confs,'s'))) + + if status: + die(1,'Transaction is neither in mempool nor blockchain!') def send(self,prompt_user=True,exit_on_fail=False): @@ -354,6 +385,10 @@ class EthereumTokenMMGenTX(EthereumMMGenTX): start_gas = ETHAmt(60000,'wei') fee_is_approximate = True + def update_change_output(self,change_amt): + if self.outputs[0].is_chg: + self.update_output_amt(0,self.inputs[0].amt) + def check_sufficient_funds(self,inputs_sum,sel_unspent): eth_bal = ETHAmt(int(g.rpch.eth_getBalance('0x'+sel_unspent[0].addr),16),'wei') if eth_bal == 0: # we don't know the fee yet @@ -366,13 +401,13 @@ class EthereumTokenMMGenTX(EthereumMMGenTX): def final_inputs_ok_msg(self,change_amt): m = u"Transaction leaves ≈{} {} and {} {} in the sender's account" - tbal = g.proto.coin_amt(Token(g.token).balance(self.inputs[0].addr) - self.outputs[0].amt) - chg = g.proto.coin_amt(change_amt) - return m.format(chg.hl(),g.coin,tbal.hl(),g.dcoin) + send_acct_tbal = 0 if self.outputs[0].is_chg else \ + Token(g.token).balance(self.inputs[0].addr) - self.outputs[0].amt + return m.format(ETHAmt(change_amt).hl(),g.coin,ETHAmt(send_acct_tbal).hl(),g.dcoin) def get_change_amt(self): # here we know the fee eth_bal = ETHAmt(int(g.rpch.eth_getBalance('0x'+self.inputs[0].addr),16),'wei') - return Decimal(eth_bal) - self.fee + return eth_bal - self.fee def set_g_token(self): g.dcoin = self.dcoin @@ -390,7 +425,7 @@ class EthereumTokenMMGenTX(EthereumMMGenTX): t = Token(g.token) o = t.txcreate( self.inputs[0].addr, self.outputs[0].addr, - self.outputs[0].amt, + (self.inputs[0].amt if self.outputs[0].is_chg else self.outputs[0].amt), self.start_gas, self.usr_rel_fee or self.fee_abs2rel(self.fee,to_unit='eth')) self.txobj['token_addr'] = self.token_addr = t.addr @@ -440,6 +475,9 @@ class EthereumMMGenBumpTX(EthereumMMGenTX,MMGenBumpTX): def update_fee(self,foo,fee): self.fee = fee + def get_nonce(self): + return self.txobj['nonce'] + class EthereumTokenMMGenBumpTX(EthereumTokenMMGenTX,EthereumMMGenBumpTX): pass class EthereumMMGenSplitTX(MMGenSplitTX): pass diff --git a/mmgen/main_txdo.py b/mmgen/main_txdo.py index f1c21907..3407b869 100755 --- a/mmgen/main_txdo.py +++ b/mmgen/main_txdo.py @@ -111,4 +111,4 @@ tx.send(exit_on_fail=True) tx.write_to_file(ask_overwrite=False,ask_write=False) if hasattr(tx,'token_addr'): - msg('Token address: {}'.format(tx.token_addr.hl())) + msg('Contract address: {}'.format(tx.token_addr.hl())) diff --git a/mmgen/main_txsend.py b/mmgen/main_txsend.py index 194d5c66..5826f0c6 100755 --- a/mmgen/main_txsend.py +++ b/mmgen/main_txsend.py @@ -68,4 +68,4 @@ tx.send(exit_on_fail=True) tx.write_to_file(ask_overwrite=False,ask_write=False) if hasattr(tx,'token_addr'): - msg('Token address: {}'.format(tx.token_addr.hl())) + msg('Contract address: {}'.format(tx.token_addr.hl())) diff --git a/mmgen/obj.py b/mmgen/obj.py index 9d663d2f..7ff66678 100755 --- a/mmgen/obj.py +++ b/mmgen/obj.py @@ -336,8 +336,14 @@ class BTCAmt(Decimal,Hilite,InitErrors): m = "{!r}: value cannot be converted to {} ({})" return cls.init_fail(m.format(num,cls.__name__,e[0]),on_fail) - def toSatoshi(self): return int(Decimal(self) / self.satoshi) - def to_unit(self,unit): return int(Decimal(self) / getattr(self,unit)) + def toSatoshi(self): + return int(Decimal(self) / self.satoshi) + + def to_unit(self,unit,show_decimal=False): + ret = Decimal(self) / getattr(self,unit) + if show_decimal and ret < 1: + return '{:.4f}'.format(ret) + return int(ret) @classmethod def fmtc(cls): diff --git a/mmgen/tx.py b/mmgen/tx.py index 5e341fdc..8a25241d 100755 --- a/mmgen/tx.py +++ b/mmgen/tx.py @@ -315,16 +315,22 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam self.outputs.append(MMGenTX.MMGenTxOutput(addr=coinaddr,amt=amt,is_chg=is_chg)) def get_chg_output_idx(self): - for i in range(len(self.outputs)): - if self.outputs[i].is_chg == True: - return i - return None + try: return map(lambda x: x.is_chg,self.outputs).index(True) + except ValueError: return None def update_output_amt(self,idx,amt): o = self.outputs[idx].__dict__ o['amt'] = amt self.outputs[idx] = MMGenTX.MMGenTxOutput(**o) + def update_change_output(self,change_amt): + chg_idx = self.get_chg_output_idx() + if change_amt == 0: + msg(self.no_chg_msg) + self.del_output(chg_idx) + else: + self.update_output_amt(chg_idx,g.proto.coin_amt(change_amt)) + def del_output(self,idx): self.outputs.pop(idx) @@ -1201,22 +1207,26 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam if self.dcoin: self.set_g_token() - def process_cmd_args(self,cmd_args,ad_f,ad_w): - for a in cmd_args: - if ',' in a: - a1,a2 = a.split(',',1) - if is_mmgen_id(a1) or is_coin_addr(a1): - coin_addr = mmaddr2coinaddr(a1,ad_w,ad_f) if is_mmgen_id(a1) else CoinAddr(a1) - self.add_output(coin_addr,g.proto.coin_amt(a2)) - else: - die(2,"{}: invalid subargument in command-line argument '{}'".format(a1,a)) - elif is_mmgen_id(a) or is_coin_addr(a): - if self.get_chg_output_idx() != None: - die(2,'ERROR: More than one change address listed on command line') - coin_addr = mmaddr2coinaddr(a,ad_w,ad_f) if is_mmgen_id(a) else CoinAddr(a) - self.add_output(coin_addr,g.proto.coin_amt('0'),is_chg=True) + def process_cmd_arg(self,arg,ad_f,ad_w): + + def add_output_chk(addr,amt,err_desc): + if not amt and self.get_chg_output_idx() != None: + die(2,'ERROR: More than one change address listed on command line') + if is_mmgen_id(addr) or is_coin_addr(addr): + coin_addr = mmaddr2coinaddr(addr,ad_w,ad_f) if is_mmgen_id(addr) else CoinAddr(addr) + self.add_output(coin_addr,g.proto.coin_amt(amt or '0'),is_chg=not amt) else: - die(2,'{}: invalid command-line argument'.format(a)) + die(2,"{}: invalid {} '{}'".format(addr,err_desc,','.join((addr,amt)) if amt else addr)) + + if ',' in arg: + addr,amt = arg.split(',',1) + add_output_chk(addr,amt,'coin argument in command-line argument') + else: + add_output_chk(arg,None,'command-line argument') + + def process_cmd_args(self,cmd_args,ad_f,ad_w): + + for a in cmd_args: self.process_cmd_arg(a,ad_f,ad_w) if self.get_chg_output_idx() == None: die(2,( 'ERROR: No change output specified', @@ -1336,6 +1346,10 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam def check_fee(self): assert self.sum_inputs() - self.sum_outputs() <= g.proto.max_tx_fee + def update_send_amt(self,change_amt): + if not self.send_amt: + self.send_amt = change_amt + def create(self,cmd_args,locktime,do_info=False): assert type(locktime) == int @@ -1367,16 +1381,8 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam if opt.rbf: self.inputs[0].sequence = g.max_int - 2 # handles the locktime case too elif locktime: self.inputs[0].sequence = g.max_int - 1 - chg_idx = self.get_chg_output_idx() - - if change_amt == 0: - msg(self.no_chg_msg) - self.del_output(chg_idx) - else: - self.update_output_amt(chg_idx,g.proto.coin_amt(change_amt)) - - if not self.send_amt and len(self.outputs): - self.send_amt = change_amt + self.update_change_output(change_amt) + self.update_send_amt(change_amt) if not opt.yes: self.add_comment() # edits an existing comment diff --git a/test/test.py b/test/test.py index 9759a7b2..1a51f8db 100755 --- a/test/test.py +++ b/test/test.py @@ -885,6 +885,8 @@ cmd_group['ethdev'] = ( ('ethdev_txsign3', 'signing the transaction'), ('ethdev_txsend3', 'sending the transaction'), + ('ethdev_tx_status1', 'getting the transaction status'), + ('ethdev_txcreate4', 'creating a transaction (spend from MMGen address, low TX fee)'), ('ethdev_txbump', 'bumping the transaction fee'), @@ -909,6 +911,8 @@ cmd_group['ethdev'] = ( ('ethdev_token_deploy1b', 'deploying ERC20 token #1 (Owned)'), ('ethdev_token_deploy1c', 'deploying ERC20 token #1 (Token)'), + ('ethdev_tx_status2', 'getting the transaction status'), + ('ethdev_token_compile2', 'compiling ERC20 token #2'), ('ethdev_token_deploy2a', 'deploying ERC20 token #2 (SafeMath)'), @@ -938,10 +942,23 @@ cmd_group['ethdev'] = ( ('ethdev_addrimport_token_burn_addr',"importing the token burn address"), - ('ethdev_token_bal', 'the token balance'), + ('ethdev_token_bal1', 'the token balance'), ('ethdev_token_bal_getbalance','the token balance (getbalance)'), - ('ethdev_stop', 'stopping parity'), + ('ethdev_txcreate_noamt', 'creating a transaction (full amount send)'), + ('ethdev_txsign_noamt', 'signing the transaction'), + ('ethdev_txsend_noamt', 'sending the transaction'), + + ('ethdev_token_bal2', 'the token balance'), + ('ethdev_bal3', 'the ETH balance'), + + ('ethdev_token_txcreate_noamt', 'creating a token transaction (full amount send)'), + ('ethdev_token_txsign_noamt', 'signing the transaction'), + ('ethdev_token_txsend_noamt', 'sending the transaction'), + + ('ethdev_token_bal3', 'the token balance'), + +# ('ethdev_stop', 'stopping parity'), ) cmd_group['autosign'] = ( @@ -3239,6 +3256,16 @@ class MMGenTestSuite(object): def ethdev_txsign3(self,name): self.ethdev_txsign(name,ni=True,ext='2.345,50000].rawtx') def ethdev_txsend3(self,name): self.ethdev_txsend(name,ext='2.345,50000].sigtx') + def ethdev_tx_status(self,name,ext,expect_str): + tx_fn = get_file_with_ext(ext,cfg['tmpdir'],no_dot=True) + t = MMGenExpect(name,'mmgen-txsend', eth_args + ['--status',tx_fn]) + t.expect(expect_str) + t.read() + t.ok() + + def ethdev_tx_status1(self,name): + self.ethdev_tx_status(name,ext='2.345,50000].sigtx',expect_str='has 1 confirmation') + def ethdev_txcreate4(self,name): args = ['98831F3A:E:2,23.45495'] interactive_fee='40G' @@ -3321,12 +3348,12 @@ class MMGenTestSuite(object): token_data = { 'name':'MMGen Token 2', 'symbol':'MM2', 'supply':10**18, 'decimals':10 } self.ethdev_token_compile(name,token_data) - def ethdev_token_deploy(self,name,num,key,gas,mmgen_cmd='txdo'): + def ethdev_token_deploy(self,name,num,key,gas,mmgen_cmd='txdo',tx_fee='8G'): self.init_ethdev_common() key_fn = get_tmpfile_fn(cfg,cfg['parity_keyfile']) fn = os.path.join(cfg['tmpdir'],key+'.bin') os.environ['MMGEN_BOGUS_SEND'] = '' - args = ['-B','--tx-fee=8G','--tx-gas={}'.format(gas),'--contract-data='+fn,'--inputs='+eth_addr,'--yes'] + args = ['-B','--tx-fee='+tx_fee,'--tx-gas={}'.format(gas),'--contract-data='+fn,'--inputs='+eth_addr,'--yes'] if mmgen_cmd == 'txdo': args += ['-k',key_fn] t = MMGenExpect(name,'mmgen-'+mmgen_cmd, eth_args + args) if mmgen_cmd == 'txcreate': @@ -3339,7 +3366,7 @@ class MMGenTestSuite(object): os.environ['MMGEN_BOGUS_SEND'] = '1' txid = self.txsend_ui_common(t,mmgen_cmd,quiet=True,bogus_send=False,no_ok=True) - addr = t.expect_getend('Token address: ') + addr = t.expect_getend('Contract address: ') from mmgen.altcoins.eth.tx import EthereumMMGenTX as etx assert etx.get_exec_status(txid) != 0,"Contract '{}:{}' failed to execute. Aborting".format(num,key) if key == 'Token': @@ -3351,7 +3378,11 @@ class MMGenTestSuite(object): def ethdev_token_deploy1a(self,name): self.ethdev_token_deploy(name,num=1,key='SafeMath',gas=200000) def ethdev_token_deploy1b(self,name): self.ethdev_token_deploy(name,num=1,key='Owned',gas=250000) - def ethdev_token_deploy1c(self,name): self.ethdev_token_deploy(name,num=1,key='Token',gas=1100000) + def ethdev_token_deploy1c(self,name): self.ethdev_token_deploy(name,num=1,key='Token',gas=1100000,tx_fee='7G') + + def ethdev_tx_status2(self,name): + self.ethdev_tx_status(name,ext='ETH[0,7000].sigtx',expect_str='successfully executed') + def ethdev_token_deploy2a(self,name): self.ethdev_token_deploy(name,num=2,key='SafeMath',gas=200000) def ethdev_token_deploy2b(self,name): self.ethdev_token_deploy(name,num=2,key='Owned',gas=250000) def ethdev_token_deploy2c(self,name): self.ethdev_token_deploy(name,num=2,key='Token',gas=1100000) @@ -3388,8 +3419,8 @@ class MMGenTestSuite(object): tk_addr = read_from_tmpfile(cfg,'token_addr'+n).strip() self.ethdev_addrimport(name,ext='['+r+'].addrs',expect='3/3',add_args=['--token='+tk_addr]) - def ethdev_token_txcreate(self,name,args=[],token='',inputs='1'): - t = MMGenExpect(name,'mmgen-txcreate', eth_args + ['--token='+token,'-B','--tx-fee=50G'] + args) + def ethdev_token_txcreate(self,name,args=[],token='',inputs='1',fee='50G'): + t = MMGenExpect(name,'mmgen-txcreate', eth_args + ['--token='+token,'-B','--tx-fee='+fee] + args) self.txcreate_ui_common(t,name,menu=[], input_sels_prompt='to spend from', inputs=inputs,file_desc='Ethereum token transaction', @@ -3432,16 +3463,42 @@ class MMGenTestSuite(object): def ethdev_bal2_getbalance(self,name,t_non_mmgen='',t_mmgen=''): self.ethdev_bal_getbalance(name,t_non_mmgen='999999.12345689012345678',t_mmgen='127.0287876') - def ethdev_token_bal(self,name): + def ethdev_token_bal(self,name,expect_str): t = MMGenExpect(name,'mmgen-tool', eth_args + ['--token=mm1','twview','wide=1']) - t.expect(r'deadbeef.* '+eth_amt2,regex=True) + t.expect(expect_str,regex=True) t.read() t.ok() + def ethdev_token_bal1(self,name): + self.ethdev_token_bal(name,expect_str=r'deadbeef.* '+eth_amt2) + def ethdev_token_bal_getbalance(self,name): self.ethdev_bal_getbalance(name, t_non_mmgen='888.111122223333444455',t_mmgen='111.888877776666555545',extra_args=['--token=mm1']) + def ethdev_txcreate_noamt(self,name): + return self.ethdev_txcreate(name,args=['98831F3A:E:12']) + def ethdev_txsign_noamt(self,name): + self.ethdev_txsign(name,ext='99.99895,50000].rawtx') + def ethdev_txsend_noamt(self,name): + self.ethdev_txsend(name,ext='99.99895,50000].sigtx') + + def ethdev_token_bal2(self,name): + self.ethdev_token_bal(name,expect_str=r'98831F3A:E:12\s+1.23456\s+99.99895\s') + + def ethdev_bal3(self,name,expect_str=''): + self.ethdev_bal(name,expect_str=r'98831F3A:E:1\s+0\n') + + def ethdev_token_txcreate_noamt(self,name): + return self.ethdev_token_txcreate(name,args=['98831F3A:E:13'],token='mm1',inputs='2',fee='51G') + def ethdev_token_txsign_noamt(self,name): + self.ethdev_token_txsign(name,ext='1.23456,51000].rawtx',token='mm1') + def ethdev_token_txsend_noamt(self,name): + self.ethdev_token_txsend(name,ext='1.23456,51000].sigtx',token='mm1') + + def ethdev_token_bal3(self,name): + self.ethdev_token_bal(name,expect_str=r'98831F3A:E:13\s+1.23456\s') + def ethdev_stop(self,name): MMGenExpect(name,'',msg_only=True) pid = read_from_tmpfile(cfg,cfg['parity_pidfile'])