ct_ethdev.py 52 KB

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