swap.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. #!/usr/bin/env python3
  2. """
  3. test.modtest_d.swap: swap unit tests for the MMGen suite
  4. """
  5. from mmgen.color import cyan
  6. from mmgen.cfg import Config
  7. from mmgen.amt import UniAmt
  8. from mmgen.swap.proto.thorchain import SwapCfg, SwapAsset, Memo
  9. from mmgen.protocol import init_proto
  10. from ..include.common import cfg, vmsg, make_burn_addr
  11. class unit_tests:
  12. def cfg(self, name, ut, desc='Swap configuration'):
  13. for tl_arg, tl_chk, si_arg in (
  14. (None, None, None),
  15. ('1', UniAmt('1'), None),
  16. ('33', UniAmt('33'), 7),
  17. ('2%', 0.98, 14),
  18. ('-2%', 1.02, 1),
  19. ('3.333%', 0.96667, 1),
  20. ('-3.333%', 1.03333, 3),
  21. ('1.2345', UniAmt('1.2345'), 10)):
  22. cfg_data = {
  23. 'trade_limit': tl_arg,
  24. 'stream_interval': None if si_arg is None else str(si_arg)}
  25. sc = SwapCfg(Config(cfg_data))
  26. vmsg(f' trade_limit: {tl_arg} => {sc.trade_limit}')
  27. vmsg(f' stream_interval: {si_arg} => {sc.stream_interval}')
  28. assert sc.trade_limit == tl_chk
  29. assert sc.stream_interval == sc.si.dfl if si_arg is None else si_arg
  30. assert sc.stream_quantity == 0
  31. vmsg('\n Testing error handling')
  32. def bad1():
  33. SwapCfg(Config({'trade_limit': 'x'}))
  34. def bad2():
  35. SwapCfg(Config({'trade_limit': '1.23x'}))
  36. def bad3():
  37. SwapCfg(Config({'stream_interval': 30}))
  38. def bad4():
  39. SwapCfg(Config({'stream_interval': 0}))
  40. def bad5():
  41. SwapCfg(Config({'stream_interval': 'x'}))
  42. ut.process_bad_data((
  43. ('bad1', 'SwapCfgValueError', 'invalid parameter', bad1),
  44. ('bad2', 'SwapCfgValueError', 'invalid parameter', bad2),
  45. ('bad3', 'SwapCfgValueError', 'invalid parameter', bad3),
  46. ('bad4', 'SwapCfgValueError', 'invalid parameter', bad4),
  47. ('bad5', 'SwapCfgValueError', 'invalid parameter', bad5),
  48. ), pfx='')
  49. return True
  50. def asset(self, name, ut, desc='SwapAsset class'):
  51. for name, full_name, memo_name, chain, tokensym, direction in (
  52. ('BTC', 'BTC.BTC', 'b', 'BTC', None, 'recv'),
  53. ('LTC', 'LTC.LTC', 'l', 'LTC', None, 'recv'),
  54. ('BCH', 'BCH.BCH', 'c', 'BCH', None, 'recv'),
  55. ('ETH.USDT', 'ETH.USDT', 'ETH.USDT', 'ETH', 'USDT', 'recv'),
  56. ):
  57. a = SwapAsset(name, direction)
  58. vmsg(f' {a.name}')
  59. assert a.name == name
  60. assert a.full_name == full_name
  61. assert a.direction == direction
  62. assert a.tokensym == tokensym
  63. assert a.chain == chain
  64. assert a.memo_asset_name == memo_name
  65. return True
  66. def memo(self, name, ut, desc='Swap transaction memo'):
  67. for coin, addrtype, asset_name, token in (
  68. ('ltc', 'bech32', 'LTC', None),
  69. ('bch', 'compressed', 'BCH', None),
  70. ('eth', None, 'ETH', None),
  71. ('eth', None, 'ETH.USDT', 'USDT'),
  72. ):
  73. proto = init_proto(cfg, coin, tokensym=token, need_amt=True)
  74. addr = make_burn_addr(proto, addrtype)
  75. asset = SwapAsset(asset_name, 'recv')
  76. vmsg(f'\nTesting asset {cyan(asset_name)}:')
  77. for limit, limit_chk, si, suf in (
  78. ('123.4567', 12340000000, None, '1234e7/3/0'),
  79. ('1.234567', 123400000, 1, '1234e5/1/0'),
  80. ('0.01234567', 1234000, 10, '1234e3/10/0'),
  81. ('0.00012345', 12345, 20, '12345/20/0'),
  82. (None, 0, 3, '0/3/0'),
  83. ):
  84. vmsg('\nTesting memo initialization:')
  85. swap_cfg = SwapCfg(Config({'trade_limit': limit, 'stream_interval': si}))
  86. m = Memo(
  87. swap_cfg,
  88. proto,
  89. asset,
  90. addr,
  91. trade_limit = None if limit is None else UniAmt(limit))
  92. vmsg(f'str(memo): {m}')
  93. vmsg(f'repr(memo): {m!r}')
  94. vmsg(f'limit: {limit}')
  95. assert str(m).endswith(':' + suf), f'{m} doesn’t end with {suf}'
  96. p = Memo.parse(m)
  97. limit_dec = UniAmt(p.trade_limit, from_unit='satoshi')
  98. vmsg(f'limit_dec: {limit_dec.hl()}')
  99. vmsg('\nTesting memo parsing:')
  100. from pprint import pformat
  101. vmsg(pformat(p._asdict()))
  102. assert p.proto == 'THORChain'
  103. assert p.function == 'SWAP'
  104. assert p.asset.chain == coin.upper()
  105. assert p.asset.coin == token or coin.upper()
  106. assert p.address == addr.views[addr.view_pref]
  107. assert p.trade_limit == limit_chk
  108. assert p.stream_interval == si or swap_cfg.si.dfl, f'{p.stream_interval} != {swap_cfg.si.dfl}'
  109. assert p.stream_quantity == 0 # auto
  110. vmsg('\nTesting is_partial_memo():')
  111. for vec in (
  112. str(m),
  113. 'SWAP:xyz',
  114. '=:xyz',
  115. 's:xyz',
  116. 'a:xz',
  117. '+:xz',
  118. 'WITHDRAW:xz',
  119. 'LOAN+:xz:x:x',
  120. 'TRADE-:xz:x:x',
  121. 'BOND:xz',
  122. ):
  123. vmsg(f' pass: {vec}')
  124. assert Memo.is_partial_memo(vec.encode('ascii')), vec
  125. for vec in (
  126. '=',
  127. 'swap',
  128. 'swap:',
  129. 'swap:abc',
  130. 'SWAP:a',
  131. ):
  132. vmsg(f' fail: {vec}')
  133. assert not Memo.is_partial_memo(vec.encode('ascii')), vec
  134. vmsg('\nTesting error handling:')
  135. def bad(s):
  136. return lambda: Memo.parse(s)
  137. def bad10():
  138. coin = 'BTC'
  139. proto = init_proto(cfg, coin, need_amt=True)
  140. addr = make_burn_addr(proto, 'C')
  141. asset = SwapAsset(coin, 'send')
  142. Memo(swap_cfg, proto, asset, addr, trade_limit=None)
  143. def bad11():
  144. SwapAsset('XYZ', 'send')
  145. def bad12():
  146. SwapAsset('DOGE', 'send')
  147. ut.process_bad_data((
  148. ('bad1', 'SwapMemoParseError', 'must contain', bad('x')),
  149. ('bad2', 'SwapMemoParseError', 'must contain', bad('y:z:x')),
  150. ('bad3', 'SwapMemoParseError', 'function abbrev', bad('z:l:foobar:0/3/0')),
  151. ('bad4', 'SwapAssetError', 'unrecognized', bad('=:x:foobar:0/3/0')),
  152. ('bad5', 'SwapMemoParseError', 'failed to parse', bad('=:l:foobar:n')),
  153. ('bad6', 'SwapMemoParseError', 'invalid specifier', bad('=:l:foobar:x/3/0')),
  154. ('bad7', 'SwapMemoParseError', 'extra', bad('=:l:foobar:0/3/0:x')),
  155. ('bad10', 'AssertionError', 'recv', bad10),
  156. ('bad11', 'SwapAssetError', 'unrecognized', bad11),
  157. ('bad12', 'SwapAssetError', 'unsupported', bad12),
  158. ), pfx='')
  159. return True