Skip to content

Commit

Permalink
Import all instances of decimal.Decimal as PyDecimal in codebase
Browse files Browse the repository at this point in the history
This is a paranoia fix in reference to bug Electron-Cash#1157. Qt 5.12 also exports
the 'Decimal' symbol if you import *, and since Qt's imports sometimes
appears *after* the python imports, it may win.

We are renaming Python decimal.Decimal to PyDecimal in the codebase to
allow code to also access the Qt Decimal.

Also note that external plugins may be affected.  It is hoped that by
explicitly renaming the Python Decimal -> PyDecmal in the import, the
likelihood of a plugin being hit by this is minimal.

We needed to also rename in the lib/ directory (which doesn't normally
see the Qt namespace) because some Qt-based plugins import * from lib/
as well as from Qt.
  • Loading branch information
cculianu committed Feb 20, 2019
1 parent 9c13f65 commit 8ab34d9
Show file tree
Hide file tree
Showing 8 changed files with 60 additions and 60 deletions.
12 changes: 6 additions & 6 deletions gui/stdio.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from decimal import Decimal
from decimal import Decimal as PyDecimal
_ = lambda x:x
#from i18n import _
from electroncash import WalletStorage, Wallet
Expand Down Expand Up @@ -113,11 +113,11 @@ def get_balance(self):
msg = _( "Synchronizing..." )
else:
c, u, x = self.wallet.get_balance()
msg = _("Balance")+": %f "%(Decimal(c) / COIN)
msg = _("Balance")+": %f "%(PyDecimal(c) / COIN)
if u:
msg += " [%f unconfirmed]"%(Decimal(u) / COIN)
msg += " [%f unconfirmed]"%(PyDecimal(u) / COIN)
if x:
msg += " [%f unmatured]"%(Decimal(x) / COIN)
msg += " [%f unmatured]"%(PyDecimal(x) / COIN)
else:
msg = _( "Not connected" )

Expand Down Expand Up @@ -167,12 +167,12 @@ def do_send(self):
print(_('Invalid Bitcoin address'))
return
try:
amount = int(Decimal(self.str_amount) * COIN)
amount = int(PyDecimal(self.str_amount) * COIN)
except Exception:
print(_('Invalid Amount'))
return
try:
fee = int(Decimal(self.str_fee) * COIN)
fee = int(PyDecimal(self.str_fee) * COIN)
except Exception:
print(_('Invalid Fee'))
return
Expand Down
16 changes: 8 additions & 8 deletions gui/text.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import tty, sys
import curses, datetime, locale
from decimal import Decimal
from decimal import Decimal as PyDecimal
import getpass

import electrum
Expand Down Expand Up @@ -134,11 +134,11 @@ def print_balance(self):
msg = _("Synchronizing...")
else:
c, u, x = self.wallet.get_balance()
msg = _("Balance")+": %f "%(Decimal(c) / COIN)
msg = _("Balance")+": %f "%(PyDecimal(c) / COIN)
if u:
msg += " [%f unconfirmed]"%(Decimal(u) / COIN)
msg += " [%f unconfirmed]"%(PyDecimal(u) / COIN)
if x:
msg += " [%f unmatured]"%(Decimal(x) / COIN)
msg += " [%f unmatured]"%(PyDecimal(x) / COIN)
else:
msg = _("Not connected")

Expand Down Expand Up @@ -324,12 +324,12 @@ def do_send(self):
self.show_message(_('Invalid Bitcoin address'))
return
try:
amount = int(Decimal(self.str_amount) * COIN)
amount = int(PyDecimal(self.str_amount) * COIN)
except Exception:
self.show_message(_('Invalid Amount'))
return
try:
fee = int(Decimal(self.str_fee) * COIN)
fee = int(PyDecimal(self.str_fee) * COIN)
except Exception:
self.show_message(_('Invalid Fee'))
return
Expand Down Expand Up @@ -397,13 +397,13 @@ def network_dialog(self):
self.network.set_parameters(host, port, protocol, proxy, auto_connect)

def settings_dialog(self):
fee = str(Decimal(self.config.fee_per_kb()) / COIN)
fee = str(PyDecimal(self.config.fee_per_kb()) / COIN)
out = self.run_dialog('Settings', [
{'label':'Default fee', 'type':'satoshis', 'value': fee }
], buttons = 1)
if out:
if out.get('Default fee'):
fee = int(Decimal(out['Default fee']) * COIN)
fee = int(PyDecimal(out['Default fee']) * COIN)
self.config.set_key('fee_per_kb', fee, True)


Expand Down
22 changes: 11 additions & 11 deletions lib/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
import ast
import base64
from functools import wraps
from decimal import Decimal
from decimal import Decimal as PyDecimal # Qt 5.12 also exports Decimal

from .import util
from .util import bfh, bh2u, format_satoshis, json_decode, print_error
Expand All @@ -47,7 +47,7 @@

def satoshis(amount):
# satoshi conversion must not be performed by the parser
return int(COIN*Decimal(amount)) if amount not in ['!', None] else amount
return int(COIN*PyDecimal(amount)) if amount not in ['!', None] else amount


class Command:
Expand Down Expand Up @@ -233,7 +233,7 @@ def listunspent(self):
l = self.wallet.get_utxos(exclude_frozen=False)
for i in l:
v = i["value"]
i["value"] = str(Decimal(v)/COIN) if v is not None else None
i["value"] = str(PyDecimal(v)/COIN) if v is not None else None
i["address"] = i["address"].to_ui_string()
return l

Expand Down Expand Up @@ -359,11 +359,11 @@ def getpubkeys(self, address):
def getbalance(self):
"""Return the balance of your wallet. """
c, u, x = self.wallet.get_balance()
out = {"confirmed": str(Decimal(c)/COIN)}
out = {"confirmed": str(PyDecimal(c)/COIN)}
if u:
out["unconfirmed"] = str(Decimal(u)/COIN)
out["unconfirmed"] = str(PyDecimal(u)/COIN)
if x:
out["unmatured"] = str(Decimal(x)/COIN)
out["unmatured"] = str(PyDecimal(x)/COIN)
return out

@command('n')
Expand All @@ -373,8 +373,8 @@ def getaddressbalance(self, address):
"""
sh = Address.from_string(address).to_scripthash_hex()
out = self.network.synchronous_get(('blockchain.scripthash.get_balance', [sh]))
out["confirmed"] = str(Decimal(out["confirmed"])/COIN)
out["unconfirmed"] = str(Decimal(out["unconfirmed"])/COIN)
out["confirmed"] = str(PyDecimal(out["confirmed"])/COIN)
out["unconfirmed"] = str(PyDecimal(out["unconfirmed"])/COIN)
return out

@command('n')
Expand Down Expand Up @@ -802,7 +802,7 @@ def help(self):

# don't use floats because of rounding errors
from .transaction import tx_from_str
json_loads = lambda x: json.loads(x, parse_float=lambda x: str(Decimal(x)))
json_loads = lambda x: json.loads(x, parse_float=lambda x: str(PyDecimal(x)))
arg_types = {
'num': int,
'nbits': int,
Expand All @@ -814,8 +814,8 @@ def help(self):
'jsontx': json_loads,
'inputs': json_loads,
'outputs': json_loads,
'fee': lambda x: str(Decimal(x)) if x is not None else None,
'amount': lambda x: str(Decimal(x)) if x != '!' else '!',
'fee': lambda x: str(PyDecimal(x)) if x is not None else None,
'amount': lambda x: str(PyDecimal(x)) if x != '!' else '!',
'locktime': int,
}

Expand Down
50 changes: 25 additions & 25 deletions lib/exchange_rate.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import time
import csv
import decimal
from decimal import Decimal
from decimal import Decimal as PyDecimal # Qt 5.12 also exports Decimal

from .bitcoin import COIN
from .i18n import _
Expand Down Expand Up @@ -123,7 +123,7 @@ class BitcoinAverage(ExchangeBase):

def get_rates(self, ccy):
json = self.get_json('apiv2.bitcoinaverage.com', '/indices/global/ticker/short')
return dict([(r.replace("BCH", ""), Decimal(json[r]['last']))
return dict([(r.replace("BCH", ""), PyDecimal(json[r]['last']))
for r in json if r != 'timestamp'])

def history_ccys(self):
Expand All @@ -142,21 +142,21 @@ class Bitmarket(ExchangeBase):

def get_rates(self, ccy):
json = self.get_json('www.bitmarket.pl', '/json/BCCPLN/ticker.json')
return {'PLN': Decimal(json['last'])}
return {'PLN': PyDecimal(json['last'])}


class BitPay(ExchangeBase):

def get_rates(self, ccy):
json = self.get_json('bitpay.com', '/api/rates/BCH')
return dict([(r['code'], Decimal(r['rate'])) for r in json])
return dict([(r['code'], PyDecimal(r['rate'])) for r in json])


class Bitso(ExchangeBase):

def get_rates(self, ccy):
json = self.get_json('api.bitso.com', '/v2/ticker/?book=bch_btc')
return {'BTC': Decimal(json['last'])}
return {'BTC': PyDecimal(json['last'])}


class BitStamp(ExchangeBase):
Expand All @@ -166,17 +166,17 @@ def get_rates(self, ccy):
json_eur = self.get_json('www.bitstamp.net', '/api/v2/ticker/bcheur')
json_btc = self.get_json('www.bitstamp.net', '/api/v2/ticker/bchbtc')
return {
'USD': Decimal(json_usd['last']),
'EUR': Decimal(json_eur['last']),
'BTC': Decimal(json_btc['last'])}
'USD': PyDecimal(json_usd['last']),
'EUR': PyDecimal(json_eur['last']),
'BTC': PyDecimal(json_btc['last'])}


class Coinbase(ExchangeBase):

def get_rates(self, ccy):
json = self.get_json('coinbase.com',
'/api/v1/currencies/exchange_rates')
return dict([(r[7:].upper(), Decimal(json[r]))
return dict([(r[7:].upper(), PyDecimal(json[r]))
for r in json if r.startswith('bch_to_')])

class Kraken(ExchangeBase):
Expand All @@ -186,15 +186,15 @@ def get_rates(self, ccy):
pairs = ['BCH%s' % c for c in ccys]
json = self.get_json('api.kraken.com',
'/0/public/Ticker?pair=%s' % ','.join(pairs))
return dict((k[-3:], Decimal(float(v['c'][0])))
return dict((k[-3:], PyDecimal(float(v['c'][0])))
for k, v in json['result'].items())


class CoinFloor(ExchangeBase):
# CoinFloor API only supports GBP on public API
def get_rates(self, ccy):
json = self.get_json('webapi.coinfloor.co.uk:8090/bist/BCH/GBP', '/ticker/')
return {'GBP': Decimal(json['last'])}
return {'GBP': PyDecimal(json['last'])}


class WEX(ExchangeBase):
Expand All @@ -207,20 +207,20 @@ def get_rates(self, ccy):
json_ltc = self.get_json('wex.nz', '/api/3/ticker/bch_ltc')
json_eth = self.get_json('wex.nz', '/api/3/ticker/bch_eth')
json_dsh = self.get_json('wex.nz', '/api/3/ticker/bch_dsh')
return {'EUR': Decimal(json_eur['bch_eur']['last']),
'RUB': Decimal(json_rub['bch_rur']['last']),
'USD': Decimal(json_usd['bch_usd']['last']),
'BTC': Decimal(json_btc['bch_btc']['last']),
'LTC': Decimal(json_ltc['bch_ltc']['last']),
'ETH': Decimal(json_eth['bch_eth']['last']),
'DSH': Decimal(json_dsh['bch_dsh']['last'])}
return {'EUR': PyDecimal(json_eur['bch_eur']['last']),
'RUB': PyDecimal(json_rub['bch_rur']['last']),
'USD': PyDecimal(json_usd['bch_usd']['last']),
'BTC': PyDecimal(json_btc['bch_btc']['last']),
'LTC': PyDecimal(json_ltc['bch_ltc']['last']),
'ETH': PyDecimal(json_eth['bch_eth']['last']),
'DSH': PyDecimal(json_dsh['bch_dsh']['last'])}


class CoinCap(ExchangeBase):

def get_rates(self, ccy):
json = self.get_json('api.coincap.io', '/v2/rates/bitcoin-cash/')
return {'USD': Decimal(json['data']['rateUsd'])}
return {'USD': PyDecimal(json['data']['rateUsd'])}

def history_ccys(self):
return ['USD']
Expand All @@ -241,7 +241,7 @@ class CoinGecko(ExchangeBase):
def get_rates(self, ccy):
json = self.get_json('api.coingecko.com', '/api/v3/coins/bitcoin-cash?localization=False&sparkline=false')
prices = json["market_data"]["current_price"]
return dict([(a[0].upper(),Decimal(a[1])) for a in prices.items()])
return dict([(a[0].upper(),PyDecimal(a[1])) for a in prices.items()])

def history_ccys(self):
return ['AED', 'ARS', 'AUD', 'BTD', 'BHD', 'BMD', 'BRL', 'BTC',
Expand Down Expand Up @@ -403,10 +403,10 @@ def on_history(self):
self.network.trigger_callback('on_history')

def exchange_rate(self):
'''Returns None, or the exchange rate as a Decimal'''
'''Returns None, or the exchange rate as a PyDecimal'''
rate = self.exchange.quotes.get(self.ccy)
if rate:
return Decimal(rate)
return PyDecimal(rate)

def format_amount_and_units(self, btc_balance):
amount_str = self.format_amount(btc_balance)
Expand All @@ -428,7 +428,7 @@ def value_str(self, satoshis, rate, default_prec = 2 ):
if satoshis is None: # Can happen with incomplete history
return _("Unknown")
if rate:
value = Decimal(satoshis) / COIN * Decimal(rate)
value = PyDecimal(satoshis) / COIN * PyDecimal(rate)
return "%s" % (self.ccy_amount_str(value, True, default_prec))
return _("No data")

Expand All @@ -439,7 +439,7 @@ def history_rate(self, d_t):
if rate is None and (datetime.today().date() - d_t.date()).days <= 2:
rate = self.exchange.quotes.get(self.ccy)
self.history_used_spot = True
return Decimal(rate) if rate is not None else None
return PyDecimal(rate) if rate is not None else None

def historical_value_str(self, satoshis, d_t):
rate = self.history_rate(d_t)
Expand All @@ -448,7 +448,7 @@ def historical_value_str(self, satoshis, d_t):
def historical_value(self, satoshis, d_t):
rate = self.history_rate(d_t)
if rate:
return Decimal(satoshis) / COIN * Decimal(rate)
return PyDecimal(satoshis) / COIN * PyDecimal(rate)

def timestamp_rate(self, timestamp):
from .util import timestamp_to_datetime
Expand Down
4 changes: 2 additions & 2 deletions lib/tests/test_commands.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import unittest
from decimal import Decimal
from decimal import Decimal as PyDecimal

from ..commands import Commands

Expand All @@ -9,7 +9,7 @@ class TestCommands(unittest.TestCase):
def test_setconfig_non_auth_number(self):
self.assertEqual(7777, Commands._setconfig_normalize_value('rpcport', "7777"))
self.assertEqual(7777, Commands._setconfig_normalize_value('rpcport', '7777'))
self.assertAlmostEqual(Decimal(2.3), Commands._setconfig_normalize_value('somekey', '2.3'))
self.assertAlmostEqual(PyDecimal(2.3), Commands._setconfig_normalize_value('somekey', '2.3'))

def test_setconfig_non_auth_number_as_string(self):
self.assertEqual("7777", Commands._setconfig_normalize_value('somekey', "'7777'"))
Expand Down
8 changes: 4 additions & 4 deletions lib/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@
import os, sys, re, json, time
from collections import defaultdict
from datetime import datetime
from decimal import Decimal
import decimal
from decimal import Decimal as PyDecimal # Qt 5.12 also exports Decimal
import decimal # for SLP-specific function format_satoshis_plain_nofloat
import traceback
import threading
import hmac
Expand Down Expand Up @@ -234,7 +234,7 @@ def json_encode(obj):

def json_decode(x):
try:
return json.loads(x, parse_float=Decimal)
return json.loads(x, parse_float=PyDecimal)
except:
return x

Expand Down Expand Up @@ -416,7 +416,7 @@ def format_satoshis_plain(x, decimal_point = 8):
"""Display a satoshi amount scaled. Always uses a '.' as a decimal
point and has no thousands separator"""
scale_factor = pow(10, decimal_point)
return "{:.8f}".format(Decimal(x) / scale_factor).rstrip('0').rstrip('.')
return "{:.8f}".format(PyDecimal(x) / scale_factor).rstrip('0').rstrip('.')


def format_satoshis(x, num_zeros=0, decimal_point=8, precision=None, is_diff=False, whitespaces=False):
Expand Down
2 changes: 1 addition & 1 deletion lib/wallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
import copy
import errno
from collections import defaultdict
from decimal import Decimal
from decimal import Decimal as PyDecimal # Qt 5.12 also exports Decimal
from functools import partial

from .i18n import _
Expand Down
6 changes: 3 additions & 3 deletions lib/web.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

from decimal import Decimal
from decimal import Decimal as PyDecimal # Qt 5.12 also exports Decimal
import os
import re
import shutil
Expand Down Expand Up @@ -157,9 +157,9 @@ def parse_URI(uri, on_pr=None):
m = re.match(r'([0-9\.]+)X([0-9])', am)
if m:
k = int(m.group(2)) - 8
amount = Decimal(m.group(1)) * pow(10, k)
amount = PyDecimal(m.group(1)) * pow(10, k)
else:
amount = Decimal(am) * bitcoin.COIN
amount = PyDecimal(am) * bitcoin.COIN
out['amount'] = int(amount)
if 'message' in out:
out['message'] = out['message']
Expand Down

0 comments on commit 8ab34d9

Please sign in to comment.