xrpld
Loading...
Searching...
No Matches
LendingHelpers.h
1#pragma once
2
3#include <xrpl/basics/Number.h>
4#include <xrpl/basics/chrono.h>
5#include <xrpl/beast/utility/Journal.h>
6#include <xrpl/beast/utility/Zero.h>
7#include <xrpl/beast/utility/instrumentation.h>
8#include <xrpl/ledger/ApplyView.h>
9#include <xrpl/ledger/ReadView.h>
10#include <xrpl/protocol/Asset.h>
11#include <xrpl/protocol/LedgerFormats.h> // IWYU pragma: keep
12#include <xrpl/protocol/Protocol.h>
13#include <xrpl/protocol/Rules.h>
14#include <xrpl/protocol/SField.h>
15#include <xrpl/protocol/STAmount.h>
16#include <xrpl/protocol/STLedgerEntry.h>
17#include <xrpl/protocol/STNumber.h> // IWYU pragma: keep
18#include <xrpl/protocol/STTx.h>
19#include <xrpl/protocol/TER.h>
20#include <xrpl/protocol/Units.h>
21
22#include <cstdint>
23#include <expected>
24#include <string_view>
25#include <utility>
26
27namespace xrpl {
28
48[[nodiscard]] TER
50 ReadView const& view,
51 SLE::const_ref sleBroker,
52 Asset const& vaultAsset,
53 STAmount const& amount,
54 beast::Journal j,
55 std::string_view logPrefix);
56
57// Lending protocol has dependencies, so capture them here.
58bool
59checkLendingProtocolDependencies(Rules const& rules, STTx const& tx);
60
61static constexpr std::uint32_t kSecondsInYear = 365 * 24 * 60 * 60;
62
64loanPeriodicRate(TenthBips32 interestRate, std::uint32_t paymentInterval);
65
69inline Number
70roundPeriodicPayment(Asset const& asset, Number const& periodicPayment, std::int32_t scale)
71{
72 return roundToAsset(asset, periodicPayment, scale, Number::RoundingMode::Upward);
73}
74
75/* Represents the breakdown of amounts to be paid and changes applied to the
76 * Loan object while processing a loan payment.
77 *
78 * This structure is returned after processing a loan payment transaction and
79 * captures the amounts that need to be paid. The actual ledger entry changes
80 * are made in LoanPay based on this structure values.
81 *
82 * The sum of principalPaid, interestPaid, and feePaid represents the total
83 * amount to be deducted from the borrower's account. The valueChange field
84 * tracks whether the loan's total value increased or decreased beyond normal
85 * amortization.
86 *
87 * This structure is explained in the XLS-66 spec, section 3.2.4.2 (Payment
88 * Processing).
89 */
91{
92 // The amount of principal paid that reduces the loan balance.
93 // This amount is subtracted from sfPrincipalOutstanding in the Loan object
94 // and paid to the Vault
96
97 // The total amount of interest paid to the Vault.
98 // This includes:
99 // - Tracked interest from the amortization schedule
100 // - Untracked interest (e.g., late payment penalty interest)
101 // This value is always non-negative.
103
104 // The change in the loan's total value outstanding.
105 // - If valueChange < 0: Loan value decreased
106 // - If valueChange > 0: Loan value increased
107 // - If valueChange = 0: No value adjustment
108 //
109 // For regular on-time payments, this is always 0. Non-zero values occur
110 // when:
111 // - Overpayments reduce the loan balance beyond the scheduled amount
112 // - Late payments add penalty interest to the loan value
113 // - Early full payment may increase or decrease the loan value based on
114 // terms
116
117 /* The total amount of fees paid to the Broker.
118 * This includes:
119 * - Tracked management fees from the amortization schedule
120 * - Untracked fees (e.g., late payment fees, service fees, origination
121 * fees) This value is always non-negative.
122 */
124
126 operator+=(LoanPaymentParts const& other);
127
128 bool
129 operator==(LoanPaymentParts const& other) const;
130};
131
145{
146 // Total value still due to be paid by the borrower.
148 // Principal still due to be paid by the borrower.
150 // Interest still due to be paid to the Vault.
151 // This is a portion of interestOutstanding
153 // Management fee still due to be paid to the broker.
154 // This is a portion of interestOutstanding
156
157 // Interest still due to be paid by the borrower.
158 [[nodiscard]] Number
160 {
161 XRPL_ASSERT_PARTS(
163 "xrpl::LoanState::interestOutstanding",
164 "other values add up correctly");
166 }
167};
168
169/* Describes the initial computed properties of a loan.
170 *
171 * This structure contains the fundamental calculated values that define a
172 * loan's payment structure and amortization schedule. These properties are
173 * computed:
174 * - At loan creation (LoanSet transaction)
175 * - When loan terms change (e.g., after an overpayment that reduces the loan
176 * balance)
177 */
179{
180 // The unrounded amount to be paid at each regular payment period.
181 // Calculated using the standard amortization formula based on principal,
182 // interest rate, and number of payments.
183 // The actual amount paid in the LoanPay transaction must be rounded up to
184 // the precision of the asset and loan.
186
187 // The loan's current state, with all values rounded to the loan's scale.
189
190 // The scale (decimal places) used for rounding all loan amounts.
191 // This is the maximum of:
192 // - The asset's native scale
193 // - A minimum scale required to represent the periodic payment accurately
194 // All loan state values (principal, interest, fees) are rounded to this
195 // scale.
197
198 // The principal portion of the first payment.
200};
201
202// Some values get re-rounded to the vault scale any time they are adjusted. In
203// addition, they are prevented from ever going below zero. This helps avoid
204// accumulated rounding errors and leftover dust amounts.
205template <class NumberProxy>
206void
208 NumberProxy value,
209 Number const& adjustment,
210 Asset const& asset,
211 int vaultScale)
212{
213 value = roundToAsset(asset, value + adjustment, vaultScale);
214
215 if (*value < beast::kZero)
216 value = 0;
217}
218
219inline int
221{
222 if (!vaultSle)
223 return Number::kMinExponent - 1; // LCOV_EXCL_LINE
224 return scale(vaultSle->at(sfAssetsTotal), vaultSle->at(sfAsset));
225}
226
227// Compute the minimum required broker cover, rounded consistently.
228// DebtTotal is a broker-level aggregate maintained at vault scale, so the
229// rounding must also use vault scale — never an individual loan's scale.
230inline Number
231minimumBrokerCover(Number const& debtTotal, TenthBips32 coverRateMinimum, SLE::const_ref vaultSle)
232{
233 XRPL_ASSERT(
234 vaultSle && vaultSle->getType() == ltVAULT, "xrpl::minimumBrokerCover : valid Vault sle");
236 return roundToAsset(
237 vaultSle->at(sfAsset),
238 tenthBipsOfValue(debtTotal, coverRateMinimum),
239 getAssetsTotalScale(vaultSle));
240}
241
242TER
244 Asset const& vaultAsset,
245 Number const& principalRequested,
246 bool expectInterest,
247 std::uint32_t paymentTotal,
248 LoanProperties const& properties,
250
251LoanState
253 Rules const& rules,
254 Number const& periodicPayment,
255 Number const& periodicRate,
256 std::uint32_t const paymentRemaining,
257 TenthBips32 const managementFeeRate);
258
259// Constructs a valid LoanState object from arbitrary inputs
260LoanState
262 Number const& totalValueOutstanding,
263 Number const& principalOutstanding,
264 Number const& managementFeeOutstanding);
265
266// Overload of constructLoanState() that reads the three tracked fields
267// directly from a Loan ledger object, which always holds rounded values,
268// rather than taking them as separate Number arguments.
269LoanState
271
272Number
274 Asset const& asset,
275 Number const& interest,
276 TenthBips32 managementFeeRate,
278
279Number
281 Number const& theoreticalPrincipalOutstanding,
282 Number const& periodicRate,
283 NetClock::time_point parentCloseTime,
284 std::uint32_t paymentInterval,
285 std::uint32_t prevPaymentDate,
286 std::uint32_t startDate,
287 TenthBips32 closeInterestRate);
288
289// Deltas applied to Vault.AssetsTotal and LoanBroker.DebtTotal at a single
290// accounting touch point (origination, payment, impair/unimpair/default).
296
297// Whole-life (pre-LendingProtocolV1_1) recognition model: interest is
298// recognized into AssetsTotal/DebtTotal up front, at origination.
299namespace accrual {
300
301// LoanSet origination: what's added to Vault.AssetsTotal and LoanBroker.DebtTotal
303loanOriginationDeltas(Number const& principalRequested, Number const& interestDue);
304
305// LoanSet origination: would recognizing this loan's interest push
306// Vault.AssetsTotal past Vault.AssetsMaximum?
307bool
309 Number const& vaultMaximum,
310 Number const& vaultTotal,
311 Number const& interestDue);
312
313// LoanManage impair/unimpair/default: the vault's exposure to this loan
314Number
316
317// LoanPay: what's added to Vault.AssetsTotal and subtracted from LoanBroker.DebtTotal for a payment
320
321} // namespace accrual
322
323// Cash-basis (LendingProtocolV1_1) recognition model: AssetsTotal/DebtTotal
324// are principal-only, interest is recognized only as it's actually paid.
325namespace cash_basis {
326
328loanOriginationDeltas(Number const& principalRequested);
329
330Number
332
335
336} // namespace cash_basis
337
338// Public dispatchers: pick cash_basis:: if featureLendingProtocolV1_1 is
339// enabled AND the Vault's LEVersion (VaultHelpers::getVaultVersion) is
340// VaultVersion::CashBasis, else accrual::. These are the only entry points
341// transactors call.
344 SLE::const_ref vaultSle,
345 Number const& principalRequested,
346 Number const& interestDue);
347
348bool
350 SLE::const_ref vaultSle,
351 Number const& vaultTotal,
352 Number const& interestDue);
353
354Number
356
359
360namespace detail {
361// These classes and functions should only be accessed by LendingHelper
362// functions and unit tests
363
365
366/* Represents a single loan payment component parts.
367
368* This structure captures the "delta" (change) values that will be applied to
369* the tracked fields in the Loan ledger object when a payment is processed.
370*
371* These are called "deltas" because they represent the amount by which each
372* corresponding field in the Loan object will be reduced.
373* They are "tracked" as they change tracked loan values.
374*/
376{
377 // The change in total value outstanding for this payment.
378 // This amount will be subtracted from sfTotalValueOutstanding in the Loan
379 // object. Equal to the sum of trackedPrincipalDelta,
380 // trackedInterestPart(), and trackedManagementFeeDelta.
382
383 // The change in principal outstanding for this payment.
384 // This amount will be subtracted from sfPrincipalOutstanding in the Loan
385 // object, representing the portion of the payment that reduces the
386 // original loan amount.
388
389 // The change in management fee outstanding for this payment.
390 // This amount will be subtracted from sfManagementFeeOutstanding in the
391 // Loan object. This represents only the tracked management fees from the
392 // amortization schedule and does not include additional untracked fees
393 // (such as late payment fees) that go directly to the broker.
395
396 // Indicates if this payment has special handling requirements.
397 // - none: Regular scheduled payment
398 // - final: The last payment that closes out the loan
399 // - extra: An additional payment beyond the regular schedule (overpayment)
401
410 [[nodiscard]] Number
411 trackedInterestPart() const;
412};
413
414/* Extends PaymentComponents with untracked payment amounts.
415 *
416 * This structure adds untracked fees and interest to the base
417 * PaymentComponents, representing amounts that don't affect the Loan object's
418 * tracked state but are still part of the total payment due from the borrower.
419 *
420 * Untracked amounts include:
421 * - Late payment fees that go directly to the Broker
422 * - Late payment penalty interest that goes directly to the Vault
423 * - Service fees
424 *
425 * The key distinction is that tracked amounts reduce the Loan object's state
426 * (sfTotalValueOutstanding, sfPrincipalOutstanding,
427 * sfManagementFeeOutstanding), while untracked amounts are paid directly to the
428 * recipient without affecting the loan's amortization schedule.
429 */
431{
432 // Additional management fees that go directly to the Broker.
433 // This includes fees not part of the standard amortization schedule
434 // (e.g., late fees, service fees, origination fees).
435 // This value may be negative, though the final value returned in
436 // LoanPaymentParts.feePaid will never be negative.
438
439 // Additional interest that goes directly to the Vault.
440 // This includes interest not part of the standard amortization schedule
441 // (e.g., late payment penalty interest).
442 // This value may be negative, though the final value returned in
443 // LoanPaymentParts.interestPaid will never be negative.
445
446 // The complete amount due from the borrower for this payment.
447 // Calculated as: trackedValueDelta + untrackedInterest +
448 // untrackedManagementFee
449 //
450 // This value is used to validate that the payment amount provided by the
451 // borrower is sufficient to cover all components of the payment.
453
461};
462
463/* Represents the differences between two loan states.
464 *
465 * This structure is used to capture the change in each component of a loan's
466 * state, typically when computing the difference between two LoanState objects
467 * (e.g., before and after a payment). It is a convenient way to capture changes
468 * in each component. How that difference is used depends on the context.
469 */
471{
472 // The difference in principal outstanding between two loan states.
474
475 // The difference in interest due between two loan states.
477
478 // The difference in management fee outstanding between two loan states.
480
485 [[nodiscard]] Number
486 total() const
487 {
489 }
490
491 // Ensures all delta values are non-negative.
492 void
493 nonNegative();
494};
495
496std::expected<std::pair<LoanPaymentParts, LoanProperties>, TER>
498 Rules const& rules,
499 Asset const& asset,
500 std::int32_t loanScale,
501 ExtendedPaymentComponents const& overpaymentComponents,
502 LoanState const& roundedLoanState,
503 Number const& periodicPayment,
504 Number const& periodicRate,
505 std::uint32_t paymentRemaining,
506 TenthBips16 const managementFeeRate,
508
509[[nodiscard]] Number
510computePowerMinusOne(Number const& periodicRate, std::uint32_t paymentsRemaining);
511
512[[nodiscard]] Number
513computePowerMinusOneHybrid(Number const& periodicRate, std::uint32_t paymentsRemaining);
514
515[[nodiscard]] Number
517 Rules const& rules,
518 Number const& periodicRate,
519 std::uint32_t paymentsRemaining);
520
523 Asset const& asset,
524 Number const& interest,
525 TenthBips16 managementFeeRate,
526 std::int32_t loanScale);
527
528Number
530 Rules const& rules,
531 Number const& principalOutstanding,
532 Number const& periodicRate,
533 std::uint32_t paymentsRemaining);
534
535Number
537 Rules const& rules,
538 Number const& periodicPayment,
539 Number const& periodicRate,
540 std::uint32_t paymentsRemaining);
541
542Number
544 Number const& principalOutstanding,
545 TenthBips32 lateInterestRate,
546 NetClock::time_point parentCloseTime,
547 std::uint32_t nextPaymentDueDate);
548
549Number
551 Number const& principalOutstanding,
552 Number const& periodicRate,
553 NetClock::time_point parentCloseTime,
554 std::uint32_t startDate,
555 std::uint32_t prevPaymentDate,
556 std::uint32_t paymentInterval);
557
558ExtendedPaymentComponents
560 Rules const& rules,
561 Asset const& asset,
562 int32_t const loanScale,
563 Number const& overpayment,
564 TenthBips32 const overpaymentInterestRate,
565 TenthBips32 const overpaymentFeeRate,
566 TenthBips16 const managementFeeRate);
567
568PaymentComponents
570 Rules const& rules,
571 Asset const& asset,
573 Number const& totalValueOutstanding,
574 Number const& principalOutstanding,
575 Number const& managementFeeOutstanding,
576 Number const& periodicPayment,
577 Number const& periodicRate,
578 std::uint32_t paymentRemaining,
579 TenthBips16 managementFeeRate);
580
581} // namespace detail
582
583detail::LoanStateDeltas
584operator-(LoanState const& lhs, LoanState const& rhs);
585
586LoanState
587operator-(LoanState const& lhs, detail::LoanStateDeltas const& rhs);
588
589LoanState
590operator+(LoanState const& lhs, detail::LoanStateDeltas const& rhs);
591
592LoanProperties
594 Rules const& rules,
595 Asset const& asset,
596 Number const& principalOutstanding,
597 TenthBips32 interestRate,
598 std::uint32_t paymentInterval,
599 std::uint32_t paymentsRemaining,
600 TenthBips32 managementFeeRate,
601 std::int32_t minimumScale);
602
603LoanProperties
605 Rules const& rules,
606 Asset const& asset,
607 Number const& principalOutstanding,
608 Number const& periodicRate,
609 std::uint32_t paymentsRemaining,
610 TenthBips32 managementFeeRate,
611 std::int32_t minimumScale);
612
613bool
614isRounded(Asset const& asset, Number const& value, std::int32_t scale);
615
616// Indicates what type of payment is being made.
617// regular, late, and full are mutually exclusive.
618// overpayment is an "add on" to a regular payment, and follows that path with
619// potential extra work at the end.
621
622std::expected<LoanPaymentParts, TER>
624 Asset const& asset,
625 ApplyView& view,
626 SLE::ref loan,
627 SLE::const_ref brokerSle,
628 STAmount const& amount,
629 LoanPaymentType const paymentType,
631
632} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:44
std::chrono::time_point< NetClock > time_point
Definition chrono.h:48
Number is a floating point type that can represent a wide range of values.
Definition Number.h:351
static constexpr int kMinExponent
Definition Number.h:361
A view into a ledger.
Definition ReadView.h:41
Rules controlling protocol behavior.
Definition Rules.h:40
std::shared_ptr< STLedgerEntry > const & ref
std::shared_ptr< STLedgerEntry const > const & const_ref
constexpr Zero kZero
Definition Zero.h:30
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)
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)
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< 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)
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.
Definition algorithm.h:5
static constexpr Number kNumZero
Definition Number.h:663
constexpr BaseUInt< Bits, Tag > operator+(BaseUInt< Bits, Tag > const &a, BaseUInt< Bits, Tag > const &b)
Definition base_uint.h:643
Number loanPeriodicRate(TenthBips32 interestRate, std::uint32_t paymentInterval)
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)
Definition Number.h:789
bool loanOriginationExceedsVaultMaximum(SLE::const_ref vaultSle, Number const &vaultTotal, Number const &interestDue)
constexpr T tenthBipsOfValue(T value, TenthBips< TBips > bips)
Definition Protocol.h:138
int getAssetsTotalScale(SLE::const_ref vaultSle)
void adjustImpreciseNumber(NumberProxy value, Number const &adjustment, Asset const &asset, int vaultScale)
int scale(Number const &number, Asset const &asset)
Get the scale of a Number for a given asset.
Definition STAmount.h:794
Number minimumBrokerCover(Number const &debtTotal, TenthBips32 coverRateMinimum, SLE::const_ref vaultSle)
AccountingDeltas loanPaymentDeltas(SLE::const_ref vaultSle, LoanPaymentParts const &parts)
TenthBips< std::uint32_t > TenthBips32
Definition Units.h:454
TenthBips< std::uint16_t > TenthBips16
Definition Units.h:453
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.
Definition STAmount.h:735
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
Definition TER.h:647
Number loanVaultExposure(SLE::const_ref vaultSle, SLE::const_ref loanSle)
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)
This structure captures the parts of a loan state.
Number principalOutstanding
Number interestOutstanding() const
ExtendedPaymentComponents(PaymentComponents const &p, Number fee, Number interest=kNumZero)
Number total() const
Calculates the total change across all components.
Number trackedInterestPart() const
Calculates the tracked interest portion of this payment.