Cool idea: Make Loan.NextDueDate optional; clear it when complete

- Check "simple" loanMakePayment failure conditions early
This commit is contained in:
Ed Hennis
2025-10-05 16:03:38 -05:00
parent a8de91c57d
commit 0b8cd2d7ca
2 changed files with 42 additions and 27 deletions

View File

@@ -562,7 +562,7 @@ LEDGER_ENTRY(ltLOAN, 0x0089, Loan, loan, ({
{sfGracePeriod, soeREQUIRED}, {sfGracePeriod, soeREQUIRED},
{sfPeriodicPayment, soeREQUIRED}, {sfPeriodicPayment, soeREQUIRED},
{sfPreviousPaymentDate, soeDEFAULT}, {sfPreviousPaymentDate, soeDEFAULT},
{sfNextPaymentDueDate, soeREQUIRED}, {sfNextPaymentDueDate, soeOPTIONAL},
{sfPaymentRemaining, soeDEFAULT}, {sfPaymentRemaining, soeDEFAULT},
{sfPrincipalOutstanding, soeDEFAULT}, {sfPrincipalOutstanding, soeDEFAULT},
{sfTotalValueOutstanding, soeDEFAULT}, {sfTotalValueOutstanding, soeDEFAULT},

View File

@@ -254,7 +254,7 @@ struct PaymentComponentsPlus : public PaymentComponents
} }
}; };
template <class NumberProxy, class Int32Proxy> template <class NumberProxy, class Int32Proxy, class Int32OptionalProxy>
LoanPaymentParts LoanPaymentParts
doPayment( doPayment(
PaymentComponentsPlus const& payment, PaymentComponentsPlus const& payment,
@@ -263,9 +263,13 @@ doPayment(
NumberProxy& referencePrincipalProxy, NumberProxy& referencePrincipalProxy,
Int32Proxy& paymentRemainingProxy, Int32Proxy& paymentRemainingProxy,
Int32Proxy& prevPaymentDateProxy, Int32Proxy& prevPaymentDateProxy,
Int32Proxy& nextDueDateProxy, Int32OptionalProxy& nextDueDateProxy,
std::uint32_t paymentInterval) std::uint32_t paymentInterval)
{ {
XRPL_ASSERT_PARTS(
nextDueDateProxy,
"ripple::detail::doPayment",
"Next due date proxy set");
if (payment.final) if (payment.final)
{ {
paymentRemainingProxy = 0; paymentRemainingProxy = 0;
@@ -283,16 +287,19 @@ doPayment(
"ripple::detail::doPayment", "ripple::detail::doPayment",
"Full value payment"); "Full value payment");
prevPaymentDateProxy = nextDueDateProxy; prevPaymentDateProxy = *nextDueDateProxy;
// May as well... // Remove the field. This is the only condition where nextDueDate is
nextDueDateProxy = 0; // allowed to be removed.
nextDueDateProxy = std::nullopt;
} }
else else
{ {
paymentRemainingProxy -= 1; paymentRemainingProxy -= 1;
prevPaymentDateProxy = nextDueDateProxy; prevPaymentDateProxy = *nextDueDateProxy;
nextDueDateProxy += paymentInterval; // STObject::OptionalField does not define operator+=, and I don't want
// to add one right now.
nextDueDateProxy = *nextDueDateProxy + paymentInterval;
} }
// A single payment always pays the same amount of principal. Only the // A single payment always pays the same amount of principal. Only the
// interest and fees are extra for a late payment // interest and fees are extra for a late payment
@@ -321,21 +328,21 @@ doPayment(
* labeled "the payment is late" * labeled "the payment is late"
* * section 3.2.4.1.2 (Late Payment) * * section 3.2.4.1.2 (Late Payment)
*/ */
template <AssetType A, class NumberProxy, class Int32Proxy> template <AssetType A>
Expected<PaymentComponentsPlus, TER> Expected<PaymentComponentsPlus, TER>
handleLatePayment( handleLatePayment(
A const& asset, A const& asset,
ApplyView& view, ApplyView const& view,
NumberProxy& principalOutstandingProxy, Number const& principalOutstanding,
Int32Proxy& nextDueDateProxy, std::int32_t nextDueDate,
PaymentComponentsPlus const& periodic, PaymentComponentsPlus const& periodic,
TenthBips32 const lateInterestRate, TenthBips32 lateInterestRate,
std::int32_t loanScale, std::int32_t loanScale,
Number const& latePaymentFee, Number const& latePaymentFee,
STAmount const& amount, STAmount const& amount,
beast::Journal j) beast::Journal j)
{ {
if (!hasExpired(view, nextDueDateProxy)) if (!hasExpired(view, nextDueDate))
return Unexpected(tesSUCCESS); return Unexpected(tesSUCCESS);
// the payment is late // the payment is late
@@ -343,10 +350,10 @@ handleLatePayment(
// being late, as computed by 3.2.4.1.2. // being late, as computed by 3.2.4.1.2.
auto const latePaymentInterest = loanLatePaymentInterest( auto const latePaymentInterest = loanLatePaymentInterest(
asset, asset,
principalOutstandingProxy, principalOutstanding,
lateInterestRate, lateInterestRate,
view.parentCloseTime(), view.parentCloseTime(),
nextDueDateProxy, nextDueDate,
loanScale); loanScale);
XRPL_ASSERT( XRPL_ASSERT(
latePaymentInterest >= 0, latePaymentInterest >= 0,
@@ -770,10 +777,27 @@ loanMakePayment(
* This function is an implementation of the XLS-66 spec, * This function is an implementation of the XLS-66 spec,
* section 3.2.4.3 (Transaction Pseudo-code) * section 3.2.4.3 (Transaction Pseudo-code)
*/ */
auto principalOutstandingProxy = loan->at(sfPrincipalOutstanding);
auto paymentRemainingProxy = loan->at(sfPaymentRemaining);
if (paymentRemainingProxy == 0 || principalOutstandingProxy == 0)
{
// Loan complete
JLOG(j.warn()) << "Loan is already paid off.";
return Unexpected(tecKILLED);
}
// Next payment due date must be set unless the loan is complete
auto nextDueDateProxy = loan->at(~sfNextPaymentDueDate);
if (!nextDueDateProxy)
{
JLOG(j.warn()) << "Loan next payment due date is not set.";
return Unexpected(tecINTERNAL);
}
std::int32_t const loanScale = loan->at(sfLoanScale); std::int32_t const loanScale = loan->at(sfLoanScale);
auto totalValueOutstandingProxy = loan->at(sfTotalValueOutstanding); auto totalValueOutstandingProxy = loan->at(sfTotalValueOutstanding);
auto interestOwedProxy = loan->at(sfInterestOwed); auto interestOwedProxy = loan->at(sfInterestOwed);
auto principalOutstandingProxy = loan->at(sfPrincipalOutstanding);
auto referencePrincipalProxy = loan->at(sfReferencePrincipal); auto referencePrincipalProxy = loan->at(sfReferencePrincipal);
bool const allowOverpayment = loan->isFlag(lsfLoanOverpayment); bool const allowOverpayment = loan->isFlag(lsfLoanOverpayment);
@@ -790,18 +814,9 @@ loanMakePayment(
TenthBips32 const overpaymentFee{loan->at(sfOverpaymentFee)}; TenthBips32 const overpaymentFee{loan->at(sfOverpaymentFee)};
auto const periodicPayment = loan->at(sfPeriodicPayment); auto const periodicPayment = loan->at(sfPeriodicPayment);
auto paymentRemainingProxy = loan->at(sfPaymentRemaining);
auto prevPaymentDateProxy = loan->at(sfPreviousPaymentDate); auto prevPaymentDateProxy = loan->at(sfPreviousPaymentDate);
std::uint32_t const startDate = loan->at(sfStartDate); std::uint32_t const startDate = loan->at(sfStartDate);
auto nextDueDateProxy = loan->at(sfNextPaymentDueDate);
if (paymentRemainingProxy == 0 || principalOutstandingProxy == 0)
{
// Loan complete
JLOG(j.warn()) << "Loan is already paid off.";
return Unexpected(tecKILLED);
}
std::uint32_t const paymentInterval = loan->at(sfPaymentInterval); std::uint32_t const paymentInterval = loan->at(sfPaymentInterval);
// Compute the normal periodic rate, payment, etc. // Compute the normal periodic rate, payment, etc.
@@ -840,7 +855,7 @@ loanMakePayment(
asset, asset,
view, view,
principalOutstandingProxy, principalOutstandingProxy,
nextDueDateProxy, *nextDueDateProxy,
periodic, periodic,
lateInterestRate, lateInterestRate,
loanScale, loanScale,