1#include <xrpl/ledger/helpers/LendingHelpers.h>
3#include <xrpl/basics/Log.h>
4#include <xrpl/basics/Number.h>
5#include <xrpl/basics/chrono.h>
6#include <xrpl/beast/utility/Journal.h>
7#include <xrpl/beast/utility/Zero.h>
8#include <xrpl/beast/utility/instrumentation.h>
9#include <xrpl/ledger/ApplyView.h>
10#include <xrpl/ledger/ReadView.h>
11#include <xrpl/ledger/View.h>
12#include <xrpl/protocol/Asset.h>
13#include <xrpl/protocol/Feature.h>
14#include <xrpl/protocol/LedgerFormats.h>
15#include <xrpl/protocol/Protocol.h>
16#include <xrpl/protocol/Rules.h>
17#include <xrpl/protocol/SField.h>
18#include <xrpl/protocol/STAmount.h>
19#include <xrpl/protocol/STLedgerEntry.h>
20#include <xrpl/protocol/STTx.h>
21#include <xrpl/protocol/TER.h>
22#include <xrpl/protocol/Units.h>
37 Asset const& vaultAsset,
43 sleBroker && sleBroker->getType() == ltLOAN_BROKER,
44 "xrpl::canApplyToBrokerCover : valid LoanBroker sle");
45 XRPL_ASSERT(vaultAsset == amount.
asset(),
"xrpl::canApplyToBrokerCover : valid asset");
50 if (amount == beast::kZero)
53 int const coverScale =
scale(sleBroker->at(sfCoverAvailable), vaultAsset);
57 <<
" rounds to zero at cover scale " << coverScale;
67 if (!rules.
enabled(featureSingleAssetVault))
70 if (!rules.
enabled(featureMPTokensV1))
85 "xrpl::LoanPaymentParts::operator+= : other principal "
89 "xrpl::LoanPaymentParts::operator+= : other interest paid "
93 "xrpl::LoanPaymentParts::operator+= : other fee paid "
162 periodicRate >= beast::kZero,
163 "xrpl::detail::computePowerMinusOne",
164 "periodicRate is non-negative");
166 if (paymentsRemaining == 0 || periodicRate == beast::kZero)
170 Number term = paymentsRemaining * periodicRate;
175 term = term * periodicRate * (paymentsRemaining - k) / (k + 1);
201 periodicRate >= beast::kZero,
202 "xrpl::detail::computePowerMinusOneHybrid",
203 "periodicRate is non-negative");
205 if (paymentsRemaining == 0 || periodicRate == beast::kZero)
213 Number const cancellationThreshold{1, -9};
214 if (paymentsRemaining * periodicRate >= cancellationThreshold)
215 return power(1 + periodicRate, paymentsRemaining) - 1;
228 Number const& periodicRate,
231 if (paymentsRemaining == 0)
235 if (periodicRate == beast::kZero)
236 return Number{1} / paymentsRemaining;
238 if (rules.
enabled(fixCleanup3_2_0))
240 Number const raisedRateMinusOne =
242 Number const raisedRate = 1 + raisedRateMinusOne;
244 return (periodicRate * raisedRate) / raisedRateMinusOne;
250 Number const raisedRate =
power(1 + periodicRate, paymentsRemaining);
252 return (periodicRate * raisedRate) / (raisedRate - 1);
263 Number const& principalOutstanding,
264 Number const& periodicRate,
267 if (principalOutstanding == 0 || paymentsRemaining == 0)
271 if (periodicRate == beast::kZero)
272 return principalOutstanding / paymentsRemaining;
285 Number const& periodicPayment,
286 Number const& periodicRate,
289 if (paymentsRemaining == 0)
292 if (periodicRate == 0)
293 return periodicPayment * paymentsRemaining;
322 Number const& principalOutstanding,
327 if (principalOutstanding == beast::kZero)
337 if (now <= nextPaymentDueDate)
341 auto const secondsOverdue = now - nextPaymentDueDate;
345 return principalOutstanding * rate;
355 Number const& principalOutstanding,
356 Number const& periodicRate,
362 if (periodicRate == beast::kZero)
365 if (paymentInterval == 0)
368 auto const lastPaymentDate =
std::max(prevPaymentDate, startDate);
373 if (now <= lastPaymentDate)
377 auto const secondsSinceLastPayment = now - lastPaymentDate;
382 return principalOutstanding * periodicRate * secondsSinceLastPayment / paymentInterval;
394template <
class NumberProxy,
class UInt32Proxy,
class UInt32OptionalProxy>
398 NumberProxy& totalValueOutstandingProxy,
399 NumberProxy& principalOutstandingProxy,
400 NumberProxy& managementFeeOutstandingProxy,
401 UInt32Proxy& paymentRemainingProxy,
402 UInt32Proxy& prevPaymentDateProxy,
403 UInt32OptionalProxy& nextDueDateProxy,
406 XRPL_ASSERT_PARTS(nextDueDateProxy,
"xrpl::detail::doPayment",
"Next due date proxy set");
412 "xrpl::detail::doPayment",
413 "Full principal payment");
416 "xrpl::detail::doPayment",
417 "Full value payment");
420 "xrpl::detail::doPayment",
421 "Full management fee payment");
424 paymentRemainingProxy = 0;
427 prevPaymentDateProxy = *nextDueDateProxy;
431 nextDueDateProxy = 0;
436 principalOutstandingProxy = 0;
437 totalValueOutstandingProxy = 0;
438 managementFeeOutstandingProxy = 0;
445 paymentRemainingProxy -= 1;
447 prevPaymentDateProxy = nextDueDateProxy;
448 nextDueDateProxy += paymentInterval;
452 "xrpl::detail::doPayment",
453 "Partial principal payment");
456 "xrpl::detail::doPayment",
457 "Partial value payment");
462 "xrpl::detail::doPayment",
463 "Valid management fee");
475 static_cast<Number>(principalOutstandingProxy) <=
476 static_cast<Number>(totalValueOutstandingProxy),
477 "xrpl::detail::doPayment",
478 "principal does not exceed total");
483 static_cast<Number>(managementFeeOutstandingProxy) >= beast::kZero,
484 "xrpl::detail::doPayment",
485 "fee outstanding stays valid");
517std::expected<std::pair<LoanPaymentParts, LoanProperties>,
TER>
524 Number const& periodicPayment,
525 Number const& periodicRate,
532 rules, periodicPayment, periodicRate, paymentRemaining, managementFeeRate);
538 auto const errors = roundedOldState - theoreticalState;
542 auto const newTheoreticalPrincipal =
std::max(
552 newTheoreticalPrincipal,
558 JLOG(j.
debug()) <<
"new periodic payment: " << newLoanProperties.periodicPayment
559 <<
", new total value: " << newLoanProperties.loanState.valueOutstanding
560 <<
", first payment principal: " << newLoanProperties.firstPaymentPrincipal;
565 auto const newTheoreticalState = [&]() {
568 newLoanProperties.periodicPayment,
574 if (!rules.
enabled(fixCleanup3_2_0))
586 Number const managementFee =
591 JLOG(j.
debug()) <<
"new theoretical value: " << newTheoreticalState.valueOutstanding
592 <<
", principal: " << newTheoreticalState.principalOutstanding
593 <<
", interest gross: " << newTheoreticalState.interestOutstanding();
601 newTheoreticalState.principalOutstanding,
606 auto const totalValueOutstanding =
std::clamp(
609 principalOutstanding + newTheoreticalState.interestOutstanding(),
614 auto const managementFeeOutstanding =
std::clamp(
615 roundToAsset(asset, newTheoreticalState.managementFeeDue, loanScale),
619 auto const roundedNewState =
620 constructLoanState(totalValueOutstanding, principalOutstanding, managementFeeOutstanding);
624 newLoanProperties.loanState = roundedNewState;
626 JLOG(j.
debug()) <<
"new rounded value: " << roundedNewState.valueOutstanding
627 <<
", principal: " << roundedNewState.principalOutstanding
628 <<
", interest gross: " << roundedNewState.interestOutstanding();
633 principalOutstanding,
638 roundedNewState.interestOutstanding() != beast::kZero,
643 JLOG(j.
warn()) <<
"Principal overpayment would cause the loan to be in "
644 "an invalid state. Ignore the overpayment";
651 if (newLoanProperties.periodicPayment <= 0 ||
652 newLoanProperties.loanState.valueOutstanding <= 0 ||
653 newLoanProperties.loanState.managementFeeDue < 0)
656 JLOG(j.
warn()) <<
"Overpayment not allowed: Computed loan "
657 "properties are invalid. Does "
658 "not compute. TotalValueOutstanding: "
659 << newLoanProperties.loanState.valueOutstanding
660 <<
", PeriodicPayment : " << newLoanProperties.periodicPayment
661 <<
", ManagementFeeOwedToBroker: "
662 << newLoanProperties.loanState.managementFeeDue;
667 auto const deltas = roundedOldState - roundedNewState;
672 deltas.managementFee == roundedOldState.
managementFeeDue - managementFeeOutstanding,
673 "xrpl::detail::tryOverpayment",
683 auto const valueChange = -deltas.interest;
686 JLOG(j.
warn()) <<
"Principal overpayment would increase the value of "
687 "the loan. Ignore the overpayment";
694 .principalPaid = deltas.principal,
720template <
class NumberProxy>
721std::expected<LoanPaymentParts, TER>
727 NumberProxy& totalValueOutstandingProxy,
728 NumberProxy& principalOutstandingProxy,
729 NumberProxy& managementFeeOutstandingProxy,
730 NumberProxy& periodicPaymentProxy,
731 Number const& periodicRate,
737 totalValueOutstandingProxy, principalOutstandingProxy, managementFeeOutstandingProxy);
738 auto const periodicPayment = periodicPaymentProxy;
739 JLOG(j.
debug()) <<
"overpayment components:"
740 <<
", totalValue before: " << *totalValueOutstandingProxy
746 <<
", totalDue: " << overpaymentComponents.
totalDue
747 <<
", payments remaining :" << paymentRemaining;
755 overpaymentComponents,
765 auto const& [loanPaymentParts, newLoanProperties] = *ret;
766 auto const newRoundedLoanState = newLoanProperties.loanState;
771 if (principalOutstandingProxy <= newRoundedLoanState.principalOutstanding)
774 JLOG(j.
warn()) <<
"Overpayment not allowed: principal "
775 <<
"outstanding did not decrease. Before: " << *principalOutstandingProxy
776 <<
". After: " << newRoundedLoanState.principalOutstanding;
784 JLOG(j.
debug()) <<
"valueChange: " << loanPaymentParts.valueChange
785 <<
", totalValue before: " << *totalValueOutstandingProxy
786 <<
", totalValue after: " << newRoundedLoanState.valueOutstanding
787 <<
", totalValue delta: "
788 << (totalValueOutstandingProxy - newRoundedLoanState.valueOutstanding)
790 <<
", principalPaid: " << loanPaymentParts.principalPaid
791 <<
", Computed difference: "
793 (totalValueOutstandingProxy - newRoundedLoanState.valueOutstanding);
816 [[maybe_unused]]
bool const fix320Enabled = rules.
enabled(fixCleanup3_2_0);
820 principalOutstandingProxy - newRoundedLoanState.principalOutstanding,
821 "xrpl::detail::doOverpayment : principal change agrees");
826 Number const tvoChange = newRoundedLoanState.valueOutstanding -
828 Number const managementFeeReleased =
829 managementFeeOutstandingProxy - newRoundedLoanState.managementFeeDue;
831 return loanPaymentParts.valueChange == tvoChange + managementFeeReleased + interestPart;
833 "xrpl::detail::doOverpayment : interest paid agrees");
838 "xrpl::detail::doOverpayment : principal payment matches");
842 totalValueOutstandingProxy = newRoundedLoanState.valueOutstanding;
843 principalOutstandingProxy = newRoundedLoanState.principalOutstanding;
844 managementFeeOutstandingProxy = newRoundedLoanState.managementFeeDue;
845 periodicPaymentProxy = newLoanProperties.periodicPayment;
847 return loanPaymentParts;
863std::expected<ExtendedPaymentComponents, TER>
867 Number const& principalOutstanding,
872 Number const& latePaymentFee,
884 principalOutstanding, lateInterestRate, view.
parentCloseTime(), nextDueDate);
889 auto const [roundedLateInterest, roundedLateManagementFee] = [&]() {
890 auto const interest =
roundToAsset(asset, latePaymentInterest, loanScale);
894 XRPL_ASSERT(roundedLateInterest >= 0,
"xrpl::detail::computeLatePayment : valid late interest");
897 "xrpl::detail::computeLatePayment",
898 "no extra parts to this payment");
922 "xrpl::detail::computeLatePayment",
923 "total due is rounded");
930 JLOG(j.
warn()) <<
"Late loan payment amount is insufficient. Due: " << late.
totalDue
931 <<
", paid: " << amount;
957std::expected<ExtendedPaymentComponents, TER>
961 Number const& principalOutstanding,
962 Number const& managementFeeOutstanding,
963 Number const& periodicPayment,
970 Number const& totalInterestOutstanding,
971 Number const& periodicRate,
972 Number const& closePaymentFee,
978 if (paymentRemaining <= 1)
981 JLOG(j.
warn()) <<
"Last payment cannot be a full payment.";
989 view.
rules(), periodicPayment, periodicRate, paymentRemaining);
994 theoreticalPrincipalOutstanding,
1004 auto const [roundedFullInterest, roundedFullManagementFee] = [&]() {
1005 auto const interest =
1015 .trackedValueDelta =
1016 principalOutstanding + totalInterestOutstanding + managementFeeOutstanding,
1017 .trackedPrincipalDelta = principalOutstanding,
1021 .trackedManagementFeeDelta = managementFeeOutstanding,
1032 closePaymentFee + roundedFullManagementFee - managementFeeOutstanding,
1041 roundedFullInterest - totalInterestOutstanding,
1046 "xrpl::detail::computeFullPayment",
1047 "total due is rounded");
1049 JLOG(j.
trace()) <<
"computeFullPayment result: periodicPayment: " << periodicPayment
1050 <<
", periodicRate: " << periodicRate
1051 <<
", paymentRemaining: " << paymentRemaining
1052 <<
", theoreticalPrincipalOutstanding: " << theoreticalPrincipalOutstanding
1053 <<
", fullPaymentInterest: " << fullPaymentInterest
1054 <<
", roundedFullInterest: " << roundedFullInterest
1055 <<
", roundedFullManagementFee: " << roundedFullManagementFee
1097 Number const& totalValueOutstanding,
1098 Number const& principalOutstanding,
1099 Number const& managementFeeOutstanding,
1100 Number const& periodicPayment,
1101 Number const& periodicRate,
1109 "xrpl::detail::computePaymentComponents",
1110 "Outstanding values are rounded");
1112 paymentRemaining > 0,
"xrpl::detail::computePaymentComponents",
"some payments remaining");
1118 if (paymentRemaining == 1 || totalValueOutstanding <= roundedPeriodicPayment)
1123 .trackedValueDelta = totalValueOutstanding,
1124 .trackedPrincipalDelta = principalOutstanding,
1125 .trackedManagementFeeDelta = managementFeeOutstanding,
1132 rules, periodicPayment, periodicRate, paymentRemaining - 1, managementFeeRate);
1141 bool const fixCleanup320Enabled = rules.
enabled(fixCleanup3_2_0);
1148 .principalOutstanding =
1155 constructLoanState(totalValueOutstanding, principalOutstanding, managementFeeOutstanding);
1168 "xrpl::detail::computePaymentComponents",
1169 "principal delta not greater than outstanding");
1174 if (fixCleanup320Enabled)
1178 "xrpl::detail::computePaymentComponents",
1179 "interest due delta not greater than outstanding");
1190 "xrpl::detail::computePaymentComponents",
1191 "management fee due delta not greater than outstanding");
1204 auto takeFrom = [](
Number& component,
Number& excess) {
1205 if (excess > beast::kZero)
1207 auto part =
std::min(component, excess);
1212 excess >= beast::kZero,
1213 "xrpl::detail::computePaymentComponents",
1214 "excess non-negative");
1230 if (totalOverpayment > beast::kZero)
1234 "xrpl::detail::computePaymentComponents : payment exceeded loan "
1236 addressExcess(deltas, totalOverpayment);
1241 Number shortage = roundedPeriodicPayment - deltas.
total();
1245 "xrpl::detail::computePaymentComponents",
1246 "shortage is rounded");
1248 if (shortage < beast::kZero)
1251 Number excess = -shortage;
1252 addressExcess(deltas, excess);
1260 shortage >= beast::kZero,
1261 "xrpl::detail::computePaymentComponents",
1262 "no shortage or excess");
1267 "xrpl::detail::computePaymentComponents",
1268 "total value adds up");
1273 "xrpl::detail::computePaymentComponents",
1274 "valid principal result");
1277 "xrpl::detail::computePaymentComponents",
1278 "valid interest result");
1282 "xrpl::detail::computePaymentComponents",
1283 "valid fee result");
1287 "xrpl::detail::computePaymentComponents",
1288 "payment parts add to payment");
1292 .trackedValueDelta =
1294 .trackedPrincipalDelta =
1296 .trackedManagementFeeDelta =
1319ExtendedPaymentComponents
1323 int32_t
const loanScale,
1324 Number const& overpayment,
1330 rules.
enabled(fixCleanup3_2_0),
1331 overpayment > 0 &&
isRounded(asset, overpayment, loanScale),
1332 "xrpl::detail::computeOverpaymentComponents : valid overpayment "
1338 Number const overpaymentFee =
1345 auto const [roundedOverpaymentInterest, roundedOverpaymentManagementFee] = [&]() {
1346 auto const interest =
1356 .trackedValueDelta = overpayment - overpaymentFee,
1357 .trackedPrincipalDelta = overpayment - roundedOverpaymentInterest -
1358 roundedOverpaymentManagementFee - overpaymentFee,
1359 .trackedManagementFeeDelta = roundedOverpaymentManagementFee,
1368 roundedOverpaymentInterest};
1370 result.trackedInterestPart() == roundedOverpaymentInterest,
1371 "xrpl::detail::computeOverpaymentComponents",
1372 "valid interest computation");
1378detail::LoanStateDeltas
1418 Asset const& vaultAsset,
1419 Number const& principalRequested,
1420 bool expectInterest,
1425 auto const totalInterestOutstanding =
1430 if (expectInterest && totalInterestOutstanding <= 0)
1434 JLOG(j.
warn()) <<
"Loan for " << principalRequested <<
" with interest has no interest due";
1439 if (!expectInterest && totalInterestOutstanding > 0)
1442 JLOG(j.
warn()) <<
"Loan for " << principalRequested <<
" with no interest has interest due";
1456 JLOG(j.
warn()) <<
"Loan is unable to pay principal.";
1463 auto const roundedPayment =
1465 if (roundedPayment == beast::kZero)
1468 <<
") rounds to 0. ";
1480 computedPayments != paymentTotal)
1483 <<
") rounding (" << roundedPayment <<
") on a total value of "
1485 <<
" can not complete the loan in the specified "
1486 "number of payments ("
1487 << computedPayments <<
" != " << paymentTotal <<
")";
1502 Number const& theoreticalPrincipalOutstanding,
1503 Number const& periodicRate,
1511 theoreticalPrincipalOutstanding,
1518 accruedInterest >= 0,
1519 "xrpl::detail::computeFullPaymentInterest : valid accrued "
1523 auto const prepaymentPenalty = closeInterestRate == beast::kZero
1528 prepaymentPenalty >= 0,
1529 "xrpl::detail::computeFullPaymentInterest : valid prepayment "
1533 return accruedInterest + prepaymentPenalty;
1561 Number const& periodicPayment,
1562 Number const& periodicRate,
1566 if (paymentRemaining == 0)
1569 .valueOutstanding = 0,
1570 .principalOutstanding = 0,
1572 .managementFeeDue = 0};
1576 Number const totalValueOutstanding = periodicPayment * paymentRemaining;
1579 rules, periodicPayment, periodicRate, paymentRemaining);
1582 Number const interestOutstandingGross = totalValueOutstanding - principalOutstanding;
1585 Number const managementFeeOutstanding =
1589 Number const interestOutstandingNet = interestOutstandingGross - managementFeeOutstanding;
1592 .valueOutstanding = totalValueOutstanding,
1593 .principalOutstanding = principalOutstanding,
1594 .interestDue = interestOutstandingNet,
1595 .managementFeeDue = managementFeeOutstanding,
1621 Number const& totalValueOutstanding,
1622 Number const& principalOutstanding,
1623 Number const& managementFeeOutstanding)
1628 .valueOutstanding = totalValueOutstanding,
1629 .principalOutstanding = principalOutstanding,
1630 .interestDue = totalValueOutstanding - principalOutstanding - managementFeeOutstanding,
1631 .managementFeeDue = managementFeeOutstanding};
1638 loan->at(sfTotalValueOutstanding),
1639 loan->at(sfPrincipalOutstanding),
1640 loan->at(sfManagementFeeOutstanding));
1672 Number const& principalOutstanding,
1680 XRPL_ASSERT(interestRate == 0 || periodicRate > 0,
"xrpl::computeLoanProperties : valid rate");
1684 principalOutstanding,
1703 Number const& principalOutstanding,
1704 Number const& periodicRate,
1709 auto const periodicPayment =
1712 auto const [totalValueOutstanding, loanScale] = [&]() {
1721 STAmount amount{asset, periodicPayment * paymentsRemaining};
1728 (amount.
integral() && loanScale == 0) ||
1730 "xrpl::computeLoanProperties",
1731 "loanScale value fits expectations");
1743 auto const roundedPrincipalOutstanding =
1747 auto const totalInterestOutstanding = totalValueOutstanding - roundedPrincipalOutstanding;
1748 auto const feeOwedToBroker =
1754 auto const firstPaymentPrincipal = [&]() {
1758 rules, periodicPayment, periodicRate, paymentsRemaining, managementFeeRate);
1761 rules, periodicPayment, periodicRate, paymentsRemaining - 1, managementFeeRate);
1765 return startingState.principalOutstanding - firstPaymentState.principalOutstanding;
1769 .periodicPayment = periodicPayment,
1771 constructLoanState(totalValueOutstanding, roundedPrincipalOutstanding, feeOwedToBroker),
1772 .loanScale = loanScale,
1773 .firstPaymentPrincipal = firstPaymentPrincipal,
1783std::expected<LoanPaymentParts, TER>
1795 auto principalOutstandingProxy = loan->at(sfPrincipalOutstanding);
1796 auto paymentRemainingProxy = loan->at(sfPaymentRemaining);
1798 if (paymentRemainingProxy == 0 || principalOutstandingProxy == 0)
1802 JLOG(j.
warn()) <<
"Loan is already paid off.";
1807 auto totalValueOutstandingProxy = loan->at(sfTotalValueOutstanding);
1808 auto managementFeeOutstandingProxy = loan->at(sfManagementFeeOutstanding);
1811 auto nextDueDateProxy = loan->at(sfNextPaymentDueDate);
1812 if (*nextDueDateProxy == 0)
1814 JLOG(j.
warn()) <<
"Loan next payment due date is not set.";
1820 TenthBips32 const interestRate{loan->at(sfInterestRate)};
1822 Number const serviceFee = loan->at(sfLoanServiceFee);
1823 TenthBips16 const managementFeeRate{brokerSle->at(sfManagementFeeRate)};
1825 Number const periodicPayment = loan->at(sfPeriodicPayment);
1827 auto prevPaymentDateProxy = loan->at(sfPreviousPaymentDueDate);
1830 std::uint32_t const paymentInterval = loan->at(sfPaymentInterval);
1835 XRPL_ASSERT(interestRate == 0 || periodicRate > 0,
"xrpl::loanMakePayment : valid rate");
1837 XRPL_ASSERT(*totalValueOutstandingProxy > 0,
"xrpl::loanMakePayment : valid total value");
1847 JLOG(j.
warn()) <<
"Loan payment is overdue. Use the tfLoanLatePayment "
1849 "flag to make a late payment. Loan was created on "
1850 << startDate <<
", prev payment due date is " << prevPaymentDateProxy
1851 <<
", next payment due date is " << nextDueDateProxy <<
", ledger time is "
1860 TenthBips32 const closeInterestRate{loan->at(sfCloseInterestRate)};
1861 Number const closePaymentFee =
roundToAsset(asset, loan->at(sfClosePaymentFee), loanScale);
1864 totalValueOutstandingProxy, principalOutstandingProxy, managementFeeOutstandingProxy);
1869 principalOutstandingProxy,
1870 managementFeeOutstandingProxy,
1872 paymentRemainingProxy,
1873 prevPaymentDateProxy,
1885 if (fullPaymentComponents.has_value())
1888 *fullPaymentComponents,
1889 totalValueOutstandingProxy,
1890 principalOutstandingProxy,
1891 managementFeeOutstandingProxy,
1892 paymentRemainingProxy,
1893 prevPaymentDateProxy,
1898 if (fullPaymentComponents.error())
1907 UNREACHABLE(
"xrpl::loanMakePayment : invalid full payment result");
1908 JLOG(j.
error()) <<
"Full payment computation failed unexpectedly.";
1921 totalValueOutstandingProxy,
1922 principalOutstandingProxy,
1923 managementFeeOutstandingProxy,
1926 paymentRemainingProxy,
1931 "xrpl::loanMakePayment",
1932 "regular payment valid principal");
1938 TenthBips32 const lateInterestRate{loan->at(sfLateInterestRate)};
1939 Number const latePaymentFee = loan->at(sfLatePaymentFee);
1944 principalOutstandingProxy,
1954 if (latePaymentComponents.has_value())
1957 *latePaymentComponents,
1958 totalValueOutstandingProxy,
1959 principalOutstandingProxy,
1960 managementFeeOutstandingProxy,
1961 paymentRemainingProxy,
1962 prevPaymentDateProxy,
1967 if (latePaymentComponents.error())
1975 UNREACHABLE(
"xrpl::loanMakePayment : invalid late payment result");
1976 JLOG(j.
error()) <<
"Late payment computation failed unexpectedly.";
1986 "xrpl::loanMakePayment",
1987 "regular payment type");
1994 while ((amount >= (totalPaid + periodic.
totalDue)) && paymentRemainingProxy > 0 &&
1995 numPayments < kLoanMaximumPaymentsPerTransaction)
2000 "xrpl::loanMakePayment",
2001 "payment pays non-negative principal");
2006 totalValueOutstandingProxy,
2007 principalOutstandingProxy,
2008 managementFeeOutstandingProxy,
2009 paymentRemainingProxy,
2010 prevPaymentDateProxy,
2017 (paymentRemainingProxy == 0),
2018 "xrpl::loanMakePayment",
2019 "final payment is the final payment");
2030 totalValueOutstandingProxy,
2031 principalOutstandingProxy,
2032 managementFeeOutstandingProxy,
2035 paymentRemainingProxy,
2040 if (numPayments == 0)
2042 JLOG(j.
warn()) <<
"Regular loan payment amount is insufficient. Due: " << periodic.
totalDue
2043 <<
", paid: " << amount;
2049 "xrpl::loanMakePayment",
2050 "payment parts add up");
2051 XRPL_ASSERT_PARTS(totalParts.
valueChange == 0,
"xrpl::loanMakePayment",
"no value change");
2062 auto const roundedAmount = view.
rules().
enabled(fixCleanup3_1_3)
2066 paymentRemainingProxy > 0 && totalPaid < roundedAmount &&
2067 numPayments < kLoanMaximumPaymentsPerTransaction)
2069 TenthBips32 const overpaymentInterestRate{loan->at(sfOverpaymentInterestRate)};
2070 TenthBips32 const overpaymentFeeRate{loan->at(sfOverpaymentFee)};
2075 Number const overpaymentRaw =
2076 std::min(roundedAmount - totalPaid, *totalValueOutstandingProxy);
2078 bool const fixEnabled = view.
rules().
enabled(fixCleanup3_2_0);
2079 Number const overpayment = fixEnabled
2085 if (!fixEnabled || overpayment > 0)
2093 overpaymentInterestRate,
2103 "xrpl::loanMakePayment",
2104 "overpayment penalty did not reduce value of loan");
2107 auto periodicPaymentProxy = loan->at(sfPeriodicPayment);
2112 overpaymentComponents,
2113 totalValueOutstandingProxy,
2114 principalOutstandingProxy,
2115 managementFeeOutstandingProxy,
2116 periodicPaymentProxy,
2118 paymentRemainingProxy,
2122 totalParts += *overResult;
2124 else if (overResult.error())
2141 "xrpl::loanMakePayment : total principal paid is valid");
2145 "xrpl::loanMakePayment : total interest paid is valid");
2148 "xrpl::loanMakePayment : loan value change is valid");
2151 "xrpl::loanMakePayment : fee paid is valid");
A generic endpoint for log messages.
Stream trace() const
Severity stream access functions.
Writeable view to a ledger, for applying a transaction.
virtual void update(SLE::ref sle)=0
Indicate changes to a peeked SLE.
std::chrono::time_point< NetClock > time_point
Number is a floating point type that can represent a wide range of values.
static RoundingMode getround()
constexpr int exponent() const noexcept
Returns the exponent of the external view of the Number.
virtual Rules const & rules() const =0
Returns the tx processing rules.
NetClock::time_point parentCloseTime() const
Returns the close time of the previous ledger.
Rules controlling protocol behavior.
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
std::string getFullText() const override
bool integral() const noexcept
Asset const & asset() const
int exponent() const noexcept
bool isZeroAtScale(int scale) const
Checks if this amount evaluates to zero when constrained to a specific accounting scale.
std::shared_ptr< STLedgerEntry > const & ref
std::shared_ptr< STLedgerEntry const > const & const_ref
bool isFieldPresent(SField const &field) const
Number computePaymentFactor(Rules const &rules, Number const &periodicRate, std::uint32_t paymentsRemaining)
Number loanPrincipalFromPeriodicPayment(Rules const &rules, Number const &periodicPayment, Number const &periodicRate, std::uint32_t paymentsRemaining)
Number computePowerMinusOneHybrid(Number const &periodicRate, std::uint32_t paymentsRemaining)
Number loanPeriodicPayment(Rules const &rules, Number const &principalOutstanding, Number const &periodicRate, std::uint32_t paymentsRemaining)
std::expected< ExtendedPaymentComponents, TER > computeLatePayment(Asset const &asset, ApplyView const &view, Number const &principalOutstanding, std::int32_t nextDueDate, ExtendedPaymentComponents const &periodic, TenthBips32 lateInterestRate, std::int32_t loanScale, Number const &latePaymentFee, STAmount const &amount, TenthBips16 managementFeeRate, beast::Journal j)
Number loanAccruedInterest(Number const &principalOutstanding, Number const &periodicRate, NetClock::time_point parentCloseTime, std::uint32_t startDate, std::uint32_t prevPaymentDate, std::uint32_t paymentInterval)
std::pair< Number, Number > computeInterestAndFeeParts(Asset const &asset, Number const &interest, TenthBips16 managementFeeRate, std::int32_t loanScale)
Number loanLatePaymentInterest(Number const &principalOutstanding, TenthBips32 lateInterestRate, NetClock::time_point parentCloseTime, std::uint32_t nextPaymentDueDate)
std::expected< ExtendedPaymentComponents, TER > computeFullPayment(Asset const &asset, ApplyView &view, Number const &principalOutstanding, Number const &managementFeeOutstanding, Number const &periodicPayment, std::uint32_t paymentRemaining, std::uint32_t prevPaymentDate, std::uint32_t const startDate, std::uint32_t const paymentInterval, TenthBips32 const closeInterestRate, std::int32_t loanScale, Number const &totalInterestOutstanding, Number const &periodicRate, Number const &closePaymentFee, STAmount const &amount, TenthBips16 managementFeeRate, beast::Journal j)
std::expected< LoanPaymentParts, TER > doOverpayment(Rules const &rules, Asset const &asset, std::int32_t loanScale, ExtendedPaymentComponents const &overpaymentComponents, NumberProxy &totalValueOutstandingProxy, NumberProxy &principalOutstandingProxy, NumberProxy &managementFeeOutstandingProxy, NumberProxy &periodicPaymentProxy, Number const &periodicRate, std::uint32_t const paymentRemaining, TenthBips16 const managementFeeRate, beast::Journal j)
std::expected< std::pair< LoanPaymentParts, LoanProperties >, TER > tryOverpayment(Rules const &rules, Asset const &asset, std::int32_t loanScale, ExtendedPaymentComponents const &overpaymentComponents, LoanState const &roundedLoanState, Number const &periodicPayment, Number const &periodicRate, std::uint32_t paymentRemaining, TenthBips16 const managementFeeRate, beast::Journal j)
ExtendedPaymentComponents computeOverpaymentComponents(Rules const &rules, Asset const &asset, int32_t const loanScale, Number const &overpayment, TenthBips32 const overpaymentInterestRate, TenthBips32 const overpaymentFeeRate, TenthBips16 const managementFeeRate)
LoanPaymentParts doPayment(ExtendedPaymentComponents const &payment, NumberProxy &totalValueOutstandingProxy, NumberProxy &principalOutstandingProxy, NumberProxy &managementFeeOutstandingProxy, UInt32Proxy &paymentRemainingProxy, UInt32Proxy &prevPaymentDateProxy, UInt32OptionalProxy &nextDueDateProxy, std::uint32_t paymentInterval)
Number computePowerMinusOne(Number const &periodicRate, std::uint32_t paymentsRemaining)
PaymentComponents computePaymentComponents(Rules const &rules, Asset const &asset, std::int32_t scale, Number const &totalValueOutstanding, Number const &principalOutstanding, Number const &managementFeeOutstanding, Number const &periodicPayment, Number const &periodicRate, std::uint32_t paymentRemaining, TenthBips16 managementFeeRate)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
static constexpr Number kNumZero
constexpr BaseUInt< Bits, Tag > operator+(BaseUInt< Bits, Tag > const &a, BaseUInt< Bits, Tag > const &b)
Number loanPeriodicRate(TenthBips32 interestRate, std::uint32_t paymentInterval)
static auto sum(TCollection const &col)
TER canApplyToBrokerCover(ReadView const &view, SLE::const_ref sleBroker, Asset const &vaultAsset, STAmount const &amount, beast::Journal j, std::string_view logPrefix)
Broker cover preclaim precision guard (fixCleanup3_2_0).
Number operator-(Number const &x, Number const &y)
constexpr T tenthBipsOfValue(T value, TenthBips< TBips > bips)
bool hasExpired(ReadView const &view, std::optional< std::uint32_t > const &exp)
Determines whether the given expiration time has passed.
int scale(Number const &number, Asset const &asset)
Get the scale of a Number for a given asset.
Number power(Number const &f, unsigned n)
TenthBips< std::uint32_t > TenthBips32
TenthBips< std::uint16_t > TenthBips16
TER checkLoanGuards(Asset const &vaultAsset, Number const &principalRequested, bool expectInterest, std::uint32_t paymentTotal, LoanProperties const &properties, beast::Journal j)
std::expected< LoanPaymentParts, TER > loanMakePayment(Asset const &asset, ApplyView &view, SLE::ref loan, SLE::const_ref brokerSle, STAmount const &amount, LoanPaymentType const paymentType, beast::Journal j)
LoanState computeTheoreticalLoanState(Rules const &rules, Number const &periodicPayment, Number const &periodicRate, std::uint32_t const paymentRemaining, TenthBips32 const managementFeeRate)
void roundToAsset(A const &asset, Number &value)
Round an arbitrary precision Number IN PLACE to the precision of a given Asset.
Number roundPeriodicPayment(Asset const &asset, Number const &periodicPayment, std::int32_t scale)
Ensure the periodic payment is always rounded consistently.
LoanState constructRoundedLoanState(SLE::const_ref loan)
Number computeManagementFee(Asset const &asset, Number const &interest, TenthBips32 managementFeeRate, std::int32_t scale)
TERSubset< CanCvtToTER > TER
@ tecINSUFFICIENT_PAYMENT
static constexpr std::uint32_t kSecondsInYear
LoanProperties computeLoanProperties(Rules const &rules, Asset const &asset, Number const &principalOutstanding, TenthBips32 interestRate, std::uint32_t paymentInterval, std::uint32_t paymentsRemaining, TenthBips32 managementFeeRate, std::int32_t minimumScale)
LoanState constructLoanState(Number const &totalValueOutstanding, Number const &principalOutstanding, Number const &managementFeeOutstanding)
Number computeFullPaymentInterest(Number const &theoreticalPrincipalOutstanding, Number const &periodicRate, NetClock::time_point parentCloseTime, std::uint32_t paymentInterval, std::uint32_t prevPaymentDate, std::uint32_t startDate, TenthBips32 closeInterestRate)
bool checkLendingProtocolDependencies(Rules const &rules, STTx const &tx)
bool isRounded(Asset const &asset, Number const &value, std::int32_t scale)
bool operator==(LoanPaymentParts const &other) const
LoanPaymentParts & operator+=(LoanPaymentParts const &other)
Number firstPaymentPrincipal
This structure captures the parts of a loan state.
Number principalOutstanding
Number untrackedManagementFee
Number trackedPrincipalDelta
PaymentSpecialCase specialCase
Number trackedManagementFeeDelta
Number trackedInterestPart() const
T time_since_epoch(T... args)