swaptxcreate: add --router-gas option; proto.eth.tx: new total_gas attr

This commit is contained in:
The MMGen Project 2025-05-07 18:24:07 +00:00
commit aaaccc6bad
Signed by: mmgen
GPG key ID: 3F8B1861E32B7DA2
11 changed files with 49 additions and 13 deletions

View file

@ -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

View file

@ -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)

View file

@ -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):

View file

@ -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)

View file

@ -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'

View file

@ -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:

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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):

View file

@ -0,0 +1,3 @@
from .base_orig import *
TokenBase.dfl_router_gas = 525000