chainlib-eth

Ethereum implementation of the chainlib interface
Info | Log | Files | Refs | README | LICENSE

encode.py (7307B)


      1 # SPDX-License-Identifier: GPL-3.0-or-later
      2 
      3 # standard imports
      4 import io
      5 import sys
      6 import os
      7 import json
      8 import argparse
      9 import logging
     10 import urllib
     11 import sha3
     12 
     13 # external imports
     14 #from chainlib.cli import flag_reset
     15 from chainlib.settings import ChainSettings
     16 from funga.eth.signer import EIP155Signer
     17 from funga.eth.keystore.dict import DictKeystore
     18 from hexathon import (
     19         add_0x,
     20         strip_0x,
     21         )
     22 
     23 # local imports
     24 import chainlib.eth.cli
     25 from chainlib.eth.cli.arg import (
     26         Arg,
     27         ArgFlag,
     28         process_args,
     29         )
     30 from chainlib.eth.cli.config import (
     31         Config,
     32         process_config,
     33         )
     34 from chainlib.eth.cli.log import process_log
     35 from chainlib.eth.cli.encode import CLIEncoder
     36 from chainlib.eth.constant import ZERO_ADDRESS
     37 from chainlib.eth.address import to_checksum
     38 from chainlib.eth.connection import EthHTTPConnection
     39 from chainlib.eth.settings import process_settings
     40 from chainlib.jsonrpc import (
     41         JSONRPCRequest,
     42         IntSequenceGenerator,
     43         )
     44 from chainlib.eth.tx import (
     45         TxFactory,
     46         TxFormat,
     47         raw,
     48         )
     49 from chainlib.error import SignerMissingException
     50 from chainlib.chain import ChainSpec
     51 from chainlib.eth.jsonrpc import to_blockheight_param
     52 from chainlib.eth.address import to_checksum_address
     53 
     54 logging.basicConfig(level=logging.WARNING)
     55 logg = logging.getLogger()
     56 
     57 script_dir = os.path.dirname(os.path.realpath(__file__)) 
     58 config_dir = os.path.join(script_dir, '..', 'data', 'config')
     59 
     60 
     61 def process_config_local(config, arg, args, flags):
     62     config.add(args.signature, '_SIGNATURE', False)
     63     config.add(args.contract_args, '_CONTRACT_ARGS', False)
     64     # workaround to avoid rpc lookup of fee parameters when using arg mode
     65     if args.mode == 'arg':
     66         config.add(0, '_FEE_PRICE', True)
     67         config.add(0, '_FEE_LIMIT', True)
     68     return config
     69 
     70 arg_flags = ArgFlag()
     71 arg = Arg(arg_flags)
     72 flags = arg_flags.STD_WRITE | arg_flags.EXEC | arg_flags.FEE | arg_flags.FMT_HUMAN | arg_flags.FMT_WIRE | arg_flags.FMT_RPC
     73 
     74 argparser = chainlib.eth.cli.ArgumentParser()
     75 argparser = process_args(argparser, arg, flags)
     76 argparser.add_argument('--mode', type=str, choices=['tx', 'call', 'arg'], help='Mode of operation')
     77 argparser.add_argument('--signature', type=str, help='Method signature to encode')
     78 argparser.add_argument('contract_args', type=str, nargs='*', help='arguments to encode')
     79 args = argparser.parse_args()
     80 
     81 logg = process_log(args, logg)
     82 
     83 config = Config()
     84 config = process_config(config, arg, args, flags)
     85 config = process_config_local(config, arg, args, flags)
     86 logg.debug('config loaded:\n{}'.format(config))
     87 
     88 settings = ChainSettings()
     89 settings = process_settings(settings, config)
     90 logg.debug('settings loaded:\n{}'.format(settings))
     91 
     92 
     93 def main():
     94 
     95     signer_address = ZERO_ADDRESS
     96     signer = None
     97     conn = settings.get('CONN')
     98     signer_address = settings.get('SENDER_ADDRESS')
     99 
    100     code = '0x'
    101     cli_encoder = CLIEncoder(signature=config.get('_SIGNATURE'))
    102 
    103     for arg in config.get('_CONTRACT_ARGS'):
    104         cli_encoder.add_from(arg)
    105     
    106     code += cli_encoder.get()
    107 
    108     exec_address = config.get('_EXEC_ADDRESS')
    109     if exec_address:
    110         exec_address = add_0x(to_checksum_address(exec_address))
    111 
    112     mode = args.mode
    113     if mode == None:
    114         if signer == None:
    115             mode = 'call'
    116         else:
    117             mode = 'tx'
    118 
    119     if not config.get('_SIGNATURE'):
    120         if mode != 'arg':
    121             logg.error('mode tx without contract method signature makes no sense. Use eth-get with --data instead.')
    122             sys.exit(1)
    123 
    124     if mode == 'arg':
    125         print(strip_0x(code))
    126         return
    127     elif not exec_address:
    128         logg.error('exec address (-e) must be defined with mode "{}"'.format(args.mode))
    129         sys.exit(1)
    130 
    131     if config.get('RPC_PROVIDER'):
    132         logg.debug('provider {}'.format(config.get('RPC_PROVIDER')))
    133         if not config.get('_FEE_LIMIT') or not config.get('_FEE_PRICE'):
    134             #gas_oracle = rpc.get_gas_oracle()
    135             gas_oracle = settings.get('GAS_ORACLE')
    136             (price, limit) = gas_oracle.get_gas()
    137         if not config.get('_FEE_PRICE'):
    138             config.add(price, '_FEE_PRICE')
    139         if not config.get('_FEE_LIMIT'):
    140             config.add(limit, '_FEE_LIMIT')
    141 
    142         if mode == 'tx':
    143             if not config.get('_NONCE'):
    144                 nonce_oracle = settings.get('NONCE_ORACLE') #rpc.get_nonce_oracle()
    145                 config.add(nonce_oracle.get_nonce(), '_NONCE')
    146     else: 
    147         for arg in [
    148                 '_FEE_PRICE',
    149                 '_FEE_LIMIT',
    150                 '_NONCE',
    151                 ]:
    152             if not config.get(arg):
    153                 logg.error('--{} must be specified when no rpc provider has been set.'.format(arg.replace('_', '-').lower()))
    154                 sys.exit(1)
    155 
    156 
    157     if mode == 'call': #signer == None or config.true('_NOTX'):
    158         c = TxFactory(settings.get('CHAIN_SPEC'))
    159         j = JSONRPCRequest(id_generator=settings.get('RPC_ID_GENERATOR'))
    160         o = j.template()
    161         gas_limit = add_0x(int.to_bytes(config.get('_FEE_LIMIT'), 8, byteorder='big').hex(), compact_value=True)
    162         gas_price = add_0x(int.to_bytes(config.get('_FEE_PRICE'), 8, byteorder='big').hex(), compact_value=True)
    163         o['method'] = 'eth_call'
    164         o['params'].append({
    165                 'to': exec_address,
    166                 'from': signer_address,
    167                 'value': '0x0',
    168                 'gas': gas_limit, # TODO: better get of network gas limit
    169                 'gasPrice': gas_price,
    170                 'data': add_0x(code),
    171                 })
    172         height = to_blockheight_param(config.get('_HEIGHT'))
    173         o['params'].append(height)
    174         o = j.finalize(o)
    175 
    176         if settings.get('RPC_SEND'):
    177             r = conn.do(o)
    178             try:
    179                 print(strip_0x(r))
    180                 return
    181             except ValueError:
    182                 sys.stderr.write('query returned an empty value ({})\n'.format(r))
    183                 sys.exit(1)
    184         else:
    185             print(o)
    186             return
    187 
    188     if settings.get('SIGNER') == None:
    189         logg.error('mode "tx" without signer does not make sense. Please specify a key file with -y.')
    190         sys.exit(1)
    191 
    192     if settings.get('CHAIN_SPEC') == None:
    193         raise ValueError('chain spec must be specified')
    194 
    195     c = TxFactory(
    196             settings.get('CHAIN_SPEC'),
    197             signer=settings.get('SIGNER'),
    198             gas_oracle=settings.get('FEE_ORACLE'),
    199             nonce_oracle=settings.get('NONCE_ORACLE'),
    200             )
    201 
    202     tx = c.template(
    203             settings.get('SENDER_ADDRESS'),
    204             settings.get('EXEC'),
    205             use_nonce=True,
    206             )
    207     tx = c.set_code(tx, code)
    208     tx_format = TxFormat.JSONRPC
    209     if config.get('_RAW'):
    210         tx_format = TxFormat.RLP_SIGNED
    211     (tx_hash_hex, o) = c.finalize(tx, tx_format=tx_format)
    212     if settings.get('RPC_SEND'):
    213         r = conn.do(o)
    214         if settings.get('WAIT'):
    215             r = conn.wait(tx_hash_hex)
    216             if r['status'] == 0:
    217                 logg.critical('VM revert. Wish I could tell you more')
    218                 sys.exit(1)
    219         print(tx_hash_hex)
    220     else:
    221         if config.get('_RAW'):
    222             o = strip_0x(o)
    223         print(o)
    224 
    225 if __name__ == '__main__':
    226     main()