From d669229da0d9051d910492a2f8af850fda1959e0 Mon Sep 17 00:00:00 2001 From: The MMGen Project Date: Sat, 15 Jan 2022 14:00:06 +0000 Subject: [PATCH] move CoinAmt and related classes to amt.py --- mmgen/altcoins/eth/contract.py | 3 +- mmgen/altcoins/eth/obj.py | 3 +- mmgen/altcoins/eth/tw.py | 3 +- mmgen/altcoins/eth/tx.py | 1 + mmgen/amt.py | 181 +++++++++++++++++++++++++++++++++ mmgen/obj.py | 154 ---------------------------- mmgen/protocol.py | 4 +- mmgen/txfile.py | 3 +- test/objtest.py | 1 + test/test_py_d/ts_ethdev.py | 2 +- test/test_py_d/ts_xmrwallet.py | 3 +- test/unit_tests_d/ut_obj.py | 3 +- 12 files changed, 199 insertions(+), 162 deletions(-) create mode 100755 mmgen/amt.py diff --git a/mmgen/altcoins/eth/contract.py b/mmgen/altcoins/eth/contract.py index 3d168847..0bc01d72 100755 --- a/mmgen/altcoins/eth/contract.py +++ b/mmgen/altcoins/eth/contract.py @@ -25,8 +25,9 @@ from . import rlp from mmgen.globalvars import g from mmgen.common import * -from mmgen.obj import MMGenObject,CoinAddr,TokenAddr,CoinTxID,ETHAmt,AsyncInit +from mmgen.obj import MMGenObject,CoinAddr,TokenAddr,CoinTxID,AsyncInit from mmgen.util import msg +from .obj import ETHAmt try: assert not g.use_internal_keccak_module diff --git a/mmgen/altcoins/eth/obj.py b/mmgen/altcoins/eth/obj.py index 742c6205..72b371bc 100755 --- a/mmgen/altcoins/eth/obj.py +++ b/mmgen/altcoins/eth/obj.py @@ -21,7 +21,8 @@ altcoins.eth.obj: Ethereum data type classes for the MMGen suite """ from decimal import Decimal -from mmgen.obj import CoinAmt,Int +from mmgen.obj import Int +from mmgen.amt import CoinAmt # Kwei (babbage) 3, Mwei (lovelace) 6, Gwei (shannon) 9, µETH (szabo) 12, mETH (finney) 15, ETH 18 class ETHAmt(CoinAmt): diff --git a/mmgen/altcoins/eth/tw.py b/mmgen/altcoins/eth/tw.py index 6b62de0c..1b2aa0ae 100755 --- a/mmgen/altcoins/eth/tw.py +++ b/mmgen/altcoins/eth/tw.py @@ -21,10 +21,11 @@ altcoins.eth.tw: Ethereum tracking wallet and related classes for the MMGen suit """ from mmgen.common import * -from mmgen.obj import ETHAmt,TwLabel,is_coin_addr,is_mmgen_id,ListItemAttr,ImmutableAttr +from mmgen.obj import TwLabel,is_coin_addr,is_mmgen_id,ListItemAttr,ImmutableAttr from mmgen.tw import TrackingWallet,TwAddrList,TwUnspentOutputs,TwGetBalance from mmgen.addr import AddrData,TwAddrData from .contract import Token,TokenResolve +from .obj import ETHAmt class EthereumTrackingWallet(TrackingWallet): diff --git a/mmgen/altcoins/eth/tx.py b/mmgen/altcoins/eth/tx.py index b7456c95..90171a8c 100755 --- a/mmgen/altcoins/eth/tx.py +++ b/mmgen/altcoins/eth/tx.py @@ -28,6 +28,7 @@ from mmgen.obj import * from mmgen.tx import MMGenTX from mmgen.tw import TrackingWallet from .contract import Token +from .obj import ETHAmt,ETHNonce class EthereumMMGenTX: diff --git a/mmgen/amt.py b/mmgen/amt.py new file mode 100755 index 00000000..3acfee77 --- /dev/null +++ b/mmgen/amt.py @@ -0,0 +1,181 @@ +#!/usr/bin/env python3 +# +# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution +# Copyright (C)2013-2022 The MMGen Project +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +""" +amt.py: MMGen CoinAmt and related classes +""" + +from decimal import Decimal +from .obj import Hilite,InitErrors + +class UnknownCoinAmt(Decimal): pass + +class DecimalNegateResult(Decimal): pass + +class CoinAmt(Decimal,Hilite,InitErrors): # abstract class + """ + Instantiating with 'from_decimal' rounds value down to 'max_prec' precision. + For addition and subtraction, operand types must match. + For multiplication and division, operand types may differ. + Negative amounts, floor division and modulus operation are unimplemented. + """ + color = 'yellow' + forbidden_types = (float,int) + + max_prec = 0 # number of decimal places for this coin + max_amt = None # coin supply if known, otherwise None + units = () # defined unit names, e.g. ('satoshi',...) + amt_fs = '0.0' # format string for the fmt() method + + def __new__(cls,num,from_unit=None,from_decimal=False): + if type(num) == cls: + return num + try: + if from_unit: + assert from_unit in cls.units, f'{from_unit!r}: unrecognized denomination for {cls.__name__}' + assert type(num) == int,'value is not an integer' + me = Decimal.__new__(cls,num * getattr(cls,from_unit)) + elif from_decimal: + assert type(num) == Decimal, f'number must be of type Decimal, not {type(num).__name__})' + me = Decimal.__new__(cls,num.quantize(Decimal('10') ** -cls.max_prec)) + else: + for t in cls.forbidden_types: + assert type(num) is not t, f'number is of forbidden type {t.__name__}' + me = Decimal.__new__(cls,str(num)) + assert me.normalize().as_tuple()[-1] >= -cls.max_prec,'too many decimal places in coin amount' + if cls.max_amt: + assert me <= cls.max_amt, f'{me}: coin amount too large (>{cls.max_amt})' + assert me >= 0,'coin amount cannot be negative' + return me + except Exception as e: + return cls.init_fail(e,num) + + def to_unit(self,unit,show_decimal=False): + ret = Decimal(self) // getattr(self,unit) + if show_decimal and ret < 1: + return f'{ret:.8f}'.rstrip('0') + return int(ret) + + @classmethod + def fmtc(cls): + cls.method_not_implemented() + + def fmt(self,fs=None,color=False,suf='',prec=1000): + if fs == None: + fs = self.amt_fs + s = self.__str__() + if '.' in fs: + p1,p2 = list(map(int,fs.split('.',1))) + ss = s.split('.',1) + if len(ss) == 2: + a,b = ss + ret = a.rjust(p1) + '.' + ((b+suf).ljust(p2+len(suf)))[:prec] + else: + ret = s.rjust(p1) + suf + (' ' * (p2+1))[:prec+1-len(suf)] + else: + ret = s.ljust(int(fs)) + return self.colorize(ret,color=color) + + def hl(self,color=True): + return self.colorize(self.__str__(),color=color) + + def hl2(self,color=True,encl=''): # display with coin symbol + return ( + encl[:-1] + + self.colorize(self.__str__(),color=color) + + ' ' + type(self).__name__[:-3] + + encl[1:] + ) + + def __str__(self): # format simply, with no exponential notation + return str(int(self)) if int(self) == self else self.normalize().__format__('f') + + def __repr__(self): + return "{}('{}')".format(type(self).__name__,self.__str__()) + + def __add__(self,other,*args,**kwargs): + """ + we must allow other to be int(0) to use the sum() builtin + """ + if type(other) not in ( type(self), DecimalNegateResult ) and other != 0: + raise ValueError( + f'operand {other} of incorrect type ({type(other).__name__} != {type(self).__name__})') + return type(self)(Decimal.__add__(self,other,*args,**kwargs)) + + __radd__ = __add__ + + def __sub__(self,other,*args,**kwargs): + if type(other) is not type(self): + raise ValueError( + f'operand {other} of incorrect type ({type(other).__name__} != {type(self).__name__})') + return type(self)(Decimal.__sub__(self,other,*args,**kwargs)) + + def copy_negate(self,*args,**kwargs): + """ + We implement this so that __add__() can check type, because: + class Decimal: + def __sub__(self, other, ...): + ... + return self.__add__(other.copy_negate(), ...) + """ + return DecimalNegateResult(Decimal.copy_negate(self,*args,**kwargs)) + + def __mul__(self,other,*args,**kwargs): + return type(self)('{:0.{p}f}'.format( + Decimal.__mul__(self,Decimal(other),*args,**kwargs), + p = self.max_prec + )) + + __rmul__ = __mul__ + + def __truediv__(self,other,*args,**kwargs): + return type(self)('{:0.{p}f}'.format( + Decimal.__truediv__(self,Decimal(other),*args,**kwargs), + p = self.max_prec + )) + + def __neg__(self,*args,**kwargs): + self.method_not_implemented() + + def __floordiv__(self,*args,**kwargs): + self.method_not_implemented() + + def __mod__(self,*args,**kwargs): + self.method_not_implemented() + +class BTCAmt(CoinAmt): + max_prec = 8 + max_amt = 21000000 + satoshi = Decimal('0.00000001') + units = ('satoshi',) + amt_fs = '4.8' + +class BCHAmt(BTCAmt): + pass + +class B2XAmt(BTCAmt): + pass + +class LTCAmt(BTCAmt): + max_amt = 84000000 + +class XMRAmt(CoinAmt): + max_prec = 12 + atomic = Decimal('0.000000000001') + units = ('atomic',) + amt_fs = '4.12' diff --git a/mmgen/obj.py b/mmgen/obj.py index a10ef9b8..cba9dd92 100755 --- a/mmgen/obj.py +++ b/mmgen/obj.py @@ -440,160 +440,6 @@ class MMGenRange(tuple,InitErrors,MMGenObject): def items(self): return list(self.iterate()) -class UnknownCoinAmt(Decimal): pass - -class DecimalNegateResult(Decimal): pass - -class CoinAmt(Decimal,Hilite,InitErrors): # abstract class - """ - Instantiating with 'from_decimal' rounds value down to 'max_prec' precision. - For addition and subtraction, operand types must match. - For multiplication and division, operand types may differ. - Negative amounts, floor division and modulus operation are unimplemented. - """ - color = 'yellow' - forbidden_types = (float,int) - - max_prec = 0 # number of decimal places for this coin - max_amt = None # coin supply if known, otherwise None - units = () # defined unit names, e.g. ('satoshi',...) - amt_fs = '0.0' # format string for the fmt() method - - def __new__(cls,num,from_unit=None,from_decimal=False): - if type(num) == cls: - return num - try: - if from_unit: - assert from_unit in cls.units, f'{from_unit!r}: unrecognized denomination for {cls.__name__}' - assert type(num) == int,'value is not an integer' - me = Decimal.__new__(cls,num * getattr(cls,from_unit)) - elif from_decimal: - assert type(num) == Decimal, f'number must be of type Decimal, not {type(num).__name__})' - me = Decimal.__new__(cls,num.quantize(Decimal('10') ** -cls.max_prec)) - else: - for t in cls.forbidden_types: - assert type(num) is not t, f'number is of forbidden type {t.__name__}' - me = Decimal.__new__(cls,str(num)) - assert me.normalize().as_tuple()[-1] >= -cls.max_prec,'too many decimal places in coin amount' - if cls.max_amt: - assert me <= cls.max_amt, f'{me}: coin amount too large (>{cls.max_amt})' - assert me >= 0,'coin amount cannot be negative' - return me - except Exception as e: - return cls.init_fail(e,num) - - def to_unit(self,unit,show_decimal=False): - ret = Decimal(self) // getattr(self,unit) - if show_decimal and ret < 1: - return f'{ret:.8f}'.rstrip('0') - return int(ret) - - @classmethod - def fmtc(cls): - cls.method_not_implemented() - - def fmt(self,fs=None,color=False,suf='',prec=1000): - if fs == None: - fs = self.amt_fs - s = self.__str__() - if '.' in fs: - p1,p2 = list(map(int,fs.split('.',1))) - ss = s.split('.',1) - if len(ss) == 2: - a,b = ss - ret = a.rjust(p1) + '.' + ((b+suf).ljust(p2+len(suf)))[:prec] - else: - ret = s.rjust(p1) + suf + (' ' * (p2+1))[:prec+1-len(suf)] - else: - ret = s.ljust(int(fs)) - return self.colorize(ret,color=color) - - def hl(self,color=True): - return self.colorize(self.__str__(),color=color) - - def hl2(self,color=True,encl=''): # display with coin symbol - return ( - encl[:-1] - + self.colorize(self.__str__(),color=color) - + ' ' + type(self).__name__[:-3] - + encl[1:] - ) - - def __str__(self): # format simply, with no exponential notation - return str(int(self)) if int(self) == self else self.normalize().__format__('f') - - def __repr__(self): - return "{}('{}')".format(type(self).__name__,self.__str__()) - - def __add__(self,other,*args,**kwargs): - """ - we must allow other to be int(0) to use the sum() builtin - """ - if type(other) not in ( type(self), DecimalNegateResult ) and other != 0: - raise ValueError( - f'operand {other} of incorrect type ({type(other).__name__} != {type(self).__name__})') - return type(self)(Decimal.__add__(self,other,*args,**kwargs)) - - __radd__ = __add__ - - def __sub__(self,other,*args,**kwargs): - if type(other) is not type(self): - raise ValueError( - f'operand {other} of incorrect type ({type(other).__name__} != {type(self).__name__})') - return type(self)(Decimal.__sub__(self,other,*args,**kwargs)) - - def copy_negate(self,*args,**kwargs): - """ - We implement this so that __add__() can check type, because: - class Decimal: - def __sub__(self, other, ...): - ... - return self.__add__(other.copy_negate(), ...) - """ - return DecimalNegateResult(Decimal.copy_negate(self,*args,**kwargs)) - - def __mul__(self,other,*args,**kwargs): - return type(self)('{:0.{p}f}'.format( - Decimal.__mul__(self,Decimal(other),*args,**kwargs), - p = self.max_prec - )) - - __rmul__ = __mul__ - - def __truediv__(self,other,*args,**kwargs): - return type(self)('{:0.{p}f}'.format( - Decimal.__truediv__(self,Decimal(other),*args,**kwargs), - p = self.max_prec - )) - - def __neg__(self,*args,**kwargs): - self.method_not_implemented() - - def __floordiv__(self,*args,**kwargs): - self.method_not_implemented() - - def __mod__(self,*args,**kwargs): - self.method_not_implemented() - -class BTCAmt(CoinAmt): - max_prec = 8 - max_amt = 21000000 - satoshi = Decimal('0.00000001') - units = ('satoshi',) - amt_fs = '4.8' - -class BCHAmt(BTCAmt): pass -class B2XAmt(BTCAmt): pass -class LTCAmt(BTCAmt): max_amt = 84000000 - -class XMRAmt(CoinAmt): - max_prec = 12 - atomic = Decimal('0.000000000001') - units = ('atomic',) - amt_fs = '4.12' - -from .altcoins.eth.obj import ETHAmt,ETHNonce - class CoinAddr(str,Hilite,InitErrors,MMGenObject): color = 'cyan' hex_width = 40 diff --git a/mmgen/protocol.py b/mmgen/protocol.py index 842a61d0..e30f45f4 100755 --- a/mmgen/protocol.py +++ b/mmgen/protocol.py @@ -25,8 +25,10 @@ from collections import namedtuple from .util import msg,ymsg,Msg,ydie from .devtools import * -from .obj import BTCAmt,LTCAmt,BCHAmt,B2XAmt,XMRAmt,ETHAmt,CoinAddr,MMGenAddrType,PrivKey +from .obj import CoinAddr,MMGenAddrType,PrivKey from .globalvars import g +from .amt import BTCAmt,LTCAmt,BCHAmt,B2XAmt,XMRAmt +from .altcoins.eth.obj import ETHAmt import mmgen.bech32 as bech32 parsed_wif = namedtuple('parsed_wif',['sec','pubkey_type','compressed']) diff --git a/mmgen/txfile.py b/mmgen/txfile.py index 6e8aacf8..e031c3cb 100755 --- a/mmgen/txfile.py +++ b/mmgen/txfile.py @@ -21,8 +21,9 @@ txfile.py: Transaction file operations for the MMGen suite """ from .common import * -from .obj import HexStr,MMGenTxID,UnknownCoinAmt,CoinTxID,MMGenTxLabel +from .obj import HexStr,MMGenTxID,CoinTxID,MMGenTxLabel from .tx import MMGenTxOutput,MMGenTxOutputList,MMGenTxInput,MMGenTxInputList +from .amt import UnknownCoinAmt from .exception import MaxFileSizeExceeded class MMGenTxFile: diff --git a/test/objtest.py b/test/objtest.py index d0a968e7..46c4edac 100755 --- a/test/objtest.py +++ b/test/objtest.py @@ -33,6 +33,7 @@ from mmgen.common import * from mmgen.obj import * from mmgen.altcoins.eth.obj import * from mmgen.seedsplit import * +from mmgen.amt import * opts_data = { 'sets': [('super_silent', True, 'silent', True)], diff --git a/test/test_py_d/ts_ethdev.py b/test/test_py_d/ts_ethdev.py index 76675166..90fe3208 100755 --- a/test/test_py_d/ts_ethdev.py +++ b/test/test_py_d/ts_ethdev.py @@ -29,7 +29,7 @@ from mmgen.globalvars import g from mmgen.opts import opt from mmgen.util import die from mmgen.exception import * -from mmgen.obj import ETHAmt +from mmgen.altcoins.eth.obj import ETHAmt from mmgen.protocol import CoinProtocol from ..include.common import * from .common import * diff --git a/test/test_py_d/ts_xmrwallet.py b/test/test_py_d/ts_xmrwallet.py index fb0a5bf3..7e79ac9d 100755 --- a/test/test_py_d/ts_xmrwallet.py +++ b/test/test_py_d/ts_xmrwallet.py @@ -25,7 +25,8 @@ from subprocess import run,PIPE from mmgen.globalvars import g from mmgen.opts import opt -from mmgen.obj import MMGenRange,XMRAmt +from mmgen.obj import MMGenRange +from mmgen.amt import XMRAmt from mmgen.addr import KeyAddrList,AddrIdxList from ..include.common import * from .common import * diff --git a/test/unit_tests_d/ut_obj.py b/test/unit_tests_d/ut_obj.py index 5f0673e9..dea4eb02 100755 --- a/test/unit_tests_d/ut_obj.py +++ b/test/unit_tests_d/ut_obj.py @@ -9,7 +9,8 @@ class unit_tests: def coinamt(self,name,ut): - from mmgen.obj import BTCAmt,LTCAmt,XMRAmt,ETHAmt + from mmgen.amt import BTCAmt,LTCAmt,XMRAmt + from mmgen.altcoins.eth.obj import ETHAmt for cls,aa,bb in ( ( BTCAmt, '1.2345', '11234567.897' ),