Compare commits

..

6 Commits

Author SHA1 Message Date
Ayaz Salikhov
909836ffc2 Better comment 2026-07-01 00:06:42 +01:00
Ayaz Salikhov
2af67d8f6f Hardcode 23 2026-07-01 00:05:33 +01:00
Ayaz Salikhov
9171fef02c Use user.package:cppstd_version 2026-07-01 00:04:12 +01:00
Ayaz Salikhov
273c1862ac Fix style 2026-06-30 23:56:22 +01:00
Ayaz Salikhov
49e9c2acb0 Use conf section 2026-06-30 23:51:47 +01:00
Ayaz Salikhov
cabafec58d build: Don't reuse binaries between different C++ versions 2026-06-30 23:42:40 +01:00
11 changed files with 540 additions and 1882 deletions

View File

@@ -10,16 +10,16 @@
os={{ os }}
arch={{ arch }}
build_type=Debug
compiler={{compiler}}
compiler={{ compiler }}
compiler.version={{ compiler_version }}
compiler.cppstd=23
{% if os == "Windows" %}
compiler.runtime=static
{% else %}
compiler.libcxx={{detect_api.detect_libcxx(compiler, version, compiler_exe)}}
compiler.libcxx={{ detect_api.detect_libcxx(compiler, version, compiler_exe) }}
{% endif %}
[conf]
{% if compiler == "gcc" and compiler_version < 13 %}
tools.build:cxxflags+=['-Wno-restrict']
{% endif %}
{# By default, conan tries compatibility mode to reuse binaries built with different cppstd versions #}
user.package:cppstd_version=23
tools.info.package_id:confs+=["user.package:cppstd_version"]

View File

@@ -87,15 +87,15 @@ include(default)
{% endif %}
[conf]
tools.build:defines+={{defines}}
tools.build:cxxflags+={{sanitizer_compiler_flags}}
tools.build:sharedlinkflags+={{sanitizer_linker_flags}}
tools.build:exelinkflags+={{sanitizer_linker_flags}}
tools.build:defines+={{ defines }}
tools.build:cxxflags+={{ sanitizer_compiler_flags }}
tools.build:sharedlinkflags+={{ sanitizer_linker_flags }}
tools.build:exelinkflags+={{ sanitizer_linker_flags }}
tools.info.package_id:confs+=["tools.build:cxxflags", "tools.build:exelinkflags", "tools.build:sharedlinkflags", "tools.build:defines"]
# &: means "apply only to the consumer/root package"
&:tools.cmake.cmaketoolchain:extra_variables={"SANITIZERS": "{{sanitizers}}", "SANITIZERS_COMPILER_FLAGS": "{{sanitizer_compiler_flags | join(' ')}}", "SANITIZERS_LINKER_FLAGS": "{{sanitizer_linker_flags | join(' ')}}"}
&:tools.cmake.cmaketoolchain:extra_variables={"SANITIZERS": "{{ sanitizers }}", "SANITIZERS_COMPILER_FLAGS": "{{ sanitizer_compiler_flags | join(' ') }}", "SANITIZERS_LINKER_FLAGS": "{{ sanitizer_linker_flags | join(' ') }}"}
[options]
{% if enable_asan %}

View File

@@ -51,43 +51,37 @@ namespace detail {
* compile time. Doing it at runtime would be pretty wasteful and
* inefficient.
*/
constexpr std::size_t kUint64Digits = 20;
[[maybe_unused]] constexpr std::size_t kUint128Digits = 39;
template <typename T, std::size_t Digits>
consteval std::array<T, Digits>
constexpr std::size_t kInt64Digits = 20;
consteval std::array<std::uint64_t, kInt64Digits>
buildPowersOfTen()
{
std::array<T, Digits> result{};
std::array<std::uint64_t, kInt64Digits> result{};
T power = 1;
std::uint64_t power = 1;
std::size_t exponent = 0;
// end the loop early so it doesn't overflow;
for (; exponent < result.size() - 1; ++exponent, power *= 10)
{
result[exponent] = power;
if (power > std::numeric_limits<T>::max() / 10)
if (power > std::numeric_limits<std::uint64_t>::max() / 10)
throw std::logic_error("Power of 10 table is too big");
}
result[exponent] = power;
if (power < std::numeric_limits<T>::max() / 10)
throw std::logic_error("Power of 10 table is not big enough for the given type");
if (power < std::numeric_limits<std::uint64_t>::max() / 10)
throw std::logic_error("Power of 10 table is not big enough for the uint64_t type");
return result;
}
} // namespace detail
template <typename T = std::uint64_t, std::size_t Digits = detail::kUint64Digits>
constexpr std::array<T, Digits> kPowerOfTenImpl = detail::buildPowersOfTen<T, Digits>();
constexpr auto kPowerOfTen = kPowerOfTenImpl<std::uint64_t, detail::kUint64Digits>;
constexpr std::array<std::uint64_t, detail::kInt64Digits> kPowerOfTen = detail::buildPowersOfTen();
static_assert(kPowerOfTen[0] == 1);
static_assert(kPowerOfTen[1] == 10);
static_assert(kPowerOfTen[10] == 10'000'000'000);
static_assert(
isPowerOfTen(kPowerOfTen.back()) && *logTen(kPowerOfTen.back()) == detail::kUint64Digits - 1);
isPowerOfTen(kPowerOfTen.back()) && *logTen(kPowerOfTen.back()) == detail::kInt64Digits - 1);
/** MantissaRange defines a range for the mantissa of a normalized Number.
*
@@ -126,37 +120,17 @@ struct MantissaRange final
{
using rep = std::uint64_t;
// NOLINTBEGIN(readability-enum-initial-value)
// The values don't matter, except for Large
enum class MantissaScale {
// Small can be removed when either featureSingleAssetVault or featureLendingProtocol are
// retired
Small,
// LargeLegacy can be removed when fixCleanup3_2_0 is retired
LargeLegacy,
// Large320 can be removed when fixCleanup3_3_0 is retired
Large320,
// If Large330 is ever the only remaining "Large*" entry, it can be renamed to just "Large".
Large330,
// Large is a de-facto alias for "the latest", and is only here for backward compatibility
// in the extremely unlikely case that a downstream project made use of it. Note that
// because the behavior changed, this may still be a breaking change.
Large = Large330,
Large,
};
// NOLINTEND(readability-enum-initial-value)
// This entire enum can be removed when the last relevant amendment is retired
enum class CuspRoundingFix : std::uint8_t {
// Disabled can be removed when fixCleanup3_2_0 is retired
Disabled = 0,
// Enabled320 can be removed when fixCleanup3_3_0 is retired
Enabled320 = 1,
// If we ever get to the point that there's only one entry, remove the entire enum
Enabled330 = 2,
// Enabled is a de-facto alias for "the latest", and is only here for backward compatibility
// in the extremely unlikely case that a downstream project made use of it. Note that
// because the behavior changed, this may still be a breaking change.
Enabled = Enabled330,
// This entire enum can be removed when fixCleanup3_2_0 is retired
enum class CuspRoundingFix : bool {
Disabled = false,
Enabled = true,
};
explicit constexpr MantissaRange(MantissaScale sc) : scale(sc)
@@ -167,27 +141,13 @@ struct MantissaRange final
int const log{getExponent(scale)};
rep const min{getMin(scale, log)};
rep const max{(min * 10) - 1};
CuspRoundingFix const cuspRoundingFix{isCuspFixEnabled(scale)};
CuspRoundingFix const cuspRoundingFixEnabled{isCuspFixEnabled(scale)};
static MantissaRange const&
getMantissaRange(MantissaScale scale);
static std::set<MantissaScale> const&
getAllScales()
{
static std::set<MantissaRange::MantissaScale> const kScales = {
MantissaRange::MantissaScale::Small,
MantissaRange::MantissaScale::LargeLegacy,
MantissaRange::MantissaScale::Large320,
MantissaRange::MantissaScale::Large330,
};
return kScales;
}
class Access
{
static constexpr MantissaRange const&
mantissaRange(MantissaScale scale);
friend Number;
};
getAllScales();
private:
static constexpr int
@@ -198,8 +158,7 @@ private:
case MantissaScale::Small:
return 15;
case MantissaScale::LargeLegacy:
case MantissaScale::Large320:
case MantissaScale::Large330:
case MantissaScale::Large:
return 18;
// LCOV_EXCL_START
default:
@@ -228,16 +187,17 @@ private:
case MantissaScale::Small:
case MantissaScale::LargeLegacy:
return CuspRoundingFix::Disabled;
case MantissaScale::Large320:
return CuspRoundingFix::Enabled320;
case MantissaScale::Large330:
return CuspRoundingFix::Enabled330;
case MantissaScale::Large:
return CuspRoundingFix::Enabled;
default:
// If called in a constexpr context, this throw assures that the build fails if an
// invalid scale is used.
throw std::runtime_error("Unknown mantissa scale"); // LCOV_EXCL_LINE
}
}
static std::unordered_map<MantissaScale, MantissaRange> const&
getRanges();
};
// Like std::integral, but only 64-bit integral types.
@@ -359,8 +319,6 @@ public:
static constexpr internalrep kMaxRep = std::numeric_limits<rep>::max();
static_assert(kMaxRep == 9'223'372'036'854'775'807);
static_assert(-kMaxRep == std::numeric_limits<rep>::min() + 1);
static constexpr internalrep kMaxRepUp = ((kMaxRep / 10) + 1) * 10;
static_assert(kMaxRepUp == 9'223'372'036'854'775'810ULL);
// May need to make unchecked private
struct Unchecked
@@ -583,13 +541,6 @@ public:
std::pair<T, int>
normalizeToRange() const;
// Safely convert rep (int64) mantissa to internalrep (uint64). If the rep
// is negative, returns the positive value. This takes a little extra work
// because converting std::numeric_limits<std::int64_t>::min() flirts with
// UB, and can vary across compilers.
static internalrep
externalToInternal(rep mantissa);
private:
static thread_local RoundingMode mode;
// The available ranges for mantissa
@@ -599,15 +550,9 @@ private:
// changing the values inside the range.
static thread_local std::reference_wrapper<MantissaRange const> kRange;
class Guard;
void
normalize(MantissaRange const& range);
// Guard has the fields that we need, as well as MantissaRange, so if we have a guard, use that
void
normalize(Guard const& guard);
/** Normalize Number components to an arbitrary range.
*
* min/maxMantissa are parameters because this function is used by both
@@ -622,7 +567,7 @@ private:
int& exponent,
internalrep const& minMantissa,
internalrep const& maxMantissa,
MantissaRange::CuspRoundingFix cuspRoundingFix);
MantissaRange::CuspRoundingFix cuspRoundingFixEnabled);
template <class T>
friend void
@@ -632,7 +577,7 @@ private:
int& exponent,
MantissaRange::rep const& minMantissa,
MantissaRange::rep const& maxMantissa,
MantissaRange::CuspRoundingFix cuspRoundingFix,
MantissaRange::CuspRoundingFix cuspRoundingFixEnabled,
bool dropped);
[[nodiscard]] bool
@@ -643,6 +588,15 @@ private:
// exponent could go out of range, so it will be checked.
[[nodiscard]] Number
shiftExponent(int exponentDelta) const;
// Safely convert rep (int64) mantissa to internalrep (uint64). If the rep
// is negative, returns the positive value. This takes a little extra work
// because converting std::numeric_limits<std::int64_t>::min() flirts with
// UB, and can vary across compilers.
static internalrep
externalToInternal(rep mantissa);
class Guard;
};
constexpr Number::Number(bool negative, internalrep mantissa, int exponent, Unchecked) noexcept
@@ -904,11 +858,21 @@ squelch(Number const& x, Number const& limit) noexcept
return x;
}
std::string
to_string(MantissaRange::MantissaScale const& scale);
std::string
to_string(Number::RoundingMode const& round);
inline std::string
to_string(MantissaRange::MantissaScale const& scale)
{
switch (scale)
{
case MantissaRange::MantissaScale::Small:
return "small";
case MantissaRange::MantissaScale::LargeLegacy:
return "largeLegacy";
case MantissaRange::MantissaScale::Large:
return "large";
default:
throw std::runtime_error("Bad scale");
}
}
class SaveNumberRoundMode
{

File diff suppressed because it is too large Load Diff

View File

@@ -39,30 +39,22 @@ setCurrentTransactionRules(std::optional<Rules> r)
// Push the appropriate setting, instead of having the class pull every time
// the value is needed. That could get expensive fast.
// Declare the range this way to keep clang-tidy from complaining
auto const range = [&r]() {
// If any new conditions with new amendments are added to "enableLargeNumbers", those
// amendments must also be added to useRulesGuards.
bool const enableLargeNumbers =
!r || (r->enabled(featureSingleAssetVault) || r->enabled(featureLendingProtocol));
// If enableLargeNumbers is true, then useRulesGuard must also return true.
// However, the reverse is not true. Other amendments can cause the rules guard to be used,
// even though large numbers are _not_ used.
XRPL_ASSERT(
!r || !enableLargeNumbers || useRulesGuards(*r),
"setCurrentTransactionRules : rule decisions match");
// If any new conditions with new amendments are added, those amendments must also be added to
// useRulesGuards.
bool const enableVaultNumbers =
!r || (r->enabled(featureSingleAssetVault) || r->enabled(featureLendingProtocol));
bool const enableCuspRoundingFix = !r || r->enabled(fixCleanup3_2_0);
XRPL_ASSERT(
!r || useRulesGuards(*r) == (enableCuspRoundingFix || enableVaultNumbers),
"setCurrentTransactionRules : rule decisions match");
if (enableLargeNumbers)
// Declare the range this way to keep clang-tidy from complaining
auto const range = [enableCuspRoundingFix, enableVaultNumbers]() {
if (enableVaultNumbers)
{
static_assert(
MantissaRange::MantissaScale::Large == MantissaRange::MantissaScale::Large330);
if (!r || r->enabled(fixCleanup3_3_0))
if (enableCuspRoundingFix)
{
return MantissaRange::MantissaScale::Large330;
}
if (r->enabled(fixCleanup3_2_0))
{
return MantissaRange::MantissaScale::Large320;
return MantissaRange::MantissaScale::Large;
}
return MantissaRange::MantissaScale::LargeLegacy;
}
@@ -77,14 +69,14 @@ bool
useRulesGuards(Rules const& rules)
{
// The list of amendments used here - to decide whether to create a RulesGuard - must be a
// superset of the list used to determine "enableLargeNumbers" in setCurrentTransactionRules.
// Additional amendments can be added if desired.
// superset of the list used to figure out which mantissa scale to use in
// setCurrentTransactionRules. Additional amendments can be added if desired.
//
// As soon as any one of these amendments is retired, this whole function can be removed, along
// with createGuards, and any other callers, and the first set of guards can be created directly
// at the call site, without using optional.
return rules.enabled(featureSingleAssetVault) || rules.enabled(featureLendingProtocol) ||
rules.enabled(fixCleanup3_2_0) || rules.enabled(fixCleanup3_3_0);
return rules.enabled(fixCleanup3_2_0) || rules.enabled(featureSingleAssetVault) ||
rules.enabled(featureLendingProtocol);
}
void
@@ -95,8 +87,7 @@ createGuards(
{
if (useRulesGuards(rules))
{
// raii classes for the current ledger rules. If the rules are set, the MantissaRange will
// be updated, too.
// raii classes for the current ledger rules.
rulesGuard.emplace(rules);
}
else

View File

@@ -88,6 +88,7 @@ STNumber::add(Serializer& s) const
}
else
{
#if !NDEBUG
// There are circumstances where an already-rounded Number is
// serialized without being touched by a transactor, and thus
// without an asset. We can't know if it's rounded, because it could
@@ -95,9 +96,11 @@ STNumber::add(Serializer& s) const
// Json. Regardless, the only time we should be serializing an
// STNumber is when the scale is large.
XRPL_ASSERT_PARTS(
Number::getMantissaScale() != MantissaRange::MantissaScale::Small,
Number::getMantissaScale() == MantissaRange::MantissaScale::LargeLegacy ||
Number::getMantissaScale() == MantissaRange::MantissaScale::Large,
"xrpl::STNumber::add",
"STNumber only used with large mantissa scale");
#endif
}
}
@@ -255,47 +258,8 @@ numberFromJson(SField const& field, json::Value const& value)
Throw<std::runtime_error>("not a number");
}
Number const num{parts.negative, parts.mantissa, parts.exponent, Number::Normalized{}};
// Canonicalize "parts" and "num" with each other by getting rid of trailing 0s until either the
// exponents match, or there are no more 0s. If the two results don't match exactly, then the
// value has been rounded one way or another, and should not be used, because it may lead to an
// unexpected result. canonicalizeParts is not to be confused with Number::canonicalize, because
// they're have completely different goals.
auto canonicalizeParts = [](NumberParts p, int otherExponent) {
if (p.mantissa == 0)
return NumberParts{};
while (p.exponent < otherExponent && p.mantissa % 10 == 0)
{
p.mantissa /= 10;
++p.exponent;
}
return p;
};
auto const numberMantissa = num.mantissa();
auto const numberExponent = num.exponent();
auto const canonicalParts = canonicalizeParts(parts, numberExponent);
auto const canonicalNum = canonicalizeParts(
NumberParts{
.mantissa = Number::externalToInternal(numberMantissa),
.exponent = numberExponent,
.negative = numberMantissa < 0,
},
canonicalParts.exponent);
if (canonicalParts.mantissa != canonicalNum.mantissa ||
canonicalParts.exponent != canonicalNum.exponent ||
canonicalParts.negative != canonicalNum.negative)
{
Throw<std::runtime_error>("number cannot be represented");
}
return STNumber{field, num};
return STNumber{
field, Number{parts.negative, parts.mantissa, parts.exponent, Number::Normalized{}}};
}
} // namespace xrpl

View File

@@ -5059,10 +5059,11 @@ class Invariants_test : public beast::unit_test::Suite
std::vector<ValidVault::DeltaInfo> values;
};
for (auto const mantissaScale : MantissaRange::getAllScales())
for (auto const mantissaScale : {
MantissaRange::MantissaScale::LargeLegacy,
MantissaRange::MantissaScale::Large,
})
{
if (mantissaScale == MantissaRange::MantissaScale::Small)
continue;
NumberMantissaScaleGuard const g{mantissaScale};
auto makeDelta = [&vaultAsset](Number const& n) -> ValidVault::DeltaInfo {

View File

@@ -52,7 +52,6 @@
#include <array>
#include <cstdint>
#include <exception>
#include <functional>
#include <memory>
#include <optional>
@@ -1437,77 +1436,18 @@ class LoanBroker_test : public beast::unit_test::Suite
env(tx2, Ter(temINVALID));
}
env.setParseFailureExpected(true);
try
{
tx2[sfDebtMaximum] = "9223372036854775808";
auto const dm = power(2, 63) - 1;
BEAST_EXPECTS(dm > kMaxMpTokenAmount, to_string(dm));
tx2[sfDebtMaximum] = dm;
env(tx2, Ter(temINVALID));
// should throw in parser
fail();
}
catch (std::exception const& e)
{
BEAST_EXPECT(
std::string(e.what()) ==
"invalidParamsField 'tx_json.DebtMaximum' has invalid data.");
}
env.setParseFailureExpected(false);
if (Number::getMantissaScale() >= MantissaRange::MantissaScale::Large330)
{
// For the Large330 scale, 2^63 rounds _down_ to Number::kMaxRep
{
auto const dm = power(2, 63);
BEAST_EXPECTS(dm == kMaxMpTokenAmount, to_string(dm));
tx2[sfDebtMaximum] = dm;
env(tx2, Ter(tesSUCCESS));
}
{
auto const dm = power(2, 63) + Number{1, -1};
BEAST_EXPECTS(dm == kMaxMpTokenAmount, to_string(dm));
tx2[sfDebtMaximum] = dm;
env(tx2, Ter(tesSUCCESS));
}
{
auto const dm = power(2, 63) - 1;
BEAST_EXPECTS(dm < kMaxMpTokenAmount, to_string(dm));
tx2[sfDebtMaximum] = dm;
env(tx2, Ter(tesSUCCESS));
}
{
auto const dm = power(2, 63) - 3;
BEAST_EXPECTS(dm < kMaxMpTokenAmount, to_string(dm));
tx2[sfDebtMaximum] = dm;
env(tx2, Ter(tesSUCCESS));
}
{
auto const dm = power(2, 63) + 3;
BEAST_EXPECTS(dm > kMaxMpTokenAmount, to_string(dm));
tx2[sfDebtMaximum] = dm;
env(tx2, Ter(temINVALID));
}
}
else
{
// For other scales, 2^63 rounds _up_ to Number::kMaxRepUp. Subtracting 1 rounds up
// again.
{
auto const dm = power(2, 63) - 1;
BEAST_EXPECTS(dm > kMaxMpTokenAmount, to_string(dm));
tx2[sfDebtMaximum] = dm;
env(tx2, Ter(temINVALID));
}
{
auto const dm = power(2, 63) - 3;
BEAST_EXPECTS(dm == kMaxMpTokenAmount, to_string(dm));
tx2[sfDebtMaximum] = dm;
env(tx2, Ter(tesSUCCESS));
}
auto const dm = power(2, 63) - 3;
BEAST_EXPECTS(dm == kMaxMpTokenAmount, to_string(dm));
tx2[sfDebtMaximum] = dm;
env(tx2, Ter(tesSUCCESS));
}
{

View File

@@ -60,7 +60,6 @@
#include <chrono>
#include <cstdint>
#include <exception>
#include <functional>
#include <limits>
#include <memory>
@@ -5253,15 +5252,11 @@ class Vault_test : public beast::unit_test::Suite
auto const maxInt64 = std::to_string(std::numeric_limits<std::int64_t>::max());
BEAST_EXPECT(maxInt64 == "9223372036854775807");
// Naming things is hard
auto const maxInt64Plus1 = std::to_string(
static_cast<std::uint64_t>(std::numeric_limits<std::int64_t>::max()) + 1);
BEAST_EXPECT(maxInt64Plus1 == "9223372036854775808");
// Naming things is hard
auto const maxInt64Plus2 = std::to_string(
static_cast<std::uint64_t>(std::numeric_limits<std::int64_t>::max()) + 2);
BEAST_EXPECT(maxInt64Plus2 == "9223372036854775809");
auto const initialXRP = to_string(kInitialXrp);
BEAST_EXPECT(initialXRP == "100000000000000000");
@@ -5288,58 +5283,25 @@ class Vault_test : public beast::unit_test::Suite
env(tx);
env.close();
// There are several parse failures expected in this function, so just disable it once.
env.setParseFailureExpected(true);
try
{
tx[sfAssetsMaximum] = maxInt64Plus1;
env(tx, Ter(tefEXCEPTION));
env.close();
// should throw in parser
fail();
}
catch (std::exception const& e)
{
BEAST_EXPECT(
std::string(e.what()) ==
"invalidParamsField 'tx_json.AssetsMaximum' has invalid data.");
}
try
{
tx[sfAssetsMaximum] = maxInt64Plus2;
env(tx, Ter(tefEXCEPTION));
// should throw in parser
fail();
}
catch (std::exception const& e)
{
BEAST_EXPECT(
std::string(e.what()) ==
"invalidParamsField 'tx_json.AssetsMaximum' has invalid data.");
}
tx[sfAssetsMaximum] = maxInt64Plus1;
env(tx, Ter(tefEXCEPTION));
env.close();
// This value will be rounded
auto const insertAt = maxInt64Plus1.size() - 3;
auto const decimalTest = maxInt64Plus1.substr(0, insertAt) + "." +
maxInt64Plus1.substr(insertAt); // (max int64+1) / 1000
BEAST_EXPECT(decimalTest == "9223372036854775.808");
tx[sfAssetsMaximum] = decimalTest;
auto const newKeylet = keylet::vault(owner.id(), env.seq(owner));
try
{
auto const insertAt = maxInt64Plus2.size() - 3;
auto const decimalTest = maxInt64Plus2.substr(0, insertAt) + "." +
maxInt64Plus2.substr(insertAt); // (max int64+2) / 1000
BEAST_EXPECT(decimalTest == "9223372036854775.809");
tx[sfAssetsMaximum] = decimalTest;
env(tx);
// should throw in parser
fail();
}
catch (std::exception const& e)
{
BEAST_EXPECT(
std::string(e.what()) ==
"invalidParamsField 'tx_json.AssetsMaximum' has invalid data.");
}
env(tx);
env.close();
auto const vaultSle = env.le(newKeylet);
BEAST_EXPECT(!vaultSle);
if (!BEAST_EXPECT(vaultSle))
return;
BEAST_EXPECT(vaultSle->at(sfAssetsMaximum) == 9223372036854776);
}
{
@@ -5373,41 +5335,25 @@ class Vault_test : public beast::unit_test::Suite
env(tx);
env.close();
try
{
tx[sfAssetsMaximum] = maxInt64Plus2;
env(tx, Ter(tefEXCEPTION));
// should throw in parser
fail();
}
catch (std::exception const& e)
{
BEAST_EXPECT(
std::string(e.what()) ==
"invalidParamsField 'tx_json.AssetsMaximum' has invalid data.");
}
tx[sfAssetsMaximum] = maxInt64Plus1;
env(tx, Ter(tefEXCEPTION));
env.close();
// This value will be rounded
auto const insertAt = maxInt64Plus1.size() - 1;
auto const decimalTest = maxInt64Plus1.substr(0, insertAt) + "." +
maxInt64Plus1.substr(insertAt); // (max int64+1) / 10
BEAST_EXPECT(decimalTest == "922337203685477580.8");
tx[sfAssetsMaximum] = decimalTest;
auto const newKeylet = keylet::vault(owner.id(), env.seq(owner));
try
{
auto const insertAt = maxInt64Plus2.size() - 1;
auto const decimalTest = maxInt64Plus2.substr(0, insertAt) + "." +
maxInt64Plus2.substr(insertAt); // (max int64+2) / 10
BEAST_EXPECT(decimalTest == "922337203685477580.9");
tx[sfAssetsMaximum] = decimalTest;
env(tx);
// should throw in parser
fail();
}
catch (std::exception const& e)
{
BEAST_EXPECT(
std::string(e.what()) ==
"invalidParamsField 'tx_json.AssetsMaximum' has invalid data.");
}
env(tx);
env.close();
auto const vaultSle = env.le(newKeylet);
BEAST_EXPECT(!vaultSle);
if (!BEAST_EXPECT(vaultSle))
return;
BEAST_EXPECT(vaultSle->at(sfAssetsMaximum) == 922337203685477581);
}
{
@@ -5434,22 +5380,9 @@ class Vault_test : public beast::unit_test::Suite
env(tx);
env.close();
// Since several tests are expected to have parser failures, leave this flag set for the
// remainder of this function.
env.setParseFailureExpected(true);
try
{
tx[sfAssetsMaximum] = maxInt64Plus2;
env(tx);
// should throw in parser
fail();
}
catch (std::exception const& e)
{
BEAST_EXPECT(
std::string(e.what()) ==
"invalidParamsField 'tx_json.AssetsMaximum' has invalid data.");
}
tx[sfAssetsMaximum] = maxInt64Plus1;
env(tx);
env.close();
tx[sfAssetsMaximum] = "1000000000000000e80";
env.close();
@@ -5459,27 +5392,22 @@ class Vault_test : public beast::unit_test::Suite
// These values will be rounded to 15 significant digits
{
auto const insertAt = maxInt64Plus1.size() - 1;
auto const decimalTest = maxInt64Plus1.substr(0, insertAt) + "." +
maxInt64Plus1.substr(insertAt); // (max int64+1) / 10
BEAST_EXPECT(decimalTest == "922337203685477580.8");
tx[sfAssetsMaximum] = decimalTest;
auto const newKeylet = keylet::vault(owner.id(), env.seq(owner));
try
{
auto const insertAt = maxInt64Plus2.size() - 1;
auto const decimalTest = maxInt64Plus2.substr(0, insertAt) + "." +
maxInt64Plus2.substr(insertAt); // (max int64+2) / 10
BEAST_EXPECT(decimalTest == "922337203685477580.9");
tx[sfAssetsMaximum] = decimalTest;
env(tx);
// should throw in parser
fail();
}
catch (std::exception const& e)
{
BEAST_EXPECT(
std::string(e.what()) ==
"invalidParamsField 'tx_json.AssetsMaximum' has invalid data.");
}
env(tx);
env.close();
auto const vaultSle = env.le(newKeylet);
BEAST_EXPECT(!vaultSle);
if (!BEAST_EXPECT(vaultSle))
return;
BEAST_EXPECT(
(vaultSle->at(sfAssetsMaximum) ==
Number{9223372036854776, 2, Number::Normalized{}}));
}
{
tx[sfAssetsMaximum] = "9223372036854775807e40"; // max int64 * 10^40

File diff suppressed because it is too large Load Diff

View File

@@ -9,7 +9,6 @@
#include <xrpl/protocol/Serializer.h>
#include <cstdint>
#include <exception>
#include <initializer_list>
#include <limits>
#include <stdexcept>
@@ -102,39 +101,6 @@ struct STNumber_test : public beast::unit_test::Suite
BEAST_EXPECT(numberFromJson(sfNumber, "-0.000e6") == STNumber(sfNumber, 0));
{
auto const parseNumber = [](std::string const& boundary) {
return numberFromJson(sfNumber, boundary);
};
auto const expectParseThrows = [this, &parseNumber](std::string const& boundary) {
try
{
auto const bad = parseNumber(boundary);
fail();
}
catch (std::exception const& e)
{
BEAST_EXPECT(std::string(e.what()) == "number cannot be represented");
}
};
// Small rejects this; large scales parse it as 9223372036854775800e-1.
auto constexpr positiveBoundary = "922337203685477580";
auto constexpr negativeBoundary = "-922337203685477580";
if (Number::getMantissaScale() == MantissaRange::MantissaScale::Small)
{
expectParseThrows(positiveBoundary);
expectParseThrows(negativeBoundary);
}
else
{
BEAST_EXPECT(
parseNumber(positiveBoundary) ==
STNumber(sfNumber, Number{922'337'203'685'477'580, 0}));
BEAST_EXPECT(
parseNumber(negativeBoundary) ==
STNumber(sfNumber, Number{-922'337'203'685'477'580, 0}));
}
NumberRoundModeGuard const mg(Number::RoundingMode::TowardsZero);
// maxint64 9,223,372,036,854,775,807
auto const maxInt = std::to_string(std::numeric_limits<std::int64_t>::max());
@@ -142,19 +108,23 @@ struct STNumber_test : public beast::unit_test::Suite
auto const minInt = std::to_string(std::numeric_limits<std::int64_t>::min());
if (Number::getMantissaScale() == MantissaRange::MantissaScale::Small)
{
// min/maxInt can't be exactly represented with the small mantissa, so they
// don't parse, and are expected to throw.
expectParseThrows(maxInt);
expectParseThrows(minInt);
BEAST_EXPECT(
numberFromJson(sfNumber, maxInt) ==
STNumber(sfNumber, Number{9'223'372'036'854'775, 3}));
BEAST_EXPECT(
numberFromJson(sfNumber, minInt) ==
STNumber(sfNumber, Number{-9'223'372'036'854'775, 3}));
}
else
{
// with large mantissas, maxint is fine
BEAST_EXPECT(
parseNumber(maxInt) ==
numberFromJson(sfNumber, maxInt) ==
STNumber(sfNumber, Number{9'223'372'036'854'775'807, 0}));
// but minint's mantissa is > kMaxRep, and so rounds, and thus can't be parsed
expectParseThrows(minInt);
BEAST_EXPECT(
numberFromJson(sfNumber, minInt) ==
STNumber(
sfNumber,
Number{true, 9'223'372'036'854'775'808ULL, 0, Number::Normalized{}}));
}
}