From 104e3ab3a932825d1f093e780b5e9edaeb2ffbdd Mon Sep 17 00:00:00 2001 From: Mark Omo Date: Mon, 19 Dec 2016 18:30:48 -0700 Subject: [PATCH 1/5] Changes to allow cross platform compatability --- ethjsonrpc/client.py | 4 +- ethjsonrpc/ethereum_utils.py | 523 +++++++++++++++++++++++++++++++++++ requirements.txt | 2 +- setup.py | 2 +- 4 files changed, 527 insertions(+), 4 deletions(-) create mode 100644 ethjsonrpc/ethereum_utils.py diff --git a/ethjsonrpc/client.py b/ethjsonrpc/client.py index 54bb869..072376f 100644 --- a/ethjsonrpc/client.py +++ b/ethjsonrpc/client.py @@ -4,8 +4,8 @@ import requests from requests.adapters import HTTPAdapter from requests.exceptions import ConnectionError as RequestsConnectionError -from ethereum import utils -from ethereum.abi import encode_abi, decode_abi +import ethjsonrpc.ethereum_utils as utils +from ethjsonrpc.abi import encode_abi, decode_abi from ethjsonrpc.constants import BLOCK_TAGS, BLOCK_TAG_LATEST from ethjsonrpc.utils import hex_to_dec, clean_hex, validate_block diff --git a/ethjsonrpc/ethereum_utils.py b/ethjsonrpc/ethereum_utils.py new file mode 100644 index 0000000..f116726 --- /dev/null +++ b/ethjsonrpc/ethereum_utils.py @@ -0,0 +1,523 @@ +# From pyethereum (https://github.com/ethereum/pyethereum) at Commit 94da783 +# Licensed under MIT: + +# The MIT License (MIT) +# +# Copyright (c) 2015 Vitalik Buterin, Heiko Hees +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +try: + from Crypto.Hash import keccak + sha3_256 = lambda x: keccak.new(digest_bits=256, data=x).digest() +except: + import sha3 as _sha3 + sha3_256 = lambda x: _sha3.sha3_256(x).digest() +from bitcoin import privtopub +import sys +import rlp +from rlp.sedes import big_endian_int, BigEndianInt, Binary +from rlp.utils import decode_hex, encode_hex, ascii_chr, str_to_bytes +import random + +big_endian_to_int = lambda x: big_endian_int.deserialize(str_to_bytes(x).lstrip(b'\x00')) +int_to_big_endian = lambda x: big_endian_int.serialize(x) + + +TT256 = 2 ** 256 +TT256M1 = 2 ** 256 - 1 +TT255 = 2 ** 255 + +if sys.version_info.major == 2: + is_numeric = lambda x: isinstance(x, (int, long)) + is_string = lambda x: isinstance(x, (str, unicode)) + + def to_string(value): + return str(value) + + def int_to_bytes(value): + if isinstance(value, str): + return value + return int_to_big_endian(value) + + def to_string_for_regexp(value): + return str(value) + unicode = unicode + + def bytearray_to_bytestr(value): + return bytes(''.join(chr(c) for c in value)) + +else: + is_numeric = lambda x: isinstance(x, int) + is_string = lambda x: isinstance(x, bytes) + + def to_string(value): + if isinstance(value, bytes): + return value + if isinstance(value, str): + return bytes(value, 'utf-8') + if isinstance(value, int): + return bytes(str(value), 'utf-8') + + def int_to_bytes(value): + if isinstance(value, bytes): + return value + return int_to_big_endian(value) + + def to_string_for_regexp(value): + return str(to_string(value), 'utf-8') + unicode = str + + def bytearray_to_bytestr(value): + return bytes(value) + +isnumeric = is_numeric + + +def mk_contract_address(sender, nonce): + return sha3(rlp.encode([normalize_address(sender), nonce]))[12:] + + +def mk_metropolis_contract_address(sender, initcode): + return sha3(normalize_address(sender) + initcode)[12:] + + +def safe_ord(value): + if isinstance(value, int): + return value + else: + return ord(value) + +# decorator + + +def debug(label): + def deb(f): + def inner(*args, **kwargs): + i = random.randrange(1000000) + print(label, i, 'start', args) + x = f(*args, **kwargs) + print(label, i, 'end', x) + return x + return inner + return deb + + +def flatten(li): + o = [] + for l in li: + o.extend(l) + return o + + +def bytearray_to_int(arr): + o = 0 + for a in arr: + o = (o << 8) + a + return o + + +def int_to_32bytearray(i): + o = [0] * 32 + for x in range(32): + o[31 - x] = i & 0xff + i >>= 8 + return o + +sha3_count = [0] + + +def sha3(seed): + sha3_count[0] += 1 + return sha3_256(to_string(seed)) + +assert encode_hex(sha3(b'')) == b'c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470' + + +def privtoaddr(x, extended=False): + if len(x) > 32: + x = decode_hex(x) + o = sha3(privtopub(x)[1:])[12:] + return add_checksum(o) if extended else o + + +def add_checksum(x): + if len(x) in (40, 48): + x = decode_hex(x) + if len(x) == 24: + return x + return x + sha3(x)[:4] + + +def add_cool_checksum(addr): + addr = normalize_address(addr) + addr_hex = encode_hex(addr) + + o = '' + h = encode_hex(sha3(addr_hex)) + if not isinstance(addr_hex, str): + # py3 bytes sequence + addr_hex = list(chr(c) for c in addr_hex) + h = list(chr(c) for c in h) + + for i, c in enumerate(addr_hex): + if c in '0123456789': + o += c + else: + o += c.lower() if h[i] in '01234567' else c.upper() + return '0x' + o + + +def check_and_strip_checksum(x): + if len(x) in (40, 48): + x = decode_hex(x) + assert len(x) == 24 and sha3(x[:20])[:4] == x[-4:] + return x[:20] + + +def check_and_strip_cool_checksum(addr): + assert add_cool_checksum(addr.lower()) == addr + return normalize_address(addr) + + +def normalize_address(x, allow_blank=False): + if is_numeric(x): + return int_to_addr(x) + if allow_blank and x in {'', b''}: + return b'' + if len(x) in (42, 50) and x[:2] in {'0x', b'0x'}: + x = x[2:] + if len(x) in (40, 48): + x = decode_hex(x) + if len(x) == 24: + assert len(x) == 24 and sha3(x[:20])[:4] == x[-4:] + x = x[:20] + if len(x) != 20: + raise Exception("Invalid address format: %r" % x) + return x + + +def zpad(x, l): + """ Left zero pad value `x` at least to length `l`. + + >>> zpad('', 1) + '\x00' + >>> zpad('\xca\xfe', 4) + '\x00\x00\xca\xfe' + >>> zpad('\xff', 1) + '\xff' + >>> zpad('\xca\xfe', 2) + '\xca\xfe' + """ + return b'\x00' * max(0, l - len(x)) + x + + +def rzpad(value, total_length): + """ Right zero pad value `x` at least to length `l`. + + >>> zpad('', 1) + '\x00' + >>> zpad('\xca\xfe', 4) + '\xca\xfe\x00\x00' + >>> zpad('\xff', 1) + '\xff' + >>> zpad('\xca\xfe', 2) + '\xca\xfe' + """ + return value + b'\x00' * max(0, total_length - len(value)) + + +def zunpad(x): + i = 0 + while i < len(x) and (x[i] == 0 or x[i] == b'\x00'): + i += 1 + return x[i:] + + +def int_to_addr(x): + o = [b''] * 20 + for i in range(20): + o[19 - i] = ascii_chr(x & 0xff) + x >>= 8 + return b''.join(o) + + +def coerce_addr_to_bin(x): + if is_numeric(x): + return encode_hex(zpad(big_endian_int.serialize(x), 20)) + elif len(x) == 40 or len(x) == 0: + return decode_hex(x) + else: + return zpad(x, 20)[-20:] + + +def coerce_addr_to_hex(x): + if is_numeric(x): + return encode_hex(zpad(big_endian_int.serialize(x), 20)) + elif len(x) == 40 or len(x) == 0: + return x + else: + return encode_hex(zpad(x, 20)[-20:]) + + +def coerce_to_int(x): + if is_numeric(x): + return x + elif len(x) == 40: + return big_endian_to_int(decode_hex(x)) + else: + return big_endian_to_int(x) + + +def coerce_to_bytes(x): + if is_numeric(x): + return big_endian_int.serialize(x) + elif len(x) == 40: + return decode_hex(x) + else: + return x + + +def parse_int_or_hex(s): + if is_numeric(s): + return s + elif s[:2] in (b'0x', '0x'): + s = to_string(s) + tail = (b'0' if len(s) % 2 else b'') + s[2:] + return big_endian_to_int(decode_hex(tail)) + else: + return int(s) + + +def ceil32(x): + return x if x % 32 == 0 else x + 32 - (x % 32) + + +def to_signed(i): + return i if i < TT255 else i - TT256 + + +def sha3rlp(x): + return sha3(rlp.encode(x)) + + +# Format encoders/decoders for bin, addr, int + + +def decode_bin(v): + '''decodes a bytearray from serialization''' + if not is_string(v): + raise Exception("Value must be binary, not RLP array") + return v + + +def decode_addr(v): + '''decodes an address from serialization''' + if len(v) not in [0, 20]: + raise Exception("Serialized addresses must be empty or 20 bytes long!") + return encode_hex(v) + + +def decode_int(v): + '''decodes and integer from serialization''' + if len(v) > 0 and (v[0] == b'\x00' or v[0] == 0): + raise Exception("No leading zero bytes allowed for integers") + return big_endian_to_int(v) + + +def decode_int256(v): + return big_endian_to_int(v) + + +def encode_bin(v): + '''encodes a bytearray into serialization''' + return v + + +def encode_root(v): + '''encodes a trie root into serialization''' + return v + + +def encode_int(v): + '''encodes an integer into serialization''' + if not is_numeric(v) or v < 0 or v >= TT256: + raise Exception("Integer invalid or out of range: %r" % v) + return int_to_big_endian(v) + + +def encode_int256(v): + return zpad(int_to_big_endian(v), 256) + + +def scan_bin(v): + if v[:2] in ('0x', b'0x'): + return decode_hex(v[2:]) + else: + return decode_hex(v) + + +def scan_int(v): + if v[:2] in ('0x', b'0x'): + return big_endian_to_int(decode_hex(v[2:])) + else: + return int(v) + + +# Decoding from RLP serialization +decoders = { + "bin": decode_bin, + "addr": decode_addr, + "int": decode_int, + "int256b": decode_int256, +} + +# Encoding to RLP serialization +encoders = { + "bin": encode_bin, + "int": encode_int, + "trie_root": encode_root, + "int256b": encode_int256, +} + +# Encoding to printable format +printers = { + "bin": lambda v: b'0x' + encode_hex(v), + "addr": lambda v: v, + "int": lambda v: to_string(v), + "trie_root": lambda v: encode_hex(v), + "int256b": lambda x: encode_hex(zpad(encode_int256(x), 256)) +} + +# Decoding from printable format +scanners = { + "bin": scan_bin, + "addr": lambda x: x[2:] if x[:2] == b'0x' else x, + "int": scan_int, + "trie_root": lambda x: scan_bin, + "int256b": lambda x: big_endian_to_int(decode_hex(x)) +} + + +def int_to_hex(x): + o = encode_hex(encode_int(x)) + return b'0x' + (o[1:] if (len(o) > 0 and o[0] == b'0') else o) + + +def remove_0x_head(s): + return s[2:] if s[:2] == b'0x' else s + + +def print_func_call(ignore_first_arg=False, max_call_number=100): + ''' utility function to facilitate debug, it will print input args before + function call, and print return value after function call + + usage: + + @print_func_call + def some_func_to_be_debu(): + pass + + :param ignore_first_arg: whether print the first arg or not. + useful when ignore the `self` parameter of an object method call + ''' + from functools import wraps + + def display(x): + x = to_string(x) + try: + x.decode('ascii') + except: + return 'NON_PRINTABLE' + return x + + local = {'call_number': 0} + + def inner(f): + + @wraps(f) + def wrapper(*args, **kwargs): + local['call_number'] += 1 + tmp_args = args[1:] if ignore_first_arg and len(args) else args + this_call_number = local['call_number'] + print(('{0}#{1} args: {2}, {3}'.format( + f.__name__, + this_call_number, + ', '.join([display(x) for x in tmp_args]), + ', '.join(display(key) + '=' + to_string(value) + for key, value in kwargs.items()) + ))) + res = f(*args, **kwargs) + print(('{0}#{1} return: {2}'.format( + f.__name__, + this_call_number, + display(res)))) + + if local['call_number'] > 100: + raise Exception("Touch max call number!") + return res + return wrapper + return inner + + +def dump_state(trie): + res = '' + for k, v in list(trie.to_dict().items()): + res += '%r:%r\n' % (encode_hex(k), encode_hex(v)) + return res + + +class Denoms(): + + def __init__(self): + self.wei = 1 + self.babbage = 10 ** 3 + self.lovelace = 10 ** 6 + self.shannon = 10 ** 9 + self.szabo = 10 ** 12 + self.finney = 10 ** 15 + self.ether = 10 ** 18 + self.turing = 2 ** 256 + +denoms = Denoms() + + +address = Binary.fixed_length(20, allow_empty=True) +int20 = BigEndianInt(20) +int32 = BigEndianInt(32) +int256 = BigEndianInt(256) +hash32 = Binary.fixed_length(32) +trie_root = Binary.fixed_length(32, allow_empty=True) + + +class bcolors: + HEADER = '\033[95m' + OKBLUE = '\033[94m' + OKGREEN = '\033[92m' + WARNING = '\033[91m' + FAIL = '\033[91m' + ENDC = '\033[0m' + BOLD = '\033[1m' + UNDERLINE = '\033[4m' + + +def DEBUG(msg, *args, **kwargs): + from ethereum import slogging + + slogging.DEBUG(msg, *args, **kwargs) diff --git a/requirements.txt b/requirements.txt index effd275..91ba3b0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ -ethereum==1.0.8 requests==2.9.1 +rlp>=0.4.4 diff --git a/setup.py b/setup.py index e98903f..3df4226 100644 --- a/setup.py +++ b/setup.py @@ -22,7 +22,7 @@ 'Programming Language :: Python :: 2', ], install_requires=[ - 'ethereum==1.0.8', 'requests==2.9.1', + 'rlp>=0.4.4', ], ) From 8e22cd86f522ad4fcc4da28ff6c1a30a197ff83c Mon Sep 17 00:00:00 2001 From: Mark Omo Date: Mon, 19 Dec 2016 18:45:30 -0700 Subject: [PATCH 2/5] Changes to allow cross platform compatability --- README.md | 232 +++++++++++++ ethjsonrpc/abi.py | 857 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1089 insertions(+) create mode 100644 README.md create mode 100644 ethjsonrpc/abi.py diff --git a/README.md b/README.md new file mode 100644 index 0000000..adbe641 --- /dev/null +++ b/README.md @@ -0,0 +1,232 @@ +This is a brantch of the ethjsonrpc libary that only includes the required files from dependent packages, so there are no requirments for +compiled packages, allowing this software to run on windows + +ethjsonrpc +========== + +Python client for Ethereum using the JSON-RPC interface + +* complete: implements all 62 JSON-RPC methods plus several client-specific methods +* provides a high-level interface to create contracts on the blockchain and to call contract methods + +Important note +-------------- + +The API is not yet stable, so please use caution when upgrading. + +Installation +------------ + +You may need additional libraries and tools before installing ethjsonrpc. + +On Ubuntu 16.04: + +.. code:: bash + + $ sudo apt install python-minimal + $ sudo apt install gcc + $ sudo apt install virtualenv # optional but recommended + $ sudo apt install libpython-dev + $ sudo apt install libssl-dev + + +On Ubuntu 14.04: + +.. code:: bash + + $ sudo apt-get install python-virtualenv # optional but recommended + $ sudo apt-get install libpython-dev + $ sudo apt-get install libssl-dev + + +To install ethjsonrpc: + +.. code:: bash + + $ pip install ethjsonrpc + + +Make sure to have a node running an Ethereum client (such as geth) for the library to connect to. + +Example +------- + +.. code:: python + + >>> from ethjsonrpc import EthJsonRpc # to use Parity-specific methods, import ParityEthJsonRpc + >>> c = EthJsonRpc('127.0.0.1', 8545) + >>> c.net_version() + u'1' + >>> c.web3_clientVersion() + u'Geth/v1.3.3/linux/go1.5.1' + >>> c.eth_gasPrice() + 50000000000 + >>> c.eth_blockNumber() + 828948 + + +High-level functionality +------------------------ + +These examples assume the following simple Solidity contract: + +.. code:: + + contract Example { + + string s; + + function set_s(string new_s) { + s = new_s; + } + + function get_s() returns (string) { + return s; + } + } + + +Compile it like this: + +.. code:: bash + + $ solc --binary stdout example.sol + + +Setup +````` + +.. code:: python + + >>> compiled = '606060405261020f806100136000396000f30060606040526000357c01000000000000000000000000000000000000000000000000000000009004806375d74f3914610044578063e7aab290146100bd57610042565b005b61004f600450610191565b60405180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156100af5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b61010d6004803590602001906004018035906020019191908080601f016020809104026020016040519081016040528093929190818152602001838380828437820191505050505050905061010f565b005b806000600050908051906020019082805482825590600052602060002090601f01602090048101928215610160579182015b8281111561015f578251826000505591602001919060010190610141565b5b50905061018b919061016d565b80821115610187576000818150600090555060010161016d565b5090565b50505b50565b60206040519081016040528060008152602001506000600050805480601f0160208091040260200160405190810160405280929190818152602001828054801561020057820191906000526020600020905b8154815290600101906020018083116101e357829003601f168201915b5050505050905061020c565b9056' + >>> from ethjsonrpc import EthJsonRpc # to use Parity-specific methods, import ParityEthJsonRpc + >>> c = EthJsonRpc('127.0.0.1', 8545) + + +Creating a contract on the blockchain +````````````````````````````````````` + +.. code:: python + + >>> # continued from above + >>> contract_tx = c.create_contract(c.eth_coinbase(), compiled, gas=300000) + >>> # wait here for the contract to be created when a new block is mined + >>> contract_addr = c.get_contract_address(contract_tx) + >>> contract_addr + u'0x24988147f2f2300450103d8c42c43182cf226857' + + +Calling a contract function with a transaction (storing data) +````````````````````````````````````````````````````````````` + +.. code:: python + + >>> # continued from above + >>> tx = c.call_with_transaction(c.eth_coinbase(), contract_addr, 'set_s(string)', ['Hello, world']) + >>> tx + u'0x15bde63d79466e3db5169a913bb2069130ca387033d2ff2e29f4dfbef1bc6e0d' + + +Calling a contract function on the local blockchain (reading data) +`````````````````````````````````````````````````````````````````` + +.. code:: python + + >>> # continued from above + >>> results = c.call(contract_addr, 'get_s()', [], ['string']) + >>> results + ['Hello, world'] + + +Additional examples +------------------- + +Please see ``test.py`` for additional examples. + +Implemented JSON-RPC methods +---------------------------- + +* web3_clientVersion +* web3_sha3 +* net_version +* net_listening +* net_peerCount +* eth_protocolVersion +* eth_syncing +* eth_coinbase +* eth_mining +* eth_hashrate +* eth_gasPrice +* eth_accounts +* eth_blockNumber +* eth_getBalance +* eth_getStorageAt +* eth_getTransactionCount +* eth_getBlockTransactionCountByHash +* eth_getBlockTransactionCountByNumber +* eth_getUncleCountByBlockHash +* eth_getUncleCountByBlockNumber +* eth_getCode +* eth_sign +* eth_sendTransaction +* eth_sendRawTransaction +* eth_call +* eth_estimateGas +* eth_getBlockByHash +* eth_getBlockByNumber +* eth_getTransactionByHash +* eth_getTransactionByBlockHashAndIndex +* eth_getTransactionByBlockNumberAndIndex +* eth_getTransactionReceipt +* eth_getUncleByBlockHashAndIndex +* eth_getUncleByBlockNumberAndIndex +* eth_getCompilers +* eth_compileSolidity +* eth_compileLLL +* eth_compileSerpent +* eth_newFilter +* eth_newBlockFilter +* eth_newPendingTransactionFilter +* eth_uninstallFilter +* eth_getFilterChanges +* eth_getFilterLogs +* eth_getLogs +* eth_getWork +* eth_submitWork +* eth_submitHashrate +* db_putString +* db_getString +* db_putHex +* db_getHex +* shh_version +* shh_post +* shh_newIdentity +* shh_hasIdentity +* shh_newGroup +* shh_addToGroup +* shh_newFilter +* shh_uninstallFilter +* shh_getFilterChanges +* shh_getMessages + +Parity-only JSON-RPC methods +---------------------------- + +To use these methods, make sure that you're + +* running Parity as your client +* running with the ``--tracing on`` option +* using this library's ``ParityEthJsonRpc`` client (not the vanilla ``EthJsonRpc`` client) + +Methods: + +* trace_filter +* trace_get +* trace_transaction +* trace_block + +Reference +--------- + +* https://github.com/ethereum/wiki/wiki/JSON-RPC +* https://github.com/ethcore/parity/wiki/JSONRPC-trace-module diff --git a/ethjsonrpc/abi.py b/ethjsonrpc/abi.py new file mode 100644 index 0000000..51cb297 --- /dev/null +++ b/ethjsonrpc/abi.py @@ -0,0 +1,857 @@ +# From pyethereum (https://github.com/ethereum/pyethereum) at Commit 91f385e +# Licensed under MIT: + +# The MIT License (MIT) +# +# Copyright (c) 2015 Vitalik Buterin, Heiko Hees +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +# -*- coding: utf8 -*- +from __future__ import print_function + +import ast +import re +import warnings + +import yaml # use yaml instead of json to get non unicode (works with ascii only data) +from rlp.utils import decode_hex, encode_hex + +import ethjsonrpc.ethereum_utils as utils +from ethjsonrpc.ethereum_utils import ( + big_endian_to_int, ceil32, int_to_big_endian, encode_int, is_numeric, isnumeric, is_string, + rzpad, TT255, TT256, zpad, +) + +# The number of bytes is encoded as a uint256 +# Type used to encode a string/bytes length +INT256 = 'uint', '256', [] +lentyp = INT256 # pylint: disable=invalid-name + + +class EncodingError(Exception): + pass + + +class ValueOutOfBounds(EncodingError): + pass + + +def json_decode(data): + return yaml.safe_load(data) + + +def split32(data): + """ Split data into pieces of 32 bytes. """ + all_pieces = [] + + for position in range(0, len(data), 32): + piece = data[position:position + 32] + all_pieces.append(piece) + + return all_pieces + + +def _canonical_type(name): # pylint: disable=too-many-return-statements + """ Replace aliases to the corresponding type to compute the ids. """ + + if name == 'int': + return 'int256' + + if name == 'uint': + return 'uint256' + + if name == 'fixed': + return 'fixed128x128' + + if name == 'ufixed': + return 'ufixed128x128' + + if name.startswith('int['): + return 'int256' + name[3:] + + if name.startswith('uint['): + return 'uint256' + name[4:] + + if name.startswith('fixed['): + return 'fixed128x128' + name[5:] + + if name.startswith('ufixed['): + return 'ufixed128x128' + name[6:] + + return name + + +def normalize_name(name): + """ Return normalized event/function name. """ + if '(' in name: + return name[:name.find('(')] + + return name + + +def method_id(name, encode_types): + """ Return the unique method id. + + The signature is defined as the canonical expression of the basic + prototype, i.e. the function name with the parenthesised list of parameter + types. Parameter types are split by a single comma - no spaces are used. + + The method id is defined as the first four bytes (left, high-order in + big-endian) of the Keccak (SHA-3) hash of the signature of the function. + """ + function_types = [ + _canonical_type(type_) + for type_ in encode_types + ] + + function_signature = '{function_name}({canonical_types})'.format( + function_name=name, + canonical_types=','.join(function_types), + ) + + function_keccak = utils.sha3(function_signature) + first_bytes = function_keccak[:4] + + return big_endian_to_int(first_bytes) + + +def event_id(name, encode_types): + """ Return the event id. + + Defined as: + + `keccak(EVENT_NAME+"("+EVENT_ARGS.map(canonical_type_of).join(",")+")")` + + Where `canonical_type_of` is a function that simply returns the canonical + type of a given argument, e.g. for uint indexed foo, it would return + uint256). Note the lack of spaces. + """ + + event_types = [ + _canonical_type(type_) + for type_ in encode_types + ] + + event_signature = '{event_name}({canonical_types})'.format( + event_name=name, + canonical_types=','.join(event_types), + ) + + return big_endian_to_int(utils.sha3(event_signature)) + + +def decint(n, signed=False): # pylint: disable=invalid-name,too-many-branches + ''' Decode an unsigned/signed integer. ''' + + if isinstance(n, str): + n = utils.to_string(n) + + if n is True: + return 1 + + if n is False: + return 0 + + if n is None: + return 0 + + if is_numeric(n): + if signed: + if not -TT255 <= n <= TT255 - 1: + raise EncodingError('Number out of range: %r' % n) + else: + if not 0 <= n <= TT256 - 1: + raise EncodingError('Number out of range: %r' % n) + + return n + + if is_string(n): + if len(n) > 32: + raise EncodingError('String too long: %r' % n) + + if len(n) == 40: + int_bigendian = decode_hex(n) + else: + int_bigendian = n # pylint: disable=redefined-variable-type + + result = big_endian_to_int(int_bigendian) + if signed: + if result >= TT255: + result -= TT256 + + if not -TT255 <= result <= TT255 - 1: + raise EncodingError('Number out of range: %r' % n) + else: + if not 0 <= result <= TT256 - 1: + raise EncodingError('Number out of range: %r' % n) + + return result + + raise EncodingError('Cannot decode integer: %r' % n) + + +def encode_single(typ, arg): # pylint: disable=too-many-return-statements,too-many-branches,too-many-statements,too-many-locals + ''' Encode `arg` as `typ`. + + `arg` will be encoded in a best effort manner, were necessary the function + will try to correctly define the underlying binary representation (ie. + decoding a hex-encoded address/hash). + + Args: + typ (Tuple[(str, int, list)]): A 3-tuple defining the `arg` type. + + The first element defines the type name. + The second element defines the type length in bits. + The third element defines if it's an array type. + + Together the first and second defines the elementary type, the third + element must be present but is ignored. + + Valid type names are: + - uint + - int + - bool + - ufixed + - fixed + - string + - bytes + - hash + - address + + arg (object): The object to be encoded, it must be a python object + compatible with the `typ`. + + Raises: + ValueError: when an invalid `typ` is supplied. + ValueOutOfBounds: when `arg` cannot be encoded as `typ` because of the + binary contraints. + + Note: + This function don't work with array types, for that use the `enc` + function. + ''' + base, sub, _ = typ + + if base == 'uint': + sub = int(sub) + + if not (0 < sub <= 256 and sub % 8 == 0): + raise ValueError('invalid unsigned integer bit length {}'.format(sub)) + + try: + i = decint(arg, signed=False) + except EncodingError: + # arg is larger than 2**256 + raise ValueOutOfBounds(repr(arg)) + + if not 0 <= i < 2 ** sub: + raise ValueOutOfBounds(repr(arg)) + + value_encoded = int_to_big_endian(i) + return zpad(value_encoded, 32) + + if base == 'int': + sub = int(sub) + bits = sub - 1 + + if not (0 < sub <= 256 and sub % 8 == 0): + raise ValueError('invalid integer bit length {}'.format(sub)) + + try: + i = decint(arg, signed=True) + except EncodingError: + # arg is larger than 2**255 + raise ValueOutOfBounds(repr(arg)) + + if not -2 ** bits <= i < 2 ** bits: + raise ValueOutOfBounds(repr(arg)) + + value = i % 2 ** sub # convert negative to "equivalent" positive + value_encoded = int_to_big_endian(value) + return zpad(value_encoded, 32) + + if base == 'bool': + if arg is True: + value_encoded = int_to_big_endian(1) + elif arg is False: + value_encoded = int_to_big_endian(0) + else: + raise ValueError('%r is not bool' % arg) + + return zpad(value_encoded, 32) + + if base == 'ufixed': + sub = str(sub) # pylint: disable=redefined-variable-type + + high_str, low_str = sub.split('x') + high = int(high_str) + low = int(low_str) + + if not (0 < high + low <= 256 and high % 8 == 0 and low % 8 == 0): + raise ValueError('invalid unsigned fixed length {}'.format(sub)) + + if not 0 <= arg < 2 ** high: + raise ValueOutOfBounds(repr(arg)) + + float_point = arg * 2 ** low + fixed_point = int(float_point) + return zpad(int_to_big_endian(fixed_point), 32) + + if base == 'fixed': + sub = str(sub) # pylint: disable=redefined-variable-type + + high_str, low_str = sub.split('x') + high = int(high_str) + low = int(low_str) + bits = high - 1 + + if not (0 < high + low <= 256 and high % 8 == 0 and low % 8 == 0): + raise ValueError('invalid unsigned fixed length {}'.format(sub)) + + if not -2 ** bits <= arg < 2 ** bits: + raise ValueOutOfBounds(repr(arg)) + + float_point = arg * 2 ** low + fixed_point = int(float_point) + value = fixed_point % 2 ** 256 + return zpad(int_to_big_endian(value), 32) + + if base == 'string': + if isinstance(arg, utils.unicode): + arg = arg.encode('utf8') + else: + try: + arg.decode('utf8') + except UnicodeDecodeError: + raise ValueError('string must be utf8 encoded') + + if len(sub): # fixed length + if not 0 <= len(arg) <= int(sub): + raise ValueError('invalid string length {}'.format(sub)) + + if not 0 <= int(sub) <= 32: + raise ValueError('invalid string length {}'.format(sub)) + + return rzpad(arg, 32) + + if not 0 <= len(arg) < TT256: + raise Exception('Integer invalid or out of range: %r' % arg) + + length_encoded = zpad(int_to_big_endian(len(arg)), 32) + value_encoded = rzpad(arg, utils.ceil32(len(arg))) + + return length_encoded + value_encoded + + if base == 'bytes': + if not is_string(arg): + raise EncodingError('Expecting string: %r' % arg) + + arg = utils.to_string(arg) # py2: force unicode into str + + if len(sub): # fixed length + if not 0 <= len(arg) <= int(sub): + raise ValueError('string must be utf8 encoded') + + if not 0 <= int(sub) <= 32: + raise ValueError('string must be utf8 encoded') + + return rzpad(arg, 32) + + if not 0 <= len(arg) < TT256: + raise Exception('Integer invalid or out of range: %r' % arg) + + length_encoded = zpad(int_to_big_endian(len(arg)), 32) + value_encoded = rzpad(arg, utils.ceil32(len(arg))) + + return length_encoded + value_encoded + + if base == 'hash': + if not (int(sub) and int(sub) <= 32): + raise EncodingError('too long: %r' % arg) + + if isnumeric(arg): + return zpad(encode_int(arg), 32) + + if len(arg) == int(sub): + return zpad(arg, 32) + + if len(arg) == int(sub) * 2: + return zpad(decode_hex(arg), 32) + + raise EncodingError('Could not parse hash: %r' % arg) + + if base == 'address': + assert sub == '' + + if isnumeric(arg): + return zpad(encode_int(arg), 32) + + if len(arg) == 20: + return zpad(arg, 32) + + if len(arg) == 40: + return zpad(decode_hex(arg), 32) + + if len(arg) == 42 and arg[:2] == '0x': + return zpad(decode_hex(arg[2:]), 32) + + raise EncodingError('Could not parse address: %r' % arg) + raise EncodingError('Unhandled type: %r %r' % (base, sub)) + + +class ContractTranslator(object): + + def __init__(self, contract_interface): + if is_string(contract_interface): + contract_interface = json_decode(contract_interface) + + self.fallback_data = None + self.constructor_data = None + self.function_data = {} + self.event_data = {} + + for description in contract_interface: + entry_type = description.get('type', 'function') + encode_types = [] + signature = [] + + # If it's a function/constructor/event + if entry_type != 'fallback' and 'inputs' in description: + encode_types = [ + element['type'] + for element in description.get('inputs') + ] + + signature = [ + (element['type'], element['name']) + for element in description.get('inputs') + ] + + if entry_type == 'function': + normalized_name = normalize_name(description['name']) + + decode_types = [ + element['type'] + for element in description['outputs'] + ] + + self.function_data[normalized_name] = { + 'prefix': method_id(normalized_name, encode_types), + 'encode_types': encode_types, + 'decode_types': decode_types, + 'is_constant': description.get('constant', False), + 'signature': signature, + 'payable': description.get('payable', False), + } + + elif entry_type == 'event': + normalized_name = normalize_name(description['name']) + + indexed = [ + element['indexed'] + for element in description['inputs'] + ] + names = [ + element['name'] + for element in description['inputs'] + ] + # event_id == topics[0] + self.event_data[event_id(normalized_name, encode_types)] = { + 'types': encode_types, + 'name': normalized_name, + 'names': names, + 'indexed': indexed, + 'anonymous': description.get('anonymous', False), + } + + elif entry_type == 'constructor': + if self.constructor_data is not None: + raise ValueError('Only one constructor is supported.') + + self.constructor_data = { + 'encode_types': encode_types, + 'signature': signature, + } + + elif entry_type == 'fallback': + if self.fallback_data is not None: + raise ValueError('Only one fallback function is supported.') + self.fallback_data = {'payable': description['payable']} + + else: + raise ValueError('Unknown type {}'.format(description['type'])) + + def encode(self, function_name, args): + warnings.warn('encode is deprecated, please use encode_function_call', DeprecationWarning) + return self.encode_function_call(function_name, args) + + def decode(self, function_name, data): + warnings.warn('decode is deprecated, please use decode_function_result', DeprecationWarning) + return self.decode_function_result(function_name, data) + + def encode_function_call(self, function_name, args): + """ Return the encoded function call. + + Args: + function_name (str): One of the existing functions described in the + contract interface. + args (List[object]): The function arguments that wll be encoded and + used in the contract execution in the vm. + + Return: + bin: The encoded function name and arguments so that it can be used + with the evm to execute a funcion call, the binary string follows + the Ethereum Contract ABI. + """ + if function_name not in self.function_data: + raise ValueError('Unkown function {}'.format(function_name)) + + description = self.function_data[function_name] + + function_selector = zpad(encode_int(description['prefix']), 4) + arguments = encode_abi(description['encode_types'], args) + + return function_selector + arguments + + def decode_function_result(self, function_name, data): + """ Return the function call result decoded. + + Args: + function_name (str): One of the existing functions described in the + contract interface. + data (bin): The encoded result from calling `function_name`. + + Return: + List[object]: The values returned by the call to `function_name`. + """ + description = self.function_data[function_name] + arguments = decode_abi(description['decode_types'], data) + return arguments + + def encode_constructor_arguments(self, args): + """ Return the encoded constructor call. """ + if self.constructor_data is None: + raise ValueError("The contract interface didn't have a constructor") + + return encode_abi(self.constructor_data['encode_types'], args) + + def decode_event(self, log_topics, log_data): + """ Return a dictionary representation the log. + + Note: + This function won't work with anonymous events. + + Args: + log_topics (List[bin]): The log's indexed arguments. + log_data (bin): The encoded non-indexed arguments. + """ + # https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI#function-selector-and-argument-encoding + + # topics[0]: keccak(EVENT_NAME+"("+EVENT_ARGS.map(canonical_type_of).join(",")+")") + # If the event is declared as anonymous the topics[0] is not generated; + if not len(log_topics) or log_topics[0] not in self.event_data: + raise ValueError('Unknown log type') + + event_id_ = log_topics[0] + + event = self.event_data[event_id_] + + # data: abi_serialise(EVENT_NON_INDEXED_ARGS) + # EVENT_NON_INDEXED_ARGS is the series of EVENT_ARGS that are not + # indexed, abi_serialise is the ABI serialisation function used for + # returning a series of typed values from a function. + unindexed_types = [ + type_ + for type_, indexed in zip(event['types'], event['indexed']) + if not indexed + ] + unindexed_args = decode_abi(unindexed_types, log_data) + + # topics[n]: EVENT_INDEXED_ARGS[n - 1] + # EVENT_INDEXED_ARGS is the series of EVENT_ARGS that are indexed + indexed_count = 1 # skip topics[0] + + result = {} + for name, type_, indexed in zip(event['names'], event['types'], event['indexed']): + if indexed: + topic_bytes = utils.zpad( + utils.encode_int(log_topics[indexed_count]), + 32, + ) + indexed_count += 1 + value = decode_single(process_type(type_), topic_bytes) + else: + value = unindexed_args.pop(0) + + result[name] = value + result['_event_type'] = utils.to_string(event['name']) + + return result + + def listen(self, log, noprint=True): + """ + Return a dictionary representation of the Log instance. + + Note: + This function won't work with anonymous events. + + Args: + log (processblock.Log): The Log instance that needs to be parsed. + noprint (bool): Flag to turn off priting of the decoded log instance. + """ + try: + result = self.decode_event(log.topics, log.data) + except ValueError: + return # api compatibility + + if not noprint: + print(result) + + return result + + +def process_type(typ): + # Crazy reg expression to separate out base type component (eg. uint), + # size (eg. 256, 128x128, none), array component (eg. [], [45], none) + regexp = '([a-z]*)([0-9]*x?[0-9]*)((\[[0-9]*\])*)' + base, sub, arr, _ = re.match(regexp, utils.to_string_for_regexp(typ)).groups() + arrlist = re.findall('\[[0-9]*\]', arr) + assert len(''.join(arrlist)) == len(arr), \ + "Unknown characters found in array declaration" + # Check validity of string type + if base == 'string' or base == 'bytes': + assert re.match('^[0-9]*$', sub), \ + "String type must have no suffix or numerical suffix" + assert not sub or int(sub) <= 32, \ + "Maximum 32 bytes for fixed-length str or bytes" + # Check validity of integer type + elif base == 'uint' or base == 'int': + assert re.match('^[0-9]+$', sub), \ + "Integer type must have numerical suffix" + assert 8 <= int(sub) <= 256, \ + "Integer size out of bounds" + assert int(sub) % 8 == 0, \ + "Integer size must be multiple of 8" + # Check validity of fixed type + elif base == 'ufixed' or base == 'fixed': + assert re.match('^[0-9]+x[0-9]+$', sub), \ + "Real type must have suffix of form x, eg. 128x128" + high, low = [int(x) for x in sub.split('x')] + assert 8 <= (high + low) <= 256, \ + "Real size out of bounds (max 32 bytes)" + assert high % 8 == 0 and low % 8 == 0, \ + "Real high/low sizes must be multiples of 8" + # Check validity of hash type + elif base == 'hash': + assert re.match('^[0-9]+$', sub), \ + "Hash type must have numerical suffix" + # Check validity of address type + elif base == 'address': + assert sub == '', "Address cannot have suffix" + return base, sub, [ast.literal_eval(x) for x in arrlist] + + +# Returns the static size of a type, or None if dynamic +def get_size(typ): + base, sub, arrlist = typ + if not len(arrlist): + if base in ('string', 'bytes') and not sub: + return None + return 32 + if arrlist[-1] == []: + return None + o = get_size((base, sub, arrlist[:-1])) + if o is None: + return None + return arrlist[-1][0] * o + + +# Encodes a single value (static or dynamic) +def enc(typ, arg): + base, sub, arrlist = typ + type_size = get_size(typ) + + if base in ('string', 'bytes') and not sub: + return encode_single(typ, arg) + + # Encode dynamic-sized lists via the head/tail mechanism described in + # https://github.com/ethereum/wiki/wiki/Proposal-for-new-ABI-value-encoding + if type_size is None: + assert isinstance(arg, list), \ + "Expecting a list argument" + subtyp = base, sub, arrlist[:-1] + subsize = get_size(subtyp) + myhead, mytail = b'', b'' + if arrlist[-1] == []: + myhead += enc(INT256, len(arg)) + else: + assert len(arg) == arrlist[-1][0], \ + "Wrong array size: found %d, expecting %d" % \ + (len(arg), arrlist[-1][0]) + for i in range(len(arg)): + if subsize is None: + myhead += enc(INT256, 32 * len(arg) + len(mytail)) + mytail += enc(subtyp, arg[i]) + else: + myhead += enc(subtyp, arg[i]) + return myhead + mytail + # Encode static-sized lists via sequential packing + else: + if arrlist == []: + return utils.to_string(encode_single(typ, arg)) + else: + subtyp = base, sub, arrlist[:-1] + o = b'' + for x in arg: + o += enc(subtyp, x) + return o + + +# Encodes multiple arguments using the head/tail mechanism +def encode_abi(types, args): + headsize = 0 + proctypes = [process_type(typ) for typ in types] + sizes = [get_size(typ) for typ in proctypes] + for i, arg in enumerate(args): + if sizes[i] is None: + headsize += 32 + else: + headsize += sizes[i] + myhead, mytail = b'', b'' + for i, arg in enumerate(args): + if sizes[i] is None: + myhead += enc(INT256, headsize + len(mytail)) + mytail += enc(proctypes[i], args[i]) + else: + myhead += enc(proctypes[i], args[i]) + return myhead + mytail + + +# Decodes a single base datum +def decode_single(typ, data): + base, sub, _ = typ + if base == 'address': + return encode_hex(data[12:]) + elif base == 'hash': + return data[32 - int(sub):] + elif base == 'string' or base == 'bytes': + if len(sub): + return data[:int(sub)] + else: + l = big_endian_to_int(data[0:32]) + return data[32:][:l] + elif base == 'uint': + return big_endian_to_int(data) + elif base == 'int': + o = big_endian_to_int(data) + return (o - 2 ** int(sub)) if o >= 2 ** (int(sub) - 1) else o + elif base == 'ufixed': + high, low = [int(x) for x in sub.split('x')] + return big_endian_to_int(data) * 1.0 // 2 ** low + elif base == 'fixed': + high, low = [int(x) for x in sub.split('x')] + o = big_endian_to_int(data) + i = (o - 2 ** (high + low)) if o >= 2 ** (high + low - 1) else o + return (i * 1.0 // 2 ** low) + elif base == 'bool': + return bool(int(encode_hex(data), 16)) + + +# Decodes multiple arguments using the head/tail mechanism +def decode_abi(types, data): + # Process types + proctypes = [process_type(typ) for typ in types] + # Get sizes of everything + sizes = [get_size(typ) for typ in proctypes] + # Initialize array of outputs + outs = [None] * len(types) + # Initialize array of start positions + start_positions = [None] * len(types) + [len(data)] + # If a type is static, grab the data directly, otherwise record + # its start position + pos = 0 + for i, typ in enumerate(types): + if sizes[i] is None: + start_positions[i] = big_endian_to_int(data[pos:pos + 32]) + j = i - 1 + while j >= 0 and start_positions[j] is None: + start_positions[j] = start_positions[i] + j -= 1 + pos += 32 + else: + outs[i] = data[pos:pos + sizes[i]] + pos += sizes[i] + # We add a start position equal to the length of the entire data + # for convenience. + j = len(types) - 1 + while j >= 0 and start_positions[j] is None: + start_positions[j] = start_positions[len(types)] + j -= 1 + assert pos <= len(data), "Not enough data for head" + # Grab the data for tail arguments using the start positions + # calculated above + for i, typ in enumerate(types): + if sizes[i] is None: + offset = start_positions[i] + next_offset = start_positions[i + 1] + outs[i] = data[offset:next_offset] + # Recursively decode them all + return [dec(proctypes[i], outs[i]) for i in range(len(outs))] + + +# Decode a single value (static or dynamic) +def dec(typ, arg): + base, sub, arrlist = typ + sz = get_size(typ) + # Dynamic-sized strings are encoded as + + if base in ('string', 'bytes') and not sub: + L = big_endian_to_int(arg[:32]) + assert len(arg[32:]) == ceil32(L), "Wrong data size for string/bytes object" + return arg[32:][:L] + # Dynamic-sized arrays + elif sz is None: + L = big_endian_to_int(arg[:32]) + subtyp = base, sub, arrlist[:-1] + subsize = get_size(subtyp) + # If children are dynamic, use the head/tail mechanism. Fortunately, + # here the code is simpler since we do not have to worry about + # mixed dynamic and static children, as we do in the top-level multi-arg + # case + if subsize is None: + assert len(arg) >= 32 + 32 * L, "Not enough data for head" + start_positions = [big_endian_to_int(arg[32 + 32 * i: 64 + 32 * i]) + for i in range(L)] + [len(arg)] + outs = [arg[start_positions[i]: start_positions[i + 1]] + for i in range(L)] + return [dec(subtyp, out) for out in outs] + # If children are static, then grab the data slice for each one and + # sequentially decode them manually + else: + return [dec(subtyp, arg[32 + subsize * i: 32 + subsize * (i + 1)]) + for i in range(L)] + # Static-sized arrays: decode piece-by-piece + elif len(arrlist): + L = arrlist[-1][0] + subtyp = base, sub, arrlist[:-1] + subsize = get_size(subtyp) + return [dec(subtyp, arg[subsize * i:subsize * (i + 1)]) + for i in range(L)] + else: + return decode_single(typ, arg) \ No newline at end of file From 54db0e2709e88c6223f24301b16c868b71be848c Mon Sep 17 00:00:00 2001 From: Mark Omo Date: Mon, 19 Dec 2016 19:04:09 -0700 Subject: [PATCH 3/5] Updated requirments to reflect new files --- requirements.txt | 3 +++ setup.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/requirements.txt b/requirements.txt index 91ba3b0..2a39e49 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,5 @@ requests==2.9.1 rlp>=0.4.4 +pycryptodome>=3.3.1 +bitcoin +PyYAML diff --git a/setup.py b/setup.py index 3df4226..71eb1c8 100644 --- a/setup.py +++ b/setup.py @@ -24,5 +24,8 @@ install_requires=[ 'requests==2.9.1', 'rlp>=0.4.4', + 'pycryptodome>=3.3.1', + 'bitcoin', + 'PyYAML', ], ) From eced5ca70e2b2bd163339639570ef016d5046635 Mon Sep 17 00:00:00 2001 From: Mark Omo Date: Fri, 30 Dec 2016 17:22:26 -0700 Subject: [PATCH 4/5] Fixed errors --- requirements.txt | 10 ++++---- setup.py | 62 ++++++++++++++++++++++++------------------------ 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/requirements.txt b/requirements.txt index 2a39e49..533ff24 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ -requests==2.9.1 -rlp>=0.4.4 -pycryptodome>=3.3.1 -bitcoin -PyYAML +requests==2.9.1 +rlp>=0.4.4 +pycryptodome>=3.3.1 +bitcoin +PyYAML diff --git a/setup.py b/setup.py index 71eb1c8..9b871f3 100644 --- a/setup.py +++ b/setup.py @@ -1,31 +1,31 @@ -try: - from setuptools import setup -except ImportError: - from distutils.core import setup - -setup( - name='ethjsonrpc', - version='0.3.0', - description='Ethereum JSON-RPC client', - long_description=open('README.rst').read(), - author='ConsenSys', - author_email='info@consensys.net', - url='https://github.com/ConsenSys/ethjsonrpc', - packages=['ethjsonrpc'], - license='Unlicense', - classifiers=[ - 'Development Status :: 4 - Beta', - 'Intended Audience :: Developers', - 'License :: Public Domain', - 'Operating System :: OS Independent', - 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - ], - install_requires=[ - 'requests==2.9.1', - 'rlp>=0.4.4', - 'pycryptodome>=3.3.1', - 'bitcoin', - 'PyYAML', - ], -) +try: + from setuptools import setup +except ImportError: + from distutils.core import setup + +setup( + name='ethjsonrpc', + version='0.3.0', + description='Ethereum JSON-RPC client', + long_description=open('README.rst').read(), + author='ConsenSys', + author_email='info@consensys.net', + url='https://github.com/ConsenSys/ethjsonrpc', + packages=['ethjsonrpc'], + license='Unlicense', + classifiers=[ + 'Development Status :: 4 - Beta', + 'Intended Audience :: Developers', + 'License :: Public Domain', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2', + ], + install_requires=[ + 'requests==2.9.1', + 'rlp>=0.4.4', + 'bitcoin', + 'PyYAML', + 'pycryptodome>=3.3.1' + ], +) From 41657283af3321c722989c7bb8af175a13123877 Mon Sep 17 00:00:00 2001 From: janet Date: Fri, 12 Oct 2018 15:52:09 +0800 Subject: [PATCH 5/5] change basestring to str --- ethjsonrpc/client.py | 6 +++--- ethjsonrpc/utils.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ethjsonrpc/client.py b/ethjsonrpc/client.py index 072376f..ecc9048 100644 --- a/ethjsonrpc/client.py +++ b/ethjsonrpc/client.py @@ -306,7 +306,7 @@ def eth_getCode(self, address, default_block=BLOCK_TAG_LATEST): NEEDS TESTING ''' - if isinstance(default_block, basestring): + if isinstance(default_block, str): if default_block not in BLOCK_TAGS: raise ValueError return self._call('eth_getCode', [address, default_block]) @@ -357,7 +357,7 @@ def eth_call(self, to_address, from_address=None, gas=None, gas_price=None, valu NEEDS TESTING ''' - if isinstance(default_block, basestring): + if isinstance(default_block, str): if default_block not in BLOCK_TAGS: raise ValueError obj = {} @@ -381,7 +381,7 @@ def eth_estimateGas(self, to_address=None, from_address=None, gas=None, gas_pric NEEDS TESTING ''' - if isinstance(default_block, basestring): + if isinstance(default_block, str): if default_block not in BLOCK_TAGS: raise ValueError obj = {} diff --git a/ethjsonrpc/utils.py b/ethjsonrpc/utils.py index e4defc2..0ff4ab5 100644 --- a/ethjsonrpc/utils.py +++ b/ethjsonrpc/utils.py @@ -16,7 +16,7 @@ def clean_hex(d): return hex(d).rstrip('L') def validate_block(block): - if isinstance(block, basestring): + if isinstance(block, str): if block not in BLOCK_TAGS: raise ValueError('invalid block tag') if isinstance(block, int):