From 0952c035dcac581f887dc2b405a629c5ec5af891 Mon Sep 17 00:00:00 2001 From: Sergey Kroshnin Date: Tue, 26 Mar 2019 20:40:23 +0300 Subject: [PATCH] Support sending decimals from QT wallet (#1843) * Support decimal part decoding for nano amounts * Support sending decimals from QT wallet * Show 3 decimal digits in wallet balances * Fix integer scale * Updated tests with integer strings * Update QT wallet.send test * Fix integer result * Fix OSX clang errors * Hardcode "." delimiter --- nano/core_test/uint256_union.cpp | 33 +++++++++ nano/lib/numbers.cpp | 76 ++++++++++++++++++++- nano/lib/numbers.hpp | 3 +- nano/qt/qt.cpp | 111 ++++++++++++------------------- nano/qt_test/qt.cpp | 5 +- 5 files changed, 155 insertions(+), 73 deletions(-) diff --git a/nano/core_test/uint256_union.cpp b/nano/core_test/uint256_union.cpp index 3458ae25..2273a1fc 100644 --- a/nano/core_test/uint256_union.cpp +++ b/nano/core_test/uint256_union.cpp @@ -126,6 +126,39 @@ TEST (uint128_union, balance_format) ASSERT_EQ ("12-3456-789+123", nano::amount (nano::Mxrb_ratio * 123456789 + nano::kxrb_ratio * 123).format_balance (nano::Mxrb_ratio, 4, true, std::locale (std::cout.getloc (), new test_punct))); } +TEST (uint128_union, decode_decimal) +{ + nano::amount amount; + ASSERT_FALSE (amount.decode_dec ("340282366920938463463374607431768211455", nano::raw_ratio)); + ASSERT_EQ (std::numeric_limits::max (), amount.number ()); + ASSERT_TRUE (amount.decode_dec ("340282366920938463463374607431768211456", nano::raw_ratio)); + ASSERT_TRUE (amount.decode_dec ("340282366920938463463374607431768211455.1", nano::raw_ratio)); + ASSERT_TRUE (amount.decode_dec ("0.1", nano::raw_ratio)); + ASSERT_FALSE (amount.decode_dec ("1", nano::raw_ratio)); + ASSERT_EQ (1, amount.number ()); + ASSERT_FALSE (amount.decode_dec ("340282366.920938463463374607431768211454", nano::Mxrb_ratio)); + ASSERT_EQ (std::numeric_limits::max () - 1, amount.number ()); + ASSERT_TRUE (amount.decode_dec ("340282366.920938463463374607431768211456", nano::Mxrb_ratio)); + ASSERT_TRUE (amount.decode_dec ("340282367", nano::Mxrb_ratio)); + ASSERT_FALSE (amount.decode_dec ("0.000000000000000000000001", nano::Mxrb_ratio)); + ASSERT_EQ (1000000, amount.number ()); + ASSERT_FALSE (amount.decode_dec ("0.000000000000000000000000000001", nano::Mxrb_ratio)); + ASSERT_EQ (1, amount.number ()); + ASSERT_TRUE (amount.decode_dec ("0.0000000000000000000000000000001", nano::Mxrb_ratio)); + ASSERT_TRUE (amount.decode_dec (".1", nano::Mxrb_ratio)); + ASSERT_TRUE (amount.decode_dec ("0.", nano::Mxrb_ratio)); + ASSERT_FALSE (amount.decode_dec ("9.999999999999999999999999999999", nano::Mxrb_ratio)); + ASSERT_EQ (nano::uint128_t ("9999999999999999999999999999999"), amount.number ()); + ASSERT_FALSE (amount.decode_dec ("170141183460469.231731687303715884105727", nano::xrb_ratio)); + ASSERT_EQ (nano::uint128_t ("170141183460469231731687303715884105727"), amount.number ()); + ASSERT_FALSE (amount.decode_dec ("2.000000000000000000000002", nano::xrb_ratio)); + ASSERT_EQ (2 * nano::xrb_ratio + 2, amount.number ()); + ASSERT_FALSE (amount.decode_dec ("2", nano::xrb_ratio)); + ASSERT_EQ (2 * nano::xrb_ratio, amount.number ()); + ASSERT_FALSE (amount.decode_dec ("1230", nano::Gxrb_ratio)); + ASSERT_EQ (1230 * nano::Gxrb_ratio, amount.number ()); +} + TEST (unions, identity) { ASSERT_EQ (1, nano::uint128_union (1).number ().convert_to ()); diff --git a/nano/lib/numbers.cpp b/nano/lib/numbers.cpp index 74b481fd..ae403363 100644 --- a/nano/lib/numbers.cpp +++ b/nano/lib/numbers.cpp @@ -538,9 +538,9 @@ void nano::uint128_union::encode_dec (std::string & text) const text = stream.str (); } -bool nano::uint128_union::decode_dec (std::string const & text) +bool nano::uint128_union::decode_dec (std::string const & text, bool decimal) { - auto error (text.size () > 39 || (text.size () > 1 && text.front () == '0') || (!text.empty () && text.front () == '-')); + auto error (text.size () > 39 || (text.size () > 1 && text.front () == '0' && !decimal) || (!text.empty () && text.front () == '-')); if (!error) { std::stringstream stream (text); @@ -564,6 +564,78 @@ bool nano::uint128_union::decode_dec (std::string const & text) return error; } +bool nano::uint128_union::decode_dec (std::string const & text, nano::uint128_t scale) +{ + bool error (text.size () > 40 || (!text.empty () && text.front () == '-')); + if (!error) + { + auto delimiter_position (text.find (".")); // Dot delimiter hardcoded until decision for supporting other locales + if (delimiter_position == std::string::npos) + { + nano::uint128_union integer; + error = integer.decode_dec (text); + if (!error) + { + // Overflow check + try + { + auto result (boost::multiprecision::checked_uint128_t (integer.number ()) * boost::multiprecision::checked_uint128_t (scale)); + error = (result > std::numeric_limits::max ()); + if (!error) + { + *this = nano::uint128_t (result); + } + } + catch (std::overflow_error &) + { + error = true; + } + } + } + else + { + nano::uint128_union integer_part; + std::string integer_text (text.substr (0, delimiter_position)); + error = (integer_text.empty () || integer_part.decode_dec (integer_text)); + if (!error) + { + // Overflow check + try + { + error = ((boost::multiprecision::checked_uint128_t (integer_part.number ()) * boost::multiprecision::checked_uint128_t (scale)) > std::numeric_limits::max ()); + } + catch (std::overflow_error &) + { + error = true; + } + if (!error) + { + nano::uint128_union decimal_part; + std::string decimal_text (text.substr (delimiter_position + 1, text.length ())); + error = (decimal_text.empty () || decimal_part.decode_dec (decimal_text, true)); + if (!error) + { + // Overflow check + auto scale_length (scale.convert_to ().length ()); + error = (scale_length <= decimal_text.length ()); + if (!error) + { + auto result (integer_part.number () * scale + decimal_part.number () * boost::multiprecision::pow (boost::multiprecision::cpp_int (10), (scale_length - decimal_text.length () - 1))); + // Overflow check + error = (result > std::numeric_limits::max ()); + if (!error) + { + *this = nano::uint128_t (result); + } + } + } + } + } + } + } + return error; +} + void format_frac (std::ostringstream & stream, nano::uint128_t value, nano::uint128_t scale, int precision) { auto reduce = scale; diff --git a/nano/lib/numbers.hpp b/nano/lib/numbers.hpp index 218d7b9a..d9958e05 100644 --- a/nano/lib/numbers.hpp +++ b/nano/lib/numbers.hpp @@ -58,7 +58,8 @@ public: void encode_hex (std::string &) const; bool decode_hex (std::string const &); void encode_dec (std::string &) const; - bool decode_dec (std::string const &); + bool decode_dec (std::string const &, bool = false); + bool decode_dec (std::string const &, nano::uint128_t); std::string format_balance (nano::uint128_t scale, int precision, bool group_digits); std::string format_balance (nano::uint128_t scale, int precision, bool group_digits, const std::locale & locale); nano::uint128_t number () const; diff --git a/nano/qt/qt.cpp b/nano/qt/qt.cpp index 05b79db9..7b27a7a3 100644 --- a/nano/qt/qt.cpp +++ b/nano/qt/qt.cpp @@ -1133,75 +1133,54 @@ void nano_qt::wallet::start () show_line_ok (*this_l->send_count); show_line_ok (*this_l->send_account); nano::amount amount; - if (!amount.decode_dec (this_l->send_count->text ().toStdString ())) + if (!amount.decode_dec (this_l->send_count->text ().toStdString (), this_l->rendering_ratio)) { - nano::uint128_t actual (amount.number () * this_l->rendering_ratio); - if (actual / this_l->rendering_ratio == amount.number ()) + nano::uint128_t actual (amount.number ()); + QString account_text (this_l->send_account->text ()); + std::string account_text_narrow (account_text.toLocal8Bit ()); + nano::account account_l; + auto parse_error (account_l.decode_account (account_text_narrow)); + if (!parse_error) { - QString account_text (this_l->send_account->text ()); - std::string account_text_narrow (account_text.toLocal8Bit ()); - nano::account account_l; - auto parse_error (account_l.decode_account (account_text_narrow)); - if (!parse_error) + auto balance (this_l->node.balance (this_l->account)); + if (actual <= balance) { - auto balance (this_l->node.balance (this_l->account)); - if (actual <= balance) + auto transaction (this_l->wallet_m->wallets.tx_begin_read ()); + if (this_l->wallet_m->store.valid_password (transaction)) { - auto transaction (this_l->wallet_m->wallets.tx_begin_read ()); - if (this_l->wallet_m->store.valid_password (transaction)) - { - this_l->send_blocks_send->setEnabled (false); - this_l->node.background ([this_w, account_l, actual]() { - if (auto this_l = this_w.lock ()) - { - this_l->wallet_m->send_async (this_l->account, account_l, actual, [this_w](std::shared_ptr block_a) { - if (auto this_l = this_w.lock ()) - { - auto succeeded (block_a != nullptr); - this_l->application.postEvent (&this_l->processor, new eventloop_event ([this_w, succeeded]() { - if (auto this_l = this_w.lock ()) + this_l->send_blocks_send->setEnabled (false); + this_l->node.background ([this_w, account_l, actual]() { + if (auto this_l = this_w.lock ()) + { + this_l->wallet_m->send_async (this_l->account, account_l, actual, [this_w](std::shared_ptr block_a) { + if (auto this_l = this_w.lock ()) + { + auto succeeded (block_a != nullptr); + this_l->application.postEvent (&this_l->processor, new eventloop_event ([this_w, succeeded]() { + if (auto this_l = this_w.lock ()) + { + this_l->send_blocks_send->setEnabled (true); + if (succeeded) { - this_l->send_blocks_send->setEnabled (true); - if (succeeded) - { - this_l->send_count->clear (); - this_l->send_account->clear (); - this_l->accounts.refresh (); - } - else - { - show_line_error (*this_l->send_count); - } + this_l->send_count->clear (); + this_l->send_account->clear (); + this_l->accounts.refresh (); } - })); - } - }); - } - }); - } - else - { - show_button_error (*this_l->send_blocks_send); - this_l->send_blocks_send->setText ("Wallet is locked, unlock it to send"); - this_l->node.alarm.add (std::chrono::steady_clock::now () + std::chrono::seconds (5), [this_w]() { - if (auto this_l = this_w.lock ()) - { - this_l->application.postEvent (&this_l->processor, new eventloop_event ([this_w]() { - if (auto this_l = this_w.lock ()) - { - show_button_ok (*this_l->send_blocks_send); - this_l->send_blocks_send->setText ("Send"); - } - })); - } - }); - } + else + { + show_line_error (*this_l->send_count); + } + } + })); + } + }); + } + }); } else { - show_line_error (*this_l->send_count); show_button_error (*this_l->send_blocks_send); - this_l->send_blocks_send->setText ("Not enough balance"); + this_l->send_blocks_send->setText ("Wallet is locked, unlock it to send"); this_l->node.alarm.add (std::chrono::steady_clock::now () + std::chrono::seconds (5), [this_w]() { if (auto this_l = this_w.lock ()) { @@ -1218,9 +1197,9 @@ void nano_qt::wallet::start () } else { - show_line_error (*this_l->send_account); + show_line_error (*this_l->send_count); show_button_error (*this_l->send_blocks_send); - this_l->send_blocks_send->setText ("Bad destination account"); + this_l->send_blocks_send->setText ("Not enough balance"); this_l->node.alarm.add (std::chrono::steady_clock::now () + std::chrono::seconds (5), [this_w]() { if (auto this_l = this_w.lock ()) { @@ -1237,19 +1216,15 @@ void nano_qt::wallet::start () } else { - show_line_error (*this_l->send_count); + show_line_error (*this_l->send_account); show_button_error (*this_l->send_blocks_send); - this_l->send_blocks_send->setText ("Amount too big"); + this_l->send_blocks_send->setText ("Bad destination account"); this_l->node.alarm.add (std::chrono::steady_clock::now () + std::chrono::seconds (5), [this_w]() { if (auto this_l = this_w.lock ()) { - show_line_ok (*this_l->send_account); - show_button_ok (*this_l->send_blocks_send); - this_l->send_blocks_send->setText ("Send"); this_l->application.postEvent (&this_l->processor, new eventloop_event ([this_w]() { if (auto this_l = this_w.lock ()) { - show_line_ok (*this_l->send_account); show_button_ok (*this_l->send_blocks_send); this_l->send_blocks_send->setText ("Send"); } @@ -1457,7 +1432,7 @@ void nano_qt::wallet::change_rendering_ratio (nano::uint128_t const & rendering_ std::string nano_qt::wallet::format_balance (nano::uint128_t const & balance) const { - auto balance_str = nano::amount (balance).format_balance (rendering_ratio, 0, false); + auto balance_str = nano::amount (balance).format_balance (rendering_ratio, 3, false); auto unit = std::string ("NANO"); if (rendering_ratio == nano::kxrb_ratio) { diff --git a/nano/qt_test/qt.cpp b/nano/qt_test/qt.cpp index 913cb472..e1bb6830 100644 --- a/nano/qt_test/qt.cpp +++ b/nano/qt_test/qt.cpp @@ -265,9 +265,10 @@ TEST (wallet, send) auto account (nano::test_genesis_key.pub); auto wallet (std::make_shared (*test_application, processor, *system.nodes[0], system.wallet (0), account)); wallet->start (); + ASSERT_NE (wallet->rendering_ratio, nano::raw_ratio); QTest::mouseClick (wallet->send_blocks, Qt::LeftButton); QTest::keyClicks (wallet->send_account, key1.to_account ().c_str ()); - QTest::keyClicks (wallet->send_count, "2"); + QTest::keyClicks (wallet->send_count, "2.03"); QTest::mouseClick (wallet->send_blocks_send, Qt::LeftButton); system.deadline_set (10s); while (wallet->node.balance (key1).is_zero ()) @@ -275,7 +276,7 @@ TEST (wallet, send) ASSERT_NO_ERROR (system.poll ()); } nano::uint128_t amount (wallet->node.balance (key1)); - ASSERT_EQ (2 * wallet->rendering_ratio, amount); + ASSERT_EQ (2 * wallet->rendering_ratio + (3 * wallet->rendering_ratio / 100), amount); QTest::mouseClick (wallet->send_blocks_back, Qt::LeftButton); QTest::mouseClick (wallet->show_advanced, Qt::LeftButton); QTest::mouseClick (wallet->advanced.show_ledger, Qt::LeftButton);