From aaaccc6bad54ccb6fa81458e432aa10ec759ba9f Mon Sep 17 00:00:00 2001 From: The MMGen Project Date: Wed, 7 May 2025 18:24:07 +0000 Subject: [PATCH] swaptxcreate: add `--router-gas` option; proto.eth.tx: new `total_gas` attr --- mmgen/main_txcreate.py | 2 ++ mmgen/main_txdo.py | 2 ++ mmgen/proto/eth/tx/base.py | 7 ++++--- mmgen/proto/eth/tx/completed.py | 8 ++++++++ mmgen/proto/eth/tx/info.py | 7 ++++--- mmgen/proto/eth/tx/new.py | 16 ++++++++++++++-- mmgen/proto/eth/tx/online.py | 6 ++++++ mmgen/proto/eth/tx/signed.py | 1 - mmgen/proto/eth/tx/unsigned.py | 6 +++--- test/cmdtest_d/ethswap.py | 4 +++- test/overlay/fakemods/mmgen/proto/eth/tx/base.py | 3 +++ 11 files changed, 49 insertions(+), 13 deletions(-) create mode 100755 test/overlay/fakemods/mmgen/proto/eth/tx/base.py diff --git a/mmgen/main_txcreate.py b/mmgen/main_txcreate.py index 9062f83b..b86b4b92 100755 --- a/mmgen/main_txcreate.py +++ b/mmgen/main_txcreate.py @@ -59,6 +59,8 @@ opts_data = { + calculated using network fee estimation. et -g, --gas=N Specify gas limit (integer) -s -g, --gas=N Specify gas limit for Ethereum (integer) + -s -G, --router-gas=N Specify gas limit for Ethereum router contract + + (integer). Applicable only for swaps from token assets -- -i, --info Display {a_info} and exit -- -I, --inputs= i Specify transaction inputs (comma-separated list of + MMGen IDs or coin addresses). Note that ALL unspent diff --git a/mmgen/main_txdo.py b/mmgen/main_txdo.py index 9472b598..4fa7d4b1 100755 --- a/mmgen/main_txdo.py +++ b/mmgen/main_txdo.py @@ -59,6 +59,8 @@ opts_data = { + calculated using network fee estimation. et -g, --gas=N Specify gas limit (integer) -s -g, --gas=N Specify gas limit for Ethereum (integer) + -s -G, --router-gas=N Specify gas limit for Ethereum router contract + + (integer). Applicable only for swaps from token assets -- -H, --hidden-incog-input-params=f,o Read hidden incognito data from file + 'f' at offset 'o' (comma-separated) -- -i, --in-fmt= f Input is from wallet format 'f' (see FMT CODES below) diff --git a/mmgen/proto/eth/tx/base.py b/mmgen/proto/eth/tx/base.py index 4172a6cb..c2a41fd2 100755 --- a/mmgen/proto/eth/tx/base.py +++ b/mmgen/proto/eth/tx/base.py @@ -44,12 +44,12 @@ class Base(TxBase): # given absolute fee in ETH, return gas price in ETH def fee_abs2gasprice(self, abs_fee): - return self.proto.coin_amt(int(abs_fee.toWei() // self.gas), from_unit='wei') + return self.proto.coin_amt(int(abs_fee.toWei() // self.total_gas), from_unit='wei') - # given rel fee (gasPrice) in wei, return absolute fee using self.gas (Ethereum-only method) + # given rel fee (gasPrice) in wei, return absolute fee using self.total_gas def fee_gasPrice2abs(self, rel_fee): assert isinstance(rel_fee, int), f'{rel_fee!r}: incorrect type for fee estimate (not an integer)' - return self.proto.coin_amt(rel_fee * self.gas, from_unit='wei') + return self.proto.coin_amt(rel_fee * self.total_gas, from_unit='wei') def is_replaceable(self): return True @@ -112,6 +112,7 @@ class Base(TxBase): class TokenBase(Base): dfl_gas = 75000 + dfl_router_gas = 150000 contract_desc = 'token contract' def check_serialized_integrity(self): diff --git a/mmgen/proto/eth/tx/completed.py b/mmgen/proto/eth/tx/completed.py index 587b5178..e5479fea 100755 --- a/mmgen/proto/eth/tx/completed.py +++ b/mmgen/proto/eth/tx/completed.py @@ -25,6 +25,10 @@ class Completed(Base, TxBase.Completed): def send_amt(self): return self.outputs[0].amt if self.outputs else self.proto.coin_amt('0') + @property + def total_gas(self): + return self.txobj['startGas'] + @property def fee(self): return self.fee_gasPrice2abs(self.txobj['gasPrice'].toWei()) @@ -53,3 +57,7 @@ class TokenCompleted(TokenBase, Completed): @property def change(self): return self.sum_inputs() - self.send_amt + + @property + def total_gas(self): + return self.txobj['startGas'] + (self.txobj['router_gas'] if self.is_swap else 0) diff --git a/mmgen/proto/eth/tx/info.py b/mmgen/proto/eth/tx/info.py index ee44a7ae..a09dc7bb 100755 --- a/mmgen/proto/eth/tx/info.py +++ b/mmgen/proto/eth/tx/info.py @@ -14,7 +14,7 @@ proto.eth.tx.info: Ethereum transaction info class from ....tx.info import TxInfo from ....util import fmt, pp_fmt -from ....color import yellow, blue, cyan, pink +from ....color import red, yellow, blue, cyan, pink from ....addr import MMGenID from ....obj import Int @@ -38,7 +38,7 @@ class TxInfo(TxInfo): {toaddr} {t}{t_mmid}{tvault} Amount: {a} {c} Gas price: {g} Gwei - Gas limit: {G} + Gas limit: {G}{G_dec} Nonce: {n} Data: {d} """.strip().replace('\t', '') + ('\nMemo: {m}' if tx.is_swap else '') @@ -57,7 +57,8 @@ class TxInfo(TxInfo): m = pink(tx.swap_memo) if tx.is_swap else None, c = tx.proto.dcoin if len(tx.outputs) else '', g = yellow(tx.pretty_fmt_fee(t['gasPrice'].to_unit('Gwei'))), - G = Int(t['startGas']).hl(), + G = Int(tx.total_gas).hl(), + G_dec = red(f" ({t['startGas']} + {t['router_gas']})") if tokenswap else '', f_mmid = mmid_disp(tx.inputs[0]), t_mmid = mmid_disp(tx.outputs[0]) if tx.outputs and not tx.is_swap else '') + '\n\n' diff --git a/mmgen/proto/eth/tx/new.py b/mmgen/proto/eth/tx/new.py index dd56610b..8ee8b390 100755 --- a/mmgen/proto/eth/tx/new.py +++ b/mmgen/proto/eth/tx/new.py @@ -37,6 +37,9 @@ class New(Base, TxBase.New): self.gas = int(self.cfg.gas or self.dfl_gas) + if self.is_token and self.is_swap: + self.router_gas = int(self.cfg.router_gas or self.dfl_router_gas) + if self.cfg.contract_data: m = "'--contract-data' option may not be used with token transaction" assert 'Token' not in self.name, m @@ -149,9 +152,13 @@ class New(Base, TxBase.New): if not self.disable_fee_check: assert self.usr_fee <= self.proto.max_tx_fee - # given rel fee and units, return absolute fee using self.gas + @property + def total_gas(self): + return self.gas + + # given rel fee and units, return absolute fee using self.total_gas def fee_rel2abs(self, tx_size, amt_in_units, unit): - return self.proto.coin_amt(int(amt_in_units * self.gas), from_unit=unit) + return self.proto.coin_amt(int(amt_in_units * self.total_gas), from_unit=unit) # given fee estimate (gas price) in wei, return absolute fee, adjusting by self.cfg.fee_adjust def fee_est2abs(self, net_fee): @@ -207,6 +214,10 @@ class TokenNew(TokenBase, New): desc = 'transaction' fee_is_approximate = True + @property + def total_gas(self): + return self.gas + (self.router_gas if self.is_swap else 0) + async def make_txobj(self): # called by create_serialized() await super().make_txobj() t = Token(self.cfg, self.proto, self.twctl.token, decimals=self.twctl.decimals) @@ -216,6 +227,7 @@ class TokenNew(TokenBase, New): o['token_to'] = o['to'] if self.is_swap: o['expiry'] = self.quote_data.data['expiry'] + o['router_gas'] = self.router_gas def update_change_output(self, funds_left): if self.outputs[0].is_chg: diff --git a/mmgen/proto/eth/tx/online.py b/mmgen/proto/eth/tx/online.py index 620043cf..a2ca1ec7 100755 --- a/mmgen/proto/eth/tx/online.py +++ b/mmgen/proto/eth/tx/online.py @@ -79,6 +79,12 @@ class TokenOnlineSigned(TokenSigned, OnlineSigned): t = Token(self.cfg, self.proto, o['token_addr'], decimals=o['decimals']) o['amt'] = t.transferdata2amt(o['data']) o['token_to'] = t.transferdata2sendaddr(o['data']) + if self.is_swap: + from ..pyethereum.transactions import Transaction + from .. import rlp + etx = rlp.decode(bytes.fromhex(self.serialized2), Transaction) + d = etx.to_dict() + o['router_gas'] = d['startgas'] class Sent(TxBase.Sent, OnlineSigned): pass diff --git a/mmgen/proto/eth/tx/signed.py b/mmgen/proto/eth/tx/signed.py index 2903f280..89cbcb70 100755 --- a/mmgen/proto/eth/tx/signed.py +++ b/mmgen/proto/eth/tx/signed.py @@ -44,7 +44,6 @@ class Signed(Completed, TxBase.Signed): self.disable_fee_check = True txid = CoinTxID(etx.hash.hex()) assert txid == self.coin_txid, "txid in tx.serialized doesn't match value in MMGen transaction file" - self.gas = o['startGas'] self.txobj = o return d # 'token_addr', 'decimals' required by Token subclass diff --git a/mmgen/proto/eth/tx/unsigned.py b/mmgen/proto/eth/tx/unsigned.py index 708876fd..e49f4fa2 100755 --- a/mmgen/proto/eth/tx/unsigned.py +++ b/mmgen/proto/eth/tx/unsigned.py @@ -38,7 +38,6 @@ class Unsigned(Completed, TxBase.Unsigned): 'nonce': ETHNonce(d['nonce']), 'chainId': None if d['chainId'] == 'None' else Int(d['chainId']), 'data': HexStr(d['data'])} - self.gas = o['startGas'] self.txobj = o return d # 'token_addr', 'decimals' required by Token subclass @@ -114,11 +113,12 @@ class TokenUnsigned(TokenCompleted, Unsigned): o['token_to'] = o['to'] if self.is_swap: o['expiry'] = Int(d['expiry']) + o['router_gas'] = Int(d['router_gas']) async def do_sign(self, o, wif): t = Token(self.cfg, self.proto, o['token_addr'], decimals=o['decimals']) 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=o['startGas'], gasPrice=o['gasPrice'], nonce=o['nonce'], data=tdata) res = await t.txsign(tx_in, wif, o['from'], chain_id=o['chainId']) self.serialized = res.txhex self.coin_txid = res.txid @@ -131,7 +131,7 @@ class TokenUnsigned(TokenCompleted, Unsigned): self.swap_memo.encode(), o['expiry']) tx_in = c.make_tx_in( - gas = self.gas * (7 if self.cfg.test_suite else 2), + gas = o['router_gas'], gasPrice = o['gasPrice'], nonce = o['nonce'] + 1, data = cdata) diff --git a/test/cmdtest_d/ethswap.py b/test/cmdtest_d/ethswap.py index 0211ca34..32940f76 100755 --- a/test/cmdtest_d/ethswap.py +++ b/test/cmdtest_d/ethswap.py @@ -447,7 +447,9 @@ class CmdTestEthSwapEth(CmdTestEthSwapMethods, CmdTestSwapMethods, CmdTestEthdev return self._swaptxcreate_ui_common(t) def swaptxcreate5b(self): - t = self._swaptxcreate(['ETH.MM1', '98.7654321', 'ETH', f'{dfl_sid}:E:12']) + t = self._swaptxcreate( + ['ETH.MM1', '98.7654321', 'ETH', f'{dfl_sid}:E:12'], + add_opts = ['--gas=58000', '--router-gas=500000']) return self._swaptxcreate_ui_common(t) def swaptxsign1(self): diff --git a/test/overlay/fakemods/mmgen/proto/eth/tx/base.py b/test/overlay/fakemods/mmgen/proto/eth/tx/base.py new file mode 100755 index 00000000..238045d6 --- /dev/null +++ b/test/overlay/fakemods/mmgen/proto/eth/tx/base.py @@ -0,0 +1,3 @@ +from .base_orig import * + +TokenBase.dfl_router_gas = 525000