chainlib

Generic blockchain access library and tooling
Log | Files | Refs | README | LICENSE

commit 69bb03d10cbeaab6df3e757706212a448ca243be
parent 39c59cceb9f3cf424bdddd7d755630157f7d8aaa
Author: nolash <dev@holbrook.no>
Date:   Sun,  2 May 2021 14:49:34 +0200

Remove ERC20 interface

Diffstat:
MCHANGELOG | 8+++++---
Dchainlib/eth/erc20.py | 238-------------------------------------------------------------------------------
Mchainlib/eth/runnable/balance.py | 23++---------------------
Dchainlib/eth/runnable/transfer.py | 162-------------------------------------------------------------------------------
Msetup.cfg | 3+--
Dtests/conftest.py | 25-------------------------
Dtests/test_erc20.py | 74--------------------------------------------------------------------------
Mtests/test_sign.py | 4----
8 files changed, 8 insertions(+), 529 deletions(-)

diff --git a/CHANGELOG b/CHANGELOG @@ -1,9 +1,11 @@ -- 0.0.1 +- 0.0.3-pending + * Remove erc20 module (to new external package) +- 0.0.2-unreleased + * +- 0.0.1-unreleased * Add eth tx decode * Add eth balance query with erc20 option * Add eth checksum address * Add eth subscribe monitor * Add eth erc20 transfer script * Add eth gas send script - * Add eth eip165 support - * Add eth eip173, custom "owned" support diff --git a/chainlib/eth/erc20.py b/chainlib/eth/erc20.py @@ -1,238 +0,0 @@ -# standard imports -import logging - -# external imports -import sha3 -from hexathon import ( - add_0x, - strip_0x, - ) -from crypto_dev_signer.eth.transaction import EIP155Transaction - -# local imports -from chainlib.hash import ( - keccak256_hex_to_hex, - keccak256_string_to_hex, - ) -from .constant import ZERO_ADDRESS -from .tx import ( - TxFactory, - TxFormat, - ) -from .contract import ( - ABIContractEncoder, - ABIContractDecoder, - ABIContractType, - abi_decode_single, - ) -from chainlib.jsonrpc import jsonrpc_template -from .error import RequestMismatchException - -logg = logging.getLogger() - - -class ERC20(TxFactory): - - - def balance_of(self, contract_address, address, sender_address=ZERO_ADDRESS): - o = jsonrpc_template() - o['method'] = 'eth_call' - enc = ABIContractEncoder() - enc.method('balanceOf') - enc.typ(ABIContractType.ADDRESS) - enc.address(address) - data = add_0x(enc.get()) - tx = self.template(sender_address, contract_address) - tx = self.set_code(tx, data) - o['params'].append(self.normalize(tx)) - o['params'].append('latest') - return o - - - def balance(self, contract_address, address, sender_address=ZERO_ADDRESS): - return self.balance_of(contract_address, address, sender_address=ZERO_ADDRESS) - - - def symbol(self, contract_address, sender_address=ZERO_ADDRESS): - o = jsonrpc_template() - o['method'] = 'eth_call' - enc = ABIContractEncoder() - enc.method('symbol') - data = add_0x(enc.get()) - tx = self.template(sender_address, contract_address) - tx = self.set_code(tx, data) - o['params'].append(self.normalize(tx)) - o['params'].append('latest') - return o - - - def name(self, contract_address, sender_address=ZERO_ADDRESS): - o = jsonrpc_template() - o['method'] = 'eth_call' - enc = ABIContractEncoder() - enc.method('name') - data = add_0x(enc.get()) - tx = self.template(sender_address, contract_address) - tx = self.set_code(tx, data) - o['params'].append(self.normalize(tx)) - o['params'].append('latest') - return o - - - def decimals(self, contract_address, sender_address=ZERO_ADDRESS): - o = jsonrpc_template() - o['method'] = 'eth_call' - enc = ABIContractEncoder() - enc.method('decimals') - data = add_0x(enc.get()) - tx = self.template(sender_address, contract_address) - tx = self.set_code(tx, data) - o['params'].append(self.normalize(tx)) - o['params'].append('latest') - return o - - - def transfer(self, contract_address, sender_address, recipient_address, value, tx_format=TxFormat.JSONRPC): - enc = ABIContractEncoder() - enc.method('transfer') - enc.typ(ABIContractType.ADDRESS) - enc.typ(ABIContractType.UINT256) - enc.address(recipient_address) - enc.uint256(value) - data = add_0x(enc.get()) - tx = self.template(sender_address, contract_address, use_nonce=True) - tx = self.set_code(tx, data) - tx = self.finalize(tx, tx_format) - return tx - - - def transfer_from(self, contract_address, sender_address, holder_address, recipient_address, value, tx_format=TxFormat.JSONRPC): - enc = ABIContractEncoder() - enc.method('transferFrom') - enc.typ(ABIContractType.ADDRESS) - enc.typ(ABIContractType.ADDRESS) - enc.typ(ABIContractType.UINT256) - enc.address(holder_address) - enc.address(recipient_address) - enc.uint256(value) - data = add_0x(enc.get()) - tx = self.template(sender_address, contract_address, use_nonce=True) - tx = self.set_code(tx, data) - tx = self.finalize(tx, tx_format) - return tx - - - def approve(self, contract_address, sender_address, spender_address, value, tx_format=TxFormat.JSONRPC): - enc = ABIContractEncoder() - enc.method('approve') - enc.typ(ABIContractType.ADDRESS) - enc.typ(ABIContractType.UINT256) - enc.address(spender_address) - enc.uint256(value) - data = add_0x(enc.get()) - tx = self.template(sender_address, contract_address, use_nonce=True) - tx = self.set_code(tx, data) - tx = self.finalize(tx, tx_format) - return tx - - - @classmethod - def parse_symbol(self, v): - return abi_decode_single(ABIContractType.STRING, v) - - - @classmethod - def parse_name(self, v): - return abi_decode_single(ABIContractType.STRING, v) - - - @classmethod - def parse_decimals(self, v): - return abi_decode_single(ABIContractType.UINT256, v) - - - @classmethod - def parse_balance(self, v): - return abi_decode_single(ABIContractType.UINT256, v) - - - @classmethod - def parse_transfer_request(self, v): - v = strip_0x(v) - cursor = 0 - enc = ABIContractEncoder() - enc.method('transfer') - enc.typ(ABIContractType.ADDRESS) - enc.typ(ABIContractType.UINT256) - r = enc.get() - l = len(r) - m = v[:l] - if m != r: - logg.error('method mismatch, expected {}, got {}'.format(r, m)) - raise RequestMismatchException(v) - cursor += l - - dec = ABIContractDecoder() - dec.typ(ABIContractType.ADDRESS) - dec.typ(ABIContractType.UINT256) - dec.val(v[cursor:cursor+64]) - cursor += 64 - dec.val(v[cursor:cursor+64]) - r = dec.decode() - return r - - - @classmethod - def parse_transfer_from_request(self, v): - v = strip_0x(v) - cursor = 0 - enc = ABIContractEncoder() - enc.method('transferFrom') - enc.typ(ABIContractType.ADDRESS) - enc.typ(ABIContractType.ADDRESS) - enc.typ(ABIContractType.UINT256) - r = enc.get() - l = len(r) - m = v[:l] - if m != r: - logg.error('method mismatch, expected {}, got {}'.format(r, m)) - raise RequestMismatchException(v) - cursor += l - - dec = ABIContractDecoder() - dec.typ(ABIContractType.ADDRESS) - dec.typ(ABIContractType.ADDRESS) - dec.typ(ABIContractType.UINT256) - dec.val(v[cursor:cursor+64]) - cursor += 64 - dec.val(v[cursor:cursor+64]) - cursor += 64 - dec.val(v[cursor:cursor+64]) - r = dec.decode() - return r - - - @classmethod - def parse_approve_request(self, v): - v = strip_0x(v) - cursor = 0 - enc = ABIContractEncoder() - enc.method('approve') - enc.typ(ABIContractType.ADDRESS) - enc.typ(ABIContractType.UINT256) - r = enc.get() - l = len(r) - m = v[:l] - if m != r: - logg.error('method mismatch, expected {}, got {}'.format(r, m)) - raise RequestMismatchException(v) - cursor += l - - dec = ABIContractDecoder() - dec.typ(ABIContractType.ADDRESS) - dec.typ(ABIContractType.UINT256) - dec.val(v[cursor:cursor+64]) - cursor += 64 - dec.val(v[cursor:cursor+64]) - r = dec.decode() - return r diff --git a/chainlib/eth/runnable/balance.py b/chainlib/eth/runnable/balance.py @@ -30,7 +30,6 @@ from chainlib.jsonrpc import ( jsonrpc_template, jsonrpc_result, ) -from chainlib.eth.erc20 import ERC20 from chainlib.eth.connection import EthHTTPConnection from chainlib.eth.gas import ( OverrideGasOracle, @@ -46,7 +45,6 @@ default_eth_provider = os.environ.get('ETH_PROVIDER', 'http://localhost:8545') argparser = argparse.ArgumentParser() argparser.add_argument('-p', '--provider', dest='p', default=default_eth_provider, type=str, help='Web3 provider url (http only)') -argparser.add_argument('-a', '--token-address', dest='a', type=str, help='Token address. If not set, will return gas balance') argparser.add_argument('-i', '--chain-spec', dest='i', type=str, default='evm:ethereum:1', help='Chain specification string') argparser.add_argument('-u', '--unsafe', dest='u', action='store_true', help='Auto-convert address to checksum adddress') argparser.add_argument('-v', action='store_true', help='Be verbose') @@ -67,31 +65,14 @@ address = to_checksum(args.address) if not args.u and address != add_0x(args.address): raise ValueError('invalid checksum address') -token_symbol = 'eth' - chain_spec = ChainSpec.from_chain_str(args.i) def main(): r = None decimals = 18 - if args.a != None: - #g = ERC20(gas_oracle=gas_oracle) - g = ERC20(chain_spec=chain_spec) - # determine decimals - decimals_o = g.decimals(args.a) - r = conn.do(decimals_o) - decimals = int(strip_0x(r), 16) - symbol_o = g.symbol(args.a) - r = conn.do(decimals_o) - token_symbol = r - - # get balance - balance_o = g.balance(args.a, address) - r = conn.do(balance_o) - else: - o = balance(address) - r = conn.do(o) + o = balance(address) + r = conn.do(o) hx = strip_0x(r) balance_value = int(hx, 16) diff --git a/chainlib/eth/runnable/transfer.py b/chainlib/eth/runnable/transfer.py @@ -1,162 +0,0 @@ -#!python3 - -"""Token transfer script - -.. moduleauthor:: Louis Holbrook <dev@holbrook.no> -.. pgp:: 0826EDA1702D1E87C6E2875121D2E7BB88C2A746 - -""" - -# SPDX-License-Identifier: GPL-3.0-or-later - -# standard imports -import os -import io -import json -import argparse -import logging - -# third-party imports -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.connection import EthHTTPConnection -from chainlib.jsonrpc import jsonrpc_template -from chainlib.eth.nonce import ( - RPCNonceOracle, - OverrideNonceOracle, - ) -from chainlib.eth.gas import ( - RPCGasOracle, - OverrideGasOracle, - ) -from chainlib.eth.erc20 import ERC20 -from chainlib.chain import ChainSpec -from chainlib.eth.runnable.util import decode_for_puny_humans - - -logging.basicConfig(level=logging.WARNING) -logg = logging.getLogger() - -logging.getLogger('web3').setLevel(logging.WARNING) -logging.getLogger('urllib3').setLevel(logging.WARNING) - -default_abi_dir = '/usr/local/share/cic/solidity/abi' -argparser = argparse.ArgumentParser() -argparser.add_argument('-p', '--provider', dest='p', default='http://localhost:8545', type=str, help='Web3 provider url (http only)') -argparser.add_argument('-w', action='store_true', help='Wait for the last transaction to be confirmed') -argparser.add_argument('-ww', action='store_true', help='Wait for every transaction to be confirmed') -argparser.add_argument('-i', '--chain-spec', dest='i', type=str, default='evm:ethereum:1', help='Chain specification string') -argparser.add_argument('-a', '--token-address', required='True', dest='a', type=str, help='Token address') -argparser.add_argument('-y', '--key-file', dest='y', type=str, help='Ethereum keystore file to use for signing') -argparser.add_argument('--env-prefix', default=os.environ.get('CONFINI_ENV_PREFIX'), dest='env_prefix', type=str, help='environment prefix for variables to overwrite configuration') -argparser.add_argument('-u', '--unsafe', dest='u', action='store_true', help='Auto-convert address to checksum adddress') -argparser.add_argument('-s', '--send', dest='s', action='store_true', help='Send to network') -argparser.add_argument('--nonce', type=int, help='Override nonce') -argparser.add_argument('--gas-price', dest='gas_price', type=int, help='Override gas price') -argparser.add_argument('--gas-limit', dest='gas_limit', type=int, help='Override gas limit') -argparser.add_argument('-v', action='store_true', help='Be verbose') -argparser.add_argument('-vv', action='store_true', help='Be more verbose') -argparser.add_argument('recipient', type=str, help='Recipient account address') -argparser.add_argument('amount', type=int, help='Amount of tokens to mint and gift') -args = argparser.parse_args() - - -if args.vv: - logg.setLevel(logging.DEBUG) -elif args.v: - logg.setLevel(logging.INFO) - -block_all = args.ww -block_last = args.w or block_all - -passphrase_env = 'ETH_PASSPHRASE' -if args.env_prefix != None: - passphrase_env = args.env_prefix + '_' + passphrase_env -passphrase = os.environ.get(passphrase_env) -logg.error('pass {}'.format(passphrase_env)) -if passphrase == None: - logg.warning('no passphrase given') - passphrase='' - -signer_address = None -keystore = DictKeystore() -if args.y != None: - logg.debug('loading keystore file {}'.format(args.y)) - signer_address = keystore.import_keystore_file(args.y, password=passphrase) - logg.debug('now have key for signer address {}'.format(signer_address)) -signer = EIP155Signer(keystore) - -conn = EthHTTPConnection(args.p) - -nonce_oracle = None -if args.nonce != None: - nonce_oracle = OverrideNonceOracle(signer_address, args.nonce) -else: - nonce_oracle = RPCNonceOracle(signer_address, conn) - -def _max_gas(code=None): - return 8000000 - -gas_oracle = None -if args.gas_price != None or args.gas_limit != None: - gas_oracle = OverrideGasOracle(price=args.gas_price, limit=args.gas_limit) -else: - gas_oracle = RPCGasOracle(conn, code_callback=_max_gas) - -chain_spec = ChainSpec.from_chain_str(args.i) -chain_id = chain_spec.network_id() - -value = args.amount - -send = args.s - -g = ERC20(chain_spec, signer=signer, gas_oracle=gas_oracle, nonce_oracle=nonce_oracle) - - -def balance(token_address, address): - o = g.balance(token_address, address) - r = conn.do(o) - hx = strip_0x(r) - return int(hx, 16) - - -def main(): - recipient = args.recipient - if not args.u and recipient != add_0x(args.recipient): - raise ValueError('invalid checksum address') - - if logg.isEnabledFor(logging.DEBUG): - logg.debug('sender {} balance after: {}'.format(signer_address, balance(args.a, signer_address))) - logg.debug('recipient {} balance after: {}'.format(recipient, balance(args.a, recipient))) - - (tx_hash_hex, o) = g.transfer(args.a, signer_address, recipient, value) - - if send: - conn.do(o) - if block_last: - r = conn.wait(tx_hash_hex) - if logg.isEnabledFor(logging.DEBUG): - logg.debug('sender {} balance after: {}'.format(signer_address, balance(args.a, signer_address))) - logg.debug('recipient {} balance after: {}'.format(recipient, balance(args.a, recipient))) - if r['status'] == 0: - logg.critical('VM revert. Wish I could tell you more') - sys.exit(1) - print(tx_hash_hex) - - else: - if logg.isEnabledFor(logging.INFO): - io_str = io.StringIO() - decode_for_puny_humans(o['params'][0], chain_spec, io_str) - print(io_str.getvalue()) - else: - print(o['params'][0]) - - -if __name__ == '__main__': - main() diff --git a/setup.cfg b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = chainlib -version = 0.0.2b1 +version = 0.0.3a1 description = Generic blockchain access library and tooling author = Louis Holbrook author_email = dev@holbrook.no @@ -38,7 +38,6 @@ console_scripts = eth-checksum = chainlib.eth.runnable.checksum:main eth-gas = chainlib.eth.runnable.gas:main eth-raw = chainlib.eth.runnable.raw:main - eth-transfer = chainlib.eth.runnable.transfer:main eth-get = chainlib.eth.runnable.get:main eth-decode = chainlib.eth.runnable.decode:main eth-info = chainlib.eth.runnable.info:main diff --git a/tests/conftest.py b/tests/conftest.py @@ -1,25 +0,0 @@ -# standard imports -import os - -# external imports -import pytest -from crypto_dev_signer.keystore.dict import DictKeystore -from crypto_dev_signer.eth.signer import ReferenceSigner as EIP155Signer - - -@pytest.fixture(scope='session') -def keystore(): - ks = DictKeystore() - - pk = os.urandom(32) - ks.import_raw_key(pk) - return ks - - -@pytest.fixture(scope='session') -def signer( - keystore, - ): - - s = EIP155Signer(keystore) - return s diff --git a/tests/test_erc20.py b/tests/test_erc20.py @@ -1,74 +0,0 @@ -# standard imports -import logging -import os - -# external imports -from hexathon import ( - strip_0x, - add_0x, - ) - -# local imports -from chainlib.eth.erc20 import ERC20 -from chainlib.eth.address import to_checksum_address -from chainlib.eth.tx import ( - unpack, - TxFormat, - ) -from chainlib.eth.pytest import * - -logg = logging.getLogger() - -contract_address = to_checksum_address('0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef') -benefactor_address = to_checksum_address('0xefdeadbeefdeadbeefdeadbeefdeadbeefdeadbe') - - -# TODO: use unittest instead -def test_erc20_balance( - default_chain_spec, - ): - e = ERC20(default_chain_spec,) - - holder_address = to_checksum_address('0xbeefdeadbeefdeadbeefdeadbeefdeadbeefdead') - o = e.balance_of(contract_address, holder_address) - assert len(o['params'][0]['data']) == 64 + 8 + 2 - assert o['params'][0]['data'][:10] == add_0x('70a08231') - - -def test_erc20_decimals( - default_chain_spec, - ): - e = ERC20(default_chain_spec) - - o = e.decimals(contract_address) - assert o['params'][0]['data'] == add_0x('313ce567') - - -def test_erc20_transfer( - keystore, - signer, - default_chain_spec, - ): - e = ERC20(default_chain_spec, signer=signer) - - addresses = keystore.list() - (tx_hash_hex, o) = e.transfer(contract_address, addresses[0], benefactor_address, 1024) - - -def test_erc20_parse_transfer_request( - keystore, - signer, - default_chain_spec, - ): - - e = ERC20(default_chain_spec, signer=signer) - - addresses = keystore.list() - (tx_hash_hex, o) = e.transfer(contract_address, addresses[0], benefactor_address, 1024, tx_format=TxFormat.RLP_SIGNED) - b = bytes.fromhex(strip_0x(o)) - - #chain_spec = ChainSpec('evm', 'foo', 1, 'bar') - tx = unpack(b, default_chain_spec) - r = ERC20.parse_transfer_request(tx['data']) - assert r[0] == benefactor_address - assert r[1] == 1024 diff --git a/tests/test_sign.py b/tests/test_sign.py @@ -73,10 +73,6 @@ class Mocket(socket.socket): else: tx = Mocket.tx r = Mocket.signer.sign_transaction_to_rlp(tx) - #mock_sig = os.urandom(64) - #tx.r = mock_sig[:32] - #tx.s = mock_sig[32:] - #r = add_0x(tx.rlp_serialize().hex()) Mocket.tx = None o = jsonrpc_response(Mocket.req_id, add_0x(r.hex())) Mocket.req_id = None