From 715d209d034acef657c20f7b8f6e5d212c5ef53d Mon Sep 17 00:00:00 2001 From: Stephane Janel Date: Sat, 28 Oct 2023 22:06:06 +0200 Subject: [PATCH] MonetaryAmount from std::string_view should be be constructible with no amount when currency is non default --- src/api/common/src/ssl_sha.cpp | 2 +- src/main/src/main.cpp | 6 +-- src/objects/include/currencycode.hpp | 38 ++++++++++--------- src/objects/include/currencyexchange.hpp | 4 +- src/objects/include/exchangename.hpp | 18 ++++----- src/objects/include/loadconfiguration.hpp | 3 +- src/objects/include/market.hpp | 4 +- src/objects/include/marketorderbook.hpp | 1 - src/objects/include/monetaryamount.hpp | 1 + src/objects/include/priceoptions.hpp | 1 - .../include/volumeandpricenbdecimals.hpp | 2 +- src/objects/include/wallet.hpp | 3 +- src/objects/src/monetaryamount.cpp | 4 ++ src/objects/test/currencycode_test.cpp | 14 +++++++ src/objects/test/monetaryamount_test.cpp | 6 +++ src/tech/include/stringhelpers.hpp | 8 +++- 16 files changed, 71 insertions(+), 44 deletions(-) diff --git a/src/api/common/src/ssl_sha.cpp b/src/api/common/src/ssl_sha.cpp index 6fb18fd0..bb4ab55d 100644 --- a/src/api/common/src/ssl_sha.cpp +++ b/src/api/common/src/ssl_sha.cpp @@ -68,7 +68,7 @@ inline EVPMDCTXUniquePtr InitEVPMDCTXUniquePtr(ShaType shaType) { } inline string EVPBinToHex(const EVPMDCTXUniquePtr& mdctx) { - unsigned int len; + unsigned int len = 0; unsigned char binData[EVP_MAX_MD_SIZE]; EVP_DigestFinal_ex(mdctx.get(), binData, &len); return BinToHex(std::span(binData, len)); diff --git a/src/main/src/main.cpp b/src/main/src/main.cpp index 96f2520d..20ae1fcf 100644 --- a/src/main/src/main.cpp +++ b/src/main/src/main.cpp @@ -10,11 +10,11 @@ int main(int argc, const char* argv[]) { try { - auto cmdLineOptionsVector = cct::CoincenterCommands::ParseOptions(argc, argv); + const auto cmdLineOptionsVector = cct::CoincenterCommands::ParseOptions(argc, argv); if (!cmdLineOptionsVector.empty()) { - cct::CoincenterCommands coincenterCommands(cmdLineOptionsVector); - auto programName = std::filesystem::path(argv[0]).filename().string(); + const cct::CoincenterCommands coincenterCommands(cmdLineOptionsVector); + const auto programName = std::filesystem::path(argv[0]).filename().string(); cct::ProcessCommandsFromCLI(programName, coincenterCommands, cmdLineOptionsVector.front(), cct::settings::RunMode::kProd); diff --git a/src/objects/include/currencycode.hpp b/src/objects/include/currencycode.hpp index 02890bdc..4c9b8054 100644 --- a/src/objects/include/currencycode.hpp +++ b/src/objects/include/currencycode.hpp @@ -69,39 +69,40 @@ class CurrencyCodeIterator { using pointer = const char *; using reference = const char &; - constexpr auto operator<=>(const CurrencyCodeIterator &) const noexcept = default; - bool operator==(const CurrencyCodeIterator &) const noexcept = default; + constexpr std::strong_ordering operator<=>(const CurrencyCodeIterator &) const noexcept = default; - CurrencyCodeIterator &operator++() noexcept { // Prefix increment + constexpr bool operator==(const CurrencyCodeIterator &) const noexcept = default; + + constexpr CurrencyCodeIterator &operator++() noexcept { // Prefix increment ++_pos; return *this; } - CurrencyCodeIterator &operator--() noexcept { // Prefix decrement + constexpr CurrencyCodeIterator &operator--() noexcept { // Prefix decrement --_pos; return *this; } - CurrencyCodeIterator operator++(int) noexcept { // Postfix increment + constexpr CurrencyCodeIterator operator++(int) noexcept { // Postfix increment CurrencyCodeIterator oldSelf = *this; ++*this; return oldSelf; } - CurrencyCodeIterator operator--(int) noexcept { // Postfix decrement + constexpr CurrencyCodeIterator operator--(int) noexcept { // Postfix decrement CurrencyCodeIterator oldSelf = *this; --*this; return oldSelf; } - char operator*() const noexcept { return CurrencyCodeBase::CharAt(_data, static_cast(_pos)); } + constexpr char operator*() const noexcept { return CurrencyCodeBase::CharAt(_data, static_cast(_pos)); } // operator-> cannot be implemented here - we would need a const char * but it's not possible. private: friend class CurrencyCode; // Default constructor needed for an iterator in C++20 - explicit CurrencyCodeIterator(uint64_t data = 0, uint64_t pos = 0) noexcept : _data(data), _pos(pos) {} + constexpr explicit CurrencyCodeIterator(uint64_t data = 0, uint64_t pos = 0) noexcept : _data(data), _pos(pos) {} uint64_t _data; uint64_t _pos; @@ -137,8 +138,8 @@ class CurrencyCode { _data = CurrencyCodeBase::StrToBmp(acronym); } - const_iterator begin() const { return const_iterator(_data); } - const_iterator end() const { return const_iterator(_data, size()); } + constexpr const_iterator begin() const { return const_iterator(_data); } + constexpr const_iterator end() const { return const_iterator(_data, size()); } constexpr uint64_t size() const { uint64_t sz = 0; @@ -165,7 +166,7 @@ class CurrencyCode { return false; } for (uint32_t charPos = 0; charPos < kMaxLen; ++charPos) { - char ch = (*this)[charPos]; + const char ch = (*this)[charPos]; if (ch == CurrencyCodeBase::kFirstAuthorizedLetter) { return curStr.size() == charPos; } @@ -178,16 +179,16 @@ class CurrencyCode { /// Append currency string representation to given string. void appendStrTo(string &str) const { - auto len = size(); + const auto len = size(); str.append(len, '\0'); append(str.end() - len); } /// Append currency string representation to given output iterator template - OutputIt append(OutputIt it) const { + constexpr OutputIt append(OutputIt it) const { for (uint32_t charPos = 0; charPos < kMaxLen; ++charPos) { - char ch = (*this)[charPos]; + const char ch = (*this)[charPos]; if (ch == CurrencyCodeBase::kFirstAuthorizedLetter) { break; } @@ -205,13 +206,13 @@ class CurrencyCode { constexpr char operator[](uint32_t pos) const { return CurrencyCodeBase::CharAt(_data, static_cast(pos)); } /// Note that this respects the lexicographical order - chars are encoded from the most significant bits first - constexpr auto operator<=>(const CurrencyCode &) const noexcept = default; + constexpr std::strong_ordering operator<=>(const CurrencyCode &) const noexcept = default; constexpr bool operator==(const CurrencyCode &) const noexcept = default; friend std::ostream &operator<<(std::ostream &os, const CurrencyCode &cur) { for (uint32_t charPos = 0; charPos < kMaxLen; ++charPos) { - char ch = cur[charPos]; + const char ch = cur[charPos]; if (ch == CurrencyCodeBase::kFirstAuthorizedLetter) { break; } @@ -257,7 +258,7 @@ class CurrencyCode { /// Append currency string representation to given string, with a space before (used by MonetaryAmount) void appendStrWithSpaceTo(string &str) const { - auto len = size(); + const auto len = size(); str.append(len + 1UL, ' '); append(str.end() - len); } @@ -269,7 +270,8 @@ class CurrencyCode { template <> struct fmt::formatter { constexpr auto parse(format_parse_context &ctx) -> decltype(ctx.begin()) { - auto it = ctx.begin(), end = ctx.end(); + const auto it = ctx.begin(); + const auto end = ctx.end(); if (it != end && *it != '}') { throw format_error("invalid format"); } diff --git a/src/objects/include/currencyexchange.hpp b/src/objects/include/currencyexchange.hpp index c95e8ddb..3869d95f 100644 --- a/src/objects/include/currencyexchange.hpp +++ b/src/objects/include/currencyexchange.hpp @@ -1,8 +1,8 @@ #pragma once +#include #include #include -#include #include "cct_string.hpp" #include "currencycode.hpp" @@ -50,7 +50,7 @@ class CurrencyExchange { bool isFiat() const { return _isFiat; } // Compare by standard code first. - constexpr auto operator<=>(const CurrencyExchange &) const noexcept = default; + constexpr std::strong_ordering operator<=>(const CurrencyExchange &) const noexcept = default; constexpr bool operator==(const CurrencyExchange &) const noexcept = default; diff --git a/src/objects/include/exchangename.hpp b/src/objects/include/exchangename.hpp index 780f20a2..7b997173 100644 --- a/src/objects/include/exchangename.hpp +++ b/src/objects/include/exchangename.hpp @@ -29,14 +29,14 @@ class ExchangeName { ExchangeName(std::string_view exchangeName, std::string_view keyName); std::string_view name() const { - std::size_t underscore = underscorePos(); + const std::size_t underscore = underscorePos(); return std::string_view(_nameWithKey.data(), underscore == string::npos ? _nameWithKey.size() : underscore); } std::string_view keyName() const { - std::size_t underscore = underscorePos(); - return std::string_view(_nameWithKey.begin() + (underscore == string::npos ? _nameWithKey.size() : underscore + 1U), - _nameWithKey.end()); + const std::size_t underscore = underscorePos(); + return {_nameWithKey.begin() + (underscore == string::npos ? _nameWithKey.size() : underscore + 1U), + _nameWithKey.end()}; } bool isKeyNameDefined() const { return underscorePos() != string::npos; } @@ -45,10 +45,7 @@ class ExchangeName { bool operator==(const ExchangeName &) const noexcept = default; - friend std::ostream &operator<<(std::ostream &os, const ExchangeName &v) { - os << v.str(); - return os; - } + friend std::ostream &operator<<(std::ostream &os, const ExchangeName &rhs) { return os << rhs.str(); } using trivially_relocatable = is_trivially_relocatable::type; @@ -64,7 +61,7 @@ class ExchangeName { }; using ExchangeNameSpan = std::span; -using ExchangeNames = SmallVector; +using ExchangeNames = SmallVector; inline std::string_view ToString(std::string_view exchangeName) { return exchangeName; } inline std::string_view ToString(const ExchangeName &exchangeName) { return exchangeName.str(); } @@ -95,7 +92,8 @@ struct fmt::formatter { bool printKeyName = false; constexpr auto parse(format_parse_context &ctx) -> decltype(ctx.begin()) { - auto it = ctx.begin(), end = ctx.end(); + auto it = ctx.begin(); + const auto end = ctx.end(); if (it == end || *it == '}') { printExchangeName = true; printKeyName = true; diff --git a/src/objects/include/loadconfiguration.hpp b/src/objects/include/loadconfiguration.hpp index 581ab07e..ce534a4d 100644 --- a/src/objects/include/loadconfiguration.hpp +++ b/src/objects/include/loadconfiguration.hpp @@ -1,9 +1,8 @@ #pragma once +#include #include -#include "cct_const.hpp" - namespace cct { class LoadConfiguration { public: diff --git a/src/objects/include/market.hpp b/src/objects/include/market.hpp index c87ee6f9..03491edc 100644 --- a/src/objects/include/market.hpp +++ b/src/objects/include/market.hpp @@ -2,7 +2,6 @@ #include #include -#include #include "cct_format.hpp" #include "cct_string.hpp" @@ -70,7 +69,8 @@ class Market { template <> struct fmt::formatter { constexpr auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) { - auto it = ctx.begin(), end = ctx.end(); + auto it = ctx.begin(); + const auto end = ctx.end(); if (it != end && *it != '}') { throw format_error("invalid format"); } diff --git a/src/objects/include/marketorderbook.hpp b/src/objects/include/marketorderbook.hpp index 45a4fdd5..de396110 100644 --- a/src/objects/include/marketorderbook.hpp +++ b/src/objects/include/marketorderbook.hpp @@ -1,6 +1,5 @@ #pragma once -#include #include #include #include diff --git a/src/objects/include/monetaryamount.hpp b/src/objects/include/monetaryamount.hpp index cd381fc4..3566acbc 100644 --- a/src/objects/include/monetaryamount.hpp +++ b/src/objects/include/monetaryamount.hpp @@ -74,6 +74,7 @@ class MonetaryAmount { /// Constructs a new MonetaryAmount from a string, containing an optional CurrencyCode. /// - If a currency is not present, assume default CurrencyCode /// - If the currency is too long to fit in a CurrencyCode, exception will be raised + /// - If only a currency is given, invalid_argument exception will be raised /// - If given string is empty, it is equivalent to a default constructor /// /// A space can be present or not between the amount and the currency code. diff --git a/src/objects/include/priceoptions.hpp b/src/objects/include/priceoptions.hpp index 09b3bbe0..e3534bac 100644 --- a/src/objects/include/priceoptions.hpp +++ b/src/objects/include/priceoptions.hpp @@ -5,7 +5,6 @@ #include "cct_string.hpp" #include "monetaryamount.hpp" #include "priceoptionsdef.hpp" -#include "timedef.hpp" namespace cct { class PriceOptions { diff --git a/src/objects/include/volumeandpricenbdecimals.hpp b/src/objects/include/volumeandpricenbdecimals.hpp index ad1ebbd5..38dc4e75 100644 --- a/src/objects/include/volumeandpricenbdecimals.hpp +++ b/src/objects/include/volumeandpricenbdecimals.hpp @@ -5,7 +5,7 @@ namespace cct { struct VolAndPriNbDecimals { - constexpr bool operator==(const VolAndPriNbDecimals &o) const = default; + constexpr bool operator==(const VolAndPriNbDecimals &) const noexcept = default; int8_t volNbDecimals = std::numeric_limits::digits10; int8_t priNbDecimals = std::numeric_limits::digits10; diff --git a/src/objects/include/wallet.hpp b/src/objects/include/wallet.hpp index 8ab1145c..e1d908bb 100644 --- a/src/objects/include/wallet.hpp +++ b/src/objects/include/wallet.hpp @@ -88,7 +88,8 @@ class Wallet { template <> struct fmt::formatter { constexpr auto parse(format_parse_context &ctx) -> decltype(ctx.begin()) { - auto it = ctx.begin(), end = ctx.end(); + auto it = ctx.begin(); + const auto end = ctx.end(); if (it != end && *it != '}') { throw format_error("invalid format"); } diff --git a/src/objects/src/monetaryamount.cpp b/src/objects/src/monetaryamount.cpp index 8cdf4770..9008d6a7 100644 --- a/src/objects/src/monetaryamount.cpp +++ b/src/objects/src/monetaryamount.cpp @@ -19,6 +19,7 @@ #include "cct_config.hpp" #include "cct_exception.hpp" +#include "cct_invalid_argument_exception.hpp" #include "currencycode.hpp" #include "mathhelpers.hpp" #include "stringhelpers.hpp" @@ -155,6 +156,9 @@ MonetaryAmount::MonetaryAmount(std::string_view amountCurrencyStr) { std::string_view currencyStr(last, endIt); RemoveTrailingSpaces(currencyStr); RemovePrefixSpaces(currencyStr); + if (!currencyStr.empty() && amountStr.empty()) { + throw invalid_argument("Cannot construct MonetaryAmount with a currency without any amount"); + } _curWithDecimals = CurrencyCode(currencyStr); sanitizeDecimals(nbDecimals, maxNbDecimals()); } diff --git a/src/objects/test/currencycode_test.cpp b/src/objects/test/currencycode_test.cpp index 602173e9..4e8c0d22 100644 --- a/src/objects/test/currencycode_test.cpp +++ b/src/objects/test/currencycode_test.cpp @@ -156,9 +156,23 @@ TEST(CurrencyCodeTest, UpperConversion) { EXPECT_EQ(CurrencyCode("etc").str(), "ETC"); } +namespace { +constexpr bool HasZ(CurrencyCode cur) { + for (char ch : cur) { + if (ch == 'Z') { + return true; + } + } + return false; +} +} // namespace + TEST(CurrencyCodeTest, Constexpr) { static_assert(CurrencyCode("doge") == CurrencyCode("DOGE")); static_assert(CurrencyCode("XRP").code() != 0); + + static_assert(!HasZ(CurrencyCode("LONGCUR"))); + static_assert(HasZ(CurrencyCode("GTZFD"))); } TEST(CurrencyCodeTest, Iterator) { diff --git a/src/objects/test/monetaryamount_test.cpp b/src/objects/test/monetaryamount_test.cpp index a6d60fd3..bdb11ce8 100644 --- a/src/objects/test/monetaryamount_test.cpp +++ b/src/objects/test/monetaryamount_test.cpp @@ -6,6 +6,7 @@ #include #include "cct_exception.hpp" +#include "cct_invalid_argument_exception.hpp" #include "cct_string.hpp" #include "currencycode.hpp" #include "mathhelpers.hpp" @@ -180,6 +181,8 @@ TEST(MonetaryAmountTest, OverflowProtectionMultiplication) { MonetaryAmount("0.00426622338114037", cur)); EXPECT_EQ(MonetaryAmount("38.0566894350664") * MonetaryAmount("0.00008795", cur), MonetaryAmount("0.00334708583581405", cur)); + EXPECT_EQ((-1) * MonetaryAmount("-9223372036854775807", cur), MonetaryAmount("922337203685477580", cur)); + EXPECT_EQ((-1) * MonetaryAmount("-922337203685477580", cur), MonetaryAmount("922337203685477580", cur)); } } @@ -238,10 +241,13 @@ TEST(MonetaryAmountTest, StringConstructor) { EXPECT_EQ(MonetaryAmount("-210.50 CAKE"), MonetaryAmount("-210.50", "CAKE")); EXPECT_EQ(MonetaryAmount("05AUD"), MonetaryAmount(5, "AUD")); EXPECT_EQ(MonetaryAmount("746REPV2"), MonetaryAmount("746", "REPV2")); + + EXPECT_THROW(MonetaryAmount("usdt"), invalid_argument); } TEST(MonetaryAmountTest, StringConstructorAmbiguity) { EXPECT_EQ(MonetaryAmount("804.621INCH"), MonetaryAmount("804.621", "INCH")); + EXPECT_EQ(MonetaryAmount("804.62 1INCH"), MonetaryAmount("804.62", "1INCH")); EXPECT_EQ(MonetaryAmount("804.62", "1INCH"), MonetaryAmount("804.62", "1INCH")); } diff --git a/src/tech/include/stringhelpers.hpp b/src/tech/include/stringhelpers.hpp index b26fe22d..8b9364b6 100644 --- a/src/tech/include/stringhelpers.hpp +++ b/src/tech/include/stringhelpers.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "cct_config.hpp" #include "cct_exception.hpp" @@ -30,9 +31,12 @@ inline string ToString(std::integral auto i) { template Integral FromString(std::string_view str) { - Integral ret; + Integral ret{}; if (auto [ptr, errc] = std::from_chars(str.data(), str.data() + str.size(), ret); CCT_UNLIKELY(errc != std::errc())) { - throw exception("Unable to decode string into integral"); + if (errc == std::errc::result_out_of_range) { + throw exception("'{}' would produce an out of range integral", str); + } + throw exception("Unable to decode '{}' into integral", str); } return ret; }