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/ledger/helpers/VaultHelpers.h>
13#include <xrpl/protocol/Asset.h>
14#include <xrpl/protocol/Feature.h>
15#include <xrpl/protocol/LedgerFormats.h>
16#include <xrpl/protocol/Protocol.h>
17#include <xrpl/protocol/Rules.h>
18#include <xrpl/protocol/SField.h>
19#include <xrpl/protocol/STAmount.h>
20#include <xrpl/protocol/STLedgerEntry.h>
21#include <xrpl/protocol/STTx.h>
22#include <xrpl/protocol/TER.h>
23#include <xrpl/protocol/Units.h>
38 Asset const& vaultAsset,
44 sleBroker && sleBroker->getType() == ltLOAN_BROKER,
45 "xrpl::canApplyToBrokerCover : valid LoanBroker sle");
46 XRPL_ASSERT(vaultAsset == amount.
asset(),
"xrpl::canApplyToBrokerCover : valid asset");
54 int const coverScale =
scale(sleBroker->at(sfCoverAvailable), vaultAsset);
58 <<
" rounds to zero at cover scale " << coverScale;
68 if (!rules.
enabled(featureSingleAssetVault))
71 if (!rules.
enabled(featureMPTokensV1))
86 "xrpl::LoanPaymentParts::operator+= : other principal "
90 "xrpl::LoanPaymentParts::operator+= : other interest paid "
94 "xrpl::LoanPaymentParts::operator+= : other fee paid "
139 return {.assetsTotalDelta = interestDue, .debtTotalDelta = principalRequested + interestDue};
144 Number const& vaultMaximum,
146 Number const& interestDue)
148 return vaultMaximum != 0 && interestDue > vaultMaximum - vaultTotal;
161 return loanSle->at(sfTotalValueOutstanding) - loanSle->at(sfManagementFeeOutstanding);
174namespace cash_basis {
179 return {.assetsTotalDelta =
kNumZero, .debtTotalDelta = principalRequested};
190 return loanSle->at(sfPrincipalOutstanding);
218 Number const& principalRequested,
219 Number const& interestDue)
221 return cashBasisEnabled(vaultSle)
230 Number const& interestDue)
234 if (cashBasisEnabled(vaultSle))
237 auto const vaultMaximum = vaultSle->at(sfAssetsMaximum);
285 "xrpl::detail::computePowerMinusOne",
286 "periodicRate is non-negative");
288 if (paymentsRemaining == 0 || periodicRate ==
beast::kZero)
292 Number term = paymentsRemaining * periodicRate;
297 term = term * periodicRate * (paymentsRemaining - k) / (k + 1);
324 "xrpl::detail::computePowerMinusOneHybrid",
325 "periodicRate is non-negative");
327 if (paymentsRemaining == 0 || periodicRate ==
beast::kZero)
335 Number const cancellationThreshold{1, -9};
336 if (paymentsRemaining * periodicRate >= cancellationThreshold)
337 return power(1 + periodicRate, paymentsRemaining) - 1;
350 Number const& periodicRate,
353 if (paymentsRemaining == 0)
358 return Number{1} / paymentsRemaining;
360 if (rules.
enabled(fixCleanup3_2_0))
362 Number const raisedRateMinusOne =
364 Number const raisedRate = 1 + raisedRateMinusOne;
366 return (periodicRate * raisedRate) / raisedRateMinusOne;
372 Number const raisedRate =
power(1 + periodicRate, paymentsRemaining);
374 return (periodicRate * raisedRate) / (raisedRate - 1);
385 Number const& principalOutstanding,
386 Number const& periodicRate,
389 if (principalOutstanding == 0 || paymentsRemaining == 0)
394 return principalOutstanding / paymentsRemaining;
407 Number const& periodicPayment,
408 Number const& periodicRate,
411 if (paymentsRemaining == 0)
414 if (periodicRate == 0)
415 return periodicPayment * paymentsRemaining;
447 Number const& rawInterest,
452 auto const interest =
roundToAsset(asset, rawInterest, loanScale, mode);
463 Number const& principalOutstanding,
478 if (now <= nextPaymentDueDate)
482 auto const secondsOverdue = now - nextPaymentDueDate;
486 return principalOutstanding * rate;
496 Number const& principalOutstanding,
497 Number const& periodicRate,
506 if (paymentInterval == 0)
509 auto const lastPaymentDate =
std::max(prevPaymentDate, startDate);
514 if (now <= lastPaymentDate)
518 auto const secondsSinceLastPayment = now - lastPaymentDate;
523 return principalOutstanding * periodicRate * secondsSinceLastPayment / paymentInterval;
535 auto totalValueOutstandingProxy = loan->at(sfTotalValueOutstanding);
536 auto principalOutstandingProxy = loan->at(sfPrincipalOutstanding);
537 auto managementFeeOutstandingProxy = loan->at(sfManagementFeeOutstanding);
538 auto paymentRemainingProxy = loan->at(sfPaymentRemaining);
539 auto prevPaymentDateProxy = loan->at(sfPreviousPaymentDueDate);
540 auto nextDueDateProxy = loan->at(sfNextPaymentDueDate);
541 std::uint32_t const paymentInterval = loan->at(sfPaymentInterval);
543 XRPL_ASSERT_PARTS(nextDueDateProxy,
"xrpl::detail::doPayment",
"Next due date proxy set");
549 "xrpl::detail::doPayment",
550 "Full principal payment");
553 "xrpl::detail::doPayment",
554 "Full value payment");
557 "xrpl::detail::doPayment",
558 "Full management fee payment");
561 paymentRemainingProxy = 0;
564 prevPaymentDateProxy = *nextDueDateProxy;
568 nextDueDateProxy = 0;
573 principalOutstandingProxy = 0;
574 totalValueOutstandingProxy = 0;
575 managementFeeOutstandingProxy = 0;
582 paymentRemainingProxy -= 1;
584 prevPaymentDateProxy = nextDueDateProxy;
585 nextDueDateProxy += paymentInterval;
589 "xrpl::detail::doPayment",
590 "Partial principal payment");
593 "xrpl::detail::doPayment",
594 "Partial value payment");
599 "xrpl::detail::doPayment",
600 "Valid management fee");
610 static_cast<Number>(principalOutstandingProxy) <=
611 static_cast<Number>(totalValueOutstandingProxy),
612 "xrpl::detail::doPayment",
613 "principal does not exceed total");
617 "xrpl::detail::doPayment",
618 "fee outstanding stays valid");
650std::expected<std::pair<LoanPaymentParts, LoanProperties>,
TER>
657 Number const& periodicPayment,
658 Number const& periodicRate,
665 rules, periodicPayment, periodicRate, paymentRemaining, managementFeeRate);
671 auto const errors = roundedOldState - theoreticalState;
675 auto const newTheoreticalPrincipal =
std::max(
685 newTheoreticalPrincipal,
691 JLOG(j.
debug()) <<
"new periodic payment: " << newLoanProperties.periodicPayment
692 <<
", new total value: " << newLoanProperties.loanState.valueOutstanding
693 <<
", first payment principal: " << newLoanProperties.firstPaymentPrincipal;
698 auto const newTheoreticalState = [&]() {
701 newLoanProperties.periodicPayment,
707 if (!rules.
enabled(fixCleanup3_2_0))
719 Number const managementFee =
724 JLOG(j.
debug()) <<
"new theoretical value: " << newTheoreticalState.valueOutstanding
725 <<
", principal: " << newTheoreticalState.principalOutstanding
726 <<
", interest gross: " << newTheoreticalState.interestOutstanding();
734 newTheoreticalState.principalOutstanding,
739 auto const totalValueOutstanding =
std::clamp(
742 principalOutstanding + newTheoreticalState.interestOutstanding(),
747 auto const managementFeeOutstanding =
std::clamp(
748 roundToAsset(asset, newTheoreticalState.managementFeeDue, loanScale),
752 auto const roundedNewState =
753 constructLoanState(totalValueOutstanding, principalOutstanding, managementFeeOutstanding);
757 newLoanProperties.loanState = roundedNewState;
759 JLOG(j.
debug()) <<
"new rounded value: " << roundedNewState.valueOutstanding
760 <<
", principal: " << roundedNewState.principalOutstanding
761 <<
", interest gross: " << roundedNewState.interestOutstanding();
766 principalOutstanding,
776 JLOG(j.
warn()) <<
"Principal overpayment would cause the loan to be in "
777 "an invalid state. Ignore the overpayment";
784 if (newLoanProperties.periodicPayment <= 0 ||
785 newLoanProperties.loanState.valueOutstanding <= 0 ||
786 newLoanProperties.loanState.managementFeeDue < 0)
789 JLOG(j.
warn()) <<
"Overpayment not allowed: Computed loan "
790 "properties are invalid. Does "
791 "not compute. TotalValueOutstanding: "
792 << newLoanProperties.loanState.valueOutstanding
793 <<
", PeriodicPayment : " << newLoanProperties.periodicPayment
794 <<
", ManagementFeeOwedToBroker: "
795 << newLoanProperties.loanState.managementFeeDue;
800 auto const deltas = roundedOldState - roundedNewState;
805 deltas.managementFee == roundedOldState.
managementFeeDue - managementFeeOutstanding,
806 "xrpl::detail::tryOverpayment",
816 auto const valueChange = -deltas.interest;
819 JLOG(j.
warn()) <<
"Principal overpayment would increase the value of "
820 "the loan. Ignore the overpayment";
827 .principalPaid = deltas.principal,
853std::expected<LoanPaymentParts, TER>
860 Number const& periodicRate,
864 auto totalValueOutstandingProxy = loan->at(sfTotalValueOutstanding);
865 auto principalOutstandingProxy = loan->at(sfPrincipalOutstanding);
866 auto managementFeeOutstandingProxy = loan->at(sfManagementFeeOutstanding);
867 auto periodicPaymentProxy = loan->at(sfPeriodicPayment);
868 auto const paymentsRemaining = loan->at(sfPaymentRemaining);
871 totalValueOutstandingProxy, principalOutstandingProxy, managementFeeOutstandingProxy);
872 auto const periodicPayment = periodicPaymentProxy;
873 JLOG(j.
debug()) <<
"overpayment components:"
874 <<
", totalValue before: " << *totalValueOutstandingProxy
880 <<
", totalDue: " << overpaymentComponents.
totalDue
881 <<
", payments remaining :" << paymentsRemaining;
889 overpaymentComponents,
899 auto const& [loanPaymentParts, newLoanProperties] = *ret;
900 auto const newRoundedLoanState = newLoanProperties.loanState;
905 if (principalOutstandingProxy <= newRoundedLoanState.principalOutstanding)
908 JLOG(j.
warn()) <<
"Overpayment not allowed: principal "
909 <<
"outstanding did not decrease. Before: " << *principalOutstandingProxy
910 <<
". After: " << newRoundedLoanState.principalOutstanding;
918 JLOG(j.
debug()) <<
"valueChange: " << loanPaymentParts.valueChange
919 <<
", totalValue before: " << *totalValueOutstandingProxy
920 <<
", totalValue after: " << newRoundedLoanState.valueOutstanding
921 <<
", totalValue delta: "
922 << (totalValueOutstandingProxy - newRoundedLoanState.valueOutstanding)
924 <<
", principalPaid: " << loanPaymentParts.principalPaid
925 <<
", Computed difference: "
927 (totalValueOutstandingProxy - newRoundedLoanState.valueOutstanding);
950 [[maybe_unused]]
bool const fix320Enabled = rules.
enabled(fixCleanup3_2_0);
954 principalOutstandingProxy - newRoundedLoanState.principalOutstanding,
955 "xrpl::detail::doOverpayment : principal change agrees");
960 Number const tvoChange = newRoundedLoanState.valueOutstanding -
962 Number const managementFeeReleased =
963 managementFeeOutstandingProxy - newRoundedLoanState.managementFeeDue;
965 return loanPaymentParts.valueChange == tvoChange + managementFeeReleased + interestPart;
967 "xrpl::detail::doOverpayment : interest paid agrees");
972 "xrpl::detail::doOverpayment : principal payment matches");
976 totalValueOutstandingProxy = newRoundedLoanState.valueOutstanding;
977 principalOutstandingProxy = newRoundedLoanState.principalOutstanding;
978 managementFeeOutstandingProxy = newRoundedLoanState.managementFeeDue;
979 periodicPaymentProxy = newLoanProperties.periodicPayment;
981 return loanPaymentParts;
997std::expected<ExtendedPaymentComponents, TER>
1007 std::int32_t const nextDueDate = loan->at(sfNextPaymentDueDate);
1017 loan->at(sfPrincipalOutstanding),
1024 auto const [roundedLateInterest, roundedLateManagementFee] =
1027 XRPL_ASSERT(roundedLateInterest >= 0,
"xrpl::detail::computeLatePayment : valid late interest");
1030 "xrpl::detail::computeLatePayment",
1031 "no extra parts to this payment");
1055 "xrpl::detail::computeLatePayment",
1056 "total due is rounded");
1063 JLOG(j.
warn()) <<
"Late loan payment amount is insufficient. Due: " << late.
totalDue
1064 <<
", paid: " << amount;
1090std::expected<ExtendedPaymentComponents, TER>
1095 Number const& periodicRate,
1100 std::uint32_t const paymentRemaining = loan->at(sfPaymentRemaining);
1104 if (paymentRemaining <= 1)
1107 JLOG(j.
warn()) <<
"Last payment cannot be a full payment.";
1115 view.
rules(), loan->at(sfPeriodicPayment), periodicRate, paymentRemaining);
1120 theoreticalPrincipalOutstanding,
1123 loan->at(sfPaymentInterval),
1124 loan->at(sfPreviousPaymentDueDate),
1125 loan->at(sfStartDate),
1137 Number const closePaymentFee =
roundToAsset(asset, loan->at(sfClosePaymentFee), loanScale);
1144 .trackedValueDelta =
1145 principalOutstanding + totalInterestOutstanding + managementFeeOutstanding,
1146 .trackedPrincipalDelta = principalOutstanding,
1150 .trackedManagementFeeDelta = managementFeeOutstanding,
1161 closePaymentFee + roundedFullManagementFee - managementFeeOutstanding,
1170 roundedFullInterest - totalInterestOutstanding,
1175 "xrpl::detail::computeFullPayment",
1176 "total due is rounded");
1178 JLOG(j.
trace()) <<
"computeFullPayment result: periodicRate: " << periodicRate
1179 <<
", paymentRemaining: " << paymentRemaining
1180 <<
", theoreticalPrincipalOutstanding: " << theoreticalPrincipalOutstanding
1181 <<
", fullPaymentInterest: " << fullPaymentInterest
1182 <<
", roundedFullInterest: " << roundedFullInterest
1183 <<
", roundedFullManagementFee: " << roundedFullManagementFee
1225 Number const& totalValueOutstanding,
1226 Number const& principalOutstanding,
1227 Number const& managementFeeOutstanding,
1228 Number const& periodicPayment,
1229 Number const& periodicRate,
1237 "xrpl::detail::computePaymentComponents",
1238 "Outstanding values are rounded");
1240 paymentRemaining > 0,
"xrpl::detail::computePaymentComponents",
"some payments remaining");
1246 if (paymentRemaining == 1 || totalValueOutstanding <= roundedPeriodicPayment)
1251 .trackedValueDelta = totalValueOutstanding,
1252 .trackedPrincipalDelta = principalOutstanding,
1253 .trackedManagementFeeDelta = managementFeeOutstanding,
1260 rules, periodicPayment, periodicRate, paymentRemaining - 1, managementFeeRate);
1269 bool const fixCleanup320Enabled = rules.
enabled(fixCleanup3_2_0);
1276 .principalOutstanding =
1283 constructLoanState(totalValueOutstanding, principalOutstanding, managementFeeOutstanding);
1296 "xrpl::detail::computePaymentComponents",
1297 "principal delta not greater than outstanding");
1302 if (fixCleanup320Enabled)
1306 "xrpl::detail::computePaymentComponents",
1307 "interest due delta not greater than outstanding");
1318 "xrpl::detail::computePaymentComponents",
1319 "management fee due delta not greater than outstanding");
1332 auto takeFrom = [](
Number& component,
Number& excess) {
1335 auto part =
std::min(component, excess);
1341 "xrpl::detail::computePaymentComponents",
1342 "excess non-negative");
1362 "xrpl::detail::computePaymentComponents : payment exceeded loan "
1364 addressExcess(deltas, totalOverpayment);
1369 Number shortage = roundedPeriodicPayment - deltas.
total();
1373 "xrpl::detail::computePaymentComponents",
1374 "shortage is rounded");
1379 Number excess = -shortage;
1380 addressExcess(deltas, excess);
1389 "xrpl::detail::computePaymentComponents",
1390 "no shortage or excess");
1395 "xrpl::detail::computePaymentComponents",
1396 "total value adds up");
1401 "xrpl::detail::computePaymentComponents",
1402 "valid principal result");
1405 "xrpl::detail::computePaymentComponents",
1406 "valid interest result");
1410 "xrpl::detail::computePaymentComponents",
1411 "valid fee result");
1415 "xrpl::detail::computePaymentComponents",
1416 "payment parts add to payment");
1420 .trackedValueDelta =
1422 .trackedPrincipalDelta =
1424 .trackedManagementFeeDelta =
1441 Number const& periodicRate,
1447 loan->at(sfLoanScale),
1448 loan->at(sfTotalValueOutstanding),
1449 loan->at(sfPrincipalOutstanding),
1450 loan->at(sfManagementFeeOutstanding),
1451 loan->at(sfPeriodicPayment),
1453 loan->at(sfPaymentRemaining),
1475ExtendedPaymentComponents
1479 int32_t
const loanScale,
1480 Number const& overpayment,
1486 rules.
enabled(fixCleanup3_2_0),
1487 overpayment > 0 &&
isRounded(asset, overpayment, loanScale),
1488 "xrpl::detail::computeOverpaymentComponents : valid overpayment "
1494 Number const overpaymentFee =
1501 auto const [roundedOverpaymentInterest, roundedOverpaymentManagementFee] =
1513 .trackedValueDelta = overpayment - overpaymentFee,
1514 .trackedPrincipalDelta = overpayment - roundedOverpaymentInterest -
1515 roundedOverpaymentManagementFee - overpaymentFee,
1516 .trackedManagementFeeDelta = roundedOverpaymentManagementFee,
1525 roundedOverpaymentInterest};
1527 result.trackedInterestPart() == roundedOverpaymentInterest,
1528 "xrpl::detail::computeOverpaymentComponents",
1529 "valid interest computation");
1540 TenthBips16 const managementFeeRate{brokerSle->at(sfManagementFeeRate)};
1541 TenthBips32 const interestRate{loan->at(sfInterestRate)};
1543 XRPL_ASSERT(interestRate == 0 || periodicRate > 0,
"xrpl::detail::loanRatesFor : valid rate");
1544 return {managementFeeRate, periodicRate};
1551std::expected<LoanPaymentParts, TER>
1560 auto const [managementFeeRate, periodicRate] =
loanRatesFor(loan, brokerSle);
1562 auto const fullPaymentComponents =
1567 if (fullPaymentComponents.has_value())
1568 return doPayment(*fullPaymentComponents, loan);
1576std::expected<LoanPaymentParts, TER>
1585 auto const [managementFeeRate, periodicRate] =
loanRatesFor(loan, brokerSle);
1587 Number const serviceFee = loan->at(sfLoanServiceFee);
1593 "xrpl::detail::makeLatePayment",
1594 "regular payment valid principal");
1596 auto const latePaymentComponents =
1601 if (latePaymentComponents.has_value())
1602 return doPayment(*latePaymentComponents, loan);
1610std::expected<LoanPaymentParts, TER>
1624 "xrpl::detail::makeRegularPayment",
1625 "regular payment type");
1627 auto const [managementFeeRate, periodicRate] =
loanRatesFor(loan, brokerSle);
1630 Number const serviceFee = loan->at(sfLoanServiceFee);
1637 "xrpl::detail::makeRegularPayment",
1638 "regular payment valid principal");
1648 auto paymentRemainingProxy = loan->at(sfPaymentRemaining);
1650 while ((amount >= (totalPaid + periodic.
totalDue)) && paymentRemainingProxy > 0 &&
1651 numPayments < kLoanMaximumPaymentsPerTransaction)
1656 "xrpl::detail::makeRegularPayment",
1657 "payment pays non-negative principal");
1660 totalParts +=
doPayment(periodic, loan);
1665 "xrpl::detail::makeRegularPayment",
1666 "final payment is the final payment");
1677 if (numPayments == 0)
1679 JLOG(j.
warn()) <<
"Regular loan payment amount is insufficient. Due: " << periodic.
totalDue
1680 <<
", paid: " << amount;
1686 "xrpl::detail::makeRegularPayment",
1687 "payment parts add up");
1689 totalParts.
valueChange == 0,
"xrpl::detail::makeRegularPayment",
"no value change");
1700 auto const roundedAmount = view.
rules().
enabled(fixCleanup3_1_3)
1704 bool const overpaymentSupported =
1707 bool const overpaymentAllowed =
1708 paymentRemainingProxy > 0 &&
1709 totalPaid < roundedAmount &&
1710 numPayments < kLoanMaximumPaymentsPerTransaction;
1712 if (overpaymentSupported && overpaymentAllowed)
1714 TenthBips32 const overpaymentInterestRate{loan->at(sfOverpaymentInterestRate)};
1715 TenthBips32 const overpaymentFeeRate{loan->at(sfOverpaymentFee)};
1720 Number const overpaymentRaw =
1721 std::min(roundedAmount - totalPaid, *loan->at(sfTotalValueOutstanding));
1723 bool const fixEnabled = view.
rules().
enabled(fixCleanup3_2_0);
1724 Number const overpayment = fixEnabled
1730 if (!fixEnabled || overpayment > 0)
1737 overpaymentInterestRate,
1747 "xrpl::detail::makeRegularPayment",
1748 "overpayment penalty did not reduce value of loan");
1753 overpaymentComponents,
1759 totalParts += *overResult;
1761 else if (overResult.error())
1778 "xrpl::detail::makeRegularPayment : total principal paid is valid");
1782 "xrpl::detail::makeRegularPayment : total interest paid is valid");
1785 "xrpl::detail::makeRegularPayment : loan value change is valid");
1788 "xrpl::detail::makeRegularPayment : fee paid is valid");
1794detail::LoanStateDeltas
1834 Asset const& vaultAsset,
1835 Number const& principalRequested,
1836 bool expectInterest,
1841 auto const totalInterestOutstanding =
1846 if (expectInterest && totalInterestOutstanding <= 0)
1850 JLOG(j.
warn()) <<
"Loan for " << principalRequested <<
" with interest has no interest due";
1855 if (!expectInterest && totalInterestOutstanding > 0)
1858 JLOG(j.
warn()) <<
"Loan for " << principalRequested <<
" with no interest has interest due";
1872 JLOG(j.
warn()) <<
"Loan is unable to pay principal.";
1879 auto const roundedPayment =
1884 <<
") rounds to 0. ";
1896 computedPayments != paymentTotal)
1899 <<
") rounding (" << roundedPayment <<
") on a total value of "
1901 <<
" can not complete the loan in the specified "
1902 "number of payments ("
1903 << computedPayments <<
" != " << paymentTotal <<
")";
1918 Number const& theoreticalPrincipalOutstanding,
1919 Number const& periodicRate,
1927 theoreticalPrincipalOutstanding,
1934 accruedInterest >= 0,
1935 "xrpl::detail::computeFullPaymentInterest : valid accrued "
1939 auto const prepaymentPenalty = closeInterestRate ==
beast::kZero
1944 prepaymentPenalty >= 0,
1945 "xrpl::detail::computeFullPaymentInterest : valid prepayment "
1949 return accruedInterest + prepaymentPenalty;
1977 Number const& periodicPayment,
1978 Number const& periodicRate,
1982 if (paymentRemaining == 0)
1985 .valueOutstanding = 0,
1986 .principalOutstanding = 0,
1988 .managementFeeDue = 0};
1992 Number const totalValueOutstanding = periodicPayment * paymentRemaining;
1995 rules, periodicPayment, periodicRate, paymentRemaining);
1998 Number const interestOutstandingGross = totalValueOutstanding - principalOutstanding;
2001 Number const managementFeeOutstanding =
2005 Number const interestOutstandingNet = interestOutstandingGross - managementFeeOutstanding;
2008 .valueOutstanding = totalValueOutstanding,
2009 .principalOutstanding = principalOutstanding,
2010 .interestDue = interestOutstandingNet,
2011 .managementFeeDue = managementFeeOutstanding,
2037 Number const& totalValueOutstanding,
2038 Number const& principalOutstanding,
2039 Number const& managementFeeOutstanding)
2044 .valueOutstanding = totalValueOutstanding,
2045 .principalOutstanding = principalOutstanding,
2046 .interestDue = totalValueOutstanding - principalOutstanding - managementFeeOutstanding,
2047 .managementFeeDue = managementFeeOutstanding};
2053 XRPL_ASSERT(loan && loan->getType() == ltLOAN,
"xrpl::constructLoanState : valid loan SLE");
2056 loan->at(sfTotalValueOutstanding),
2057 loan->at(sfPrincipalOutstanding),
2058 loan->at(sfManagementFeeOutstanding));
2090 Number const& principalOutstanding,
2098 XRPL_ASSERT(interestRate == 0 || periodicRate > 0,
"xrpl::computeLoanProperties : valid rate");
2102 principalOutstanding,
2121 Number const& principalOutstanding,
2122 Number const& periodicRate,
2127 auto const periodicPayment =
2130 auto const [totalValueOutstanding, loanScale] = [&]() {
2139 STAmount amount{asset, periodicPayment * paymentsRemaining};
2146 (amount.
integral() && loanScale == 0) ||
2148 "xrpl::computeLoanProperties",
2149 "loanScale value fits expectations");
2161 auto const roundedPrincipalOutstanding =
2165 auto const totalInterestOutstanding = totalValueOutstanding - roundedPrincipalOutstanding;
2166 auto const feeOwedToBroker =
2172 auto const firstPaymentPrincipal = [&]() {
2176 rules, periodicPayment, periodicRate, paymentsRemaining, managementFeeRate);
2179 rules, periodicPayment, periodicRate, paymentsRemaining - 1, managementFeeRate);
2183 return startingState.principalOutstanding - firstPaymentState.principalOutstanding;
2187 .periodicPayment = periodicPayment,
2189 constructLoanState(totalValueOutstanding, roundedPrincipalOutstanding, feeOwedToBroker),
2190 .loanScale = loanScale,
2191 .firstPaymentPrincipal = firstPaymentPrincipal,
2201std::expected<LoanPaymentParts, TER>
2211 if (loan->at(sfPaymentRemaining) == 0 || loan->at(sfPrincipalOutstanding) == 0)
2215 JLOG(j.
warn()) <<
"Loan is already paid off.";
2221 auto nextDueDateProxy = loan->at(sfNextPaymentDueDate);
2222 if (*nextDueDateProxy == 0)
2224 JLOG(j.
warn()) <<
"Loan next payment due date is not set.";
2229 *loan->at(sfTotalValueOutstanding) > 0,
"xrpl::loanMakePayment : valid total value");
2239 JLOG(j.
warn()) <<
"Loan payment is overdue. Use the tfLoanLatePayment transaction flag to "
2240 "make a late payment. Loan was created on "
2241 << loan->at(sfStartDate) <<
", prev payment due date is "
2242 << loan->at(sfPreviousPaymentDueDate) <<
", next payment due date is "
2243 << nextDueDateProxy <<
", ledger time is "
2248 switch (paymentType)
2260 UNREACHABLE(
"xrpl::loanMakePayment : invalid payment type");
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
AccountingDeltas loanPaymentDeltas(LoanPaymentParts const &parts)
bool loanOriginationExceedsVaultMaximum(Number const &vaultMaximum, Number const &vaultTotal, Number const &interestDue)
Number loanVaultExposure(SLE::const_ref loanSle)
AccountingDeltas loanOriginationDeltas(Number const &principalRequested, Number const &interestDue)
AccountingDeltas loanPaymentDeltas(LoanPaymentParts const &parts)
AccountingDeltas loanOriginationDeltas(Number const &principalRequested)
Number loanVaultExposure(SLE::const_ref loanSle)
std::expected< LoanPaymentParts, TER > makeLatePayment(Asset const &asset, ApplyView const &view, SLE::ref loan, SLE::const_ref brokerSle, STAmount const &amount, beast::Journal j)
Number computePaymentFactor(Rules const &rules, Number const &periodicRate, std::uint32_t paymentsRemaining)
LoanPaymentParts doPayment(ExtendedPaymentComponents const &payment, SLE::ref loan)
std::expected< ExtendedPaymentComponents, TER > computeLatePayment(Asset const &asset, ApplyView const &view, SLE::const_ref loan, ExtendedPaymentComponents const &periodic, STAmount const &amount, TenthBips16 managementFeeRate, beast::Journal j)
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< LoanPaymentParts, TER > doOverpayment(Rules const &rules, Asset const &asset, std::int32_t loanScale, ExtendedPaymentComponents const &overpaymentComponents, SLE::ref loan, Number const &periodicRate, TenthBips16 const 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 > roundAndSplitInterest(Asset const &asset, Number const &rawInterest, TenthBips16 managementFeeRate, std::int32_t loanScale, Number::RoundingMode mode=Number::getround())
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< LoanPaymentParts, TER > makeRegularPayment(Asset const &asset, ApplyView const &view, SLE::ref loan, SLE::const_ref brokerSle, STAmount const &amount, LoanPaymentType const paymentType, beast::Journal j)
std::expected< LoanPaymentParts, TER > makeFullPayment(Asset const &asset, ApplyView &view, SLE::ref loan, SLE::const_ref brokerSle, STAmount const &amount, 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)
std::pair< TenthBips16, Number > loanRatesFor(SLE::const_ref loan, SLE::const_ref brokerSle)
ExtendedPaymentComponents computeOverpaymentComponents(Rules const &rules, Asset const &asset, int32_t const loanScale, Number const &overpayment, TenthBips32 const overpaymentInterestRate, TenthBips32 const overpaymentFeeRate, TenthBips16 const managementFeeRate)
std::expected< ExtendedPaymentComponents, TER > computeFullPayment(Asset const &asset, ApplyView &view, SLE::const_ref loan, Number const &periodicRate, STAmount const &amount, TenthBips16 managementFeeRate, beast::Journal j)
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)
bool hasExpired(ReadView const &view, std::optional< std::uint32_t > const &exp, ExpiryComparison comparison=ExpiryComparison::Inclusive)
Determines whether the given expiration time has passed.
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)
bool loanOriginationExceedsVaultMaximum(SLE::const_ref vaultSle, Number const &vaultTotal, Number const &interestDue)
constexpr T tenthBipsOfValue(T value, TenthBips< TBips > bips)
int scale(Number const &number, Asset const &asset)
Get the scale of a Number for a given asset.
AccountingDeltas loanPaymentDeltas(SLE::const_ref vaultSle, LoanPaymentParts const &parts)
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.
AccountingDeltas loanOriginationDeltas(SLE::const_ref vaultSle, Number const &principalRequested, Number const &interestDue)
Number computeManagementFee(Asset const &asset, Number const &interest, TenthBips32 managementFeeRate, std::int32_t scale)
TERSubset< CanCvtToTER > TER
Number loanVaultExposure(SLE::const_ref vaultSle, SLE::const_ref loanSle)
@ tecINSUFFICIENT_PAYMENT
static constexpr std::uint32_t kSecondsInYear
VaultVersion getVaultVersion(SLE::const_ref vault)
Resolves a Vault's LEVersion, the single point every accounting touch point should call to determine ...
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 total() const
Calculates the total change across all components.
Number trackedPrincipalDelta
PaymentSpecialCase specialCase
Number trackedManagementFeeDelta
Number trackedInterestPart() const
Calculates the tracked interest portion of this payment.
T time_since_epoch(T... args)