ts_ethdev.py 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779
  1. #!/usr/bin/env python3
  2. #
  3. # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
  4. # Copyright (C)2013-2019 The MMGen Project <mmgen@tuta.io>
  5. #
  6. # This program is free software: you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation, either version 3 of the License, or
  9. # (at your option) any later version.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License
  17. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. """
  19. ts_ethdev.py: Ethdev tests for the test.py test suite
  20. """
  21. import sys,os,subprocess,re,shutil
  22. from decimal import Decimal
  23. from mmgen.globalvars import g
  24. from mmgen.opts import opt
  25. from mmgen.util import die
  26. from mmgen.exception import *
  27. from test.common import *
  28. from test.test_py_d.common import *
  29. del_addrs = ('4','1')
  30. dfl_sid = '98831F3A'
  31. # The Parity dev address with lots of coins. Create with "ethkey -b info ''":
  32. dfl_addr = '00a329c0648769a73afac7f9381e08fb43dbea72'
  33. dfl_addr_chk = '00a329c0648769A73afAc7F9381E08FB43dBEA72'
  34. dfl_privkey = '4d5db4107d237df6a3d58ee5f70ae63d73d7658d4026f2eefd2f204c81682cb7'
  35. burn_addr = 'deadbeef'*5
  36. amt1 = '999999.12345689012345678'
  37. amt2 = '888.111122223333444455'
  38. parity_pid_fn = 'parity.pid'
  39. parity_key_fn = 'parity.devkey'
  40. # Token sends require varying amounts of gas, depending on compiler version
  41. try:
  42. solc_ver = re.search(r'Version:\s*(.*)',
  43. subprocess.Popen(['solc','--version'],stdout=subprocess.PIPE
  44. ).stdout.read().decode()).group(1)
  45. except:
  46. solc_ver = ''
  47. if re.match(r'\b0.5.1\b',solc_ver): # Raspbian Stretch
  48. vbal1 = '1.2288337'
  49. vbal2 = '99.997085083'
  50. vbal3 = '1.23142165'
  51. vbal4 = '127.0287837'
  52. elif re.match(r'\b0.5.3\b',solc_ver): # Ubuntu Bionic
  53. vbal1 = '1.2288487'
  54. vbal2 = '99.997092733'
  55. vbal3 = '1.23142915'
  56. vbal4 = '127.0287987'
  57. else:
  58. vbal1 = vbal2 = vbal3 = vbal4 = None
  59. bals = {
  60. '1': [ ('98831F3A:E:1','123.456')],
  61. '2': [ ('98831F3A:E:1','123.456'),('98831F3A:E:11','1.234')],
  62. '3': [ ('98831F3A:E:1','123.456'),('98831F3A:E:11','1.234'),('98831F3A:E:21','2.345')],
  63. '4': [ ('98831F3A:E:1','100'),
  64. ('98831F3A:E:2','23.45495'),
  65. ('98831F3A:E:11','1.234'),
  66. ('98831F3A:E:21','2.345')],
  67. '5': [ ('98831F3A:E:1','100'),
  68. ('98831F3A:E:2','23.45495'),
  69. ('98831F3A:E:11','1.234'),
  70. ('98831F3A:E:21','2.345'),
  71. (burn_addr + '\s+Non-MMGen',amt1)],
  72. '8': [ ('98831F3A:E:1','0'),
  73. ('98831F3A:E:2','23.45495'),
  74. ('98831F3A:E:11',vbal1,'a'),
  75. ('98831F3A:E:12','99.99895'),
  76. ('98831F3A:E:21','2.345'),
  77. (burn_addr + '\s+Non-MMGen',amt1)],
  78. '9': [ ('98831F3A:E:1','0'),
  79. ('98831F3A:E:2','23.45495'),
  80. ('98831F3A:E:11',vbal1,'a'),
  81. ('98831F3A:E:12',vbal2),
  82. ('98831F3A:E:21','2.345'),
  83. (burn_addr + '\s+Non-MMGen',amt1)]
  84. }
  85. token_bals = {
  86. '1': [ ('98831F3A:E:11','1000','1.234')],
  87. '2': [ ('98831F3A:E:11','998.76544',vbal3,'a'),
  88. ('98831F3A:E:12','1.23456','0')],
  89. '3': [ ('98831F3A:E:11','110.654317776666555545',vbal1,'a'),
  90. ('98831F3A:E:12','1.23456','0')],
  91. '4': [ ('98831F3A:E:11','110.654317776666555545',vbal1,'a'),
  92. ('98831F3A:E:12','1.23456','0'),
  93. (burn_addr + '\s+Non-MMGen',amt2,amt1)],
  94. '5': [ ('98831F3A:E:11','110.654317776666555545',vbal1,'a'),
  95. ('98831F3A:E:12','1.23456','99.99895'),
  96. (burn_addr + '\s+Non-MMGen',amt2,amt1)],
  97. '6': [ ('98831F3A:E:11','110.654317776666555545',vbal1,'a'),
  98. ('98831F3A:E:12','0',vbal2),
  99. ('98831F3A:E:13','1.23456','0'),
  100. (burn_addr + '\s+Non-MMGen',amt2,amt1)]
  101. }
  102. token_bals_getbalance = {
  103. '1': (vbal4,'999999.12345689012345678'),
  104. '2': ('111.888877776666555545','888.111122223333444455')
  105. }
  106. from test.test_py_d.ts_base import *
  107. from test.test_py_d.ts_shared import *
  108. class TestSuiteEthdev(TestSuiteBase,TestSuiteShared):
  109. 'Ethereum transacting, token deployment and tracking wallet operations'
  110. networks = ('eth','etc')
  111. passthru_opts = ('coin',)
  112. tmpdir_nums = [22]
  113. cmd_group = (
  114. ('setup', 'Ethereum Parity dev mode tests for coin {} (start parity)'.format(g.coin)),
  115. ('addrgen', 'generating addresses'),
  116. ('addrimport', 'importing addresses'),
  117. ('addrimport_dev_addr', "importing Parity dev address 'Ox00a329c..'"),
  118. ('txcreate1', 'creating a transaction (spend from dev address to address :1)'),
  119. ('txsign1', 'signing the transaction'),
  120. ('txsign1_ni', 'signing the transaction (non-interactive)'),
  121. ('txsend1', 'sending the transaction'),
  122. ('bal1', 'the {} balance'.format(g.coin)),
  123. ('txcreate2', 'creating a transaction (spend from dev address to address :11)'),
  124. ('txsign2', 'signing the transaction'),
  125. ('txsend2', 'sending the transaction'),
  126. ('bal2', 'the {} balance'.format(g.coin)),
  127. ('txcreate3', 'creating a transaction (spend from dev address to address :21)'),
  128. ('txsign3', 'signing the transaction'),
  129. ('txsend3', 'sending the transaction'),
  130. ('bal3', 'the {} balance'.format(g.coin)),
  131. ('tx_status1', 'getting the transaction status'),
  132. ('txcreate4', 'creating a transaction (spend from MMGen address, low TX fee)'),
  133. ('txbump', 'bumping the transaction fee'),
  134. ('txsign4', 'signing the transaction'),
  135. ('txsend4', 'sending the transaction'),
  136. ('bal4', 'the {} balance'.format(g.coin)),
  137. ('txcreate5', 'creating a transaction (fund burn address)'),
  138. ('txsign5', 'signing the transaction'),
  139. ('txsend5', 'sending the transaction'),
  140. ('addrimport_burn_addr',"importing burn address"),
  141. ('bal5', 'the {} balance'.format(g.coin)),
  142. ('add_label', 'adding a UTF-8 label'),
  143. ('chk_label', 'the label'),
  144. ('remove_label', 'removing the label'),
  145. ('token_compile1', 'compiling ERC20 token #1'),
  146. ('token_deploy1a', 'deploying ERC20 token #1 (SafeMath)'),
  147. ('token_deploy1b', 'deploying ERC20 token #1 (Owned)'),
  148. ('token_deploy1c', 'deploying ERC20 token #1 (Token)'),
  149. ('tx_status2', 'getting the transaction status'),
  150. ('bal6', 'the {} balance'.format(g.coin)),
  151. ('token_compile2', 'compiling ERC20 token #2'),
  152. ('token_deploy2a', 'deploying ERC20 token #2 (SafeMath)'),
  153. ('token_deploy2b', 'deploying ERC20 token #2 (Owned)'),
  154. ('token_deploy2c', 'deploying ERC20 token #2 (Token)'),
  155. ('contract_deploy', 'deploying contract (create,sign,send)'),
  156. ('token_fund_users', 'transferring token funds from dev to user'),
  157. ('token_user_bals', 'show balances after transfer'),
  158. ('token_addrgen', 'generating token addresses'),
  159. ('token_addrimport_badaddr1','importing token addresses (no token address)'),
  160. ('token_addrimport_badaddr2','importing token addresses (bad token address)'),
  161. ('token_addrimport', 'importing token addresses'),
  162. ('bal7', 'the {} balance'.format(g.coin)),
  163. ('token_bal1', 'the {} balance and token balance'.format(g.coin)),
  164. ('token_txcreate1', 'creating a token transaction'),
  165. ('token_txsign1', 'signing the transaction'),
  166. ('token_txsend1', 'sending the transaction'),
  167. ('token_bal2', 'the {} balance and token balance'.format(g.coin)),
  168. ('token_txcreate2', 'creating a token transaction (to burn address)'),
  169. ('token_txbump', 'bumping the transaction fee'),
  170. ('token_txsign2', 'signing the transaction'),
  171. ('token_txsend2', 'sending the transaction'),
  172. ('token_bal3', 'the {} balance and token balance'.format(g.coin)),
  173. ('del_dev_addr', "deleting the dev address"),
  174. ('bal1_getbalance', 'the {} balance (getbalance)'.format(g.coin)),
  175. ('addrimport_token_burn_addr',"importing the token burn address"),
  176. ('token_bal4', 'the {} balance and token balance'.format(g.coin)),
  177. ('token_bal_getbalance','the token balance (getbalance)'),
  178. ('txcreate_noamt', 'creating a transaction (full amount send)'),
  179. ('txsign_noamt', 'signing the transaction'),
  180. ('txsend_noamt', 'sending the transaction'),
  181. ('bal8', 'the {} balance'.format(g.coin)),
  182. ('token_bal5', 'the token balance'),
  183. ('token_txcreate_noamt', 'creating a token transaction (full amount send)'),
  184. ('token_txsign_noamt', 'signing the transaction'),
  185. ('token_txsend_noamt', 'sending the transaction'),
  186. ('bal9', 'the {} balance'.format(g.coin)),
  187. ('token_bal6', 'the token balance'),
  188. ('listaddresses1', 'listaddresses'),
  189. ('listaddresses2', 'listaddresses minconf=999999999 (ignored)'),
  190. ('listaddresses3', 'listaddresses sort=age (ignored)'),
  191. ('listaddresses4', 'listaddresses showempty=1 sort=age (ignored)'),
  192. ('token_listaddresses1','listaddresses --token=mm1'),
  193. ('token_listaddresses2','listaddresses --token=mm1 showempty=1'),
  194. ('twview1','twview'),
  195. ('twview2','twview wide=1'),
  196. ('twview3','twview wide=1 sort=age (ignored)'),
  197. ('twview4','twview wide=1 minconf=999999999 (ignored)'),
  198. ('twview5','twview wide=1 minconf=0 (ignored)'),
  199. ('twview6','twview age_fmt=days (ignored)'),
  200. ('token_twview1','twview --token=mm1'),
  201. ('token_twview2','twview --token=mm1 wide=1'),
  202. ('token_twview3','twview --token=mm1 wide=1 sort=age (ignored)'),
  203. ('edit_label1','adding label to addr #{} in {} tracking wallet'.format(del_addrs[0],g.coin)),
  204. ('edit_label2','adding label to addr #{} in {} tracking wallet'.format(del_addrs[1],g.coin)),
  205. ('edit_label3','removing label from addr #{} in {} tracking wallet'.format(del_addrs[0],g.coin)),
  206. ('remove_addr1','removing addr #{} from {} tracking wallet'.format(del_addrs[0],g.coin)),
  207. ('remove_addr2','removing addr #{} from {} tracking wallet'.format(del_addrs[1],g.coin)),
  208. ('remove_token_addr1','removing addr #{} from {} token tracking wallet'.format(del_addrs[0],g.coin)),
  209. ('remove_token_addr2','removing addr #{} from {} token tracking wallet'.format(del_addrs[1],g.coin)),
  210. ('stop', 'stopping parity'),
  211. )
  212. @property
  213. def eth_args(self):
  214. return ['--outdir={}'.format(self.tmpdir),'--coin='+g.coin,'--rpc-port=8549','--quiet']
  215. def setup(self):
  216. self.spawn('',msg_only=True)
  217. os.environ['MMGEN_BOGUS_WALLET_DATA'] = ''
  218. if subprocess.call(['which','parity'],stdout=subprocess.PIPE) == 0:
  219. lf_arg = '--log-file=' + joinpath(self.tr.data_dir,'parity.log')
  220. ss = 'parity.*--log-file=test/data_dir.*/parity.log' # allow for UTF8_DEBUG
  221. try:
  222. pid = subprocess.check_output(['pgrep','-af',ss]).split()[0]
  223. os.kill(int(pid),9)
  224. except: pass
  225. # '--base-path' doesn't work together with daemon mode, so we have to clobber the main dev chain
  226. dc_dir = joinpath(os.environ['HOME'],'.local/share/io.parity.ethereum/chains/DevelopmentChain')
  227. shutil.rmtree(dc_dir,ignore_errors=True)
  228. bdir = joinpath(self.tr.data_dir,'parity')
  229. try: os.mkdir(bdir)
  230. except: pass
  231. opts = ['--ports-shift=4','--config=dev']
  232. redir = None if opt.exact_output else subprocess.PIPE
  233. pidfile = joinpath(self.tmpdir,parity_pid_fn)
  234. subprocess.check_call(['parity',lf_arg] + opts + ['daemon',pidfile],stderr=redir,stdout=redir)
  235. time.sleep(3) # race condition
  236. pid = self.read_from_tmpfile(parity_pid_fn)
  237. elif subprocess.call('netstat -tnl | grep -q 127.0.0.1:8549',shell=True) == 0:
  238. m1 = 'No parity executable found on system, but port 8549 is active!'
  239. m2 = 'Before continuing, you should probably run the command'
  240. m3 = 'test/test.py -X setup ethdev'
  241. m4 = 'on the remote host.'
  242. sys.stderr.write('{}\n{}\n{} {}\n'.format(m1,m2,cyan(m3),m4))
  243. confirm_continue()
  244. else:
  245. die(1,'No parity executable found!')
  246. return 'ok'
  247. def addrgen(self,addrs='1-3,11-13,21-23'):
  248. from mmgen.addr import MMGenAddrType
  249. t = self.spawn('mmgen-addrgen', self.eth_args + [dfl_words_file,addrs])
  250. t.written_to_file('Addresses')
  251. t.read()
  252. return t
  253. def addrimport(self,ext='21-23]{}.addrs',expect='9/9',add_args=[],bad_input=False):
  254. ext = ext.format('-α' if g.debug_utf8 else '')
  255. fn = self.get_file_with_ext(ext,no_dot=True,delete=False)
  256. t = self.spawn('mmgen-addrimport', self.eth_args[1:] + add_args + [fn])
  257. if bad_input:
  258. t.read()
  259. t.req_exit_val = 2
  260. return t
  261. if g.debug: t.expect("Type uppercase 'YES' to confirm: ",'YES\n')
  262. t.expect('Importing')
  263. t.expect(expect)
  264. t.read()
  265. return t
  266. def addrimport_one_addr(self,addr=None,extra_args=[]):
  267. t = self.spawn('mmgen-addrimport', self.eth_args[1:] + extra_args + ['--address='+addr])
  268. t.expect('OK')
  269. return t
  270. def addrimport_dev_addr(self):
  271. return self.addrimport_one_addr(addr=dfl_addr)
  272. def addrimport_burn_addr(self):
  273. return self.addrimport_one_addr(addr=burn_addr)
  274. def txcreate(self,args=[],menu=[],acct='1',non_mmgen_inputs=0,
  275. interactive_fee = '50G',
  276. eth_fee_res = None,
  277. fee_res_fs = '0.00105 {} (50 gas price in Gwei)',
  278. fee_desc = 'gas price' ):
  279. fee_res = fee_res_fs.format(g.coin)
  280. t = self.spawn('mmgen-txcreate', self.eth_args + ['-B'] + args)
  281. t.expect(r'add \[l\]abel, .*?:.','p', regex=True)
  282. t.written_to_file('Account balances listing')
  283. return self.txcreate_ui_common( t, menu=menu,
  284. input_sels_prompt = 'to spend from',
  285. inputs = acct,
  286. file_desc = 'Ethereum transaction',
  287. bad_input_sels = True,
  288. non_mmgen_inputs = non_mmgen_inputs,
  289. interactive_fee = interactive_fee,
  290. fee_res = fee_res,
  291. fee_desc = fee_desc,
  292. eth_fee_res = eth_fee_res,
  293. add_comment = ref_tx_label_jp )
  294. def txsign(self,ni=False,ext='{}.rawtx',add_args=[]):
  295. ext = ext.format('-α' if g.debug_utf8 else '')
  296. keyfile = joinpath(self.tmpdir,parity_key_fn)
  297. write_to_file(keyfile,dfl_privkey+'\n')
  298. txfile = self.get_file_with_ext(ext,no_dot=True)
  299. t = self.spawn( 'mmgen-txsign',
  300. self.eth_args
  301. + add_args
  302. + ([],['--yes'])[ni]
  303. + ['-k', keyfile, txfile, dfl_words_file] )
  304. return self.txsign_ui_common(t,ni=ni,has_label=True)
  305. def txsend(self,ni=False,bogus_send=False,ext='{}.sigtx',add_args=[]):
  306. ext = ext.format('-α' if g.debug_utf8 else '')
  307. txfile = self.get_file_with_ext(ext,no_dot=True)
  308. if not bogus_send: os.environ['MMGEN_BOGUS_SEND'] = ''
  309. t = self.spawn('mmgen-txsend', self.eth_args + add_args + [txfile])
  310. if not bogus_send: os.environ['MMGEN_BOGUS_SEND'] = '1'
  311. txid = self.txsend_ui_common(t,quiet=True,bogus_send=bogus_send,has_label=True)
  312. return t
  313. def txcreate1(self):
  314. # valid_keypresses = EthereumTwUnspentOutputs.key_mappings.keys()
  315. menu = ['a','d','r','M','D','e','m','m'] # include one invalid keypress, 'D'
  316. args = ['98831F3A:E:1,123.456']
  317. return self.txcreate(args=args,menu=menu,acct='1',non_mmgen_inputs=1)
  318. def txsign1(self): return self.txsign()
  319. def txsign1_ni(self): return self.txsign(ni=True)
  320. def txsend1(self): return self.txsend()
  321. def bal1(self): return self.bal(n='1')
  322. def txcreate2(self):
  323. args = ['98831F3A:E:11,1.234']
  324. return self.txcreate(args=args,acct='10',non_mmgen_inputs=1)
  325. def txsign2(self): return self.txsign(ni=True,ext='1.234,50000]{}.rawtx')
  326. def txsend2(self): return self.txsend(ext='1.234,50000]{}.sigtx')
  327. def bal2(self): return self.bal(n='2')
  328. def txcreate3(self):
  329. args = ['98831F3A:E:21,2.345']
  330. return self.txcreate(args=args,acct='10',non_mmgen_inputs=1)
  331. def txsign3(self): return self.txsign(ni=True,ext='2.345,50000]{}.rawtx')
  332. def txsend3(self): return self.txsend(ext='2.345,50000]{}.sigtx')
  333. def bal3(self): return self.bal(n='3')
  334. def tx_status(self,ext,expect_str):
  335. ext = ext.format('-α' if g.debug_utf8 else '')
  336. txfile = self.get_file_with_ext(ext,no_dot=True)
  337. t = self.spawn('mmgen-txsend', self.eth_args + ['--status',txfile])
  338. t.expect(expect_str)
  339. t.read()
  340. return t
  341. def tx_status1(self):
  342. return self.tx_status(ext='2.345,50000]{}.sigtx',expect_str='has 1 confirmation')
  343. def txcreate4(self):
  344. args = ['98831F3A:E:2,23.45495']
  345. interactive_fee='40G'
  346. fee_res_fs='0.00084 {} (40 gas price in Gwei)'
  347. return self.txcreate( args = args,
  348. acct = '1',
  349. non_mmgen_inputs = 0,
  350. interactive_fee = interactive_fee,
  351. fee_res_fs = fee_res_fs,
  352. eth_fee_res = True)
  353. def txbump(self,ext=',40000]{}.rawtx',fee='50G',add_args=[]):
  354. ext = ext.format('-α' if g.debug_utf8 else '')
  355. txfile = self.get_file_with_ext(ext,no_dot=True)
  356. t = self.spawn('mmgen-txbump', self.eth_args + add_args + ['--yes',txfile])
  357. t.expect('or gas price: ',fee+'\n')
  358. t.read()
  359. return t
  360. def txsign4(self): return self.txsign(ni=True,ext='.45495,50000]{}.rawtx')
  361. def txsend4(self): return self.txsend(ext='.45495,50000]{}.sigtx')
  362. def bal4(self): return self.bal(n='4')
  363. def txcreate5(self):
  364. args = [burn_addr + ','+amt1]
  365. return self.txcreate(args=args,acct='10',non_mmgen_inputs=1)
  366. def txsign5(self): return self.txsign(ni=True,ext=amt1+',50000]{}.rawtx')
  367. def txsend5(self): return self.txsend(ext=amt1+',50000]{}.sigtx')
  368. def bal5(self): return self.bal(n='5')
  369. bal_corr = Decimal('0.0000032') # gas use for token sends varies between ETH and ETC!
  370. def bal(self,n=None):
  371. t = self.spawn('mmgen-tool', self.eth_args + ['twview','wide=1'])
  372. for b in bals[n]:
  373. addr,amt,adj = b if len(b) == 3 else b + (False,)
  374. if adj and g.coin == 'ETC': amt = str(Decimal(amt) + self.bal_corr)
  375. pat = r'{}\s+{}\s'.format(addr,amt.replace('.',r'\.'))
  376. t.expect(pat,regex=True)
  377. t.read()
  378. return t
  379. def token_bal(self,n=None):
  380. t = self.spawn('mmgen-tool', self.eth_args + ['--token=mm1','twview','wide=1'])
  381. for b in token_bals[n]:
  382. addr,_amt1,_amt2,adj = b if len(b) == 4 else b + (False,)
  383. if adj and g.coin == 'ETC': _amt2 = str(Decimal(_amt2) + self.bal_corr)
  384. pat = r'{}\s+{}\s+{}\s'.format(addr,_amt1.replace('.',r'\.'),_amt2.replace('.',r'\.'))
  385. t.expect(pat,regex=True)
  386. t.read()
  387. return t
  388. def bal_getbalance(self,idx,etc_adj=False,extra_args=[]):
  389. bal1 = token_bals_getbalance[idx][0]
  390. bal2 = token_bals_getbalance[idx][1]
  391. bal1 = Decimal(bal1)
  392. if etc_adj and g.coin == 'ETC': bal1 += self.bal_corr
  393. t = self.spawn('mmgen-tool', self.eth_args + extra_args + ['getbalance'])
  394. t.expect(r'\n[0-9A-F]{8}: .* '+str(bal1),regex=True)
  395. t.expect(r'\nNon-MMGen: .* '+bal2,regex=True)
  396. total = t.expect_getend(r'\nTOTAL:\s+',regex=True).split()[0]
  397. t.read()
  398. assert Decimal(bal1) + Decimal(bal2) == Decimal(total)
  399. return t
  400. def add_label(self,addr='98831F3A:E:3',lbl=utf8_label):
  401. t = self.spawn('mmgen-tool', self.eth_args + ['add_label',addr,lbl])
  402. t.expect('Added label.*in tracking wallet',regex=True)
  403. return t
  404. def chk_label(self,addr='98831F3A:E:3',label_pat=utf8_label_pat):
  405. t = self.spawn('mmgen-tool', self.eth_args + ['listaddresses','all_labels=1'])
  406. t.expect(r'{}\s+\S{{30}}\S+\s+{}\s+'.format(addr,(label_pat or label)),regex=True)
  407. return t
  408. def remove_label(self,addr='98831F3A:E:3'):
  409. t = self.spawn('mmgen-tool', self.eth_args + ['remove_label',addr])
  410. t.expect('Removed label.*in tracking wallet',regex=True)
  411. return t
  412. def token_compile(self,token_data={}):
  413. self.spawn('',msg_only=True)
  414. cmd_args = ['--{}={}'.format(k,v) for k,v in list(token_data.items())]
  415. imsg("Compiling solidity token contract '{}' with 'solc'".format(token_data['symbol']))
  416. cmd = ['scripts/create-token.py','--coin='+g.coin,'--outdir='+self.tmpdir] + cmd_args + [dfl_addr_chk]
  417. imsg("Executing: {}".format(' '.join(cmd)))
  418. subprocess.check_output(cmd,stderr=subprocess.STDOUT)
  419. imsg("ERC20 token '{}' compiled".format(token_data['symbol']))
  420. return 'ok'
  421. def token_compile1(self):
  422. token_data = { 'name':'MMGen Token 1', 'symbol':'MM1', 'supply':10**26, 'decimals':18 }
  423. return self.token_compile(token_data)
  424. def token_compile2(self):
  425. token_data = { 'name':'MMGen Token 2', 'symbol':'MM2', 'supply':10**18, 'decimals':10 }
  426. return self.token_compile(token_data)
  427. def _rpc_init(self):
  428. g.proto.rpc_port = 8549
  429. rpc_init()
  430. def token_deploy(self,num,key,gas,mmgen_cmd='txdo',tx_fee='8G'):
  431. self._rpc_init()
  432. keyfile = joinpath(self.tmpdir,parity_key_fn)
  433. fn = joinpath(self.tmpdir,key+'.bin')
  434. os.environ['MMGEN_BOGUS_SEND'] = ''
  435. args = ['-B',
  436. '--tx-fee='+tx_fee,
  437. '--tx-gas={}'.format(gas),
  438. '--contract-data='+fn,
  439. '--inputs='+dfl_addr,
  440. '--yes' ]
  441. if mmgen_cmd == 'txdo': args += ['-k',keyfile]
  442. t = self.spawn( 'mmgen-'+mmgen_cmd, self.eth_args + args)
  443. if mmgen_cmd == 'txcreate':
  444. t.written_to_file('Ethereum transaction')
  445. ext = '[0,8000]{}.rawtx'.format('-α' if g.debug_utf8 else '')
  446. txfile = self.get_file_with_ext(ext,no_dot=True)
  447. t = self.spawn('mmgen-txsign', self.eth_args + ['--yes','-k',keyfile,txfile],no_msg=True)
  448. self.txsign_ui_common(t,ni=True)
  449. txfile = txfile.replace('.rawtx','.sigtx')
  450. t = self.spawn('mmgen-txsend', self.eth_args + [txfile],no_msg=True)
  451. os.environ['MMGEN_BOGUS_SEND'] = '1'
  452. txid = self.txsend_ui_common(t,caller=mmgen_cmd,quiet=True,bogus_send=False)
  453. addr = t.expect_getend('Contract address: ')
  454. from mmgen.altcoins.eth.tx import EthereumMMGenTX as etx
  455. assert etx.get_exec_status(txid,True) != 0,(
  456. "Contract '{}:{}' failed to execute. Aborting".format(num,key))
  457. if key == 'Token':
  458. self.write_to_tmpfile('token_addr{}'.format(num),addr+'\n')
  459. imsg('\nToken #{} ({}) deployed!'.format(num,addr))
  460. return t
  461. def token_deploy1a(self): return self.token_deploy(num=1,key='SafeMath',gas=200000)
  462. def token_deploy1b(self): return self.token_deploy(num=1,key='Owned',gas=250000)
  463. def token_deploy1c(self): return self.token_deploy(num=1,key='Token',gas=1100000,tx_fee='7G')
  464. def tx_status2(self):
  465. return self.tx_status(ext=g.coin+'[0,7000]{}.sigtx',expect_str='successfully executed')
  466. def bal6(self): return self.bal5()
  467. def token_deploy2a(self): return self.token_deploy(num=2,key='SafeMath',gas=200000)
  468. def token_deploy2b(self): return self.token_deploy(num=2,key='Owned',gas=250000)
  469. def token_deploy2c(self): return self.token_deploy(num=2,key='Token',gas=1100000)
  470. def contract_deploy(self): # test create,sign,send
  471. return self.token_deploy(num=2,key='SafeMath',gas=1100000,mmgen_cmd='txcreate')
  472. def token_transfer_ops(self,op,amt=1000):
  473. self.spawn('',msg_only=True)
  474. sid = dfl_sid
  475. from mmgen.tool import MMGenToolCmd
  476. usr_mmaddrs = ['{}:E:{}'.format(sid,i) for i in (11,21)]
  477. usr_addrs = [MMGenToolCmd().gen_addr(addr,dfl_words_file) for addr in usr_mmaddrs]
  478. self._rpc_init()
  479. from mmgen.altcoins.eth.contract import Token
  480. from mmgen.altcoins.eth.tx import EthereumMMGenTX as etx
  481. def do_transfer():
  482. for i in range(2):
  483. tk = Token(self.read_from_tmpfile('token_addr{}'.format(i+1)).strip())
  484. imsg_r('\n'+tk.info())
  485. imsg('dev token balance (pre-send): {}'.format(tk.balance(dfl_addr)))
  486. imsg('Sending {} {} to address {} ({})'.format(amt,g.coin,usr_addrs[i],usr_mmaddrs[i]))
  487. from mmgen.obj import ETHAmt
  488. txid = tk.transfer( dfl_addr, usr_addrs[i], amt, dfl_privkey,
  489. start_gas = ETHAmt(60000,'wei'),
  490. gasPrice = ETHAmt(8,'Gwei') )
  491. assert etx.get_exec_status(txid,True) != 0,'Transfer of token funds failed. Aborting'
  492. def show_bals():
  493. for i in range(2):
  494. tk = Token(self.read_from_tmpfile('token_addr{}'.format(i+1)).strip())
  495. imsg('Token: {}'.format(tk.symbol()))
  496. imsg('dev token balance: {}'.format(tk.balance(dfl_addr)))
  497. imsg('usr token balance: {} ({} {})'.format(
  498. tk.balance(usr_addrs[i]),usr_mmaddrs[i],usr_addrs[i]))
  499. silence()
  500. if op == 'show_bals': show_bals()
  501. elif op == 'do_transfer': do_transfer()
  502. end_silence()
  503. return 'ok'
  504. def token_fund_users(self):
  505. return self.token_transfer_ops(op='do_transfer')
  506. def token_user_bals(self):
  507. return self.token_transfer_ops(op='show_bals')
  508. def token_addrgen(self):
  509. self.addrgen(addrs='11-13')
  510. ok_msg()
  511. return self.addrgen(addrs='21-23')
  512. def token_addrimport_badaddr1(self):
  513. t = self.addrimport(ext='[11-13]{}.addrs',add_args=['--token=abc'],bad_input=True)
  514. t.req_exit_val = 2
  515. return t
  516. def token_addrimport_badaddr2(self):
  517. t = self.addrimport(ext='[11-13]{}.addrs',add_args=['--token='+'00deadbeef'*4],bad_input=True)
  518. t.req_exit_val = 2
  519. return t
  520. def token_addrimport(self):
  521. for n,r in ('1','11-13'),('2','21-23'):
  522. tk_addr = self.read_from_tmpfile('token_addr'+n).strip()
  523. t = self.addrimport(ext='['+r+']{}.addrs',expect='3/3',add_args=['--token='+tk_addr])
  524. t.p.wait()
  525. ok_msg()
  526. t.skip_ok = True
  527. return t
  528. def bal7(self): return self.bal5()
  529. def token_bal1(self): return self.token_bal(n='1')
  530. def token_txcreate(self,args=[],token='',inputs='1',fee='50G'):
  531. t = self.spawn('mmgen-txcreate', self.eth_args + ['--token='+token,'-B','--tx-fee='+fee] + args)
  532. return self.txcreate_ui_common( t,
  533. menu = [],
  534. inputs = inputs,
  535. input_sels_prompt = 'to spend from',
  536. file_desc = 'Ethereum token transaction',
  537. add_comment = ref_tx_label_lat_cyr_gr)
  538. def token_txsign(self,ext='',token=''):
  539. return self.txsign(ni=True,ext=ext,add_args=['--token='+token])
  540. def token_txsend(self,ext='',token=''):
  541. return self.txsend(ext=ext,add_args=['--token=mm1'])
  542. def token_txcreate1(self):
  543. return self.token_txcreate(args=['98831F3A:E:12,1.23456'],token='mm1')
  544. def token_txsign1(self):
  545. return self.token_txsign(ext='1.23456,50000]{}.rawtx',token='mm1')
  546. def token_txsend1(self):
  547. return self.token_txsend(ext='1.23456,50000]{}.sigtx',token='mm1')
  548. def token_bal2(self):
  549. return self.token_bal(n='2')
  550. def twview(self,args=[],expect_str='',tool_args=[],exit_val=0):
  551. t = self.spawn('mmgen-tool', self.eth_args + args + ['twview'] + tool_args)
  552. if expect_str:
  553. t.expect(expect_str,regex=True)
  554. t.read()
  555. t.req_exit_val = exit_val
  556. return t
  557. def token_txcreate2(self):
  558. return self.token_txcreate(args=[burn_addr+','+amt2],token='mm1')
  559. def token_txbump(self):
  560. return self.txbump(ext=amt2+',50000]{}.rawtx',fee='56G',add_args=['--token=mm1'])
  561. def token_txsign2(self):
  562. return self.token_txsign(ext=amt2+',50000]{}.rawtx',token='mm1')
  563. def token_txsend2(self):
  564. return self.token_txsend(ext=amt2+',50000]{}.sigtx',token='mm1')
  565. def token_bal3(self):
  566. return self.token_bal(n='3')
  567. def del_dev_addr(self):
  568. t = self.spawn('mmgen-tool', self.eth_args + ['remove_address',dfl_addr])
  569. t.read() # TODO
  570. return t
  571. def bal1_getbalance(self):
  572. return self.bal_getbalance('1',etc_adj=True)
  573. def addrimport_token_burn_addr(self):
  574. return self.addrimport_one_addr(addr=burn_addr,extra_args=['--token=mm1'])
  575. def token_bal4(self):
  576. return self.token_bal(n='4')
  577. def token_bal_getbalance(self):
  578. return self.bal_getbalance('2',extra_args=['--token=mm1'])
  579. def txcreate_noamt(self):
  580. return self.txcreate(args=['98831F3A:E:12'],eth_fee_res=True)
  581. def txsign_noamt(self):
  582. return self.txsign(ext='99.99895,50000]{}.rawtx')
  583. def txsend_noamt(self):
  584. return self.txsend(ext='99.99895,50000]{}.sigtx')
  585. def bal8(self): return self.bal(n='8')
  586. def token_bal5(self): return self.token_bal(n='5')
  587. def token_txcreate_noamt(self):
  588. return self.token_txcreate(args=['98831F3A:E:13'],token='mm1',inputs='2',fee='51G')
  589. def token_txsign_noamt(self):
  590. return self.token_txsign(ext='1.23456,51000]{}.rawtx',token='mm1')
  591. def token_txsend_noamt(self):
  592. return self.token_txsend(ext='1.23456,51000]{}.sigtx',token='mm1')
  593. def bal9(self): return self.bal(n='9')
  594. def token_bal6(self): return self.token_bal(n='6')
  595. def listaddresses(self,args=[],tool_args=['all_labels=1'],exit_val=0):
  596. t = self.spawn('mmgen-tool', self.eth_args + args + ['listaddresses'] + tool_args)
  597. t.read()
  598. t.req_exit_val = exit_val
  599. return t
  600. def listaddresses1(self):
  601. return self.listaddresses()
  602. def listaddresses2(self):
  603. return self.listaddresses(tool_args=['minconf=999999999'])
  604. def listaddresses3(self):
  605. return self.listaddresses(tool_args=['sort=age'])
  606. def listaddresses4(self):
  607. return self.listaddresses(tool_args=['sort=age','showempty=1'])
  608. def token_listaddresses1(self):
  609. return self.listaddresses(args=['--token=mm1'])
  610. def token_listaddresses2(self):
  611. return self.listaddresses(args=['--token=mm1'],tool_args=['showempty=1'])
  612. def twview1(self):
  613. return self.twview()
  614. def twview2(self):
  615. return self.twview(tool_args=['wide=1'])
  616. def twview3(self):
  617. return self.twview(tool_args=['wide=1','sort=age'])
  618. def twview4(self):
  619. return self.twview(tool_args=['wide=1','minconf=999999999'])
  620. def twview5(self):
  621. return self.twview(tool_args=['wide=1','minconf=0'])
  622. def twview6(self):
  623. return self.twview(tool_args=['age_fmt=days'])
  624. def token_twview1(self):
  625. return self.twview(args=['--token=mm1'])
  626. def token_twview2(self):
  627. return self.twview(args=['--token=mm1'],tool_args=['wide=1'])
  628. def token_twview3(self):
  629. return self.twview(args=['--token=mm1'],tool_args=['wide=1','sort=age'])
  630. def edit_label(self,out_num,args=[],action='l',label_text=None):
  631. t = self.spawn('mmgen-txcreate', self.eth_args + args + ['-B','-i'])
  632. p1,p2 = ('emove address:\b','return to main menu): ')
  633. p3,r3 = (p2,label_text+'\n') if label_text is not None else ('(y/N): ','y')
  634. p4,r4 = (('(y/N): ',),('y',)) if label_text == '' else ((),())
  635. for p,r in zip((p1,p1,p2,p3)+p4+(p1,p1),('M',action,out_num+'\n',r3)+r4+('M','q')):
  636. t.expect(p,r)
  637. return t
  638. def edit_label1(self):
  639. return self.edit_label(out_num=del_addrs[0],label_text='First added label-α')
  640. def edit_label2(self):
  641. return self.edit_label(out_num=del_addrs[1],label_text='Second added label')
  642. def edit_label3(self):
  643. return self.edit_label(out_num=del_addrs[0],label_text='')
  644. def remove_addr1(self):
  645. return self.edit_label(out_num=del_addrs[0],action='R')
  646. def remove_addr2(self):
  647. return self.edit_label(out_num=del_addrs[1],action='R')
  648. def remove_token_addr1(self):
  649. return self.edit_label(out_num=del_addrs[0],args=['--token=mm1'],action='R')
  650. def remove_token_addr2(self):
  651. return self.edit_label(out_num=del_addrs[1],args=['--token=mm1'],action='R')
  652. def stop(self):
  653. self.spawn('',msg_only=True)
  654. if subprocess.call(['which','parity'],stdout=subprocess.PIPE) == 0:
  655. pid = self.read_from_tmpfile(parity_pid_fn)
  656. if opt.no_daemon_stop:
  657. msg_r('(leaving daemon running by user request)')
  658. else:
  659. subprocess.check_call(['kill',pid])
  660. else:
  661. imsg('No parity executable found on system. Ignoring')
  662. return 'ok'