|
|
@@ -13,19 +13,18 @@ proto.eth.tx.new: Ethereum new transaction class
|
|
|
"""
|
|
|
|
|
|
from ....tx import new as TxBase
|
|
|
-from ....obj import Int, ETHNonce, MMGenTxID
|
|
|
-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 ....obj import Int, ETHNonce
|
|
|
+from ....util import msg, ymsg, is_int
|
|
|
+
|
|
|
+from ...vm.tx.new import New as VmNew
|
|
|
+
|
|
|
from ..contract import Token
|
|
|
+
|
|
|
from .base import Base, TokenBase
|
|
|
|
|
|
-class New(Base, TxBase.New):
|
|
|
- desc = 'transaction'
|
|
|
+class New(VmNew, Base, TxBase.New):
|
|
|
fee_fail_fs = 'Network fee estimation failed'
|
|
|
- no_chg_msg = 'Warning: Transaction leaves account with zero balance'
|
|
|
usr_fee_prompt = 'Enter transaction fee or gas price: '
|
|
|
- msg_insufficient_funds = 'Account balance insufficient to fund this transaction ({} {} needed)'
|
|
|
byte_cost = 68 # https://ethereum.stackexchange.com/questions/39401/
|
|
|
# how-do-you-calculate-gas-limit-for-transaction-with-data-in-ethereum
|
|
|
|
|
|
@@ -70,24 +69,6 @@ class New(Base, TxBase.New):
|
|
|
'chainId': self.rpc.chainID,
|
|
|
'data': self.usr_contract_data.hex()}
|
|
|
|
|
|
- # Instead of serializing tx data as with BTC, just create a JSON dump.
|
|
|
- # This complicates things but means we avoid using the rlp library to deserialize the data,
|
|
|
- # thus removing an attack vector
|
|
|
- async def create_serialized(self, *, locktime=None):
|
|
|
- assert len(self.inputs) == 1, 'Transaction has more than one input!'
|
|
|
- o_num = len(self.outputs)
|
|
|
- o_ok = 0 if self.usr_contract_data else 1
|
|
|
- assert o_num == o_ok, f'Transaction has {o_num} output{suf(o_num)} (should have {o_ok})'
|
|
|
- await self.make_txobj()
|
|
|
- self.serialized = {k:v if v is None else str(v) for k, v in self.txobj.items() if k != 'token_to'}
|
|
|
- self.update_txid()
|
|
|
-
|
|
|
- def update_txid(self):
|
|
|
- import json
|
|
|
- assert not is_hex_str(self.serialized), (
|
|
|
- 'update_txid() must be called only when self.serialized is not hex data')
|
|
|
- self.txid = MMGenTxID(make_chksum_6(json.dumps(self.serialized)).upper())
|
|
|
-
|
|
|
def set_gas_with_data(self, data):
|
|
|
if not self.is_token:
|
|
|
self.gas = self.dfl_gas + self.byte_cost * len(data)
|
|
|
@@ -98,47 +79,6 @@ class New(Base, TxBase.New):
|
|
|
self.gas += self.byte_cost * extra_data_len
|
|
|
self._gas_adjusted = True
|
|
|
|
|
|
- async def process_cmdline_args(self, cmd_args, ad_f, ad_w):
|
|
|
-
|
|
|
- lc = len(cmd_args)
|
|
|
-
|
|
|
- if lc == 2 and self.is_swap:
|
|
|
- data_arg = cmd_args.pop()
|
|
|
- lc = 1
|
|
|
- assert data_arg.startswith('data:'), f'{data_arg}: invalid data arg (must start with "data:")'
|
|
|
- self.swap_memo = data_arg.removeprefix('data:')
|
|
|
- self.set_gas_with_data(self.swap_memo.encode())
|
|
|
-
|
|
|
- if lc == 0 and self.usr_contract_data and 'Token' not in self.name:
|
|
|
- return
|
|
|
-
|
|
|
- if lc != 1:
|
|
|
- die(1, f'{lc} output{suf(lc)} specified, but Ethereum transactions must have exactly one')
|
|
|
-
|
|
|
- a = self.parse_cmdline_arg(self.proto, cmd_args[0], ad_f, ad_w)
|
|
|
-
|
|
|
- self.add_output(
|
|
|
- coinaddr = None if a.is_vault else a.addr,
|
|
|
- amt = self.proto.coin_amt(a.amt or '0'),
|
|
|
- is_chg = not a.amt,
|
|
|
- is_vault = a.is_vault)
|
|
|
-
|
|
|
- self.add_mmaddrs_to_outputs(ad_f, ad_w)
|
|
|
-
|
|
|
- def get_unspent_nums_from_user(self, unspent):
|
|
|
- from ....ui import line_input
|
|
|
- while True:
|
|
|
- reply = line_input(self.cfg, 'Enter an account to spend from: ').strip()
|
|
|
- if reply:
|
|
|
- if not is_int(reply):
|
|
|
- msg('Account number must be an integer')
|
|
|
- elif int(reply) < 1:
|
|
|
- msg('Account number must be >= 1')
|
|
|
- elif int(reply) > len(unspent):
|
|
|
- msg(f'Account number must be <= {len(unspent)}')
|
|
|
- else:
|
|
|
- return [int(reply)]
|
|
|
-
|
|
|
@property
|
|
|
def network_estimated_fee_label(self):
|
|
|
return 'Network-estimated'
|
|
|
@@ -153,17 +93,6 @@ class New(Base, TxBase.New):
|
|
|
Int(await self.rpc.call('eth_gasPrice'), base=16),
|
|
|
'eth_gasPrice')
|
|
|
|
|
|
- def check_chg_addr_is_wallet_addr(self):
|
|
|
- pass
|
|
|
-
|
|
|
- def check_fee(self):
|
|
|
- if not self.disable_fee_check:
|
|
|
- assert self.usr_fee <= self.proto.max_tx_fee
|
|
|
-
|
|
|
- @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.total_gas), from_unit=unit)
|
|
|
@@ -189,35 +118,6 @@ class New(Base, TxBase.New):
|
|
|
else:
|
|
|
return abs_fee
|
|
|
|
|
|
- def update_change_output(self, funds_left):
|
|
|
- if self.outputs and self.outputs[0].is_chg:
|
|
|
- self.update_output_amt(0, funds_left)
|
|
|
-
|
|
|
- async def get_input_addrs_from_inputs_opt(self):
|
|
|
- ret = []
|
|
|
- if self.cfg.inputs:
|
|
|
- data_root = (await TwCtl(self.cfg, self.proto)).data_root # must create new instance here
|
|
|
- errmsg = 'Address {!r} not in tracking wallet'
|
|
|
- for addr in self.cfg.inputs.split(','):
|
|
|
- if is_mmgen_id(self.proto, addr):
|
|
|
- for waddr in data_root:
|
|
|
- if data_root[waddr]['mmid'] == addr:
|
|
|
- ret.append(waddr)
|
|
|
- break
|
|
|
- else:
|
|
|
- die('UserAddressNotInWallet', errmsg.format(addr))
|
|
|
- elif is_coin_addr(self.proto, addr):
|
|
|
- if not addr in data_root:
|
|
|
- die('UserAddressNotInWallet', errmsg.format(addr))
|
|
|
- ret.append(addr)
|
|
|
- else:
|
|
|
- die(1, f'{addr!r}: not an MMGen ID or coin address')
|
|
|
- return ret
|
|
|
-
|
|
|
- def final_inputs_ok_msg(self, funds_left):
|
|
|
- chg = self.proto.coin_amt('0') if (self.outputs and self.outputs[0].is_chg) else funds_left
|
|
|
- return 'Transaction leaves {} {} in the sender’s account'.format(chg.hl(), self.proto.coin)
|
|
|
-
|
|
|
class TokenNew(TokenBase, New):
|
|
|
desc = 'transaction'
|
|
|
fee_is_approximate = True
|