Compare commits

...

23 Commits

Author SHA1 Message Date
Ed Hennis
e45d847558 Merge branch 'develop' into ximinez/lending-number-simple 2025-11-18 22:51:51 -05:00
Ed Hennis
546bfa89d8 For now, skip the larger mantissas in AMM transactions and tests 2025-11-16 20:59:17 -05:00
Ed Hennis
470c9c3936 Fix root2, and add tests for it 2025-11-16 00:18:51 -05:00
Ed Hennis
9f50cc033e Merge branch 'develop' into ximinez/lending-number-simple 2025-11-15 03:13:10 -05:00
Ed Hennis
a5f43fb59b Fix some build errors - unused variables, large constants 2025-11-15 02:59:56 -05:00
Ed Hennis
3451d15e12 Step 3: Automatically switch precision in the transaction engine
- Default Number outside of transaction processing to be "large" so RPC
  will work.
2025-11-15 02:55:48 -05:00
Ed Hennis
595a5ee220 Step 2.5cont: Make some updates to STNumber
- Added test cases min int64.
- Updated numberFromJson range checking to use the larger range
  available from Number.
2025-11-15 00:50:59 -05:00
Ed Hennis
c9ad49faf3 Step 2.5: Run the STNumber tests using both mantissa sizes
- Nothing really needed to be changed in the tests, but I added a couple
  of test cases for the min and max int64.
2025-11-14 19:41:26 -05:00
Ed Hennis
857eaffa55 refactor: Move the mantissa_scale enum into the MantissaRange struct 2025-11-14 19:06:44 -05:00
Ed Hennis
93109918ed Fix Number::power(), and a bunch of Number unit tests 2025-11-14 18:51:01 -05:00
Ed Hennis
fbcd4f33eb Add more edge case Number tests 2025-11-14 15:10:10 -05:00
Ed Hennis
6f1fe5047b Step 2 cont. Refactor the to_string(Number) test 2025-11-14 12:40:54 -05:00
Ed Hennis
4cf22b50de fixup! Step 2: Add the ability to change the mantissa range
- Fix cross-compiler build issues
2025-11-14 11:33:09 -05:00
Ed Hennis
606e3ec0b7 Step 2: Add the ability to change the mantissa range
- Update tests. Unfinished.
- TODO: Finish Number tests. Use both modes for STNumber tests. Move
  mantissa_scale into MantissaRange.
2025-11-14 02:34:56 -05:00
Ed Hennis
2eca3dca89 Merge remote-tracking branch 'upstream/develop' into ximinez/lending-number-simple
* upstream/develop:
  chore: Clean up incorrect comments (6031)
  refactor: Retire MultiSignReserve and ExpandedSignerList amendments (5981)
2025-11-13 11:01:22 -05:00
Ed Hennis
2881aade2c Fix Vault unit tests for default fields with value 0.
- Field will be absent in RPC results instead of returning 0.
2025-11-13 01:53:36 -05:00
Ed Hennis
4abb6d9dfe Turns out there's no to_string(__int128_t) 2025-11-13 00:51:37 -05:00
Ed Hennis
7cd48a7713 fixup! Continue with Step 1 2025-11-12 22:58:06 -05:00
Ed Hennis
d2d403da90 Continue with Step 1
- Track down and fix edge cases.
- Some refactoring and renaming for clarity and simplicity
2025-11-12 20:32:47 -05:00
Ed Hennis
343824332c Merge branch 'develop' into ximinez/lending-number-simple 2025-11-12 14:17:35 -05:00
Ed Hennis
a32b5723e5 fixup! fixup! Step 1: Convert Number to use 128-bit numbers internally 2025-11-12 12:16:18 -05:00
Ed Hennis
3048f55270 fixup! Step 1: Convert Number to use 128-bit numbers internally 2025-11-12 10:14:05 -05:00
Ed Hennis
d030fdaa2b Step 1: Convert Number to use 128-bit numbers internally
- Update the conversion points between Number and *Amount & STNumber.
- Tests probably don't pass.
2025-11-12 00:31:47 -05:00
25 changed files with 1710 additions and 418 deletions

View File

@@ -1,8 +1,13 @@
#ifndef XRPL_BASICS_NUMBER_H_INCLUDED
#define XRPL_BASICS_NUMBER_H_INCLUDED
#ifdef _MSC_VER
#include <boost/multiprecision/cpp_int.hpp>
#endif
#include <cstdint>
#include <limits>
#include <optional>
#include <ostream>
#include <string>
@@ -13,21 +18,71 @@ class Number;
std::string
to_string(Number const& amount);
template <typename T>
constexpr std::optional<int>
logTen(T value)
{
int log = 0;
while (value >= 10 && value % 10 == 0)
{
value /= 10;
++log;
}
if (value == 1)
return log;
return std::nullopt;
}
template <typename T>
constexpr bool
isPowerOfTen(T value)
{
return logTen(value).has_value();
}
#ifdef _MSC_VER
using numberuint128 = boost::multiprecision::uint128_t;
using numberint128 = boost::multiprecision::int128_t;
#else // !defined(_MSC_VER)
using numberuint128 = __uint128_t;
using numberint128 = __int128_t;
#endif // !defined(_MSC_VER)
struct MantissaRange
{
using internalrep = numberint128;
enum mantissa_scale { small, large };
explicit constexpr MantissaRange(mantissa_scale scale_, internalrep min_)
: min(min_)
, max(min_ * 10 - 1)
, log(logTen(min).value_or(-1))
, scale(scale_)
{
}
internalrep min;
internalrep max;
int log;
mantissa_scale scale;
};
class Number
{
using uint128_t = numberuint128;
using int128_t = numberint128;
using rep = std::int64_t;
rep mantissa_{0};
using internalrep = MantissaRange::internalrep;
internalrep mantissa_{0};
int exponent_{std::numeric_limits<int>::lowest()};
public:
// The range for the mantissa when normalized
constexpr static std::int64_t minMantissa = 1'000'000'000'000'000LL;
constexpr static std::int64_t maxMantissa = 9'999'999'999'999'999LL;
// The range for the exponent when normalized
constexpr static int minExponent = -32768;
constexpr static int maxExponent = 32768;
// May need to make unchecked private
struct unchecked
{
explicit unchecked() = default;
@@ -36,10 +91,13 @@ public:
explicit constexpr Number() = default;
Number(rep mantissa);
explicit Number(rep mantissa, int exponent);
explicit constexpr Number(rep mantissa, int exponent, unchecked) noexcept;
explicit Number(internalrep mantissa, int exponent);
explicit constexpr Number(
internalrep mantissa,
int exponent,
unchecked) noexcept;
constexpr rep
constexpr internalrep
mantissa() const noexcept;
constexpr int
exponent() const noexcept;
@@ -141,7 +199,7 @@ public:
while (ret.exponent_ < 0 && ret.mantissa_ != 0)
{
ret.exponent_ += 1;
ret.mantissa_ /= rep(10);
ret.mantissa_ /= internalrep(10);
}
// We are guaranteed that normalize() will never throw an exception
// because exponent is either negative or zero at this point.
@@ -181,23 +239,96 @@ public:
static rounding_mode
setround(rounding_mode mode);
static MantissaRange::mantissa_scale
getMantissaScale();
static void
setMantissaScale(MantissaRange::mantissa_scale scale);
inline static internalrep
minMantissa()
{
return range_.get().min;
}
inline static internalrep
maxMantissa()
{
return range_.get().max;
}
inline static int
mantissaLog()
{
return range_.get().log;
}
/// oneSmall is needed because the ranges are private
constexpr static Number
oneSmall();
/// oneLarge is needed because the ranges are private
constexpr static Number
oneLarge();
// And one is needed because it needs to choose between oneSmall and
// oneLarge based on the current range
static Number
one();
template <class T>
[[nodiscard]]
std::pair<T, int>
normalizeToRange(T minMantissa, T maxMantissa) const;
private:
static thread_local rounding_mode mode_;
// The available ranges for mantissa
constexpr static MantissaRange smallRange{
MantissaRange::small,
1'000'000'000'000'000LL};
static_assert(isPowerOfTen(smallRange.min));
static_assert(smallRange.max == 9'999'999'999'999'999LL);
static_assert(smallRange.log == 15);
// maxint64 9,223,372,036,854,775,808
constexpr static MantissaRange largeRange{
MantissaRange::large,
1'000'000'000'000'000'000LL};
static_assert(isPowerOfTen(largeRange.min));
static_assert(largeRange.max == internalrep(9'999'999'999'999'999'999ULL));
static_assert(largeRange.log == 18);
static_assert(largeRange.min < std::numeric_limits<std::int64_t>::max());
static_assert(largeRange.max > std::numeric_limits<std::int64_t>::max());
// The range for the mantissa when normalized.
// Use reference_wrapper to avoid making copies, and prevent accidentally
// changing the values inside the range.
static thread_local std::reference_wrapper<MantissaRange const> range_;
void
normalize();
static void
normalize(
internalrep& mantissa,
int& exponent,
internalrep const& minMantissa,
internalrep const& maxMantissa);
constexpr bool
isnormal() const noexcept;
class Guard;
};
inline constexpr Number::Number(rep mantissa, int exponent, unchecked) noexcept
inline constexpr Number::Number(
internalrep mantissa,
int exponent,
unchecked) noexcept
: mantissa_{mantissa}, exponent_{exponent}
{
}
inline Number::Number(rep mantissa, int exponent)
inline Number::Number(internalrep mantissa, int exponent)
: mantissa_{mantissa}, exponent_{exponent}
{
normalize();
@@ -207,7 +338,7 @@ inline Number::Number(rep mantissa) : Number{mantissa, 0}
{
}
inline constexpr Number::rep
inline constexpr Number::internalrep
Number::mantissa() const noexcept
{
return mantissa_;
@@ -236,7 +367,7 @@ Number::operator-() const noexcept
inline Number&
Number::operator++()
{
*this += Number{1000000000000000, -15, unchecked{}};
*this += one();
return *this;
}
@@ -251,7 +382,7 @@ Number::operator++(int)
inline Number&
Number::operator--()
{
*this -= Number{1000000000000000, -15, unchecked{}};
*this -= one();
return *this;
}
@@ -304,29 +435,41 @@ operator/(Number const& x, Number const& y)
inline constexpr Number
Number::min() noexcept
{
return Number{minMantissa, minExponent, unchecked{}};
return Number{range_.get().min, minExponent, unchecked{}};
}
inline constexpr Number
Number::max() noexcept
{
return Number{maxMantissa, maxExponent, unchecked{}};
return Number{range_.get().max, maxExponent, unchecked{}};
}
inline constexpr Number
Number::lowest() noexcept
{
return -Number{maxMantissa, maxExponent, unchecked{}};
return -Number{range_.get().max, maxExponent, unchecked{}};
}
inline constexpr bool
Number::isnormal() const noexcept
{
MantissaRange const& range = range_;
auto const abs_m = mantissa_ < 0 ? -mantissa_ : mantissa_;
return minMantissa <= abs_m && abs_m <= maxMantissa &&
return range.min <= abs_m && abs_m <= range.max &&
minExponent <= exponent_ && exponent_ <= maxExponent;
}
template <class T>
std::pair<T, int>
Number::normalizeToRange(T minMantissa, T maxMantissa) const
{
internalrep mantissa = mantissa_;
int exponent = exponent_;
Number::normalize(mantissa, exponent, minMantissa, maxMantissa);
return std::make_pair(static_cast<T>(mantissa), exponent);
}
inline constexpr Number
abs(Number x) noexcept
{
@@ -366,6 +509,20 @@ squelch(Number const& x, Number const& limit) noexcept
return x;
}
inline std::string
to_string(MantissaRange::mantissa_scale const& scale)
{
switch (scale)
{
case MantissaRange::small:
return "small";
case MantissaRange::large:
return "large";
default:
throw std::runtime_error("Bad scale");
}
}
class saveNumberRoundMode
{
Number::rounding_mode mode_;
@@ -404,6 +561,33 @@ public:
operator=(NumberRoundModeGuard const&) = delete;
};
// Sets the new scale and restores the old scale when it leaves scope. Since
// Number doesn't have that facility, we'll build it here.
//
// This class may only end up needed in tests
class NumberMantissaScaleGuard
{
MantissaRange::mantissa_scale saved_;
public:
explicit NumberMantissaScaleGuard(
MantissaRange::mantissa_scale scale) noexcept
: saved_{Number::getMantissaScale()}
{
Number::setMantissaScale(scale);
}
~NumberMantissaScaleGuard()
{
Number::setMantissaScale(saved_);
}
NumberMantissaScaleGuard(NumberMantissaScaleGuard const&) = delete;
NumberMantissaScaleGuard&
operator=(NumberMantissaScaleGuard const&) = delete;
};
} // namespace ripple
#endif // XRPL_BASICS_NUMBER_H_INCLUDED

View File

@@ -122,7 +122,7 @@ toAmount(
{
if (isXRP(issue))
return STAmount(issue, static_cast<std::int64_t>(n));
return STAmount(issue, n.mantissa(), n.exponent());
return STAmount(issue, n);
}
else
{

View File

@@ -84,6 +84,19 @@ public:
return holds<Issue>() && get<Issue>().native();
}
bool
integral() const
{
return std::visit(
[&]<ValidIssueType TIss>(TIss const& issue) {
if constexpr (std::is_same_v<TIss, Issue>)
return issue.native();
if constexpr (std::is_same_v<TIss, MPTIssue>)
return true;
},
issue_);
}
friend constexpr bool
operator==(Asset const& lhs, Asset const& rhs);

View File

@@ -26,8 +26,10 @@ class IOUAmount : private boost::totally_ordered<IOUAmount>,
private boost::additive<IOUAmount>
{
private:
std::int64_t mantissa_;
int exponent_;
using mantissa_type = std::int64_t;
using exponent_type = int;
mantissa_type mantissa_;
exponent_type exponent_;
/** Adjusts the mantissa and exponent to the proper range.
@@ -38,11 +40,19 @@ private:
void
normalize();
IOUAmount(std::pair<mantissa_type, exponent_type> parts)
: IOUAmount(parts.first, parts.second)
{
}
static std::pair<mantissa_type, exponent_type>
scaleNumber(Number const& number);
public:
IOUAmount() = default;
explicit IOUAmount(Number const& other);
IOUAmount(beast::Zero);
IOUAmount(std::int64_t mantissa, int exponent);
IOUAmount(mantissa_type mantissa, exponent_type exponent);
IOUAmount& operator=(beast::Zero);
@@ -71,10 +81,10 @@ public:
int
signum() const noexcept;
int
exponent_type
exponent() const noexcept;
std::int64_t
mantissa_type
mantissa() const noexcept;
static IOUAmount
@@ -92,7 +102,7 @@ inline IOUAmount::IOUAmount(beast::Zero)
*this = beast::zero;
}
inline IOUAmount::IOUAmount(std::int64_t mantissa, int exponent)
inline IOUAmount::IOUAmount(mantissa_type mantissa, exponent_type exponent)
: mantissa_(mantissa), exponent_(exponent)
{
normalize();
@@ -149,13 +159,13 @@ IOUAmount::signum() const noexcept
return (mantissa_ < 0) ? -1 : (mantissa_ ? 1 : 0);
}
inline int
inline IOUAmount::exponent_type
IOUAmount::exponent() const noexcept
{
return exponent_;
}
inline std::int64_t
inline IOUAmount::mantissa_type
IOUAmount::mantissa() const noexcept
{
return mantissa_;

View File

@@ -37,6 +37,9 @@ public:
bool
native() const;
bool
integral() const;
friend constexpr std::weak_ordering
operator<=>(Issue const& lhs, Issue const& rhs);
};

View File

@@ -46,6 +46,12 @@ public:
{
return false;
}
bool
integral() const
{
return true;
}
};
constexpr bool

View File

@@ -47,9 +47,11 @@ public:
static int const cMaxOffset = 80;
// Maximum native value supported by the code
static std::uint64_t const cMinValue = 1000000000000000ull;
static std::uint64_t const cMaxValue = 9999999999999999ull;
static std::uint64_t const cMaxNative = 9000000000000000000ull;
constexpr static std::uint64_t cMinValue = 1'000'000'000'000'000ull;
static_assert(isPowerOfTen(cMinValue));
constexpr static std::uint64_t cMaxValue = cMinValue * 10 - 1;
static_assert(cMaxValue == 9'999'999'999'999'999ull);
static std::uint64_t const cMaxNative = 9'000'000'000'000'000'000ull;
// Max native value on network.
static std::uint64_t const cMaxNativeN = 100000000000000000ull;
@@ -136,7 +138,7 @@ public:
template <AssetType A>
STAmount(A const& asset, Number const& number)
: STAmount(asset, number.mantissa(), number.exponent())
: STAmount(asset, scaleNumber(asset, number))
{
}
@@ -155,6 +157,9 @@ public:
int
exponent() const noexcept;
bool
integral() const noexcept;
bool
native() const noexcept;
@@ -277,6 +282,22 @@ public:
mpt() const;
private:
template <AssetType A>
STAmount(
A const& asset,
std::tuple<mantissa_type, exponent_type, bool> parts)
: STAmount(
asset,
std::get<mantissa_type>(parts),
std::get<exponent_type>(parts),
std::get<bool>(parts))
{
}
template <AssetType A>
static std::tuple<mantissa_type, exponent_type, bool>
scaleNumber(A const& asset, Number const& number);
static std::unique_ptr<STAmount>
construct(SerialIter&, SField const& name);
@@ -340,10 +361,19 @@ STAmount::STAmount(
, mIsNegative(negative)
{
// mValue is uint64, but needs to fit in the range of int64
XRPL_ASSERT(
mValue <= std::numeric_limits<std::int64_t>::max(),
"ripple::STAmount::STAmount(SField, A, std::uint64_t, int, bool) : "
"maximum mantissa input");
if (Number::getMantissaScale() == MantissaRange::small)
{
XRPL_ASSERT(
mValue <= std::numeric_limits<std::int64_t>::max(),
"ripple::STAmount::STAmount(SField, A, std::uint64_t, int, bool) : "
"maximum mantissa input");
}
else
{
if (integral() && mValue > std::numeric_limits<std::int64_t>::max())
throw std::overflow_error(
"STAmount mantissa is too large " + std::to_string(mantissa));
}
canonicalize();
}
@@ -435,6 +465,12 @@ STAmount::exponent() const noexcept
return mOffset;
}
inline bool
STAmount::integral() const noexcept
{
return mAsset.integral();
}
inline bool
STAmount::native() const noexcept
{
@@ -531,12 +567,29 @@ STAmount::operator=(XRPAmount const& amount)
return *this;
}
template <AssetType A>
inline std::tuple<STAmount::mantissa_type, STAmount::exponent_type, bool>
STAmount::scaleNumber(A const& asset, Number const& number)
{
bool const negative = number.mantissa() < 0;
Number const working{negative ? -number : number};
if (asset.integral())
{
return std::make_tuple(std::int64_t(working), 0, negative);
}
else
{
auto const [mantissa, exponent] =
working.normalizeToRange(cMinValue, cMaxValue);
return std::make_tuple(mantissa, exponent, negative);
}
}
inline STAmount&
STAmount::operator=(Number const& number)
{
mIsNegative = number.mantissa() < 0;
mValue = mIsNegative ? -number.mantissa() : number.mantissa();
mOffset = number.exponent();
std::tie(mValue, mOffset, mIsNegative) = scaleNumber(mAsset, number);
canonicalize();
return *this;
}
@@ -553,7 +606,7 @@ STAmount::clear()
{
// The -100 is used to allow 0 to sort less than a small positive values
// which have a negative exponent.
mOffset = native() ? 0 : -100;
mOffset = integral() ? 0 : -100;
mValue = 0;
mIsNegative = false;
}

View File

@@ -482,6 +482,8 @@ public:
value_type
operator*() const;
/// Do not use operator->() unless the field is required, or you've checked
/// that it's set.
T const*
operator->() const;
@@ -718,6 +720,8 @@ STObject::Proxy<T>::operator*() const -> value_type
return this->value();
}
/// Do not use operator->() unless the field is required, or you've checked that
/// it's set.
template <class T>
T const*
STObject::Proxy<T>::operator->() const

View File

@@ -23,6 +23,7 @@ systemName()
/** Number of drops in the genesis account. */
constexpr XRPAmount INITIAL_XRP{100'000'000'000 * DROPS_PER_XRP};
static_assert(INITIAL_XRP.drops() == 100'000'000'000'000'000);
/** Returns true if the amount does not exceed the initial XRP in existence. */
inline bool

View File

@@ -479,10 +479,10 @@ LEDGER_ENTRY(ltVAULT, 0x0084, Vault, vault, ({
{sfAccount, soeREQUIRED},
{sfData, soeOPTIONAL},
{sfAsset, soeREQUIRED},
{sfAssetsTotal, soeREQUIRED},
{sfAssetsAvailable, soeREQUIRED},
{sfAssetsTotal, soeDEFAULT},
{sfAssetsAvailable, soeDEFAULT},
{sfAssetsMaximum, soeDEFAULT},
{sfLossUnrealized, soeREQUIRED},
{sfLossUnrealized, soeDEFAULT},
{sfShareMPTID, soeREQUIRED},
{sfWithdrawalPolicy, soeREQUIRED},
{sfScale, soeDEFAULT},

View File

@@ -1,4 +1,6 @@
#include <xrpl/basics/Number.h>
//
#include <xrpl/basics/contract.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <algorithm>
@@ -19,10 +21,23 @@ using uint128_t = boost::multiprecision::uint128_t;
#else // !defined(_MSC_VER)
using uint128_t = __uint128_t;
#endif // !defined(_MSC_VER)
static_assert(std::is_same_v<uint128_t, ripple::numberuint128>);
namespace std {
template <>
struct make_unsigned<ripple::numberint128>
{
using type = uint128_t;
};
} // namespace std
namespace ripple {
thread_local Number::rounding_mode Number::mode_ = Number::to_nearest;
thread_local std::reference_wrapper<MantissaRange const> Number::range_ =
largeRange;
Number::rounding_mode
Number::getround()
@@ -36,6 +51,21 @@ Number::setround(rounding_mode mode)
return std::exchange(mode_, mode);
}
MantissaRange::mantissa_scale
Number::getMantissaScale()
{
return range_.get().scale;
}
void
Number::setMantissaScale(MantissaRange::mantissa_scale scale)
{
// scale_ and range_ MUST stay in lockstep
if (scale != MantissaRange::small && scale != MantissaRange::large)
LogicError("Unknown mantissa scale");
range_ = scale == MantissaRange::small ? smallRange : largeRange;
}
// Guard
// The Guard class is used to tempoarily add extra digits of
@@ -62,8 +92,9 @@ public:
is_negative() const noexcept;
// add a digit
template <class T>
void
push(unsigned d) noexcept;
push(T d) noexcept;
// recover a digit
unsigned
@@ -74,6 +105,10 @@ public:
// tie, round towards even.
int
round() noexcept;
private:
void
doPush(unsigned d) noexcept;
};
inline void
@@ -95,13 +130,20 @@ Number::Guard::is_negative() const noexcept
}
inline void
Number::Guard::push(unsigned d) noexcept
Number::Guard::doPush(unsigned d) noexcept
{
xbit_ = xbit_ || (digits_ & 0x0000'0000'0000'000F) != 0;
digits_ >>= 4;
digits_ |= (d & 0x0000'0000'0000'000FULL) << 60;
}
template <class T>
inline void
Number::Guard::push(T d) noexcept
{
doPush(static_cast<unsigned>(d));
}
inline unsigned
Number::Guard::pop() noexcept
{
@@ -153,20 +195,51 @@ Number::Guard::round() noexcept
// Number
constexpr Number one{1000000000000000, -15, Number::unchecked{}};
void
Number::normalize()
constexpr Number
Number::oneSmall()
{
return Number{
Number::smallRange.min, -Number::smallRange.log, Number::unchecked{}};
};
constexpr Number oneSml = Number::oneSmall();
constexpr Number
Number::oneLarge()
{
return Number{
Number::largeRange.min, -Number::largeRange.log, Number::unchecked{}};
};
constexpr Number oneLrg = Number::oneLarge();
Number
Number::one()
{
if (&range_.get() == &smallRange)
return oneSml;
XRPL_ASSERT(&range_.get() == &largeRange, "Number::one() : valid range_");
return oneLrg;
}
// Use the member names in this static function for now so the diff is cleaner
void
Number::normalize(
internalrep& mantissa_,
int& exponent_,
internalrep const& minMantissa,
internalrep const& maxMantissa)
{
constexpr Number zero = Number{};
if (mantissa_ == 0)
{
*this = Number{};
mantissa_ = zero.mantissa_;
exponent_ = zero.exponent_;
return;
}
bool const negative = (mantissa_ < 0);
auto m = static_cast<std::make_unsigned_t<rep>>(mantissa_);
if (negative)
m = -m;
auto m = static_cast<std::make_unsigned_t<internalrep>>(
negative ? -mantissa_ : mantissa_);
while ((m < minMantissa) && (exponent_ > minExponent))
{
m *= 10;
@@ -186,7 +259,8 @@ Number::normalize()
mantissa_ = m;
if ((exponent_ < minExponent) || (mantissa_ < minMantissa))
{
*this = Number{};
mantissa_ = zero.mantissa_;
exponent_ = zero.exponent_;
return;
}
@@ -207,6 +281,13 @@ Number::normalize()
mantissa_ = -mantissa_;
}
void
Number::normalize()
{
normalize(
mantissa_, exponent_, Number::minMantissa(), Number::maxMantissa());
}
Number&
Number::operator+=(Number const& y)
{
@@ -266,6 +347,8 @@ Number::operator+=(Number const& y)
}
if (xn == yn)
{
auto const maxMantissa = Number::maxMantissa();
xm += ym;
if (xm > maxMantissa)
{
@@ -288,6 +371,8 @@ Number::operator+=(Number const& y)
}
else
{
auto const minMantissa = Number::minMantissa();
if (xm > ym)
{
xm = xm - ym;
@@ -388,6 +473,9 @@ Number::operator*=(Number const& y)
Guard g;
if (zn == -1)
g.set_negative();
auto const maxMantissa = Number::maxMantissa();
while (zm > maxMantissa)
{
// The following is optimization for:
@@ -396,7 +484,7 @@ Number::operator*=(Number const& y)
g.push(divu10(zm));
++ze;
}
xm = static_cast<rep>(zm);
xm = static_cast<internalrep>(zm);
xe = ze;
auto r = g.round();
if (r == 1 || (r == 0 && (xm & 1) == 1))
@@ -448,11 +536,66 @@ Number::operator/=(Number const& y)
dm = -dm;
dp = -1;
}
// Shift by 10^17 gives greatest precision while not overflowing uint128_t
// or the cast back to int64_t
uint128_t const f = 100'000'000'000'000'000;
mantissa_ = static_cast<std::int64_t>(uint128_t(nm) * f / uint128_t(dm));
exponent_ = ne - de - 17;
// Shift by 10^17 gives greatest precision while not overflowing
// uint128_t or the cast back to int64_t
// TODO: Can/should this be made bigger for largeRange?
// log(2^128,10) ~ 38.5
// largeRange.log = 18, fits in 10^19
// f can be up to 10^(38-19) = 10^19 safely
static_assert(smallRange.log == 15);
static_assert(largeRange.log == 18);
bool small = Number::getMantissaScale() == MantissaRange::small;
uint128_t const f =
small ? 100'000'000'000'000'000 : 10'000'000'000'000'000'000ULL;
XRPL_ASSERT_PARTS(
f >= Number::minMantissa() * 10,
"Number::operator/=",
"factor expected size");
// unsigned denominator
auto const dmu = static_cast<uint128_t>(dm);
// correctionFactor can be anything between 10 and f, depending on how much
// extra precision we want to only use for rounding with the
// largeMantissa. Three digits seems like plenty, and is more than
// the smallMantissa uses.
uint128_t const correctionFactor = 1'000;
auto const numerator = uint128_t(nm) * f;
mantissa_ = numerator / dmu;
exponent_ = ne - de - (small ? 17 : 19);
if (!small)
{
// Virtually multiply numerator by correctionFactor. Since that would
// overflow in the existing uint128_t, we'll do that part separately.
// The math for this would work for small mantissas, but we need to
// preserve existing behavior.
//
// Consider:
// ((numerator * correctionFactor) / dmu) / correctionFactor
// = ((numerator / dmu) * correctionFactor) / correctionFactor)
//
// But that assumes infinite precision. With integer math, this is
// equivalent to
//
// = ((numerator / dmu * correctionFactor)
// + ((numerator % dmu) * correctionFactor) / dmu) / correctionFactor
//
// We have already set `mantissa_ = numerator / dmu`. Now we
// compute `remainder = numerator % dmu`, and if it is
// nonzero, we do the rest of the arithmetic. If it's zero, we can skip
// it.
auto const remainder = (numerator % dmu);
if (remainder != 0)
{
mantissa_ *= correctionFactor;
auto const correction = remainder * correctionFactor / dmu;
mantissa_ += correction;
// divide by 1000 by moving the exponent, so we don't lose the
// integer value we just computed
exponent_ -= 3;
}
}
mantissa_ *= np * dp;
normalize();
return *this;
@@ -460,7 +603,7 @@ Number::operator/=(Number const& y)
Number::operator rep() const
{
rep drops = mantissa_;
internalrep drops = mantissa_;
int offset = exponent_;
Guard g;
if (drops != 0)
@@ -475,9 +618,16 @@ Number::operator rep() const
g.push(drops % 10);
drops /= 10;
}
if (offset == 0 && drops > std::numeric_limits<rep>::max())
{
// If offset == 0, then the loop won't run, and the overflow check
// won't be made, but a int128 can overflow int64 by itself, so
// check here.
throw std::overflow_error("Number::operator rep() overflow");
}
for (; offset > 0; --offset)
{
if (drops > std::numeric_limits<decltype(drops)>::max() / 10)
if (drops > std::numeric_limits<rep>::max() / 10)
throw std::overflow_error("Number::operator rep() overflow");
drops *= 10;
}
@@ -489,7 +639,7 @@ Number::operator rep() const
if (g.is_negative())
drops = -drops;
}
return drops;
return static_cast<rep>(drops);
}
std::string
@@ -500,30 +650,41 @@ to_string(Number const& amount)
return "0";
auto const exponent = amount.exponent();
auto mantissa = amount.mantissa();
bool const negative = amount.mantissa() < 0;
auto const mantissa = [&]() {
auto mantissa = amount.mantissa();
if (negative)
{
mantissa = -mantissa;
}
XRPL_ASSERT(
mantissa < std::numeric_limits<std::uint64_t>::max(),
"ripple::to_string(Number) : mantissa fits in uin64_t");
return static_cast<std::uint64_t>(mantissa);
}();
// Use scientific notation for exponents that are too small or too large
if (((exponent != 0) && ((exponent < -25) || (exponent > -5))))
auto const rangeLog = Number::mantissaLog();
if (((exponent != 0) &&
((exponent < -(rangeLog + 10)) || (exponent > -(rangeLog - 10)))))
{
std::string ret = std::to_string(mantissa);
std::string ret = negative ? "-" : "";
ret.append(std::to_string(mantissa));
ret.append(1, 'e');
ret.append(std::to_string(exponent));
return ret;
}
bool negative = false;
if (mantissa < 0)
{
mantissa = -mantissa;
negative = true;
}
// TODO: These numbers are probably wrong for largeRange
XRPL_ASSERT(
exponent + 43 > 0, "ripple::to_string(Number) : minimum exponent");
ptrdiff_t const pad_prefix = 27;
ptrdiff_t const pad_suffix = 23;
auto const mantissaLog = Number::mantissaLog();
ptrdiff_t const pad_prefix = mantissaLog + 12;
ptrdiff_t const pad_suffix = mantissaLog + 8;
std::string const raw_value(std::to_string(mantissa));
std::string val;
@@ -533,7 +694,7 @@ to_string(Number const& amount)
val.append(raw_value);
val.append(pad_suffix, '0');
ptrdiff_t const offset(exponent + 43);
ptrdiff_t const offset(exponent + pad_prefix + mantissaLog + 1);
auto pre_from(val.begin());
auto const pre_to(val.begin() + offset);
@@ -594,7 +755,7 @@ Number
power(Number const& f, unsigned n)
{
if (n == 0)
return one;
return Number::one();
if (n == 1)
return f;
auto r = power(f, n / 2);
@@ -616,6 +777,8 @@ power(Number const& f, unsigned n)
Number
root(Number f, unsigned d)
{
auto const one = Number::one();
if (f == one || d == 1)
return f;
if (d == 0)
@@ -632,7 +795,7 @@ root(Number f, unsigned d)
return f;
// Scale f into the range (0, 1) such that f's exponent is a multiple of d
auto e = f.exponent() + 16;
auto e = f.exponent() + Number::mantissaLog() + 1;
auto const di = static_cast<int>(d);
auto ex = [e = e, di = di]() // Euclidean remainder of e/d
{
@@ -681,6 +844,8 @@ root(Number f, unsigned d)
Number
root2(Number f)
{
auto const one = Number::one();
if (f == one)
return f;
if (f < Number{})
@@ -689,7 +854,7 @@ root2(Number f)
return f;
// Scale f into the range (0, 1) such that f's exponent is a multiple of d
auto e = f.exponent() + 16;
auto e = f.exponent() + Number::mantissaLog() + 1;
if (e % 2 != 0)
++e;
f = Number{f.mantissa(), f.exponent() - e}; // f /= 10^e;
@@ -721,6 +886,8 @@ root2(Number f)
Number
power(Number const& f, unsigned n, unsigned d)
{
auto const one = Number::one();
if (f == one)
return f;
auto g = std::gcd(n, d);

View File

@@ -1,8 +1,10 @@
#include <xrpl/protocol/IOUAmount.h>
//
#include <xrpl/basics/LocalValue.h>
#include <xrpl/basics/Number.h>
#include <xrpl/basics/contract.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/protocol/IOUAmount.h>
#include <xrpl/protocol/STAmount.h>
#include <boost/multiprecision/cpp_int.hpp>
@@ -40,11 +42,19 @@ setSTNumberSwitchover(bool v)
}
/* The range for the mantissa when normalized */
static std::int64_t constexpr minMantissa = 1000000000000000ull;
static std::int64_t constexpr maxMantissa = 9999999999999999ull;
// log(2^63,10) ~ 18.96
//
static std::int64_t constexpr minMantissa = STAmount::cMinValue;
static std::int64_t constexpr maxMantissa = STAmount::cMaxValue;
/* The range for the exponent when normalized */
static int constexpr minExponent = -96;
static int constexpr maxExponent = 80;
static int constexpr minExponent = STAmount::cMinOffset;
static int constexpr maxExponent = STAmount::cMaxOffset;
std::pair<IOUAmount::mantissa_type, IOUAmount::exponent_type>
IOUAmount::scaleNumber(Number const& number)
{
return number.normalizeToRange(minMantissa, maxMantissa);
}
IOUAmount
IOUAmount::minPositiveAmount()
@@ -64,8 +74,7 @@ IOUAmount::normalize()
if (getSTNumberSwitchover())
{
Number const v{mantissa_, exponent_};
mantissa_ = v.mantissa();
exponent_ = v.exponent();
std::tie(mantissa_, exponent_) = scaleNumber(v);
if (exponent_ > maxExponent)
Throw<std::overflow_error>("value overflow");
if (exponent_ < minExponent)
@@ -106,8 +115,7 @@ IOUAmount::normalize()
mantissa_ = -mantissa_;
}
IOUAmount::IOUAmount(Number const& other)
: mantissa_(other.mantissa()), exponent_(other.exponent())
IOUAmount::IOUAmount(Number const& other) : IOUAmount(scaleNumber(other))
{
if (exponent_ > maxExponent)
Throw<std::overflow_error>("value overflow");

View File

@@ -49,6 +49,12 @@ Issue::native() const
return *this == xrpIssue();
}
bool
Issue::integral() const
{
return native();
}
bool
isConsistent(Issue const& ac)
{

View File

@@ -1,10 +1,12 @@
#include <xrpl/protocol/Rules.h>
//
#include <xrpl/basics/LocalValue.h>
#include <xrpl/basics/Number.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/hardened_hash.h>
#include <xrpl/beast/hash/uhash.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Rules.h>
#include <xrpl/protocol/STVector256.h>
#include <memory>
@@ -33,6 +35,15 @@ getCurrentTransactionRules()
void
setCurrentTransactionRules(std::optional<Rules> r)
{
// Make global changes associated with the rules before the value is moved.
// Push the appropriate setting, instead of having the class pull every time
// the value is needed. That could get expensive fast.
bool enableLargeNumbers = !r ||
(r->enabled(featureSingleAssetVault) /*||
r->enabled(featureLendingProtocol)*/);
Number::setMantissaScale(
enableLargeNumbers ? MantissaRange::large : MantissaRange::small);
*getCurrentTransactionRulesRef() = std::move(r);
}

View File

@@ -310,8 +310,8 @@ STAmount&
STAmount::operator=(IOUAmount const& iou)
{
XRPL_ASSERT(
native() == false,
"ripple::STAmount::operator=(IOUAmount) : is not XRP");
integral() == false,
"ripple::STAmount::operator=(IOUAmount) : is not integral");
mOffset = iou.exponent();
mIsNegative = iou < beast::zero;
if (mIsNegative)
@@ -851,8 +851,9 @@ STAmount::canonicalize()
if (getSTNumberSwitchover())
{
auto const value = unsafe_cast<std::int64_t>(mValue);
Number num(
mIsNegative ? -mValue : mValue, mOffset, Number::unchecked{});
mIsNegative ? -value : value, mOffset, Number::unchecked{});
auto set = [&](auto const& val) {
mIsNegative = val.value() < 0;
mValue = mIsNegative ? -val.value() : val.value();
@@ -1323,7 +1324,7 @@ multiply(STAmount const& v1, STAmount const& v2, Asset const& asset)
if (getSTNumberSwitchover())
{
auto const r = Number{v1} * Number{v2};
return STAmount{asset, r.mantissa(), r.exponent()};
return STAmount{asset, r};
}
std::uint64_t value1 = v1.mantissa();

View File

@@ -50,8 +50,27 @@ STNumber::add(Serializer& s) const
XRPL_ASSERT(
getFName().fieldType == getSType(),
"ripple::STNumber::add : field type match");
s.add64(value_.mantissa());
s.add32(value_.exponent());
if (value_.mantissa() <= std::numeric_limits<std::int64_t>::max() &&
value_.mantissa() >= std::numeric_limits<std::int64_t>::min())
{
// If the mantissa fits in the range of std::int64_t, write it directly.
// This preserves the maximum available precision.
// With the small range, all numbers should be written this way. With
// the large range, it's likely that most numbers will be written this
// way.
s.add64(static_cast<std::int64_t>(value_.mantissa()));
s.add32(value_.exponent());
}
else
{
constexpr std::int64_t min = 100'000'000'000'000'000LL;
constexpr std::int64_t max = min * 10 - 1;
static_assert(
min < (std::numeric_limits<std::int64_t>::max() - 1 / 10));
auto const [mantissa, exponent] = value_.normalizeToRange(min, max);
s.add64(mantissa);
s.add32(exponent);
}
}
Number const&
@@ -180,16 +199,18 @@ numberFromJson(SField const& field, Json::Value const& value)
else if (value.isString())
{
parts = partsFromString(value.asString());
// Only strings can represent out-of-range values.
if (parts.mantissa > std::numeric_limits<std::int64_t>::max())
Throw<std::range_error>("too high");
// Number mantissas are much bigger than the allowable parsed values, so
// it can't be out of range.
static_assert(
std::numeric_limits<numberint128>::max() >
std::numeric_limits<decltype(parts.mantissa)>::max());
}
else
{
Throw<std::runtime_error>("not a number");
}
std::int64_t mantissa = parts.mantissa;
numberint128 mantissa = parts.mantissa;
if (parts.negative)
mantissa = -mantissa;

View File

@@ -30,6 +30,9 @@ namespace test {
*/
struct AMM_test : public jtx::AMMTest
{
// Use small Number mantissas for the life of this test.
NumberMantissaScaleGuard sg_{ripple::MantissaRange::small};
private:
void
testInstanceCreate()
@@ -1384,15 +1387,14 @@ private:
// equal asset deposit: unit test to exercise the rounding-down of
// LPTokens in the AMMHelpers.cpp: adjustLPTokens calculations
// The LPTokens need to have 16 significant digits and a fractional part
for (Number const deltaLPTokens :
for (Number const& deltaLPTokens :
{Number{UINT64_C(100000'0000000009), -10},
Number{UINT64_C(100000'0000000001), -10}})
{
testAMM([&](AMM& ammAlice, Env& env) {
// initial LPToken balance
IOUAmount const initLPToken = ammAlice.getLPTokensBalance();
IOUAmount const newLPTokens{
deltaLPTokens.mantissa(), deltaLPTokens.exponent()};
IOUAmount const newLPTokens{deltaLPTokens};
// carol performs a two-asset deposit
ammAlice.deposit(
@@ -1417,11 +1419,9 @@ private:
Number const deltaXRP = fr * 1e10;
Number const deltaUSD = fr * 1e4;
STAmount const depositUSD =
STAmount{USD, deltaUSD.mantissa(), deltaUSD.exponent()};
STAmount const depositUSD = STAmount{USD, deltaUSD};
STAmount const depositXRP =
STAmount{XRP, deltaXRP.mantissa(), deltaXRP.exponent()};
STAmount const depositXRP = STAmount{XRP, deltaXRP};
// initial LPTokens (1e7) + newLPTokens
BEAST_EXPECT(ammAlice.expectBalances(
@@ -3014,6 +3014,11 @@ private:
using namespace jtx;
using namespace std::chrono;
// For now, just disable SAV entirely, which locks in the small Number
// mantissas
features =
features - featureSingleAssetVault /* - featureLendingProtocol */;
// Auction slot initially is owned by AMM creator, who pays 0 price.
// Bid 110 tokens. Pay bidMin.
@@ -3758,6 +3763,11 @@ private:
testcase("Basic Payment");
using namespace jtx;
// For now, just disable SAV entirely, which locks in the small Number
// mantissas
features =
features - featureSingleAssetVault /* - featureLendingProtocol */;
// Payment 100USD for 100XRP.
// Force one path with tfNoRippleDirect.
testAMM(
@@ -6476,6 +6486,8 @@ private:
Env env(*this, features, std::make_unique<CaptureLogs>(&logs));
auto rules = env.current()->rules();
CurrentTransactionRulesGuard rg(rules);
NumberMantissaScaleGuard sg(MantissaRange::small);
for (auto const& t : tests)
{
auto getPool = [&](std::string const& v, bool isXRP) {

View File

@@ -2474,7 +2474,7 @@ class MPToken_test : public beast::unit_test::suite
alice.name(), makeMptID(env.seq(alice), alice));
Json::Value jv = claw(alice, mpt(1), bob);
jv[jss::Amount][jss::value] = to_string(maxMPTokenAmount + 1);
jv[jss::Amount][jss::value] = std::to_string(maxMPTokenAmount + 1);
Json::Value jv1;
jv1[jss::secret] = alice.name();
jv1[jss::tx_json] = jv;

View File

@@ -4525,7 +4525,7 @@ class Vault_test : public beast::unit_test::suite
BEAST_EXPECT(checkString(vault, sfAssetsAvailable, "50"));
BEAST_EXPECT(checkString(vault, sfAssetsMaximum, "1000"));
BEAST_EXPECT(checkString(vault, sfAssetsTotal, "50"));
BEAST_EXPECT(checkString(vault, sfLossUnrealized, "0"));
BEAST_EXPECT(!vault.isMember(sfLossUnrealized.getJsonName()));
auto const strShareID = strHex(sle->at(sfShareMPTID));
BEAST_EXPECT(checkString(vault, sfShareMPTID, strShareID));

File diff suppressed because it is too large Load Diff

View File

@@ -192,6 +192,12 @@ public:
return to_json(asset_);
}
bool
integral() const
{
return asset_.integral();
}
template <std::integral T>
PrettyAmount
operator()(T v, Number::rounding_mode rounding = Number::getround()) const
@@ -242,6 +248,12 @@ struct XRP_t
return xrpIssue();
}
bool
integral() const
{
return true;
}
/** Returns an amount of XRP as PrettyAmount,
which is trivially convertible to STAmount
@@ -366,6 +378,11 @@ public:
{
return issue();
}
bool
integral() const
{
return issue().integral();
}
/** Implicit conversion to Issue or Asset.
@@ -456,6 +473,11 @@ public:
{
return mptIssue();
}
bool
integral() const
{
return true;
}
/** Implicit conversion to MPTIssue or asset.

View File

@@ -29,10 +29,8 @@ struct STNumber_test : public beast::unit_test::suite
}
void
run() override
doRun()
{
static_assert(!std::is_convertible_v<STNumber*, Number*>);
{
STNumber const stnum{sfNumber};
BEAST_EXPECT(stnum.getSType() == STI_NUMBER);
@@ -127,6 +125,40 @@ struct STNumber_test : public beast::unit_test::suite
BEAST_EXPECT(
numberFromJson(sfNumber, "-0.000e6") == STNumber(sfNumber, 0));
{
NumberRoundModeGuard mg(Number::towards_zero);
// maxint64 9,223,372,036,854,775,807
auto const maxInt =
std::to_string(std::numeric_limits<std::int64_t>::max());
// minint64 -9,223,372,036,854,775,808
auto const minInt =
std::to_string(std::numeric_limits<std::int64_t>::min());
if (Number::getMantissaScale() == MantissaRange::small)
{
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
{
BEAST_EXPECT(
numberFromJson(sfNumber, maxInt) ==
STNumber(
sfNumber, Number{9'223'372'036'854'775'807, 0}));
BEAST_EXPECT(
numberFromJson(sfNumber, minInt) ==
STNumber(
sfNumber,
-Number{
numberint128(9'223'372'036'854'775) * 1000 +
808,
0}));
}
}
constexpr auto imin = std::numeric_limits<int>::min();
BEAST_EXPECT(
numberFromJson(sfNumber, imin) ==
@@ -279,15 +311,21 @@ struct STNumber_test : public beast::unit_test::suite
}
}
}
void
run() override
{
static_assert(!std::is_convertible_v<STNumber*, Number*>);
for (auto const scale : {MantissaRange::small, MantissaRange::large})
{
NumberMantissaScaleGuard sg(scale);
testcase << to_string(Number::getMantissaScale());
doRun();
}
}
};
BEAST_DEFINE_TESTSUITE(STNumber, protocol, ripple);
void
testCompile(std::ostream& out)
{
STNumber number{sfNumber, 42};
out << number;
}
} // namespace ripple

View File

@@ -592,6 +592,24 @@ Transactor::ticketDelete(
return tesSUCCESS;
}
bool
Transactor::useOldNumberRules(TxType txType)
{
constexpr auto skipTransactions = std::to_array<TxType>(
{ttAMM_BID,
ttAMM_CLAWBACK,
ttAMM_CREATE,
ttAMM_DELETE,
ttAMM_DEPOSIT,
ttAMM_VOTE,
ttAMM_WITHDRAW});
return std::find(
std::begin(skipTransactions),
std::end(skipTransactions),
txType) != std::end(skipTransactions);
}
// check stuff before you bother to lock the ledger
void
Transactor::preCompute()
@@ -1106,10 +1124,16 @@ Transactor::operator()()
{
JLOG(j_.trace()) << "apply: " << ctx_.tx.getTransactionID();
// These global updates really should have been for every Transaction
// step: preflight, preclaim, and doApply. And even calculateBaseFee. See
// with_txn_type().
//
// raii classes for the current ledger rules.
// fixUniversalNumber predate the rulesGuard and should be replaced.
NumberSO stNumberSO{view().rules().enabled(fixUniversalNumber)};
CurrentTransactionRulesGuard currentTransctionRulesGuard(view().rules());
if (Transactor::useOldNumberRules(ctx_.tx.getTxnType()))
Number::setMantissaScale(MantissaRange::small);
#ifdef DEBUG
{
@@ -1122,7 +1146,7 @@ Transactor::operator()()
{
// LCOV_EXCL_START
JLOG(j_.fatal()) << "Transaction serdes mismatch";
JLOG(j_.info()) << to_string(ctx_.tx.getJson(JsonOptions::none));
JLOG(j_.fatal()) << ctx_.tx.getJson(JsonOptions::none);
JLOG(j_.fatal()) << s2.getJson(JsonOptions::none);
UNREACHABLE(
"ripple::Transactor::operator() : transaction serdes mismatch");

View File

@@ -230,6 +230,9 @@ public:
uint256 const& ticketIndex,
beast::Journal j);
static bool
useOldNumberRules(TxType txType);
protected:
TER
apply();

View File

@@ -34,8 +34,33 @@ struct UnknownTxnType : std::exception
// throw an "UnknownTxnType" exception on error
template <class F>
auto
with_txn_type(TxType txnType, F&& f)
with_txn_type(Rules const& rules, TxType txnType, F&& f)
{
// These global updates really should have been for every Transaction
// step: preflight, preclaim, calculateBaseFee, and doApply. Unfortunately,
// they were only included in doApply (via Transactor::operator()). That may
// have been sufficient when the changes were only related to operations
// that mutated data, but some features will now change how they read data,
// so these need to be more global.
//
// To prevent unintentional side effects on existing checks, they will be
// set for every operation only once SingleAssetVault (or later
// LendingProtocol) are enabled.
//
// See also Transactor::operator().
//
std::optional<NumberSO> stNumberSO;
std::optional<CurrentTransactionRulesGuard> rulesGuard;
if (rules.enabled(featureSingleAssetVault) /*|| rules.enabled(featureLendingProtocol)*/)
{
// raii classes for the current ledger rules.
// fixUniversalNumber predate the rulesGuard and should be replaced.
stNumberSO.emplace(rules.enabled(fixUniversalNumber));
rulesGuard.emplace(rules);
}
if (Transactor::useOldNumberRules(txnType))
Number::setMantissaScale(MantissaRange::small);
switch (txnType)
{
#pragma push_macro("TRANSACTION")
@@ -99,7 +124,7 @@ invoke_preflight(PreflightContext const& ctx)
{
try
{
return with_txn_type(ctx.tx.getTxnType(), [&]<typename T>() {
return with_txn_type(ctx.rules, ctx.tx.getTxnType(), [&]<typename T>() {
auto const tec = Transactor::invokePreflight<T>(ctx);
return std::make_pair(
tec,
@@ -126,50 +151,51 @@ invoke_preclaim(PreclaimContext const& ctx)
{
// use name hiding to accomplish compile-time polymorphism of static
// class functions for Transactor and derived classes.
return with_txn_type(ctx.tx.getTxnType(), [&]<typename T>() -> TER {
// preclaim functionality is divided into two sections:
// 1. Up to and including the signature check: returns NotTEC.
// All transaction checks before and including checkSign
// MUST return NotTEC, or something more restrictive.
// Allowing tec results in these steps risks theft or
// destruction of funds, as a fee will be charged before the
// signature is checked.
// 2. After the signature check: returns TER.
return with_txn_type(
ctx.view.rules(), ctx.tx.getTxnType(), [&]<typename T>() -> TER {
// preclaim functionality is divided into two sections:
// 1. Up to and including the signature check: returns NotTEC.
// All transaction checks before and including checkSign
// MUST return NotTEC, or something more restrictive.
// Allowing tec results in these steps risks theft or
// destruction of funds, as a fee will be charged before the
// signature is checked.
// 2. After the signature check: returns TER.
// If the transactor requires a valid account and the
// transaction doesn't list one, preflight will have already
// a flagged a failure.
auto const id = ctx.tx.getAccountID(sfAccount);
// If the transactor requires a valid account and the
// transaction doesn't list one, preflight will have already
// a flagged a failure.
auto const id = ctx.tx.getAccountID(sfAccount);
if (id != beast::zero)
{
if (NotTEC const preSigResult = [&]() -> NotTEC {
if (NotTEC const result =
T::checkSeqProxy(ctx.view, ctx.tx, ctx.j))
return result;
if (id != beast::zero)
{
if (NotTEC const preSigResult = [&]() -> NotTEC {
if (NotTEC const result =
T::checkSeqProxy(ctx.view, ctx.tx, ctx.j))
return result;
if (NotTEC const result =
T::checkPriorTxAndLastLedger(ctx))
return result;
if (NotTEC const result =
T::checkPriorTxAndLastLedger(ctx))
return result;
if (NotTEC const result =
T::checkPermission(ctx.view, ctx.tx))
return result;
if (NotTEC const result =
T::checkPermission(ctx.view, ctx.tx))
return result;
if (NotTEC const result = T::checkSign(ctx))
return result;
if (NotTEC const result = T::checkSign(ctx))
return result;
return tesSUCCESS;
}())
return preSigResult;
return tesSUCCESS;
}())
return preSigResult;
if (TER const result =
T::checkFee(ctx, calculateBaseFee(ctx.view, ctx.tx)))
return result;
}
if (TER const result = T::checkFee(
ctx, calculateBaseFee(ctx.view, ctx.tx)))
return result;
}
return T::preclaim(ctx);
});
return T::preclaim(ctx);
});
}
catch (UnknownTxnType const& e)
{
@@ -204,7 +230,7 @@ invoke_calculateBaseFee(ReadView const& view, STTx const& tx)
{
try
{
return with_txn_type(tx.getTxnType(), [&]<typename T>() {
return with_txn_type(view.rules(), tx.getTxnType(), [&]<typename T>() {
return T::calculateBaseFee(view, tx);
});
}
@@ -264,10 +290,11 @@ invoke_apply(ApplyContext& ctx)
{
try
{
return with_txn_type(ctx.tx.getTxnType(), [&]<typename T>() {
T p(ctx);
return p();
});
return with_txn_type(
ctx.view().rules(), ctx.tx.getTxnType(), [&]<typename T>() {
T p(ctx);
return p();
});
}
catch (UnknownTxnType const& e)
{