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:
parent
878cce97e3
commit
0952c035dc
5 changed files with 155 additions and 73 deletions
|
@ -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> ());
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
111
nano/qt/qt.cpp
111
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<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)
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue