swap.py 5.1 KB

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