From 6ad7d1c076a0b973e23361d23ee6cd3148f1eb4e Mon Sep 17 00:00:00 2001 From: Ed Hennis Date: Thu, 23 Oct 2025 01:02:03 -0400 Subject: [PATCH] Rewrite functionality calculations to use updated calculations --- src/xrpld/app/misc/LendingHelpers.h | 101 ++++++++++++++++------------ 1 file changed, 59 insertions(+), 42 deletions(-) diff --git a/src/xrpld/app/misc/LendingHelpers.h b/src/xrpld/app/misc/LendingHelpers.h index 2c6bc16052..f15402cc83 100644 --- a/src/xrpld/app/misc/LendingHelpers.h +++ b/src/xrpld/app/misc/LendingHelpers.h @@ -595,6 +595,7 @@ template Expected tryOverpayment( A const& asset, + std::int32_t loanScale, PaymentComponentsPlus const& overpaymentComponents, Number& totalValueOutstanding, Number& principalOutstanding, @@ -602,50 +603,52 @@ tryOverpayment( Number& periodicPayment, TenthBips32 interestRate, std::uint32_t paymentInterval, + Number const& periodicRate, std::uint32_t paymentRemaining, std::uint32_t prevPaymentDate, std::optional nextDueDate, TenthBips16 const managementFeeRate, beast::Journal j) { - // Compute what the properties would be if the loan was new in its current - // state. They are not likely to match the original properties. We're - // interested in the error. - auto const loanPropertiesBefore = computeLoanProperties( - asset, - principalOutstanding, - interestRate, - paymentInterval, - paymentRemaining, - managementFeeRate); + auto const raw = calculateRawLoanState( + periodicPayment, periodicRate, paymentRemaining, managementFeeRate); + auto const rounded = calculateRoundedLoanState( + totalValueOutstanding, principalOutstanding, managementFeeOutstanding); - auto const accumulatedTotalValueError = - loanPropertiesBefore.totalValueOutstanding - totalValueOutstanding; - auto const accumulatedFeeError = - loanPropertiesBefore.managementFeeOwedToBroker - - managementFeeOutstanding; + auto const totalValueError = totalValueOutstanding - raw.valueOutstanding; + auto const principalError = principalOutstanding - raw.principalOutstanding; + auto const feeError = managementFeeOutstanding - raw.managementFeeDue; - auto const paymentParts = detail::doPayment( - overpaymentComponents, - totalValueOutstanding, - principalOutstanding, - managementFeeOutstanding, - paymentRemaining, - prevPaymentDate, - nextDueDate, - paymentInterval); + auto const newRawPrincipal = + raw.principalOutstanding - overpaymentComponents.roundedPrincipal; auto newLoanProperties = computeLoanProperties( asset, - principalOutstanding, + newRawPrincipal, interestRate, paymentInterval, paymentRemaining, managementFeeRate); - newLoanProperties.totalValueOutstanding += accumulatedTotalValueError; - newLoanProperties.managementFeeOwedToBroker += accumulatedFeeError; + auto const newRaw = calculateRawLoanState( + newLoanProperties.periodicPayment, + periodicRate, + paymentRemaining, + managementFeeRate); + totalValueOutstanding = roundToAsset( + asset, newRaw.valueOutstanding + totalValueError, loanScale); + principalOutstanding = roundToAsset( + asset, + newRaw.principalOutstanding + principalError, + loanScale, + Number::downward); + managementFeeOutstanding = + roundToAsset(asset, newRaw.managementFeeDue + feeError, loanScale); + + periodicPayment = newLoanProperties.periodicPayment; + + // check that the loan is still valid if (newLoanProperties.firstPaymentPrincipal <= 0 && principalOutstanding > 0) { @@ -660,7 +663,7 @@ tryOverpayment( // Check that the other computed values are valid if (newLoanProperties.periodicPayment <= 0 || newLoanProperties.totalValueOutstanding <= 0 || - newLoanProperties.managementFeeOwedToBroker <= 0) + newLoanProperties.managementFeeOwedToBroker < 0) { // LCOV_EXCL_START JLOG(j.warn()) << "Overpayment not allowed: Computed loan " @@ -675,16 +678,29 @@ tryOverpayment( // LCOV_EXCL_STOP } - totalValueOutstanding = newLoanProperties.totalValueOutstanding; - periodicPayment = newLoanProperties.periodicPayment; + auto const newRounded = calculateRoundedLoanState( + totalValueOutstanding, principalOutstanding, managementFeeOutstanding); + auto const valueChange = + newRounded.interestOutstanding - rounded.interestOutstanding; + XRPL_ASSERT_PARTS( + valueChange < beast::zero, + "ripple::detail::tryOverpayment", + "principal overpayment reduced value of loan"); - return paymentParts; + return LoanPaymentParts{ + .principalPaid = + rounded.principalOutstanding - newRounded.principalOutstanding, + .interestPaid = rounded.interestDue - newRounded.interestDue, + .valueChange = valueChange + overpaymentComponents.valueChange, + .feePaid = rounded.managementFeeDue - newRounded.managementFeeDue + + overpaymentComponents.extraFee}; } template Expected computeOverpayment( A const& asset, + std::int32_t loanScale, PaymentComponentsPlus const& overpaymentComponents, NumberProxy& totalValueOutstandingProxy, NumberProxy& principalOutstandingProxy, @@ -692,6 +708,7 @@ computeOverpayment( NumberProxy& periodicPaymentProxy, TenthBips32 const interestRate, std::uint32_t const paymentInterval, + Number const& periodicRate, std::uint32_t const paymentRemaining, std::uint32_t const prevPaymentDate, std::optional const nextDueDate, @@ -707,6 +724,7 @@ computeOverpayment( auto const ret = tryOverpayment( asset, + loanScale, overpaymentComponents, totalValueOutstanding, principalOutstanding, @@ -714,6 +732,7 @@ computeOverpayment( periodicPayment, interestRate, paymentInterval, + periodicRate, paymentRemaining, prevPaymentDate, nextDueDate, @@ -743,8 +762,7 @@ computeOverpayment( XRPL_ASSERT_PARTS( managementFeeOutstandingProxy - managementFeeOutstanding == - overpaymentComponents.roundedManagementFee && - overpaymentComponents.roundedManagementFee == beast::zero, + overpaymentComponents.roundedManagementFee, "ripple::detail::computeOverpayment", "no fee change"); @@ -760,14 +778,7 @@ computeOverpayment( overpaymentComponents.roundedPrincipal, "ripple::detail::computeOverpayment", "principal payment matches"); - XRPL_ASSERT_PARTS( - loanPaymentParts.interestPaid == overpaymentComponents.roundedInterest, - "ripple::detail::computeOverpayment", - "interest payment matches"); - XRPL_ASSERT_PARTS( - loanPaymentParts.valueChange == overpaymentComponents.valueChange, - "ripple::detail::computeOverpayment", - "value change matches"); + XRPL_ASSERT_PARTS( loanPaymentParts.feePaid == overpaymentComponents.extraFee + @@ -1768,10 +1779,15 @@ loanMakePayment( if (overpaymentComponents.rawPrincipal > 0 && overpaymentComponents.roundedPrincipal > 0) { - // Can't just use periodicPayment here, because it might change + XRPL_ASSERT_PARTS( + overpaymentComponents.valueChange >= beast::zero, + "ripple::loanMakePayment", + "overpayment penalty did not reduce value of loan"); + // Can't just use `periodicPayment` here, because it might change auto periodicPaymentProxy = loan->at(sfPeriodicPayment); if (auto const overResult = detail::computeOverpayment( asset, + loanScale, overpaymentComponents, totalValueOutstandingProxy, principalOutstandingProxy, @@ -1779,6 +1795,7 @@ loanMakePayment( periodicPaymentProxy, interestRate, paymentInterval, + periodicRate, paymentRemainingProxy, prevPaymentDateProxy, nextDueDateProxy,