ts_ethdev.py 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940
  1. #!/usr/bin/env python3
  2. #
  3. # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
  4. # Copyright (C)2013-2020 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,re,shutil
  22. from decimal import Decimal
  23. from subprocess import run,PIPE,DEVNULL
  24. from mmgen.globalvars import g
  25. from mmgen.opts import opt
  26. from mmgen.util import die
  27. from mmgen.exception import *
  28. from ..include.common import *
  29. from .common import *
  30. del_addrs = ('4','1')
  31. dfl_sid = '98831F3A'
  32. # The Parity dev address with lots of coins. Create with "ethkey -b info ''":
  33. dfl_addr = '00a329c0648769a73afac7f9381e08fb43dbea72'
  34. dfl_addr_chk = '00a329c0648769A73afAc7F9381E08FB43dBEA72'
  35. dfl_privkey = '4d5db4107d237df6a3d58ee5f70ae63d73d7658d4026f2eefd2f204c81682cb7'
  36. burn_addr = 'deadbeef'*5
  37. amt1 = '999999.12345689012345678'
  38. amt2 = '888.111122223333444455'
  39. parity_pid_fn = 'parity.pid'
  40. parity_key_fn = 'parity.devkey'
  41. # Token sends require varying amounts of gas, depending on compiler version
  42. def get_solc_ver():
  43. try: cp = run(['solc','--version'],stdout=PIPE)
  44. except: return None
  45. if cp.returncode:
  46. return None
  47. line = cp.stdout.decode().splitlines()[1]
  48. m = re.search(r'Version:\s*(\d+)\.(\d+)\.(\d+)',line)
  49. return '.'.join(m.groups()) if m else None
  50. solc_ver = get_solc_ver()
  51. if solc_ver == '0.5.1':
  52. vbal1 = '1.2288337'
  53. vbal1a = 'TODO'
  54. vbal2 = '99.997085083'
  55. vbal3 = '1.23142165'
  56. vbal4 = '127.0287837'
  57. else: # 0.5.3 or precompiled 0.5.3
  58. vbal1 = '1.2288487'
  59. vbal1a = '1.22627465'
  60. vbal2 = '99.997092733'
  61. vbal3 = '1.23142915'
  62. vbal4 = '127.0287987'
  63. bals = {
  64. '1': [ ('98831F3A:E:1','123.456')],
  65. '2': [ ('98831F3A:E:1','123.456'),('98831F3A:E:11','1.234')],
  66. '3': [ ('98831F3A:E:1','123.456'),('98831F3A:E:11','1.234'),('98831F3A:E:21','2.345')],
  67. '4': [ ('98831F3A:E:1','100'),
  68. ('98831F3A:E:2','23.45495'),
  69. ('98831F3A:E:11','1.234'),
  70. ('98831F3A:E:21','2.345')],
  71. '5': [ ('98831F3A:E:1','100'),
  72. ('98831F3A:E:2','23.45495'),
  73. ('98831F3A:E:11','1.234'),
  74. ('98831F3A:E:21','2.345'),
  75. (burn_addr + '\s+Non-MMGen',amt1)],
  76. '8': [ ('98831F3A:E:1','0'),
  77. ('98831F3A:E:2','23.45495'),
  78. ('98831F3A:E:11',vbal1,'a1'),
  79. ('98831F3A:E:12','99.99895'),
  80. ('98831F3A:E:21','2.345'),
  81. (burn_addr + '\s+Non-MMGen',amt1)],
  82. '9': [ ('98831F3A:E:1','0'),
  83. ('98831F3A:E:2','23.45495'),
  84. ('98831F3A:E:11',vbal1,'a1'),
  85. ('98831F3A:E:12',vbal2),
  86. ('98831F3A:E:21','2.345'),
  87. (burn_addr + '\s+Non-MMGen',amt1)],
  88. '10': [ ('98831F3A:E:1','0'),
  89. ('98831F3A:E:2','23.0218'),
  90. ('98831F3A:E:3','0.4321'),
  91. ('98831F3A:E:11',vbal1,'a1'),
  92. ('98831F3A:E:12',vbal2),
  93. ('98831F3A:E:21','2.345'),
  94. (burn_addr + '\s+Non-MMGen',amt1)]
  95. }
  96. token_bals = {
  97. '1': [ ('98831F3A:E:11','1000','1.234')],
  98. '2': [ ('98831F3A:E:11','998.76544',vbal3,'a1'),
  99. ('98831F3A:E:12','1.23456','0')],
  100. '3': [ ('98831F3A:E:11','110.654317776666555545',vbal1,'a1'),
  101. ('98831F3A:E:12','1.23456','0')],
  102. '4': [ ('98831F3A:E:11','110.654317776666555545',vbal1,'a1'),
  103. ('98831F3A:E:12','1.23456','0'),
  104. (burn_addr + '\s+Non-MMGen',amt2,amt1)],
  105. '5': [ ('98831F3A:E:11','110.654317776666555545',vbal1,'a1'),
  106. ('98831F3A:E:12','1.23456','99.99895'),
  107. (burn_addr + '\s+Non-MMGen',amt2,amt1)],
  108. '6': [ ('98831F3A:E:11','110.654317776666555545',vbal1,'a1'),
  109. ('98831F3A:E:12','0',vbal2),
  110. ('98831F3A:E:13','1.23456','0'),
  111. (burn_addr + '\s+Non-MMGen',amt2,amt1)],
  112. '7': [ ('98831F3A:E:11','67.444317776666555545',vbal1a,'a2'),
  113. ('98831F3A:E:12','43.21',vbal2),
  114. ('98831F3A:E:13','1.23456','0'),
  115. (burn_addr + '\s+Non-MMGen',amt2,amt1)]
  116. }
  117. token_bals_getbalance = {
  118. '1': (vbal4,'999999.12345689012345678'),
  119. '2': ('111.888877776666555545','888.111122223333444455')
  120. }
  121. from .ts_base import *
  122. from .ts_shared import *
  123. coin = g.coin
  124. class TestSuiteEthdev(TestSuiteBase,TestSuiteShared):
  125. 'Ethereum transacting, token deployment and tracking wallet operations'
  126. networks = ('eth','etc')
  127. passthru_opts = ('coin',)
  128. tmpdir_nums = [22]
  129. solc_vers = ('0.5.1','0.5.3') # 0.5.1: Raspbian Stretch, 0.5.3: Ubuntu Bionic
  130. cmd_group = (
  131. ('setup', 'Ethereum Parity dev mode tests for coin {} (start parity)'.format(coin)),
  132. ('wallet_upgrade1', 'upgrading the tracking wallet (v1 -> v2)'),
  133. ('wallet_upgrade2', 'upgrading the tracking wallet (v2 -> v3)'),
  134. ('addrgen', 'generating addresses'),
  135. ('addrimport', 'importing addresses'),
  136. ('addrimport_dev_addr', "importing Parity dev address 'Ox00a329c..'"),
  137. ('txcreate1', 'creating a transaction (spend from dev address to address :1)'),
  138. ('txsign1', 'signing the transaction'),
  139. ('tx_status0_bad', 'getting the transaction status'),
  140. ('txsign1_ni', 'signing the transaction (non-interactive)'),
  141. ('txsend1', 'sending the transaction'),
  142. ('bal1', 'the {} balance'.format(coin)),
  143. ('txcreate2', 'creating a transaction (spend from dev address to address :11)'),
  144. ('txsign2', 'signing the transaction'),
  145. ('txsend2', 'sending the transaction'),
  146. ('bal2', 'the {} balance'.format(coin)),
  147. ('txcreate3', 'creating a transaction (spend from dev address to address :21)'),
  148. ('txsign3', 'signing the transaction'),
  149. ('txsend3', 'sending the transaction'),
  150. ('bal3', 'the {} balance'.format(coin)),
  151. ('tx_status1', 'getting the transaction status'),
  152. ('txcreate4', 'creating a transaction (spend from MMGen address, low TX fee)'),
  153. ('txbump', 'bumping the transaction fee'),
  154. ('txsign4', 'signing the transaction'),
  155. ('txsend4', 'sending the transaction'),
  156. ('tx_status1a', 'getting the transaction status'),
  157. ('bal4', 'the {} balance'.format(coin)),
  158. ('txcreate5', 'creating a transaction (fund burn address)'),
  159. ('txsign5', 'signing the transaction'),
  160. ('txsend5', 'sending the transaction'),
  161. ('addrimport_burn_addr',"importing burn address"),
  162. ('bal5', 'the {} balance'.format(coin)),
  163. ('add_label1', 'adding a UTF-8 label (zh)'),
  164. ('chk_label1', 'the label'),
  165. ('add_label2', 'adding a UTF-8 label (lat+cyr+gr)'),
  166. ('chk_label2', 'the label'),
  167. ('remove_label', 'removing the label'),
  168. ('token_compile1', 'compiling ERC20 token #1'),
  169. ('token_deploy1a', 'deploying ERC20 token #1 (SafeMath)'),
  170. ('token_deploy1b', 'deploying ERC20 token #1 (Owned)'),
  171. ('token_deploy1c', 'deploying ERC20 token #1 (Token)'),
  172. ('tx_status2', 'getting the transaction status'),
  173. ('bal6', 'the {} balance'.format(coin)),
  174. ('token_compile2', 'compiling ERC20 token #2'),
  175. ('token_deploy2a', 'deploying ERC20 token #2 (SafeMath)'),
  176. ('token_deploy2b', 'deploying ERC20 token #2 (Owned)'),
  177. ('token_deploy2c', 'deploying ERC20 token #2 (Token)'),
  178. ('contract_deploy', 'deploying contract (create,sign,send)'),
  179. ('token_fund_users', 'transferring token funds from dev to user'),
  180. ('token_user_bals', 'show balances after transfer'),
  181. ('token_addrgen', 'generating token addresses'),
  182. ('token_addrimport_badaddr1','importing token addresses (no token address)'),
  183. ('token_addrimport_badaddr2','importing token addresses (bad token address)'),
  184. ('token_addrimport', 'importing token addresses'),
  185. ('token_addrimport_batch','importing token addresses (dummy batch mode)'),
  186. ('bal7', 'the {} balance'.format(coin)),
  187. ('token_bal1', 'the {} balance and token balance'.format(coin)),
  188. ('token_txcreate1', 'creating a token transaction'),
  189. ('token_txsign1', 'signing the transaction'),
  190. ('token_txsend1', 'sending the transaction'),
  191. ('tx_status3', 'getting the transaction status'),
  192. ('token_bal2', 'the {} balance and token balance'.format(coin)),
  193. ('token_txcreate2', 'creating a token transaction (to burn address)'),
  194. ('token_txbump', 'bumping the transaction fee'),
  195. ('token_txsign2', 'signing the transaction'),
  196. ('token_txsend2', 'sending the transaction'),
  197. ('token_bal3', 'the {} balance and token balance'.format(coin)),
  198. ('del_dev_addr', "deleting the dev address"),
  199. ('bal1_getbalance', 'the {} balance (getbalance)'.format(coin)),
  200. ('addrimport_token_burn_addr',"importing the token burn address"),
  201. ('token_bal4', 'the {} balance and token balance'.format(coin)),
  202. ('token_bal_getbalance','the token balance (getbalance)'),
  203. ('txcreate_noamt', 'creating a transaction (full amount send)'),
  204. ('txsign_noamt', 'signing the transaction'),
  205. ('txsend_noamt', 'sending the transaction'),
  206. ('bal8', 'the {} balance'.format(coin)),
  207. ('token_bal5', 'the token balance'),
  208. ('token_txcreate_noamt', 'creating a token transaction (full amount send)'),
  209. ('token_txsign_noamt', 'signing the transaction'),
  210. ('token_txsend_noamt', 'sending the transaction'),
  211. ('bal9', 'the {} balance'.format(coin)),
  212. ('token_bal6', 'the token balance'),
  213. ('listaddresses1', 'listaddresses'),
  214. ('listaddresses2', 'listaddresses minconf=999999999 (ignored)'),
  215. ('listaddresses3', 'listaddresses sort=age (ignored)'),
  216. ('listaddresses4', 'listaddresses showempty=1 sort=age (ignored)'),
  217. ('token_listaddresses1','listaddresses --token=mm1'),
  218. ('token_listaddresses2','listaddresses --token=mm1 showempty=1'),
  219. ('twview_cached_balances','twview (cached balances)'),
  220. ('token_twview_cached_balances','token twview (cached balances)'),
  221. ('txcreate_cached_balances','txcreate (cached balances)'),
  222. ('token_txcreate_cached_balances','token txcreate (cached balances)'),
  223. ('txdo_cached_balances', 'txdo (cached balances)'),
  224. ('txcreate_refresh_balances','refreshing balances'),
  225. ('bal10', 'the {} balance'.format(coin)),
  226. ('token_txdo_cached_balances', 'token txdo (cached balances)'),
  227. ('token_txcreate_refresh_balances','refreshing token balances'),
  228. ('token_bal7', 'the token balance'),
  229. ('twview1','twview'),
  230. ('twview2','twview wide=1'),
  231. ('twview3','twview wide=1 sort=age (ignored)'),
  232. ('twview4','twview wide=1 minconf=999999999 (ignored)'),
  233. ('twview5','twview wide=1 minconf=0 (ignored)'),
  234. ('token_twview1','twview --token=mm1'),
  235. ('token_twview2','twview --token=mm1 wide=1'),
  236. ('token_twview3','twview --token=mm1 wide=1 sort=age (ignored)'),
  237. ('edit_label1','adding label to addr #{} in {} tracking wallet (zh)'.format(del_addrs[0],coin)),
  238. ('edit_label2','adding label to addr #{} in {} tracking wallet (lat+cyr+gr)'.format(del_addrs[1],coin)),
  239. ('edit_label3','removing label from addr #{} in {} tracking wallet'.format(del_addrs[0],coin)),
  240. ('token_edit_label1','adding label to addr #{} in {} token tracking wallet'.format(del_addrs[0],coin)),
  241. ('remove_addr1','removing addr #{} from {} tracking wallet'.format(del_addrs[0],coin)),
  242. ('remove_addr2','removing addr #{} from {} tracking wallet'.format(del_addrs[1],coin)),
  243. ('token_remove_addr1','removing addr #{} from {} token tracking wallet'.format(del_addrs[0],coin)),
  244. ('token_remove_addr2','removing addr #{} from {} token tracking wallet'.format(del_addrs[1],coin)),
  245. ('stop', 'stopping parity'),
  246. )
  247. def __init__(self,trunner,cfgs,spawn):
  248. from mmgen.daemon import CoinDaemon
  249. self.rpc_port = CoinDaemon(g.coin,test_suite=True).rpc_port
  250. os.environ['MMGEN_BOGUS_WALLET_DATA'] = ''
  251. return TestSuiteBase.__init__(self,trunner,cfgs,spawn)
  252. @property
  253. def eth_args(self):
  254. return ['--outdir={}'.format(self.tmpdir),'--coin='+g.coin,'--rpc-port={}'.format(self.rpc_port),'--quiet']
  255. def setup(self):
  256. self.spawn('',msg_only=True)
  257. if solc_ver in self.solc_vers:
  258. imsg('Found solc version {}'.format(solc_ver))
  259. else:
  260. imsg('Solc compiler {}. Using precompiled contract data'.format(
  261. 'version {} not supported by test suite'.format(solc_ver)
  262. if solc_ver else 'not found' ))
  263. srcdir = os.path.join(self.tr.repo_root,'test','ref','ethereum','bin')
  264. from shutil import copytree
  265. for d in ('mm1','mm2'):
  266. copytree(os.path.join(srcdir,d),os.path.join(self.tmpdir,d))
  267. restart_test_daemons(g.coin)
  268. return 'ok'
  269. def wallet_upgrade(self,src_file):
  270. if g.coin == 'ETC':
  271. msg('skipping test {!r} for ETC'.format(self.test_name))
  272. return 'skip'
  273. src_dir = joinpath(ref_dir,'ethereum')
  274. dest_dir = joinpath(self.tr.data_dir,'altcoins',g.coin.lower())
  275. w_from = joinpath(src_dir,src_file)
  276. w_to = joinpath(dest_dir,'tracking-wallet.json')
  277. os.makedirs(dest_dir,mode=0o750,exist_ok=True)
  278. dest = shutil.copy2(w_from,w_to)
  279. assert dest == w_to, dest
  280. t = self.spawn('mmgen-tool', self.eth_args + ['twview'])
  281. t.read()
  282. os.unlink(w_to)
  283. return t
  284. def wallet_upgrade1(self): return self.wallet_upgrade('tracking-wallet-v1.json')
  285. def wallet_upgrade2(self): return self.wallet_upgrade('tracking-wallet-v2.json')
  286. def addrgen(self,addrs='1-3,11-13,21-23'):
  287. from mmgen.addr import MMGenAddrType
  288. t = self.spawn('mmgen-addrgen', self.eth_args + [dfl_words_file,addrs])
  289. t.written_to_file('Addresses')
  290. t.read()
  291. return t
  292. def addrimport(self,ext='21-23]{}.addrs',expect='9/9',add_args=[],bad_input=False):
  293. ext = ext.format('-α' if g.debug_utf8 else '')
  294. fn = self.get_file_with_ext(ext,no_dot=True,delete=False)
  295. t = self.spawn('mmgen-addrimport', self.eth_args[1:-1] + add_args + [fn])
  296. if bad_input:
  297. t.read()
  298. return t
  299. t.expect('Importing')
  300. t.expect(expect)
  301. t.read()
  302. return t
  303. def addrimport_one_addr(self,addr=None,extra_args=[]):
  304. t = self.spawn('mmgen-addrimport', self.eth_args[1:] + extra_args + ['--address='+addr])
  305. t.expect('OK')
  306. return t
  307. def addrimport_dev_addr(self):
  308. return self.addrimport_one_addr(addr=dfl_addr)
  309. def addrimport_burn_addr(self):
  310. return self.addrimport_one_addr(addr=burn_addr)
  311. def txcreate(self,args=[],menu=[],acct='1',non_mmgen_inputs=0,caller='txcreate',
  312. interactive_fee = '50G',
  313. eth_fee_res = None,
  314. fee_res_fs = '0.00105 {} (50 gas price in Gwei)',
  315. fee_desc = 'gas price',
  316. no_read = False):
  317. fee_res = fee_res_fs.format(g.coin)
  318. t = self.spawn('mmgen-'+caller, self.eth_args + ['-B'] + args)
  319. t.expect(r'add \[l\]abel, .*?:.','p', regex=True)
  320. t.written_to_file('Account balances listing')
  321. t = self.txcreate_ui_common( t, menu=menu, caller=caller,
  322. input_sels_prompt = 'to spend from',
  323. inputs = acct,
  324. file_desc = 'Ethereum transaction',
  325. bad_input_sels = True,
  326. non_mmgen_inputs = non_mmgen_inputs,
  327. interactive_fee = interactive_fee,
  328. fee_res = fee_res,
  329. fee_desc = fee_desc,
  330. eth_fee_res = eth_fee_res,
  331. add_comment = tx_label_jp )
  332. if not no_read:
  333. t.read()
  334. return t
  335. def txsign(self,ni=False,ext='{}.rawtx',add_args=[]):
  336. ext = ext.format('-α' if g.debug_utf8 else '')
  337. keyfile = joinpath(self.tmpdir,parity_key_fn)
  338. write_to_file(keyfile,dfl_privkey+'\n')
  339. txfile = self.get_file_with_ext(ext,no_dot=True)
  340. t = self.spawn( 'mmgen-txsign',
  341. ['--outdir={}'.format(self.tmpdir),'--coin='+g.coin,'--quiet']
  342. + ['--rpc-host=bad_host'] # ETH signing must work without RPC
  343. + add_args
  344. + ([],['--yes'])[ni]
  345. + ['-k', keyfile, txfile, dfl_words_file] )
  346. return self.txsign_ui_common(t,ni=ni,has_label=True)
  347. def txsend(self,ni=False,bogus_send=False,ext='{}.sigtx',add_args=[]):
  348. ext = ext.format('-α' if g.debug_utf8 else '')
  349. txfile = self.get_file_with_ext(ext,no_dot=True)
  350. if not bogus_send: os.environ['MMGEN_BOGUS_SEND'] = ''
  351. t = self.spawn('mmgen-txsend', self.eth_args + add_args + [txfile])
  352. if not bogus_send: os.environ['MMGEN_BOGUS_SEND'] = '1'
  353. txid = self.txsend_ui_common(t,quiet=not g.debug,bogus_send=bogus_send,has_label=True)
  354. return t
  355. def txcreate1(self):
  356. # valid_keypresses = EthereumTwUnspentOutputs.key_mappings.keys()
  357. menu = ['a','d','r','M','X','e','m','m'] # include one invalid keypress, 'X'
  358. args = ['98831F3A:E:1,123.456']
  359. return self.txcreate(args=args,menu=menu,acct='1',non_mmgen_inputs=1)
  360. def txsign1(self): return self.txsign(add_args=['--use-internal-keccak-module'])
  361. def tx_status0_bad(self):
  362. return self.tx_status(ext='{}.sigtx',expect_str='neither in mempool nor blockchain',exit_val=1)
  363. def txsign1_ni(self): return self.txsign(ni=True)
  364. def txsend1(self): return self.txsend()
  365. def bal1(self): return self.bal(n='1')
  366. def txcreate2(self):
  367. args = ['98831F3A:E:11,1.234']
  368. return self.txcreate(args=args,acct='10',non_mmgen_inputs=1)
  369. def txsign2(self): return self.txsign(ni=True,ext='1.234,50000]{}.rawtx')
  370. def txsend2(self): return self.txsend(ext='1.234,50000]{}.sigtx')
  371. def bal2(self): return self.bal(n='2')
  372. def txcreate3(self):
  373. args = ['98831F3A:E:21,2.345']
  374. return self.txcreate(args=args,acct='10',non_mmgen_inputs=1)
  375. def txsign3(self): return self.txsign(ni=True,ext='2.345,50000]{}.rawtx')
  376. def txsend3(self): return self.txsend(ext='2.345,50000]{}.sigtx')
  377. def bal3(self): return self.bal(n='3')
  378. def tx_status(self,ext,expect_str,expect_str2='',add_args=[],exit_val=0):
  379. ext = ext.format('-α' if g.debug_utf8 else '')
  380. txfile = self.get_file_with_ext(ext,no_dot=True)
  381. t = self.spawn('mmgen-txsend', self.eth_args + add_args + ['--status',txfile])
  382. t.expect(expect_str)
  383. if expect_str2:
  384. t.expect(expect_str2)
  385. t.read()
  386. t.req_exit_val = exit_val
  387. return t
  388. def tx_status1(self):
  389. return self.tx_status(ext='2.345,50000]{}.sigtx',expect_str='has 1 confirmation')
  390. def tx_status1a(self):
  391. return self.tx_status(ext='2.345,50000]{}.sigtx',expect_str='has 2 confirmations')
  392. def txcreate4(self):
  393. args = ['98831F3A:E:2,23.45495']
  394. interactive_fee='40G'
  395. fee_res_fs='0.00084 {} (40 gas price in Gwei)'
  396. return self.txcreate( args = args,
  397. acct = '1',
  398. non_mmgen_inputs = 0,
  399. interactive_fee = interactive_fee,
  400. fee_res_fs = fee_res_fs,
  401. eth_fee_res = True )
  402. def txbump(self,ext=',40000]{}.rawtx',fee='50G',add_args=[]):
  403. ext = ext.format('-α' if g.debug_utf8 else '')
  404. txfile = self.get_file_with_ext(ext,no_dot=True)
  405. t = self.spawn('mmgen-txbump', self.eth_args + add_args + ['--yes',txfile])
  406. t.expect('or gas price: ',fee+'\n')
  407. t.read()
  408. return t
  409. def txsign4(self): return self.txsign(ni=True,ext='.45495,50000]{}.rawtx')
  410. def txsend4(self): return self.txsend(ext='.45495,50000]{}.sigtx')
  411. def bal4(self): return self.bal(n='4')
  412. def txcreate5(self):
  413. args = [burn_addr + ','+amt1]
  414. return self.txcreate(args=args,acct='10',non_mmgen_inputs=1)
  415. def txsign5(self): return self.txsign(ni=True,ext=amt1+',50000]{}.rawtx')
  416. def txsend5(self): return self.txsend(ext=amt1+',50000]{}.sigtx')
  417. def bal5(self): return self.bal(n='5')
  418. bal_corr = Decimal('0.0000032') # gas use for token sends varies between ETH and ETC!
  419. def bal(self,n=None):
  420. t = self.spawn('mmgen-tool', self.eth_args + ['twview','wide=1'])
  421. for b in bals[n]:
  422. addr,amt,adj = b if len(b) == 3 else b + (False,)
  423. if adj and g.coin == 'ETC': amt = str(Decimal(amt) + Decimal(adj[1]) * self.bal_corr)
  424. pat = r'{}\s+{}\s'.format(addr,amt.replace('.',r'\.'))
  425. t.expect(pat,regex=True)
  426. t.read()
  427. return t
  428. def token_bal(self,n=None):
  429. t = self.spawn('mmgen-tool', self.eth_args + ['--token=mm1','twview','wide=1'])
  430. for b in token_bals[n]:
  431. addr,_amt1,_amt2,adj = b if len(b) == 4 else b + (False,)
  432. if adj and g.coin == 'ETC':
  433. _amt2 = str(Decimal(_amt2) + Decimal(adj[1]) * self.bal_corr)
  434. pat = r'{}\s+{}\s+{}\s'.format(addr,_amt1.replace('.',r'\.'),_amt2.replace('.',r'\.'))
  435. t.expect(pat,regex=True)
  436. t.expect('Total MM1:')
  437. t.read()
  438. return t
  439. def bal_getbalance(self,idx,etc_adj=False,extra_args=[]):
  440. bal1 = token_bals_getbalance[idx][0]
  441. bal2 = token_bals_getbalance[idx][1]
  442. bal1 = Decimal(bal1)
  443. if etc_adj and g.coin == 'ETC':
  444. bal1 += self.bal_corr
  445. t = self.spawn('mmgen-tool', self.eth_args + extra_args + ['getbalance'])
  446. t.expect(r'\n[0-9A-F]{8}: .* '+str(bal1),regex=True)
  447. t.expect(r'\nNon-MMGen: .* '+bal2,regex=True)
  448. total = t.expect_getend(r'\nTOTAL:\s+',regex=True).split()[0]
  449. t.read()
  450. assert Decimal(bal1) + Decimal(bal2) == Decimal(total)
  451. return t
  452. def add_label(self,lbl,addr='98831F3A:E:3'):
  453. t = self.spawn('mmgen-tool', self.eth_args + ['add_label',addr,lbl])
  454. t.expect('Added label.*in tracking wallet',regex=True)
  455. return t
  456. def chk_label(self,lbl_pat,addr='98831F3A:E:3'):
  457. t = self.spawn('mmgen-tool', self.eth_args + ['listaddresses','all_labels=1'])
  458. t.expect(r'{}\s+\S{{30}}\S+\s+{}\s+'.format(addr,lbl_pat),regex=True)
  459. return t
  460. def add_label1(self): return self.add_label(lbl=tw_label_zh)
  461. def chk_label1(self): return self.chk_label(lbl_pat=tw_label_zh)
  462. def add_label2(self): return self.add_label(lbl=tw_label_lat_cyr_gr)
  463. def chk_label2(self): return self.chk_label(lbl_pat=tw_label_lat_cyr_gr)
  464. def remove_label(self,addr='98831F3A:E:3'):
  465. t = self.spawn('mmgen-tool', self.eth_args + ['remove_label',addr])
  466. t.expect('Removed label.*in tracking wallet',regex=True)
  467. return t
  468. def token_compile(self,token_data={}):
  469. odir = joinpath(self.tmpdir,token_data['symbol'].lower())
  470. if not solc_ver:
  471. imsg('Using precompiled contract data in {}'.format(odir))
  472. return 'skip' if os.path.exists(odir) else False
  473. self.spawn('',msg_only=True)
  474. cmd_args = ['--{}={}'.format(k,v) for k,v in list(token_data.items())]
  475. imsg("Compiling solidity token contract '{}' with 'solc'".format(token_data['symbol']))
  476. try: os.mkdir(odir)
  477. except: pass
  478. cmd = [
  479. 'scripts/traceback_run.py',
  480. 'scripts/create-token.py',
  481. '--coin=' + g.coin,
  482. '--outdir=' + odir
  483. ] + cmd_args + [dfl_addr_chk]
  484. imsg("Executing: {}".format(' '.join(cmd)))
  485. cp = run(cmd,stdout=DEVNULL,stderr=PIPE)
  486. if cp.returncode != 0:
  487. rdie(2,'solc failed with the following output: {}'.format(cp.stderr))
  488. imsg("ERC20 token '{}' compiled".format(token_data['symbol']))
  489. return 'ok'
  490. def token_compile1(self):
  491. token_data = { 'name':'MMGen Token 1', 'symbol':'MM1', 'supply':10**26, 'decimals':18 }
  492. return self.token_compile(token_data)
  493. def token_compile2(self):
  494. token_data = { 'name':'MMGen Token 2', 'symbol':'MM2', 'supply':10**18, 'decimals':10 }
  495. return self.token_compile(token_data)
  496. async def token_deploy(self,num,key,gas,mmgen_cmd='txdo',tx_fee='8G'):
  497. keyfile = joinpath(self.tmpdir,parity_key_fn)
  498. fn = joinpath(self.tmpdir,'mm'+str(num),key+'.bin')
  499. os.environ['MMGEN_BOGUS_SEND'] = ''
  500. args = ['-B',
  501. '--tx-fee='+tx_fee,
  502. '--tx-gas={}'.format(gas),
  503. '--contract-data='+fn,
  504. '--inputs='+dfl_addr,
  505. '--yes' ]
  506. if mmgen_cmd == 'txdo': args += ['-k',keyfile]
  507. t = self.spawn( 'mmgen-'+mmgen_cmd, self.eth_args + args)
  508. if mmgen_cmd == 'txcreate':
  509. t.written_to_file('Ethereum transaction')
  510. ext = '[0,8000]{}.rawtx'.format('-α' if g.debug_utf8 else '')
  511. txfile = self.get_file_with_ext(ext,no_dot=True)
  512. t = self.spawn('mmgen-txsign', self.eth_args + ['--yes','-k',keyfile,txfile],no_msg=True)
  513. self.txsign_ui_common(t,ni=True)
  514. txfile = txfile.replace('.rawtx','.sigtx')
  515. t = self.spawn('mmgen-txsend', self.eth_args + [txfile],no_msg=True)
  516. os.environ['MMGEN_BOGUS_SEND'] = '1'
  517. txid = self.txsend_ui_common(t,caller=mmgen_cmd,
  518. quiet = mmgen_cmd == 'txdo' or not g.debug,
  519. bogus_send=False)
  520. addr = t.expect_getend('Contract address: ')
  521. from mmgen.altcoins.eth.tx import EthereumMMGenTX as etx
  522. assert (await etx.get_exec_status(txid,True)) != 0,(
  523. "Contract '{}:{}' failed to execute. Aborting".format(num,key))
  524. if key == 'Token':
  525. self.write_to_tmpfile('token_addr{}'.format(num),addr+'\n')
  526. imsg('\nToken MM{} deployed!'.format(num))
  527. return t
  528. async def token_deploy1a(self): return await self.token_deploy(num=1,key='SafeMath',gas=200000)
  529. async def token_deploy1b(self): return await self.token_deploy(num=1,key='Owned',gas=250000)
  530. async def token_deploy1c(self): return await self.token_deploy(num=1,key='Token',gas=1100000,tx_fee='7G')
  531. def tx_status2(self):
  532. return self.tx_status(ext=g.coin+'[0,7000]{}.sigtx',expect_str='successfully executed')
  533. def bal6(self): return self.bal5()
  534. async def token_deploy2a(self): return await self.token_deploy(num=2,key='SafeMath',gas=200000)
  535. async def token_deploy2b(self): return await self.token_deploy(num=2,key='Owned',gas=250000)
  536. async def token_deploy2c(self): return await self.token_deploy(num=2,key='Token',gas=1100000)
  537. async def contract_deploy(self): # test create,sign,send
  538. return await self.token_deploy(num=2,key='SafeMath',gas=1100000,mmgen_cmd='txcreate')
  539. async def token_transfer_ops(self,op,amt=1000):
  540. self.spawn('',msg_only=True)
  541. sid = dfl_sid
  542. from mmgen.tool import MMGenToolCmdWallet
  543. usr_mmaddrs = ['{}:E:{}'.format(sid,i) for i in (11,21)]
  544. usr_addrs = [MMGenToolCmdWallet().gen_addr(addr,dfl_words_file) for addr in usr_mmaddrs]
  545. from mmgen.altcoins.eth.contract import TokenResolve
  546. from mmgen.altcoins.eth.tx import EthereumMMGenTX as etx
  547. async def do_transfer():
  548. for i in range(2):
  549. tk = await TokenResolve(
  550. self.read_from_tmpfile(f'token_addr{i+1}').strip() )
  551. imsg_r( '\n' + await tk.info() )
  552. imsg('dev token balance (pre-send): {}'.format(await tk.get_balance(dfl_addr)))
  553. imsg('Sending {} {} to address {} ({})'.format(amt,g.coin,usr_addrs[i],usr_mmaddrs[i]))
  554. from mmgen.obj import ETHAmt
  555. txid = await tk.transfer(
  556. dfl_addr,
  557. usr_addrs[i],
  558. amt,
  559. dfl_privkey,
  560. start_gas = ETHAmt(60000,'wei'),
  561. gasPrice = ETHAmt(8,'Gwei') )
  562. assert (await etx.get_exec_status(txid,True)) != 0,'Transfer of token funds failed. Aborting'
  563. async def show_bals():
  564. for i in range(2):
  565. tk = await TokenResolve(
  566. self.read_from_tmpfile(f'token_addr{i+1}').strip() )
  567. imsg('Token: {}'.format(await tk.get_symbol()))
  568. imsg('dev token balance: {}'.format(await tk.get_balance(dfl_addr)))
  569. imsg('usr token balance: {} ({} {})'.format(
  570. await tk.get_balance(usr_addrs[i]),usr_mmaddrs[i],usr_addrs[i]))
  571. silence()
  572. if op == 'show_bals':
  573. await show_bals()
  574. elif op == 'do_transfer':
  575. await do_transfer()
  576. end_silence()
  577. return 'ok'
  578. def token_fund_users(self):
  579. return self.token_transfer_ops(op='do_transfer')
  580. def token_user_bals(self):
  581. return self.token_transfer_ops(op='show_bals')
  582. def token_addrgen(self):
  583. self.addrgen(addrs='11-13')
  584. ok_msg()
  585. return self.addrgen(addrs='21-23')
  586. def token_addrimport_badaddr1(self):
  587. t = self.addrimport(ext='[11-13]{}.addrs',add_args=['--token=abc'],bad_input=True)
  588. t.req_exit_val = 2
  589. return t
  590. def token_addrimport_badaddr2(self):
  591. t = self.addrimport(ext='[11-13]{}.addrs',add_args=['--token='+'00deadbeef'*4],bad_input=True)
  592. t.req_exit_val = 2
  593. return t
  594. def token_addrimport(self,extra_args=[],expect='3/3'):
  595. for n,r in ('1','11-13'),('2','21-23'):
  596. tk_addr = self.read_from_tmpfile('token_addr'+n).strip()
  597. t = self.addrimport(ext='['+r+']{}.addrs',expect=expect,add_args=['--token='+tk_addr]+extra_args)
  598. t.p.wait()
  599. ok_msg()
  600. t.skip_ok = True
  601. return t
  602. def token_addrimport_batch(self):
  603. return self.token_addrimport(extra_args=['--batch'],expect='OK: 3')
  604. def bal7(self): return self.bal5()
  605. def token_bal1(self): return self.token_bal(n='1')
  606. def token_txcreate(self,args=[],token='',inputs='1',fee='50G'):
  607. t = self.spawn('mmgen-txcreate', self.eth_args + ['--token='+token,'-B','--tx-fee='+fee] + args)
  608. t = self.txcreate_ui_common(
  609. t,
  610. menu = [],
  611. inputs = inputs,
  612. input_sels_prompt = 'to spend from',
  613. file_desc = 'Ethereum token transaction',
  614. add_comment = tx_label_lat_cyr_gr )
  615. t.read()
  616. return t
  617. def token_txsign(self,ext='',token=''):
  618. return self.txsign(ni=True,ext=ext,add_args=['--token='+token])
  619. def token_txsend(self,ext='',token=''):
  620. return self.txsend(ext=ext,add_args=['--token=mm1'])
  621. def token_txcreate1(self):
  622. return self.token_txcreate(args=['98831F3A:E:12,1.23456'],token='mm1')
  623. def token_txsign1(self):
  624. return self.token_txsign(ext='1.23456,50000]{}.rawtx',token='mm1')
  625. def token_txsend1(self):
  626. return self.token_txsend(ext='1.23456,50000]{}.sigtx',token='mm1')
  627. def tx_status3(self):
  628. return self.tx_status(
  629. ext='1.23456,50000]{}.sigtx',
  630. add_args=['--token=mm1'],
  631. expect_str='successfully executed',
  632. expect_str2='has 1 confirmation')
  633. def token_bal2(self):
  634. return self.token_bal(n='2')
  635. def twview(self,args=[],expect_str='',tool_args=[],exit_val=0):
  636. t = self.spawn('mmgen-tool', self.eth_args + args + ['twview'] + tool_args)
  637. if expect_str:
  638. t.expect(expect_str,regex=True)
  639. t.read()
  640. t.req_exit_val = exit_val
  641. return t
  642. def token_txcreate2(self):
  643. return self.token_txcreate(args=[burn_addr+','+amt2],token='mm1')
  644. def token_txbump(self):
  645. return self.txbump(ext=amt2+',50000]{}.rawtx',fee='56G',add_args=['--token=mm1'])
  646. def token_txsign2(self):
  647. return self.token_txsign(ext=amt2+',50000]{}.rawtx',token='mm1')
  648. def token_txsend2(self):
  649. return self.token_txsend(ext=amt2+',50000]{}.sigtx',token='mm1')
  650. def token_bal3(self):
  651. return self.token_bal(n='3')
  652. def del_dev_addr(self):
  653. t = self.spawn('mmgen-tool', self.eth_args + ['remove_address',dfl_addr])
  654. t.read() # TODO
  655. return t
  656. def bal1_getbalance(self):
  657. return self.bal_getbalance('1',etc_adj=True)
  658. def addrimport_token_burn_addr(self):
  659. return self.addrimport_one_addr(addr=burn_addr,extra_args=['--token=mm1'])
  660. def token_bal4(self):
  661. return self.token_bal(n='4')
  662. def token_bal_getbalance(self):
  663. return self.bal_getbalance('2',extra_args=['--token=mm1'])
  664. def txcreate_noamt(self):
  665. return self.txcreate(args=['98831F3A:E:12'],eth_fee_res=True)
  666. def txsign_noamt(self):
  667. return self.txsign(ext='99.99895,50000]{}.rawtx')
  668. def txsend_noamt(self):
  669. return self.txsend(ext='99.99895,50000]{}.sigtx')
  670. def bal8(self): return self.bal(n='8')
  671. def token_bal5(self): return self.token_bal(n='5')
  672. def token_txcreate_noamt(self):
  673. return self.token_txcreate(args=['98831F3A:E:13'],token='mm1',inputs='2',fee='51G')
  674. def token_txsign_noamt(self):
  675. return self.token_txsign(ext='1.23456,51000]{}.rawtx',token='mm1')
  676. def token_txsend_noamt(self):
  677. return self.token_txsend(ext='1.23456,51000]{}.sigtx',token='mm1')
  678. def bal9(self): return self.bal(n='9')
  679. def token_bal6(self): return self.token_bal(n='6')
  680. def listaddresses(self,args=[],tool_args=['all_labels=1'],exit_val=0):
  681. t = self.spawn('mmgen-tool', self.eth_args + args + ['listaddresses'] + tool_args)
  682. t.read()
  683. t.req_exit_val = exit_val
  684. return t
  685. def listaddresses1(self):
  686. return self.listaddresses()
  687. def listaddresses2(self):
  688. return self.listaddresses(tool_args=['minconf=999999999'])
  689. def listaddresses3(self):
  690. return self.listaddresses(tool_args=['sort=age'])
  691. def listaddresses4(self):
  692. return self.listaddresses(tool_args=['sort=age','showempty=1'])
  693. def token_listaddresses1(self):
  694. return self.listaddresses(args=['--token=mm1'])
  695. def token_listaddresses2(self):
  696. return self.listaddresses(args=['--token=mm1'],tool_args=['showempty=1'])
  697. def twview_cached_balances(self):
  698. return self.twview(args=['--cached-balances'])
  699. def token_twview_cached_balances(self):
  700. return self.twview(args=['--token=mm1','--cached-balances'])
  701. def txcreate_cached_balances(self):
  702. args = ['--tx-fee=20G','--cached-balances','98831F3A:E:3,0.1276']
  703. return self.txcreate(args=args,acct='2')
  704. def token_txcreate_cached_balances(self):
  705. args=['--cached-balances','--tx-fee=12G','98831F3A:E:12,1.2789']
  706. return self.token_txcreate(args=args,token='mm1')
  707. def txdo_cached_balances(self,
  708. acct = '2',
  709. fee_res_fs = '0.00105 {} (50 gas price in Gwei)',
  710. add_args = ['98831F3A:E:3,0.4321']):
  711. args = ['--tx-fee=20G','--cached-balances'] + add_args + [dfl_words_file]
  712. os.environ['MMGEN_BOGUS_SEND'] = ''
  713. t = self.txcreate(args=args,acct=acct,caller='txdo',fee_res_fs=fee_res_fs,no_read=True)
  714. os.environ['MMGEN_BOGUS_SEND'] = '1'
  715. self._do_confirm_send(t,quiet=not g.debug,sure=False)
  716. t.read()
  717. return t
  718. def txcreate_refresh_balances(self,
  719. bals=['2','3'],
  720. args=['-B','--cached-balances','-i'],
  721. total= '1000126.14829832312345678',
  722. adj_total=True,
  723. total_coin=g.coin):
  724. if g.coin == 'ETC' and adj_total:
  725. total = str(Decimal(total) + self.bal_corr)
  726. t = self.spawn('mmgen-txcreate', self.eth_args + args)
  727. for n in bals:
  728. t.expect('[R]efresh balance:\b','R')
  729. t.expect(' main menu): ',n)
  730. t.expect('Is this what you want? (y/N): ','y')
  731. t.expect('[R]efresh balance:\b','q')
  732. t.expect('Total unspent: {} {}'.format(total,total_coin))
  733. t.read()
  734. return t
  735. def bal10(self): return self.bal(n='10')
  736. def token_txdo_cached_balances(self):
  737. return self.txdo_cached_balances(
  738. acct='1',
  739. fee_res_fs='0.0026 {} (50 gas price in Gwei)',
  740. add_args=['--token=mm1','98831F3A:E:12,43.21'])
  741. def token_txcreate_refresh_balances(self):
  742. return self.txcreate_refresh_balances(
  743. bals=['1','2'],
  744. args=['--token=mm1','-B','--cached-balances','-i'],
  745. total='1000',adj_total=False,total_coin='MM1')
  746. def token_bal7(self): return self.token_bal(n='7')
  747. def twview1(self):
  748. return self.twview()
  749. def twview2(self):
  750. return self.twview(tool_args=['wide=1'])
  751. def twview3(self):
  752. return self.twview(tool_args=['wide=1','sort=age'])
  753. def twview4(self):
  754. return self.twview(tool_args=['wide=1','minconf=999999999'])
  755. def twview5(self):
  756. return self.twview(tool_args=['wide=1','minconf=0'])
  757. def token_twview1(self):
  758. return self.twview(args=['--token=mm1'])
  759. def token_twview2(self):
  760. return self.twview(args=['--token=mm1'],tool_args=['wide=1'])
  761. def token_twview3(self):
  762. return self.twview(args=['--token=mm1'],tool_args=['wide=1','sort=age'])
  763. def edit_label(self,out_num,args=[],action='l',label_text=None):
  764. t = self.spawn('mmgen-txcreate', self.eth_args + args + ['-B','-i'])
  765. p1,p2 = ('efresh balance:\b','return to main menu): ')
  766. p3,r3 = (p2,label_text+'\n') if label_text is not None else ('(y/N): ','y')
  767. p4,r4 = (('(y/N): ',),('y',)) if label_text == '' else ((),())
  768. for p,r in zip((p1,p1,p2,p3)+p4,('M',action,out_num+'\n',r3)+r4):
  769. t.expect(p,r)
  770. m = ( 'Account #{} removed' if action == 'D' else
  771. 'Label added to account #{}' if label_text else
  772. 'Label removed from account #{}' )
  773. t.expect(m.format(out_num))
  774. for p,r in zip((p1,p1),('M','q')):
  775. t.expect(p,r)
  776. t.expect('Total unspent:')
  777. t.read()
  778. return t
  779. def edit_label1(self):
  780. return self.edit_label(out_num=del_addrs[0],label_text=tw_label_zh)
  781. def edit_label2(self):
  782. return self.edit_label(out_num=del_addrs[1],label_text=tw_label_lat_cyr_gr)
  783. def edit_label3(self):
  784. return self.edit_label(out_num=del_addrs[0],label_text='')
  785. def token_edit_label1(self):
  786. return self.edit_label(out_num='1',label_text='Token label #1',args=['--token=mm1'])
  787. def remove_addr1(self):
  788. return self.edit_label(out_num=del_addrs[0],action='D')
  789. def remove_addr2(self):
  790. return self.edit_label(out_num=del_addrs[1],action='D')
  791. def token_remove_addr1(self):
  792. return self.edit_label(out_num=del_addrs[0],args=['--token=mm1'],action='D')
  793. def token_remove_addr2(self):
  794. return self.edit_label(out_num=del_addrs[1],args=['--token=mm1'],action='D')
  795. def stop(self):
  796. self.spawn('',msg_only=True)
  797. stop_test_daemons(g.coin)
  798. return 'ok'