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
This commit is contained in:
Sergey Kroshnin 2019-03-26 20:40:23 +03:00 committed by Zach Hyatt
commit 0952c035dc
5 changed files with 155 additions and 73 deletions

View file

@ -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<nano::uint128_t>::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<nano::uint128_t>::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<uint8_t> ());

View file

@ -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<nano::uint128_t>::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<nano::uint128_t>::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<std::string> ().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<nano::uint128_t>::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;

View file

@ -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;

View file

@ -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<nano::block> 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<nano::block> 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)
{

View file

@ -265,9 +265,10 @@ TEST (wallet, send)
auto account (nano::test_genesis_key.pub);
auto wallet (std::make_shared<nano_qt::wallet> (*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);