From 55a72ed6d622c43dd1e3e88b5a7ba0cf8fb68f5f Mon Sep 17 00:00:00 2001 From: nolash Date: Wed, 6 Oct 2021 06:57:51 +0200 Subject: [PATCH 1/9] Add generic contract call/tx encoder tool --- chainlib/eth/runnable/encode.py | 219 +++++++++++++++++++++++++++++ chainlib/eth/runnable/gas.py | 4 +- chainlib/eth/runnable/raw.py | 61 ++++---- chainlib/eth/tx.py | 1 - chainlib/eth/unittest/ethtester.py | 5 +- requirements.txt | 4 +- setup.cfg | 2 +- 7 files changed, 259 insertions(+), 37 deletions(-) create mode 100644 chainlib/eth/runnable/encode.py diff --git a/chainlib/eth/runnable/encode.py b/chainlib/eth/runnable/encode.py new file mode 100644 index 0000000..0c8d2af --- /dev/null +++ b/chainlib/eth/runnable/encode.py @@ -0,0 +1,219 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +# standard imports +import io +import sys +import os +import json +import argparse +import logging +import urllib +import re +import sha3 + +# external imports +import chainlib.eth.cli +from crypto_dev_signer.eth.signer import ReferenceSigner as EIP155Signer +from crypto_dev_signer.keystore.dict import DictKeystore +from hexathon import ( + add_0x, + strip_0x, + ) + +# local imports +from chainlib.eth.constant import ZERO_ADDRESS +from chainlib.eth.address import to_checksum +from chainlib.eth.connection import EthHTTPConnection +from chainlib.jsonrpc import ( + JSONRPCRequest, + IntSequenceGenerator, + ) +from chainlib.eth.nonce import ( + RPCNonceOracle, + OverrideNonceOracle, + ) +from chainlib.eth.gas import ( + RPCGasOracle, + OverrideGasOracle, + ) +from chainlib.eth.tx import ( + TxFactory, + TxFormat, + raw, + ) +from chainlib.eth.contract import ( + ABIMethodEncoder, + ABIContractEncoder, + ABIContractType, + ) +from chainlib.error import SignerMissingException +from chainlib.chain import ChainSpec +from chainlib.eth.runnable.util import decode_for_puny_humans +from chainlib.eth.jsonrpc import to_blockheight_param + +logging.basicConfig(level=logging.WARNING) +logg = logging.getLogger() + +script_dir = os.path.dirname(os.path.realpath(__file__)) +config_dir = os.path.join(script_dir, '..', 'data', 'config') + +arg_flags = chainlib.eth.cli.argflag_std_write | chainlib.eth.cli.Flag.EXEC +argparser = chainlib.eth.cli.ArgumentParser(arg_flags) +argparser = chainlib.eth.cli.ArgumentParser(arg_flags) +argparser.add_argument('--signature', type=str, help='Method signature to encode') +argparser.add_argument('contract_args', type=str, nargs='*', help='arguments to encode') +args = argparser.parse_args() +config = chainlib.eth.cli.Config.from_args(args, arg_flags, default_config_dir=config_dir) + +if not config.get('_EXEC_ADDRESS'): + argparser.error('exec address (-e) must be defined') + +block_all = args.ww +block_last = args.w or block_all + +wallet = chainlib.eth.cli.Wallet(EIP155Signer) +wallet.from_config(config) + +rpc = chainlib.eth.cli.Rpc(wallet=wallet) +conn = rpc.connect_by_config(config) + +send = config.true('_RPC_SEND') + +chain_spec = None +try: + chain_spec = ChainSpec.from_chain_str(config.get('CHAIN_SPEC')) +except AttributeError: + pass + + +class CLIEncoder: + + __re_uint = r'^([uU])[int]*([0-9]+)?$' + __re_bytes = r'^([bB])[ytes]*([0-9]+)?$' + __re_string = r'^([sS])[tring]*$' + __translations = [ + 'to_uint', + 'to_bytes', + 'to_string', + ] + + def to_uint(self, typ): + s = None + a = None + m = re.match(self.__re_uint, typ) + if m == None: + return None + + n = m.group(2) + if m.group(2) == None: + n = 256 + s = 'UINT256'.format(m.group(2)) + a = getattr(ABIContractType, s) + return (s, a) + + + def to_bytes(self, typ): + s = None + a = None + m = re.match(self.__re_bytes, typ) + if m == None: + return None + + n = m.group(2) + if n == None: + n = 32 + s = 'BYTES{}'.format(n) + a = getattr(ABIContractType, s) + return (s, a) + + + def to_string(self, typ): + m = re.match(self.__re_string, typ) + if m == None: + return None + s = 'STRING' + a = getattr(ABIContractType, s) + return (s, a) + + + def translate_type(self, typ): + r = None + for tr in self.__translations: + r = getattr(self, tr)(typ) + if r != None: + break + if r == None: + raise ValueError('no translation for type {}'.format(typ)) + logg.debug('type {} translated to {}'.format(typ, r[0])) + return r[1] + + +def main(): + + signer_address = ZERO_ADDRESS + signer = None + try: + signer = rpc.get_signer() + signer_address = rpc.get_signer_address() + except SignerMissingException: + pass + + code = '0x' + cli_encoder = CLIEncoder() + contract_encoder = ABIContractEncoder() + + if args.signature: + contract_encoder.method(args.signature) + + for arg in args.contract_args: + logg.debug('arg {}'.format(arg)) + (typ, val) = arg.split(':', maxsplit=1) + real_typ = cli_encoder.translate_type(typ) + contract_encoder.typ(real_typ) + fn = getattr(contract_encoder, real_typ.value) + fn(val) + + code += contract_encoder.get() + + if signer == None: + c = TxFactory(chain_spec) + j = JSONRPCRequest(id_generator=rpc.id_generator) + o = j.template() + o['method'] = 'eth_call' + o['params'].append({ + 'to': exec_address, + 'from': signer_address, + 'value': '0x00', + 'gas': add_0x(int.to_bytes(8000000, 8, byteorder='big').hex()), # TODO: better get of network gas limit + 'gasPrice': '0x01', + 'data': add_0x(code), + }) + height = to_blockheight_param(config.get('_HEIGHT')) + o['params'].append(height) + o = j.finalize(o) + r = conn.do(r) + try: + print(strip_0x(r)) + return + except ValueError: + sys.stderr.write('query returned an empty value ({})\n'.format(r)) + sys.exit(1) + + if chain_spec == None: + raise ValueError('chain spec must be specified') + + c = TxFactory(chain_spec, signer=signer, gas_oracle=rpc.get_gas_oracle(), nonce_oracle=rpc.get_nonce_oracle()) + tx = c.template(signer_address, config.get('_EXEC_ADDRESS'), use_nonce=True) + tx = c.set_code(tx, code) + tx_format = TxFormat.JSONRPC + if config.get('_RAW'): + tx_format = TxFormat.RLP_SIGNED + (tx_hash_hex, o) = c.finalize(tx, tx_format=tx_format) + if send: + r = conn.do(r) + print(r) + else: + print(o) + +if __name__ == '__main__': + main() diff --git a/chainlib/eth/runnable/gas.py b/chainlib/eth/runnable/gas.py index 9e1638d..02001f1 100644 --- a/chainlib/eth/runnable/gas.py +++ b/chainlib/eth/runnable/gas.py @@ -80,7 +80,7 @@ def main(): if logg.isEnabledFor(logging.DEBUG): try: sender_balance = balance(signer_address, rpc.id_generator) - recipient_balance = balance(recipient, rpc.id_generator) + recipient_balance = balance(add_0x(recipient), rpc.id_generator) logg.debug('sender {} balance before: {}'.format(signer_address, sender_balance)) logg.debug('recipient {} balance before: {}'.format(recipient, recipient_balance)) except urllib.error.URLError: @@ -94,7 +94,7 @@ def main(): r = conn.wait(tx_hash_hex) if logg.isEnabledFor(logging.DEBUG): sender_balance = balance(signer_address, rpc.id_generator) - recipient_balance = balance(recipient, rpc.id_generator) + recipient_balance = balance(add_0x(recipient), rpc.id_generator) logg.debug('sender {} balance after: {}'.format(signer_address, sender_balance)) logg.debug('recipient {} balance after: {}'.format(recipient, recipient_balance)) if r['status'] == 0: diff --git a/chainlib/eth/runnable/raw.py b/chainlib/eth/runnable/raw.py index d893597..7f92996 100644 --- a/chainlib/eth/runnable/raw.py +++ b/chainlib/eth/runnable/raw.py @@ -50,6 +50,7 @@ config_dir = os.path.join(script_dir, '..', 'data', 'config') arg_flags = chainlib.eth.cli.argflag_std_write | chainlib.eth.cli.Flag.EXEC argparser = chainlib.eth.cli.ArgumentParser(arg_flags) +argparser.add_argument('--deploy', action='store_true', help='Deploy data as contract') argparser.add_positional('data', type=str, help='Transaction data') args = argparser.parse_args() config = chainlib.eth.cli.Config.from_args(args, arg_flags, default_config_dir=config_dir) @@ -65,8 +66,8 @@ conn = rpc.connect_by_config(config) send = config.true('_RPC_SEND') -if config.get('_EXEC_ADDRESS') != None: - send = False +#if config.get('_EXEC_ADDRESS') != None: +# send = False chain_spec = None try: @@ -83,34 +84,37 @@ def main(): except SignerMissingException: pass - if config.get('_EXEC_ADDRESS') != None: - exec_address = add_0x(to_checksum(config.get('_EXEC_ADDRESS'))) - if not args.u and exec_address != add_0x(exec_address): - raise ValueError('invalid checksum address') + if config.get('_EXEC_ADDRESS') != None or args.deploy: + exec_address = None + if config.get('_EXEC_ADDRESS') != None: + exec_address = add_0x(to_checksum(config.get('_EXEC_ADDRESS'))) + #if not args.u and exec_address != add_0x(exec_address): + if not args.u and exec_address != exec_address: + raise ValueError('invalid checksum address') - j = JSONRPCRequest(id_generator=rpc.id_generator) - o = j.template() - o['method'] = 'eth_call' - o['params'].append({ - 'to': exec_address, - 'from': signer_address, - 'value': '0x00', - 'gas': add_0x(int.to_bytes(8000000, 8, byteorder='big').hex()), # TODO: better get of network gas limit - 'gasPrice': '0x01', - 'data': add_0x(args.data), - }) - height = to_blockheight_param(config.get('_HEIGHT')) - o['params'].append(height) - o = j.finalize(o) - r = conn.do(o) - try: - print(strip_0x(r)) - except ValueError: - sys.stderr.write('query returned an empty value\n') - sys.exit(1) - return + if signer_address == None: + j = JSONRPCRequest(id_generator=rpc.id_generator) + o = j.template() + o['method'] = 'eth_call' + o['params'].append({ + 'to': exec_address, + 'from': signer_address, + 'value': '0x00', + 'gas': add_0x(int.to_bytes(8000000, 8, byteorder='big').hex()), # TODO: better get of network gas limit + 'gasPrice': '0x01', + 'data': add_0x(args.data), + }) + height = to_blockheight_param(config.get('_HEIGHT')) + o['params'].append(height) + o = j.finalize(o) + r = conn.do(o) + try: + print(strip_0x(r)) + except ValueError: + sys.stderr.write('query returned an empty value ({})\n'.format(r)) + sys.exit(1) - if signer_address != None: + else: if chain_spec == None: raise ValueError('chain spec must be specified') g = TxFactory(chain_spec, signer=rpc.get_signer(), gas_oracle=rpc.get_gas_oracle(), nonce_oracle=rpc.get_nonce_oracle()) @@ -125,7 +129,6 @@ def main(): print(r) else: print(o) - print(tx_hash_hex) else: o = raw(args.data, id_generator=rpc.id_generator) diff --git a/chainlib/eth/tx.py b/chainlib/eth/tx.py index 7b0ee86..b0e0c6f 100644 --- a/chainlib/eth/tx.py +++ b/chainlib/eth/tx.py @@ -167,7 +167,6 @@ def __unpack_raw(tx_raw_bytes, chain_id=1): s[32-len(d[8]):] = d[8] logg.debug('vb {}'.format(vb)) sig = b''.join([r, s, bytes([vb])]) - #so = KeyAPI.Signature(signature_bytes=sig) h = sha3.keccak_256() h.update(rlp_encode(d)) diff --git a/chainlib/eth/unittest/ethtester.py b/chainlib/eth/unittest/ethtester.py index ed3eba2..1c54c0b 100644 --- a/chainlib/eth/unittest/ethtester.py +++ b/chainlib/eth/unittest/ethtester.py @@ -26,7 +26,8 @@ from chainlib.connection import ( from chainlib.eth.address import to_checksum_address from chainlib.chain import ChainSpec -logg = logging.getLogger(__name__) +#logg = logging.getLogger(__name__) +logg = logging.getLogger() test_address = bytes.fromhex('Eb3907eCad74a0013c259D5874AE7f22DcBcC95C') @@ -63,6 +64,7 @@ class EthTesterCase(unittest.TestCase): self.helper = eth_tester_instance self.backend = self.helper.backend self.rpc = TestRPCConnection(None, eth_tester_instance, self.signer) + for a in self.keystore.list(): self.accounts.append(add_0x(to_checksum_address(a))) @@ -73,7 +75,6 @@ class EthTesterCase(unittest.TestCase): RPCConnection.register_constructor(ConnType.CUSTOM, rpc_with_tester, tag='signer') RPCConnection.register_location('custom', self.chain_spec, tag='default', exist_ok=True) RPCConnection.register_location('custom', self.chain_spec, tag='signer', exist_ok=True) - def tearDown(self): diff --git a/requirements.txt b/requirements.txt index 3de6992..4d12f7a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ -crypto-dev-signer>=0.4.15a4,<=0.4.15 +crypto-dev-signer>=0.4.15rc2,<=0.4.15 pysha3==1.0.2 hexathon~=0.0.1a8 websocket-client==0.57.0 potaahto~=0.0.1a1 -chainlib==0.0.9a10 +chainlib==0.0.9a11 confini>=0.4.1a1,<0.5.0 diff --git a/setup.cfg b/setup.cfg index 5262482..d41a8d7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = chainlib-eth -version = 0.0.9a13 +version = 0.0.9a16 description = Ethereum implementation of the chainlib interface author = Louis Holbrook author_email = dev@holbrook.no From 75c72b78283f2bd1e784ed16ccce0dc0789d7258 Mon Sep 17 00:00:00 2001 From: nolash Date: Wed, 6 Oct 2021 07:13:05 +0200 Subject: [PATCH 2/9] Incorporate reset method for arg flags --- chainlib/eth/cli.py | 1 + chainlib/eth/runnable/encode.py | 1 - chainlib/eth/runnable/raw.py | 3 --- requirements.txt | 2 +- setup.cfg | 2 +- 5 files changed, 3 insertions(+), 6 deletions(-) diff --git a/chainlib/eth/cli.py b/chainlib/eth/cli.py index 985f1ef..6b32162 100644 --- a/chainlib/eth/cli.py +++ b/chainlib/eth/cli.py @@ -8,6 +8,7 @@ from chainlib.cli import ( argflag_std_read, argflag_std_write, argflag_std_base, + reset as argflag_reset, Config as BaseConfig, Wallet as BaseWallet, Rpc as BaseRpc, Flag, diff --git a/chainlib/eth/runnable/encode.py b/chainlib/eth/runnable/encode.py index 0c8d2af..d0ab4a8 100644 --- a/chainlib/eth/runnable/encode.py +++ b/chainlib/eth/runnable/encode.py @@ -59,7 +59,6 @@ config_dir = os.path.join(script_dir, '..', 'data', 'config') arg_flags = chainlib.eth.cli.argflag_std_write | chainlib.eth.cli.Flag.EXEC argparser = chainlib.eth.cli.ArgumentParser(arg_flags) -argparser = chainlib.eth.cli.ArgumentParser(arg_flags) argparser.add_argument('--signature', type=str, help='Method signature to encode') argparser.add_argument('contract_args', type=str, nargs='*', help='arguments to encode') args = argparser.parse_args() diff --git a/chainlib/eth/runnable/raw.py b/chainlib/eth/runnable/raw.py index 7f92996..4cd3dd5 100644 --- a/chainlib/eth/runnable/raw.py +++ b/chainlib/eth/runnable/raw.py @@ -66,9 +66,6 @@ conn = rpc.connect_by_config(config) send = config.true('_RPC_SEND') -#if config.get('_EXEC_ADDRESS') != None: -# send = False - chain_spec = None try: chain_spec = ChainSpec.from_chain_str(config.get('CHAIN_SPEC')) diff --git a/requirements.txt b/requirements.txt index 4d12f7a..3ba976b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,5 +3,5 @@ pysha3==1.0.2 hexathon~=0.0.1a8 websocket-client==0.57.0 potaahto~=0.0.1a1 -chainlib==0.0.9a11 +chainlib==0.0.9rc1 confini>=0.4.1a1,<0.5.0 diff --git a/setup.cfg b/setup.cfg index d41a8d7..d9de2e5 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = chainlib-eth -version = 0.0.9a16 +version = 0.0.9rc1 description = Ethereum implementation of the chainlib interface author = Louis Holbrook author_email = dev@holbrook.no From e4d6b8d8454fcef2aed7930dfb352bcad7da7cd4 Mon Sep 17 00:00:00 2001 From: nolash Date: Wed, 6 Oct 2021 07:30:07 +0200 Subject: [PATCH 3/9] Add option to generate abi encoding without method --- chainlib/eth/runnable/encode.py | 19 +++++++++++++++---- chainlib/eth/runnable/raw.py | 2 ++ 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/chainlib/eth/runnable/encode.py b/chainlib/eth/runnable/encode.py index d0ab4a8..0edb446 100644 --- a/chainlib/eth/runnable/encode.py +++ b/chainlib/eth/runnable/encode.py @@ -62,7 +62,11 @@ argparser = chainlib.eth.cli.ArgumentParser(arg_flags) argparser.add_argument('--signature', type=str, help='Method signature to encode') argparser.add_argument('contract_args', type=str, nargs='*', help='arguments to encode') args = argparser.parse_args() -config = chainlib.eth.cli.Config.from_args(args, arg_flags, default_config_dir=config_dir) +extra_args = { + 'signature': None, + 'contract_args': None, + } +config = chainlib.eth.cli.Config.from_args(args, arg_flags, extra_args=extra_args, default_config_dir=config_dir) if not config.get('_EXEC_ADDRESS'): argparser.error('exec address (-e) must be defined') @@ -161,19 +165,24 @@ def main(): cli_encoder = CLIEncoder() contract_encoder = ABIContractEncoder() - if args.signature: + if config.get('_SIGNATURE'): contract_encoder.method(args.signature) - for arg in args.contract_args: + for arg in config.get('_CONTRACT_ARGS'): logg.debug('arg {}'.format(arg)) (typ, val) = arg.split(':', maxsplit=1) real_typ = cli_encoder.translate_type(typ) - contract_encoder.typ(real_typ) + if config.get('_SIGNATURE'): + contract_encoder.typ(real_typ) fn = getattr(contract_encoder, real_typ.value) fn(val) code += contract_encoder.get() + if not config.get('_SIGNATURE'): + print(strip_0x(code)) + return + if signer == None: c = TxFactory(chain_spec) j = JSONRPCRequest(id_generator=rpc.id_generator) @@ -212,6 +221,8 @@ def main(): r = conn.do(r) print(r) else: + if config.get('_RAW'): + o = strip_0x(o) print(o) if __name__ == '__main__': diff --git a/chainlib/eth/runnable/raw.py b/chainlib/eth/runnable/raw.py index 4cd3dd5..784d537 100644 --- a/chainlib/eth/runnable/raw.py +++ b/chainlib/eth/runnable/raw.py @@ -125,6 +125,8 @@ def main(): r = conn.do(o) print(r) else: + if config.get('_RAW'): + o = strip_0x(o) print(o) else: From 0e0dbf180ebeaed0cb5e9b3e6c33de0dd03070b7 Mon Sep 17 00:00:00 2001 From: nolash Date: Wed, 6 Oct 2021 07:44:53 +0200 Subject: [PATCH 4/9] Split cli helper classes to respective files --- chainlib/eth/cli/__init__.py | 4 ++ chainlib/eth/cli/arg.py | 9 ++++ chainlib/eth/cli/config.py | 33 ++++++++++++++ chainlib/eth/cli/encode.py | 70 +++++++++++++++++++++++++++++ chainlib/eth/{cli.py => cli/rpc.py} | 57 +---------------------- chainlib/eth/cli/wallet.py | 19 ++++++++ chainlib/eth/runnable/encode.py | 70 +---------------------------- setup.cfg | 2 + 8 files changed, 141 insertions(+), 123 deletions(-) create mode 100644 chainlib/eth/cli/__init__.py create mode 100644 chainlib/eth/cli/arg.py create mode 100644 chainlib/eth/cli/config.py create mode 100644 chainlib/eth/cli/encode.py rename chainlib/eth/{cli.py => cli/rpc.py} (50%) create mode 100644 chainlib/eth/cli/wallet.py diff --git a/chainlib/eth/cli/__init__.py b/chainlib/eth/cli/__init__.py new file mode 100644 index 0000000..3d8554e --- /dev/null +++ b/chainlib/eth/cli/__init__.py @@ -0,0 +1,4 @@ +from .arg import * +from .config import Config +from .rpc import Rpc +from .wallet import Wallet diff --git a/chainlib/eth/cli/arg.py b/chainlib/eth/cli/arg.py new file mode 100644 index 0000000..99a4b9e --- /dev/null +++ b/chainlib/eth/cli/arg.py @@ -0,0 +1,9 @@ +# external imports +from chainlib.cli import ( + ArgumentParser, + argflag_std_read, + argflag_std_write, + argflag_std_base, + reset as argflag_reset, + Flag, + ) diff --git a/chainlib/eth/cli/config.py b/chainlib/eth/cli/config.py new file mode 100644 index 0000000..2b47fdd --- /dev/null +++ b/chainlib/eth/cli/config.py @@ -0,0 +1,33 @@ +# standard imports +import os + +# external imports +from chainlib.cli import Config as BaseConfig + +script_dir = os.path.dirname(os.path.realpath(__file__)) +data_dir = os.path.join(script_dir, '..') + + +class Config(BaseConfig): + """Convenience constructor to set Ethereum defaults for the chainlib cli config object + """ + default_base_config_dir = os.path.join(data_dir, 'data', 'config') + default_fee_limit = 21000 + + @classmethod + def from_args(cls, args, arg_flags=0x0f, env=os.environ, extra_args={}, base_config_dir=None, default_config_dir=None, user_config_dir=None, default_fee_limit=None, logger=None, load_callback=None): + super(Config, cls).override_defaults(base_dir=cls.default_base_config_dir) + if default_fee_limit == None: + default_fee_limit = cls.default_fee_limit + config = BaseConfig.from_args(args, arg_flags=arg_flags, env=env, extra_args=extra_args, base_config_dir=base_config_dir, default_config_dir=default_config_dir, user_config_dir=user_config_dir, default_fee_limit=default_fee_limit, logger=logger, load_callback=load_callback) + + if not config.get('RPC_DIALECT'): + config.add('default', 'RPC_DIALECT', exists_ok=True) + elif config.get('RPC_DIALECT') not in [ + 'openethereum', + 'default', + ]: + raise ValueError('unknown rpc dialect {}'.format(config.get('RPC_DIALECT'))) + + return config + diff --git a/chainlib/eth/cli/encode.py b/chainlib/eth/cli/encode.py new file mode 100644 index 0000000..11cf0b7 --- /dev/null +++ b/chainlib/eth/cli/encode.py @@ -0,0 +1,70 @@ +# standard imports +import re +import logging + +# external imports +from chainlib.eth.contract import ABIContractType + +logg = logging.getLogger(__name__) + + +class CLIEncoder: + + __re_uint = r'^([uU])[int]*([0-9]+)?$' + __re_bytes = r'^([bB])[ytes]*([0-9]+)?$' + __re_string = r'^([sS])[tring]*$' + __translations = [ + 'to_uint', + 'to_bytes', + 'to_string', + ] + + def to_uint(self, typ): + s = None + a = None + m = re.match(self.__re_uint, typ) + if m == None: + return None + + n = m.group(2) + if m.group(2) == None: + n = 256 + s = 'UINT256'.format(m.group(2)) + a = getattr(ABIContractType, s) + return (s, a) + + + def to_bytes(self, typ): + s = None + a = None + m = re.match(self.__re_bytes, typ) + if m == None: + return None + + n = m.group(2) + if n == None: + n = 32 + s = 'BYTES{}'.format(n) + a = getattr(ABIContractType, s) + return (s, a) + + + def to_string(self, typ): + m = re.match(self.__re_string, typ) + if m == None: + return None + s = 'STRING' + a = getattr(ABIContractType, s) + return (s, a) + + + def translate_type(self, typ): + r = None + for tr in self.__translations: + r = getattr(self, tr)(typ) + if r != None: + break + if r == None: + raise ValueError('no translation for type {}'.format(typ)) + logg.debug('type {} translated to {}'.format(typ, r[0])) + return r[1] diff --git a/chainlib/eth/cli.py b/chainlib/eth/cli/rpc.py similarity index 50% rename from chainlib/eth/cli.py rename to chainlib/eth/cli/rpc.py index 6b32162..a9975a7 100644 --- a/chainlib/eth/cli.py +++ b/chainlib/eth/cli/rpc.py @@ -1,23 +1,8 @@ -# standard imports -import os -import logging - # external imports -from chainlib.cli import ( - ArgumentParser, - argflag_std_read, - argflag_std_write, - argflag_std_base, - reset as argflag_reset, - Config as BaseConfig, - Wallet as BaseWallet, - Rpc as BaseRpc, Flag, - ) -from crypto_dev_signer.eth.signer import ReferenceSigner as EIP155Signer +from chainlib.cli import Rpc as BaseRpc +from chainlib.eth.connection import EthHTTPConnection # local imports -from chainlib.eth.address import AddressChecksum -from chainlib.eth.connection import EthHTTPConnection from chainlib.eth.gas import ( OverrideGasOracle, RPCGasOracle, @@ -27,20 +12,6 @@ from chainlib.eth.nonce import ( RPCNonceOracle, ) -logg = logging.getLogger(__name__) - -script_dir = os.path.dirname(os.path.realpath(__file__)) - - -class Wallet(BaseWallet): - """Convenience constructor to set Ethereum defaults for chainlib cli Wallet object - - :param checksummer: Address checksummer object - :type checksummer: Implementation of chainlib.eth.address.AddressChecksum - """ - def __init__(self, checksummer=AddressChecksum): - super(Wallet, self).__init__(EIP155Signer, checksummer=checksummer) - class Rpc(BaseRpc): """Convenience constructor to set Ethereum defaults for chainlib cli Rpc object @@ -97,27 +68,3 @@ class Rpc(BaseRpc): def get_gas_oracle(self): return self.get_fee_oracle() - - -class Config(BaseConfig): - """Convenience constructor to set Ethereum defaults for the chainlib cli config object - """ - default_base_config_dir = os.path.join(script_dir, 'data', 'config') - default_fee_limit = 21000 - - @classmethod - def from_args(cls, args, arg_flags=0x0f, env=os.environ, extra_args={}, base_config_dir=None, default_config_dir=None, user_config_dir=None, default_fee_limit=None, logger=None, load_callback=None): - super(Config, cls).override_defaults(base_dir=cls.default_base_config_dir) - if default_fee_limit == None: - default_fee_limit = cls.default_fee_limit - config = BaseConfig.from_args(args, arg_flags=arg_flags, env=env, extra_args=extra_args, base_config_dir=base_config_dir, default_config_dir=default_config_dir, user_config_dir=user_config_dir, default_fee_limit=default_fee_limit, logger=logger, load_callback=load_callback) - - if not config.get('RPC_DIALECT'): - config.add('default', 'RPC_DIALECT', exists_ok=True) - elif config.get('RPC_DIALECT') not in [ - 'openethereum', - 'default', - ]: - raise ValueError('unknown rpc dialect {}'.format(config.get('RPC_DIALECT'))) - - return config diff --git a/chainlib/eth/cli/wallet.py b/chainlib/eth/cli/wallet.py new file mode 100644 index 0000000..94c3f78 --- /dev/null +++ b/chainlib/eth/cli/wallet.py @@ -0,0 +1,19 @@ +# external imports +from crypto_dev_signer.eth.signer import ReferenceSigner as EIP155Signer +from chainlib.cli import Wallet as BaseWallet + +# local imports +from chainlib.eth.address import AddressChecksum + + +class Wallet(BaseWallet): + """Convenience constructor to set Ethereum defaults for chainlib cli Wallet object + + :param checksummer: Address checksummer object + :type checksummer: Implementation of chainlib.eth.address.AddressChecksum + """ + def __init__(self, checksummer=AddressChecksum): + super(Wallet, self).__init__(EIP155Signer, checksummer=checksummer) + + + diff --git a/chainlib/eth/runnable/encode.py b/chainlib/eth/runnable/encode.py index 0edb446..bed4c70 100644 --- a/chainlib/eth/runnable/encode.py +++ b/chainlib/eth/runnable/encode.py @@ -8,11 +8,11 @@ import json import argparse import logging import urllib -import re import sha3 # external imports import chainlib.eth.cli +from chainlib.eth.cli.encode import CLIEncoder from crypto_dev_signer.eth.signer import ReferenceSigner as EIP155Signer from crypto_dev_signer.keystore.dict import DictKeystore from hexathon import ( @@ -41,15 +41,11 @@ from chainlib.eth.tx import ( TxFormat, raw, ) -from chainlib.eth.contract import ( - ABIMethodEncoder, - ABIContractEncoder, - ABIContractType, - ) from chainlib.error import SignerMissingException from chainlib.chain import ChainSpec from chainlib.eth.runnable.util import decode_for_puny_humans from chainlib.eth.jsonrpc import to_blockheight_param +from chainlib.eth.contract import ABIContractEncoder logging.basicConfig(level=logging.WARNING) logg = logging.getLogger() @@ -89,68 +85,6 @@ except AttributeError: pass -class CLIEncoder: - - __re_uint = r'^([uU])[int]*([0-9]+)?$' - __re_bytes = r'^([bB])[ytes]*([0-9]+)?$' - __re_string = r'^([sS])[tring]*$' - __translations = [ - 'to_uint', - 'to_bytes', - 'to_string', - ] - - def to_uint(self, typ): - s = None - a = None - m = re.match(self.__re_uint, typ) - if m == None: - return None - - n = m.group(2) - if m.group(2) == None: - n = 256 - s = 'UINT256'.format(m.group(2)) - a = getattr(ABIContractType, s) - return (s, a) - - - def to_bytes(self, typ): - s = None - a = None - m = re.match(self.__re_bytes, typ) - if m == None: - return None - - n = m.group(2) - if n == None: - n = 32 - s = 'BYTES{}'.format(n) - a = getattr(ABIContractType, s) - return (s, a) - - - def to_string(self, typ): - m = re.match(self.__re_string, typ) - if m == None: - return None - s = 'STRING' - a = getattr(ABIContractType, s) - return (s, a) - - - def translate_type(self, typ): - r = None - for tr in self.__translations: - r = getattr(self, tr)(typ) - if r != None: - break - if r == None: - raise ValueError('no translation for type {}'.format(typ)) - logg.debug('type {} translated to {}'.format(typ, r[0])) - return r[1] - - def main(): signer_address = ZERO_ADDRESS diff --git a/setup.cfg b/setup.cfg index d9de2e5..596249d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -32,6 +32,7 @@ packages = chainlib.eth.runnable chainlib.eth.pytest chainlib.eth.unittest + chainlib.eth.cli [options.entry_points] console_scripts = @@ -41,6 +42,7 @@ console_scripts = eth-raw = chainlib.eth.runnable.raw:main eth-get = chainlib.eth.runnable.get:main eth-decode = chainlib.eth.runnable.decode:main + eth-encode = chainlib.eth.runnable.encode:main eth-info = chainlib.eth.runnable.info:main eth-nonce = chainlib.eth.runnable.count:main eth = chainlib.eth.runnable.info:main From b983938d8031a916d98b178a26e15ca5ef36487e Mon Sep 17 00:00:00 2001 From: nolash Date: Wed, 6 Oct 2021 07:52:19 +0200 Subject: [PATCH 5/9] Move all encoding steps to cliencoder --- chainlib/eth/cli/encode.py | 23 +++++++++++++++++++++-- chainlib/eth/runnable/encode.py | 19 ++++--------------- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/chainlib/eth/cli/encode.py b/chainlib/eth/cli/encode.py index 11cf0b7..7dbb14e 100644 --- a/chainlib/eth/cli/encode.py +++ b/chainlib/eth/cli/encode.py @@ -3,12 +3,15 @@ import re import logging # external imports -from chainlib.eth.contract import ABIContractType +from chainlib.eth.contract import ( + ABIContractType, + ABIContractEncoder, + ) logg = logging.getLogger(__name__) -class CLIEncoder: +class CLIEncoder(ABIContractEncoder): __re_uint = r'^([uU])[int]*([0-9]+)?$' __re_bytes = r'^([bB])[ytes]*([0-9]+)?$' @@ -19,6 +22,12 @@ class CLIEncoder: 'to_string', ] + def __init__(self, signature=None): + super(CLIEncoder, self).__init__() + self.signature = signature + if signature != None: + self.method(signature) + def to_uint(self, typ): s = None a = None @@ -68,3 +77,13 @@ class CLIEncoder: raise ValueError('no translation for type {}'.format(typ)) logg.debug('type {} translated to {}'.format(typ, r[0])) return r[1] + + + def add_from(self, arg): + logg.debug('arg {}'.format(arg)) + (typ, val) = arg.split(':', maxsplit=1) + real_typ = self.translate_type(typ) + if self.signature != None: + self.typ(real_typ) + fn = getattr(self, real_typ.value) + fn(val) diff --git a/chainlib/eth/runnable/encode.py b/chainlib/eth/runnable/encode.py index bed4c70..b2a7f93 100644 --- a/chainlib/eth/runnable/encode.py +++ b/chainlib/eth/runnable/encode.py @@ -45,7 +45,6 @@ from chainlib.error import SignerMissingException from chainlib.chain import ChainSpec from chainlib.eth.runnable.util import decode_for_puny_humans from chainlib.eth.jsonrpc import to_blockheight_param -from chainlib.eth.contract import ABIContractEncoder logging.basicConfig(level=logging.WARNING) logg = logging.getLogger() @@ -96,22 +95,12 @@ def main(): pass code = '0x' - cli_encoder = CLIEncoder() - contract_encoder = ABIContractEncoder() - - if config.get('_SIGNATURE'): - contract_encoder.method(args.signature) + cli_encoder = CLIEncoder(signature=config.get('_SIGNATURE')) for arg in config.get('_CONTRACT_ARGS'): - logg.debug('arg {}'.format(arg)) - (typ, val) = arg.split(':', maxsplit=1) - real_typ = cli_encoder.translate_type(typ) - if config.get('_SIGNATURE'): - contract_encoder.typ(real_typ) - fn = getattr(contract_encoder, real_typ.value) - fn(val) - - code += contract_encoder.get() + cli_encoder.add_from(arg) + + code += cli_encoder.get() if not config.get('_SIGNATURE'): print(strip_0x(code)) From ab8c510fa0462072a85c3c6b9a0312e91c615fd0 Mon Sep 17 00:00:00 2001 From: Louis Holbrook Date: Mon, 18 Oct 2021 10:07:03 +0000 Subject: [PATCH 6/9] refactor: Normalize signature verification --- chainlib/eth/unittest/base.py | 4 +++- requirements.txt | 2 +- setup.cfg | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/chainlib/eth/unittest/base.py b/chainlib/eth/unittest/base.py index e10d11b..533c168 100644 --- a/chainlib/eth/unittest/base.py +++ b/chainlib/eth/unittest/base.py @@ -5,6 +5,7 @@ import logging # external imports import eth_tester import coincurve +from chainlib.encode import TxHexNormalizer from chainlib.connection import ( RPCConnection, error_parser, @@ -184,7 +185,8 @@ class TestRPCConnection(RPCConnection): pk_bytes = self.backend.keystore.get(tx.sender) pk = coincurve.PrivateKey(secret=pk_bytes) result_address = private_key_to_address(pk) - assert strip_0x(result_address) == strip_0x(tx.sender) + tx_normalize = TxHexNormalizer() + assert tx_normalize.wallet_address(strip_0x(result_address)) == tx_normalize.wallet_address(strip_0x(tx.sender)) def sign_transaction(self, tx, passphrase=''): diff --git a/requirements.txt b/requirements.txt index 3de6992..ee71e7d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,5 +3,5 @@ pysha3==1.0.2 hexathon~=0.0.1a8 websocket-client==0.57.0 potaahto~=0.0.1a1 -chainlib==0.0.9a10 +chainlib==0.0.9rc1 confini>=0.4.1a1,<0.5.0 diff --git a/setup.cfg b/setup.cfg index 5262482..0e20f3a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = chainlib-eth -version = 0.0.9a13 +version = 0.0.9rc3 description = Ethereum implementation of the chainlib interface author = Louis Holbrook author_email = dev@holbrook.no From 94e016366ba309d6f8b0633d4aaa3fc272b7a06c Mon Sep 17 00:00:00 2001 From: Louis Holbrook Date: Mon, 18 Oct 2021 10:18:20 +0000 Subject: [PATCH 7/9] feat: Add generic contract tx/call encoder cli tool --- chainlib/eth/cli/__init__.py | 4 + chainlib/eth/cli/arg.py | 9 ++ chainlib/eth/cli/config.py | 33 ++++++ chainlib/eth/cli/encode.py | 89 ++++++++++++++++ chainlib/eth/{cli.py => cli/rpc.py} | 56 +--------- chainlib/eth/cli/wallet.py | 19 ++++ chainlib/eth/runnable/encode.py | 152 ++++++++++++++++++++++++++++ chainlib/eth/runnable/gas.py | 4 +- chainlib/eth/runnable/raw.py | 62 ++++++------ chainlib/eth/tx.py | 1 - chainlib/eth/unittest/ethtester.py | 5 +- requirements.txt | 2 +- setup.cfg | 2 + 13 files changed, 348 insertions(+), 90 deletions(-) create mode 100644 chainlib/eth/cli/__init__.py create mode 100644 chainlib/eth/cli/arg.py create mode 100644 chainlib/eth/cli/config.py create mode 100644 chainlib/eth/cli/encode.py rename chainlib/eth/{cli.py => cli/rpc.py} (50%) create mode 100644 chainlib/eth/cli/wallet.py create mode 100644 chainlib/eth/runnable/encode.py diff --git a/chainlib/eth/cli/__init__.py b/chainlib/eth/cli/__init__.py new file mode 100644 index 0000000..3d8554e --- /dev/null +++ b/chainlib/eth/cli/__init__.py @@ -0,0 +1,4 @@ +from .arg import * +from .config import Config +from .rpc import Rpc +from .wallet import Wallet diff --git a/chainlib/eth/cli/arg.py b/chainlib/eth/cli/arg.py new file mode 100644 index 0000000..99a4b9e --- /dev/null +++ b/chainlib/eth/cli/arg.py @@ -0,0 +1,9 @@ +# external imports +from chainlib.cli import ( + ArgumentParser, + argflag_std_read, + argflag_std_write, + argflag_std_base, + reset as argflag_reset, + Flag, + ) diff --git a/chainlib/eth/cli/config.py b/chainlib/eth/cli/config.py new file mode 100644 index 0000000..2b47fdd --- /dev/null +++ b/chainlib/eth/cli/config.py @@ -0,0 +1,33 @@ +# standard imports +import os + +# external imports +from chainlib.cli import Config as BaseConfig + +script_dir = os.path.dirname(os.path.realpath(__file__)) +data_dir = os.path.join(script_dir, '..') + + +class Config(BaseConfig): + """Convenience constructor to set Ethereum defaults for the chainlib cli config object + """ + default_base_config_dir = os.path.join(data_dir, 'data', 'config') + default_fee_limit = 21000 + + @classmethod + def from_args(cls, args, arg_flags=0x0f, env=os.environ, extra_args={}, base_config_dir=None, default_config_dir=None, user_config_dir=None, default_fee_limit=None, logger=None, load_callback=None): + super(Config, cls).override_defaults(base_dir=cls.default_base_config_dir) + if default_fee_limit == None: + default_fee_limit = cls.default_fee_limit + config = BaseConfig.from_args(args, arg_flags=arg_flags, env=env, extra_args=extra_args, base_config_dir=base_config_dir, default_config_dir=default_config_dir, user_config_dir=user_config_dir, default_fee_limit=default_fee_limit, logger=logger, load_callback=load_callback) + + if not config.get('RPC_DIALECT'): + config.add('default', 'RPC_DIALECT', exists_ok=True) + elif config.get('RPC_DIALECT') not in [ + 'openethereum', + 'default', + ]: + raise ValueError('unknown rpc dialect {}'.format(config.get('RPC_DIALECT'))) + + return config + diff --git a/chainlib/eth/cli/encode.py b/chainlib/eth/cli/encode.py new file mode 100644 index 0000000..7dbb14e --- /dev/null +++ b/chainlib/eth/cli/encode.py @@ -0,0 +1,89 @@ +# standard imports +import re +import logging + +# external imports +from chainlib.eth.contract import ( + ABIContractType, + ABIContractEncoder, + ) + +logg = logging.getLogger(__name__) + + +class CLIEncoder(ABIContractEncoder): + + __re_uint = r'^([uU])[int]*([0-9]+)?$' + __re_bytes = r'^([bB])[ytes]*([0-9]+)?$' + __re_string = r'^([sS])[tring]*$' + __translations = [ + 'to_uint', + 'to_bytes', + 'to_string', + ] + + def __init__(self, signature=None): + super(CLIEncoder, self).__init__() + self.signature = signature + if signature != None: + self.method(signature) + + def to_uint(self, typ): + s = None + a = None + m = re.match(self.__re_uint, typ) + if m == None: + return None + + n = m.group(2) + if m.group(2) == None: + n = 256 + s = 'UINT256'.format(m.group(2)) + a = getattr(ABIContractType, s) + return (s, a) + + + def to_bytes(self, typ): + s = None + a = None + m = re.match(self.__re_bytes, typ) + if m == None: + return None + + n = m.group(2) + if n == None: + n = 32 + s = 'BYTES{}'.format(n) + a = getattr(ABIContractType, s) + return (s, a) + + + def to_string(self, typ): + m = re.match(self.__re_string, typ) + if m == None: + return None + s = 'STRING' + a = getattr(ABIContractType, s) + return (s, a) + + + def translate_type(self, typ): + r = None + for tr in self.__translations: + r = getattr(self, tr)(typ) + if r != None: + break + if r == None: + raise ValueError('no translation for type {}'.format(typ)) + logg.debug('type {} translated to {}'.format(typ, r[0])) + return r[1] + + + def add_from(self, arg): + logg.debug('arg {}'.format(arg)) + (typ, val) = arg.split(':', maxsplit=1) + real_typ = self.translate_type(typ) + if self.signature != None: + self.typ(real_typ) + fn = getattr(self, real_typ.value) + fn(val) diff --git a/chainlib/eth/cli.py b/chainlib/eth/cli/rpc.py similarity index 50% rename from chainlib/eth/cli.py rename to chainlib/eth/cli/rpc.py index 985f1ef..a9975a7 100644 --- a/chainlib/eth/cli.py +++ b/chainlib/eth/cli/rpc.py @@ -1,22 +1,8 @@ -# standard imports -import os -import logging - # external imports -from chainlib.cli import ( - ArgumentParser, - argflag_std_read, - argflag_std_write, - argflag_std_base, - Config as BaseConfig, - Wallet as BaseWallet, - Rpc as BaseRpc, Flag, - ) -from crypto_dev_signer.eth.signer import ReferenceSigner as EIP155Signer +from chainlib.cli import Rpc as BaseRpc +from chainlib.eth.connection import EthHTTPConnection # local imports -from chainlib.eth.address import AddressChecksum -from chainlib.eth.connection import EthHTTPConnection from chainlib.eth.gas import ( OverrideGasOracle, RPCGasOracle, @@ -26,20 +12,6 @@ from chainlib.eth.nonce import ( RPCNonceOracle, ) -logg = logging.getLogger(__name__) - -script_dir = os.path.dirname(os.path.realpath(__file__)) - - -class Wallet(BaseWallet): - """Convenience constructor to set Ethereum defaults for chainlib cli Wallet object - - :param checksummer: Address checksummer object - :type checksummer: Implementation of chainlib.eth.address.AddressChecksum - """ - def __init__(self, checksummer=AddressChecksum): - super(Wallet, self).__init__(EIP155Signer, checksummer=checksummer) - class Rpc(BaseRpc): """Convenience constructor to set Ethereum defaults for chainlib cli Rpc object @@ -96,27 +68,3 @@ class Rpc(BaseRpc): def get_gas_oracle(self): return self.get_fee_oracle() - - -class Config(BaseConfig): - """Convenience constructor to set Ethereum defaults for the chainlib cli config object - """ - default_base_config_dir = os.path.join(script_dir, 'data', 'config') - default_fee_limit = 21000 - - @classmethod - def from_args(cls, args, arg_flags=0x0f, env=os.environ, extra_args={}, base_config_dir=None, default_config_dir=None, user_config_dir=None, default_fee_limit=None, logger=None, load_callback=None): - super(Config, cls).override_defaults(base_dir=cls.default_base_config_dir) - if default_fee_limit == None: - default_fee_limit = cls.default_fee_limit - config = BaseConfig.from_args(args, arg_flags=arg_flags, env=env, extra_args=extra_args, base_config_dir=base_config_dir, default_config_dir=default_config_dir, user_config_dir=user_config_dir, default_fee_limit=default_fee_limit, logger=logger, load_callback=load_callback) - - if not config.get('RPC_DIALECT'): - config.add('default', 'RPC_DIALECT', exists_ok=True) - elif config.get('RPC_DIALECT') not in [ - 'openethereum', - 'default', - ]: - raise ValueError('unknown rpc dialect {}'.format(config.get('RPC_DIALECT'))) - - return config diff --git a/chainlib/eth/cli/wallet.py b/chainlib/eth/cli/wallet.py new file mode 100644 index 0000000..94c3f78 --- /dev/null +++ b/chainlib/eth/cli/wallet.py @@ -0,0 +1,19 @@ +# external imports +from crypto_dev_signer.eth.signer import ReferenceSigner as EIP155Signer +from chainlib.cli import Wallet as BaseWallet + +# local imports +from chainlib.eth.address import AddressChecksum + + +class Wallet(BaseWallet): + """Convenience constructor to set Ethereum defaults for chainlib cli Wallet object + + :param checksummer: Address checksummer object + :type checksummer: Implementation of chainlib.eth.address.AddressChecksum + """ + def __init__(self, checksummer=AddressChecksum): + super(Wallet, self).__init__(EIP155Signer, checksummer=checksummer) + + + diff --git a/chainlib/eth/runnable/encode.py b/chainlib/eth/runnable/encode.py new file mode 100644 index 0000000..b2a7f93 --- /dev/null +++ b/chainlib/eth/runnable/encode.py @@ -0,0 +1,152 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +# standard imports +import io +import sys +import os +import json +import argparse +import logging +import urllib +import sha3 + +# external imports +import chainlib.eth.cli +from chainlib.eth.cli.encode import CLIEncoder +from crypto_dev_signer.eth.signer import ReferenceSigner as EIP155Signer +from crypto_dev_signer.keystore.dict import DictKeystore +from hexathon import ( + add_0x, + strip_0x, + ) + +# local imports +from chainlib.eth.constant import ZERO_ADDRESS +from chainlib.eth.address import to_checksum +from chainlib.eth.connection import EthHTTPConnection +from chainlib.jsonrpc import ( + JSONRPCRequest, + IntSequenceGenerator, + ) +from chainlib.eth.nonce import ( + RPCNonceOracle, + OverrideNonceOracle, + ) +from chainlib.eth.gas import ( + RPCGasOracle, + OverrideGasOracle, + ) +from chainlib.eth.tx import ( + TxFactory, + TxFormat, + raw, + ) +from chainlib.error import SignerMissingException +from chainlib.chain import ChainSpec +from chainlib.eth.runnable.util import decode_for_puny_humans +from chainlib.eth.jsonrpc import to_blockheight_param + +logging.basicConfig(level=logging.WARNING) +logg = logging.getLogger() + +script_dir = os.path.dirname(os.path.realpath(__file__)) +config_dir = os.path.join(script_dir, '..', 'data', 'config') + +arg_flags = chainlib.eth.cli.argflag_std_write | chainlib.eth.cli.Flag.EXEC +argparser = chainlib.eth.cli.ArgumentParser(arg_flags) +argparser.add_argument('--signature', type=str, help='Method signature to encode') +argparser.add_argument('contract_args', type=str, nargs='*', help='arguments to encode') +args = argparser.parse_args() +extra_args = { + 'signature': None, + 'contract_args': None, + } +config = chainlib.eth.cli.Config.from_args(args, arg_flags, extra_args=extra_args, default_config_dir=config_dir) + +if not config.get('_EXEC_ADDRESS'): + argparser.error('exec address (-e) must be defined') + +block_all = args.ww +block_last = args.w or block_all + +wallet = chainlib.eth.cli.Wallet(EIP155Signer) +wallet.from_config(config) + +rpc = chainlib.eth.cli.Rpc(wallet=wallet) +conn = rpc.connect_by_config(config) + +send = config.true('_RPC_SEND') + +chain_spec = None +try: + chain_spec = ChainSpec.from_chain_str(config.get('CHAIN_SPEC')) +except AttributeError: + pass + + +def main(): + + signer_address = ZERO_ADDRESS + signer = None + try: + signer = rpc.get_signer() + signer_address = rpc.get_signer_address() + except SignerMissingException: + pass + + code = '0x' + cli_encoder = CLIEncoder(signature=config.get('_SIGNATURE')) + + for arg in config.get('_CONTRACT_ARGS'): + cli_encoder.add_from(arg) + + code += cli_encoder.get() + + if not config.get('_SIGNATURE'): + print(strip_0x(code)) + return + + if signer == None: + c = TxFactory(chain_spec) + j = JSONRPCRequest(id_generator=rpc.id_generator) + o = j.template() + o['method'] = 'eth_call' + o['params'].append({ + 'to': exec_address, + 'from': signer_address, + 'value': '0x00', + 'gas': add_0x(int.to_bytes(8000000, 8, byteorder='big').hex()), # TODO: better get of network gas limit + 'gasPrice': '0x01', + 'data': add_0x(code), + }) + height = to_blockheight_param(config.get('_HEIGHT')) + o['params'].append(height) + o = j.finalize(o) + r = conn.do(r) + try: + print(strip_0x(r)) + return + except ValueError: + sys.stderr.write('query returned an empty value ({})\n'.format(r)) + sys.exit(1) + + if chain_spec == None: + raise ValueError('chain spec must be specified') + + c = TxFactory(chain_spec, signer=signer, gas_oracle=rpc.get_gas_oracle(), nonce_oracle=rpc.get_nonce_oracle()) + tx = c.template(signer_address, config.get('_EXEC_ADDRESS'), use_nonce=True) + tx = c.set_code(tx, code) + tx_format = TxFormat.JSONRPC + if config.get('_RAW'): + tx_format = TxFormat.RLP_SIGNED + (tx_hash_hex, o) = c.finalize(tx, tx_format=tx_format) + if send: + r = conn.do(r) + print(r) + else: + if config.get('_RAW'): + o = strip_0x(o) + print(o) + +if __name__ == '__main__': + main() diff --git a/chainlib/eth/runnable/gas.py b/chainlib/eth/runnable/gas.py index 9e1638d..02001f1 100644 --- a/chainlib/eth/runnable/gas.py +++ b/chainlib/eth/runnable/gas.py @@ -80,7 +80,7 @@ def main(): if logg.isEnabledFor(logging.DEBUG): try: sender_balance = balance(signer_address, rpc.id_generator) - recipient_balance = balance(recipient, rpc.id_generator) + recipient_balance = balance(add_0x(recipient), rpc.id_generator) logg.debug('sender {} balance before: {}'.format(signer_address, sender_balance)) logg.debug('recipient {} balance before: {}'.format(recipient, recipient_balance)) except urllib.error.URLError: @@ -94,7 +94,7 @@ def main(): r = conn.wait(tx_hash_hex) if logg.isEnabledFor(logging.DEBUG): sender_balance = balance(signer_address, rpc.id_generator) - recipient_balance = balance(recipient, rpc.id_generator) + recipient_balance = balance(add_0x(recipient), rpc.id_generator) logg.debug('sender {} balance after: {}'.format(signer_address, sender_balance)) logg.debug('recipient {} balance after: {}'.format(recipient, recipient_balance)) if r['status'] == 0: diff --git a/chainlib/eth/runnable/raw.py b/chainlib/eth/runnable/raw.py index d893597..784d537 100644 --- a/chainlib/eth/runnable/raw.py +++ b/chainlib/eth/runnable/raw.py @@ -50,6 +50,7 @@ config_dir = os.path.join(script_dir, '..', 'data', 'config') arg_flags = chainlib.eth.cli.argflag_std_write | chainlib.eth.cli.Flag.EXEC argparser = chainlib.eth.cli.ArgumentParser(arg_flags) +argparser.add_argument('--deploy', action='store_true', help='Deploy data as contract') argparser.add_positional('data', type=str, help='Transaction data') args = argparser.parse_args() config = chainlib.eth.cli.Config.from_args(args, arg_flags, default_config_dir=config_dir) @@ -65,9 +66,6 @@ conn = rpc.connect_by_config(config) send = config.true('_RPC_SEND') -if config.get('_EXEC_ADDRESS') != None: - send = False - chain_spec = None try: chain_spec = ChainSpec.from_chain_str(config.get('CHAIN_SPEC')) @@ -83,34 +81,37 @@ def main(): except SignerMissingException: pass - if config.get('_EXEC_ADDRESS') != None: - exec_address = add_0x(to_checksum(config.get('_EXEC_ADDRESS'))) - if not args.u and exec_address != add_0x(exec_address): - raise ValueError('invalid checksum address') + if config.get('_EXEC_ADDRESS') != None or args.deploy: + exec_address = None + if config.get('_EXEC_ADDRESS') != None: + exec_address = add_0x(to_checksum(config.get('_EXEC_ADDRESS'))) + #if not args.u and exec_address != add_0x(exec_address): + if not args.u and exec_address != exec_address: + raise ValueError('invalid checksum address') - j = JSONRPCRequest(id_generator=rpc.id_generator) - o = j.template() - o['method'] = 'eth_call' - o['params'].append({ - 'to': exec_address, - 'from': signer_address, - 'value': '0x00', - 'gas': add_0x(int.to_bytes(8000000, 8, byteorder='big').hex()), # TODO: better get of network gas limit - 'gasPrice': '0x01', - 'data': add_0x(args.data), - }) - height = to_blockheight_param(config.get('_HEIGHT')) - o['params'].append(height) - o = j.finalize(o) - r = conn.do(o) - try: - print(strip_0x(r)) - except ValueError: - sys.stderr.write('query returned an empty value\n') - sys.exit(1) - return + if signer_address == None: + j = JSONRPCRequest(id_generator=rpc.id_generator) + o = j.template() + o['method'] = 'eth_call' + o['params'].append({ + 'to': exec_address, + 'from': signer_address, + 'value': '0x00', + 'gas': add_0x(int.to_bytes(8000000, 8, byteorder='big').hex()), # TODO: better get of network gas limit + 'gasPrice': '0x01', + 'data': add_0x(args.data), + }) + height = to_blockheight_param(config.get('_HEIGHT')) + o['params'].append(height) + o = j.finalize(o) + r = conn.do(o) + try: + print(strip_0x(r)) + except ValueError: + sys.stderr.write('query returned an empty value ({})\n'.format(r)) + sys.exit(1) - if signer_address != None: + else: if chain_spec == None: raise ValueError('chain spec must be specified') g = TxFactory(chain_spec, signer=rpc.get_signer(), gas_oracle=rpc.get_gas_oracle(), nonce_oracle=rpc.get_nonce_oracle()) @@ -124,8 +125,9 @@ def main(): r = conn.do(o) print(r) else: + if config.get('_RAW'): + o = strip_0x(o) print(o) - print(tx_hash_hex) else: o = raw(args.data, id_generator=rpc.id_generator) diff --git a/chainlib/eth/tx.py b/chainlib/eth/tx.py index 7b0ee86..b0e0c6f 100644 --- a/chainlib/eth/tx.py +++ b/chainlib/eth/tx.py @@ -167,7 +167,6 @@ def __unpack_raw(tx_raw_bytes, chain_id=1): s[32-len(d[8]):] = d[8] logg.debug('vb {}'.format(vb)) sig = b''.join([r, s, bytes([vb])]) - #so = KeyAPI.Signature(signature_bytes=sig) h = sha3.keccak_256() h.update(rlp_encode(d)) diff --git a/chainlib/eth/unittest/ethtester.py b/chainlib/eth/unittest/ethtester.py index ed3eba2..1c54c0b 100644 --- a/chainlib/eth/unittest/ethtester.py +++ b/chainlib/eth/unittest/ethtester.py @@ -26,7 +26,8 @@ from chainlib.connection import ( from chainlib.eth.address import to_checksum_address from chainlib.chain import ChainSpec -logg = logging.getLogger(__name__) +#logg = logging.getLogger(__name__) +logg = logging.getLogger() test_address = bytes.fromhex('Eb3907eCad74a0013c259D5874AE7f22DcBcC95C') @@ -63,6 +64,7 @@ class EthTesterCase(unittest.TestCase): self.helper = eth_tester_instance self.backend = self.helper.backend self.rpc = TestRPCConnection(None, eth_tester_instance, self.signer) + for a in self.keystore.list(): self.accounts.append(add_0x(to_checksum_address(a))) @@ -73,7 +75,6 @@ class EthTesterCase(unittest.TestCase): RPCConnection.register_constructor(ConnType.CUSTOM, rpc_with_tester, tag='signer') RPCConnection.register_location('custom', self.chain_spec, tag='default', exist_ok=True) RPCConnection.register_location('custom', self.chain_spec, tag='signer', exist_ok=True) - def tearDown(self): diff --git a/requirements.txt b/requirements.txt index ee71e7d..3ba976b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -crypto-dev-signer>=0.4.15a4,<=0.4.15 +crypto-dev-signer>=0.4.15rc2,<=0.4.15 pysha3==1.0.2 hexathon~=0.0.1a8 websocket-client==0.57.0 diff --git a/setup.cfg b/setup.cfg index 0e20f3a..588f603 100644 --- a/setup.cfg +++ b/setup.cfg @@ -32,6 +32,7 @@ packages = chainlib.eth.runnable chainlib.eth.pytest chainlib.eth.unittest + chainlib.eth.cli [options.entry_points] console_scripts = @@ -41,6 +42,7 @@ console_scripts = eth-raw = chainlib.eth.runnable.raw:main eth-get = chainlib.eth.runnable.get:main eth-decode = chainlib.eth.runnable.decode:main + eth-encode = chainlib.eth.runnable.encode:main eth-info = chainlib.eth.runnable.info:main eth-nonce = chainlib.eth.runnable.count:main eth = chainlib.eth.runnable.info:main From a188c4e6bb4fe44f6cf0f9a1b26db2dfc254294b Mon Sep 17 00:00:00 2001 From: Louis Holbrook Date: Mon, 18 Oct 2021 10:49:40 +0000 Subject: [PATCH 8/9] bug: Correct rpc format for gas cli request --- chainlib/eth/address.py | 4 ++++ chainlib/eth/runnable/gas.py | 9 +++++---- setup.cfg | 2 +- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/chainlib/eth/address.py b/chainlib/eth/address.py index 48acf17..151fc9e 100644 --- a/chainlib/eth/address.py +++ b/chainlib/eth/address.py @@ -42,3 +42,7 @@ class AddressChecksum: :returns: Checksum address """ return to_checksum_address(v) + + +def is_same_address(a, b): + return uniform(strip_0x(a)) == uniform(strip_0x(b)) diff --git a/chainlib/eth/runnable/gas.py b/chainlib/eth/runnable/gas.py index 02001f1..8cfd57c 100644 --- a/chainlib/eth/runnable/gas.py +++ b/chainlib/eth/runnable/gas.py @@ -26,6 +26,7 @@ from chainlib.eth.gas import Gas from chainlib.eth.gas import balance as gas_balance from chainlib.chain import ChainSpec from chainlib.eth.runnable.util import decode_for_puny_humans +from chainlib.eth.address import is_same_address import chainlib.eth.cli logging.basicConfig(level=logging.WARNING) @@ -73,27 +74,27 @@ def main(): g = Gas(chain_spec, signer=signer, gas_oracle=rpc.get_gas_oracle(), nonce_oracle=rpc.get_nonce_oracle()) recipient = to_checksum_address(config.get('_RECIPIENT')) - if not config.true('_UNSAFE') and recipient != add_0x(config.get('_RECIPIENT')): + if not config.true('_UNSAFE') and is_checksum_address(recipient): raise ValueError('invalid checksum address') logg.info('gas transfer from {} to {} value {}'.format(signer_address, recipient, value)) if logg.isEnabledFor(logging.DEBUG): try: - sender_balance = balance(signer_address, rpc.id_generator) + sender_balance = balance(add_0x(signer_address), rpc.id_generator) recipient_balance = balance(add_0x(recipient), rpc.id_generator) logg.debug('sender {} balance before: {}'.format(signer_address, sender_balance)) logg.debug('recipient {} balance before: {}'.format(recipient, recipient_balance)) except urllib.error.URLError: pass - (tx_hash_hex, o) = g.create(signer_address, recipient, value, data=config.get('_DATA'), id_generator=rpc.id_generator) + (tx_hash_hex, o) = g.create(signer_address, add_0x(recipient), value, data=config.get('_DATA'), id_generator=rpc.id_generator) if send: conn.do(o) if block_last: r = conn.wait(tx_hash_hex) if logg.isEnabledFor(logging.DEBUG): - sender_balance = balance(signer_address, rpc.id_generator) + sender_balance = balance(add_0x(signer_address), rpc.id_generator) recipient_balance = balance(add_0x(recipient), rpc.id_generator) logg.debug('sender {} balance after: {}'.format(signer_address, sender_balance)) logg.debug('recipient {} balance after: {}'.format(recipient, recipient_balance)) diff --git a/setup.cfg b/setup.cfg index 588f603..0d9d387 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = chainlib-eth -version = 0.0.9rc3 +version = 0.0.9rc4 description = Ethereum implementation of the chainlib interface author = Louis Holbrook author_email = dev@holbrook.no From 7abce82eef7dfe60e292cf8f445dfa841e725d80 Mon Sep 17 00:00:00 2001 From: nolash Date: Mon, 18 Oct 2021 13:28:53 +0200 Subject: [PATCH 9/9] Fix wrong variable name in encode cli --- chainlib/eth/runnable/encode.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/chainlib/eth/runnable/encode.py b/chainlib/eth/runnable/encode.py index b2a7f93..35371bf 100644 --- a/chainlib/eth/runnable/encode.py +++ b/chainlib/eth/runnable/encode.py @@ -45,6 +45,7 @@ from chainlib.error import SignerMissingException from chainlib.chain import ChainSpec from chainlib.eth.runnable.util import decode_for_puny_humans from chainlib.eth.jsonrpc import to_blockheight_param +from chainlib.eth.address import to_checksum_address logging.basicConfig(level=logging.WARNING) logg = logging.getLogger() @@ -106,6 +107,8 @@ def main(): print(strip_0x(code)) return + exec_address = add_0x(to_checksum_address(config.get('_EXEC_ADDRESS'))) + if signer == None: c = TxFactory(chain_spec) j = JSONRPCRequest(id_generator=rpc.id_generator) @@ -122,7 +125,7 @@ def main(): height = to_blockheight_param(config.get('_HEIGHT')) o['params'].append(height) o = j.finalize(o) - r = conn.do(r) + r = conn.do(o) try: print(strip_0x(r)) return