xrpld
Loading...
Searching...
No Matches
LendingHelpers.h
1#pragma once
2
3#include <xrpl/ledger/View.h>
4#include <xrpl/protocol/Rules.h>
5#include <xrpl/protocol/st.h>
6
7#include <expected>
8#include <string_view>
9
10namespace xrpl {
11
31[[nodiscard]] TER
33 ReadView const& view,
34 SLE::const_ref sleBroker,
35 Asset const& vaultAsset,
36 STAmount const& amount,
37 beast::Journal j,
38 std::string_view logPrefix);
39
40// Lending protocol has dependencies, so capture them here.
41bool
42checkLendingProtocolDependencies(Rules const& rules, STTx const& tx);
43
44static constexpr std::uint32_t kSecondsInYear = 365 * 24 * 60 * 60;
45
47loanPeriodicRate(TenthBips32 interestRate, std::uint32_t paymentInterval);
48
50inline Number
51roundPeriodicPayment(Asset const& asset, Number const& periodicPayment, std::int32_t scale)
52{
53 return roundToAsset(asset, periodicPayment, scale, Number::RoundingMode::Upward);
54}
55
56/* Represents the breakdown of amounts to be paid and changes applied to the
57 * Loan object while processing a loan payment.
58 *
59 * This structure is returned after processing a loan payment transaction and
60 * captures the amounts that need to be paid. The actual ledger entry changes
61 * are made in LoanPay based on this structure values.
62 *
63 * The sum of principalPaid, interestPaid, and feePaid represents the total
64 * amount to be deducted from the borrower's account. The valueChange field
65 * tracks whether the loan's total value increased or decreased beyond normal
66 * amortization.
67 *
68 * This structure is explained in the XLS-66 spec, section 3.2.4.2 (Payment
69 * Processing).
70 */
72{
73 // The amount of principal paid that reduces the loan balance.
74 // This amount is subtracted from sfPrincipalOutstanding in the Loan object
75 // and paid to the Vault
77
78 // The total amount of interest paid to the Vault.
79 // This includes:
80 // - Tracked interest from the amortization schedule
81 // - Untracked interest (e.g., late payment penalty interest)
82 // This value is always non-negative.
84
85 // The change in the loan's total value outstanding.
86 // - If valueChange < 0: Loan value decreased
87 // - If valueChange > 0: Loan value increased
88 // - If valueChange = 0: No value adjustment
89 //
90 // For regular on-time payments, this is always 0. Non-zero values occur
91 // when:
92 // - Overpayments reduce the loan balance beyond the scheduled amount
93 // - Late payments add penalty interest to the loan value
94 // - Early full payment may increase or decrease the loan value based on
95 // terms
97
98 /* The total amount of fees paid to the Broker.
99 * This includes:
100 * - Tracked management fees from the amortization schedule
101 * - Untracked fees (e.g., late payment fees, service fees, origination
102 * fees) This value is always non-negative.
103 */
105
107 operator+=(LoanPaymentParts const& other);
108
109 bool
110 operator==(LoanPaymentParts const& other) const;
111};
112
125{
126 // Total value still due to be paid by the borrower.
128 // Principal still due to be paid by the borrower.
130 // Interest still due to be paid to the Vault.
131 // This is a portion of interestOutstanding
133 // Management fee still due to be paid to the broker.
134 // This is a portion of interestOutstanding
136
137 // Interest still due to be paid by the borrower.
138 [[nodiscard]] Number
140 {
141 XRPL_ASSERT_PARTS(
143 "xrpl::LoanState::interestOutstanding",
144 "other values add up correctly");
146 }
147};
148
149/* Describes the initial computed properties of a loan.
150 *
151 * This structure contains the fundamental calculated values that define a
152 * loan's payment structure and amortization schedule. These properties are
153 * computed:
154 * - At loan creation (LoanSet transaction)
155 * - When loan terms change (e.g., after an overpayment that reduces the loan
156 * balance)
157 */
159{
160 // The unrounded amount to be paid at each regular payment period.
161 // Calculated using the standard amortization formula based on principal,
162 // interest rate, and number of payments.
163 // The actual amount paid in the LoanPay transaction must be rounded up to
164 // the precision of the asset and loan.
166
167 // The loan's current state, with all values rounded to the loan's scale.
169
170 // The scale (decimal places) used for rounding all loan amounts.
171 // This is the maximum of:
172 // - The asset's native scale
173 // - A minimum scale required to represent the periodic payment accurately
174 // All loan state values (principal, interest, fees) are rounded to this
175 // scale.
177
178 // The principal portion of the first payment.
180};
181
182// Some values get re-rounded to the vault scale any time they are adjusted. In
183// addition, they are prevented from ever going below zero. This helps avoid
184// accumulated rounding errors and leftover dust amounts.
185template <class NumberProxy>
186void
188 NumberProxy value,
189 Number const& adjustment,
190 Asset const& asset,
191 int vaultScale)
192{
193 value = roundToAsset(asset, value + adjustment, vaultScale);
194
195 if (*value < beast::kZero)
196 value = 0;
197}
198
199inline int
201{
202 if (!vaultSle)
203 return Number::kMinExponent - 1; // LCOV_EXCL_LINE
204 return scale(vaultSle->at(sfAssetsTotal), vaultSle->at(sfAsset));
205}
206
207// Compute the minimum required broker cover, rounded consistently.
208// DebtTotal is a broker-level aggregate maintained at vault scale, so the
209// rounding must also use vault scale — never an individual loan's scale.
210inline Number
211minimumBrokerCover(Number const& debtTotal, TenthBips32 coverRateMinimum, SLE::const_ref vaultSle)
212{
213 XRPL_ASSERT(
214 vaultSle && vaultSle->getType() == ltVAULT, "xrpl::minimumBrokerCover : valid Vault sle");
216 return roundToAsset(
217 vaultSle->at(sfAsset),
218 tenthBipsOfValue(debtTotal, coverRateMinimum),
219 getAssetsTotalScale(vaultSle));
220}
221
222TER
224 Asset const& vaultAsset,
225 Number const& principalRequested,
226 bool expectInterest,
227 std::uint32_t paymentTotal,
228 LoanProperties const& properties,
230
231LoanState
233 Rules const& rules,
234 Number const& periodicPayment,
235 Number const& periodicRate,
236 std::uint32_t const paymentRemaining,
237 TenthBips32 const managementFeeRate);
238
239// Constructs a valid LoanState object from arbitrary inputs
240LoanState
242 Number const& totalValueOutstanding,
243 Number const& principalOutstanding,
244 Number const& managementFeeOutstanding);
245
246// Constructs a valid LoanState object from a Loan object, which always has
247// rounded values
248LoanState
250
251Number
253 Asset const& asset,
254 Number const& interest,
255 TenthBips32 managementFeeRate,
257
258Number
260 Number const& theoreticalPrincipalOutstanding,
261 Number const& periodicRate,
262 NetClock::time_point parentCloseTime,
263 std::uint32_t paymentInterval,
264 std::uint32_t prevPaymentDate,
265 std::uint32_t startDate,
266 TenthBips32 closeInterestRate);
267
268namespace detail {
269// These classes and functions should only be accessed by LendingHelper
270// functions and unit tests
271
273
274/* Represents a single loan payment component parts.
275
276* This structure captures the "delta" (change) values that will be applied to
277* the tracked fields in the Loan ledger object when a payment is processed.
278*
279* These are called "deltas" because they represent the amount by which each
280* corresponding field in the Loan object will be reduced.
281* They are "tracked" as they change tracked loan values.
282*/
284{
285 // The change in total value outstanding for this payment.
286 // This amount will be subtracted from sfTotalValueOutstanding in the Loan
287 // object. Equal to the sum of trackedPrincipalDelta,
288 // trackedInterestPart(), and trackedManagementFeeDelta.
290
291 // The change in principal outstanding for this payment.
292 // This amount will be subtracted from sfPrincipalOutstanding in the Loan
293 // object, representing the portion of the payment that reduces the
294 // original loan amount.
296
297 // The change in management fee outstanding for this payment.
298 // This amount will be subtracted from sfManagementFeeOutstanding in the
299 // Loan object. This represents only the tracked management fees from the
300 // amortization schedule and does not include additional untracked fees
301 // (such as late payment fees) that go directly to the broker.
303
304 // Indicates if this payment has special handling requirements.
305 // - none: Regular scheduled payment
306 // - final: The last payment that closes out the loan
307 // - extra: An additional payment beyond the regular schedule (overpayment)
309
310 // Calculates the tracked interest portion of this payment.
311 // This is derived from the other components as:
312 // trackedValueDelta - trackedPrincipalDelta - trackedManagementFeeDelta
313 //
314 // @return The amount of tracked interest included in this payment that
315 // will be paid to the vault.
316 [[nodiscard]] Number
317 trackedInterestPart() const;
318};
319
320/* Extends PaymentComponents with untracked payment amounts.
321 *
322 * This structure adds untracked fees and interest to the base
323 * PaymentComponents, representing amounts that don't affect the Loan object's
324 * tracked state but are still part of the total payment due from the borrower.
325 *
326 * Untracked amounts include:
327 * - Late payment fees that go directly to the Broker
328 * - Late payment penalty interest that goes directly to the Vault
329 * - Service fees
330 *
331 * The key distinction is that tracked amounts reduce the Loan object's state
332 * (sfTotalValueOutstanding, sfPrincipalOutstanding,
333 * sfManagementFeeOutstanding), while untracked amounts are paid directly to the
334 * recipient without affecting the loan's amortization schedule.
335 */
337{
338 // Additional management fees that go directly to the Broker.
339 // This includes fees not part of the standard amortization schedule
340 // (e.g., late fees, service fees, origination fees).
341 // This value may be negative, though the final value returned in
342 // LoanPaymentParts.feePaid will never be negative.
344
345 // Additional interest that goes directly to the Vault.
346 // This includes interest not part of the standard amortization schedule
347 // (e.g., late payment penalty interest).
348 // This value may be negative, though the final value returned in
349 // LoanPaymentParts.interestPaid will never be negative.
351
352 // The complete amount due from the borrower for this payment.
353 // Calculated as: trackedValueDelta + untrackedInterest +
354 // untrackedManagementFee
355 //
356 // This value is used to validate that the payment amount provided by the
357 // borrower is sufficient to cover all components of the payment.
359
367};
368
369/* Represents the differences between two loan states.
370 *
371 * This structure is used to capture the change in each component of a loan's
372 * state, typically when computing the difference between two LoanState objects
373 * (e.g., before and after a payment). It is a convenient way to capture changes
374 * in each component. How that difference is used depends on the context.
375 */
377{
378 // The difference in principal outstanding between two loan states.
380
381 // The difference in interest due between two loan states.
383
384 // The difference in management fee outstanding between two loan states.
386
387 /* Calculates the total change across all components.
388 * @return The sum of principal, interest, and management fee deltas.
389 */
390 [[nodiscard]] Number
391 total() const
392 {
394 }
395
396 // Ensures all delta values are non-negative.
397 void
398 nonNegative();
399};
400
401std::expected<std::pair<LoanPaymentParts, LoanProperties>, TER>
403 Rules const& rules,
404 Asset const& asset,
405 std::int32_t loanScale,
406 ExtendedPaymentComponents const& overpaymentComponents,
407 LoanState const& roundedLoanState,
408 Number const& periodicPayment,
409 Number const& periodicRate,
410 std::uint32_t paymentRemaining,
411 TenthBips16 const managementFeeRate,
413
414[[nodiscard]] Number
415computePowerMinusOne(Number const& periodicRate, std::uint32_t paymentsRemaining);
416
417[[nodiscard]] Number
418computePowerMinusOneHybrid(Number const& periodicRate, std::uint32_t paymentsRemaining);
419
420[[nodiscard]] Number
422 Rules const& rules,
423 Number const& periodicRate,
424 std::uint32_t paymentsRemaining);
425
428 Asset const& asset,
429 Number const& interest,
430 TenthBips16 managementFeeRate,
431 std::int32_t loanScale);
432
433Number
435 Rules const& rules,
436 Number const& principalOutstanding,
437 Number const& periodicRate,
438 std::uint32_t paymentsRemaining);
439
440Number
442 Rules const& rules,
443 Number const& periodicPayment,
444 Number const& periodicRate,
445 std::uint32_t paymentsRemaining);
446
447Number
449 Number const& principalOutstanding,
450 TenthBips32 lateInterestRate,
451 NetClock::time_point parentCloseTime,
452 std::uint32_t nextPaymentDueDate);
453
454Number
456 Number const& principalOutstanding,
457 Number const& periodicRate,
458 NetClock::time_point parentCloseTime,
459 std::uint32_t startDate,
460 std::uint32_t prevPaymentDate,
461 std::uint32_t paymentInterval);
462
463ExtendedPaymentComponents
465 Rules const& rules,
466 Asset const& asset,
467 int32_t const loanScale,
468 Number const& overpayment,
469 TenthBips32 const overpaymentInterestRate,
470 TenthBips32 const overpaymentFeeRate,
471 TenthBips16 const managementFeeRate);
472
473PaymentComponents
475 Rules const& rules,
476 Asset const& asset,
478 Number const& totalValueOutstanding,
479 Number const& principalOutstanding,
480 Number const& managementFeeOutstanding,
481 Number const& periodicPayment,
482 Number const& periodicRate,
483 std::uint32_t paymentRemaining,
484 TenthBips16 managementFeeRate);
485
486} // namespace detail
487
488detail::LoanStateDeltas
489operator-(LoanState const& lhs, LoanState const& rhs);
490
491LoanState
492operator-(LoanState const& lhs, detail::LoanStateDeltas const& rhs);
493
494LoanState
495operator+(LoanState const& lhs, detail::LoanStateDeltas const& rhs);
496
497LoanProperties
499 Rules const& rules,
500 Asset const& asset,
501 Number const& principalOutstanding,
502 TenthBips32 interestRate,
503 std::uint32_t paymentInterval,
504 std::uint32_t paymentsRemaining,
505 TenthBips32 managementFeeRate,
506 std::int32_t minimumScale);
507
508LoanProperties
510 Rules const& rules,
511 Asset const& asset,
512 Number const& principalOutstanding,
513 Number const& periodicRate,
514 std::uint32_t paymentsRemaining,
515 TenthBips32 managementFeeRate,
516 std::int32_t minimumScale);
517
518bool
519isRounded(Asset const& asset, Number const& value, std::int32_t scale);
520
521// Indicates what type of payment is being made.
522// regular, late, and full are mutually exclusive.
523// overpayment is an "add on" to a regular payment, and follows that path with
524// potential extra work at the end.
526
527std::expected<LoanPaymentParts, TER>
529 Asset const& asset,
530 ApplyView& view,
531 SLE::ref loan,
532 SLE::const_ref brokerSle,
533 STAmount const& amount,
534 LoanPaymentType const paymentType,
536
537} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:38
std::chrono::time_point< NetClock > time_point
Definition chrono.h:46
Number is a floating point type that can represent a wide range of values.
Definition Number.h:306
static constexpr int kMinExponent
Definition Number.h:316
A view into a ledger.
Definition ReadView.h:31
Rules controlling protocol behavior.
Definition Rules.h:33
std::shared_ptr< STLedgerEntry > const & ref
std::shared_ptr< STLedgerEntry const > const & const_ref
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:612
constexpr BaseUInt< Bits, Tag > operator+(BaseUInt< Bits, Tag > const &a, BaseUInt< Bits, Tag > const &b)
Definition base_uint.h:625
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:736
constexpr T tenthBipsOfValue(T value, TenthBips< TBips > bips)
Definition Protocol.h:111
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:779
Number minimumBrokerCover(Number const &debtTotal, TenthBips32 coverRateMinimum, SLE::const_ref vaultSle)
TenthBips< std::uint32_t > TenthBips32
Definition Units.h:439
TenthBips< std::uint16_t > TenthBips16
Definition Units.h:438
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:722
Number roundPeriodicPayment(Asset const &asset, Number const &periodicPayment, std::int32_t scale)
Ensure the periodic payment is always rounded consistently.
LoanState constructRoundedLoanState(SLE::const_ref loan)
Number computeManagementFee(Asset const &asset, Number const &interest, TenthBips32 managementFeeRate, std::int32_t scale)
TERSubset< CanCvtToTER > TER
Definition TER.h:634
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)