Merge branch 'ripple/smart-escrow' into ripple/se/supported

This commit is contained in:
Mayukha Vadari
2026-01-22 17:10:39 -05:00
committed by GitHub
151 changed files with 11704 additions and 4433 deletions

View File

@@ -1,8 +1,11 @@
#ifndef XRPL_BASICS_NUMBER_H_INCLUDED
#define XRPL_BASICS_NUMBER_H_INCLUDED
#include <xrpl/beast/utility/instrumentation.h>
#include <cstdint>
#include <limits>
#include <optional>
#include <ostream>
#include <string>
@@ -13,42 +16,252 @@ 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)
{
while (value >= 10 && value % 10 == 0)
value /= 10;
return value == 1;
return logTen(value).has_value();
}
/** MantissaRange defines a range for the mantissa of a normalized Number.
*
* The mantissa is in the range [min, max], where
* * min is a power of 10, and
* * max = min * 10 - 1.
*
* The mantissa_scale enum indicates whether the range is "small" or "large".
* This intentionally restricts the number of MantissaRanges that can be
* instantiated to two: one for each scale.
*
* The "small" scale is based on the behavior of STAmount for IOUs. It has a min
* value of 10^15, and a max value of 10^16-1. This was sufficient for
* uses before Lending Protocol was implemented, mostly related to AMM.
*
* However, it does not have sufficient precision to represent the full integer
* range of int64_t values (-2^63 to 2^63-1), which are needed for XRP and MPT
* values. The implementation of SingleAssetVault, and LendingProtocol need to
* represent those integer values accurately and precisely, both for the
* STNumber field type, and for internal calculations. That necessitated the
* "large" scale.
*
* The "large" scale is intended to represent all values that can be represented
* by an STAmount - IOUs, XRP, and MPTs. It has a min value of 10^18, and a max
* value of 10^19-1.
*
* Note that if the mentioned amendments are eventually retired, this class
* should be left in place, but the "small" scale option should be removed. This
* will allow for future expansion beyond 64-bits if it is ever needed.
*/
struct MantissaRange
{
using rep = std::uint64_t;
enum mantissa_scale { small, large };
explicit constexpr MantissaRange(mantissa_scale scale_)
: min(getMin(scale_))
, max(min * 10 - 1)
, log(logTen(min).value_or(-1))
, scale(scale_)
{
}
rep min;
rep max;
int log;
mantissa_scale scale;
private:
static constexpr rep
getMin(mantissa_scale scale_)
{
switch (scale_)
{
case small:
return 1'000'000'000'000'000ULL;
case large:
return 1'000'000'000'000'000'000ULL;
default:
// Since this can never be called outside a non-constexpr
// context, this throw assures that the build fails if an
// invalid scale is used.
throw std::runtime_error("Unknown mantissa scale");
}
}
};
// Like std::integral, but only 64-bit integral types.
template <class T>
concept Integral64 =
std::is_same_v<T, std::int64_t> || std::is_same_v<T, std::uint64_t>;
/** Number is a floating point type that can represent a wide range of values.
*
* It can represent all values that can be represented by an STAmount -
* regardless of asset type - XRPAmount, MPTAmount, and IOUAmount, with at least
* as much precision as those types require.
*
* ---- Internal Representation ----
*
* Internally, Number is represented with three values:
* 1. a bool sign flag,
* 2. a std::uint64_t mantissa,
* 3. an int exponent.
*
* The internal mantissa is an unsigned integer in the range defined by the
* current MantissaRange. The exponent is an integer in the range
* [minExponent, maxExponent].
*
* See the description of MantissaRange for more details on the ranges.
*
* A non-zero mantissa is (almost) always normalized, meaning it and the
* exponent are grown or shrunk until the mantissa is in the range
* [MantissaRange.min, MantissaRange.max].
*
* Note:
* 1. Normalization can be disabled by using the "unchecked" ctor tag. This
* should only be used at specific conversion points, some constexpr
* values, and in unit tests.
* 2. The max of the "large" range, 10^19-1, is the largest 10^X-1 value that
* fits in an unsigned 64-bit number. (10^19-1 < 2^64-1 and
* 10^20-1 > 2^64-1). This avoids under- and overflows.
*
* ---- External Interface ----
*
* The external interface of Number consists of a std::int64_t mantissa, which
* is restricted to 63-bits, and an int exponent, which must be in the range
* [minExponent, maxExponent]. The range of the mantissa depends on which
* MantissaRange is currently active. For the "short" range, the mantissa will
* be between 10^15 and 10^16-1. For the "large" range, the mantissa will be
* between -(2^63-1) and 2^63-1. As noted above, the "large" range is needed to
* represent the full range of valid XRP and MPT integer values accurately.
*
* Note:
* 1. 2^63-1 is between 10^18 and 10^19-1, which are the limits of the "large"
* mantissa range.
* 2. The functions mantissa() and exponent() return the external view of the
* Number value, specifically using a signed 63-bit mantissa. This may
* require altering the internal representation to fit into that range
* before the value is returned. The interface guarantees consistency of
* the two values.
* 3. Number cannot represent -2^63 (std::numeric_limits<std::int64_t>::min())
* as an exact integer, but it doesn't need to, because all asset values
* on-ledger are non-negative. This is due to implementation details of
* several operations which use unsigned arithmetic internally. This is
* sufficient to represent all valid XRP values (where the absolute value
* can not exceed INITIAL_XRP: 10^17), and MPT values (where the absolute
* value can not exceed maxMPTokenAmount: 2^63-1).
*
* ---- Mantissa Range Switching ----
*
* The mantissa range may be changed at runtime via setMantissaScale(). The
* default mantissa range is "large". The range is updated whenever transaction
* processing begins, based on whether SingleAssetVault or LendingProtocol are
* enabled. If either is enabled, the mantissa range is set to "large". If not,
* it is set to "small", preserving backward compatibility and correct
* "amendment-gating".
*
* It is extremely unlikely that any more calls to setMantissaScale() will be
* needed outside of unit tests.
*
* ---- Usage With Different Ranges ----
*
* Outside of unit tests, and existing checks, code that uses Number should not
* know or care which mantissa range is active.
*
* The results of computations using Numbers with a small mantissa may differ
* from computations using Numbers with a large mantissa, specifically as it
* effects the results after rounding. That is why the large mantissa range is
* amendment gated in transaction processing.
*
* It is extremely unlikely that any more calls to getMantissaScale() will be
* needed outside of unit tests.
*
* Code that uses Number should not assume or check anything about the
* mantissa() or exponent() except that they fit into the "large" range
* specified in the "External Interface" section.
*
* ----- Unit Tests -----
*
* Within unit tests, it may be useful to explicitly switch between the two
* ranges, or to check which range is active when checking the results of
* computations. If the test is doing the math directly, the
* set/getMantissaScale() functions may be most appropriate. However, if the
* test has anything to do with transaction processing, it should enable or
* disable the amendments that control the mantissa range choice
* (SingleAssetVault and LendingProtocol), and/or check if either of those
* amendments are enabled to determine which result to expect.
*
*/
class Number
{
using rep = std::int64_t;
rep mantissa_{0};
using internalrep = MantissaRange::rep;
bool negative_{false};
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;
static_assert(isPowerOfTen(minMantissa));
constexpr static std::int64_t maxMantissa = minMantissa * 10 - 1;
static_assert(maxMantissa == 9'999'999'999'999'999LL);
// The range for the exponent when normalized
constexpr static int minExponent = -32768;
constexpr static int maxExponent = 32768;
constexpr static internalrep maxRep = std::numeric_limits<rep>::max();
static_assert(maxRep == 9'223'372'036'854'775'807);
static_assert(-maxRep == std::numeric_limits<rep>::min() + 1);
// May need to make unchecked private
struct unchecked
{
explicit unchecked() = default;
};
// Like unchecked, normalized is used with the ctors that take an
// internalrep mantissa. Unlike unchecked, those ctors will normalize the
// value.
// Only unit tests are expected to use this class
struct normalized
{
explicit normalized() = default;
};
explicit constexpr Number() = default;
Number(rep mantissa);
explicit Number(rep mantissa, int exponent);
explicit constexpr Number(rep mantissa, int exponent, unchecked) noexcept;
explicit constexpr Number(
bool negative,
internalrep mantissa,
int exponent,
unchecked) noexcept;
// Assume unsigned values are... unsigned. i.e. positive
explicit constexpr Number(
internalrep mantissa,
int exponent,
unchecked) noexcept;
// Only unit tests are expected to use this ctor
explicit Number(
bool negative,
internalrep mantissa,
int exponent,
normalized);
// Assume unsigned values are... unsigned. i.e. positive
explicit Number(internalrep mantissa, int exponent, normalized);
constexpr rep
mantissa() const noexcept;
@@ -78,11 +291,11 @@ public:
Number&
operator/=(Number const& x);
static constexpr Number
static Number
min() noexcept;
static constexpr Number
static Number
max() noexcept;
static constexpr Number
static Number
lowest() noexcept;
/** Conversions to Number are implicit and conversions away from Number
@@ -96,7 +309,8 @@ public:
friend constexpr bool
operator==(Number const& x, Number const& y) noexcept
{
return x.mantissa_ == y.mantissa_ && x.exponent_ == y.exponent_;
return x.negative_ == y.negative_ && x.mantissa_ == y.mantissa_ &&
x.exponent_ == y.exponent_;
}
friend constexpr bool
@@ -110,8 +324,8 @@ public:
{
// If the two amounts have different signs (zero is treated as positive)
// then the comparison is true iff the left is negative.
bool const lneg = x.mantissa_ < 0;
bool const rneg = y.mantissa_ < 0;
bool const lneg = x.negative_;
bool const rneg = y.negative_;
if (lneg != rneg)
return lneg;
@@ -139,7 +353,7 @@ public:
constexpr int
signum() const noexcept
{
return (mantissa_ < 0) ? -1 : (mantissa_ ? 1 : 0);
return negative_ ? -1 : (mantissa_ ? 1 : 0);
}
Number
@@ -169,6 +383,15 @@ public:
return os << to_string(x);
}
friend std::string
to_string(Number const& amount);
friend Number
root(Number f, unsigned d);
friend Number
root2(Number f);
// Thread local rounding control. Default is to_nearest
enum rounding_mode { to_nearest, towards_zero, downward, upward };
static rounding_mode
@@ -177,44 +400,206 @@ public:
static rounding_mode
setround(rounding_mode mode);
/** Returns which mantissa scale is currently in use for normalization.
*
* If you think you need to call this outside of unit tests, no you don't.
*/
static MantissaRange::mantissa_scale
getMantissaScale();
/** Changes which mantissa scale is used for normalization.
*
* If you think you need to call this outside of unit tests, no you don't.
*/
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 <Integral64 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};
static_assert(isPowerOfTen(smallRange.min));
static_assert(smallRange.min == 1'000'000'000'000'000LL);
static_assert(smallRange.max == 9'999'999'999'999'999LL);
static_assert(smallRange.log == 15);
static_assert(smallRange.min < maxRep);
static_assert(smallRange.max < maxRep);
constexpr static MantissaRange largeRange{MantissaRange::large};
static_assert(isPowerOfTen(largeRange.min));
static_assert(largeRange.min == 1'000'000'000'000'000'000ULL);
static_assert(largeRange.max == internalrep(9'999'999'999'999'999'999ULL));
static_assert(largeRange.log == 18);
static_assert(largeRange.min < maxRep);
static_assert(largeRange.max > maxRep);
// 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();
constexpr bool
/** Normalize Number components to an arbitrary range.
*
* min/maxMantissa are parameters because this function is used by both
* normalize(), which reads from range_, and by normalizeToRange,
* which is public and can accept an arbitrary range from the caller.
*/
template <class T>
static void
normalize(
bool& negative,
T& mantissa,
int& exponent,
internalrep const& minMantissa,
internalrep const& maxMantissa);
template <class T>
friend void
doNormalize(
bool& negative,
T& mantissa_,
int& exponent_,
MantissaRange::rep const& minMantissa,
MantissaRange::rep const& maxMantissa);
bool
isnormal() const noexcept;
// Copy the number, but modify the exponent by "exponentDelta". Because the
// mantissa doesn't change, the result will be "mostly" normalized, but the
// exponent could go out of range, so it will be checked.
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;
};
inline constexpr Number::Number(
bool negative,
internalrep mantissa,
int exponent,
unchecked) noexcept
: negative_(negative), mantissa_{mantissa}, exponent_{exponent}
{
}
inline constexpr Number::Number(
internalrep mantissa,
int exponent,
unchecked) noexcept
: Number(false, mantissa, exponent, unchecked{})
{
}
constexpr static Number numZero{};
inline constexpr Number::Number(rep mantissa, int exponent, unchecked) noexcept
: mantissa_{mantissa}, exponent_{exponent}
inline Number::Number(
bool negative,
internalrep mantissa,
int exponent,
normalized)
: Number(negative, mantissa, exponent, unchecked{})
{
normalize();
}
inline Number::Number(internalrep mantissa, int exponent, normalized)
: Number(false, mantissa, exponent, normalized{})
{
}
inline Number::Number(rep mantissa, int exponent)
: mantissa_{mantissa}, exponent_{exponent}
: Number(mantissa < 0, externalToInternal(mantissa), exponent, normalized{})
{
normalize();
}
inline Number::Number(rep mantissa) : Number{mantissa, 0}
{
}
/** Returns the mantissa of the external view of the Number.
*
* Please see the "---- External Interface ----" section of the class
* documentation for an explanation of why the internal value may be modified.
*/
inline constexpr Number::rep
Number::mantissa() const noexcept
{
return mantissa_;
auto m = mantissa_;
if (m > maxRep)
{
XRPL_ASSERT_PARTS(
!isnormal() || (m % 10 == 0 && m / 10 <= maxRep),
"xrpl::Number::mantissa",
"large normalized mantissa has no remainder");
m /= 10;
}
auto const sign = negative_ ? -1 : 1;
return sign * static_cast<Number::rep>(m);
}
/** Returns the exponent of the external view of the Number.
*
* Please see the "---- External Interface ----" section of the class
* documentation for an explanation of why the internal value may be modified.
*/
inline constexpr int
Number::exponent() const noexcept
{
return exponent_;
auto e = exponent_;
if (mantissa_ > maxRep)
{
XRPL_ASSERT_PARTS(
!isnormal() || (mantissa_ % 10 == 0 && mantissa_ / 10 <= maxRep),
"xrpl::Number::exponent",
"large normalized mantissa has no remainder");
++e;
}
return e;
}
inline constexpr Number
@@ -226,15 +611,17 @@ Number::operator+() const noexcept
inline constexpr Number
Number::operator-() const noexcept
{
if (mantissa_ == 0)
return Number{};
auto x = *this;
x.mantissa_ = -x.mantissa_;
x.negative_ = !x.negative_;
return x;
}
inline Number&
Number::operator++()
{
*this += Number{1000000000000000, -15, unchecked{}};
*this += one();
return *this;
}
@@ -249,7 +636,7 @@ Number::operator++(int)
inline Number&
Number::operator--()
{
*this -= Number{1000000000000000, -15, unchecked{}};
*this -= one();
return *this;
}
@@ -299,30 +686,54 @@ operator/(Number const& x, Number const& y)
return z;
}
inline constexpr Number
inline Number
Number::min() noexcept
{
return Number{minMantissa, minExponent, unchecked{}};
return Number{false, range_.get().min, minExponent, unchecked{}};
}
inline constexpr Number
inline Number
Number::max() noexcept
{
return Number{maxMantissa, maxExponent, unchecked{}};
return Number{
false, std::min(range_.get().max, maxRep), maxExponent, unchecked{}};
}
inline constexpr Number
inline Number
Number::lowest() noexcept
{
return -Number{maxMantissa, maxExponent, unchecked{}};
return Number{
true, std::min(range_.get().max, maxRep), maxExponent, unchecked{}};
}
inline constexpr bool
inline bool
Number::isnormal() const noexcept
{
auto const abs_m = mantissa_ < 0 ? -mantissa_ : mantissa_;
return minMantissa <= abs_m && abs_m <= maxMantissa &&
minExponent <= exponent_ && exponent_ <= maxExponent;
MantissaRange const& range = range_;
auto const abs_m = mantissa_;
return *this == Number{} ||
(range.min <= abs_m && abs_m <= range.max &&
(abs_m <= maxRep || abs_m % 10 == 0) && minExponent <= exponent_ &&
exponent_ <= maxExponent);
}
template <Integral64 T>
std::pair<T, int>
Number::normalizeToRange(T minMantissa, T maxMantissa) const
{
bool negative = negative_;
internalrep mantissa = mantissa_;
int exponent = exponent_;
if constexpr (std::is_unsigned_v<T>)
XRPL_ASSERT_PARTS(
!negative,
"xrpl::Number::normalizeToRange",
"Number is non-negative for unsigned range.");
Number::normalize(negative, mantissa, exponent, minMantissa, maxMantissa);
auto const sign = negative ? -1 : 1;
return std::make_pair(static_cast<T>(sign * mantissa), exponent);
}
inline constexpr Number
@@ -368,6 +779,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_;
@@ -406,6 +831,34 @@ public:
operator=(NumberRoundModeGuard const&) = delete;
};
/** Sets the new scale and restores the old scale when it leaves scope.
*
* If you think you need to use this class outside of unit tests, no you don't.
*
*/
class NumberMantissaScaleGuard
{
MantissaRange::mantissa_scale const 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 xrpl
#endif // XRPL_BASICS_NUMBER_H_INCLUDED

View File

@@ -0,0 +1,50 @@
#ifndef XRPL_LEDGER_CREDIT_H_INCLUDED
#define XRPL_LEDGER_CREDIT_H_INCLUDED
#include <xrpl/ledger/View.h>
#include <xrpl/protocol/IOUAmount.h>
#include <xrpl/protocol/STAmount.h>
namespace xrpl {
/** Calculate the maximum amount of IOUs that an account can hold
@param ledger the ledger to check against.
@param account the account of interest.
@param issuer the issuer of the IOU.
@param currency the IOU to check.
@return The maximum amount that can be held.
*/
/** @{ */
STAmount
creditLimit(
ReadView const& view,
AccountID const& account,
AccountID const& issuer,
Currency const& currency);
IOUAmount
creditLimit2(
ReadView const& v,
AccountID const& acc,
AccountID const& iss,
Currency const& cur);
/** @} */
/** Returns the amount of IOUs issued by issuer that are held by an account
@param ledger the ledger to check against.
@param account the account of interest.
@param issuer the issuer of the IOU.
@param currency the IOU to check.
*/
/** @{ */
STAmount
creditBalance(
ReadView const& view,
AccountID const& account,
AccountID const& issuer,
Currency const& currency);
/** @} */
} // namespace xrpl
#endif

View File

@@ -61,6 +61,9 @@ enum FreezeHandling { fhIGNORE_FREEZE, fhZERO_IF_FROZEN };
/** Controls the treatment of unauthorized MPT balances */
enum AuthHandling { ahIGNORE_AUTH, ahZERO_IF_UNAUTHORIZED };
/** Controls whether to include the account's full spendable balance */
enum SpendableHandling { shSIMPLE_BALANCE, shFULL_BALANCE };
[[nodiscard]] bool
isGlobalFrozen(ReadView const& view, AccountID const& issuer);
@@ -305,86 +308,57 @@ isLPTokenFrozen(
Issue const& asset,
Issue const& asset2);
// Returns the amount an account can spend without going into debt.
// Returns the amount an account can spend.
//
// <-- saAmount: amount of currency held by account. May be negative.
[[nodiscard]] STAmount
accountHolds(
ReadView const& view,
AccountID const& account,
Currency const& currency,
AccountID const& issuer,
FreezeHandling zeroIfFrozen,
beast::Journal j);
[[nodiscard]] STAmount
accountHolds(
ReadView const& view,
AccountID const& account,
Issue const& issue,
FreezeHandling zeroIfFrozen,
beast::Journal j);
[[nodiscard]] STAmount
accountHolds(
ReadView const& view,
AccountID const& account,
MPTIssue const& mptIssue,
FreezeHandling zeroIfFrozen,
AuthHandling zeroIfUnauthorized,
beast::Journal j);
[[nodiscard]] STAmount
accountHolds(
ReadView const& view,
AccountID const& account,
Asset const& asset,
FreezeHandling zeroIfFrozen,
AuthHandling zeroIfUnauthorized,
beast::Journal j);
// Returns the amount an account can spend total.
// If shSIMPLE_BALANCE is specified, this is the amount the account can spend
// without going into debt.
//
// These functions use accountHolds, but unlike accountHolds:
// * The account can go into debt.
// * If the account is the asset issuer the only limit is defined by the asset /
// If shFULL_BALANCE is specified, this is the amount the account can spend
// total. Specifically:
// * The account can go into debt if using a trust line, and the other side has
// a non-zero limit.
// * If the account is the asset issuer the limit is defined by the asset /
// issuance.
//
// <-- saAmount: amount of currency held by account. May be negative.
[[nodiscard]] STAmount
accountSpendable(
accountHolds(
ReadView const& view,
AccountID const& account,
Currency const& currency,
AccountID const& issuer,
FreezeHandling zeroIfFrozen,
beast::Journal j);
beast::Journal j,
SpendableHandling includeFullBalance = shSIMPLE_BALANCE);
[[nodiscard]] STAmount
accountSpendable(
accountHolds(
ReadView const& view,
AccountID const& account,
Issue const& issue,
FreezeHandling zeroIfFrozen,
beast::Journal j);
beast::Journal j,
SpendableHandling includeFullBalance = shSIMPLE_BALANCE);
[[nodiscard]] STAmount
accountSpendable(
accountHolds(
ReadView const& view,
AccountID const& account,
MPTIssue const& mptIssue,
FreezeHandling zeroIfFrozen,
AuthHandling zeroIfUnauthorized,
beast::Journal j);
beast::Journal j,
SpendableHandling includeFullBalance = shSIMPLE_BALANCE);
[[nodiscard]] STAmount
accountSpendable(
accountHolds(
ReadView const& view,
AccountID const& account,
Asset const& asset,
FreezeHandling zeroIfFrozen,
AuthHandling zeroIfUnauthorized,
beast::Journal j);
beast::Journal j,
SpendableHandling includeFullBalance = shSIMPLE_BALANCE);
// Returns the amount an account can spend of the currency type saDefault, or
// returns saDefault if this account is the issuer of the currency in
@@ -655,7 +629,7 @@ createPseudoAccount(
uint256 const& pseudoOwnerKey,
SField const& ownerField);
// Returns true iff sleAcct is a pseudo-account or specific
// Returns true if and only if sleAcct is a pseudo-account or specific
// pseudo-accounts in pseudoFieldFilter.
//
// Returns false if sleAcct is
@@ -710,13 +684,16 @@ checkDestinationAndTag(SLE::const_ref toSle, bool hasDestinationTag);
* - If withdrawing to self, succeed.
* - If not, checks if the receiver requires deposit authorization, and if
* the sender has it.
* - Checks that the receiver will not exceed the limit (IOU trustline limit
* or MPT MaximumAmount).
*/
[[nodiscard]] TER
canWithdraw(
AccountID const& from,
ReadView const& view,
AccountID const& from,
AccountID const& to,
SLE::const_ref toSle,
STAmount const& amount,
bool hasDestinationTag);
/** Checks that can withdraw funds from an object to itself or a destination.
@@ -730,12 +707,15 @@ canWithdraw(
* - If withdrawing to self, succeed.
* - If not, checks if the receiver requires deposit authorization, and if
* the sender has it.
* - Checks that the receiver will not exceed the limit (IOU trustline limit
* or MPT MaximumAmount).
*/
[[nodiscard]] TER
canWithdraw(
AccountID const& from,
ReadView const& view,
AccountID const& from,
AccountID const& to,
STAmount const& amount,
bool hasDestinationTag);
/** Checks that can withdraw funds from an object to itself or a destination.
@@ -749,6 +729,8 @@ canWithdraw(
* - If withdrawing to self, succeed.
* - If not, checks if the receiver requires deposit authorization, and if
* the sender has it.
* - Checks that the receiver will not exceed the limit (IOU trustline limit
* or MPT MaximumAmount).
*/
[[nodiscard]] TER
canWithdraw(ReadView const& view, STTx const& tx);

View File

@@ -121,7 +121,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

@@ -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,18 +40,14 @@ private:
void
normalize();
public:
/* The range for the mantissa when normalized */
static std::int64_t constexpr minMantissa = 1000000000000000ull;
static std::int64_t constexpr maxMantissa = 9999999999999999ull;
/* The range for the exponent when normalized */
static int constexpr minExponent = -96;
static int constexpr maxExponent = 80;
static IOUAmount
fromNumber(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);
@@ -78,10 +76,10 @@ public:
int
signum() const noexcept;
int
exponent_type
exponent() const noexcept;
std::int64_t
mantissa_type
mantissa() const noexcept;
static IOUAmount
@@ -99,7 +97,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();
@@ -156,13 +154,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

@@ -233,6 +233,7 @@ std::size_t constexpr maxMPTokenMetadataLength = 1024;
/** The maximum amount of MPTokenIssuance */
std::uint64_t constexpr maxMPTokenAmount = 0x7FFF'FFFF'FFFF'FFFFull;
static_assert(Number::maxRep >= maxMPTokenAmount);
/** The maximum length of Data payload */
std::size_t constexpr maxDataPayloadLength = 256;

View File

@@ -135,7 +135,10 @@ public:
sMD_Always = 0x10, // value when node containing it is affected at all
sMD_BaseTen = 0x20, // value is treated as base 10, overriding behavior
sMD_PseudoAccount = 0x40, // if this field is set in an ACCOUNT_ROOT
// _only_, then it is a pseudo-account
// _only_, then it is a pseudo-account
sMD_NeedsAsset = 0x80, // This field needs to be associated with an
// asset before it is serialized as a ledger
// object. Intended for STNumber.
sMD_Default =
sMD_ChangeOrig | sMD_ChangeNew | sMD_DeleteFinal | sMD_Create
};

View File

@@ -138,7 +138,7 @@ public:
template <AssetType A>
STAmount(A const& asset, Number const& number)
: STAmount(asset, number.mantissa(), number.exponent())
: STAmount(fromNumber(asset, number))
{
}
@@ -282,6 +282,10 @@ public:
mpt() const;
private:
template <AssetType A>
static STAmount
fromNumber(A const& asset, Number const& number);
static std::unique_ptr<STAmount>
construct(SerialIter&, SField const& name);
@@ -345,10 +349,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(),
"xrpl::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(),
"xrpl::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();
}
@@ -542,14 +555,23 @@ STAmount::operator=(XRPAmount const& amount)
return *this;
}
inline STAmount&
STAmount::operator=(Number const& number)
template <AssetType A>
inline STAmount
STAmount::fromNumber(A const& a, Number const& number)
{
mIsNegative = number.mantissa() < 0;
mValue = mIsNegative ? -number.mantissa() : number.mantissa();
mOffset = number.exponent();
canonicalize();
return *this;
bool const negative = number.mantissa() < 0;
Number const working{negative ? -number : number};
Asset asset{a};
if (asset.integral())
{
std::uint64_t const intValue = static_cast<std::int64_t>(working);
return STAmount{asset, intValue, 0, negative};
}
auto const [mantissa, exponent] =
working.normalizeToRange(cMinValue, cMaxValue);
return STAmount{asset, mantissa, exponent, negative};
}
inline void
@@ -699,17 +721,32 @@ getRate(STAmount const& offerOut, STAmount const& offerIn);
* @param rounding Optional Number rounding mode
*
*/
STAmount
[[nodiscard]] STAmount
roundToScale(
STAmount const& value,
std::int32_t scale,
Number::rounding_mode rounding = Number::getround());
/** Round an arbitrary precision Number IN PLACE to the precision of a given
* Asset.
*
* This is used to ensure that calculations do not collect dust for IOUs, or
* fractional amounts for the integral types XRP and MPT.
*
* @param asset The relevant asset
* @param value The lvalue to be rounded
*/
template <AssetType A>
void
roundToAsset(A const& asset, Number& value)
{
value = STAmount{asset, value};
}
/** Round an arbitrary precision Number to the precision of a given Asset.
*
* This is used to ensure that calculations do not collect dust beyond the
* precision of the reference value for IOUs, or fractional amounts for the
* integral types XRP and MPT.
* This is used to ensure that calculations do not collect dust beyond specified
* scale for IOUs, or fractional amounts for the integral types XRP and MPT.
*
* @param asset The relevant asset
* @param value The value to be rounded
@@ -718,7 +755,7 @@ roundToScale(
* @param rounding Optional Number rounding mode
*/
template <AssetType A>
Number
[[nodiscard]] Number
roundToAsset(
A const& asset,
Number const& value,

View File

@@ -4,6 +4,7 @@
#include <xrpl/basics/CountedObject.h>
#include <xrpl/basics/Number.h>
#include <xrpl/protocol/STBase.h>
#include <xrpl/protocol/STTakesAsset.h>
#include <ostream>
@@ -19,8 +20,19 @@ namespace xrpl {
* it can represent a value of any token type (XRP, IOU, or MPT)
* without paying the storage cost of duplicating asset information
* that may be deduced from the context.
*
* STNumber derives from STTakesAsset, so that it can be associated with the
* related Asset during transaction processing. Which asset is relevant depends
* on the object and transaction. As of this writing, only Vault, LoanBroker,
* and Loan objects use STNumber fields. All of those fields represent amounts
* of the Vault's Asset, so they should be associated with the Vault's Asset.
*
* e.g.
* associateAsset(*loanSle, asset);
* associateAsset(*brokerSle, asset);
* associateAsset(*vaultSle, asset);
*/
class STNumber : public STBase, public CountedObject<STNumber>
class STNumber : public STTakesAsset, public CountedObject<STNumber>
{
private:
Number value_;
@@ -56,6 +68,9 @@ public:
bool
isDefault() const override;
void
associateAsset(Asset const& a) override;
operator Number() const
{
return value_;

View File

@@ -0,0 +1,63 @@
#ifndef XRPL_PROTOCOL_STTAKESASSET_H_INCLUDED
#define XRPL_PROTOCOL_STTAKESASSET_H_INCLUDED
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/STBase.h>
namespace xrpl {
/** Intermediate class for any STBase-derived class to store an Asset.
*
* In the class definition, this class should be specified as a base class
* _instead_ of STBase.
*
* Specifically, the Asset is only stored and used at runtime. It should not be
* serialized to the ledger.
*
* The derived class decides what to do with the Asset, and when. It will not
* necessarily be set at any given time. As of this writing, only STNumber uses
* it to round the stored Number to the Asset's precision both when associated,
* and when serializing the Number.
*/
class STTakesAsset : public STBase
{
protected:
std::optional<Asset> asset_;
public:
using STBase::STBase;
using STBase::operator=;
virtual void
associateAsset(Asset const& a);
};
inline void
STTakesAsset::associateAsset(Asset const& a)
{
asset_.emplace(a);
}
class STLedgerEntry;
/** Associate an Asset with all sMD_NeedsAsset fields in a ledger entry.
*
* This function iterates over all fields in the given ledger entry. For each
* field that is set and has the SField::sMD_NeedsAsset metadata flag, it calls
* `associateAsset` on that field with the given Asset. Such field must be
* derived from STTakesAsset - if it is not, the conversion will throw.
*
* Typically, associateAsset should be called near the end of doApply() of any
* Transactor classes on the SLEs of any new or modified ledger entries
* containing STNumber fields, after doing all of the modifications t the SLEs.
*
* @param sle The ledger entry whose fields will be updated.
* @param asset The Asset to associate with the relevant fields.
*
*/
void
associateAsset(STLedgerEntry& sle, Asset const& asset);
} // namespace xrpl
#endif

View File

@@ -23,6 +23,8 @@ 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);
static_assert(Number::maxRep >= INITIAL_XRP.drops());
/** Returns true if the amount does not exceed the initial XRP in existence. */
inline bool

View File

@@ -18,7 +18,7 @@
XRPL_FEATURE(SmartEscrow, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (BatchInnerSigs, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(LendingProtocol, Supported::no, VoteBehavior::DefaultNo)
XRPL_FEATURE(LendingProtocol, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(PermissionDelegationV1_1, Supported::no, VoteBehavior::DefaultNo)
XRPL_FIX (DirectoryLimit, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (IncludeKeyletFields, Supported::yes, VoteBehavior::DefaultNo)
@@ -32,7 +32,7 @@ XRPL_FIX (EnforceNFTokenTrustlineV2, Supported::yes, VoteBehavior::DefaultNo
XRPL_FIX (AMMv1_3, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(PermissionedDEX, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(Batch, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(SingleAssetVault, Supported::no, VoteBehavior::DefaultNo)
XRPL_FEATURE(SingleAssetVault, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (PayChanCancelAfter, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (InvalidTxFlags, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (FrozenLPTokenTransfer, Supported::yes, VoteBehavior::DefaultNo)

View File

@@ -548,7 +548,7 @@ LEDGER_ENTRY(ltLOAN, 0x0089, Loan, loan, ({
{sfStartDate, soeREQUIRED},
{sfPaymentInterval, soeREQUIRED},
{sfGracePeriod, soeDEFAULT},
{sfPreviousPaymentDate, soeDEFAULT},
{sfPreviousPaymentDueDate, soeDEFAULT},
{sfNextPaymentDueDate, soeDEFAULT},
// The loan object tracks these values:
//

View File

@@ -102,7 +102,7 @@ TYPED_SFIELD(sfMutableFlags, UINT32, 53)
TYPED_SFIELD(sfStartDate, UINT32, 54)
TYPED_SFIELD(sfPaymentInterval, UINT32, 55)
TYPED_SFIELD(sfGracePeriod, UINT32, 56)
TYPED_SFIELD(sfPreviousPaymentDate, UINT32, 57)
TYPED_SFIELD(sfPreviousPaymentDueDate, UINT32, 57)
TYPED_SFIELD(sfNextPaymentDueDate, UINT32, 58)
TYPED_SFIELD(sfPaymentRemaining, UINT32, 59)
TYPED_SFIELD(sfPaymentTotal, UINT32, 60)
@@ -212,22 +212,22 @@ TYPED_SFIELD(sfLoanID, UINT256, 38)
// number (common)
TYPED_SFIELD(sfNumber, NUMBER, 1)
TYPED_SFIELD(sfAssetsAvailable, NUMBER, 2)
TYPED_SFIELD(sfAssetsMaximum, NUMBER, 3)
TYPED_SFIELD(sfAssetsTotal, NUMBER, 4)
TYPED_SFIELD(sfLossUnrealized, NUMBER, 5)
TYPED_SFIELD(sfDebtTotal, NUMBER, 6)
TYPED_SFIELD(sfDebtMaximum, NUMBER, 7)
TYPED_SFIELD(sfCoverAvailable, NUMBER, 8)
TYPED_SFIELD(sfAssetsAvailable, NUMBER, 2, SField::sMD_NeedsAsset | SField::sMD_Default)
TYPED_SFIELD(sfAssetsMaximum, NUMBER, 3, SField::sMD_NeedsAsset | SField::sMD_Default)
TYPED_SFIELD(sfAssetsTotal, NUMBER, 4, SField::sMD_NeedsAsset | SField::sMD_Default)
TYPED_SFIELD(sfLossUnrealized, NUMBER, 5, SField::sMD_NeedsAsset | SField::sMD_Default)
TYPED_SFIELD(sfDebtTotal, NUMBER, 6, SField::sMD_NeedsAsset | SField::sMD_Default)
TYPED_SFIELD(sfDebtMaximum, NUMBER, 7, SField::sMD_NeedsAsset | SField::sMD_Default)
TYPED_SFIELD(sfCoverAvailable, NUMBER, 8, SField::sMD_NeedsAsset | SField::sMD_Default)
TYPED_SFIELD(sfLoanOriginationFee, NUMBER, 9)
TYPED_SFIELD(sfLoanServiceFee, NUMBER, 10)
TYPED_SFIELD(sfLatePaymentFee, NUMBER, 11)
TYPED_SFIELD(sfClosePaymentFee, NUMBER, 12)
TYPED_SFIELD(sfPrincipalOutstanding, NUMBER, 13)
TYPED_SFIELD(sfPrincipalOutstanding, NUMBER, 13, SField::sMD_NeedsAsset | SField::sMD_Default)
TYPED_SFIELD(sfPrincipalRequested, NUMBER, 14)
TYPED_SFIELD(sfTotalValueOutstanding, NUMBER, 15)
TYPED_SFIELD(sfTotalValueOutstanding, NUMBER, 15, SField::sMD_NeedsAsset | SField::sMD_Default)
TYPED_SFIELD(sfPeriodicPayment, NUMBER, 16)
TYPED_SFIELD(sfManagementFeeOutstanding, NUMBER, 17)
TYPED_SFIELD(sfManagementFeeOutstanding, NUMBER, 17, SField::sMD_NeedsAsset | SField::sMD_Default)
// 32-bit signed (common)
TYPED_SFIELD(sfLoanScale, INT32, 1)