ethdev.py 59 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721
  1. #!/usr/bin/env python3
  2. #
  3. # MMGen Wallet, a terminal-based cryptocurrency wallet
  4. # Copyright (C)2013-2025 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. test.cmdtest_d.ethdev: Ethdev tests for the cmdtest.py test suite
  20. """
  21. import sys, time, os, re, shutil, asyncio, json
  22. from decimal import Decimal
  23. from collections import namedtuple
  24. from subprocess import run, PIPE, DEVNULL
  25. from pathlib import Path
  26. from mmgen.color import red, yellow, blue, cyan, set_vt100
  27. from mmgen.util import msg, rmsg, die
  28. from mmgen.proto.eth.misc import compute_contract_addr
  29. from ..include.common import (
  30. cfg,
  31. check_solc_ver,
  32. omsg,
  33. imsg,
  34. imsg_r,
  35. joinpath,
  36. read_from_file,
  37. write_to_file,
  38. cmp_or_die,
  39. strip_ansi_escapes,
  40. silence,
  41. end_silence,
  42. gr_uc,
  43. stop_test_daemons)
  44. from .include.common import (
  45. ref_dir,
  46. dfl_words_file,
  47. tx_comment_jp,
  48. tx_comment_lat_cyr_gr,
  49. tw_comment_zh,
  50. tw_comment_lat_cyr_gr,
  51. get_file_with_ext,
  52. ok_msg,
  53. Ctrl_U,
  54. cleanup_env)
  55. from .base import CmdTestBase
  56. from .shared import CmdTestShared
  57. from .httpd.etherscan import EtherscanServer
  58. etherscan_server = EtherscanServer()
  59. del_addrs = ('4', '1')
  60. dfl_sid = '98831F3A'
  61. # The OpenEthereum dev address with lots of coins. Create with "ethkey -b info ''":
  62. dfl_devaddr = '00a329c0648769a73afac7f9381e08fb43dbea72'
  63. dfl_devkey = '4d5db4107d237df6a3d58ee5f70ae63d73d7658d4026f2eefd2f204c81682cb7'
  64. def get_reth_dev_keypair(cfg):
  65. from mmgen.bip39 import bip39
  66. from mmgen.bip_hd import MasterNode
  67. mn = 'test test test test test test test test test test test junk' # See ‘reth node --help’
  68. seed = bip39().generate_seed(mn.split())
  69. m = MasterNode(cfg, seed)
  70. node = m.to_chain(idx=0, coin='eth').derive_private(0)
  71. return (node.key.hex(), node.address)
  72. burn_addr = 'deadbeef'*5
  73. burn_addr2 = 'beadcafe'*5
  74. amt1 = '999777.12345689012345678'
  75. amt2 = '888.111122223333444455'
  76. parity_devkey_fn = 'parity.devkey'
  77. def set_vbals(daemon_id):
  78. global vbal1, vbal2, vbal3, vbal4, vbal5, vbal6, vbal7, vbal9
  79. if daemon_id == 'geth':
  80. vbal1 = '1.2288334'
  81. vbal2 = '99.996560752'
  82. vbal3 = '1.2314176'
  83. vbal4 = '127.0287834'
  84. vbal5 = '999904.14775104212345678'
  85. vbal6 = '999904.14880104212345678'
  86. vbal7 = '999902.91891764212345678'
  87. vbal9 = '1.2262504'
  88. elif daemon_id == 'reth':
  89. vbal1 = '1.2288334'
  90. vbal2 = '99.996560752'
  91. vbal3 = '1.23142525'
  92. vbal3 = '1.2314176'
  93. vbal4 = '127.0287834'
  94. vbal5 = '999904.14775104212345678'
  95. vbal6 = '999904.14880104212345678'
  96. vbal7 = '999902.91891764212345678'
  97. vbal9 = '1.2262504'
  98. else:
  99. vbal1 = '1.2288396'
  100. vbal2 = '99.997088092'
  101. vbal3 = '1.23142525'
  102. vbal3 = '1.2314246'
  103. vbal4 = '127.0287896'
  104. vbal5 = '1000126.14828458212345678'
  105. vbal6 = '1000126.14933458212345678'
  106. vbal7 = '1000124.91944498212345678'
  107. vbal9 = '1.226261'
  108. coin = cfg.coin
  109. class CmdTestEthdevMethods: # mixin class
  110. def _addrgen(self, addrs='1-3,11-13,21-23', no_msg=False):
  111. t = self.spawn(
  112. 'mmgen-addrgen',
  113. [f'--coin={self.proto.coin}'] + self.eth_opts + [dfl_words_file, addrs],
  114. no_msg = no_msg,
  115. no_passthru_opts = True)
  116. t.written_to_file('Addresses')
  117. return t
  118. def _create_tx(self, *, fee, args, add_opts=[]):
  119. return self.txcreate_ui_common(
  120. self.spawn('mmgen-txcreate', add_opts + ['-B'] + args),
  121. caller = 'txcreate',
  122. input_sels_prompt = 'to spend from',
  123. inputs = '1',
  124. file_desc = 'transaction',
  125. interactive_fee = fee,
  126. fee_desc = 'transaction fee or gas price')
  127. def _send_tx(self, *, desc='transaction', add_opts=[]):
  128. t = self.spawn('mmgen-txsend', add_opts, no_passthru_opts=['coin'])
  129. t.view_tx('t')
  130. t.expect('(y/N): ', 'n')
  131. self._do_confirm_send(t, quiet=True)
  132. t.written_to_file(f'Sent {desc}')
  133. return t
  134. def _txdo(self, *, args, acct, fee='50G'):
  135. t = self.txcreate(
  136. args,
  137. acct = acct,
  138. caller = 'txdo',
  139. no_read = True,
  140. bad_input_sels = False,
  141. interactive_fee = fee,
  142. print_listing = False)
  143. t.written_to_file('Signed transaction')
  144. t.expect('confirm: ', 'YES\n')
  145. return t
  146. def _txbump(self, *, fee, ext, add_opts=[], add_args=[]):
  147. ext = ext.format('-α' if self.cfg.debug_utf8 else '')
  148. txfile = self.get_file_with_ext(ext, no_dot=True)
  149. t = self.spawn('mmgen-txbump', self.eth_opts + add_opts + ['--yes', txfile] + add_args)
  150. t.expect('or gas price: ', fee+'\n')
  151. return t
  152. def _tx_receipt(self, ext='{}.regtest.sigtx'):
  153. self.mining_delay()
  154. return self.txsend(
  155. ext = ext,
  156. add_args = ['--receipt'],
  157. return_early = True,
  158. env = cleanup_env(cfg=self.cfg))
  159. def _fund_mmgen_address(self, arg):
  160. return self._txdo(
  161. args = [f'--keys-from-file={joinpath(self.tmpdir, parity_devkey_fn)}', arg, dfl_words_file],
  162. acct = '10')
  163. def _bal_check(self, *, pat, add_opts=[]):
  164. self.mining_delay()
  165. t = self.spawn('mmgen-tool', ['--regtest=1'] + add_opts + ['twview', 'wide=1'])
  166. text = t.read(strip_color=True)
  167. assert re.search(pat, text, re.DOTALL), f'output failed to match regex {pat}'
  168. return t
  169. def _token_addrgen(self, *, mm_idxs, naddrs):
  170. self.spawn(msg_only=True)
  171. for idx in mm_idxs:
  172. t = self._addrgen(addrs=f'{idx}-{idx+naddrs-1}', no_msg=True)
  173. return t
  174. def _token_addrimport(self, addr_file, addr_range, expect, extra_args=[]):
  175. token_addr = self.read_from_tmpfile(addr_file).strip()
  176. return self.addrimport(
  177. ext = f'[{addr_range}]{{}}.regtest.addrs',
  178. expect = expect,
  179. add_args = ['--token-addr='+token_addr]+extra_args)
  180. def _get_contract_address(self, deployer_addr):
  181. t = self.spawn(
  182. 'mmgen-cli',
  183. ['--regtest=1', 'eth_getTransactionCount', '0x'+deployer_addr, 'pending'],
  184. env = cleanup_env(cfg=self.cfg),
  185. silent = True,
  186. no_msg = True)
  187. nonce = t.read().strip()
  188. imsg(f'Nonce: {red(nonce)}')
  189. ret = compute_contract_addr(self.cfg, deployer_addr, int(nonce, 16))
  190. imsg(f'Computed contract address: {blue(ret)}')
  191. return ret
  192. async def _token_deploy(self, num, key, gas, mmgen_cmd='txdo', gas_price='8G', get_receipt=True):
  193. keyfile = joinpath(self.tmpdir, parity_devkey_fn)
  194. fn = joinpath(self.tmpdir, 'mm'+str(num), key+'.bin')
  195. args = [
  196. '-B',
  197. f'--fee={gas_price}',
  198. f'--gas={gas}',
  199. f'--contract-data={fn}',
  200. f'--inputs={dfl_devaddr}',
  201. '--yes',
  202. ]
  203. contract_addr = self._get_contract_address(dfl_devaddr)
  204. if key == 'Token':
  205. self.write_to_tmpfile(f'token_addr{num}', contract_addr+'\n')
  206. if mmgen_cmd == 'txdo':
  207. args += ['-k', keyfile]
  208. t = self.spawn('mmgen-'+mmgen_cmd, self.eth_opts + args)
  209. if mmgen_cmd == 'txcreate':
  210. t.written_to_file('transaction')
  211. ext = '[0,8000]{}.regtest.rawtx'.format('-α' if self.cfg.debug_utf8 else '')
  212. txfile = self.get_file_with_ext(ext, no_dot=True)
  213. t = self.spawn(
  214. 'mmgen-txsign',
  215. self.eth_opts + ['--yes', '-k', keyfile, txfile], no_msg=True, no_passthru_opts=['coin'])
  216. self.txsign_ui_common(t, ni=True)
  217. txfile = txfile.replace('.rawtx', '.sigtx')
  218. t = self.spawn('mmgen-txsend',
  219. self.eth_opts + [txfile], no_msg=True, no_passthru_opts=['coin'])
  220. txid = self.txsend_ui_common(t,
  221. caller = mmgen_cmd,
  222. quiet = mmgen_cmd == 'txdo' or not self.cfg.debug,
  223. bogus_send = False)
  224. _ = strip_ansi_escapes(t.expect_getend('Contract address: '))
  225. assert _ == contract_addr, f'Contract address mismatch: {_} != {contract_addr}'
  226. if get_receipt:
  227. if (await self.get_tx_receipt(txid)).status == 0:
  228. die(2, f'Contract {num}:{key} failed to execute. Aborting')
  229. if key == 'Token':
  230. imsg(f'\nToken MM{num} deployed!')
  231. return t
  232. async def _token_deploy_math(self, *, num, get_receipt=True, mmgen_cmd='txdo'):
  233. return await self._token_deploy(
  234. num=num, key='SafeMath', gas=500_000, get_receipt=get_receipt, mmgen_cmd=mmgen_cmd)
  235. async def _token_deploy_owned(self, *, num, get_receipt=True):
  236. return await self._token_deploy(
  237. num=num, key='Owned', gas=1_000_000, get_receipt=get_receipt)
  238. async def _token_deploy_token(self, *, num, get_receipt=True):
  239. return await self._token_deploy(
  240. num=num, key='Token', gas=4_000_000, gas_price='7G', get_receipt=get_receipt)
  241. def _token_bal_check(self, *, pat):
  242. return self._bal_check(pat=pat, add_opts=['--token=MM1'])
  243. def _create_token_tx(self, *, cmd, fee, args, add_opts=[]):
  244. return self.txcreate_ui_common(
  245. self.spawn(
  246. f'mmgen-{cmd}',
  247. ['--token=MM1', '-B', f'--fee={fee}'] + add_opts + args),
  248. inputs = '1',
  249. input_sels_prompt = 'to spend from',
  250. caller = cmd,
  251. file_desc = 'Unsigned automount transaction')
  252. async def _token_transfer_ops(self, *, op, mm_idxs, amt=1000, get_receipt=True, sid=dfl_sid):
  253. self.spawn(msg_only=True)
  254. from mmgen.tool.wallet import tool_cmd
  255. usr_mmaddrs = [f'{sid}:E:{i}' for i in mm_idxs]
  256. from mmgen.proto.eth.contract import ResolvedToken
  257. async def fund_user(rpc):
  258. for i in range(len(usr_mmaddrs)):
  259. tk = await ResolvedToken(
  260. self.cfg,
  261. self.proto,
  262. rpc,
  263. self.read_from_tmpfile(f'token_addr{i+1}').strip())
  264. imsg_r('\n' + await tk.info())
  265. imsg('dev token balance (pre-send): {}'.format(await tk.get_balance(dfl_devaddr)))
  266. imsg(f'Sending {amt} {self.proto.dcoin} to address {usr_addrs[i]} ({usr_mmaddrs[i]})')
  267. txid = await tk.transfer(
  268. from_addr = dfl_devaddr,
  269. to_addr = usr_addrs[i],
  270. amt = amt,
  271. key = dfl_devkey,
  272. gas = self.proto.coin_amt(120000, from_unit='wei'),
  273. gasPrice = self.proto.coin_amt(8, from_unit='Gwei'))
  274. if get_receipt and (await self.get_tx_receipt(txid)).status == 0:
  275. die(2, 'Transfer of token funds failed. Aborting')
  276. async def show_bals(rpc):
  277. for i in range(len(usr_mmaddrs)):
  278. tk = await ResolvedToken(
  279. self.cfg,
  280. self.proto,
  281. rpc,
  282. self.read_from_tmpfile(f'token_addr{i+1}').strip())
  283. imsg('Token: {}'.format(await tk.get_symbol()))
  284. imsg(f'dev token balance: {await tk.get_balance(dfl_devaddr)}')
  285. imsg('usr token balance: {} ({} {})'.format(
  286. await tk.get_balance(usr_addrs[i]),
  287. usr_mmaddrs[i],
  288. usr_addrs[i]))
  289. def gen_addr(addr):
  290. return tool_cmd(
  291. self.cfg, cmdname='gen_addr', proto=self.proto).gen_addr(addr, wallet=dfl_words_file)
  292. silence()
  293. usr_addrs = list(map(gen_addr, usr_mmaddrs))
  294. if op == 'show_bals':
  295. await show_bals(await self.rpc)
  296. elif op == 'fund_user':
  297. await fund_user(await self.rpc)
  298. end_silence()
  299. return 'ok'
  300. class CmdTestEthdev(CmdTestBase, CmdTestShared, CmdTestEthdevMethods):
  301. 'Ethereum transacting, token deployment and tracking wallet operations'
  302. networks = ('eth', 'etc')
  303. passthru_opts = ('coin', 'daemon_id', 'eth_daemon_id', 'http_timeout', 'rpc_backend')
  304. tmpdir_nums = [22]
  305. color = True
  306. menu_prompt = 'efresh balance:\b'
  307. input_sels_prompt = 'to spend from: '
  308. bals = lambda self, k: {
  309. '1': [ ('98831F3A:E:1', '123.456')],
  310. '2': [ ('98831F3A:E:1', '123.456'), ('98831F3A:E:11', '1.234')],
  311. '3': [ ('98831F3A:E:1', '123.456'), ('98831F3A:E:11', '1.234'), ('98831F3A:E:21', '2.345')],
  312. '4': [ ('98831F3A:E:1', '100'),
  313. ('98831F3A:E:2', '23.45495'),
  314. ('98831F3A:E:11', '1.234'),
  315. ('98831F3A:E:21', '2.345')],
  316. '5': [ ('98831F3A:E:1', '100'),
  317. ('98831F3A:E:2', '23.45495'),
  318. ('98831F3A:E:11', '1.234'),
  319. ('98831F3A:E:21', '2.345'),
  320. (burn_addr + r'\s+non-MMGen', amt1)],
  321. '8': [ ('98831F3A:E:1', '0'),
  322. ('98831F3A:E:2', '23.45495'),
  323. ('98831F3A:E:11', vbal1),
  324. ('98831F3A:E:12', '99.99895'),
  325. ('98831F3A:E:21', '2.345'),
  326. (burn_addr + r'\s+non-MMGen', amt1)],
  327. '9': [ ('98831F3A:E:1', '0'),
  328. ('98831F3A:E:2', '23.45495'),
  329. ('98831F3A:E:11', vbal1),
  330. ('98831F3A:E:12', vbal2),
  331. ('98831F3A:E:21', '2.345'),
  332. (burn_addr + r'\s+non-MMGen', amt1)],
  333. '10': [ ('98831F3A:E:1', '0'),
  334. ('98831F3A:E:2', '23.0218'),
  335. ('98831F3A:E:3', '0.4321'),
  336. ('98831F3A:E:11', vbal1),
  337. ('98831F3A:E:12', vbal2),
  338. ('98831F3A:E:21', '2.345'),
  339. (burn_addr + r'\s+non-MMGen', amt1)]
  340. }[k]
  341. token_bals = lambda self, k: {
  342. '1': [ ('98831F3A:E:11', '1000', '1.234')],
  343. '2': [ ('98831F3A:E:11', '998.76544', vbal3),
  344. ('98831F3A:E:12', '1.23456', '0')],
  345. '3': [ ('98831F3A:E:11', '110.654317776666555545', vbal1),
  346. ('98831F3A:E:12', '1.23456', '0')],
  347. '4': [ ('98831F3A:E:11', '110.654317776666555545', vbal1),
  348. ('98831F3A:E:12', '1.23456', '0'),
  349. (burn_addr + r'\s+non-MMGen', amt2, amt1)],
  350. '5': [ ('98831F3A:E:11', '110.654317776666555545', vbal1),
  351. ('98831F3A:E:12', '1.23456', '99.99895'),
  352. (burn_addr + r'\s+non-MMGen', amt2, amt1)],
  353. '6': [ ('98831F3A:E:11', '110.654317776666555545', vbal1),
  354. ('98831F3A:E:12', '0', vbal2),
  355. ('98831F3A:E:13', '1.23456', '0'),
  356. (burn_addr + r'\s+non-MMGen', amt2, amt1)],
  357. '7': [ ('98831F3A:E:11', '67.444317776666555545', vbal9),
  358. ('98831F3A:E:12', '43.21', vbal2),
  359. ('98831F3A:E:13', '1.23456', '0'),
  360. (burn_addr + r'\s+non-MMGen', amt2, amt1)]
  361. }[k]
  362. token_bals_getbalance = lambda self, k: {
  363. '1': (vbal4, '999777.12345689012345678'),
  364. '2': ('111.888877776666555545', '888.111122223333444455')
  365. }[k]
  366. cmd_group_in = (
  367. ('setup', f'dev mode tests for coin {coin} (start daemon)'),
  368. ('subgroup.misc', []),
  369. ('subgroup.init', []),
  370. ('subgroup.msg', ['init']),
  371. ('subgroup.main', ['init']),
  372. ('subgroup.contract', ['main']),
  373. ('subgroup.token', ['contract']),
  374. ('subgroup.twexport', ['token']),
  375. ('subgroup.cached', ['token']),
  376. ('subgroup.view', ['cached']),
  377. ('subgroup.label', ['cached']),
  378. ('subgroup.remove', ['cached']),
  379. ('stop', 'stopping daemon'),
  380. )
  381. cmd_subgroups = {
  382. 'misc': (
  383. 'miscellaneous commands',
  384. ('daemon_version', 'mmgen-tool daemon_version'),
  385. ),
  386. 'init': (
  387. 'initializing wallets',
  388. ('wallet_upgrade1', 'upgrading the tracking wallet (v1 -> v2)'),
  389. ('wallet_upgrade2', 'upgrading the tracking wallet (v2 -> v3)'),
  390. ('addrgen', 'generating addresses'),
  391. ('addrimport', 'importing addresses'),
  392. ('addrimport_dev_addr', "importing dev faucet address 'Ox00a329c..'"),
  393. ('fund_dev_address', 'funding the default (Parity dev) address'),
  394. ('cli_dev_balance', 'mmgen-cli eth_getBalance'),
  395. ),
  396. 'msg': (
  397. 'message signing',
  398. ('msgsign_chk', "signing a message (low-level, check against 'eth_sign' RPC call)"),
  399. ('msgcreate', 'creating a message file'),
  400. ('msgsign', 'signing the message file'),
  401. ('msgverify', 'verifying the message file'),
  402. ('msgexport', 'exporting the message file data to JSON for third-party verifier'),
  403. ('msgverify_export', 'verifying the exported JSON data'),
  404. ('msgcreate_raw', 'creating a message file (--msghash-type=raw)'),
  405. ('msgsign_raw', 'signing the message file (msghash_type=raw)'),
  406. ('msgverify_raw', 'verifying the message file (msghash_type=raw)'),
  407. ('msgexport_raw', 'exporting the message file data to JSON (msghash_type=raw)'),
  408. ('msgverify_export_raw', 'verifying the exported JSON data (msghash_type=raw)'),
  409. ),
  410. 'main': (
  411. 'creating, signing, sending and bumping Ethereum transactions',
  412. ('txcreate1', 'creating a transaction (spend from dev address to address :1)'),
  413. ('txview1_raw', 'viewing the raw transaction'),
  414. ('txsign1', 'signing the transaction'),
  415. ('txview1_sig', 'viewing the signed transaction'),
  416. ('tx_status0_bad', 'getting the transaction status'),
  417. ('txsign1_ni', 'signing the transaction (non-interactive)'),
  418. ('etherscan_server_start','starting the Etherscan server'),
  419. ('txsend_etherscan_test','sending the transaction via Etherscan (simulation, with --test)'),
  420. ('txsend_etherscan', 'sending the transaction via Etherscan (simulation)'),
  421. ('etherscan_server_stop','stopping the Etherscan server'),
  422. ('txsend1', 'sending the transaction'),
  423. ('bal1', f'the {coin} balance'),
  424. ('txcreate2', 'creating a transaction (spend from dev address to address :11)'),
  425. ('txsign2', 'signing the transaction'),
  426. ('txsend2', 'sending the transaction'),
  427. ('bal2', f'the {coin} balance'),
  428. ('txcreate3', 'creating a transaction (spend from dev address to address :21)'),
  429. ('txsign3', 'signing the transaction'),
  430. ('txsend3', 'sending the transaction'),
  431. ('bal3', f'the {coin} balance'),
  432. ('tx_status1', 'getting the transaction status'),
  433. ('txcreate4', 'creating a transaction (spend from MMGen address, low TX fee)'),
  434. ('txbump', 'bumping the transaction fee'),
  435. ('txsign4', 'signing the transaction'),
  436. ('txsend4', 'sending the transaction'),
  437. ('tx_status1a', 'getting the transaction status'),
  438. ('bal4', f'the {coin} balance'),
  439. ('txcreate5', 'creating a transaction (fund burn address)'),
  440. ('txsign5', 'signing the transaction'),
  441. ('txsend5', 'sending the transaction'),
  442. ('addrimport_burn_addr', 'importing burn address'),
  443. ('bal5', f'the {coin} balance'),
  444. ('add_comment1', 'adding a UTF-8 label (zh)'),
  445. ('chk_comment1', 'checking the label'),
  446. ('add_comment2', 'adding a UTF-8 label (lat+cyr+gr)'),
  447. ('chk_comment2', 'checking the label'),
  448. ('remove_comment', 'removing the label'),
  449. ),
  450. 'contract': (
  451. 'creating and deploying ERC20 tokens',
  452. ('token_compile1', 'compiling ERC20 token #1'),
  453. ('token_deploy1a', 'deploying ERC20 token #1 (SafeMath)'),
  454. ('token_deploy1b', 'deploying ERC20 token #1 (Owned)'),
  455. ('token_deploy1c', 'deploying ERC20 token #1 (Token)'),
  456. ('tx_status2', 'getting the transaction status'),
  457. ('bal6', f'the {coin} balance'),
  458. ('token_compile2', 'compiling ERC20 token #2'),
  459. ('token_deploy2a', 'deploying ERC20 token #2 (SafeMath)'),
  460. ('token_deploy2b', 'deploying ERC20 token #2 (Owned)'),
  461. ('token_deploy2c', 'deploying ERC20 token #2 (Token)'),
  462. ),
  463. 'token': (
  464. 'creating, signing, sending and bumping ERC20 token transactions',
  465. ('token_fund_users', 'transferring token funds from dev to user'),
  466. ('token_user_bals', 'show balances after transfer'),
  467. ('token_addrgen', 'generating token addresses'),
  468. ('token_addrimport_badaddr1', 'importing token addresses (no token address)'),
  469. ('token_addrimport_badaddr2', 'importing token addresses (bad token address)'),
  470. ('token_addrimport_addr1', 'importing token addresses using token address (MM1)'),
  471. ('token_addrimport_addr2', 'importing token addresses using token address (MM2)'),
  472. ('token_addrimport_batch', 'importing token addresses (dummy batch mode) (MM1)'),
  473. ('token_addrimport_sym', 'importing token addresses using token symbol (MM2)'),
  474. ('bal7', f'the {coin} balance'),
  475. ('token_bal1', f'the {coin} balance and token balance'),
  476. ('token_txcreate1', 'creating a token transaction'),
  477. ('token_txview1_raw', 'viewing the raw transaction'),
  478. ('token_txsign1', 'signing the transaction'),
  479. ('token_txsend1', 'sending the transaction'),
  480. ('token_txview1_sig', 'viewing the signed transaction'),
  481. ('tx_status3', 'getting the transaction status'),
  482. ('token_bal2', f'the {coin} balance and token balance'),
  483. ('token_txcreate2', 'creating a token transaction (to burn address)'),
  484. ('token_txbump', 'bumping the transaction fee'),
  485. ('token_txsign2', 'signing the transaction'),
  486. ('token_txsend2', 'sending the transaction'),
  487. ('token_bal3', f'the {coin} balance and token balance'),
  488. ('del_dev_addr', 'deleting the dev address'),
  489. ('bal1_getbalance', f'the {coin} balance (getbalance)'),
  490. ('addrimport_token_burn_addr', 'importing the token burn address'),
  491. ('token_bal4', f'the {coin} balance and token balance'),
  492. ('token_bal_getbalance', 'the token balance (getbalance)'),
  493. ('txcreate_noamt', 'creating a transaction (full amount send)'),
  494. ('txsign_noamt', 'signing the transaction'),
  495. ('txsend_noamt', 'sending the transaction'),
  496. ('bal8', f'the {coin} balance'),
  497. ('token_bal5', 'the token balance'),
  498. ('token_txcreate_noamt', 'creating a token transaction (full amount send)'),
  499. ('token_txsign_noamt', 'signing the transaction'),
  500. ('token_txsend_noamt', 'sending the transaction'),
  501. ('bal9', f'the {coin} balance'),
  502. ('token_bal6', 'the token balance'),
  503. ('listaddresses1', 'listaddresses'),
  504. ('listaddresses2', 'listaddresses minconf=999999999 (ignored)'),
  505. ('listaddresses3', 'listaddresses sort=age (ignored)'),
  506. ('listaddresses4', 'listaddresses showempty=1 sort=age (ignored)'),
  507. ('token_listaddresses1', 'listaddresses --token=mm1'),
  508. ('token_listaddresses2', 'listaddresses --token=mm1 showempty=1'),
  509. ),
  510. 'twexport': (
  511. 'exporting and importing tracking wallet to JSON',
  512. ('twexport_noamt', 'exporting the tracking wallet (include_amts=0)'),
  513. ('twmove', 'moving the tracking wallet'),
  514. ('twimport', 'importing the tracking wallet'),
  515. ('twview7', 'twview (cached_balances=1)'),
  516. ('twview8', 'twview'),
  517. ('twexport', 'exporting the tracking wallet'),
  518. ('tw_chktotal', 'checking total value in tracking wallet dump'),
  519. ('twmove', 'moving the tracking wallet'),
  520. ('twimport', 'importing the tracking wallet'),
  521. ('twcompare', 'comparing imported tracking wallet with original'),
  522. ('edit_json_twdump', 'editing the tracking wallet JSON dump'),
  523. ('twmove', 'moving the tracking wallet'),
  524. ('twimport_nochksum', 'importing the edited tracking wallet JSON dump (ignore_checksum=1)'),
  525. ('token_listaddresses3', 'listaddresses --token=mm1 showempty=1'),
  526. ('token_listaddresses4', 'listaddresses --token=mm2 showempty=1'),
  527. ('twview9', 'twview (check balance)'),
  528. ),
  529. 'cached': (
  530. 'creating and sending transactions using cached balances',
  531. ('twview_cached_balances', 'twview (cached balances)'),
  532. ('token_twview_cached_balances', 'token twview (cached balances)'),
  533. ('txcreate_cached_balances', 'txcreate (cached balances)'),
  534. ('token_txcreate_cached_balances', 'token txcreate (cached balances)'),
  535. ('txdo_cached_balances', 'txdo (cached balances)'),
  536. ('txcreate_refresh_balances', 'refreshing balances'),
  537. ('bal10', f'the {coin} balance'),
  538. ('token_txdo_cached_balances', 'token txdo (cached balances)'),
  539. ('token_txcreate_refresh_balances', 'refreshing token balances'),
  540. ('token_bal7', 'the token balance'),
  541. ),
  542. 'view': (
  543. 'viewing addresses and unspent outputs',
  544. ('twview1', 'twview'),
  545. ('twview2', 'twview wide=1'),
  546. ('twview3', 'twview wide=1 sort=age (ignored)'),
  547. ('twview4', 'twview wide=1 minconf=999999999 (ignored)'),
  548. ('twview5', 'twview wide=1 minconf=0 (ignored)'),
  549. ('token_twview1', 'twview --token=mm1'),
  550. ('token_twview2', 'twview --token=mm1 wide=1'),
  551. ('token_twview3', 'twview --token=mm1 wide=1 sort=age (ignored)'),
  552. ),
  553. 'label': (
  554. 'creating, editing and removing labels',
  555. ('edit_comment1', f'adding label to addr #{del_addrs[0]} in {coin} tracking wallet (zh)'),
  556. ('edit_comment2', f'editing label for addr #{del_addrs[0]} in {coin} tracking wallet (zh)'),
  557. ('edit_comment3', f'adding label to addr #{del_addrs[1]} in {coin} tracking wallet (lat+cyr+gr)'),
  558. ('edit_comment4', f'removing label from addr #{del_addrs[0]} in {coin} tracking wallet'),
  559. ('token_edit_comment1', f'adding label to addr #{del_addrs[0]} in {coin} token tracking wallet'),
  560. ),
  561. 'remove': (
  562. 'removing addresses from tracking wallet',
  563. ('remove_addr1', f'removing addr #{del_addrs[0]} from {coin} tracking wallet'),
  564. ('twview6', 'twview (balance reduced after address removal)'),
  565. ('remove_addr2', f'removing addr #{del_addrs[1]} from {coin} tracking wallet'),
  566. ('token_remove_addr1', f'removing addr #{del_addrs[0]} from {coin} token tracking wallet'),
  567. ('token_remove_addr2', f'removing addr #{del_addrs[1]} from {coin} token tracking wallet'),
  568. ),
  569. }
  570. def __init__(self, cfg, trunner, cfgs, spawn):
  571. CmdTestBase.__init__(self, cfg, trunner, cfgs, spawn)
  572. if trunner is None:
  573. return
  574. global coin
  575. coin = cfg.coin
  576. self.eth_opts = [f'--outdir={self.tmpdir}', '--regtest=1', '--quiet']
  577. self.eth_opts_noquiet = [f'--outdir={self.tmpdir}', '--regtest=1']
  578. from mmgen.protocol import init_proto
  579. self.proto = init_proto(cfg, network_id=self.proto.coin+'_rt', need_amt=True)
  580. from mmgen.daemon import CoinDaemon
  581. self.daemon = CoinDaemon(cfg, network_id=self.proto.coin+'_rt', test_suite=True)
  582. if self.daemon.id == 'reth':
  583. global dfl_devkey, dfl_devaddr
  584. dfl_devkey, dfl_devaddr = get_reth_dev_keypair(cfg)
  585. set_vbals(self.daemon.id)
  586. self.using_solc = check_solc_ver()
  587. if not self.using_solc:
  588. omsg(yellow('Using precompiled contract data'))
  589. omsg(blue(f'Coin daemon {self.daemon.id!r} selected'))
  590. self.genesis_fn = joinpath(self.tmpdir, 'genesis.json')
  591. self.keystore_dir = os.path.relpath(joinpath(self.daemon.datadir, 'keystore'))
  592. write_to_file(
  593. joinpath(self.tmpdir, parity_devkey_fn),
  594. dfl_devkey+'\n')
  595. self.message = 'attack at dawn'
  596. self.spawn_env['MMGEN_BOGUS_SEND'] = ''
  597. @property
  598. async def rpc(self):
  599. from mmgen.rpc import rpc_init
  600. return await rpc_init(self.cfg, self.proto)
  601. def mining_delay(self): # workaround for mining race condition in dev mode
  602. if self.daemon.id == 'reth':
  603. time.sleep(1)
  604. async def setup(self):
  605. self.spawn(msg_only=True)
  606. d = self.daemon
  607. if not self.using_solc:
  608. subdir = 'reth' if d.id == 'reth' else 'geth'
  609. srcdir = os.path.join(self.tr.repo_root, 'test', 'ref', 'ethereum', 'bin', subdir)
  610. from shutil import copytree
  611. for _ in ('mm1', 'mm2'):
  612. copytree(os.path.join(srcdir, _), os.path.join(self.tmpdir, _))
  613. if d.id in ('geth', 'erigon'):
  614. self.genesis_setup(d)
  615. set_vt100()
  616. if d.id == 'erigon':
  617. self.write_to_tmpfile('signer_key', self.keystore_data['key']+'\n')
  618. d.usr_coind_args = [
  619. '--miner.sigfile={}'.format(os.path.join(self.tmpdir, 'signer_key')),
  620. '--miner.etherbase={}'.format(self.keystore_data['address'])]
  621. if d.id in ('geth', 'erigon'):
  622. imsg(' {:19} {}'.format('Cmdline:', ' '.join(e for e in d.start_cmd if not 'verbosity' in e)))
  623. if not self.cfg.no_daemon_autostart:
  624. if not d.id in ('geth', 'erigon'):
  625. d.stop(silent=True)
  626. d.remove_datadir()
  627. d.start(silent=self.tr.quiet)
  628. rpc = await self.rpc
  629. imsg(f'Daemon: {rpc.daemon.coind_name} v{rpc.daemon_version_str}')
  630. return 'ok'
  631. @property
  632. def keystore_data(self):
  633. if not hasattr(self, '_keystore_data'):
  634. wallet_fn = os.path.join( self.keystore_dir, os.listdir(self.keystore_dir)[0])
  635. from mmgen.proto.eth.misc import decrypt_geth_keystore
  636. key = decrypt_geth_keystore(
  637. cfg = self.cfg,
  638. wallet_fn = wallet_fn,
  639. passwd = b'')
  640. with open(wallet_fn) as fh:
  641. res = json.loads(fh.read())
  642. res.update( { 'key': key.hex()})
  643. self._keystore_data = res
  644. return self._keystore_data
  645. def genesis_setup(self, d):
  646. def make_key():
  647. pwfile = joinpath(self.tmpdir, 'account_passwd')
  648. write_to_file(pwfile, '')
  649. run(['rm', '-rf', self.keystore_dir])
  650. cmd = f'geth account new --password={pwfile} --lightkdf --keystore {self.keystore_dir}'
  651. cp = run(cmd.split(), stdout=PIPE, stderr=PIPE)
  652. if cp.returncode:
  653. die(1, cp.stderr.decode())
  654. def make_genesis(signer_addr, prealloc_addr):
  655. return {
  656. 'config': {
  657. 'chainId': 1337, # TODO: replace constant with var
  658. 'homesteadBlock': 0,
  659. 'eip150Block': 0,
  660. 'eip155Block': 0,
  661. 'eip158Block': 0,
  662. 'byzantiumBlock': 0,
  663. 'constantinopleBlock': 0,
  664. 'petersburgBlock': 0,
  665. 'istanbulBlock': 0,
  666. 'muirGlacierBlock': 0,
  667. 'berlinBlock': 0,
  668. 'londonBlock': 0,
  669. 'arrowGlacierBlock': 0,
  670. 'grayGlacierBlock': 0,
  671. 'shanghaiTime': 0,
  672. 'terminalTotalDifficulty': 0,
  673. 'terminalTotalDifficultyPassed': True,
  674. 'isDev': True
  675. },
  676. 'nonce': '0x0',
  677. 'timestamp': '0x0',
  678. 'extraData': '0x',
  679. 'gasLimit': '0xaf79e0',
  680. 'difficulty': '0x1',
  681. 'mixHash': '0x0000000000000000000000000000000000000000000000000000000000000000',
  682. 'coinbase': '0x0000000000000000000000000000000000000000',
  683. 'number': '0x0',
  684. 'gasUsed': '0x0',
  685. 'parentHash': '0x0000000000000000000000000000000000000000000000000000000000000000',
  686. 'baseFeePerGas': '0x3b9aca00',
  687. 'excessBlobGas': None,
  688. 'blobGasUsed': None,
  689. 'alloc': {
  690. prealloc_addr: { 'balance': hex(prealloc_amt.toWei())}
  691. }
  692. }
  693. def init_genesis(fn):
  694. cmd = f'{d.exec_fn} init --datadir {d.datadir} {fn}'
  695. cp = run( cmd.split(), stdout=PIPE, stderr=PIPE)
  696. if cp.returncode:
  697. die(1, cp.stderr.decode())
  698. d.stop(quiet=True)
  699. d.remove_datadir()
  700. imsg(cyan('Initializing Genesis Block:'))
  701. prealloc_amt = self.proto.coin_amt('1_000_000_000')
  702. make_key()
  703. signer_addr = self.keystore_data['address']
  704. self.write_to_tmpfile('signer_addr', signer_addr + '\n')
  705. imsg(f' Keystore: {self.keystore_dir}')
  706. imsg(f' Signer key: {self.keystore_data["key"]}')
  707. imsg(f' Signer address: {signer_addr}')
  708. imsg(f' Faucet: {dfl_devaddr} ({prealloc_amt} ETH)')
  709. imsg(f' Genesis block data: {self.genesis_fn}')
  710. genesis_data = make_genesis(signer_addr, dfl_devaddr)
  711. write_to_file( self.genesis_fn, json.dumps(genesis_data, indent=' ')+'\n')
  712. init_genesis(self.genesis_fn)
  713. def daemon_version(self):
  714. t = self.spawn('mmgen-tool', self.eth_opts + ['daemon_version'])
  715. t.expect('version')
  716. return t
  717. def cli_dev_balance(self):
  718. t = self.spawn(
  719. 'mmgen-cli',
  720. [f'--coin={self.proto.coin}', '--regtest=1', 'eth_getBalance', '0x'+dfl_devaddr, 'latest'])
  721. if self.daemon.id == 'geth':
  722. t.expect('0x33b2e3c91ec0e9113986000')
  723. elif self.daemon.id == 'reth':
  724. t.expect('0xd3c21bcecceda1000000')
  725. return t
  726. async def _wallet_upgrade(self, src_fn, expect1, expect2=None):
  727. if self.proto.coin == 'ETC':
  728. msg(f'skipping test {self.test_name!r} for ETC')
  729. return 'skip'
  730. from mmgen.tw.ctl import TwCtl
  731. twctl = await TwCtl(self.cfg, self.proto, no_wallet_init=True)
  732. from_fn = Path(ref_dir) / 'ethereum' / src_fn
  733. bak_fn = twctl.tw_dir / f'upgraded-{src_fn}'
  734. twctl.tw_dir.mkdir(mode=0o750, parents=True, exist_ok=True)
  735. dest = shutil.copy2(from_fn, twctl.tw_path)
  736. assert dest == twctl.tw_path, f'{dest} != {twctl.tw_path}'
  737. t = self.spawn('mmgen-tool', self.eth_opts + ['twview'])
  738. t.expect(expect1)
  739. if expect2:
  740. t.expect(expect2)
  741. t.read()
  742. twctl.tw_path.rename(bak_fn)
  743. return t
  744. async def wallet_upgrade1(self):
  745. return await self._wallet_upgrade('tracking-wallet-v1.json', 'accounts field', 'network field')
  746. async def wallet_upgrade2(self):
  747. return await self._wallet_upgrade('tracking-wallet-v2.json', 'token params field', 'network field')
  748. def addrgen(self):
  749. return self._addrgen()
  750. def addrimport(
  751. self,
  752. ext = '21-23]{}.regtest.addrs',
  753. expect = '9/9',
  754. add_args = [],
  755. bad_input = False,
  756. exit_val = None):
  757. ext = ext.format('-α' if self.cfg.debug_utf8 else '')
  758. fn = self.get_file_with_ext(ext, no_dot=True, delete=False)
  759. t = self.spawn('mmgen-addrimport', ['--regtest=1'] + add_args + [fn], exit_val=exit_val)
  760. if bad_input:
  761. return t
  762. t.expect('Importing')
  763. t.expect(expect)
  764. return t
  765. def addrimport_one_addr(self, addr=None, extra_args=[]):
  766. t = self.spawn('mmgen-addrimport', ['--regtest=1', '--quiet', f'--address={addr}'] + extra_args)
  767. t.expect('OK')
  768. return t
  769. def addrimport_dev_addr(self):
  770. return self.addrimport_one_addr(addr=dfl_devaddr)
  771. def addrimport_burn_addr(self):
  772. return self.addrimport_one_addr(addr=burn_addr)
  773. def txcreate(
  774. self,
  775. args = [],
  776. menu = [],
  777. acct = '1',
  778. caller = 'txcreate',
  779. interactive_fee = '50G',
  780. fee_info_data = ('0.00105', '50'),
  781. no_read = False,
  782. print_listing = True,
  783. bad_input_sels = True,
  784. tweaks = []):
  785. fee_info_pat = r'\D{}\D.*{c} .*\D{}\D.*gas price in Gwei'.format(*fee_info_data, c=self.proto.coin)
  786. t = self.spawn(f'mmgen-{caller}', self.eth_opts + ['-B'] + args)
  787. if print_listing:
  788. t.expect(r'add \[l\]abel, .*?:.', 'p', regex=True)
  789. t.written_to_file('Account balances listing')
  790. t = self.txcreate_ui_common(
  791. t,
  792. menu = menu,
  793. caller = caller,
  794. input_sels_prompt = 'to spend from',
  795. inputs = acct,
  796. file_desc = 'transaction',
  797. bad_input_sels = bad_input_sels,
  798. interactive_fee = interactive_fee,
  799. fee_info_pat = fee_info_pat,
  800. fee_desc = 'transaction fee or gas price',
  801. add_comment = tx_comment_jp,
  802. tweaks = tweaks)
  803. if not no_read:
  804. t.read()
  805. return t
  806. def txsign(self, ni=False, ext='{}.regtest.rawtx', add_args=[], dev_send=False, has_label=True):
  807. ext = ext.format('-α' if self.cfg.debug_utf8 else '')
  808. keyfile = joinpath(self.tmpdir, parity_devkey_fn)
  809. txfile = self.get_file_with_ext(ext, no_dot=True)
  810. t = self.spawn(
  811. 'mmgen-txsign',
  812. self.eth_opts
  813. + ['--rpc-host=bad_host'] # ETH signing must work without RPC
  814. + ([], ['--yes'])[ni]
  815. + ([f'--keys-from-file={keyfile}'] if dev_send else [])
  816. + add_args
  817. + [txfile, dfl_words_file],
  818. no_passthru_opts = ['coin'])
  819. return self.txsign_ui_common(t, ni=ni, has_label=has_label)
  820. def txsend(
  821. self,
  822. ext = '{}.regtest.sigtx',
  823. add_args = [],
  824. test = False,
  825. return_early = False,
  826. has_label = True,
  827. env = {}):
  828. ext = ext.format('-α' if self.cfg.debug_utf8 else '')
  829. txfile = self.get_file_with_ext(ext, no_dot=True)
  830. t = self.spawn(
  831. 'mmgen-txsend',
  832. self.eth_opts + add_args + [txfile],
  833. no_passthru_opts = ['coin'],
  834. env = env)
  835. if return_early:
  836. return t
  837. self.txsend_ui_common(
  838. t,
  839. quiet = not self.cfg.debug,
  840. bogus_send = False,
  841. test = test,
  842. has_label = has_label)
  843. return t
  844. def txview(self, ext_fs):
  845. ext = ext_fs.format('-α' if self.cfg.debug_utf8 else '')
  846. txfile = self.get_file_with_ext(ext, no_dot=True)
  847. return self.spawn('mmgen-tool', ['--verbose', 'txview', txfile])
  848. def fund_dev_address(self):
  849. """
  850. For Erigon, fund the default (Parity) dev address from the Erigon dev address
  851. For the others, send a junk TX to keep block counts equal for all daemons
  852. """
  853. dt = namedtuple('data', ['devkey_fn', 'dest', 'amt'])
  854. d = dt(parity_devkey_fn, burn_addr2, '1')
  855. t = self.txcreate(
  856. args = self.eth_opts_noquiet + [
  857. f'--keys-from-file={joinpath(self.tmpdir, d.devkey_fn)}',
  858. f'{d.dest},{d.amt}',
  859. ],
  860. menu = ['a', 'r'],
  861. caller = 'txdo',
  862. acct = '1',
  863. no_read = True)
  864. self._do_confirm_send(t, quiet=not self.cfg.debug, sure=False)
  865. t.read()
  866. self.get_file_with_ext('sigtx', delete_all=True)
  867. return t
  868. def txcreate1(self):
  869. # include one invalid keypress 'X' -- see EthereumTwUnspentOutputs.key_mappings
  870. menu = ['a', 'd', 'r', 'M', 'X', 'e', 'm', 'm']
  871. args = ['98831F3A:E:1,123.456']
  872. return self.txcreate(args=args, menu=menu, acct='1', tweaks=['confirm_non_mmgen'])
  873. def txview1_raw(self):
  874. return self.txview(ext_fs='{}.regtest.rawtx')
  875. def txsign1(self):
  876. return self.txsign(add_args=['--use-internal-keccak-module'], dev_send=True)
  877. def tx_status0_bad(self):
  878. return self.tx_status(
  879. ext = '{}.regtest.sigtx',
  880. expect_str = 'neither in mempool nor blockchain',
  881. exit_val = 1)
  882. def txsign1_ni(self):
  883. return self.txsign(ni=True, dev_send=True)
  884. def etherscan_server_start(self):
  885. self.spawn(msg_only=True)
  886. etherscan_server.start()
  887. return 'ok'
  888. def txsend_etherscan_test(self):
  889. return self.txsend(add_args=['--tx-proxy=ether', '--test'], test='tx_proxy')
  890. def txsend_etherscan(self):
  891. return self.txsend(add_args=['--tx-proxy=ethersc'])
  892. def etherscan_server_stop(self):
  893. self.spawn(msg_only=True)
  894. etherscan_server.stop()
  895. return 'ok'
  896. def txsend1(self):
  897. return self.txsend()
  898. def txview1_sig(self): # do after send so that TxID is displayed
  899. return self.txview(ext_fs='{}.regtest.sigtx')
  900. def bal1(self):
  901. return self.bal(n='1')
  902. def txcreate2(self):
  903. args = ['98831F3A:E:11,1.234']
  904. return self.txcreate(args=args, acct='10', tweaks=['confirm_non_mmgen'])
  905. def txsign2(self):
  906. return self.txsign(ni=True, ext='1.234,50000]{}.regtest.rawtx', dev_send=True)
  907. def txsend2(self):
  908. return self.txsend(ext='1.234,50000]{}.regtest.sigtx')
  909. def bal2(self):
  910. return self.bal(n='2')
  911. def txcreate3(self):
  912. args = ['98831F3A:E:21,2.345']
  913. return self.txcreate(args=args, acct='10', tweaks=['confirm_non_mmgen'])
  914. def txsign3(self):
  915. return self.txsign(ni=True, ext='2.345,50000]{}.regtest.rawtx', dev_send=True)
  916. def txsend3(self):
  917. return self.txsend(ext='2.345,50000]{}.regtest.sigtx')
  918. def bal3(self):
  919. return self.bal(n='3')
  920. def tx_status(self, ext, expect_str, expect_str2='', exit_val=0):
  921. self.mining_delay()
  922. ext = ext.format('-α' if self.cfg.debug_utf8 else '')
  923. txfile = self.get_file_with_ext(ext, no_dot=True)
  924. t = self.spawn(
  925. 'mmgen-txsend',
  926. self.eth_opts + ['--status', txfile],
  927. no_passthru_opts = ['coin'],
  928. exit_val = exit_val)
  929. t.expect(expect_str)
  930. if expect_str2:
  931. t.expect(expect_str2)
  932. return t
  933. def tx_status1(self):
  934. return self.tx_status(ext='2.345,50000]{}.regtest.sigtx', expect_str='has 1 confirmation')
  935. def tx_status1a(self):
  936. return self.tx_status(ext='2.345,50000]{}.regtest.sigtx', expect_str='has 2 confirmations')
  937. async def msgsign_chk(self): # NB: Geth only!
  938. def create_signature_mmgen():
  939. key = self.keystore_data['key']
  940. imsg(f'Key: {key}')
  941. from mmgen.proto.eth.misc import ec_sign_message_with_privkey
  942. return ec_sign_message_with_privkey(self.cfg, self.message, bytes.fromhex(key), 'eth_sign')
  943. async def create_signature_rpc():
  944. addr = self.read_from_tmpfile('signer_addr').strip()
  945. imsg(f'Address: {addr}')
  946. rpc = await self.rpc
  947. return await rpc.call(
  948. 'eth_sign',
  949. '0x' + addr,
  950. '0x' + self.message.encode().hex())
  951. if not self.daemon.id == 'geth':
  952. return 'skip'
  953. self.spawn(msg_only=True)
  954. sig = '0x' + create_signature_mmgen()
  955. sig_chk = await create_signature_rpc()
  956. # Compare signatures
  957. imsg(f'Message: {self.message}')
  958. imsg(f'Signature: {sig}')
  959. cmp_or_die(sig, sig_chk, 'message signatures')
  960. imsg('Geth and MMGen signatures match')
  961. return 'ok'
  962. def msgcreate(self, add_args=[]):
  963. t = self.spawn('mmgen-msg', self.eth_opts_noquiet + add_args + ['create', self.message, '98831F3A:E:1'])
  964. t.written_to_file('Unsigned message data')
  965. return t
  966. def msgsign(self):
  967. fn = get_file_with_ext(self.tmpdir, 'rawmsg.json')
  968. t = self.spawn('mmgen-msg', self.eth_opts_noquiet + ['sign', fn, dfl_words_file])
  969. t.written_to_file('Signed message data')
  970. return t
  971. def msgverify(self, fn=None, msghash_type='eth_sign'):
  972. fn = fn or get_file_with_ext(self.tmpdir, 'sigmsg.json')
  973. t = self.spawn('mmgen-msg', self.eth_opts_noquiet + ['verify', fn])
  974. t.expect(msghash_type)
  975. t.expect('1 signature verified')
  976. return t
  977. def msgexport(self):
  978. fn = get_file_with_ext(self.tmpdir, 'sigmsg.json')
  979. t = self.spawn('mmgen-msg', self.eth_opts_noquiet + ['export', fn])
  980. t.written_to_file('Signature data')
  981. return t
  982. def msgverify_export(self):
  983. return self.msgverify(
  984. fn = os.path.join(self.tmpdir, 'signatures.json'))
  985. def msgcreate_raw(self):
  986. get_file_with_ext(self.tmpdir, 'rawmsg.json', delete_all=True)
  987. return self.msgcreate(add_args=['--msghash-type=raw'])
  988. def msgsign_raw(self):
  989. get_file_with_ext(self.tmpdir, 'sigmsg.json', delete_all=True)
  990. return self.msgsign()
  991. def msgverify_raw(self):
  992. return self.msgverify(msghash_type='raw')
  993. def msgexport_raw(self):
  994. get_file_with_ext(self.tmpdir, 'signatures.json', no_dot=True, delete_all=True)
  995. return self.msgexport()
  996. def msgverify_export_raw(self):
  997. return self.msgverify(
  998. fn = os.path.join(self.tmpdir, 'signatures.json'),
  999. msghash_type = 'raw')
  1000. def txcreate4(self):
  1001. return self.txcreate(
  1002. args = ['98831F3A:E:2,23.45495'],
  1003. acct = '1',
  1004. interactive_fee = '40G',
  1005. fee_info_data = ('0.00084', '40'))
  1006. def txbump(self):
  1007. return self._txbump(fee='50G', ext=',40000]{}.regtest.rawtx')
  1008. def txsign4(self):
  1009. return self.txsign(ext='.45495,50000]{}.regtest.rawtx', add_args=['--no-quiet', '--no-yes'])
  1010. def txsend4(self):
  1011. return self.txsend(ext='.45495,50000]{}.regtest.sigtx')
  1012. def bal4(self):
  1013. return self.bal(n='4')
  1014. def txcreate5(self):
  1015. args = [burn_addr + ','+amt1]
  1016. return self.txcreate(args=args, acct='10', tweaks=['confirm_non_mmgen'])
  1017. def txsign5(self):
  1018. return self.txsign(ni=True, ext=amt1+',50000]{}.regtest.rawtx', dev_send=True)
  1019. def txsend5(self):
  1020. return self.txsend(ext=amt1+',50000]{}.regtest.sigtx')
  1021. def bal5(self):
  1022. return self.bal(n='5')
  1023. def bal(self, n):
  1024. self.mining_delay()
  1025. t = self.spawn('mmgen-tool', self.eth_opts + ['twview', 'wide=1'])
  1026. text = t.read(strip_color=True)
  1027. for addr, amt in self.bals(n):
  1028. pat = r'\D{}\D.*\D{}\D'.format(addr, amt.replace('.', r'\.'))
  1029. assert re.search(pat, text), pat
  1030. ss = f'Total {self.proto.coin}:'
  1031. assert re.search(ss, text), ss
  1032. return t
  1033. def token_bal(self, n=None):
  1034. self.mining_delay()
  1035. t = self.spawn('mmgen-tool', self.eth_opts + ['--token=mm1', 'twview', 'wide=1'])
  1036. text = t.read(strip_color=True)
  1037. for addr, _amt1, _amt2 in self.token_bals(n):
  1038. pat = fr'{addr}\b.*\D{_amt1}\D.*\b{_amt2}\D'
  1039. assert re.search(pat, text), pat
  1040. ss = 'Total MM1:'
  1041. assert re.search(ss, text), ss
  1042. return t
  1043. def bal_getbalance(self, sid, idx, etc_adj=False, extra_args=[]):
  1044. bal1 = self.token_bals_getbalance(idx)[0]
  1045. bal2 = self.token_bals_getbalance(idx)[1]
  1046. bal1 = Decimal(bal1)
  1047. t = self.spawn('mmgen-tool', self.eth_opts + extra_args + ['getbalance'])
  1048. t.expect(rf'{sid}:.*'+str(bal1), regex=True)
  1049. t.expect(r'Non-MMGen:.*'+bal2, regex=True)
  1050. total = strip_ansi_escapes(t.expect_getend(rf'TOTAL {self.proto.coin}')).split()[0]
  1051. assert Decimal(bal1) + Decimal(bal2) == Decimal(total)
  1052. return t
  1053. def add_comment(self, comment, addr='98831F3A:E:3'):
  1054. t = self.spawn('mmgen-tool', self.eth_opts + ['add_label', addr, comment])
  1055. t.expect('Added label.*in tracking wallet', regex=True)
  1056. return t
  1057. def chk_comment(self, comment_pat, addr='98831F3A:E:3'):
  1058. t = self.spawn('mmgen-tool', self.eth_opts + ['listaddresses', 'all_labels=1'])
  1059. t.expect(fr'{addr}\b.*{comment_pat}', regex=True)
  1060. return t
  1061. def add_comment1(self):
  1062. return self.add_comment(comment=tw_comment_zh)
  1063. def chk_comment1(self):
  1064. return self.chk_comment(comment_pat=tw_comment_zh[:3])
  1065. def add_comment2(self):
  1066. return self.add_comment(comment=tw_comment_lat_cyr_gr)
  1067. def chk_comment2(self):
  1068. return self.chk_comment(comment_pat=tw_comment_lat_cyr_gr[:3])
  1069. def remove_comment(self, addr='98831F3A:E:3'):
  1070. t = self.spawn('mmgen-tool', self.eth_opts + ['remove_label', addr])
  1071. t.expect('Removed label.*in tracking wallet', regex=True)
  1072. return t
  1073. def token_compile(self, token_data={}):
  1074. odir = joinpath(self.tmpdir, token_data['symbol'].lower())
  1075. if not self.using_solc:
  1076. imsg(f'Using precompiled contract data in {odir}')
  1077. return 'skip' if os.path.exists(odir) else False
  1078. self.spawn(msg_only=True)
  1079. cmd_args = [f'--{k}={v}' for k, v in list(token_data.items())]
  1080. imsg("Compiling solidity token contract '{}' with 'solc'".format(token_data['symbol']))
  1081. try:
  1082. os.mkdir(odir)
  1083. except:
  1084. pass
  1085. cmd = [
  1086. 'python3',
  1087. 'scripts/create-token.py',
  1088. '--coin=' + self.proto.coin,
  1089. '--outdir=' + odir
  1090. ] + cmd_args + [self.proto.checksummed_addr(dfl_devaddr)]
  1091. imsg('Executing: {}'.format(' '.join(cmd)))
  1092. cp = run(cmd, stdout=DEVNULL, stderr=PIPE)
  1093. if cp.returncode != 0:
  1094. rmsg('solc failed with the following output:')
  1095. die(2, cp.stderr.decode())
  1096. imsg('ERC20 token {!r} compiled'.format(token_data['symbol']))
  1097. return 'ok'
  1098. def token_compile1(self):
  1099. token_data = {'name':'MMGen Token 1', 'symbol':'MM1', 'supply':10**26, 'decimals':18}
  1100. return self.token_compile(token_data)
  1101. def token_compile2(self):
  1102. token_data = {'name':'MMGen Token 2', 'symbol':'MM2', 'supply':10**18, 'decimals':10}
  1103. return self.token_compile(token_data)
  1104. async def get_tx_receipt(self, txid):
  1105. if self.daemon.id in ('geth', 'reth'): # workaround for mining race condition in dev mode
  1106. await asyncio.sleep(1 if self.daemon.id == 'reth' else 0.5)
  1107. from mmgen.tx import NewTX
  1108. tx = await NewTX(cfg=self.cfg, proto=self.proto, target='tx')
  1109. tx.rpc = await self.rpc
  1110. res = await tx.get_receipt(txid)
  1111. if not res:
  1112. die(1, f'Error getting receipt for transaction {txid}')
  1113. imsg(f'Gas sent: {res.gas_sent.hl():<9} {(res.gas_sent*res.gas_price).hl2(encl="()")}')
  1114. imsg(f'Gas used: {res.gas_used.hl():<9} {(res.gas_used*res.gas_price).hl2(encl="()")}')
  1115. imsg(f'Gas price: {res.gas_price.hl()}')
  1116. if res.gas_used == res.gas_sent:
  1117. omsg(yellow('Warning: all gas was used!'))
  1118. return res
  1119. async def token_deploy1a(self):
  1120. return await self._token_deploy_math(num=1)
  1121. async def token_deploy1b(self):
  1122. return await self._token_deploy_owned(num=1)
  1123. async def token_deploy1c(self):
  1124. return await self._token_deploy_token(num=1)
  1125. async def token_deploy2a(self): # test create, sign, send:
  1126. return await self._token_deploy_math(num=2, mmgen_cmd='txcreate')
  1127. async def token_deploy2b(self):
  1128. return await self._token_deploy_owned(num=2)
  1129. async def token_deploy2c(self):
  1130. return await self._token_deploy_token(num=2)
  1131. def tx_status2(self):
  1132. return self.tx_status(
  1133. ext = self.proto.coin+'[0,7000]{}.regtest.sigtx',
  1134. expect_str = 'successfully executed')
  1135. def bal6(self):
  1136. return self.bal5()
  1137. def token_fund_users(self):
  1138. return self._token_transfer_ops(op='fund_user', mm_idxs=[11, 21])
  1139. def token_user_bals(self):
  1140. return self._token_transfer_ops(op='show_bals', mm_idxs=[11, 21])
  1141. def token_addrgen(self):
  1142. return self._token_addrgen(mm_idxs=[11, 21], naddrs=3)
  1143. def token_addrimport_badaddr1(self):
  1144. t = self.addrimport(
  1145. ext = '[11-13]{}.regtest.addrs',
  1146. add_args = ['--token=abc'],
  1147. bad_input = True,
  1148. exit_val = 2)
  1149. t.expect('could not be resolved')
  1150. return t
  1151. def token_addrimport_badaddr2(self):
  1152. t = self.addrimport(
  1153. ext = '[11-13]{}.regtest.addrs',
  1154. add_args = ['--token='+'00deadbeef'*4],
  1155. bad_input = True,
  1156. exit_val = 2)
  1157. t.expect('could not be resolved')
  1158. return t
  1159. def token_addrimport_addr1(self):
  1160. return self._token_addrimport('token_addr1', '11-13', expect='3/3')
  1161. def token_addrimport_addr2(self):
  1162. return self._token_addrimport('token_addr2', '21-23', expect='3/3')
  1163. def token_addrimport_batch(self):
  1164. return self._token_addrimport('token_addr1', '11-13', expect='3 addresses', extra_args=['--batch'])
  1165. def token_addrimport_sym(self):
  1166. return self.addrimport(
  1167. ext = '[21-23]{}.regtest.addrs',
  1168. expect = '3/3',
  1169. add_args = ['--token=MM2'])
  1170. def bal7(self):
  1171. return self.bal5()
  1172. def token_bal1(self):
  1173. return self.token_bal(n='1')
  1174. def token_txcreate(
  1175. self,
  1176. args = [],
  1177. token = '',
  1178. inputs = '1',
  1179. fee = '50G',
  1180. file_desc = 'Unsigned transaction'):
  1181. return self.txcreate_ui_common(
  1182. self.spawn(
  1183. 'mmgen-txcreate',
  1184. self.eth_opts + [f'--token={token}', '-B', f'--fee={fee}'] + args),
  1185. menu = [],
  1186. inputs = inputs,
  1187. input_sels_prompt = 'to spend from',
  1188. add_comment = tx_comment_lat_cyr_gr,
  1189. file_desc = file_desc)
  1190. def token_txsign(self, ext='', token='', add_args=[], ni=True):
  1191. return self.txsign(ni=ni, ext=ext, add_args=add_args)
  1192. def token_txsend(self, ext='', token=''):
  1193. return self.txsend(ext=ext)
  1194. def token_txcreate1(self):
  1195. return self.token_txcreate(args=['98831F3A:E:12,1.23456'], token='mm1')
  1196. def token_txview1_raw(self):
  1197. return self.txview(ext_fs='1.23456,50000]{}.regtest.rawtx')
  1198. def token_txsign1(self):
  1199. return self.token_txsign(
  1200. ext = '1.23456,50000]{}.regtest.rawtx',
  1201. token = 'mm1',
  1202. ni = False,
  1203. add_args = ['--no-quiet', '--no-yes'])
  1204. def token_txsend1(self):
  1205. return self.token_txsend(ext='1.23456,50000]{}.regtest.sigtx', token='mm1')
  1206. def token_txview1_sig(self):
  1207. return self.txview(ext_fs='1.23456,50000]{}.regtest.sigtx')
  1208. def tx_status3(self):
  1209. return self.tx_status(
  1210. ext = '1.23456,50000]{}.regtest.sigtx',
  1211. expect_str = 'successfully executed',
  1212. expect_str2 = 'has 1 confirmation')
  1213. def token_bal2(self):
  1214. return self.token_bal(n='2')
  1215. def twview(self, args=[], expect_str='', tool_args=[]):
  1216. t = self.spawn('mmgen-tool', self.eth_opts + args + ['twview'] + tool_args)
  1217. if expect_str:
  1218. t.expect(expect_str, regex=True)
  1219. return t
  1220. def token_txcreate2(self):
  1221. return self.token_txcreate(args=[burn_addr+', '+amt2], token='mm1')
  1222. def token_txbump(self):
  1223. return self._txbump(ext=amt2+',50000]{}.regtest.rawtx', fee='56G', add_opts=['--token=MM1'])
  1224. def token_txsign2(self):
  1225. return self.token_txsign(ext=amt2+',50000]{}.regtest.rawtx', token='mm1')
  1226. def token_txsend2(self):
  1227. return self.token_txsend(ext=amt2+',50000]{}.regtest.sigtx', token='mm1')
  1228. def token_bal3(self):
  1229. return self.token_bal(n='3')
  1230. def del_dev_addr(self):
  1231. t = self.spawn('mmgen-tool', self.eth_opts + ['remove_address', dfl_devaddr])
  1232. t.expect(f"'{dfl_devaddr}' deleted")
  1233. return t
  1234. def bal1_getbalance(self):
  1235. return self.bal_getbalance(dfl_sid, '1', etc_adj=True)
  1236. def addrimport_token_burn_addr(self):
  1237. return self.addrimport_one_addr(addr=burn_addr, extra_args=['--token=mm1'])
  1238. def token_bal4(self):
  1239. return self.token_bal(n='4')
  1240. def token_bal_getbalance(self):
  1241. return self.bal_getbalance(dfl_sid, '2', extra_args=['--token=mm1'])
  1242. def txcreate_noamt(self):
  1243. return self.txcreate(args=['98831F3A:E:12'])
  1244. def txsign_noamt(self):
  1245. return self.txsign(ext='99.99895,50000]{}.regtest.rawtx')
  1246. def txsend_noamt(self):
  1247. return self.txsend(ext='99.99895,50000]{}.regtest.sigtx')
  1248. def bal8(self):
  1249. return self.bal(n='8')
  1250. def token_bal5(self):
  1251. return self.token_bal(n='5')
  1252. def token_txcreate_noamt(self):
  1253. return self.token_txcreate(args=['98831F3A:E:13'], token='mm1', inputs='2', fee='51G')
  1254. def token_txsign_noamt(self):
  1255. return self.token_txsign(ext='1.23456,51000]{}.regtest.rawtx', token='mm1')
  1256. def token_txsend_noamt(self):
  1257. return self.token_txsend(ext='1.23456,51000]{}.regtest.sigtx', token='mm1')
  1258. def bal9(self):
  1259. return self.bal(n='9')
  1260. def token_bal6(self):
  1261. return self.token_bal(n='6')
  1262. def listaddresses(self, args=[], tool_args=['all_labels=1']):
  1263. return self.spawn('mmgen-tool', self.eth_opts + args + ['listaddresses'] + tool_args)
  1264. def listaddresses1(self):
  1265. return self.listaddresses()
  1266. def listaddresses2(self):
  1267. return self.listaddresses(tool_args=['minconf=999999999'])
  1268. def listaddresses3(self):
  1269. return self.listaddresses(tool_args=['sort=amt', 'reverse=1'])
  1270. def listaddresses4(self):
  1271. return self.listaddresses(tool_args=['sort=age', 'showempty=0'])
  1272. def token_listaddresses1(self):
  1273. return self.listaddresses(args=['--token=mm1'])
  1274. def token_listaddresses2(self):
  1275. return self.listaddresses(args=['--token=mm1'], tool_args=['showempty=1'])
  1276. def token_listaddresses3(self):
  1277. return self.listaddresses(args=['--token=mm1'], tool_args=['showempty=0'])
  1278. def token_listaddresses4(self):
  1279. return self.listaddresses(args=['--token=mm2'], tool_args=['sort=age', 'reverse=1'])
  1280. def twview_cached_balances(self):
  1281. return self.twview(args=['--cached-balances'])
  1282. def token_twview_cached_balances(self):
  1283. return self.twview(args=['--token=mm1', '--cached-balances'])
  1284. def txcreate_cached_balances(self):
  1285. args = ['--fee=20G', '--cached-balances', '98831F3A:E:3, 0.1276']
  1286. return self.txcreate(args=args, acct='2')
  1287. def token_txcreate_cached_balances(self):
  1288. args=['--cached-balances', '--fee=12G', '98831F3A:E:12, 1.2789']
  1289. return self.token_txcreate(args=args, token='mm1')
  1290. def txdo_cached_balances(
  1291. self,
  1292. acct = '2',
  1293. fee_info_data = ('0.00105', '50'),
  1294. add_args = ['98831F3A:E:3,0.4321']):
  1295. t = self.txcreate(
  1296. args = ['--fee=20G', '--cached-balances'] + add_args + [dfl_words_file],
  1297. acct = acct,
  1298. caller = 'txdo',
  1299. fee_info_data = fee_info_data,
  1300. no_read = True)
  1301. self._do_confirm_send(t, quiet=not self.cfg.debug, sure=False)
  1302. return t
  1303. def txcreate_refresh_balances(self):
  1304. return self._txcreate_refresh_balances(
  1305. bals = ['2', '3'],
  1306. args = ['-B', '--cached-balances', '-i'],
  1307. total = vbal5,
  1308. adj_total = True,
  1309. total_coin = None)
  1310. def _txcreate_refresh_balances(self, bals, args, total, adj_total, total_coin):
  1311. self.mining_delay()
  1312. if total_coin is None:
  1313. total_coin = self.proto.coin
  1314. t = self.spawn('mmgen-txcreate', self.eth_opts + args)
  1315. for n in bals:
  1316. t.expect('[R]efresh balance:\b', 'R')
  1317. t.expect(' main menu): ', n+'\n')
  1318. t.expect('Is this what you want? (y/N): ', 'y')
  1319. t.expect('[R]efresh balance:\b', 'q')
  1320. t.expect(rf'Total unspent:.*\D{total}\D.*{total_coin}', regex=True)
  1321. return t
  1322. def bal10(self):
  1323. return self.bal(n='10')
  1324. def token_txdo_cached_balances(self):
  1325. return self.txdo_cached_balances(
  1326. acct = '1',
  1327. fee_info_data = ('0.0026', '50'),
  1328. add_args = ['--token=mm1', '98831F3A:E:12,43.21'])
  1329. def token_txcreate_refresh_balances(self):
  1330. return self._txcreate_refresh_balances(
  1331. bals = ['1', '2'],
  1332. args = ['--token=mm1', '-B', '--cached-balances', '-i'],
  1333. total = '1000',
  1334. adj_total = False,
  1335. total_coin = 'MM1')
  1336. def token_bal7(self):
  1337. return self.token_bal(n='7')
  1338. def twview1(self):
  1339. return self.twview()
  1340. def twview2(self):
  1341. return self.twview(tool_args=['wide=1'])
  1342. def twview3(self):
  1343. return self.twview(tool_args=['wide=1', 'sort=age'])
  1344. def twview4(self):
  1345. return self.twview(tool_args=['wide=1', 'minconf=999999999'])
  1346. def twview5(self):
  1347. return self.twview(tool_args=['wide=1', 'minconf=0'])
  1348. def twview6(self):
  1349. return self.twview(expect_str=vbal7)
  1350. def twview7(self):
  1351. return self.twview(args=['--cached-balances'])
  1352. def twview8(self):
  1353. return self.twview()
  1354. def twview9(self):
  1355. return self.twview(args=['--cached-balances'], expect_str=vbal6)
  1356. def token_twview1(self):
  1357. return self.twview(args=['--token=mm1'])
  1358. def token_twview2(self):
  1359. return self.twview(args=['--token=mm1'], tool_args=['wide=1'])
  1360. def token_twview3(self):
  1361. return self.twview(args=['--token=mm1'], tool_args=['wide=1', 'sort=age'])
  1362. def edit_comment(
  1363. self,
  1364. out_num,
  1365. args = [],
  1366. action = 'l',
  1367. comment_text = None,
  1368. changed = False,
  1369. pexpect_spawn = None):
  1370. t = self.spawn('mmgen-txcreate', self.eth_opts + args + ['-B', '-i'], pexpect_spawn=pexpect_spawn)
  1371. t.expect(self.menu_prompt, 'M')
  1372. t.expect(self.menu_prompt, action)
  1373. t.expect(r'return to main menu): ', out_num+'\n')
  1374. for p, r in (
  1375. ('Enter label text.*: ', comment_text+'\n') if comment_text is not None else (r'\(y/N\): ', 'y'),
  1376. (r'\(y/N\): ', 'y') if comment_text == Ctrl_U else (None, None),
  1377. ):
  1378. if p:
  1379. t.expect(p, r, regex=True)
  1380. m = (
  1381. 'Label for account #{} edited' if changed else
  1382. 'Account #{} removed' if action == 'D' else
  1383. 'Label added to account #{}' if comment_text and comment_text != Ctrl_U else
  1384. 'Label removed from account #{}')
  1385. t.expect(m.format(out_num))
  1386. t.expect(self.menu_prompt, 'M')
  1387. t.expect(self.menu_prompt, 'q')
  1388. t.expect('Total unspent:')
  1389. return t
  1390. def edit_comment1(self):
  1391. return self.edit_comment(out_num=del_addrs[0], comment_text=tw_comment_zh[:3])
  1392. def edit_comment2(self):
  1393. spawn = not sys.platform == 'win32'
  1394. return self.edit_comment(
  1395. out_num = del_addrs[0],
  1396. comment_text = tw_comment_zh[3:],
  1397. changed = True,
  1398. pexpect_spawn = spawn)
  1399. def edit_comment3(self):
  1400. return self.edit_comment(out_num=del_addrs[1], comment_text=tw_comment_lat_cyr_gr)
  1401. def edit_comment4(self):
  1402. if self.skip_for_win('no pexpect_spawn'):
  1403. return 'skip'
  1404. return self.edit_comment(out_num=del_addrs[0], comment_text=Ctrl_U, pexpect_spawn=True)
  1405. def token_edit_comment1(self):
  1406. return self.edit_comment(out_num='1', comment_text='Token label #1', args=['--token=mm1'])
  1407. def remove_addr1(self):
  1408. return self.edit_comment(out_num=del_addrs[0], action='D')
  1409. def remove_addr2(self):
  1410. return self.edit_comment(out_num=del_addrs[1], action='D')
  1411. def token_remove_addr1(self):
  1412. return self.edit_comment(out_num=del_addrs[0], args=['--token=mm1'], action='D')
  1413. def token_remove_addr2(self):
  1414. return self.edit_comment(out_num=del_addrs[1], args=['--token=mm1'], action='D')
  1415. def twexport_noamt(self):
  1416. return self.twexport(add_args=['include_amts=0'])
  1417. def twexport(self, add_args=[]):
  1418. t = self.spawn('mmgen-tool', self.eth_opts + ['twexport'] + add_args)
  1419. t.written_to_file('JSON data')
  1420. return t
  1421. async def twmove(self):
  1422. self.spawn(msg_only=True)
  1423. from mmgen.tw.ctl import TwCtl
  1424. twctl = await TwCtl(self.cfg, self.proto, no_wallet_init=True)
  1425. imsg('Moving tracking wallet')
  1426. fn_bak = twctl.tw_path.with_suffix('.bak.json')
  1427. fn_bak.unlink(missing_ok=True)
  1428. twctl.tw_path.rename(fn_bak)
  1429. return 'ok'
  1430. def twimport(self, add_args=[], expect_str=None):
  1431. from mmgen.tw.json import TwJSON
  1432. fn = joinpath(self.tmpdir, TwJSON.Base(self.cfg, self.proto).dump_fn)
  1433. t = self.spawn('mmgen-tool', self.eth_opts_noquiet + ['twimport', fn] + add_args)
  1434. t.expect('(y/N): ', 'y')
  1435. if expect_str:
  1436. t.expect(expect_str)
  1437. t.written_to_file('tracking wallet data')
  1438. return t
  1439. def twimport_nochksum(self):
  1440. return self.twimport(add_args=['ignore_checksum=true'], expect_str='ignoring incorrect checksum')
  1441. def tw_chktotal(self):
  1442. self.spawn(msg_only=True)
  1443. from mmgen.tw.json import TwJSON
  1444. fn = joinpath(self.tmpdir, TwJSON.Base(self.cfg, self.proto).dump_fn)
  1445. res = json.loads(read_from_file(fn))
  1446. cmp_or_die(res['data']['value'], vbal6, 'value in tracking wallet JSON dump')
  1447. return 'ok'
  1448. async def twcompare(self):
  1449. self.spawn(msg_only=True)
  1450. from mmgen.tw.ctl import TwCtl
  1451. twctl = await TwCtl(self.cfg, self.proto, no_wallet_init=True)
  1452. fn = twctl.tw_path
  1453. fn_bak = fn.with_suffix('.bak.json')
  1454. imsg('Comparing imported tracking wallet with original')
  1455. data = [json.dumps(json.loads(f.read_text()), sort_keys=True) for f in (fn, fn_bak)]
  1456. cmp_or_die(*data, 'tracking wallets')
  1457. return 'ok'
  1458. def edit_json_twdump(self):
  1459. self.spawn(msg_only=True)
  1460. from mmgen.tw.json import TwJSON
  1461. fn = TwJSON.Base(self.cfg, self.proto).dump_fn
  1462. text = json.loads(self.read_from_tmpfile(fn))
  1463. token_addr = self.read_from_tmpfile('token_addr2').strip()
  1464. text['data']['entries']['tokens'][token_addr][2][3] = f'edited comment [фубар] [{gr_uc}]'
  1465. self.write_to_tmpfile(fn, json.dumps(text, indent=4))
  1466. return 'ok'
  1467. def stop(self):
  1468. self.spawn(msg_only=True)
  1469. if not self.cfg.no_daemon_stop:
  1470. if not stop_test_daemons(self.proto.coin+'_rt', remove_datadir=True):
  1471. return False
  1472. set_vt100()
  1473. return 'ok'