Browse Source

move CoinAmt and related classes to amt.py

The MMGen Project 3 years ago
parent
commit
d669229da0

+ 2 - 1
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

+ 2 - 1
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):

+ 2 - 1
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):
 

+ 1 - 0
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:
 

+ 181 - 0
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 <mmgen@tuta.io>
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+"""
+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'

+ 0 - 154
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

+ 3 - 1
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'])

+ 2 - 1
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:

+ 1 - 0
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)],

+ 1 - 1
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 *

+ 2 - 1
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 *

+ 2 - 1
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' ),