From fc2da0875345f170c45f72806aff556e242118a7 Mon Sep 17 00:00:00 2001 From: Lee Clagett Date: Tue, 27 May 2025 15:45:51 -0400 Subject: [PATCH] Add safe numeric and low funds fix --- src/numeric.h | 90 +++++++++++++++++++++++++++++++++++++ src/pending_transaction.cpp | 5 ++- src/wallet.cpp | 11 +++-- 3 files changed, 101 insertions(+), 5 deletions(-) create mode 100644 src/numeric.h diff --git a/src/numeric.h b/src/numeric.h new file mode 100644 index 0000000..1b5e0ec --- /dev/null +++ b/src/numeric.h @@ -0,0 +1,90 @@ +// Copyright (c) 2025, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include +#include +#include +#include + +namespace lwsf { namespace internal +{ + template + class safe_integer + { + static_assert(std::is_integral()); + T value_; + public: + constexpr safe_integer() noexcept + : value_(0) + {} + + constexpr safe_integer(const T value) noexcept + : value_(value) + {} + + template + constexpr safe_integer(const U value) noexcept + : value_(value) + { + static_assert(std::is_same(), "invalid conversion"); + } + + constexpr safe_integer(const safe_integer&) noexcept = default; + constexpr safe_integer& operator=(const safe_integer&) noexcept = default; + + constexpr operator T() const noexcept { return value_; } + + template + constexpr operator U() const noexcept + { static_assert(std::is_same(), "invalid conversion"); }; + + safe_integer& add(safe_integer rhs) + { + if (std::numeric_limits::max() - value_ < rhs.value_) + throw std::overflow_error{"safe_integer overflow in addition"}; + value_ += rhs.value_; + return *this; + } + + safe_integer& subtract(safe_integer rhs) + { + if (value_ - std::numeric_limits::min() < rhs.value_) + throw std::underflow_error{"safe_integer underflow in subtraction"}; + value_ -= rhs.value_; + return *this; + } + + safe_integer& operator+=(safe_integer rhs) { return add(rhs); } + safe_integer& operator-=(safe_integer rhs) { return subtract(rhs); } + }; + + using safe_uint64_t = safe_integer; +}} // lwsf // internal + diff --git a/src/pending_transaction.cpp b/src/pending_transaction.cpp index 79fb96f..39e0494 100644 --- a/src/pending_transaction.cpp +++ b/src/pending_transaction.cpp @@ -33,6 +33,7 @@ #include "byte_slice.h" // monero/contrib/epee/include #include "cryptonote_basic/cryptonote_format_utils.h" // monero/src #include "error.h" +#include "numeric.h" #include "string_tools.h" // monero/contrib/epee/include @@ -93,7 +94,7 @@ namespace lwsf { namespace internal std::uint64_t pending_transaction::amount() const { - std::uint64_t out = 0; + safe_uint64_t out{}; for (const auto& e : local_) out += e->amount; return out; @@ -101,7 +102,7 @@ namespace lwsf { namespace internal std::uint64_t pending_transaction::fee() const { - std::uint64_t out = 0; + safe_uint64_t out{}; for (const auto& e : local_) out += e->fee; return out; diff --git a/src/wallet.cpp b/src/wallet.cpp index a8ed2ca..3e192ac 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -56,6 +56,7 @@ #include "net/parse.h" // monero/src #include "net/socks.h" // monero/src #include "net/socks_connect.h" // monero/src +#include "numeric.h" #include "pending_transaction.h" #include "subaddress_account.h" #include "subaddress_minor.h" @@ -1382,8 +1383,8 @@ namespace lwsf { namespace internal LWSF_TX_VERIFY(dests.size() < config::max_outputs_in_construction); LWSF_TX_VERIFY(dests.size() == transfers.size()); - std::uint64_t transfer_total = 0; - for (const auto & e : dests) + safe_uint64_t transfer_total{}; + for (const auto& e : dests) transfer_total += e.amount; std::vector extra; @@ -1594,6 +1595,10 @@ namespace lwsf { namespace internal if (!cryptonote::construct_tx_and_get_tx_key(keys, subs, rsources, rdests, change_account, extra, tx, tx_key, tx_keys, true /* rct */, config, true /* view_tags */)) throw std::runtime_error{"cryptonote::construct_tx_and_get_tx_key failed"}; + /* Collect all information for `backend::transaction` - this + helps with UI as the transaction will appear immediately when + `pending_transaction->commit()` is performed. */ + auto details = std::make_shared(); details->raw_bytes = epee::byte_slice{cryptonote::t_serializable_object_to_blob(tx)}; details->timestamp = std::chrono::system_clock::now(); @@ -1903,7 +1908,7 @@ namespace lwsf { namespace internal } if (attempt == unspent.size()) - break; + throw_low_funds(); } if (!dests_flat.empty())