| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202 |
- #!/usr/bin/env python3
- #
- # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
- # Copyright (C)2013-2023 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: MMGen CoinAmt and related classes
- """
- from decimal import Decimal
- from .objmethods import Hilite,InitErrors
- 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.
- """
- coin = 'Coin'
- 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',...)
- 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,
- color = False,
- iwidth = 1, # width of the integer part
- prec = None ):
- s = self.__str__()
- prec = prec or self.max_prec
- if '.' in s:
- a,b = s.split('.',1)
- return self.colorize(
- a.rjust(iwidth) + '.' + b.ljust(prec)[:prec], # truncation, not rounding!
- color = color )
- else:
- return self.colorize(
- s.rjust(iwidth).ljust(iwidth+prec+1),
- color = color )
- def hl(self,color=True):
- return self.colorize(self.__str__(),color=color)
- # fancy highlighting with coin unit, enclosure, formatting
- def hl2(self,color=True,unit=False,fs='{}',encl=''):
- res = fs.format(self)
- return (
- encl[:-1]
- + self.colorize(
- (res.rstrip('0').rstrip('.') if '.' in res else res) +
- (' ' + self.coin if unit else ''),
- color = color )
- + 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):
- coin = 'BTC'
- max_prec = 8
- max_amt = 21000000
- satoshi = Decimal('0.00000001')
- units = ('satoshi',)
- class BCHAmt(BTCAmt):
- coin = 'BCH'
- class LTCAmt(BTCAmt):
- coin = 'LTC'
- max_amt = 84000000
- class XMRAmt(CoinAmt):
- coin = 'XMR'
- max_prec = 12
- atomic = Decimal('0.000000000001')
- units = ('atomic',)
- # Kwei (babbage) 3, Mwei (lovelace) 6, Gwei (shannon) 9, µETH (szabo) 12, mETH (finney) 15, ETH 18
- class ETHAmt(CoinAmt):
- coin = 'ETH'
- max_prec = 18
- wei = Decimal('0.000000000000000001')
- Kwei = Decimal('0.000000000000001')
- Mwei = Decimal('0.000000000001')
- Gwei = Decimal('0.000000001')
- szabo = Decimal('0.000001')
- finney = Decimal('0.001')
- units = ('wei','Kwei','Mwei','Gwei','szabo','finney')
- def toWei(self):
- return int(Decimal(self) // self.wei)
- class ETCAmt(ETHAmt):
- coin = 'ETC'
|