proto.eth.tx: use eth_estimateGas for token transactions
This commit is contained in:
parent
907ec11e6b
commit
82294e6a88
13 changed files with 89 additions and 27 deletions
|
|
@ -1 +1 @@
|
|||
April 2025
|
||||
May 2025
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
15.1.dev33
|
||||
15.1.dev34
|
||||
|
|
|
|||
|
|
@ -118,6 +118,21 @@ FMT CODES:
|
|||
from ..tx import BaseTX
|
||||
return BaseTX(cfg=self.cfg, proto=self.proto).rel_fee_desc
|
||||
|
||||
def gas_limit(self, target):
|
||||
return """
|
||||
GAS LIMIT
|
||||
|
||||
This option specifies the maximum gas allowance for an Ethereum transaction.
|
||||
It’s generally of interest only for token transactions or swap transactions
|
||||
from token assets.
|
||||
|
||||
Parameter must be an integer or one of the special values ‘fallback’ (for a
|
||||
locally computed sane default) or ‘auto’ (for gas estimate via an RPC call,
|
||||
in the case of a token transaction, or locally computed default, in the case
|
||||
of a standard transaction). The default is ‘auto’.
|
||||
|
||||
""" if target == 'swaptx' or self.proto.base_coin == 'ETH' else ''
|
||||
|
||||
def fee(self, all_coins=False):
|
||||
from ..tx import BaseTX
|
||||
text = """
|
||||
|
|
|
|||
|
|
@ -57,10 +57,11 @@ opts_data = {
|
|||
+ {fu} (an integer followed by {fl}).
|
||||
+ See FEE SPECIFICATION below. If omitted, fee will be
|
||||
+ 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
|
||||
et -g, --gas=N Set the gas limit (see GAS LIMIT below)
|
||||
-s -g, --gas=N Set the gas limit for Ethereum (see GAS LIMIT below)
|
||||
-s -G, --router-gas=N Set the gas limit for the Ethereum router contract
|
||||
+ (integer). When unset, a hardcoded default will be
|
||||
+ used. 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
|
||||
|
|
@ -83,7 +84,7 @@ opts_data = {
|
|||
-- -y, --yes Answer 'yes' to prompts, suppress non-essential output
|
||||
e- -X, --cached-balances Use cached balances
|
||||
""",
|
||||
'notes': '\n{c}\n{n_at}\n\n{F}\n\n{x}',
|
||||
'notes': '\n{c}\n{n_at}\n\n{g}{F}\n\n{x}',
|
||||
},
|
||||
'code': {
|
||||
'usage': lambda cfg, proto, help_notes, s: s.format(
|
||||
|
|
@ -100,6 +101,7 @@ opts_data = {
|
|||
x_dfl = cfg._autoset_opts['swap_proto'].choices[0]),
|
||||
'notes': lambda cfg, help_mod, help_notes, s: s.format(
|
||||
c = help_mod(f'{target}create'),
|
||||
g = help_notes('gas_limit', target),
|
||||
F = help_notes('fee', all_coins={'tx': False, 'swaptx': True}[target]),
|
||||
n_at = help_notes('address_types'),
|
||||
x = help_mod(f'{target}create_examples'))
|
||||
|
|
|
|||
|
|
@ -57,10 +57,11 @@ opts_data = {
|
|||
+ {fu} (an integer followed by {fl!r}).
|
||||
+ See FEE SPECIFICATION below. If omitted, fee will be
|
||||
+ 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
|
||||
et -g, --gas=N Set the gas limit (see GAS LIMIT below)
|
||||
-s -g, --gas=N Set the gas limit for Ethereum (see GAS LIMIT below)
|
||||
-s -G, --router-gas=N Set the gas limit for the Ethereum router contract
|
||||
+ (integer). When unset, a hardcoded default will be
|
||||
+ used. 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)
|
||||
|
|
@ -110,7 +111,7 @@ opts_data = {
|
|||
{c}
|
||||
{n_at}
|
||||
|
||||
{F}
|
||||
{g}{F}
|
||||
|
||||
|
||||
SIGNING NOTES
|
||||
|
|
@ -145,6 +146,7 @@ column below:
|
|||
x_dfl = cfg._autoset_opts['swap_proto'].choices[0]),
|
||||
'notes': lambda cfg, help_mod, help_notes, s: s.format(
|
||||
c = help_mod(f'{target}create'),
|
||||
g = help_notes('gas_limit', target),
|
||||
F = help_notes('fee'),
|
||||
n_at = help_notes('address_types'),
|
||||
f = help_notes('fmt_codes'),
|
||||
|
|
|
|||
|
|
@ -24,6 +24,9 @@ class New(Base, TxNew):
|
|||
no_chg_msg = 'Warning: Change address will be deleted as transaction produces no change'
|
||||
msg_insufficient_funds = 'Selected outputs insufficient to fund this transaction ({} {} needed)'
|
||||
|
||||
async def set_gas(self, *, to_addr=None):
|
||||
return None
|
||||
|
||||
def process_data_output_arg(self, arg):
|
||||
if any(arg.startswith(pfx) for pfx in ('data:', 'hexdata:')):
|
||||
if hasattr(self, '_have_op_return_data'):
|
||||
|
|
|
|||
|
|
@ -55,24 +55,30 @@ class Contract:
|
|||
|
||||
async def do_call(
|
||||
self,
|
||||
method_sig,
|
||||
method_sig = '',
|
||||
method_args = '',
|
||||
*,
|
||||
method = 'eth_call',
|
||||
block = 'pending', # earliest, latest, safe, finalized
|
||||
from_addr = None,
|
||||
data = None,
|
||||
toUnit = False):
|
||||
|
||||
data = self.create_method_id(method_sig) + method_args
|
||||
data = data or (self.create_method_id(method_sig) + method_args)
|
||||
|
||||
args = {
|
||||
'to': '0x' + self.addr,
|
||||
'input': '0x' + data}
|
||||
|
||||
if from_addr:
|
||||
args['from'] = '0x' + from_addr
|
||||
|
||||
if self.cfg.debug:
|
||||
msg('ETH_CALL {}: {}'.format(
|
||||
method_sig,
|
||||
'\n '.join(parse_abi(data))))
|
||||
|
||||
ret = await self.rpc.call('eth_call', args, block)
|
||||
ret = await self.rpc.call(method, args, block)
|
||||
|
||||
await erigon_sleep(self)
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import json
|
|||
|
||||
from ....tx import new as TxBase
|
||||
from ....obj import Int, ETHNonce, MMGenTxID
|
||||
from ....util import msg, is_int, is_hex_str, make_chksum_6, suf, die
|
||||
from ....util import msg, ymsg, is_int, is_hex_str, make_chksum_6, suf, die
|
||||
from ....tw.ctl import TwCtl
|
||||
from ....addr import is_mmgen_id, is_coin_addr
|
||||
from ..contract import Token
|
||||
|
|
@ -35,8 +35,6 @@ class New(Base, TxBase.New):
|
|||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
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)
|
||||
|
||||
|
|
@ -47,6 +45,14 @@ class New(Base, TxBase.New):
|
|||
self.usr_contract_data = bytes.fromhex(fp.read().strip())
|
||||
self.disable_fee_check = True
|
||||
|
||||
async def set_gas(self, *, to_addr=None):
|
||||
if to_addr or not hasattr(self, 'gas'):
|
||||
auto_gas = self.cfg.gas in ('auto', None)
|
||||
self.gas = (
|
||||
self.dfl_gas if self.cfg.gas == 'fallback' or (auto_gas and not self.is_token) else
|
||||
(await self.get_gas_estimateGas(to_addr=to_addr)) if auto_gas else
|
||||
int(self.cfg.gas))
|
||||
|
||||
async def get_nonce(self):
|
||||
return ETHNonce(int(
|
||||
await self.rpc.call('eth_getTransactionCount', '0x'+self.inputs[0].addr, 'pending'), 16))
|
||||
|
|
@ -218,6 +224,29 @@ class TokenNew(TokenBase, New):
|
|||
def total_gas(self):
|
||||
return self.gas + (self.router_gas if self.is_swap else 0)
|
||||
|
||||
async def get_gas_estimateGas(self, *, to_addr=None):
|
||||
t = Token(
|
||||
self.cfg,
|
||||
self.proto,
|
||||
self.twctl.token,
|
||||
decimals = self.twctl.decimals,
|
||||
rpc = self.rpc)
|
||||
|
||||
data = t.create_transfer_data(
|
||||
to_addr = to_addr or self.outputs[0].addr,
|
||||
amt = self.outputs[0].amt or await self.twuo.twctl.get_balance(self.inputs[0].addr),
|
||||
op = self.token_op)
|
||||
|
||||
try:
|
||||
res = await t.do_call(method='eth_estimateGas', from_addr=self.inputs[0].addr, data=data)
|
||||
except Exception as e:
|
||||
ymsg(
|
||||
'Unable to estimate gas limit via node. '
|
||||
'Please retry with --gas set to an integer value, or ‘fallback’ for a sane default')
|
||||
raise e
|
||||
|
||||
return int(res, 16)
|
||||
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -72,6 +72,8 @@ class Bump(Completed, NewSwap):
|
|||
|
||||
output_idx = self.choose_output()
|
||||
|
||||
await self.set_gas()
|
||||
|
||||
if not silent:
|
||||
msg('Minimum fee for new transaction: {} {} ({} {})'.format(
|
||||
self.min_fee.hl(),
|
||||
|
|
|
|||
|
|
@ -493,11 +493,13 @@ class New(Base):
|
|||
while True:
|
||||
if not await self.get_inputs(outputs_sum):
|
||||
continue
|
||||
fee_hint = None
|
||||
if self.is_swap:
|
||||
fee_hint = await self.update_vault_output(
|
||||
self.vault_output.amt or self.sum_inputs(),
|
||||
deduct_est_fee = self.vault_output == self.chg_output)
|
||||
else:
|
||||
await self.set_gas()
|
||||
fee_hint = None
|
||||
desc = 'User-selected' if self.cfg.fee else 'Recommended' if fee_hint else None
|
||||
if (funds_left := await self.get_fee(
|
||||
self.cfg.fee or fee_hint,
|
||||
|
|
|
|||
|
|
@ -199,6 +199,7 @@ class NewSwap(New):
|
|||
while True:
|
||||
self.cfg._util.qmsg(f'Retrieving data from {c.rpc.host}...')
|
||||
c.get_quote()
|
||||
await self.set_gas(to_addr=c.router if self.is_token else None)
|
||||
self.swap_quote_refresh_time = time.time()
|
||||
trade_limit = get_trade_limit()
|
||||
self.cfg._util.qmsg('OK')
|
||||
|
|
|
|||
|
|
@ -1597,7 +1597,7 @@ class CmdTestEthdev(CmdTestEthdevMethods, CmdTestBase, CmdTestShared):
|
|||
def token_txdo_cached_balances(self):
|
||||
return self.txdo_cached_balances(
|
||||
acct = '1',
|
||||
fee_info_data = ('0.00375', '50'),
|
||||
fee_info_data = ('0.00260265', '50'),
|
||||
add_args = ['--token=mm1', '98831F3A:E:12,43.21'])
|
||||
|
||||
def token_txcreate_refresh_balances(self):
|
||||
|
|
|
|||
|
|
@ -419,13 +419,13 @@ class CmdTestEthSwapEth(CmdTestEthSwapMethods, CmdTestSwapMethods, CmdTestEthdev
|
|||
expect = ':2019e4/3/0')
|
||||
|
||||
def swaptxcreate3a(self):
|
||||
t = self._swaptxcreate(['ETH', '0.7654321', 'ETH.MM1'])
|
||||
t = self._swaptxcreate(['ETH', '0.7654321', 'ETH.MM1'], add_opts=['--gas=fallback'])
|
||||
t.expect(f'{dfl_sid}:E:4') # check that correct unused address was found
|
||||
t.expect('(Y/n): ', 'y')
|
||||
return self._swaptxcreate_ui_common(t)
|
||||
|
||||
def swaptxcreate3b(self):
|
||||
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'], add_opts=['--gas=auto'])
|
||||
return self._swaptxcreate_ui_common(t)
|
||||
|
||||
async def swaptxmemo3(self):
|
||||
|
|
@ -441,19 +441,19 @@ class CmdTestEthSwapEth(CmdTestEthSwapMethods, CmdTestSwapMethods, CmdTestEthdev
|
|||
return 'ok'
|
||||
|
||||
def swaptxcreate4(self):
|
||||
t = self._swaptxcreate(['ETH.MM1', '87.654321', 'BTC', f'{dfl_sid}:C:2'])
|
||||
t = self._swaptxcreate(['ETH.MM1', '87.654321', 'BTC', f'{dfl_sid}:C:2'], add_opts=['--gas=auto'])
|
||||
return self._swaptxcreate_ui_common(t)
|
||||
|
||||
def swaptxcreate5a(self):
|
||||
t = self._swaptxcreate(['ETH.MM1', '98.7654321', 'ETH'])
|
||||
t = self._swaptxcreate(
|
||||
['ETH.MM1', '98.7654321', 'ETH'],
|
||||
add_opts = ['--gas=58000', '--router-gas=500000'])
|
||||
t.expect(f'{dfl_sid}:E:13') # check that correct unused address was found
|
||||
t.expect('(Y/n): ', 'y')
|
||||
return self._swaptxcreate_ui_common(t)
|
||||
|
||||
def swaptxcreate5b(self):
|
||||
t = self._swaptxcreate(
|
||||
['ETH.MM1', '98.7654321', 'ETH', f'{dfl_sid}:E:12'],
|
||||
add_opts = ['--gas=58000', '--router-gas=500000'])
|
||||
t = self._swaptxcreate(['ETH.MM1', '98.7654321', 'ETH', f'{dfl_sid}:E:12'])
|
||||
return self._swaptxcreate_ui_common(t)
|
||||
|
||||
def swaptxsign1(self):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue