# -*- coding: utf-8 -*- # Copyright (c) 2018-2023 tecnovert # Distributed under the MIT software license, see the accompanying # file LICENSE or http://www.opensource.org/licenses/mit-license.php. import json import time import decimal COIN = 100000000 decimal_ctx = decimal.Context() decimal_ctx.prec = 20 class TemporaryError(ValueError): pass class AutomationConstraint(ValueError): pass class InactiveCoin(Exception): def __init__(self, coinid): self.coinid = coinid def __str__(self): return str(self.coinid) class LockedCoinError(Exception): def __init__(self, coinid): self.coinid = coinid def __str__(self): return 'Coin must be unlocked: ' + str(self.coinid) def ensure(v, err_string): if not v: raise ValueError(err_string) def toBool(s) -> bool: if isinstance(s, bool): return s return s.lower() in ['1', 'true'] def jsonDecimal(obj): if isinstance(obj, decimal.Decimal): return str(obj) raise TypeError def dumpj(jin, indent=4): return json.dumps(jin, indent=indent, default=jsonDecimal) def dumpje(jin): return json.dumps(jin, default=jsonDecimal).replace('"', '\\"') def SerialiseNum(n: int) -> bytes: if n == 0: return bytes((0x00,)) if n > 0 and n <= 16: return bytes((0x50 + n,)) rv = bytearray() neg = n < 0 absvalue = -n if neg else n while (absvalue): rv.append(absvalue & 0xff) absvalue >>= 8 if rv[-1] & 0x80: rv.append(0x80 if neg else 0) elif neg: rv[-1] |= 0x80 return bytes((len(rv),)) + rv def DeserialiseNum(b: bytes, o: int = 0) -> int: if b[o] == 0: return 0 if b[o] > 0x50 and b[o] <= 0x50 + 16: return b[o] - 0x50 v = 0 nb = b[o] o += 1 for i in range(0, nb): v |= b[o + i] << (8 * i) # If the input vector's most significant byte is 0x80, remove it from the result's msb and return a negative. if b[o + nb - 1] & 0x80: return -(v & ~(0x80 << (8 * (nb - 1)))) return v def float_to_str(f: float) -> str: # stackoverflow.com/questions/38847690 d1 = decimal_ctx.create_decimal(repr(f)) return format(d1, 'f') def make_int(v, scale: int = 8, r: int = 0) -> int: # r = 0, no rounding (fail), r > 0 round off, r < 0 floor if isinstance(v, float): v = float_to_str(v) elif isinstance(v, int): return v * 10 ** scale sign = 1 if v[0] == '-': v = v[1:] sign = -1 ep = 10 ** scale have_dp = False rv = 0 for c in v: if c == '.': rv *= ep have_dp = True continue if not c.isdigit(): raise ValueError('Invalid char: ' + c) if have_dp: ep //= 10 if ep <= 0: if r == 0: raise ValueError('Mantissa too long') if r > 0: # Round off if int(c) > 4: rv += 1 break rv += ep * int(c) else: rv = rv * 10 + int(c) if not have_dp: rv *= ep return rv * sign def validate_amount(amount, scale: int = 8) -> bool: str_amount = float_to_str(amount) if isinstance(amount, float) else str(amount) has_decimal = False for c in str_amount: if c == '.' and not has_decimal: has_decimal = True continue if not c.isdigit(): raise ValueError('Invalid amount') ar = str_amount.split('.') if len(ar) > 1 and len(ar[1]) > scale: raise ValueError('Too many decimal places in amount {}'.format(str_amount)) return True def format_amount(i: int, display_scale: int, scale: int = None) -> str: if not isinstance(i, int): raise ValueError('Amount must be an integer.') # Raise error instead of converting as amounts should always be integers if scale is None: scale = display_scale ep = 10 ** scale n = abs(i) quotient = n // ep remainder = n % ep if display_scale != scale: remainder %= (10 ** display_scale) rv = '{}.{:0>{scale}}'.format(quotient, remainder, scale=display_scale) if i < 0: rv = '-' + rv return rv def format_timestamp(value: int, with_seconds: bool = False) -> str: str_format = '%Y-%m-%d %H:%M' if with_seconds: str_format += ':%S' str_format += ' %z' return time.strftime(str_format, time.localtime(value)) def b2i(b: bytes) -> int: # bytes32ToInt return int.from_bytes(b, byteorder='big') def i2b(i: int) -> bytes: # intToBytes32 return i.to_bytes(32, byteorder='big') def b2h(b: bytes) -> str: return b.hex() def h2b(h: str) -> bytes: if h.startswith('0x'): h = h[2:] return bytes.fromhex(h) def i2h(x: int) -> str: return b2h(i2b(x)) def zeroIfNone(value) -> int: if value is None: return 0 return value