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/Indexes.h>
16#include <xrpl/protocol/LedgerFormats.h>
17#include <xrpl/protocol/Protocol.h>
18#include <xrpl/protocol/Rules.h>
19#include <xrpl/protocol/SField.h>
20#include <xrpl/protocol/STAmount.h>
21#include <xrpl/protocol/STLedgerEntry.h>
22#include <xrpl/protocol/STTx.h>
23#include <xrpl/protocol/TER.h>
24#include <xrpl/protocol/TxFlags.h>
25#include <xrpl/protocol/TxFormats.h>
26#include <xrpl/protocol/Units.h>
42 Asset const& vaultAsset,
48 sleBroker && sleBroker->getType() == ltLOAN_BROKER,
49 "xrpl::canApplyToBrokerCover : valid LoanBroker sle");
50 XRPL_ASSERT(vaultAsset == amount.
asset(),
"xrpl::canApplyToBrokerCover : valid asset");
58 int const coverScale =
scale(sleBroker->at(sfCoverAvailable), vaultAsset);
62 <<
" rounds to zero at cover scale " << coverScale;
72 if (!rules.
enabled(featureSingleAssetVault))
75 if (!rules.
enabled(featureMPTokensV1))
110 Asset const vaultAsset = vaultSle->at(sfAsset);
113 .broker = brokerSle->at(sfAccount),
114 .vault = vaultSle->at(sfAccount),
115 .asset = vaultAsset};
124 "xrpl::LoanPaymentParts::operator+= : other principal "
128 "xrpl::LoanPaymentParts::operator+= : other interest paid "
132 "xrpl::LoanPaymentParts::operator+= : other fee paid "
177 loanSle->at(sfNextPaymentDueDate),
182namespace instant_recognition {
187 return {.assetsTotalDelta = interestDue, .debtTotalDelta = principalRequested + interestDue};
192 Number const& vaultMaximum,
194 Number const& interestDue)
196 return vaultMaximum != 0 && interestDue > vaultMaximum - vaultTotal;
209 return loanSle->at(sfTotalValueOutstanding) - loanSle->at(sfManagementFeeOutstanding);
222namespace cash_basis {
227 return {.assetsTotalDelta =
kNumZero, .debtTotalDelta = principalRequested};
238 return loanSle->at(sfPrincipalOutstanding);
266 Number const& principalRequested,
267 Number const& interestDue)
269 return cashBasisEnabled(vaultSle)
278 Number const& interestDue)
282 if (cashBasisEnabled(vaultSle))
285 auto const vaultMaximum = vaultSle->at(sfAssetsMaximum);
287 vaultMaximum, vaultTotal, interestDue);
334 "xrpl::detail::computePowerMinusOne",
335 "periodicRate is non-negative");
337 if (paymentsRemaining == 0 || periodicRate ==
beast::kZero)
341 Number term = paymentsRemaining * periodicRate;
346 term = term * periodicRate * (paymentsRemaining - k) / (k + 1);
373 "xrpl::detail::computePowerMinusOneHybrid",
374 "periodicRate is non-negative");
376 if (paymentsRemaining == 0 || periodicRate ==
beast::kZero)
384 Number const cancellationThreshold{1, -9};
385 if (paymentsRemaining * periodicRate >= cancellationThreshold)
386 return power(1 + periodicRate, paymentsRemaining) - 1;
399 Number const& periodicRate,
402 if (paymentsRemaining == 0)
407 return Number{1} / paymentsRemaining;
409 if (rules.
enabled(fixCleanup3_2_0))
411 Number const raisedRateMinusOne =
413 Number const raisedRate = 1 + raisedRateMinusOne;
415 return (periodicRate * raisedRate) / raisedRateMinusOne;
421 Number const raisedRate =
power(1 + periodicRate, paymentsRemaining);
423 return (periodicRate * raisedRate) / (raisedRate - 1);
434 Number const& principalOutstanding,
435 Number const& periodicRate,
438 if (principalOutstanding == 0 || paymentsRemaining == 0)
443 return principalOutstanding / paymentsRemaining;
456 Number const& periodicPayment,
457 Number const& periodicRate,
460 if (paymentsRemaining == 0)
463 if (periodicRate == 0)
464 return periodicPayment * paymentsRemaining;
496 Number const& rawInterest,
501 auto const interest =
roundToAsset(asset, rawInterest, loanScale, mode);
512 Number const& principalOutstanding,
527 if (now <= nextPaymentDueDate)
531 auto const secondsOverdue = now - nextPaymentDueDate;
535 return principalOutstanding * rate;
545 Number const& principalOutstanding,
546 Number const& periodicRate,
555 if (paymentInterval == 0)
558 auto const lastPaymentDate =
std::max(prevPaymentDate, startDate);
563 if (now <= lastPaymentDate)
567 auto const secondsSinceLastPayment = now - lastPaymentDate;
572 return principalOutstanding * periodicRate * secondsSinceLastPayment / paymentInterval;
584 auto totalValueOutstandingProxy = loan->at(sfTotalValueOutstanding);
585 auto principalOutstandingProxy = loan->at(sfPrincipalOutstanding);
586 auto managementFeeOutstandingProxy = loan->at(sfManagementFeeOutstanding);
587 auto paymentRemainingProxy = loan->at(sfPaymentRemaining);
588 auto prevPaymentDateProxy = loan->at(sfPreviousPaymentDueDate);
589 auto nextDueDateProxy = loan->at(sfNextPaymentDueDate);
590 std::uint32_t const paymentInterval = loan->at(sfPaymentInterval);
592 XRPL_ASSERT_PARTS(nextDueDateProxy,
"xrpl::detail::doPayment",
"Next due date proxy set");
598 "xrpl::detail::doPayment",
599 "Full principal payment");
602 "xrpl::detail::doPayment",
603 "Full value payment");
606 "xrpl::detail::doPayment",
607 "Full management fee payment");
610 paymentRemainingProxy = 0;
613 prevPaymentDateProxy = *nextDueDateProxy;
617 nextDueDateProxy = 0;
622 principalOutstandingProxy = 0;
623 totalValueOutstandingProxy = 0;
624 managementFeeOutstandingProxy = 0;
631 paymentRemainingProxy -= 1;
633 prevPaymentDateProxy = nextDueDateProxy;
634 nextDueDateProxy += paymentInterval;
638 "xrpl::detail::doPayment",
639 "Partial principal payment");
642 "xrpl::detail::doPayment",
643 "Partial value payment");
648 "xrpl::detail::doPayment",
649 "Valid management fee");
659 static_cast<Number>(principalOutstandingProxy) <=
660 static_cast<Number>(totalValueOutstandingProxy),
661 "xrpl::detail::doPayment",
662 "principal does not exceed total");
666 "xrpl::detail::doPayment",
667 "fee outstanding stays valid");
699std::expected<std::pair<LoanPaymentParts, LoanProperties>,
TER>
706 Number const& periodicPayment,
707 Number const& periodicRate,
714 rules, periodicPayment, periodicRate, paymentRemaining, managementFeeRate);
720 auto const errors = roundedOldState - theoreticalState;
724 auto const newTheoreticalPrincipal =
std::max(
734 newTheoreticalPrincipal,
740 JLOG(j.
debug()) <<
"new periodic payment: " << newLoanProperties.periodicPayment
741 <<
", new total value: " << newLoanProperties.loanState.valueOutstanding
742 <<
", first payment principal: " << newLoanProperties.firstPaymentPrincipal;
747 auto const newTheoreticalState = [&]() {
750 newLoanProperties.periodicPayment,
756 if (!rules.
enabled(fixCleanup3_2_0))
768 Number const managementFee =
773 JLOG(j.
debug()) <<
"new theoretical value: " << newTheoreticalState.valueOutstanding
774 <<
", principal: " << newTheoreticalState.principalOutstanding
775 <<
", interest gross: " << newTheoreticalState.interestOutstanding();
783 newTheoreticalState.principalOutstanding,
788 auto const totalValueOutstanding =
std::clamp(
791 principalOutstanding + newTheoreticalState.interestOutstanding(),
796 auto const managementFeeOutstanding =
std::clamp(
797 roundToAsset(asset, newTheoreticalState.managementFeeDue, loanScale),
801 auto const roundedNewState =
802 constructLoanState(totalValueOutstanding, principalOutstanding, managementFeeOutstanding);
806 newLoanProperties.loanState = roundedNewState;
808 JLOG(j.
debug()) <<
"new rounded value: " << roundedNewState.valueOutstanding
809 <<
", principal: " << roundedNewState.principalOutstanding
810 <<
", interest gross: " << roundedNewState.interestOutstanding();
815 principalOutstanding,
825 JLOG(j.
warn()) <<
"Principal overpayment would cause the loan to be in "
826 "an invalid state. Ignore the overpayment";
833 if (newLoanProperties.periodicPayment <= 0 ||
834 newLoanProperties.loanState.valueOutstanding <= 0 ||
835 newLoanProperties.loanState.managementFeeDue < 0)
838 JLOG(j.
warn()) <<
"Overpayment not allowed: Computed loan "
839 "properties are invalid. Does "
840 "not compute. TotalValueOutstanding: "
841 << newLoanProperties.loanState.valueOutstanding
842 <<
", PeriodicPayment : " << newLoanProperties.periodicPayment
843 <<
", ManagementFeeOwedToBroker: "
844 << newLoanProperties.loanState.managementFeeDue;
849 auto const deltas = roundedOldState - roundedNewState;
854 deltas.managementFee == roundedOldState.
managementFeeDue - managementFeeOutstanding,
855 "xrpl::detail::tryOverpayment",
865 auto const valueChange = -deltas.interest;
868 JLOG(j.
warn()) <<
"Principal overpayment would increase the value of "
869 "the loan. Ignore the overpayment";
876 .principalPaid = deltas.principal,
902std::expected<LoanPaymentParts, TER>
909 Number const& periodicRate,
913 auto totalValueOutstandingProxy = loan->at(sfTotalValueOutstanding);
914 auto principalOutstandingProxy = loan->at(sfPrincipalOutstanding);
915 auto managementFeeOutstandingProxy = loan->at(sfManagementFeeOutstanding);
916 auto periodicPaymentProxy = loan->at(sfPeriodicPayment);
917 auto const paymentsRemaining = loan->at(sfPaymentRemaining);
920 totalValueOutstandingProxy, principalOutstandingProxy, managementFeeOutstandingProxy);
921 auto const periodicPayment = periodicPaymentProxy;
922 JLOG(j.
debug()) <<
"overpayment components:"
923 <<
", totalValue before: " << *totalValueOutstandingProxy
929 <<
", totalDue: " << overpaymentComponents.
totalDue
930 <<
", payments remaining :" << paymentsRemaining;
938 overpaymentComponents,
948 auto const& [loanPaymentParts, newLoanProperties] = *ret;
949 auto const newRoundedLoanState = newLoanProperties.loanState;
954 if (principalOutstandingProxy <= newRoundedLoanState.principalOutstanding)
957 JLOG(j.
warn()) <<
"Overpayment not allowed: principal "
958 <<
"outstanding did not decrease. Before: " << *principalOutstandingProxy
959 <<
". After: " << newRoundedLoanState.principalOutstanding;
967 JLOG(j.
debug()) <<
"valueChange: " << loanPaymentParts.valueChange
968 <<
", totalValue before: " << *totalValueOutstandingProxy
969 <<
", totalValue after: " << newRoundedLoanState.valueOutstanding
970 <<
", totalValue delta: "
971 << (totalValueOutstandingProxy - newRoundedLoanState.valueOutstanding)
973 <<
", principalPaid: " << loanPaymentParts.principalPaid
974 <<
", Computed difference: "
976 (totalValueOutstandingProxy - newRoundedLoanState.valueOutstanding);
999 [[maybe_unused]]
bool const fix320Enabled = rules.
enabled(fixCleanup3_2_0);
1003 principalOutstandingProxy - newRoundedLoanState.principalOutstanding,
1004 "xrpl::detail::doOverpayment : principal change agrees");
1009 Number const tvoChange = newRoundedLoanState.valueOutstanding -
1011 Number const managementFeeReleased =
1012 managementFeeOutstandingProxy - newRoundedLoanState.managementFeeDue;
1014 return loanPaymentParts.valueChange == tvoChange + managementFeeReleased + interestPart;
1016 "xrpl::detail::doOverpayment : interest paid agrees");
1021 "xrpl::detail::doOverpayment : principal payment matches");
1025 totalValueOutstandingProxy = newRoundedLoanState.valueOutstanding;
1026 principalOutstandingProxy = newRoundedLoanState.principalOutstanding;
1027 managementFeeOutstandingProxy = newRoundedLoanState.managementFeeDue;
1028 periodicPaymentProxy = newLoanProperties.periodicPayment;
1030 return loanPaymentParts;
1046std::expected<ExtendedPaymentComponents, TER>
1056 std::int32_t const nextDueDate = loan->at(sfNextPaymentDueDate);
1069 loan->at(sfPrincipalOutstanding),
1076 auto const [roundedLateInterest, roundedLateManagementFee] =
1079 XRPL_ASSERT(roundedLateInterest >= 0,
"xrpl::detail::computeLatePayment : valid late interest");
1082 "xrpl::detail::computeLatePayment",
1083 "no extra parts to this payment");
1107 "xrpl::detail::computeLatePayment",
1108 "total due is rounded");
1115 JLOG(j.
warn()) <<
"Late loan payment amount is insufficient. Due: " << late.
totalDue
1116 <<
", paid: " << amount;
1142std::expected<ExtendedPaymentComponents, TER>
1147 Number const& periodicRate,
1152 std::uint32_t const paymentRemaining = loan->at(sfPaymentRemaining);
1156 if (paymentRemaining <= 1)
1159 JLOG(j.
warn()) <<
"Last payment cannot be a full payment.";
1167 view.
rules(), loan->at(sfPeriodicPayment), periodicRate, paymentRemaining);
1172 theoreticalPrincipalOutstanding,
1175 loan->at(sfPaymentInterval),
1176 loan->at(sfPreviousPaymentDueDate),
1177 loan->at(sfStartDate),
1189 Number const closePaymentFee =
roundToAsset(asset, loan->at(sfClosePaymentFee), loanScale);
1196 .trackedValueDelta =
1197 principalOutstanding + totalInterestOutstanding + managementFeeOutstanding,
1198 .trackedPrincipalDelta = principalOutstanding,
1202 .trackedManagementFeeDelta = managementFeeOutstanding,
1213 closePaymentFee + roundedFullManagementFee - managementFeeOutstanding,
1222 roundedFullInterest - totalInterestOutstanding,
1227 "xrpl::detail::computeFullPayment",
1228 "total due is rounded");
1230 JLOG(j.
trace()) <<
"computeFullPayment result: periodicRate: " << periodicRate
1231 <<
", paymentRemaining: " << paymentRemaining
1232 <<
", theoreticalPrincipalOutstanding: " << theoreticalPrincipalOutstanding
1233 <<
", fullPaymentInterest: " << fullPaymentInterest
1234 <<
", roundedFullInterest: " << roundedFullInterest
1235 <<
", roundedFullManagementFee: " << roundedFullManagementFee
1277 Number const& totalValueOutstanding,
1278 Number const& principalOutstanding,
1279 Number const& managementFeeOutstanding,
1280 Number const& periodicPayment,
1281 Number const& periodicRate,
1289 "xrpl::detail::computePaymentComponents",
1290 "Outstanding values are rounded");
1292 paymentRemaining > 0,
"xrpl::detail::computePaymentComponents",
"some payments remaining");
1298 if (paymentRemaining == 1 || totalValueOutstanding <= roundedPeriodicPayment)
1303 .trackedValueDelta = totalValueOutstanding,
1304 .trackedPrincipalDelta = principalOutstanding,
1305 .trackedManagementFeeDelta = managementFeeOutstanding,
1312 rules, periodicPayment, periodicRate, paymentRemaining - 1, managementFeeRate);
1321 bool const fixCleanup320Enabled = rules.
enabled(fixCleanup3_2_0);
1328 .principalOutstanding =
1335 constructLoanState(totalValueOutstanding, principalOutstanding, managementFeeOutstanding);
1348 "xrpl::detail::computePaymentComponents",
1349 "principal delta not greater than outstanding");
1354 if (fixCleanup320Enabled)
1358 "xrpl::detail::computePaymentComponents",
1359 "interest due delta not greater than outstanding");
1370 "xrpl::detail::computePaymentComponents",
1371 "management fee due delta not greater than outstanding");
1384 auto takeFrom = [](
Number& component,
Number& excess) {
1387 auto part =
std::min(component, excess);
1393 "xrpl::detail::computePaymentComponents",
1394 "excess non-negative");
1414 "xrpl::detail::computePaymentComponents : payment exceeded loan "
1416 addressExcess(deltas, totalOverpayment);
1421 Number shortage = roundedPeriodicPayment - deltas.
total();
1425 "xrpl::detail::computePaymentComponents",
1426 "shortage is rounded");
1431 Number excess = -shortage;
1432 addressExcess(deltas, excess);
1441 "xrpl::detail::computePaymentComponents",
1442 "no shortage or excess");
1447 "xrpl::detail::computePaymentComponents",
1448 "total value adds up");
1453 "xrpl::detail::computePaymentComponents",
1454 "valid principal result");
1457 "xrpl::detail::computePaymentComponents",
1458 "valid interest result");
1462 "xrpl::detail::computePaymentComponents",
1463 "valid fee result");
1467 "xrpl::detail::computePaymentComponents",
1468 "payment parts add to payment");
1472 .trackedValueDelta =
1474 .trackedPrincipalDelta =
1476 .trackedManagementFeeDelta =
1493 Number const& periodicRate,
1499 loan->at(sfLoanScale),
1500 loan->at(sfTotalValueOutstanding),
1501 loan->at(sfPrincipalOutstanding),
1502 loan->at(sfManagementFeeOutstanding),
1503 loan->at(sfPeriodicPayment),
1505 loan->at(sfPaymentRemaining),
1527ExtendedPaymentComponents
1531 int32_t
const loanScale,
1532 Number const& overpayment,
1538 rules.
enabled(fixCleanup3_2_0),
1539 overpayment > 0 &&
isRounded(asset, overpayment, loanScale),
1540 "xrpl::detail::computeOverpaymentComponents : valid overpayment "
1546 Number const overpaymentFee =
1553 auto const [roundedOverpaymentInterest, roundedOverpaymentManagementFee] =
1565 .trackedValueDelta = overpayment - overpaymentFee,
1566 .trackedPrincipalDelta = overpayment - roundedOverpaymentInterest -
1567 roundedOverpaymentManagementFee - overpaymentFee,
1568 .trackedManagementFeeDelta = roundedOverpaymentManagementFee,
1577 roundedOverpaymentInterest};
1579 result.trackedInterestPart() == roundedOverpaymentInterest,
1580 "xrpl::detail::computeOverpaymentComponents",
1581 "valid interest computation");
1592 TenthBips16 const managementFeeRate{brokerSle->at(sfManagementFeeRate)};
1593 TenthBips32 const interestRate{loan->at(sfInterestRate)};
1595 XRPL_ASSERT(interestRate == 0 || periodicRate > 0,
"xrpl::detail::loanRatesFor : valid rate");
1596 return {managementFeeRate, periodicRate};
1603std::expected<LoanPaymentParts, TER>
1612 auto const [managementFeeRate, periodicRate] =
loanRatesFor(loan, brokerSle);
1614 auto const fullPaymentComponents =
1619 if (fullPaymentComponents.has_value())
1620 return doPayment(*fullPaymentComponents, loan);
1628std::expected<LoanPaymentParts, TER>
1637 auto const [managementFeeRate, periodicRate] =
loanRatesFor(loan, brokerSle);
1639 Number const serviceFee = loan->at(sfLoanServiceFee);
1645 "xrpl::detail::makeLatePayment",
1646 "regular payment valid principal");
1648 auto const latePaymentComponents =
1653 if (latePaymentComponents.has_value())
1654 return doPayment(*latePaymentComponents, loan);
1662std::expected<LoanPaymentParts, TER>
1676 "xrpl::detail::makeRegularPayment",
1677 "regular payment type");
1679 auto const [managementFeeRate, periodicRate] =
loanRatesFor(loan, brokerSle);
1682 Number const serviceFee = loan->at(sfLoanServiceFee);
1689 "xrpl::detail::makeRegularPayment",
1690 "regular payment valid principal");
1700 auto paymentRemainingProxy = loan->at(sfPaymentRemaining);
1702 while ((amount >= (totalPaid + periodic.
totalDue)) && paymentRemainingProxy > 0 &&
1703 numPayments < kLoanMaximumPaymentsPerTransaction)
1708 "xrpl::detail::makeRegularPayment",
1709 "payment pays non-negative principal");
1712 totalParts +=
doPayment(periodic, loan);
1717 "xrpl::detail::makeRegularPayment",
1718 "final payment is the final payment");
1729 if (numPayments == 0)
1731 JLOG(j.
warn()) <<
"Regular loan payment amount is insufficient. Due: " << periodic.
totalDue
1732 <<
", paid: " << amount;
1738 "xrpl::detail::makeRegularPayment",
1739 "payment parts add up");
1741 totalParts.
valueChange == 0,
"xrpl::detail::makeRegularPayment",
"no value change");
1752 auto const roundedAmount = view.
rules().
enabled(fixCleanup3_1_3)
1756 bool const overpaymentSupported =
1759 bool const overpaymentAllowed =
1760 paymentRemainingProxy > 0 &&
1761 totalPaid < roundedAmount &&
1762 numPayments < kLoanMaximumPaymentsPerTransaction;
1764 if (overpaymentSupported && overpaymentAllowed)
1766 TenthBips32 const overpaymentInterestRate{loan->at(sfOverpaymentInterestRate)};
1767 TenthBips32 const overpaymentFeeRate{loan->at(sfOverpaymentFee)};
1772 Number const overpaymentRaw =
1773 std::min(roundedAmount - totalPaid, *loan->at(sfTotalValueOutstanding));
1775 bool const fixEnabled = view.
rules().
enabled(fixCleanup3_2_0);
1776 Number const overpayment = fixEnabled
1782 if (!fixEnabled || overpayment > 0)
1789 overpaymentInterestRate,
1799 "xrpl::detail::makeRegularPayment",
1800 "overpayment penalty did not reduce value of loan");
1805 overpaymentComponents,
1811 totalParts += *overResult;
1813 else if (overResult.error())
1830 "xrpl::detail::makeRegularPayment : total principal paid is valid");
1834 "xrpl::detail::makeRegularPayment : total interest paid is valid");
1837 "xrpl::detail::makeRegularPayment : loan value change is valid");
1840 "xrpl::detail::makeRegularPayment : fee paid is valid");
1846detail::LoanStateDeltas
1886 Asset const& vaultAsset,
1887 Number const& principalRequested,
1888 bool expectInterest,
1893 auto const totalInterestOutstanding =
1898 if (expectInterest && totalInterestOutstanding <= 0)
1902 JLOG(j.
warn()) <<
"Loan for " << principalRequested <<
" with interest has no interest due";
1907 if (!expectInterest && totalInterestOutstanding > 0)
1910 JLOG(j.
warn()) <<
"Loan for " << principalRequested <<
" with no interest has interest due";
1924 JLOG(j.
warn()) <<
"Loan is unable to pay principal.";
1931 auto const roundedPayment =
1936 <<
") rounds to 0. ";
1948 computedPayments != paymentTotal)
1951 <<
") rounding (" << roundedPayment <<
") on a total value of "
1953 <<
" can not complete the loan in the specified "
1954 "number of payments ("
1955 << computedPayments <<
" != " << paymentTotal <<
")";
1970 Number const& theoreticalPrincipalOutstanding,
1971 Number const& periodicRate,
1979 theoreticalPrincipalOutstanding,
1986 accruedInterest >= 0,
1987 "xrpl::detail::computeFullPaymentInterest : valid accrued "
1991 auto const prepaymentPenalty = closeInterestRate ==
beast::kZero
1996 prepaymentPenalty >= 0,
1997 "xrpl::detail::computeFullPaymentInterest : valid prepayment "
2001 return accruedInterest + prepaymentPenalty;
2029 Number const& periodicPayment,
2030 Number const& periodicRate,
2034 if (paymentRemaining == 0)
2037 .valueOutstanding = 0,
2038 .principalOutstanding = 0,
2040 .managementFeeDue = 0};
2044 Number const totalValueOutstanding = periodicPayment * paymentRemaining;
2047 rules, periodicPayment, periodicRate, paymentRemaining);
2050 Number const interestOutstandingGross = totalValueOutstanding - principalOutstanding;
2053 Number const managementFeeOutstanding =
2057 Number const interestOutstandingNet = interestOutstandingGross - managementFeeOutstanding;
2060 .valueOutstanding = totalValueOutstanding,
2061 .principalOutstanding = principalOutstanding,
2062 .interestDue = interestOutstandingNet,
2063 .managementFeeDue = managementFeeOutstanding,
2089 Number const& totalValueOutstanding,
2090 Number const& principalOutstanding,
2091 Number const& managementFeeOutstanding)
2096 .valueOutstanding = totalValueOutstanding,
2097 .principalOutstanding = principalOutstanding,
2098 .interestDue = totalValueOutstanding - principalOutstanding - managementFeeOutstanding,
2099 .managementFeeDue = managementFeeOutstanding};
2105 XRPL_ASSERT(loan && loan->getType() == ltLOAN,
"xrpl::constructLoanState : valid loan SLE");
2108 loan->at(sfTotalValueOutstanding),
2109 loan->at(sfPrincipalOutstanding),
2110 loan->at(sfManagementFeeOutstanding));
2142 Number const& principalOutstanding,
2150 XRPL_ASSERT(interestRate == 0 || periodicRate > 0,
"xrpl::computeLoanProperties : valid rate");
2154 principalOutstanding,
2173 Number const& principalOutstanding,
2174 Number const& periodicRate,
2179 auto const periodicPayment =
2182 auto const [totalValueOutstanding, loanScale] = [&]() {
2191 STAmount amount{asset, periodicPayment * paymentsRemaining};
2198 (amount.
integral() && loanScale == 0) ||
2200 "xrpl::computeLoanProperties",
2201 "loanScale value fits expectations");
2213 auto const roundedPrincipalOutstanding =
2217 auto const totalInterestOutstanding = totalValueOutstanding - roundedPrincipalOutstanding;
2218 auto const feeOwedToBroker =
2224 auto const firstPaymentPrincipal = [&]() {
2228 rules, periodicPayment, periodicRate, paymentsRemaining, managementFeeRate);
2231 rules, periodicPayment, periodicRate, paymentsRemaining - 1, managementFeeRate);
2235 return startingState.principalOutstanding - firstPaymentState.principalOutstanding;
2239 .periodicPayment = periodicPayment,
2241 constructLoanState(totalValueOutstanding, roundedPrincipalOutstanding, feeOwedToBroker),
2242 .loanScale = loanScale,
2243 .firstPaymentPrincipal = firstPaymentPrincipal,
2253std::expected<LoanPaymentParts, TER>
2263 if (loan->at(sfPaymentRemaining) == 0 || loan->at(sfPrincipalOutstanding) == 0)
2267 JLOG(j.
warn()) <<
"Loan is already paid off.";
2273 auto nextDueDateProxy = loan->at(sfNextPaymentDueDate);
2274 if (*nextDueDateProxy == 0)
2276 JLOG(j.
warn()) <<
"Loan next payment due date is not set.";
2281 *loan->at(sfTotalValueOutstanding) > 0,
"xrpl::loanMakePayment : valid total value");
2291 JLOG(j.
warn()) <<
"Loan payment is overdue. Use the tfLoanLatePayment transaction flag to "
2292 "make a late payment. Loan was created on "
2293 << loan->at(sfStartDate) <<
", prev payment due date is "
2294 << loan->at(sfPreviousPaymentDueDate) <<
", next payment due date is "
2295 << nextDueDateProxy <<
", ledger time is "
2300 switch (paymentType)
2312 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.
AccountID const & getIssuer() const
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.
virtual SLE::const_pointer read(Keylet const &k) const =0
Return the state item associated with a key.
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 > const & ConstRef
std::shared_ptr< STLedgerEntry > const & Ref
bool isFlag(std::uint32_t) const
bool isFieldPresent(SField const &field) const
TxType getTxnType() const
AccountingDeltas loanPaymentDeltas(LoanPaymentParts const &parts)
AccountingDeltas loanOriginationDeltas(Number const &principalRequested)
Number loanVaultExposure(SLE::ConstRef loanSle)
Number computePaymentFactor(Rules const &rules, Number const &periodicRate, std::uint32_t paymentsRemaining)
LoanPaymentParts doPayment(ExtendedPaymentComponents const &payment, SLE::Ref loan)
std::pair< TenthBips16, Number > loanRatesFor(SLE::ConstRef loan, SLE::ConstRef brokerSle)
Number loanPrincipalFromPeriodicPayment(Rules const &rules, Number const &periodicPayment, Number const &periodicRate, std::uint32_t paymentsRemaining)
std::expected< ExtendedPaymentComponents, TER > computeLatePayment(Asset const &asset, ReadView const &view, SLE::ConstRef loan, ExtendedPaymentComponents const &periodic, STAmount const &amount, TenthBips16 managementFeeRate, beast::Journal j)
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 > computeFullPayment(Asset const &asset, ReadView const &view, SLE::ConstRef loan, Number const &periodicRate, 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 > 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 > makeFullPayment(Asset const &asset, ApplyView &view, SLE::Ref loan, SLE::ConstRef 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::expected< LoanPaymentParts, TER > makeLatePayment(Asset const &asset, ApplyView const &view, SLE::Ref loan, SLE::ConstRef brokerSle, STAmount const &amount, beast::Journal j)
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)
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< LoanPaymentParts, TER > makeRegularPayment(Asset const &asset, ApplyView const &view, SLE::Ref loan, SLE::ConstRef brokerSle, STAmount const &amount, LoanPaymentType const paymentType, 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)
AccountingDeltas loanPaymentDeltas(LoanPaymentParts const &parts)
Number loanVaultExposure(SLE::ConstRef loanSle)
AccountingDeltas loanOriginationDeltas(Number const &principalRequested, Number const &interestDue)
bool loanOriginationExceedsVaultMaximum(Number const &vaultMaximum, Number const &vaultTotal, Number const &interestDue)
Keylet vault(AccountID const &owner, SeqProxy const &seq) noexcept
Keylet loanBroker(AccountID const &owner, SeqProxy const &seq) noexcept
Keylet loan(UInt256 const &loanBrokerID, SeqProxy const &loanSeq) noexcept
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 loanOriginationExceedsVaultMaximum(SLE::ConstRef vaultSle, Number const &vaultTotal, Number const &interestDue)
bool hasExpired(ReadView const &view, std::optional< std::uint32_t > const &exp, ExpiryComparison comparison=ExpiryComparison::Inclusive)
Determines whether the given expiration time has passed.
Number operator-(Number const &x, Number const &y)
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.
std::expected< LoanPaymentParts, TER > loanMakePayment(Asset const &asset, ApplyView &view, SLE::Ref loan, SLE::ConstRef brokerSle, STAmount const &amount, LoanPaymentType const paymentType, beast::Journal j)
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)
bool isPaymentLate(ReadView const &view, SLE::ConstRef loanSle)
std::optional< LoanDefaultFreezeExemptAccounts > getLoanDefaultFreezeExemptAccounts(ReadView const &view, STTx const &tx)
Resolves the accounts and asset a LoanManage default transaction is exempt from freeze/lock for.
LoanState computeTheoreticalLoanState(Rules const &rules, Number const &periodicPayment, Number const &periodicRate, std::uint32_t const paymentRemaining, TenthBips32 const managementFeeRate)
AccountingDeltas loanPaymentDeltas(SLE::ConstRef vaultSle, LoanPaymentParts const &parts)
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.
Number computeManagementFee(Asset const &asset, Number const &interest, TenthBips32 managementFeeRate, std::int32_t scale)
TERSubset< CanCvtToTER > TER
Number loanVaultExposure(SLE::ConstRef vaultSle, SLE::ConstRef loanSle)
@ 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)
VaultVersion getVaultVersion(SLE::ConstRef vault)
Resolves a Vault's LEVersion, the single point every accounting touch point should call to determine ...
AccountingDeltas loanOriginationDeltas(SLE::ConstRef vaultSle, Number const &principalRequested, Number const &interestDue)
LoanState constructLoanState(Number const &totalValueOutstanding, Number const &principalOutstanding, Number const &managementFeeOutstanding)
TER canApplyToBrokerCover(ReadView const &view, SLE::ConstRef sleBroker, Asset const &vaultAsset, STAmount const &amount, beast::Journal j, std::string_view logPrefix)
Broker cover preclaim precision guard (fixCleanup3_2_0).
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)
The accounts and asset that LoanManage::defaultLoan's fixCleanup3_4_0 freeze/lock exemption applies t...
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)