Fill in payment computation shortages (#5941)

- Ensures a consistent fixed payment amount for the entire life of the
  loan, except the final payment, which is guaranteed to be the same or
  smaller.
- Convert some Loan structs to compute values that had need manual
  updates to stay consistent.
- Fail the transaction in `LoanPay` if it violates the Vault `assetsAvailable <=
  assetsTotal` invariant.
- Use constexpr to check that min mantissa value for Number and STAmount
  is a power of 10, and compute the max in terms of the min.
- Improve unit tests:
  - Use BrokerParameters and Loan Parameters instead of semi-global
    class values
  - In tests, check that the expected number of loan payments are made.
  - Add LoanBatch manual test to generate a set number of random loans,
    set them up, and pay them off.
  - Add LoanArbitrary manual test to run a single test with specific
    (hard-coded for now) parameters.
  - Add Number support to XRP_t.
This commit is contained in:
Ed Hennis
2025-11-04 17:56:16 -05:00
committed by GitHub
parent 7925cc4052
commit aed8e2b166
12 changed files with 1768 additions and 382 deletions

View File

@@ -32,6 +32,15 @@ class Number;
std::string
to_string(Number const& amount);
template <typename T>
constexpr bool
isPowerOfTen(T value)
{
while (value >= 10 && value % 10 == 0)
value /= 10;
return value == 1;
}
class Number
{
using rep = std::int64_t;
@@ -41,7 +50,9 @@ class Number
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;
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;
@@ -58,8 +69,6 @@ public:
explicit Number(rep mantissa, int exponent);
explicit constexpr Number(rep mantissa, int exponent, unchecked) noexcept;
static Number const zero;
constexpr rep
mantissa() const noexcept;
constexpr int
@@ -153,22 +162,7 @@ public:
}
Number
truncate() const noexcept
{
if (exponent_ >= 0 || mantissa_ == 0)
return *this;
Number ret = *this;
while (ret.exponent_ < 0 && ret.mantissa_ != 0)
{
ret.exponent_ += 1;
ret.mantissa_ /= rep(10);
}
// We are guaranteed that normalize() will never throw an exception
// because exponent is either negative or zero at this point.
ret.normalize();
return ret;
}
truncate() const noexcept;
friend constexpr bool
operator>(Number const& x, Number const& y) noexcept
@@ -213,6 +207,8 @@ private:
class Guard;
};
constexpr static Number numZero{};
inline constexpr Number::Number(rep mantissa, int exponent, unchecked) noexcept
: mantissa_{mantissa}, exponent_{exponent}
{

View File

@@ -102,7 +102,9 @@ std::uint16_t constexpr maxTransferFee = 50000;
* Example: 50% is 0.50 * bipsPerUnity = 5,000 bps.
*/
Bips32 constexpr bipsPerUnity(100 * 100);
static_assert(bipsPerUnity == Bips32{10'000});
TenthBips32 constexpr tenthBipsPerUnity(bipsPerUnity.value() * 10);
static_assert(tenthBipsPerUnity == TenthBips32(100'000));
constexpr Bips32
percentageToBips(std::uint32_t percentage)

View File

@@ -66,16 +66,18 @@ public:
static int const cMaxOffset = 80;
// Maximum native value supported by the code
static std::uint64_t const cMinValue = 1'000'000'000'000'000ull;
static std::uint64_t const cMaxValue = 9'999'999'999'999'999ull;
static std::uint64_t const cMaxNative = 9'000'000'000'000'000'000ull;
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);
constexpr static std::uint64_t cMaxNative = 9'000'000'000'000'000'000ull;
// Max native value on network.
static std::uint64_t const cMaxNativeN = 100'000'000'000'000'000ull;
static std::uint64_t const cIssuedCurrency = 0x8'000'000'000'000'000ull;
static std::uint64_t const cPositive = 0x4'000'000'000'000'000ull;
static std::uint64_t const cMPToken = 0x2'000'000'000'000'000ull;
static std::uint64_t const cValueMask = ~(cPositive | cMPToken);
constexpr static std::uint64_t cMaxNativeN = 100'000'000'000'000'000ull;
constexpr static std::uint64_t cIssuedCurrency = 0x8'000'000'000'000'000ull;
constexpr static std::uint64_t cPositive = 0x4'000'000'000'000'000ull;
constexpr static std::uint64_t cMPToken = 0x2'000'000'000'000'000ull;
constexpr static std::uint64_t cValueMask = ~(cPositive | cMPToken);
static std::uint64_t const uRateOne;