mirror of
https://github.com/XRPLF/rippled.git
synced 2026-07-01 03:22:19 +00:00
Compare commits
6 Commits
ximinez/nu
...
mathbunnyr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
909836ffc2 | ||
|
|
2af67d8f6f | ||
|
|
9171fef02c | ||
|
|
273c1862ac | ||
|
|
49e9c2acb0 | ||
|
|
cabafec58d |
@@ -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"]
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -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
@@ -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{}}));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user