Host-customized fork of https://github.com/tecnovert/basicswap/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
215 lines
4.9 KiB
215 lines
4.9 KiB
# -*- 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 up, 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 up |
|
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
|
|
|