Eliminate dependencies on all ethereum packages except py_ecc
The rationale for this patch is similar to that of commit a7126ed:
- Many packages were imported for the sake of just a few trivial conversion
functions. These functions have been copied into the local pyethereum.utils.
- rlp has been locally copied and its import statements modified to import
the locally-copied functions. Unneeded classes and functions have been
removed.
- As a result, dependencies on the following external packages have been
eliminated:
+ rlp
+ eth-hash
+ eth-utils
+ eth-typing
+ toolz
+ cytoolz
+ setuptools
This commit is contained in:
parent
122ac43573
commit
66d0f76635
22 changed files with 1389 additions and 258 deletions
|
|
@ -21,7 +21,7 @@ altcoins.eth.contract: Ethereum contract and token classes for the MMGen suite
|
|||
"""
|
||||
|
||||
from decimal import Decimal
|
||||
import rlp
|
||||
from . import rlp
|
||||
|
||||
from mmgen.globalvars import g
|
||||
from mmgen.common import *
|
||||
|
|
@ -110,7 +110,7 @@ class Token(MMGenObject): # ERC20
|
|||
def txsign(self,tx_in,key,from_addr,chain_id=None):
|
||||
|
||||
try: from ethereum.transactions import Transaction
|
||||
except: from mmgen.altcoins.eth.pyethereum.transactions import Transaction
|
||||
except: from .pyethereum.transactions import Transaction
|
||||
|
||||
if chain_id is None:
|
||||
chain_id_method = ('parity_chainId','eth_chainId')['eth_chainId' in g.rpch.caps]
|
||||
|
|
|
|||
21
mmgen/altcoins/eth/pyethereum/LICENSE
Normal file
21
mmgen/altcoins/eth/pyethereum/LICENSE
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
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.
|
||||
|
|
@ -1,12 +1,13 @@
|
|||
#
|
||||
# Adapted from: # https://github.com/ethereum/pyethereum/blob/master/ethereum/transactions.py
|
||||
#
|
||||
import rlp
|
||||
from rlp.sedes import big_endian_int,binary
|
||||
from mmgen.altcoins.eth.pyethereum.utils import (
|
||||
str_to_bytes,encode_hex,ascii_chr,big_endian_to_int,TT256,mk_contract_address,
|
||||
from .. import rlp
|
||||
from ..rlp.sedes import big_endian_int,binary
|
||||
from .utils import (
|
||||
str_to_bytes,encode_hex,ascii_chr,big_endian_to_int,
|
||||
TT256,mk_contract_address,
|
||||
ecsign,ecrecover_to_pub,normalize_key )
|
||||
import mmgen.altcoins.eth.pyethereum.utils as utils
|
||||
from . import utils
|
||||
|
||||
class InvalidTransaction(Exception): pass
|
||||
class opcodes(object):
|
||||
|
|
|
|||
|
|
@ -2,12 +2,9 @@
|
|||
# Adapted from: https://github.com/ethereum/pyethereum/blob/master/ethereum/utils.py
|
||||
#
|
||||
|
||||
from py_ecc.secp256k1 import privtopub, ecdsa_raw_sign, ecdsa_raw_recover
|
||||
import rlp
|
||||
from rlp.sedes import big_endian_int, BigEndianInt, Binary
|
||||
from eth_utils import encode_hex as encode_hex_0x
|
||||
from eth_utils import decode_hex, int_to_big_endian, big_endian_to_int
|
||||
from rlp.utils import ALL_BYTES
|
||||
from py_ecc.secp256k1 import privtopub,ecdsa_raw_sign,ecdsa_raw_recover
|
||||
from .. import rlp
|
||||
from ..rlp.sedes import Binary
|
||||
|
||||
from mmgen.globalvars import g
|
||||
try:
|
||||
|
|
@ -19,6 +16,60 @@ except:
|
|||
def sha3_256(x):
|
||||
return keccak_256(x).digest()
|
||||
|
||||
import struct
|
||||
ALL_BYTES = tuple( struct.pack('B', i) for i in range(256) )
|
||||
|
||||
# from eth_utils:
|
||||
|
||||
# Type ignored for `codecs.decode()` due to lack of mypy support for 'hex' encoding
|
||||
# https://github.com/python/typeshed/issues/300
|
||||
from typing import AnyStr,Any,Callable,TypeVar
|
||||
import codecs
|
||||
import functools
|
||||
|
||||
T = TypeVar("T")
|
||||
TVal = TypeVar("TVal")
|
||||
TKey = TypeVar("TKey")
|
||||
|
||||
def apply_to_return_value(callback: Callable[..., T]) -> Callable[..., Callable[..., T]]:
|
||||
|
||||
def outer(fn):
|
||||
# We would need to type annotate *args and **kwargs but doing so segfaults
|
||||
# the PyPy builds. We ignore instead.
|
||||
@functools.wraps(fn)
|
||||
def inner(*args, **kwargs) -> T: # type: ignore
|
||||
return callback(fn(*args, **kwargs))
|
||||
|
||||
return inner
|
||||
|
||||
return outer
|
||||
|
||||
to_list = apply_to_return_value(list)
|
||||
to_set = apply_to_return_value(set)
|
||||
to_dict = apply_to_return_value(dict)
|
||||
to_tuple = apply_to_return_value(tuple)
|
||||
to_list = apply_to_return_value(list)
|
||||
|
||||
def encode_hex_0x(value: AnyStr) -> str:
|
||||
if not is_string(value):
|
||||
raise TypeError("Value must be an instance of str or unicode")
|
||||
binary_hex = codecs.encode(value, "hex") # type: ignore
|
||||
return '0x' + binary_hex.decode("ascii")
|
||||
|
||||
def decode_hex(value: str) -> bytes:
|
||||
if not isinstance(value,str):
|
||||
raise TypeError("Value must be an instance of str")
|
||||
return codecs.decode(remove_0x_prefix(value), "hex") # type: ignore
|
||||
|
||||
def is_bytes(value: Any) -> bool:
|
||||
return isinstance(value, (bytes,bytearray))
|
||||
|
||||
def int_to_big_endian(value: int) -> bytes:
|
||||
return value.to_bytes((value.bit_length() + 7) // 8 or 1, "big")
|
||||
|
||||
def big_endian_to_int(value: bytes) -> int:
|
||||
return int.from_bytes(value, "big")
|
||||
# end from eth_utils
|
||||
|
||||
class Memoize:
|
||||
def __init__(self, fn):
|
||||
|
|
@ -30,9 +81,6 @@ class Memoize:
|
|||
return self.memo[args]
|
||||
|
||||
TT256 = 2 ** 256
|
||||
TT256M1 = 2 ** 256 - 1
|
||||
TT255 = 2 ** 255
|
||||
SECP256K1P = 2**256 - 4294968273
|
||||
|
||||
def is_numeric(x): return isinstance(x, int)
|
||||
|
||||
|
|
@ -46,24 +94,11 @@ def to_string(value):
|
|||
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)
|
||||
|
||||
def encode_int32(v):
|
||||
return v.to_bytes(32, byteorder='big')
|
||||
|
||||
def bytes_to_int(value):
|
||||
return int.from_bytes(value, byteorder='big')
|
||||
|
||||
def str_to_bytes(value):
|
||||
if isinstance(value, bytearray):
|
||||
value = bytes(value)
|
||||
|
|
@ -102,39 +137,6 @@ 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 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):
|
||||
return sha3_256(to_string(seed))
|
||||
|
||||
|
|
@ -183,36 +185,6 @@ def normalize_key(key):
|
|||
return o
|
||||
|
||||
|
||||
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 int_to_addr(x):
|
||||
o = [b''] * 20
|
||||
for i in range(20):
|
||||
|
|
@ -221,153 +193,10 @@ def int_to_addr(x):
|
|||
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)
|
||||
|
||||
|
||||
def int_to_hex(x):
|
||||
o = encode_hex(encode_int(x))
|
||||
return '0x' + (o[1:] if (len(o) > 0 and o[0] == b'0') else o)
|
||||
|
||||
|
||||
def remove_0x_head(s):
|
||||
def remove_0x_prefix(s):
|
||||
return s[2:] if s[:2] in (b'0x', '0x') else s
|
||||
|
||||
|
||||
def parse_as_bin(s):
|
||||
return decode_hex(s[2:] if s[:2] == '0x' else s)
|
||||
|
||||
|
||||
def parse_as_int(s):
|
||||
return s if is_numeric(s) else int(
|
||||
'0' + s[2:], 16) if s[:2] == '0x' else int(s)
|
||||
|
||||
|
||||
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):
|
||||
|
|
@ -385,13 +214,4 @@ class Denoms():
|
|||
self.ether = 10 ** 18
|
||||
self.turing = 2 ** 256 - 1
|
||||
|
||||
|
||||
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)
|
||||
|
|
|
|||
21
mmgen/altcoins/eth/rlp/LICENSE
Normal file
21
mmgen/altcoins/eth/rlp/LICENSE
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Jnnk, Vitalik Buterin
|
||||
|
||||
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.
|
||||
14
mmgen/altcoins/eth/rlp/__init__.py
Normal file
14
mmgen/altcoins/eth/rlp/__init__.py
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
from . import sedes # noqa: F401
|
||||
from .codec import ( # noqa: F401
|
||||
encode,
|
||||
decode,
|
||||
infer_sedes,
|
||||
)
|
||||
from .exceptions import ( # noqa: F401
|
||||
RLPException,
|
||||
EncodingError,
|
||||
DecodingError,
|
||||
SerializationError,
|
||||
DeserializationError,
|
||||
)
|
||||
from .sedes import Serializable # noqa: F401
|
||||
10
mmgen/altcoins/eth/rlp/atomic.py
Normal file
10
mmgen/altcoins/eth/rlp/atomic.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import abc
|
||||
|
||||
|
||||
class Atomic(metaclass=abc.ABCMeta):
|
||||
"""ABC for objects that can be RLP encoded as is."""
|
||||
pass
|
||||
|
||||
|
||||
Atomic.register(bytes)
|
||||
Atomic.register(bytearray)
|
||||
277
mmgen/altcoins/eth/rlp/codec.py
Normal file
277
mmgen/altcoins/eth/rlp/codec.py
Normal file
|
|
@ -0,0 +1,277 @@
|
|||
import collections
|
||||
|
||||
from ..pyethereum.utils import big_endian_to_int,int_to_big_endian,is_bytes,ALL_BYTES
|
||||
|
||||
from .atomic import Atomic
|
||||
from .exceptions import EncodingError, DecodingError
|
||||
from .sedes.binary import Binary as BinaryClass
|
||||
from .sedes import big_endian_int, binary, boolean, text
|
||||
from .sedes.lists import List, is_sedes, is_sequence
|
||||
from .sedes.serializable import Serializable
|
||||
|
||||
|
||||
def encode(obj, sedes=None, infer_serializer=True, cache=True):
|
||||
"""Encode a Python object in RLP format.
|
||||
|
||||
By default, the object is serialized in a suitable way first (using
|
||||
:func:`rlp.infer_sedes`) and then encoded. Serialization can be explicitly
|
||||
suppressed by setting `infer_serializer` to ``False`` and not passing an
|
||||
alternative as `sedes`.
|
||||
|
||||
If `obj` has an attribute :attr:`_cached_rlp` (as, notably,
|
||||
:class:`rlp.Serializable`) and its value is not `None`, this value is
|
||||
returned bypassing serialization and encoding, unless `sedes` is given (as
|
||||
the cache is assumed to refer to the standard serialization which can be
|
||||
replaced by specifying `sedes`).
|
||||
|
||||
If `obj` is a :class:`rlp.Serializable` and `cache` is true, the result of
|
||||
the encoding will be stored in :attr:`_cached_rlp` if it is empty.
|
||||
|
||||
:param sedes: an object implementing a function ``serialize(obj)`` which will be used to
|
||||
serialize ``obj`` before encoding, or ``None`` to use the infered one (if any)
|
||||
:param infer_serializer: if ``True`` an appropriate serializer will be selected using
|
||||
:func:`rlp.infer_sedes` to serialize `obj` before encoding
|
||||
:param cache: cache the return value in `obj._cached_rlp` if possible
|
||||
(default `True`)
|
||||
:returns: the RLP encoded item
|
||||
:raises: :exc:`rlp.EncodingError` in the rather unlikely case that the item is too big to
|
||||
encode (will not happen)
|
||||
:raises: :exc:`rlp.SerializationError` if the serialization fails
|
||||
"""
|
||||
if isinstance(obj, Serializable):
|
||||
cached_rlp = obj._cached_rlp
|
||||
if sedes is None and cached_rlp:
|
||||
return cached_rlp
|
||||
else:
|
||||
really_cache = (
|
||||
cache and
|
||||
sedes is None
|
||||
)
|
||||
else:
|
||||
really_cache = False
|
||||
|
||||
if sedes:
|
||||
item = sedes.serialize(obj)
|
||||
elif infer_serializer:
|
||||
item = infer_sedes(obj).serialize(obj)
|
||||
else:
|
||||
item = obj
|
||||
|
||||
result = encode_raw(item)
|
||||
if really_cache:
|
||||
obj._cached_rlp = result
|
||||
return result
|
||||
|
||||
|
||||
def encode_raw(item):
|
||||
"""RLP encode (a nested sequence of) :class:`Atomic`s."""
|
||||
if isinstance(item, Atomic):
|
||||
if len(item) == 1 and item[0] < 128:
|
||||
return item
|
||||
payload = item
|
||||
prefix_offset = 128 # string
|
||||
elif not isinstance(item, str) and isinstance(item, collections.Sequence):
|
||||
payload = b''.join(encode_raw(x) for x in item)
|
||||
prefix_offset = 192 # list
|
||||
else:
|
||||
msg = 'Cannot encode object of type {0}'.format(type(item).__name__)
|
||||
raise EncodingError(msg, item)
|
||||
|
||||
try:
|
||||
prefix = length_prefix(len(payload), prefix_offset)
|
||||
except ValueError:
|
||||
raise EncodingError('Item too big to encode', item)
|
||||
|
||||
return prefix + payload
|
||||
|
||||
|
||||
LONG_LENGTH = 256**8
|
||||
|
||||
|
||||
def length_prefix(length, offset):
|
||||
"""Construct the prefix to lists or strings denoting their length.
|
||||
|
||||
:param length: the length of the item in bytes
|
||||
:param offset: ``0x80`` when encoding raw bytes, ``0xc0`` when encoding a
|
||||
list
|
||||
"""
|
||||
if length < 56:
|
||||
return ALL_BYTES[offset + length]
|
||||
elif length < LONG_LENGTH:
|
||||
length_string = int_to_big_endian(length)
|
||||
return ALL_BYTES[offset + 56 - 1 + len(length_string)] + length_string
|
||||
else:
|
||||
raise ValueError('Length greater than 256**8')
|
||||
|
||||
|
||||
SHORT_STRING = 128 + 56
|
||||
|
||||
|
||||
def consume_length_prefix(rlp, start):
|
||||
"""Read a length prefix from an RLP string.
|
||||
|
||||
:param rlp: the rlp byte string to read from
|
||||
:param start: the position at which to start reading
|
||||
:returns: a tuple ``(prefix, type, length, end)``, where ``type`` is either ``str``
|
||||
or ``list`` depending on the type of the following payload,
|
||||
``length`` is the length of the payload in bytes, and ``end`` is
|
||||
the position of the first payload byte in the rlp string
|
||||
"""
|
||||
b0 = rlp[start]
|
||||
if b0 < 128: # single byte
|
||||
return (b'', bytes, 1, start)
|
||||
elif b0 < SHORT_STRING: # short string
|
||||
if b0 - 128 == 1 and rlp[start + 1] < 128:
|
||||
raise DecodingError('Encoded as short string although single byte was possible', rlp)
|
||||
return (rlp[start:start + 1], bytes, b0 - 128, start + 1)
|
||||
elif b0 < 192: # long string
|
||||
ll = b0 - 183 # - (128 + 56 - 1)
|
||||
if rlp[start + 1:start + 2] == b'\x00':
|
||||
raise DecodingError('Length starts with zero bytes', rlp)
|
||||
len_prefix = rlp[start + 1:start + 1 + ll]
|
||||
l = big_endian_to_int(len_prefix) # noqa: E741
|
||||
if l < 56:
|
||||
raise DecodingError('Long string prefix used for short string', rlp)
|
||||
return (rlp[start:start + 1] + len_prefix, bytes, l, start + 1 + ll)
|
||||
elif b0 < 192 + 56: # short list
|
||||
return (rlp[start:start + 1], list, b0 - 192, start + 1)
|
||||
else: # long list
|
||||
ll = b0 - 192 - 56 + 1
|
||||
if rlp[start + 1:start + 2] == b'\x00':
|
||||
raise DecodingError('Length starts with zero bytes', rlp)
|
||||
len_prefix = rlp[start + 1:start + 1 + ll]
|
||||
l = big_endian_to_int(len_prefix) # noqa: E741
|
||||
if l < 56:
|
||||
raise DecodingError('Long list prefix used for short list', rlp)
|
||||
return (rlp[start:start + 1] + len_prefix, list, l, start + 1 + ll)
|
||||
|
||||
|
||||
def consume_payload(rlp, prefix, start, type_, length):
|
||||
"""Read the payload of an item from an RLP string.
|
||||
|
||||
:param rlp: the rlp string to read from
|
||||
:param type_: the type of the payload (``bytes`` or ``list``)
|
||||
:param start: the position at which to start reading
|
||||
:param length: the length of the payload in bytes
|
||||
:returns: a tuple ``(item, per_item_rlp, end)``, where ``item`` is
|
||||
the read item, per_item_rlp is a list containing the RLP
|
||||
encoding of each item and ``end`` is the position of the
|
||||
first unprocessed byte
|
||||
"""
|
||||
if type_ is bytes:
|
||||
item = rlp[start: start + length]
|
||||
return (item, [prefix + item], start + length)
|
||||
elif type_ is list:
|
||||
items = []
|
||||
per_item_rlp = []
|
||||
list_rlp = prefix
|
||||
next_item_start = start
|
||||
end = next_item_start + length
|
||||
while next_item_start < end:
|
||||
p, t, l, s = consume_length_prefix(rlp, next_item_start)
|
||||
item, item_rlp, next_item_start = consume_payload(rlp, p, s, t, l)
|
||||
per_item_rlp.append(item_rlp)
|
||||
# When the item returned above is a single element, item_rlp will also contain a
|
||||
# single element, but when it's a list, the first element will be the RLP of the
|
||||
# whole List, which is what we want here.
|
||||
list_rlp += item_rlp[0]
|
||||
items.append(item)
|
||||
per_item_rlp.insert(0, list_rlp)
|
||||
if next_item_start > end:
|
||||
raise DecodingError('List length prefix announced a too small '
|
||||
'length', rlp)
|
||||
return (items, per_item_rlp, next_item_start)
|
||||
else:
|
||||
raise TypeError('Type must be either list or bytes')
|
||||
|
||||
|
||||
def consume_item(rlp, start):
|
||||
"""Read an item from an RLP string.
|
||||
|
||||
:param rlp: the rlp string to read from
|
||||
:param start: the position at which to start reading
|
||||
:returns: a tuple ``(item, per_item_rlp, end)``, where ``item`` is
|
||||
the read item, per_item_rlp is a list containing the RLP
|
||||
encoding of each item and ``end`` is the position of the
|
||||
first unprocessed byte
|
||||
"""
|
||||
p, t, l, s = consume_length_prefix(rlp, start)
|
||||
return consume_payload(rlp, p, s, t, l)
|
||||
|
||||
|
||||
def decode(rlp, sedes=None, strict=True, recursive_cache=False, **kwargs):
|
||||
"""Decode an RLP encoded object.
|
||||
|
||||
If the deserialized result `obj` has an attribute :attr:`_cached_rlp` (e.g. if `sedes` is a
|
||||
subclass of :class:`rlp.Serializable`) it will be set to `rlp`, which will improve performance
|
||||
on subsequent :func:`rlp.encode` calls. Bear in mind however that `obj` needs to make sure that
|
||||
this value is updated whenever one of its fields changes or prevent such changes entirely
|
||||
(:class:`rlp.sedes.Serializable` does the latter).
|
||||
|
||||
:param sedes: an object implementing a function ``deserialize(code)`` which will be applied
|
||||
after decoding, or ``None`` if no deserialization should be performed
|
||||
:param \*\*kwargs: additional keyword arguments that will be passed to the deserializer
|
||||
:param strict: if false inputs that are longer than necessary don't cause an exception
|
||||
:returns: the decoded and maybe deserialized Python object
|
||||
:raises: :exc:`rlp.DecodingError` if the input string does not end after the root item and
|
||||
`strict` is true
|
||||
:raises: :exc:`rlp.DeserializationError` if the deserialization fails
|
||||
"""
|
||||
if not is_bytes(rlp):
|
||||
raise DecodingError('Can only decode RLP bytes, got type %s' % type(rlp).__name__, rlp)
|
||||
try:
|
||||
item, per_item_rlp, end = consume_item(rlp, 0)
|
||||
except IndexError:
|
||||
raise DecodingError('RLP string too short', rlp)
|
||||
if end != len(rlp) and strict:
|
||||
msg = 'RLP string ends with {} superfluous bytes'.format(len(rlp) - end)
|
||||
raise DecodingError(msg, rlp)
|
||||
if sedes:
|
||||
obj = sedes.deserialize(item, **kwargs)
|
||||
if is_sequence(obj) or hasattr(obj, '_cached_rlp'):
|
||||
_apply_rlp_cache(obj, per_item_rlp, recursive_cache)
|
||||
return obj
|
||||
else:
|
||||
return item
|
||||
|
||||
|
||||
def _apply_rlp_cache(obj, split_rlp, recursive):
|
||||
item_rlp = split_rlp.pop(0)
|
||||
if isinstance(obj, (int, bool, str, bytes, bytearray)):
|
||||
return
|
||||
elif hasattr(obj, '_cached_rlp'):
|
||||
obj._cached_rlp = item_rlp
|
||||
if not recursive:
|
||||
return
|
||||
for sub in obj:
|
||||
if isinstance(sub, (int, bool, str, bytes, bytearray)):
|
||||
split_rlp.pop(0)
|
||||
else:
|
||||
sub_rlp = split_rlp.pop(0)
|
||||
_apply_rlp_cache(sub, sub_rlp, recursive)
|
||||
|
||||
|
||||
def infer_sedes(obj):
|
||||
"""Try to find a sedes objects suitable for a given Python object.
|
||||
|
||||
The sedes objects considered are `obj`'s class, `big_endian_int` and
|
||||
`binary`. If `obj` is a sequence, a :class:`rlp.sedes.List` will be
|
||||
constructed recursively.
|
||||
|
||||
:param obj: the python object for which to find a sedes object
|
||||
:raises: :exc:`TypeError` if no appropriate sedes could be found
|
||||
"""
|
||||
if is_sedes(obj.__class__):
|
||||
return obj.__class__
|
||||
elif not isinstance(obj, bool) and isinstance(obj, int) and obj >= 0:
|
||||
return big_endian_int
|
||||
elif BinaryClass.is_valid_type(obj):
|
||||
return binary
|
||||
elif not isinstance(obj, str) and isinstance(obj, collections.Sequence):
|
||||
return List(map(infer_sedes, obj))
|
||||
elif isinstance(obj, bool):
|
||||
return boolean
|
||||
elif isinstance(obj, str):
|
||||
return text
|
||||
msg = 'Did not find sedes handling type {}'.format(type(obj).__name__)
|
||||
raise TypeError(msg)
|
||||
144
mmgen/altcoins/eth/rlp/exceptions.py
Normal file
144
mmgen/altcoins/eth/rlp/exceptions.py
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
class RLPException(Exception):
|
||||
"""Base class for exceptions raised by this package."""
|
||||
pass
|
||||
|
||||
|
||||
class EncodingError(RLPException):
|
||||
"""Exception raised if encoding fails.
|
||||
|
||||
:ivar obj: the object that could not be encoded
|
||||
"""
|
||||
|
||||
def __init__(self, message, obj):
|
||||
super(EncodingError, self).__init__(message)
|
||||
self.obj = obj
|
||||
|
||||
|
||||
class DecodingError(RLPException):
|
||||
"""Exception raised if decoding fails.
|
||||
|
||||
:ivar rlp: the RLP string that could not be decoded
|
||||
"""
|
||||
|
||||
def __init__(self, message, rlp):
|
||||
super(DecodingError, self).__init__(message)
|
||||
self.rlp = rlp
|
||||
|
||||
|
||||
class SerializationError(RLPException):
|
||||
"""Exception raised if serialization fails.
|
||||
|
||||
:ivar obj: the object that could not be serialized
|
||||
"""
|
||||
|
||||
def __init__(self, message, obj):
|
||||
super(SerializationError, self).__init__(message)
|
||||
self.obj = obj
|
||||
|
||||
|
||||
class ListSerializationError(SerializationError):
|
||||
"""Exception raised if serialization by a :class:`sedes.List` fails.
|
||||
|
||||
:ivar element_exception: the exception that occurred during the serialization of one of the
|
||||
elements, or `None` if the error is unrelated to a specific element
|
||||
:ivar index: the index in the list that produced the error or `None` if the error is unrelated
|
||||
to a specific element
|
||||
"""
|
||||
|
||||
def __init__(self, message=None, obj=None, element_exception=None, index=None):
|
||||
if message is None:
|
||||
assert index is not None
|
||||
assert element_exception is not None
|
||||
message = ('Serialization failed because of element at index {} '
|
||||
'("{}")'.format(index, str(element_exception)))
|
||||
super(ListSerializationError, self).__init__(message, obj)
|
||||
self.index = index
|
||||
self.element_exception = element_exception
|
||||
|
||||
|
||||
class ObjectSerializationError(SerializationError):
|
||||
"""Exception raised if serialization of a :class:`sedes.Serializable` object fails.
|
||||
|
||||
:ivar sedes: the :class:`sedes.Serializable` that failed
|
||||
:ivar list_exception: exception raised by the underlying list sedes, or `None` if no such
|
||||
exception has been raised
|
||||
:ivar field: name of the field of the object that produced the error, or `None` if no field
|
||||
responsible for the error
|
||||
"""
|
||||
|
||||
def __init__(self, message=None, obj=None, sedes=None, list_exception=None):
|
||||
if message is None:
|
||||
assert list_exception is not None
|
||||
if list_exception.element_exception is None:
|
||||
field = None
|
||||
message = ('Serialization failed because of underlying list '
|
||||
'("{}")'.format(str(list_exception)))
|
||||
else:
|
||||
assert sedes is not None
|
||||
field = sedes._meta.field_names[list_exception.index]
|
||||
message = ('Serialization failed because of field {} '
|
||||
'("{}")'.format(field, str(list_exception.element_exception)))
|
||||
else:
|
||||
field = None
|
||||
super(ObjectSerializationError, self).__init__(message, obj)
|
||||
self.field = field
|
||||
self.list_exception = list_exception
|
||||
|
||||
|
||||
class DeserializationError(RLPException):
|
||||
"""Exception raised if deserialization fails.
|
||||
|
||||
:ivar serial: the decoded RLP string that could not be deserialized
|
||||
"""
|
||||
|
||||
def __init__(self, message, serial):
|
||||
super(DeserializationError, self).__init__(message)
|
||||
self.serial = serial
|
||||
|
||||
|
||||
class ListDeserializationError(DeserializationError):
|
||||
"""Exception raised if deserialization by a :class:`sedes.List` fails.
|
||||
|
||||
:ivar element_exception: the exception that occurred during the deserialization of one of the
|
||||
elements, or `None` if the error is unrelated to a specific element
|
||||
:ivar index: the index in the list that produced the error or `None` if the error is unrelated
|
||||
to a specific element
|
||||
"""
|
||||
|
||||
def __init__(self, message=None, serial=None, element_exception=None, index=None):
|
||||
if not message:
|
||||
assert index is not None
|
||||
assert element_exception is not None
|
||||
message = ('Deserialization failed because of element at index {} '
|
||||
'("{}")'.format(index, str(element_exception)))
|
||||
super(ListDeserializationError, self).__init__(message, serial)
|
||||
self.index = index
|
||||
self.element_exception = element_exception
|
||||
|
||||
|
||||
class ObjectDeserializationError(DeserializationError):
|
||||
"""Exception raised if deserialization by a :class:`sedes.Serializable` fails.
|
||||
|
||||
:ivar sedes: the :class:`sedes.Serializable` that failed
|
||||
:ivar list_exception: exception raised by the underlying list sedes, or `None` if no such
|
||||
exception has been raised
|
||||
:ivar field: name of the field of the object that produced the error, or `None` if no field
|
||||
responsible for the error
|
||||
"""
|
||||
|
||||
def __init__(self, message=None, serial=None, sedes=None, list_exception=None):
|
||||
if not message:
|
||||
assert list_exception is not None
|
||||
if list_exception.element_exception is None:
|
||||
field = None
|
||||
message = ('Deserialization failed because of underlying list '
|
||||
'("{}")'.format(str(list_exception)))
|
||||
else:
|
||||
assert sedes is not None
|
||||
field = sedes._meta.field_names[list_exception.index]
|
||||
message = ('Deserialization failed because of field {} '
|
||||
'("{}")'.format(field, str(list_exception.element_exception)))
|
||||
super(ObjectDeserializationError, self).__init__(message, serial)
|
||||
self.sedes = sedes
|
||||
self.list_exception = list_exception
|
||||
self.field = field
|
||||
7
mmgen/altcoins/eth/rlp/sedes/__init__.py
Normal file
7
mmgen/altcoins/eth/rlp/sedes/__init__.py
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
from . import raw # noqa: F401
|
||||
from .binary import Binary, binary # noqa: F401
|
||||
from .boolean import Boolean, boolean # noqa: F401
|
||||
from .big_endian_int import BigEndianInt, big_endian_int # noqa: F401
|
||||
from .lists import List # noqa: F401
|
||||
from .text import Text, text # noqa: #401
|
||||
from .serializable import Serializable # noqa: F401
|
||||
46
mmgen/altcoins/eth/rlp/sedes/big_endian_int.py
Normal file
46
mmgen/altcoins/eth/rlp/sedes/big_endian_int.py
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
from ...pyethereum.utils import int_to_big_endian,big_endian_to_int
|
||||
from ..exceptions import DeserializationError,SerializationError
|
||||
|
||||
|
||||
class BigEndianInt(object):
|
||||
"""A sedes for big endian integers.
|
||||
|
||||
:param l: the size of the serialized representation in bytes or `None` to
|
||||
use the shortest possible one
|
||||
"""
|
||||
|
||||
def __init__(self, l=None):
|
||||
self.l = l
|
||||
|
||||
def serialize(self, obj):
|
||||
if isinstance(obj, bool) or not isinstance(obj, int):
|
||||
raise SerializationError('Can only serialize integers', obj)
|
||||
if self.l is not None and obj >= 256**self.l:
|
||||
raise SerializationError('Integer too large (does not fit in {} '
|
||||
'bytes)'.format(self.l), obj)
|
||||
if obj < 0:
|
||||
raise SerializationError('Cannot serialize negative integers', obj)
|
||||
|
||||
if obj == 0:
|
||||
s = b''
|
||||
else:
|
||||
s = int_to_big_endian(obj)
|
||||
|
||||
if self.l is not None:
|
||||
return b'\x00' * max(0, self.l - len(s)) + s
|
||||
else:
|
||||
return s
|
||||
|
||||
def deserialize(self, serial):
|
||||
if self.l is not None and len(serial) != self.l:
|
||||
raise DeserializationError('Invalid serialization (wrong size)',
|
||||
serial)
|
||||
if self.l is None and len(serial) > 0 and serial[0:1] == b'\x00':
|
||||
raise DeserializationError('Invalid serialization (not minimal '
|
||||
'length)', serial)
|
||||
|
||||
serial = serial or b'\x00'
|
||||
return big_endian_to_int(serial)
|
||||
|
||||
|
||||
big_endian_int = BigEndianInt()
|
||||
55
mmgen/altcoins/eth/rlp/sedes/binary.py
Normal file
55
mmgen/altcoins/eth/rlp/sedes/binary.py
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
from ..exceptions import SerializationError,DeserializationError
|
||||
from ..atomic import Atomic
|
||||
|
||||
|
||||
class Binary(object):
|
||||
"""A sedes object for binary data of certain length.
|
||||
|
||||
:param min_length: the minimal length in bytes or `None` for no lower limit
|
||||
:param max_length: the maximal length in bytes or `None` for no upper limit
|
||||
:param allow_empty: if true, empty strings are considered valid even if
|
||||
a minimum length is required otherwise
|
||||
"""
|
||||
|
||||
def __init__(self, min_length=None, max_length=None, allow_empty=False):
|
||||
self.min_length = min_length or 0
|
||||
if max_length is None:
|
||||
self.max_length = float('inf')
|
||||
else:
|
||||
self.max_length = max_length
|
||||
self.allow_empty = allow_empty
|
||||
|
||||
@classmethod
|
||||
def fixed_length(cls, l, allow_empty=False):
|
||||
"""Create a sedes for binary data with exactly `l` bytes."""
|
||||
return cls(l, l, allow_empty=allow_empty)
|
||||
|
||||
@classmethod
|
||||
def is_valid_type(cls, obj):
|
||||
return isinstance(obj, (bytes, bytearray))
|
||||
|
||||
def is_valid_length(self, l):
|
||||
return any((self.min_length <= l <= self.max_length,
|
||||
self.allow_empty and l == 0))
|
||||
|
||||
def serialize(self, obj):
|
||||
if not Binary.is_valid_type(obj):
|
||||
raise SerializationError('Object is not a serializable ({})'.format(type(obj)), obj)
|
||||
|
||||
if not self.is_valid_length(len(obj)):
|
||||
raise SerializationError('Object has invalid length', obj)
|
||||
|
||||
return obj
|
||||
|
||||
def deserialize(self, serial):
|
||||
if not isinstance(serial, Atomic):
|
||||
m = 'Objects of type {} cannot be deserialized'
|
||||
raise DeserializationError(m.format(type(serial).__name__), serial)
|
||||
|
||||
if self.is_valid_length(len(serial)):
|
||||
return serial
|
||||
else:
|
||||
raise DeserializationError('{} has invalid length'.format(type(serial)), serial)
|
||||
|
||||
|
||||
binary = Binary()
|
||||
30
mmgen/altcoins/eth/rlp/sedes/boolean.py
Normal file
30
mmgen/altcoins/eth/rlp/sedes/boolean.py
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
from ..exceptions import DeserializationError,SerializationError
|
||||
|
||||
|
||||
class Boolean:
|
||||
"""A sedes for booleans
|
||||
"""
|
||||
def serialize(self, obj):
|
||||
if not isinstance(obj, bool):
|
||||
raise SerializationError('Can only serialize integers', obj)
|
||||
|
||||
if obj is False:
|
||||
return b''
|
||||
elif obj is True:
|
||||
return b'\x01'
|
||||
else:
|
||||
raise Exception("Invariant: no other options for boolean values")
|
||||
|
||||
def deserialize(self, serial):
|
||||
if serial == b'':
|
||||
return False
|
||||
elif serial == b'\x01':
|
||||
return True
|
||||
else:
|
||||
raise DeserializationError(
|
||||
'Invalid serialized boolean. Must be either 0x01 or 0x00',
|
||||
serial
|
||||
)
|
||||
|
||||
|
||||
boolean = Boolean()
|
||||
92
mmgen/altcoins/eth/rlp/sedes/lists.py
Normal file
92
mmgen/altcoins/eth/rlp/sedes/lists.py
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
"""
|
||||
Module for sedes objects that use lists as serialization format.
|
||||
"""
|
||||
from collections import Sequence
|
||||
|
||||
from ...pyethereum.utils import to_list,to_tuple
|
||||
|
||||
from ..exceptions import (
|
||||
SerializationError,
|
||||
ListSerializationError,
|
||||
DeserializationError,
|
||||
ListDeserializationError,
|
||||
)
|
||||
|
||||
from .binary import (
|
||||
Binary as BinaryClass,
|
||||
)
|
||||
|
||||
|
||||
def is_sedes(obj):
|
||||
"""Check if `obj` is a sedes object.
|
||||
|
||||
A sedes object is characterized by having the methods `serialize(obj)` and
|
||||
`deserialize(serial)`.
|
||||
"""
|
||||
return hasattr(obj, 'serialize') and hasattr(obj, 'deserialize')
|
||||
|
||||
|
||||
def is_sequence(obj):
|
||||
"""Check if `obj` is a sequence, but not a string or bytes."""
|
||||
return isinstance(obj, Sequence) and not (
|
||||
isinstance(obj, str) or BinaryClass.is_valid_type(obj))
|
||||
|
||||
|
||||
class List(list):
|
||||
|
||||
"""A sedes for lists, implemented as a list of other sedes objects.
|
||||
|
||||
:param strict: If true (de)serializing lists that have a length not
|
||||
matching the sedes length will result in an error. If false
|
||||
(de)serialization will stop as soon as either one of the
|
||||
lists runs out of elements.
|
||||
"""
|
||||
|
||||
def __init__(self, elements=None, strict=True):
|
||||
super(List, self).__init__()
|
||||
self.strict = strict
|
||||
|
||||
if elements:
|
||||
for e in elements:
|
||||
if is_sedes(e):
|
||||
self.append(e)
|
||||
elif isinstance(e, Sequence):
|
||||
self.append(List(e))
|
||||
else:
|
||||
raise TypeError(
|
||||
'Instances of List must only contain sedes objects or '
|
||||
'nested sequences thereof.'
|
||||
)
|
||||
|
||||
@to_list
|
||||
def serialize(self, obj):
|
||||
if not is_sequence(obj):
|
||||
raise ListSerializationError('Can only serialize sequences', obj)
|
||||
if self.strict and len(self) != len(obj):
|
||||
raise ListSerializationError(
|
||||
'Serializing list length (%d) does not match sedes (%d)' % (
|
||||
len(obj), len(self)),
|
||||
obj)
|
||||
|
||||
for index, (element, sedes) in enumerate(zip(obj, self)):
|
||||
try:
|
||||
yield sedes.serialize(element)
|
||||
except SerializationError as e:
|
||||
raise ListSerializationError(obj=obj, element_exception=e, index=index)
|
||||
|
||||
@to_tuple
|
||||
def deserialize(self, serial):
|
||||
if not is_sequence(serial):
|
||||
raise ListDeserializationError('Can only deserialize sequences', serial)
|
||||
|
||||
if self.strict and len(serial) != len(self):
|
||||
raise ListDeserializationError(
|
||||
'Deserializing list length (%d) does not match sedes (%d)' % (
|
||||
len(serial), len(self)),
|
||||
serial)
|
||||
|
||||
for idx, (sedes, element) in enumerate(zip(self, serial)):
|
||||
try:
|
||||
yield sedes.deserialize(element)
|
||||
except DeserializationError as e:
|
||||
raise ListDeserializationError(serial=serial, element_exception=e, index=idx)
|
||||
29
mmgen/altcoins/eth/rlp/sedes/raw.py
Normal file
29
mmgen/altcoins/eth/rlp/sedes/raw.py
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
"""
|
||||
A sedes that does nothing. Thus, everything that can be directly encoded by RLP
|
||||
is serializable. This sedes can be used as a placeholder when deserializing
|
||||
larger structures.
|
||||
"""
|
||||
from collections import Sequence
|
||||
|
||||
from ..exceptions import SerializationError
|
||||
from ..atomic import Atomic
|
||||
|
||||
|
||||
def serializable(obj):
|
||||
if isinstance(obj, Atomic):
|
||||
return True
|
||||
elif not isinstance(obj, str) and isinstance(obj, Sequence):
|
||||
return all(map(serializable, obj))
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def serialize(obj):
|
||||
if not serializable(obj):
|
||||
raise SerializationError('Can only serialize nested lists of strings',
|
||||
obj)
|
||||
return obj
|
||||
|
||||
|
||||
def deserialize(serial):
|
||||
return serial
|
||||
484
mmgen/altcoins/eth/rlp/sedes/serializable.py
Normal file
484
mmgen/altcoins/eth/rlp/sedes/serializable.py
Normal file
|
|
@ -0,0 +1,484 @@
|
|||
import abc
|
||||
import collections
|
||||
import copy
|
||||
import enum
|
||||
import re
|
||||
|
||||
from ...pyethereum.utils import to_dict,to_set,to_tuple
|
||||
|
||||
from ..exceptions import (
|
||||
ListSerializationError,
|
||||
ObjectSerializationError,
|
||||
ListDeserializationError,
|
||||
ObjectDeserializationError,
|
||||
)
|
||||
|
||||
from .lists import (
|
||||
List,
|
||||
)
|
||||
|
||||
|
||||
class MetaBase:
|
||||
fields = None
|
||||
field_names = None
|
||||
field_attrs = None
|
||||
sedes = None
|
||||
|
||||
|
||||
def _get_duplicates(values):
|
||||
counts = collections.Counter(values)
|
||||
return tuple(
|
||||
item
|
||||
for item, num in counts.items()
|
||||
if num > 1
|
||||
)
|
||||
|
||||
|
||||
def validate_args_and_kwargs(args, kwargs, arg_names, allow_missing=False):
|
||||
duplicate_arg_names = _get_duplicates(arg_names)
|
||||
if duplicate_arg_names:
|
||||
raise TypeError("Duplicate argument names: {0}".format(sorted(duplicate_arg_names)))
|
||||
|
||||
needed_kwargs = arg_names[len(args):]
|
||||
used_kwargs = set(arg_names[:len(args)])
|
||||
|
||||
duplicate_kwargs = used_kwargs.intersection(kwargs.keys())
|
||||
if duplicate_kwargs:
|
||||
raise TypeError("Duplicate kwargs: {0}".format(sorted(duplicate_kwargs)))
|
||||
|
||||
unknown_kwargs = set(kwargs.keys()).difference(arg_names)
|
||||
if unknown_kwargs:
|
||||
raise TypeError("Unknown kwargs: {0}".format(sorted(unknown_kwargs)))
|
||||
|
||||
missing_kwargs = set(needed_kwargs).difference(kwargs.keys())
|
||||
if not allow_missing and missing_kwargs:
|
||||
raise TypeError("Missing kwargs: {0}".format(sorted(missing_kwargs)))
|
||||
|
||||
|
||||
@to_tuple
|
||||
def merge_kwargs_to_args(args, kwargs, arg_names, allow_missing=False):
|
||||
validate_args_and_kwargs(args, kwargs, arg_names, allow_missing=allow_missing)
|
||||
|
||||
needed_kwargs = arg_names[len(args):]
|
||||
|
||||
yield from args
|
||||
for arg_name in needed_kwargs:
|
||||
yield kwargs[arg_name]
|
||||
|
||||
|
||||
@to_dict
|
||||
def merge_args_to_kwargs(args, kwargs, arg_names, allow_missing=False):
|
||||
validate_args_and_kwargs(args, kwargs, arg_names, allow_missing=allow_missing)
|
||||
|
||||
yield from kwargs.items()
|
||||
for value, name in zip(args, arg_names):
|
||||
yield name, value
|
||||
|
||||
|
||||
def _eq(left, right):
|
||||
"""
|
||||
Equality comparison that allows for equality between tuple and list types
|
||||
with equivalent elements.
|
||||
"""
|
||||
if isinstance(left, (tuple, list)) and isinstance(right, (tuple, list)):
|
||||
return len(left) == len(right) and all(_eq(*pair) for pair in zip(left, right))
|
||||
else:
|
||||
return left == right
|
||||
|
||||
|
||||
class ChangesetState(enum.Enum):
|
||||
INITIALIZED = 'INITIALIZED'
|
||||
OPEN = 'OPEN'
|
||||
CLOSED = 'CLOSED'
|
||||
|
||||
|
||||
class ChangesetField:
|
||||
field = None
|
||||
|
||||
def __init__(self, field):
|
||||
self.field = field
|
||||
|
||||
def __get__(self, instance, type=None):
|
||||
if instance is None:
|
||||
return self
|
||||
elif instance.__state__ is not ChangesetState.OPEN:
|
||||
raise AttributeError("Changeset is not active. Attribute access not allowed")
|
||||
else:
|
||||
try:
|
||||
return instance.__diff__[self.field]
|
||||
except KeyError:
|
||||
return getattr(instance.__original__, self.field)
|
||||
|
||||
def __set__(self, instance, value):
|
||||
if instance.__state__ is not ChangesetState.OPEN:
|
||||
raise AttributeError("Changeset is not active. Attribute access not allowed")
|
||||
instance.__diff__[self.field] = value
|
||||
|
||||
|
||||
class BaseChangeset:
|
||||
# reference to the original Serializable instance.
|
||||
__original__ = None
|
||||
# the state of this fieldset. Initialized -> Open -> Closed
|
||||
__state__ = None
|
||||
# the field changes that have been made in this change
|
||||
__diff__ = None
|
||||
|
||||
def __init__(self, obj, changes=None):
|
||||
self.__original__ = obj
|
||||
self.__state__ = ChangesetState.INITIALIZED
|
||||
self.__diff__ = changes or {}
|
||||
|
||||
def commit(self):
|
||||
obj = self.build_rlp()
|
||||
self.close()
|
||||
return obj
|
||||
|
||||
def build_rlp(self):
|
||||
if self.__state__ == ChangesetState.OPEN:
|
||||
field_kwargs = {
|
||||
name: self.__diff__.get(name, self.__original__[name])
|
||||
for name
|
||||
in self.__original__._meta.field_names
|
||||
}
|
||||
return type(self.__original__)(**field_kwargs)
|
||||
else:
|
||||
raise ValueError("Cannot open Changeset which is not in the OPEN state")
|
||||
|
||||
def open(self):
|
||||
if self.__state__ == ChangesetState.INITIALIZED:
|
||||
self.__state__ = ChangesetState.OPEN
|
||||
else:
|
||||
raise ValueError("Cannot open Changeset which is not in the INITIALIZED state")
|
||||
|
||||
def close(self):
|
||||
if self.__state__ == ChangesetState.OPEN:
|
||||
self.__state__ = ChangesetState.CLOSED
|
||||
else:
|
||||
raise ValueError("Cannot open Changeset which is not in the INITIALIZED state")
|
||||
|
||||
def __enter__(self):
|
||||
if self.__state__ == ChangesetState.INITIALIZED:
|
||||
self.open()
|
||||
return self
|
||||
else:
|
||||
raise ValueError("Cannot open Changeset which is not in the INITIALIZED state")
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
if self.__state__ == ChangesetState.OPEN:
|
||||
self.close()
|
||||
|
||||
|
||||
def Changeset(obj, changes):
|
||||
namespace = {
|
||||
name: ChangesetField(name)
|
||||
for name
|
||||
in obj._meta.field_names
|
||||
}
|
||||
cls = type(
|
||||
"{0}Changeset".format(obj.__class__.__name__),
|
||||
(BaseChangeset,),
|
||||
namespace,
|
||||
)
|
||||
return cls(obj, changes)
|
||||
|
||||
|
||||
class BaseSerializable(collections.Sequence):
|
||||
def __init__(self, *args, **kwargs):
|
||||
if kwargs:
|
||||
field_values = merge_kwargs_to_args(args, kwargs, self._meta.field_names)
|
||||
else:
|
||||
field_values = args
|
||||
|
||||
if len(field_values) != len(self._meta.field_names):
|
||||
raise TypeError(
|
||||
'Argument count mismatch. expected {0} - got {1} - missing {2}'.format(
|
||||
len(self._meta.field_names),
|
||||
len(field_values),
|
||||
','.join(self._meta.field_names[len(field_values):]),
|
||||
)
|
||||
)
|
||||
|
||||
for value, attr in zip(field_values, self._meta.field_attrs):
|
||||
setattr(self, attr, make_immutable(value))
|
||||
|
||||
_cached_rlp = None
|
||||
|
||||
def as_dict(self):
|
||||
return dict(
|
||||
(field, value)
|
||||
for field, value
|
||||
in zip(self._meta.field_names, self)
|
||||
)
|
||||
|
||||
def __iter__(self):
|
||||
for attr in self._meta.field_attrs:
|
||||
yield getattr(self, attr)
|
||||
|
||||
def __getitem__(self, idx):
|
||||
if isinstance(idx, int):
|
||||
attr = self._meta.field_attrs[idx]
|
||||
return getattr(self, attr)
|
||||
elif isinstance(idx, slice):
|
||||
field_slice = self._meta.field_attrs[idx]
|
||||
return tuple(getattr(self, field) for field in field_slice)
|
||||
elif isinstance(idx, str):
|
||||
return getattr(self, idx)
|
||||
else:
|
||||
raise IndexError("Unsupported type for __getitem__: {0}".format(type(idx)))
|
||||
|
||||
def __len__(self):
|
||||
return len(self._meta.fields)
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, Serializable) and hash(self) == hash(other)
|
||||
|
||||
def __getstate__(self):
|
||||
state = self.__dict__.copy()
|
||||
# The hash() builtin is not stable across processes
|
||||
# (https://docs.python.org/3/reference/datamodel.html#object.__hash__), so we do this here
|
||||
# to ensure pickled instances don't carry the cached hash() as that may cause issues like
|
||||
# https://github.com/ethereum/py-evm/issues/1318
|
||||
state['_hash_cache'] = None
|
||||
return state
|
||||
|
||||
_hash_cache = None
|
||||
|
||||
def __hash__(self):
|
||||
if self._hash_cache is None:
|
||||
self._hash_cache = hash(tuple(self))
|
||||
|
||||
return self._hash_cache
|
||||
|
||||
@classmethod
|
||||
def serialize(cls, obj):
|
||||
try:
|
||||
return cls._meta.sedes.serialize(obj)
|
||||
except ListSerializationError as e:
|
||||
raise ObjectSerializationError(obj=obj, sedes=cls, list_exception=e)
|
||||
|
||||
@classmethod
|
||||
def deserialize(cls, serial, **extra_kwargs):
|
||||
try:
|
||||
values = cls._meta.sedes.deserialize(serial)
|
||||
except ListDeserializationError as e:
|
||||
raise ObjectDeserializationError(serial=serial, sedes=cls, list_exception=e)
|
||||
|
||||
args_as_kwargs = merge_args_to_kwargs(values, {}, cls._meta.field_names)
|
||||
return cls(**args_as_kwargs, **extra_kwargs)
|
||||
|
||||
def copy(self, *args, **kwargs):
|
||||
missing_overrides = set(
|
||||
self._meta.field_names
|
||||
).difference(
|
||||
kwargs.keys()
|
||||
).difference(
|
||||
self._meta.field_names[:len(args)]
|
||||
)
|
||||
unchanged_kwargs = {
|
||||
key: copy.deepcopy(value)
|
||||
for key, value
|
||||
in self.as_dict().items()
|
||||
if key in missing_overrides
|
||||
}
|
||||
combined_kwargs = dict(**unchanged_kwargs, **kwargs)
|
||||
all_kwargs = merge_args_to_kwargs(args, combined_kwargs, self._meta.field_names)
|
||||
return type(self)(**all_kwargs)
|
||||
|
||||
def __copy__(self):
|
||||
return self.copy()
|
||||
|
||||
def __deepcopy__(self, *args):
|
||||
return self.copy()
|
||||
|
||||
_in_mutable_context = False
|
||||
|
||||
def build_changeset(self, *args, **kwargs):
|
||||
args_as_kwargs = merge_args_to_kwargs(
|
||||
args,
|
||||
kwargs,
|
||||
self._meta.field_names,
|
||||
allow_missing=True,
|
||||
)
|
||||
return Changeset(self, changes=args_as_kwargs)
|
||||
|
||||
|
||||
def make_immutable(value):
|
||||
if isinstance(value, list):
|
||||
return tuple(make_immutable(item) for item in value)
|
||||
else:
|
||||
return value
|
||||
|
||||
|
||||
@to_tuple
|
||||
def _mk_field_attrs(field_names, extra_namespace):
|
||||
namespace = set(field_names).union(extra_namespace)
|
||||
for field in field_names:
|
||||
while True:
|
||||
field = '_' + field
|
||||
if field not in namespace:
|
||||
namespace.add(field)
|
||||
yield field
|
||||
break
|
||||
|
||||
|
||||
def _mk_field_property(field, attr):
|
||||
def field_fn_getter(self):
|
||||
return getattr(self, attr)
|
||||
|
||||
def field_fn_setter(self, value):
|
||||
if not self._in_mutable_context:
|
||||
raise AttributeError("can't set attribute")
|
||||
setattr(self, attr, value)
|
||||
|
||||
return property(field_fn_getter, field_fn_setter)
|
||||
|
||||
|
||||
IDENTIFIER_REGEX = re.compile(r"^[^\d\W]\w*\Z", re.UNICODE)
|
||||
|
||||
|
||||
def _is_valid_identifier(value):
|
||||
# Source: https://stackoverflow.com/questions/5474008/regular-expression-to-confirm-whether-a-string-is-a-valid-identifier-in-python # noqa: E501
|
||||
if not isinstance(value, str):
|
||||
return False
|
||||
return bool(IDENTIFIER_REGEX.match(value))
|
||||
|
||||
|
||||
@to_set
|
||||
def _get_class_namespace(cls):
|
||||
if hasattr(cls, '__dict__'):
|
||||
yield from cls.__dict__.keys()
|
||||
if hasattr(cls, '__slots__'):
|
||||
yield from cls.__slots__
|
||||
|
||||
|
||||
class SerializableBase(abc.ABCMeta):
|
||||
def __new__(cls, name, bases, attrs):
|
||||
super_new = super(SerializableBase, cls).__new__
|
||||
|
||||
serializable_bases = tuple(b for b in bases if isinstance(b, SerializableBase))
|
||||
has_multiple_serializable_parents = len(serializable_bases) > 1
|
||||
is_serializable_subclass = any(serializable_bases)
|
||||
declares_fields = 'fields' in attrs
|
||||
|
||||
if not is_serializable_subclass:
|
||||
# If this is the original creation of the `Serializable` class,
|
||||
# just create the class.
|
||||
return super_new(cls, name, bases, attrs)
|
||||
elif not declares_fields:
|
||||
if has_multiple_serializable_parents:
|
||||
raise TypeError(
|
||||
"Cannot create subclass from multiple parent `Serializable` "
|
||||
"classes without explicit `fields` declaration."
|
||||
)
|
||||
else:
|
||||
# This is just a vanilla subclass of a `Serializable` parent class.
|
||||
parent_serializable = serializable_bases[0]
|
||||
|
||||
if hasattr(parent_serializable, '_meta'):
|
||||
fields = parent_serializable._meta.fields
|
||||
else:
|
||||
# This is a subclass of `Serializable` which has no
|
||||
# `fields`, likely intended for further subclassing.
|
||||
fields = ()
|
||||
else:
|
||||
# ensure that the `fields` property is a tuple of tuples to ensure
|
||||
# immutability.
|
||||
fields = tuple(tuple(field) for field in attrs.pop('fields'))
|
||||
|
||||
# split the fields into names and sedes
|
||||
if fields:
|
||||
field_names, sedes = zip(*fields)
|
||||
else:
|
||||
field_names, sedes = (), ()
|
||||
|
||||
# check that field names are unique
|
||||
duplicate_field_names = _get_duplicates(field_names)
|
||||
if duplicate_field_names:
|
||||
raise TypeError(
|
||||
"The following fields are duplicated in the `fields` "
|
||||
"declaration: "
|
||||
"{0}".format(",".join(sorted(duplicate_field_names)))
|
||||
)
|
||||
|
||||
# check that field names are valid identifiers
|
||||
invalid_field_names = {
|
||||
field_name
|
||||
for field_name
|
||||
in field_names
|
||||
if not _is_valid_identifier(field_name)
|
||||
}
|
||||
if invalid_field_names:
|
||||
raise TypeError(
|
||||
"The following field names are not valid python identifiers: {0}".format(
|
||||
",".join("`{0}`".format(item) for item in sorted(invalid_field_names))
|
||||
)
|
||||
)
|
||||
|
||||
# extract all of the fields from parent `Serializable` classes.
|
||||
parent_field_names = {
|
||||
field_name
|
||||
for base in serializable_bases if hasattr(base, '_meta')
|
||||
for field_name in base._meta.field_names
|
||||
}
|
||||
|
||||
# check that all fields from parent serializable classes are
|
||||
# represented on this class.
|
||||
missing_fields = parent_field_names.difference(field_names)
|
||||
if missing_fields:
|
||||
raise TypeError(
|
||||
"Subclasses of `Serializable` **must** contain a full superset "
|
||||
"of the fields defined in their parent classes. The following "
|
||||
"fields are missing: "
|
||||
"{0}".format(",".join(sorted(missing_fields)))
|
||||
)
|
||||
|
||||
# the actual field values are stored in separate *private* attributes.
|
||||
# This computes attribute names that don't conflict with other
|
||||
# attributes already present on the class.
|
||||
reserved_namespace = set(attrs.keys()).union(
|
||||
attr
|
||||
for base in bases
|
||||
for parent_cls in base.__mro__
|
||||
for attr in _get_class_namespace(parent_cls)
|
||||
)
|
||||
field_attrs = _mk_field_attrs(field_names, reserved_namespace)
|
||||
|
||||
# construct the Meta object to store field information for the class
|
||||
meta_namespace = {
|
||||
'fields': fields,
|
||||
'field_attrs': field_attrs,
|
||||
'field_names': field_names,
|
||||
'sedes': List(sedes),
|
||||
}
|
||||
|
||||
meta_base = attrs.pop('_meta', MetaBase)
|
||||
meta = type(
|
||||
'Meta',
|
||||
(meta_base,),
|
||||
meta_namespace,
|
||||
)
|
||||
attrs['_meta'] = meta
|
||||
|
||||
# construct `property` attributes for read only access to the fields.
|
||||
field_props = tuple(
|
||||
(field, _mk_field_property(field, attr))
|
||||
for field, attr
|
||||
in zip(meta.field_names, meta.field_attrs)
|
||||
)
|
||||
|
||||
return super_new(
|
||||
cls,
|
||||
name,
|
||||
bases,
|
||||
dict(
|
||||
field_props +
|
||||
tuple(attrs.items())
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class Serializable(BaseSerializable, metaclass=SerializableBase):
|
||||
"""
|
||||
The base class for serializable objects.
|
||||
"""
|
||||
pass
|
||||
64
mmgen/altcoins/eth/rlp/sedes/text.py
Normal file
64
mmgen/altcoins/eth/rlp/sedes/text.py
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
from ..exceptions import SerializationError,DeserializationError
|
||||
from ..atomic import Atomic
|
||||
|
||||
|
||||
class Text:
|
||||
"""A sedes object for encoded text data of certain length.
|
||||
|
||||
:param min_length: the minimal length in encoded characters or `None` for no lower limit
|
||||
:param max_length: the maximal length in encoded characters or `None` for no upper limit
|
||||
:param allow_empty: if true, empty strings are considered valid even if
|
||||
a minimum length is required otherwise
|
||||
"""
|
||||
|
||||
def __init__(self, min_length=None, max_length=None, allow_empty=False, encoding='utf8'):
|
||||
self.min_length = min_length or 0
|
||||
if max_length is None:
|
||||
self.max_length = float('inf')
|
||||
else:
|
||||
self.max_length = max_length
|
||||
self.allow_empty = allow_empty
|
||||
self.encoding = encoding
|
||||
|
||||
@classmethod
|
||||
def fixed_length(cls, l, allow_empty=False):
|
||||
"""Create a sedes for text data with exactly `l` encoded characters."""
|
||||
return cls(l, l, allow_empty=allow_empty)
|
||||
|
||||
@classmethod
|
||||
def is_valid_type(cls, obj):
|
||||
return isinstance(obj, str)
|
||||
|
||||
def is_valid_length(self, l):
|
||||
return any((
|
||||
self.min_length <= l <= self.max_length,
|
||||
self.allow_empty and l == 0
|
||||
))
|
||||
|
||||
def serialize(self, obj):
|
||||
if not self.is_valid_type(obj):
|
||||
raise SerializationError('Object is not a serializable ({})'.format(type(obj)), obj)
|
||||
|
||||
if not self.is_valid_length(len(obj)):
|
||||
raise SerializationError('Object has invalid length', obj)
|
||||
|
||||
return obj.encode(self.encoding)
|
||||
|
||||
def deserialize(self, serial):
|
||||
if not isinstance(serial, Atomic):
|
||||
m = 'Objects of type {} cannot be deserialized'
|
||||
raise DeserializationError(m.format(type(serial).__name__), serial)
|
||||
|
||||
try:
|
||||
text_value = serial.decode(self.encoding)
|
||||
except UnicodeDecodeError as err:
|
||||
raise DeserializationError(str(err), serial)
|
||||
|
||||
if self.is_valid_length(len(text_value)):
|
||||
return text_value
|
||||
else:
|
||||
raise DeserializationError('{} has invalid length'.format(type(serial)), serial)
|
||||
|
||||
|
||||
text = Text()
|
||||
|
||||
|
|
@ -25,7 +25,7 @@ from mmgen.common import *
|
|||
from mmgen.obj import ETHAmt,TwMMGenID,TwComment,TwLabel
|
||||
from mmgen.tw import TrackingWallet,TwAddrList,TwUnspentOutputs
|
||||
from mmgen.addr import AddrData
|
||||
from mmgen.altcoins.eth.contract import Token
|
||||
from .contract import Token
|
||||
|
||||
class EthereumTrackingWallet(TrackingWallet):
|
||||
|
||||
|
|
|
|||
|
|
@ -92,9 +92,9 @@ class EthereumMMGenTX(MMGenTX):
|
|||
if self.check_sigs():
|
||||
|
||||
try: from ethereum.transactions import Transaction
|
||||
except: from mmgen.altcoins.eth.pyethereum.transactions import Transaction
|
||||
except: from .pyethereum.transactions import Transaction
|
||||
|
||||
import rlp
|
||||
from . import rlp
|
||||
etx = rlp.decode(bytes.fromhex(self.hex),Transaction)
|
||||
d = etx.to_dict() # ==> hex values have '0x' prefix, 0 is '0x'
|
||||
for k in ('sender','to','data'):
|
||||
|
|
@ -288,12 +288,12 @@ class EthereumMMGenTX(MMGenTX):
|
|||
'data': bytes.fromhex(d['data'])}
|
||||
|
||||
try: from ethereum.transactions import Transaction
|
||||
except: from mmgen.altcoins.eth.pyethereum.transactions import Transaction
|
||||
except: from .pyethereum.transactions import Transaction
|
||||
|
||||
etx = Transaction(**d_in).sign(wif,d['chainId'])
|
||||
assert etx.sender.hex() == d['from'],(
|
||||
'Sender address recovered from signature does not match true sender')
|
||||
import rlp
|
||||
from . import rlp
|
||||
self.hex = rlp.encode(etx).hex()
|
||||
self.coin_txid = CoinTxID(etx.hash.hex())
|
||||
if d['data']:
|
||||
|
|
@ -410,7 +410,7 @@ class EthereumTokenMMGenTX(EthereumMMGenTX):
|
|||
if self.outputs[0].is_chg:
|
||||
send_acct_tbal = '0'
|
||||
else:
|
||||
from mmgen.altcoins.eth.contract import Token
|
||||
from .contract import Token
|
||||
send_acct_tbal = Token(g.token).balance(self.inputs[0].addr) - self.outputs[0].amt
|
||||
return m.format(ETHAmt(change_amt).hl(),g.coin,ETHAmt(send_acct_tbal).hl(),g.dcoin)
|
||||
|
||||
|
|
@ -431,7 +431,7 @@ class EthereumTokenMMGenTX(EthereumMMGenTX):
|
|||
|
||||
def make_txobj(self):
|
||||
super(EthereumTokenMMGenTX,self).make_txobj()
|
||||
from mmgen.altcoins.eth.contract import Token
|
||||
from .contract import Token
|
||||
t = Token(g.token)
|
||||
o = t.txcreate( self.inputs[0].addr,
|
||||
self.outputs[0].addr,
|
||||
|
|
@ -444,7 +444,7 @@ class EthereumTokenMMGenTX(EthereumMMGenTX):
|
|||
def check_txfile_hex_data(self):
|
||||
d = super(EthereumTokenMMGenTX,self).check_txfile_hex_data()
|
||||
o = self.txobj
|
||||
from mmgen.altcoins.eth.contract import Token
|
||||
from .contract import Token
|
||||
if self.check_sigs(): # online, from rlp
|
||||
rpc_init()
|
||||
o['token_addr'] = TokenAddr(o['to'])
|
||||
|
|
@ -462,7 +462,7 @@ class EthereumTokenMMGenTX(EthereumMMGenTX):
|
|||
r=super(EthereumTokenMMGenTX,self).format_view_body(*args,**kwargs))
|
||||
|
||||
def do_sign(self,d,wif,tx_num_str):
|
||||
from mmgen.altcoins.eth.contract import Token
|
||||
from .contract import Token
|
||||
d = self.txobj
|
||||
t = Token(d['token_addr'],decimals=d['decimals'])
|
||||
tx_in = t.txcreate(d['from'],d['to'],d['amt'],self.start_gas,d['gasPrice'],nonce=d['nonce'])
|
||||
|
|
|
|||
|
|
@ -205,13 +205,13 @@ t_alts=(
|
|||
"$gentest_py --coin=ltc 2:ext $rounds"
|
||||
"$gentest_py --coin=ltc --type=compressed 2:ext $rounds"
|
||||
# "$gentest_py --coin=ltc --type=segwit 2:ext $rounds" # pycoin generates old-style LTC Segwit addrs
|
||||
"$gentest_py --coin=etc 2:ext $rounds"
|
||||
"$gentest_py --coin=eth 2:ext $rounds"
|
||||
# "$gentest_py --coin=etc 2:ext $rounds" # no pythereum
|
||||
# "$gentest_py --coin=eth 2:ext $rounds"
|
||||
"$gentest_py --coin=zec 2:ext $rounds"
|
||||
"$gentest_py --coin=zec --type=zcash_z 2:ext $rounds_mid"
|
||||
|
||||
"$gentest_py --all 2:pycoin $rounds"
|
||||
"$gentest_py --all 2:pyethereum $rounds"
|
||||
# "$gentest_py --all 2:pyethereum $rounds"
|
||||
"$gentest_py --all 2:keyconv $rounds_mid"
|
||||
"$gentest_py --all 2:zcash_mini $rounds_mid")
|
||||
if [ "$MINGW" ]; then
|
||||
|
|
|
|||
16
setup.py
16
setup.py
|
|
@ -152,10 +152,26 @@ setup(
|
|||
'mmgen.altcoins.eth.obj',
|
||||
'mmgen.altcoins.eth.tx',
|
||||
'mmgen.altcoins.eth.tw',
|
||||
|
||||
'mmgen/altcoins/eth/pyethereum/LICENSE',
|
||||
'mmgen.altcoins.eth.pyethereum.__init__',
|
||||
'mmgen.altcoins.eth.pyethereum.transactions',
|
||||
'mmgen.altcoins.eth.pyethereum.utils',
|
||||
|
||||
'mmgen/altcoins/eth/rlp/LICENSE',
|
||||
'mmgen/altcoins/eth/rlp/__init__',
|
||||
'mmgen/altcoins/eth/rlp/atomic',
|
||||
'mmgen/altcoins/eth/rlp/codec',
|
||||
'mmgen/altcoins/eth/rlp/exceptions',
|
||||
'mmgen/altcoins/eth/rlp/sedes/__init__',
|
||||
'mmgen/altcoins/eth/rlp/sedes/big_endian_int',
|
||||
'mmgen/altcoins/eth/rlp/sedes/binary',
|
||||
'mmgen/altcoins/eth/rlp/sedes/boolean',
|
||||
'mmgen/altcoins/eth/rlp/sedes/lists',
|
||||
'mmgen/altcoins/eth/rlp/sedes/raw',
|
||||
'mmgen/altcoins/eth/rlp/sedes/serializable',
|
||||
'mmgen/altcoins/eth/rlp/sedes/text',
|
||||
|
||||
'mmgen.main',
|
||||
'mmgen.main_addrgen',
|
||||
'mmgen.main_addrimport',
|
||||
|
|
|
|||
|
|
@ -373,7 +373,7 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared):
|
|||
args = ['98831F3A:E:1,123.456']
|
||||
return self.txcreate(args=args,menu=menu,acct='1',non_mmgen_inputs=1)
|
||||
|
||||
def txsign1(self): return self.txsign()
|
||||
def txsign1(self): return self.txsign(add_args=['--use-internal-keccak-module'])
|
||||
def txsign1_ni(self): return self.txsign(ni=True)
|
||||
def txsend1(self): return self.txsend()
|
||||
def bal1(self): return self.bal(n='1')
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue