Compare commits

...

8 Commits

Author SHA1 Message Date
Ed Hennis
f58a0a551b Fix formatting 2026-01-28 19:24:45 -05:00
Ed Hennis
83799db46a Merge branch 'develop' into ximinez/lending-shortages 2026-01-28 19:30:57 -04:00
Ed Hennis
ef97ac2b7a Merge commit '5f638f55536def0d88b970d1018a465a238e55f4' into ximinez/lending-shortages
* commit '5f638f55536def0d88b970d1018a465a238e55f4':
  chore: Set ColumnLimit to 120 in clang-format (6288)
2026-01-28 18:28:42 -05:00
Ed Hennis
3823dbc74c Merge commit '92046785d1fea5f9efe5a770d636792ea6cab78b' into ximinez/lending-shortages
* commit '92046785d1fea5f9efe5a770d636792ea6cab78b':
  test: Fix the `xrpl.net` unit test using async read (6241)
  ci: Upload Conan recipes for develop, release candidates, and releases (6286)
  fix: Stop embedded tests from hanging on ARM by using `atomic_flag` (6248)
  fix:  Remove DEFAULT fields that change to the default in associateAsset (6259) (6273)
  refactor: Update Boost to 1.90 (6280)
  refactor: clean up uses of `std::source_location` (6272)
  ci: Pass missing sanitizers input to actions (6266)
  ci: Properly propagate Conan credentials (6265)
  ci: Explicitly set version when exporting the Conan recipe (6264)
  ci: Use plus instead of hyphen for Conan recipe version suffix (6261)
  chore: Detect uninitialized variables in CMake files (6247)
  ci: Run on-trigger and on-pr when generate-version is modified (6257)
  refactor: Enforce 15-char limit and simplify labels for thread naming (6212)
  docs: Update Ripple Bug Bounty public key (6258)
  ci: Add missing commit hash to Conan recipe version (6256)
  fix: Include `<functional>` header in `Number.h` (6254)
  ci: Upload Conan recipe for merges into develop and commits to release (6235)
  Limit reply size on `TMGetObjectByHash` queries (6110)
  ci: remove 'master' branch as a trigger (6234)
  Improve ledger_entry lookups for fee, amendments, NUNL, and hashes (5644)
2026-01-28 18:27:50 -05:00
Ed Hennis
958a7c12c6 Merge branch 'develop' into ximinez/lending-shortages 2026-01-15 13:16:46 -04:00
Ed Hennis
20d9cb89dd Merge branch 'develop' into ximinez/lending-shortages 2026-01-15 12:06:39 -04:00
Ed Hennis
e105d59b90 Revert "Partially revert aed8e2b166 Fill in payment computation shortages (#5941)"
This reverts commit 95fdbe520f.
2026-01-15 11:03:37 -05:00
Ed Hennis
8cae6b0adc Revert "Remove the shortage code completely"
This reverts commit 165478b929.
2026-01-14 20:43:03 -05:00
2 changed files with 45 additions and 15 deletions

View File

@@ -2346,11 +2346,10 @@ protected:
state.paymentRemaining,
broker.params.managementFeeRate);
BEAST_EXPECTS(
paymentComponents.specialCase == detail::PaymentSpecialCase::final ||
paymentComponents.trackedValueDelta <= roundedPeriodicPayment,
"Delta: " + to_string(paymentComponents.trackedValueDelta) +
", periodic payment: " + to_string(roundedPeriodicPayment));
BEAST_EXPECT(
paymentComponents.trackedValueDelta == roundedPeriodicPayment ||
(paymentComponents.specialCase == detail::PaymentSpecialCase::final &&
paymentComponents.trackedValueDelta < roundedPeriodicPayment));
xrpl::LoanState const nextTrueState = computeTheoreticalLoanState(
state.periodicPayment,

View File

@@ -960,18 +960,30 @@ computePaymentComponents(
}
XRPL_ASSERT_PARTS(excess >= beast::zero, "xrpl::detail::computePaymentComponents", "excess non-negative");
};
// Helper to reduce deltas when they collectively exceed a limit.
// Order matters: we prefer to reduce interest first (most flexible),
// then management fee, then principal (least flexible).
auto giveTo = [](Number& component, Number& shortage, Number const& maximum) {
if (shortage > beast::zero)
{
// Put as much of the shortage as we can into the provided part
// and the total
auto part = std::min(maximum - component, shortage);
component += part;
shortage -= part;
}
// If the shortage goes negative, we put too much, which should be
// impossible
XRPL_ASSERT_PARTS(shortage >= beast::zero, "ripple::detail::computePaymentComponents", "excess non-negative");
};
auto addressExcess = [&takeFrom](LoanStateDeltas& deltas, Number& excess) {
// This order is based on where errors are the least problematic
takeFrom(deltas.interest, excess);
takeFrom(deltas.managementFee, excess);
takeFrom(deltas.principal, excess);
};
// Check if deltas exceed the total outstanding value. This should never
// happen due to earlier caps, but handle it defensively.
auto addressShortage = [&giveTo](LoanStateDeltas& deltas, Number& shortage, LoanState const& current) {
giveTo(deltas.interest, shortage, current.interestDue);
giveTo(deltas.managementFee, shortage, current.managementFeeDue);
giveTo(deltas.principal, shortage, current.principalOutstanding);
};
Number totalOverpayment = deltas.total() - currentLedgerState.valueOutstanding;
if (totalOverpayment > beast::zero)
@@ -997,11 +1009,30 @@ computePaymentComponents(
addressExcess(deltas, excess);
shortage = -excess;
}
else if (shortage > beast::zero && totalOverpayment < beast::zero)
{
// If there's a shortage, and there's room in the loan itself, we can
// top up the parts to make the payment correct.
shortage = std::min(-totalOverpayment, shortage);
addressShortage(deltas, shortage, currentLedgerState);
}
// At this point, shortage >= 0 means we're paying less than the full
// periodic payment (due to rounding or component caps).
// shortage < 0 would mean we're trying to pay more than allowed (bug).
XRPL_ASSERT_PARTS(shortage >= beast::zero, "xrpl::detail::computePaymentComponents", "no shortage or excess");
// The shortage should never be negative, which indicates that the parts are
// trying to take more than the whole payment. The shortage should not be
// positive, either, which indicates that we're not going to take the whole
// payment amount. Only the last payment should be allowed to have a
// shortage, and that's handled in a special case above.
XRPL_ASSERT_PARTS(shortage == beast::zero, "ripple::detail::computePaymentComponents", "no shortage or excess");
#if LOANCOMPLETE
/*
// This used to be part of the above assert. It will eventually be removed
// if proved accurate
||
(shortage > beast::zero &&
((asset.integral() && shortage < 3) ||
(scale - shortage.exponent() > 14)))
*/
#endif
// Final validation that all components are valid
XRPL_ASSERT_PARTS(