chainlib-eth

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

commit 55a72ed6d622c43dd1e3e88b5a7ba0cf8fb68f5f
parent dbc046d946272f54f512d7a84b6db2581552538f
Author: nolash <dev@holbrook.no>
Date:   Wed,  6 Oct 2021 06:57:51 +0200

Add generic contract call/tx encoder tool

Diffstat:
Achainlib/eth/runnable/encode.py | 219+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mchainlib/eth/runnable/gas.py | 4++--
Mchainlib/eth/runnable/raw.py | 65++++++++++++++++++++++++++++++++++-------------------------------
Mchainlib/eth/tx.py | 1-
Mchainlib/eth/unittest/ethtester.py | 5+++--
Mrequirements.txt | 4++--
Msetup.cfg | 2+-
7 files changed, 261 insertions(+), 39 deletions(-)

diff --git a/chainlib/eth/runnable/encode.py 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 @@ -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 @@ -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') - - 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: + 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') + + 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) + + 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 @@ -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 @@ -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 @@ -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 @@ -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