THORChain ERC20 token swap support
Examples:
# List available assets:
$ mmgen-swaptxcreate -S
# Create a Tether-to-LTC swap transaction for autosigning, connecting to the
# swap quote server via Tor:
$ mmgen-swaptxcreate --autosign --proxy=localhost:9050 ETH.USDT 1000 LTC
# After signing, send the transaction via public Etherscan proxy over Tor:
$ mmgen-txsend --autosign --quiet --tx-proxy=etherscan --proxy=localhost:9050
# After sending, check the transaction status and receipt:
$ mmgen-txsend --autosign --verbose --status
# Create a Tether-to-DAI swap transaction, with explicit destination account:
$ mmgen-swaptxcreate ETH.USDT 1000 ETH.DAI E:01234ABC:3
Testing:
$ test/cmdtest.py -e ethswap
This commit is contained in:
parent
c449e4d4d9
commit
ff28d39a3c
23 changed files with 371 additions and 35 deletions
|
|
@ -16,6 +16,7 @@ include test/*/*.py
|
||||||
include test/*/*/*.py
|
include test/*/*/*.py
|
||||||
include test/ref/*
|
include test/ref/*
|
||||||
include test/ref/*/*
|
include test/ref/*/*
|
||||||
|
include test/ref/*/*/*
|
||||||
include test/ref/*/*/*/*
|
include test/ref/*/*/*/*
|
||||||
include test/overlay/fakemods/mmgen/*.py
|
include test/overlay/fakemods/mmgen/*.py
|
||||||
include test/overlay/fakemods/mmgen/*/*.py
|
include test/overlay/fakemods/mmgen/*/*.py
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
15.1.dev30
|
15.1.dev31
|
||||||
|
|
|
||||||
|
|
@ -73,4 +73,21 @@ EXAMPLES:
|
||||||
Check whether the funds have arrived in the BCH destination wallet:
|
Check whether the funds have arrived in the BCH destination wallet:
|
||||||
|
|
||||||
$ mmgen-tool --coin=bch --bch-rpc-host=gemini twview minconf=0
|
$ mmgen-tool --coin=bch --bch-rpc-host=gemini twview minconf=0
|
||||||
|
|
||||||
|
Create a Tether-to-LTC swap transaction for autosigning, connecting to the
|
||||||
|
swap quote server via Tor:
|
||||||
|
|
||||||
|
$ {gc.prog_name} --autosign --proxy=localhost:9050 ETH.USDT 1000 LTC
|
||||||
|
|
||||||
|
After signing, send the transaction via public Etherscan proxy over Tor:
|
||||||
|
|
||||||
|
$ mmgen-txsend --autosign --quiet --tx-proxy=etherscan --proxy=localhost:9050
|
||||||
|
|
||||||
|
After sending, check the transaction status:
|
||||||
|
|
||||||
|
$ mmgen-txsend --autosign --verbose --status
|
||||||
|
|
||||||
|
Create a Tether-to-DAI swap transaction, with explicit destination account:
|
||||||
|
|
||||||
|
$ {gc.prog_name} ETH.USDT 1000 ETH.DAI E:01234ABC:3
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -176,3 +176,23 @@ class ResolvedToken(Token, metaclass=AsyncInit):
|
||||||
if not self.decimals:
|
if not self.decimals:
|
||||||
die('TokenNotInBlockchain', f'Token {addr!r} not in blockchain')
|
die('TokenNotInBlockchain', f'Token {addr!r} not in blockchain')
|
||||||
self.base_unit = Decimal('10') ** -self.decimals
|
self.base_unit = Decimal('10') ** -self.decimals
|
||||||
|
|
||||||
|
# Tokens: First approve router to spend tokens from user: asset.approve(router,amount).
|
||||||
|
# Then call router.depositWithExpiry(inbound_address, asset, amount, memo, expiry).
|
||||||
|
# Asset is the token contract address. Amount should be in native asset decimals
|
||||||
|
# (eg 1e18 for most tokens). Do not swap to smart contract addresses.
|
||||||
|
class THORChainRouterContract(Token):
|
||||||
|
|
||||||
|
def create_deposit_with_expiry_data(self, inbound_addr, asset_addr, amt, memo, expiry):
|
||||||
|
assert isinstance(memo, bytes)
|
||||||
|
assert isinstance(expiry, int)
|
||||||
|
memo_chunks = len(memo) // 32 + bool(len(memo) % 32)
|
||||||
|
return ( # Method ID: 0x44bc937b
|
||||||
|
self.create_method_id('depositWithExpiry(address,address,uint256,string,uint256)')
|
||||||
|
+ inbound_addr.rjust(64, '0') # 32 bytes
|
||||||
|
+ asset_addr.rjust(64, '0') # 32 bytes
|
||||||
|
+ '{:064x}'.format(int(amt / self.base_unit)) # 32 bytes
|
||||||
|
+ '{:064x}'.format(32 * 5) # 32 bytes (memo offset)
|
||||||
|
+ '{:064x}'.format(expiry) # 32 bytes
|
||||||
|
+ '{:064x}'.format(len(memo)) # dynamic arg
|
||||||
|
+ memo.hex().ljust(64 * memo_chunks, '0'))
|
||||||
|
|
|
||||||
|
|
@ -141,7 +141,7 @@ class EthereumTwCtl(TwCtl):
|
||||||
|
|
||||||
async def sym2addr(self, sym):
|
async def sym2addr(self, sym):
|
||||||
for addr in self.data['tokens']:
|
for addr in self.data['tokens']:
|
||||||
if self.data['tokens'][addr]['params']['symbol'] == sym.upper():
|
if self.data['tokens'][addr]['params']['symbol'].upper() == sym.upper():
|
||||||
return addr
|
return addr
|
||||||
|
|
||||||
def get_token_param(self, token, param):
|
def get_token_param(self, token, param):
|
||||||
|
|
|
||||||
|
|
@ -111,7 +111,7 @@ class Base(TxBase):
|
||||||
f'{d[5]}: invalid swap memo in serialized data')
|
f'{d[5]}: invalid swap memo in serialized data')
|
||||||
|
|
||||||
class TokenBase(Base):
|
class TokenBase(Base):
|
||||||
dfl_gas = 52000
|
dfl_gas = 75000
|
||||||
contract_desc = 'token contract'
|
contract_desc = 'token contract'
|
||||||
|
|
||||||
def check_serialized_integrity(self):
|
def check_serialized_integrity(self):
|
||||||
|
|
@ -126,8 +126,8 @@ class TokenBase(Base):
|
||||||
assert d[4] == b'', f'{d[4]}: non-empty amount field in token transaction in serialized data'
|
assert d[4] == b'', f'{d[4]}: non-empty amount field in token transaction in serialized data'
|
||||||
|
|
||||||
data = d[5].hex()
|
data = d[5].hex()
|
||||||
assert data[:8] == 'a9059cbb', (
|
assert data[:8] == ('095ea7b3' if self.is_swap else 'a9059cbb'), (
|
||||||
f'{data[:8]}: invalid MethodID for op ‘transfer’ in serialized data')
|
f'{data[:8]}: invalid MethodID for op ‘{self.token_op}’ in serialized data')
|
||||||
assert data[32:72] == o['token_to'], (
|
assert data[32:72] == o['token_to'], (
|
||||||
f'{data[32:72]}: invalid ‘token_to‘ address in serialized data')
|
f'{data[32:72]}: invalid ‘token_to‘ address in serialized data')
|
||||||
assert TokenAmt(
|
assert TokenAmt(
|
||||||
|
|
@ -135,3 +135,20 @@ class TokenBase(Base):
|
||||||
decimals = o['decimals'],
|
decimals = o['decimals'],
|
||||||
from_unit = 'atomic') == o['amt'], (
|
from_unit = 'atomic') == o['amt'], (
|
||||||
f'{data[72:]}: invalid amt in serialized data')
|
f'{data[72:]}: invalid amt in serialized data')
|
||||||
|
|
||||||
|
if self.is_swap:
|
||||||
|
d = rlp.decode(bytes.fromhex(self.serialized2))
|
||||||
|
data = d[5].hex()
|
||||||
|
assert data[:8] == '44bc937b', (
|
||||||
|
f'{data[:8]}: invalid MethodID in router TX serialized data')
|
||||||
|
assert data[32:72] == self.token_vault_addr, (
|
||||||
|
f'{data[32:72]}: invalid vault address in router TX serialized data')
|
||||||
|
|
||||||
|
memo = bytes.fromhex(data[392:])[:len(self.swap_memo)]
|
||||||
|
assert memo == self.swap_memo.encode(), (
|
||||||
|
f'{memo}: invalid swap memo in router TX serialized data')
|
||||||
|
assert TokenAmt(
|
||||||
|
int(data[136:200], 16),
|
||||||
|
decimals = o['decimals'],
|
||||||
|
from_unit = 'atomic') == o['amt'], (
|
||||||
|
f'{data[136:200]}: invalid amt in router TX serialized data')
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ proto.eth.tx.info: Ethereum transaction info class
|
||||||
|
|
||||||
from ....tx.info import TxInfo
|
from ....tx.info import TxInfo
|
||||||
from ....util import fmt, pp_fmt
|
from ....util import fmt, pp_fmt
|
||||||
from ....color import pink, yellow, blue
|
from ....color import pink, yellow, blue, cyan
|
||||||
from ....addr import MMGenID
|
from ....addr import MMGenID
|
||||||
|
|
||||||
class TxInfo(TxInfo):
|
class TxInfo(TxInfo):
|
||||||
|
|
@ -34,7 +34,7 @@ class TxInfo(TxInfo):
|
||||||
return ' ' + (io.mmid.hl() if io.mmid else MMGenID.hlc(nonmm_str))
|
return ' ' + (io.mmid.hl() if io.mmid else MMGenID.hlc(nonmm_str))
|
||||||
fs = """
|
fs = """
|
||||||
From: {f}{f_mmid}
|
From: {f}{f_mmid}
|
||||||
To: {t}{t_mmid}
|
{toaddr} {t}{t_mmid}{tvault}
|
||||||
Amount: {a} {c}
|
Amount: {a} {c}
|
||||||
Gas price: {g} Gwei
|
Gas price: {g} Gwei
|
||||||
Start gas: {G} Kwei
|
Start gas: {G} Kwei
|
||||||
|
|
@ -44,10 +44,13 @@ class TxInfo(TxInfo):
|
||||||
t = tx.txobj
|
t = tx.txobj
|
||||||
td = t['data']
|
td = t['data']
|
||||||
to_addr = t[self.to_addr_key]
|
to_addr = t[self.to_addr_key]
|
||||||
|
tokenswap = tx.is_swap and tx.is_token
|
||||||
return fs.format(
|
return fs.format(
|
||||||
f = t['from'].hl(0),
|
f = t['from'].hl(0),
|
||||||
t = to_addr.hl(0) if to_addr else blue('None'),
|
t = to_addr.hl(0) if to_addr else blue('None'),
|
||||||
a = t['amt'].hl(),
|
a = t['amt'].hl(),
|
||||||
|
toaddr = ('Router:' if tokenswap else 'To:').ljust(8),
|
||||||
|
tvault = (f'\nVault: {cyan(tx.token_vault_addr)}' if tokenswap else ''),
|
||||||
n = t['nonce'].hl(),
|
n = t['nonce'].hl(),
|
||||||
d = blue('None') if not td else '{}... ({} bytes)'.format(td[:40], len(td)//2),
|
d = blue('None') if not td else '{}... ({} bytes)'.format(td[:40], len(td)//2),
|
||||||
m = pink(tx.swap_memo) if tx.is_swap else None,
|
m = pink(tx.swap_memo) if tx.is_swap else None,
|
||||||
|
|
@ -55,7 +58,7 @@ class TxInfo(TxInfo):
|
||||||
g = yellow(tx.pretty_fmt_fee(t['gasPrice'].to_unit('Gwei'))),
|
g = yellow(tx.pretty_fmt_fee(t['gasPrice'].to_unit('Gwei'))),
|
||||||
G = yellow(tx.pretty_fmt_fee(t['startGas'].to_unit('Kwei'))),
|
G = yellow(tx.pretty_fmt_fee(t['startGas'].to_unit('Kwei'))),
|
||||||
f_mmid = mmid_disp(tx.inputs[0]),
|
f_mmid = mmid_disp(tx.inputs[0]),
|
||||||
t_mmid = mmid_disp(tx.outputs[0]) if tx.outputs else '') + '\n\n'
|
t_mmid = mmid_disp(tx.outputs[0]) if tx.outputs and not tokenswap else '') + '\n\n'
|
||||||
|
|
||||||
def format_abs_fee(self, iwidth, /, *, color=None):
|
def format_abs_fee(self, iwidth, /, *, color=None):
|
||||||
return self.tx.fee.fmt(iwidth, color=color) + (' (max)' if self.tx.txobj['data'] else '')
|
return self.tx.fee.fmt(iwidth, color=color) + (' (max)' if self.tx.txobj['data'] else '')
|
||||||
|
|
|
||||||
|
|
@ -216,6 +216,8 @@ class TokenNew(TokenBase, New):
|
||||||
o['token_addr'] = t.addr
|
o['token_addr'] = t.addr
|
||||||
o['decimals'] = t.decimals
|
o['decimals'] = t.decimals
|
||||||
o['token_to'] = o['to']
|
o['token_to'] = o['to']
|
||||||
|
if self.is_swap:
|
||||||
|
o['expiry'] = self.quote_data.data['expiry']
|
||||||
|
|
||||||
def update_change_output(self, funds_left):
|
def update_change_output(self, funds_left):
|
||||||
if self.outputs[0].is_chg:
|
if self.outputs[0].is_chg:
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ proto.eth.tx.new_swap: Ethereum new swap transaction class
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from ....tx.new_swap import NewSwap as TxNewSwap
|
from ....tx.new_swap import NewSwap as TxNewSwap
|
||||||
from .new import New
|
from .new import New, TokenNew
|
||||||
|
|
||||||
class NewSwap(New, TxNewSwap):
|
class NewSwap(New, TxNewSwap):
|
||||||
desc = 'Ethereum swap transaction'
|
desc = 'Ethereum swap transaction'
|
||||||
|
|
@ -34,3 +34,10 @@ class NewSwap(New, TxNewSwap):
|
||||||
@property
|
@property
|
||||||
def vault_output(self):
|
def vault_output(self):
|
||||||
return self.outputs[0]
|
return self.outputs[0]
|
||||||
|
|
||||||
|
class TokenNewSwap(TokenNew, NewSwap):
|
||||||
|
desc = 'Ethereum token swap transaction'
|
||||||
|
|
||||||
|
def update_vault_addr(self, c):
|
||||||
|
self.token_vault_addr = self.proto.coin_addr(c.inbound_address)
|
||||||
|
return super().update_vault_addr(c, addr='router')
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ from ....tx import unsigned as TxBase
|
||||||
from ....util import msg, msg_r, die
|
from ....util import msg, msg_r, die
|
||||||
from ....obj import CoinTxID, ETHNonce, Int, HexStr
|
from ....obj import CoinTxID, ETHNonce, Int, HexStr
|
||||||
from ....addr import CoinAddr, ContractAddr
|
from ....addr import CoinAddr, ContractAddr
|
||||||
from ..contract import Token
|
from ..contract import Token, THORChainRouterContract
|
||||||
from .completed import Completed, TokenCompleted
|
from .completed import Completed, TokenCompleted
|
||||||
|
|
||||||
class Unsigned(Completed, TxBase.Unsigned):
|
class Unsigned(Completed, TxBase.Unsigned):
|
||||||
|
|
@ -110,14 +110,32 @@ class TokenUnsigned(TokenCompleted, Unsigned):
|
||||||
o['token_addr'] = ContractAddr(self.proto, d['token_addr'])
|
o['token_addr'] = ContractAddr(self.proto, d['token_addr'])
|
||||||
o['decimals'] = Int(d['decimals'])
|
o['decimals'] = Int(d['decimals'])
|
||||||
o['token_to'] = o['to']
|
o['token_to'] = o['to']
|
||||||
|
if self.is_swap:
|
||||||
|
o['expiry'] = Int(d['expiry'])
|
||||||
|
|
||||||
async def do_sign(self, o, wif):
|
async def do_sign(self, o, wif):
|
||||||
t = Token(self.cfg, self.proto, o['token_addr'], decimals=o['decimals'])
|
t = Token(self.cfg, self.proto, o['token_addr'], decimals=o['decimals'])
|
||||||
tdata = t.create_transfer_data(o['to'], o['amt'], op='transfer')
|
tdata = t.create_transfer_data(o['to'], o['amt'], op=self.token_op)
|
||||||
tx_in = t.make_tx_in(gas=self.gas, gasPrice=o['gasPrice'], nonce=o['nonce'], data=tdata)
|
tx_in = t.make_tx_in(gas=self.gas, gasPrice=o['gasPrice'], nonce=o['nonce'], data=tdata)
|
||||||
res = await t.txsign(tx_in, wif, o['from'], chain_id=o['chainId'])
|
res = await t.txsign(tx_in, wif, o['from'], chain_id=o['chainId'])
|
||||||
self.serialized = res.txhex
|
self.serialized = res.txhex
|
||||||
self.coin_txid = res.txid
|
self.coin_txid = res.txid
|
||||||
|
if self.is_swap:
|
||||||
|
c = THORChainRouterContract(self.cfg, self.proto, o['to'], decimals=o['decimals'])
|
||||||
|
cdata = c.create_deposit_with_expiry_data(
|
||||||
|
self.token_vault_addr,
|
||||||
|
o['token_addr'],
|
||||||
|
o['amt'],
|
||||||
|
self.swap_memo.encode(),
|
||||||
|
o['expiry'])
|
||||||
|
tx_in = c.make_tx_in(
|
||||||
|
gas = self.gas * (7.8 if self.cfg.test_suite else 2),
|
||||||
|
gasPrice = o['gasPrice'],
|
||||||
|
nonce = o['nonce'] + 1,
|
||||||
|
data = cdata)
|
||||||
|
res = await t.txsign(tx_in, wif, o['from'], chain_id=o['chainId'])
|
||||||
|
self.serialized2 = res.txhex
|
||||||
|
self.coin_txid2 = res.txid
|
||||||
|
|
||||||
class AutomountUnsigned(TxBase.AutomountUnsigned, Unsigned):
|
class AutomountUnsigned(TxBase.AutomountUnsigned, Unsigned):
|
||||||
pass
|
pass
|
||||||
|
|
|
||||||
|
|
@ -24,9 +24,44 @@ class THORChainSwapAsset(SwapAsset):
|
||||||
'ETH': _ad('Ethereum', 'ETH', None, 'e', True),
|
'ETH': _ad('Ethereum', 'ETH', None, 'e', True),
|
||||||
'DOGE': _ad('Dogecoin', 'DOGE', None, 'd', False),
|
'DOGE': _ad('Dogecoin', 'DOGE', None, 'd', False),
|
||||||
'RUNE': _ad('Rune (THORChain)', 'RUNE', 'THOR.RUNE', 'r', False),
|
'RUNE': _ad('Rune (THORChain)', 'RUNE', 'THOR.RUNE', 'r', False),
|
||||||
|
'ETH.AAVE': _ad('Aave (ETH)', None, 'ETH.AAVE', None, True),
|
||||||
|
'ETH.DAI': _ad('Sky Dollar (USDS) (ETH)', None, 'ETH.DAI', None, True),
|
||||||
|
'ETH.DPI': _ad('DeFi Pulse Index (ETH)', None, 'ETH.DPI', None, True),
|
||||||
|
'ETH.FOX': _ad('ShapeShift FOX (ETH)', None, 'ETH.FOX', None, True),
|
||||||
|
'ETH.GUSD': _ad('Gemini Dollar (ETH)', None, 'ETH.GUSD', None, True),
|
||||||
|
'ETH.LINK': _ad('Chainlink (ETH)', None, 'ETH.LINK', None, True),
|
||||||
|
'ETH.LUSD': _ad('Liquity USD (ETH)', None, 'ETH.LUSD', None, True),
|
||||||
|
'ETH.SNX': _ad('Synthetix (ETH)', None, 'ETH.SNX', None, True),
|
||||||
|
'ETH.TGT': _ad('THORWallet (ETH)', None, 'ETH.TGT', None, True),
|
||||||
|
'ETH.THOR': _ad('THORSwap (ETH)', None, 'ETH.THOR', None, True),
|
||||||
|
'ETH.USDC': _ad('USDC (ETH)', None, 'ETH.USDC', None, True),
|
||||||
|
'ETH.USDP': _ad('Pax Dollar (ETH)', None, 'ETH.USDP', None, True),
|
||||||
|
'ETH.USDT': _ad('Tether (ETH)', None, 'ETH.USDT', None, True),
|
||||||
|
'ETH.vTHOR': _ad('THORSwap Staking (ETH)', None, 'ETH.vTHOR', None, True),
|
||||||
|
'ETH.WBTC': _ad('Wrapped BTC (ETH)', None, 'ETH.WBTC', None, True),
|
||||||
|
'ETH.XRUNE': _ad('Thorstarter (ETH)', None, 'ETH.XRUNE', None, True),
|
||||||
|
'ETH.YFI': _ad('yearn.finance (ETH)', None, 'ETH.YFI', None, True),
|
||||||
}
|
}
|
||||||
|
|
||||||
evm_contracts = {}
|
evm_contracts = {
|
||||||
|
'ETH.AAVE': '7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9',
|
||||||
|
'ETH.DAI': '6b175474e89094c44da98b954eedeac495271d0f',
|
||||||
|
'ETH.DPI': '1494ca1f11d487c2bbe4543e90080aeba4ba3c2b',
|
||||||
|
'ETH.FOX': 'c770eefad204b5180df6a14ee197d99d808ee52d',
|
||||||
|
'ETH.GUSD': '056fd409e1d7a124bd7017459dfea2f387b6d5cd',
|
||||||
|
'ETH.LINK': '514910771af9ca656af840dff83e8264ecf986ca',
|
||||||
|
'ETH.LUSD': '5f98805a4e8be255a32880fdec7f6728c6568ba0',
|
||||||
|
'ETH.SNX': 'c011a73ee8576fb46f5e1c5751ca3b9fe0af2a6f',
|
||||||
|
'ETH.TGT': '108a850856db3f85d0269a2693d896b394c80325',
|
||||||
|
'ETH.THOR': 'a5f2211b9b8170f694421f2046281775e8468044',
|
||||||
|
'ETH.USDC': 'a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
|
||||||
|
'ETH.USDP': '8e870d67f660d95d5be530380d0ec0bd388289e1',
|
||||||
|
'ETH.USDT': 'dac17f958d2ee523a2206206994597c13d831ec7',
|
||||||
|
'ETH.vTHOR': '815c23eca83261b6ec689b60cc4a58b54bc24d8d',
|
||||||
|
'ETH.WBTC': '2260fac5e5542a773aa44fbcfedf7c193bc2c599',
|
||||||
|
'ETH.XRUNE': '69fa0fee221ad11012bab0fdb45d444d3d2ce71c',
|
||||||
|
'ETH.YFI': '0bc529c00c6401aef6d220be8c6ea1667f6ad93e',
|
||||||
|
}
|
||||||
|
|
||||||
unsupported = ('DOGE', 'RUNE')
|
unsupported = ('DOGE', 'RUNE')
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -172,6 +172,10 @@ class Thornode:
|
||||||
addr = self.data['inbound_address']
|
addr = self.data['inbound_address']
|
||||||
return addr.removeprefix('0x') if self.tx.proto.is_evm else addr
|
return addr.removeprefix('0x') if self.tx.proto.is_evm else addr
|
||||||
|
|
||||||
|
@property
|
||||||
|
def router(self):
|
||||||
|
return self.data['router'].lower().removeprefix('0x')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def rel_fee_hint(self):
|
def rel_fee_hint(self):
|
||||||
gas_unit = self.data['gas_rate_units']
|
gas_unit = self.data['gas_rate_units']
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,7 @@ async def _get_obj_async(_clsname, _modname, **kwargs):
|
||||||
# signing.
|
# signing.
|
||||||
if proto and proto.tokensym and clsname in (
|
if proto and proto.tokensym and clsname in (
|
||||||
'New',
|
'New',
|
||||||
|
'NewSwap',
|
||||||
'OnlineSigned',
|
'OnlineSigned',
|
||||||
'AutomountOnlineSigned',
|
'AutomountOnlineSigned',
|
||||||
'Sent',
|
'Sent',
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,10 @@ class Base(MMGenObject):
|
||||||
'swap_quote_expiry': None,
|
'swap_quote_expiry': None,
|
||||||
'swap_recv_addr_mmid': None,
|
'swap_recv_addr_mmid': None,
|
||||||
'swap_recv_asset_spec': None,
|
'swap_recv_asset_spec': None,
|
||||||
'swap_memo': None}
|
'swap_memo': None,
|
||||||
|
'token_vault_addr': None,
|
||||||
|
'serialized2': None,
|
||||||
|
'coin_txid2': CoinTxID}
|
||||||
file_format = 'json'
|
file_format = 'json'
|
||||||
non_mmgen_inputs_msg = f"""
|
non_mmgen_inputs_msg = f"""
|
||||||
This transaction includes inputs with non-{gc.proj_name} addresses. When
|
This transaction includes inputs with non-{gc.proj_name} addresses. When
|
||||||
|
|
@ -243,3 +246,8 @@ class Base(MMGenObject):
|
||||||
from ..swap.asset import SwapAsset
|
from ..swap.asset import SwapAsset
|
||||||
x = '[unknown]'
|
x = '[unknown]'
|
||||||
return SwapAsset._ad(x, x, x, x, x)
|
return SwapAsset._ad(x, x, x, x, x)
|
||||||
|
|
||||||
|
# token methods:
|
||||||
|
@property
|
||||||
|
def token_op(self):
|
||||||
|
return 'approve' if self.is_swap else 'transfer'
|
||||||
|
|
|
||||||
|
|
@ -173,11 +173,11 @@ class NewSwap(New):
|
||||||
else:
|
else:
|
||||||
self.usr_trade_limit = None
|
self.usr_trade_limit = None
|
||||||
|
|
||||||
def update_vault_addr(self, addr):
|
def update_vault_addr(self, c, *, addr='inbound_address'):
|
||||||
vault_idx = self.vault_idx
|
vault_idx = self.vault_idx
|
||||||
assert vault_idx == 0, f'{vault_idx}: vault index is not zero!'
|
assert vault_idx == 0, f'{vault_idx}: vault index is not zero!'
|
||||||
o = self.outputs[vault_idx]._asdict()
|
o = self.outputs[vault_idx]._asdict()
|
||||||
o['addr'] = addr
|
o['addr'] = getattr(c, addr)
|
||||||
self.outputs[vault_idx] = self.Output(self.proto, **o)
|
self.outputs[vault_idx] = self.Output(self.proto, **o)
|
||||||
|
|
||||||
async def update_vault_output(self, amt, *, deduct_est_fee=False):
|
async def update_vault_output(self, amt, *, deduct_est_fee=False):
|
||||||
|
|
@ -206,6 +206,7 @@ class NewSwap(New):
|
||||||
break
|
break
|
||||||
|
|
||||||
self.swap_quote_expiry = c.data['expiry']
|
self.swap_quote_expiry = c.data['expiry']
|
||||||
self.update_vault_addr(c.inbound_address)
|
self.update_vault_addr(c)
|
||||||
self.update_data_output(trade_limit)
|
self.update_data_output(trade_limit)
|
||||||
|
self.quote_data = c
|
||||||
return c.rel_fee_hint
|
return c.rel_fee_hint
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,8 @@ from .include.common import (
|
||||||
get_file_with_ext,
|
get_file_with_ext,
|
||||||
ok_msg,
|
ok_msg,
|
||||||
Ctrl_U,
|
Ctrl_U,
|
||||||
cleanup_env)
|
cleanup_env,
|
||||||
|
thorchain_router_addr_file)
|
||||||
|
|
||||||
from .base import CmdTestBase
|
from .base import CmdTestBase
|
||||||
from .shared import CmdTestShared
|
from .shared import CmdTestShared
|
||||||
|
|
@ -240,10 +241,11 @@ class CmdTestEthdevMethods:
|
||||||
gas,
|
gas,
|
||||||
mmgen_cmd = 'txdo',
|
mmgen_cmd = 'txdo',
|
||||||
gas_price = '8G',
|
gas_price = '8G',
|
||||||
|
fn = None,
|
||||||
num = None):
|
num = None):
|
||||||
|
|
||||||
keyfile = joinpath(self.tmpdir, dfl_devkey_fn)
|
keyfile = joinpath(self.tmpdir, dfl_devkey_fn)
|
||||||
fn = joinpath(self.tmpdir, 'mm'+str(num), key+'.bin')
|
fn = fn or joinpath(self.tmpdir, 'mm'+str(num), key+'.bin')
|
||||||
args = [
|
args = [
|
||||||
'-B',
|
'-B',
|
||||||
f'--fee={gas_price}',
|
f'--fee={gas_price}',
|
||||||
|
|
@ -256,6 +258,14 @@ class CmdTestEthdevMethods:
|
||||||
contract_addr = self._get_contract_address(dfl_devaddr)
|
contract_addr = self._get_contract_address(dfl_devaddr)
|
||||||
if key == 'Token':
|
if key == 'Token':
|
||||||
self.write_to_tmpfile(f'token_addr{num}', contract_addr+'\n')
|
self.write_to_tmpfile(f'token_addr{num}', contract_addr+'\n')
|
||||||
|
elif key == 'thorchain_router':
|
||||||
|
from mmgen.fileutil import write_data_to_file
|
||||||
|
write_data_to_file(
|
||||||
|
self.cfg,
|
||||||
|
thorchain_router_addr_file,
|
||||||
|
contract_addr + '\n',
|
||||||
|
ask_overwrite = False,
|
||||||
|
quiet = True)
|
||||||
|
|
||||||
if mmgen_cmd == 'txdo':
|
if mmgen_cmd == 'txdo':
|
||||||
args += ['-k', keyfile]
|
args += ['-k', keyfile]
|
||||||
|
|
@ -1587,7 +1597,7 @@ class CmdTestEthdev(CmdTestEthdevMethods, CmdTestBase, CmdTestShared):
|
||||||
def token_txdo_cached_balances(self):
|
def token_txdo_cached_balances(self):
|
||||||
return self.txdo_cached_balances(
|
return self.txdo_cached_balances(
|
||||||
acct = '1',
|
acct = '1',
|
||||||
fee_info_data = ('0.0026', '50'),
|
fee_info_data = ('0.00375', '50'),
|
||||||
add_args = ['--token=mm1', '98831F3A:E:12,43.21'])
|
add_args = ['--token=mm1', '98831F3A:E:12,43.21'])
|
||||||
|
|
||||||
def token_txcreate_refresh_balances(self):
|
def token_txcreate_refresh_balances(self):
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,17 @@
|
||||||
test.cmdtest_d.ethswap: Ethereum swap tests for the cmdtest.py test suite
|
test.cmdtest_d.ethswap: Ethereum swap tests for the cmdtest.py test suite
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from subprocess import run, PIPE, DEVNULL
|
||||||
|
|
||||||
from mmgen.cfg import Config
|
from mmgen.cfg import Config
|
||||||
|
from mmgen.util import rmsg, die
|
||||||
from mmgen.protocol import init_proto
|
from mmgen.protocol import init_proto
|
||||||
|
from mmgen.fileutil import get_data_from_file
|
||||||
|
|
||||||
|
from ..include.common import imsg, chk_equal
|
||||||
|
|
||||||
from .include.runner import CmdTestRunner
|
from .include.runner import CmdTestRunner
|
||||||
from .include.common import dfl_sid
|
from .include.common import dfl_sid, eth_inbound_addr, thorchain_router_addr_file
|
||||||
from .httpd.thornode import ThornodeServer
|
from .httpd.thornode import ThornodeServer
|
||||||
|
|
||||||
from .regtest import CmdTestRegtest
|
from .regtest import CmdTestRegtest
|
||||||
|
|
@ -42,6 +48,38 @@ class CmdTestEthSwapMethods:
|
||||||
async def token_deploy_c(self):
|
async def token_deploy_c(self):
|
||||||
return await self._token_deploy_token(num=1)
|
return await self._token_deploy_token(num=1)
|
||||||
|
|
||||||
|
def token_compile_router(self):
|
||||||
|
|
||||||
|
if not self.using_solc:
|
||||||
|
bin_fn = 'test/ref/ethereum/bin/THORChain_Router.bin'
|
||||||
|
imsg(f'Using precompiled contract data ‘{bin_fn}’')
|
||||||
|
import shutil
|
||||||
|
shutil.copy(bin_fn, self.tmpdir)
|
||||||
|
return 'skip'
|
||||||
|
|
||||||
|
imsg("Compiling THORChain router contract")
|
||||||
|
self.spawn(msg_only=True)
|
||||||
|
cmd = [
|
||||||
|
'solc',
|
||||||
|
'--evm-version=constantinople',
|
||||||
|
'--overwrite',
|
||||||
|
f'--output-dir={self.tmpdir}',
|
||||||
|
'--bin',
|
||||||
|
'test/ref/ethereum/THORChain_Router.sol']
|
||||||
|
imsg('Executing: {}'.format(' '.join(cmd)))
|
||||||
|
cp = run(cmd, stdout=DEVNULL, stderr=PIPE)
|
||||||
|
if cp.returncode != 0:
|
||||||
|
rmsg('solc failed with the following output:')
|
||||||
|
die(2, cp.stderr.decode())
|
||||||
|
imsg('THORChain router contract compiled')
|
||||||
|
return 'ok'
|
||||||
|
|
||||||
|
async def token_deploy_router(self):
|
||||||
|
return await self._token_deploy(
|
||||||
|
key = 'thorchain_router',
|
||||||
|
gas = 1_000_000,
|
||||||
|
fn = f'{self.tmpdir}/THORChain_Router.bin')
|
||||||
|
|
||||||
def token_fund_user(self):
|
def token_fund_user(self):
|
||||||
return self._token_transfer_ops(
|
return self._token_transfer_ops(
|
||||||
op = 'fund_user',
|
op = 'fund_user',
|
||||||
|
|
@ -54,9 +92,40 @@ class CmdTestEthSwapMethods:
|
||||||
def token_addrimport(self):
|
def token_addrimport(self):
|
||||||
return self._token_addrimport('token_addr1', '1-5', expect='5/5')
|
return self._token_addrimport('token_addr1', '1-5', expect='5/5')
|
||||||
|
|
||||||
|
def token_addrimport_inbound(self):
|
||||||
|
token_addr = self.read_from_tmpfile('token_addr1').strip()
|
||||||
|
return self.spawn(
|
||||||
|
'mmgen-addrimport',
|
||||||
|
['--quiet', '--regtest=1', f'--token-addr={token_addr}', f'--address={eth_inbound_addr}'])
|
||||||
|
|
||||||
def token_bal1(self):
|
def token_bal1(self):
|
||||||
return self._token_bal_check(pat=rf'{dfl_sid}:E:1\s+{self.token_fund_amt}\s')
|
return self._token_bal_check(pat=rf'{dfl_sid}:E:1\s+{self.token_fund_amt}\s')
|
||||||
|
|
||||||
|
def token_bal2(self):
|
||||||
|
return self._token_bal_check(pat=rf'{eth_inbound_addr}\s+\S+\s+87.654321\s')
|
||||||
|
|
||||||
|
async def _swaptxmemo(self, chk):
|
||||||
|
from mmgen.proto.eth.contract import Contract
|
||||||
|
self.spawn(msg_only=True)
|
||||||
|
addr = get_data_from_file(self.cfg, thorchain_router_addr_file, quiet=True).strip()
|
||||||
|
c = Contract(self.cfg, self.proto, addr, rpc=await self.rpc)
|
||||||
|
res = (await c.do_call('saved_memo()'))[2:]
|
||||||
|
memo_len = int(res[64:128], 16)
|
||||||
|
chk_equal(bytes.fromhex(res[128:128+(2*memo_len)]).decode(), chk)
|
||||||
|
imsg(f'saved_memo: {chk}')
|
||||||
|
return 'ok'
|
||||||
|
|
||||||
|
def _swaptxsend_eth_proxy(self, *, add_opts=[], test=False):
|
||||||
|
t = self._swaptxsend(
|
||||||
|
add_opts = ['--tx-proxy=eth'] + (['--test'] if test else []) + add_opts,
|
||||||
|
spawn_only = True)
|
||||||
|
t.expect('view: ', 'y')
|
||||||
|
t.expect('continue: ', '\n') # exit swap quote
|
||||||
|
t.expect('(y/N): ', '\n') # add comment
|
||||||
|
if not test:
|
||||||
|
t.expect('to confirm: ', 'YES\n')
|
||||||
|
return t
|
||||||
|
|
||||||
class CmdTestEthSwap(CmdTestSwapMethods, CmdTestRegtest):
|
class CmdTestEthSwap(CmdTestSwapMethods, CmdTestRegtest):
|
||||||
'Ethereum swap operations'
|
'Ethereum swap operations'
|
||||||
|
|
||||||
|
|
@ -117,9 +186,12 @@ class CmdTestEthSwap(CmdTestSwapMethods, CmdTestRegtest):
|
||||||
('eth_token_deploy_a', ''),
|
('eth_token_deploy_a', ''),
|
||||||
('eth_token_deploy_b', ''),
|
('eth_token_deploy_b', ''),
|
||||||
('eth_token_deploy_c', ''),
|
('eth_token_deploy_c', ''),
|
||||||
|
('eth_token_compile_router', ''),
|
||||||
|
('eth_token_deploy_router', ''),
|
||||||
('eth_token_fund_user', ''),
|
('eth_token_fund_user', ''),
|
||||||
('eth_token_addrgen', ''),
|
('eth_token_addrgen', ''),
|
||||||
('eth_token_addrimport', ''),
|
('eth_token_addrimport', ''),
|
||||||
|
('eth_token_addrimport_inbound', ''),
|
||||||
('eth_token_bal1', ''),
|
('eth_token_bal1', ''),
|
||||||
),
|
),
|
||||||
'token_swap': (
|
'token_swap': (
|
||||||
|
|
@ -153,10 +225,28 @@ class CmdTestEthSwap(CmdTestSwapMethods, CmdTestRegtest):
|
||||||
('eth_bal2', ''),
|
('eth_bal2', ''),
|
||||||
),
|
),
|
||||||
'eth_token_swap': (
|
'eth_token_swap': (
|
||||||
'swap operations (ETH <-> MM1)',
|
'swap operations (ETH -> ERC20, ERC20 -> BTC, ERC20 -> ETH)',
|
||||||
('eth_swaptxcreate3', ''),
|
# ETH -> MM1
|
||||||
('eth_swaptxsign3', ''),
|
('eth_swaptxcreate3', ''),
|
||||||
('eth_swaptxsend3', ''),
|
('eth_swaptxsign3', ''),
|
||||||
|
('eth_swaptxsend3', ''),
|
||||||
|
# MM1 -> BTC
|
||||||
|
('eth_swaptxcreate4', ''),
|
||||||
|
('eth_swaptxsign4', ''),
|
||||||
|
('eth_swaptxsend4', ''),
|
||||||
|
('eth_swaptxmemo4', ''),
|
||||||
|
('eth_swaptxstatus4', ''),
|
||||||
|
('eth_swaptxreceipt4', ''),
|
||||||
|
('eth_token_bal2', ''),
|
||||||
|
# MM1 -> ETH
|
||||||
|
('eth_swaptxcreate5', ''),
|
||||||
|
('eth_swaptxsign5', ''),
|
||||||
|
('eth_etherscan_server_start', ''),
|
||||||
|
('eth_swaptxsend5_test', ''),
|
||||||
|
('eth_swaptxsend5a', ''),
|
||||||
|
('eth_swaptxsend5b', ''),
|
||||||
|
('eth_swaptxsend5', ''),
|
||||||
|
('eth_etherscan_server_stop', ''),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -266,9 +356,12 @@ class CmdTestEthSwapEth(CmdTestEthSwapMethods, CmdTestSwapMethods, CmdTestEthdev
|
||||||
('token_deploy_a', 'deploying ERC20 token MM1 (SafeMath)'),
|
('token_deploy_a', 'deploying ERC20 token MM1 (SafeMath)'),
|
||||||
('token_deploy_b', 'deploying ERC20 token MM1 (Owned)'),
|
('token_deploy_b', 'deploying ERC20 token MM1 (Owned)'),
|
||||||
('token_deploy_c', 'deploying ERC20 token MM1 (Token)'),
|
('token_deploy_c', 'deploying ERC20 token MM1 (Token)'),
|
||||||
|
('token_compile_router', 'compiling THORChain router contract'),
|
||||||
|
('token_deploy_router', 'deploying THORChain router contract'),
|
||||||
('token_fund_user', 'transferring token funds from dev to user'),
|
('token_fund_user', 'transferring token funds from dev to user'),
|
||||||
('token_addrgen', 'generating token addresses'),
|
('token_addrgen', 'generating token addresses'),
|
||||||
('token_addrimport', 'importing token addresses using token address (MM1)'),
|
('token_addrimport', 'importing token addresses using token address (MM1)'),
|
||||||
|
('token_addrimport_inbound', 'importing THORNode inbound token address'),
|
||||||
('token_bal1', 'the token balance'),
|
('token_bal1', 'the token balance'),
|
||||||
|
|
||||||
# eth_token_swap:
|
# eth_token_swap:
|
||||||
|
|
@ -276,6 +369,25 @@ class CmdTestEthSwapEth(CmdTestEthSwapMethods, CmdTestSwapMethods, CmdTestEthdev
|
||||||
('swaptxcreate3', 'creating an ETH->MM1 swap transaction'),
|
('swaptxcreate3', 'creating an ETH->MM1 swap transaction'),
|
||||||
('swaptxsign3', 'signing the transaction'),
|
('swaptxsign3', 'signing the transaction'),
|
||||||
('swaptxsend3', 'sending the transaction'),
|
('swaptxsend3', 'sending the transaction'),
|
||||||
|
|
||||||
|
# MM1 -> BTC
|
||||||
|
('swaptxcreate4', 'creating an MM1->BTC swap transaction'),
|
||||||
|
('swaptxsign4', 'signing the transaction'),
|
||||||
|
('swaptxsend4', 'sending the transaction'),
|
||||||
|
('swaptxmemo4', 'checking the memo'),
|
||||||
|
('swaptxstatus4', 'getting the transaction status'),
|
||||||
|
('swaptxreceipt4', 'getting the transaction receipt'),
|
||||||
|
('token_bal2', 'the token balance'),
|
||||||
|
|
||||||
|
# MM1 -> ETH
|
||||||
|
('swaptxcreate5', 'creating an MM1->ETH swap transaction'),
|
||||||
|
('swaptxsign5', 'signing the transaction'),
|
||||||
|
('etherscan_server_start', 'starting the Etherscan server'),
|
||||||
|
('swaptxsend5_test', 'testing the transaction via Etherscan'),
|
||||||
|
('swaptxsend5a', 'sending the transaction via Etherscan (p1)'),
|
||||||
|
('swaptxsend5b', 'sending the transaction via Etherscan (p2)'),
|
||||||
|
('swaptxsend5', 'sending the transaction via Etherscan (complete)'),
|
||||||
|
('etherscan_server_stop', 'stopping the Etherscan server'),
|
||||||
)
|
)
|
||||||
|
|
||||||
def swaptxcreate1(self):
|
def swaptxcreate1(self):
|
||||||
|
|
@ -294,6 +406,14 @@ class CmdTestEthSwapEth(CmdTestEthSwapMethods, CmdTestSwapMethods, CmdTestEthdev
|
||||||
t = self._swaptxcreate(['ETH', '8.765', 'ETH.MM1', f'{dfl_sid}:E:5'])
|
t = self._swaptxcreate(['ETH', '8.765', 'ETH.MM1', f'{dfl_sid}:E:5'])
|
||||||
return self._swaptxcreate_ui_common(t)
|
return self._swaptxcreate_ui_common(t)
|
||||||
|
|
||||||
|
def swaptxcreate4(self):
|
||||||
|
t = self._swaptxcreate(['ETH.MM1', '87.654321', 'BTC', f'{dfl_sid}:C:2'])
|
||||||
|
return self._swaptxcreate_ui_common(t)
|
||||||
|
|
||||||
|
def swaptxcreate5(self):
|
||||||
|
t = self._swaptxcreate(['ETH.MM1', '98.7654321', 'ETH', f'{dfl_sid}:E:12'])
|
||||||
|
return self._swaptxcreate_ui_common(t)
|
||||||
|
|
||||||
def swaptxsign1(self):
|
def swaptxsign1(self):
|
||||||
return self._swaptxsign()
|
return self._swaptxsign()
|
||||||
|
|
||||||
|
|
@ -304,8 +424,28 @@ class CmdTestEthSwapEth(CmdTestEthSwapMethods, CmdTestSwapMethods, CmdTestEthdev
|
||||||
self.mining_delay()
|
self.mining_delay()
|
||||||
return self._swaptxsend(add_opts=['--verbose', '--status'], status=True)
|
return self._swaptxsend(add_opts=['--verbose', '--status'], status=True)
|
||||||
|
|
||||||
swaptxsign3 = swaptxsign1
|
def swaptxmemo4(self):
|
||||||
swaptxsend3 = swaptxsend1
|
return self._swaptxmemo('=:b:mkQsXA7mqDtnUpkaXMbDtAL1KMeof4GPw3:0/1/0')
|
||||||
|
|
||||||
|
def swaptxreceipt4(self):
|
||||||
|
self.mining_delay()
|
||||||
|
return self._swaptxsend(add_opts=['--receipt'], spawn_only=True)
|
||||||
|
|
||||||
|
def swaptxsend5_test(self):
|
||||||
|
return self._swaptxsend_eth_proxy(test=True)
|
||||||
|
|
||||||
|
def swaptxsend5a(self):
|
||||||
|
return self._swaptxsend_eth_proxy(add_opts=['--txhex-idx=1'])
|
||||||
|
|
||||||
|
def swaptxsend5b(self):
|
||||||
|
return self._swaptxsend_eth_proxy(add_opts=['--txhex-idx=2'])
|
||||||
|
|
||||||
|
def swaptxsend5(self):
|
||||||
|
return self._swaptxsend_eth_proxy()
|
||||||
|
|
||||||
|
swaptxsign5 = swaptxsign4 = swaptxsign3 = swaptxsign1
|
||||||
|
swaptxsend4 = swaptxsend3 = swaptxsend1
|
||||||
|
swaptxstatus4 = swaptxstatus1
|
||||||
|
|
||||||
def bal1(self):
|
def bal1(self):
|
||||||
return self.bal('swap1')
|
return self.bal('swap1')
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,8 @@ from mmgen.cfg import Config
|
||||||
from mmgen.amt import UniAmt
|
from mmgen.amt import UniAmt
|
||||||
from mmgen.protocol import init_proto
|
from mmgen.protocol import init_proto
|
||||||
|
|
||||||
|
from ..include.common import eth_inbound_addr, thorchain_router_addr_file
|
||||||
|
|
||||||
from . import HTTPD
|
from . import HTTPD
|
||||||
|
|
||||||
cfg = Config()
|
cfg = Config()
|
||||||
|
|
@ -117,14 +119,16 @@ data_template_eth = {
|
||||||
}
|
}
|
||||||
|
|
||||||
def make_inbound_addr(proto, mmtype):
|
def make_inbound_addr(proto, mmtype):
|
||||||
from mmgen.tool.coin import tool_cmd
|
if proto.is_evm:
|
||||||
n = int(time.time()) // (60 * 60 * 24) # increments once every 24 hrs
|
return '0x' + eth_inbound_addr # non-checksummed as per ninerealms thornode
|
||||||
ret = tool_cmd(
|
else:
|
||||||
cfg = cfg,
|
from mmgen.tool.coin import tool_cmd
|
||||||
cmdname = 'pubhash2addr',
|
n = int(time.time()) // (60 * 60 * 24) # increments once every 24 hrs
|
||||||
proto = proto,
|
return tool_cmd(
|
||||||
mmtype = mmtype).pubhash2addr(f'{n:040x}')
|
cfg = cfg,
|
||||||
return '0x' + ret if proto.is_evm else ret
|
cmdname = 'pubhash2addr',
|
||||||
|
proto = proto,
|
||||||
|
mmtype = mmtype).pubhash2addr(f'{n:040x}')
|
||||||
|
|
||||||
class ThornodeServer(HTTPD):
|
class ThornodeServer(HTTPD):
|
||||||
name = 'thornode server'
|
name = 'thornode server'
|
||||||
|
|
@ -160,4 +164,10 @@ class ThornodeServer(HTTPD):
|
||||||
'recommended_gas_rate': recommended_gas_rate[send_proto.base_proto_coin]
|
'recommended_gas_rate': recommended_gas_rate[send_proto.base_proto_coin]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if send_asset == 'MM1':
|
||||||
|
eth_proto = init_proto(cfg, 'eth', network='regtest')
|
||||||
|
with open(thorchain_router_addr_file) as fh:
|
||||||
|
raw_addr = fh.read().strip()
|
||||||
|
data['router'] = '0x' + eth_proto.checksummed_addr(raw_addr)
|
||||||
|
|
||||||
return json.dumps(data).encode()
|
return json.dumps(data).encode()
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,10 @@ chksum_pat = r'\b[A-F0-9]{4} [A-F0-9]{4} [A-F0-9]{4} [A-F0-9]{4}\b'
|
||||||
|
|
||||||
Ctrl_U = '\x15'
|
Ctrl_U = '\x15'
|
||||||
|
|
||||||
|
eth_inbound_addr = (28 * '0') + 'feedbeefcafe'
|
||||||
|
|
||||||
|
thorchain_router_addr_file = 'test/data_dir/thorchain_router_addr'
|
||||||
|
|
||||||
def ok_msg():
|
def ok_msg():
|
||||||
if cfg.profile:
|
if cfg.profile:
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -236,6 +236,9 @@ def cmp_or_die(s, t, desc=None):
|
||||||
f'ERROR: recoded data:\n{t!r}\ndiffers from original data:\n{s!r}'
|
f'ERROR: recoded data:\n{t!r}\ndiffers from original data:\n{s!r}'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def chk_equal(a, b):
|
||||||
|
assert a == b, f'equality test failed: {a} != {b}'
|
||||||
|
|
||||||
def init_coverage():
|
def init_coverage():
|
||||||
coverdir = os.path.join('test', 'trace')
|
coverdir = os.path.join('test', 'trace')
|
||||||
acc_file = os.path.join('test', 'trace.acc')
|
acc_file = os.path.join('test', 'trace.acc')
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ from .asset_orig import *
|
||||||
class overlay_fake_THORChainSwapAsset:
|
class overlay_fake_THORChainSwapAsset:
|
||||||
|
|
||||||
assets_data = {
|
assets_data = {
|
||||||
'ETH.USDT': THORChainSwapAsset._ad('Tether (ETH)', None, 'ETH.USDT', None, True),
|
|
||||||
'ETH.MM1': THORChainSwapAsset._ad('MM1 Token (ETH)', None, 'ETH.MM1', None, True),
|
'ETH.MM1': THORChainSwapAsset._ad('MM1 Token (ETH)', None, 'ETH.MM1', None, True),
|
||||||
'ETH.JUNK': THORChainSwapAsset._ad('Junk Token (ETH)', None, 'ETH.JUNK', None, True),
|
'ETH.JUNK': THORChainSwapAsset._ad('Junk Token (ETH)', None, 'ETH.JUNK', None, True),
|
||||||
'ETH.NONE': THORChainSwapAsset._ad('Unavailable Token (ETH)', None, 'ETH.NONE', None, True)
|
'ETH.NONE': THORChainSwapAsset._ad('Unavailable Token (ETH)', None, 'ETH.NONE', None, True)
|
||||||
|
|
|
||||||
35
test/ref/ethereum/THORChain_Router.sol
Normal file
35
test/ref/ethereum/THORChain_Router.sol
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
// MMGen Wallet, a terminal-based cryptocurrency wallet
|
||||||
|
// Copyright (C)2013-2025 The MMGen Project <mmgen@tuta.io>
|
||||||
|
// Licensed under the GNU General Public License, Version 3:
|
||||||
|
// https://www.gnu.org/licenses
|
||||||
|
// Public project repositories:
|
||||||
|
// https://github.com/mmgen/mmgen-wallet
|
||||||
|
// https://gitlab.com/mmgen/mmgen-wallet
|
||||||
|
//
|
||||||
|
// Minimal THORChain router for testing
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: GPL-3.0
|
||||||
|
|
||||||
|
pragma solidity >=0.8.25;
|
||||||
|
|
||||||
|
interface iERC20 {
|
||||||
|
function transferFrom(
|
||||||
|
address from,
|
||||||
|
address to,
|
||||||
|
uint tokens) external payable returns (bool success);
|
||||||
|
}
|
||||||
|
|
||||||
|
contract THORChain_Router {
|
||||||
|
string public saved_memo;
|
||||||
|
function depositWithExpiry(
|
||||||
|
address payable vault,
|
||||||
|
address asset,
|
||||||
|
uint amount,
|
||||||
|
string memory memo,
|
||||||
|
uint expiration
|
||||||
|
) external payable returns (bool success) {
|
||||||
|
require(block.timestamp < expiration, "THORChain_Router: expired");
|
||||||
|
saved_memo = memo;
|
||||||
|
return iERC20(asset).transferFrom(msg.sender, vault, amount);
|
||||||
|
}
|
||||||
|
}
|
||||||
1
test/ref/ethereum/bin/THORChain_Router.bin
Normal file
1
test/ref/ethereum/bin/THORChain_Router.bin
Normal file
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue