rippled
Loading...
Searching...
No Matches
LendingHelpers.h
1#pragma once
2
3#include <xrpl/ledger/View.h>
4#include <xrpl/protocol/st.h>
5
6namespace xrpl {
7
8struct PreflightContext;
9
10// Lending protocol has dependencies, so capture them here.
11bool
12checkLendingProtocolDependencies(PreflightContext const& ctx);
13
14static constexpr std::uint32_t secondsInYear = 365 * 24 * 60 * 60;
15
17loanPeriodicRate(TenthBips32 interestRate, std::uint32_t paymentInterval);
18
20inline Number
21roundPeriodicPayment(Asset const& asset, Number const& periodicPayment, std::int32_t scale)
22{
23 return roundToAsset(asset, periodicPayment, scale, Number::upward);
24}
25
26/* Represents the breakdown of amounts to be paid and changes applied to the
27 * Loan object while processing a loan payment.
28 *
29 * This structure is returned after processing a loan payment transaction and
30 * captures the amounts that need to be paid. The actual ledger entry changes
31 * are made in LoanPay based on this structure values.
32 *
33 * The sum of principalPaid, interestPaid, and feePaid represents the total
34 * amount to be deducted from the borrower's account. The valueChange field
35 * tracks whether the loan's total value increased or decreased beyond normal
36 * amortization.
37 *
38 * This structure is explained in the XLS-66 spec, section 3.2.4.2 (Payment
39 * Processing).
40 */
42{
43 // The amount of principal paid that reduces the loan balance.
44 // This amount is subtracted from sfPrincipalOutstanding in the Loan object
45 // and paid to the Vault
47
48 // The total amount of interest paid to the Vault.
49 // This includes:
50 // - Tracked interest from the amortization schedule
51 // - Untracked interest (e.g., late payment penalty interest)
52 // This value is always non-negative.
54
55 // The change in the loan's total value outstanding.
56 // - If valueChange < 0: Loan value decreased
57 // - If valueChange > 0: Loan value increased
58 // - If valueChange = 0: No value adjustment
59 //
60 // For regular on-time payments, this is always 0. Non-zero values occur
61 // when:
62 // - Overpayments reduce the loan balance beyond the scheduled amount
63 // - Late payments add penalty interest to the loan value
64 // - Early full payment may increase or decrease the loan value based on
65 // terms
67
68 /* The total amount of fees paid to the Broker.
69 * This includes:
70 * - Tracked management fees from the amortization schedule
71 * - Untracked fees (e.g., late payment fees, service fees, origination
72 * fees) This value is always non-negative.
73 */
75
77 operator+=(LoanPaymentParts const& other);
78
79 bool
80 operator==(LoanPaymentParts const& other) const;
81};
82
95{
96 // Total value still due to be paid by the borrower.
98 // Principal still due to be paid by the borrower.
100 // Interest still due to be paid to the Vault.
101 // This is a portion of interestOutstanding
103 // Management fee still due to be paid to the broker.
104 // This is a portion of interestOutstanding
106
107 // Interest still due to be paid by the borrower.
108 Number
110 {
111 XRPL_ASSERT_PARTS(
113 "xrpl::LoanState::interestOutstanding",
114 "other values add up correctly");
116 }
117};
118
119/* Describes the initial computed properties of a loan.
120 *
121 * This structure contains the fundamental calculated values that define a
122 * loan's payment structure and amortization schedule. These properties are
123 * computed:
124 * - At loan creation (LoanSet transaction)
125 * - When loan terms change (e.g., after an overpayment that reduces the loan
126 * balance)
127 */
129{
130 // The unrounded amount to be paid at each regular payment period.
131 // Calculated using the standard amortization formula based on principal,
132 // interest rate, and number of payments.
133 // The actual amount paid in the LoanPay transaction must be rounded up to
134 // the precision of the asset and loan.
136
137 // The loan's current state, with all values rounded to the loan's scale.
139
140 // The scale (decimal places) used for rounding all loan amounts.
141 // This is the maximum of:
142 // - The asset's native scale
143 // - A minimum scale required to represent the periodic payment accurately
144 // All loan state values (principal, interest, fees) are rounded to this
145 // scale.
147
148 // The principal portion of the first payment.
150};
151
152// Some values get re-rounded to the vault scale any time they are adjusted. In
153// addition, they are prevented from ever going below zero. This helps avoid
154// accumulated rounding errors and leftover dust amounts.
155template <class NumberProxy>
156void
158 NumberProxy value,
159 Number const& adjustment,
160 Asset const& asset,
161 int vaultScale)
162{
163 value = roundToAsset(asset, value + adjustment, vaultScale);
164
165 if (*value < beast::zero)
166 value = 0;
167}
168
169inline int
171{
172 if (!vaultSle)
173 return Number::minExponent - 1; // LCOV_EXCL_LINE
174 return STAmount{vaultSle->at(sfAsset), vaultSle->at(sfAssetsTotal)}.exponent();
175}
176
177TER
179 Asset const& vaultAsset,
180 Number const& principalRequested,
181 bool expectInterest,
182 std::uint32_t paymentTotal,
183 LoanProperties const& properties,
185
186LoanState
188 Number const& periodicPayment,
189 Number const& periodicRate,
190 std::uint32_t const paymentRemaining,
191 TenthBips32 const managementFeeRate);
192
193// Constructs a valid LoanState object from arbitrary inputs
194LoanState
196 Number const& totalValueOutstanding,
197 Number const& principalOutstanding,
198 Number const& managementFeeOutstanding);
199
200// Constructs a valid LoanState object from a Loan object, which always has
201// rounded values
202LoanState
204
205Number
207 Asset const& asset,
208 Number const& interest,
209 TenthBips32 managementFeeRate,
210 std::int32_t scale);
211
212Number
214 Number const& theoreticalPrincipalOutstanding,
215 Number const& periodicRate,
216 NetClock::time_point parentCloseTime,
217 std::uint32_t paymentInterval,
218 std::uint32_t prevPaymentDate,
219 std::uint32_t startDate,
220 TenthBips32 closeInterestRate);
221
222namespace detail {
223// These classes and functions should only be accessed by LendingHelper
224// functions and unit tests
225
226enum class PaymentSpecialCase { none, final, extra };
227
228/* Represents a single loan payment component parts.
229
230* This structure captures the "delta" (change) values that will be applied to
231* the tracked fields in the Loan ledger object when a payment is processed.
232*
233* These are called "deltas" because they represent the amount by which each
234* corresponding field in the Loan object will be reduced.
235* They are "tracked" as they change tracked loan values.
236*/
238{
239 // The change in total value outstanding for this payment.
240 // This amount will be subtracted from sfTotalValueOutstanding in the Loan
241 // object. Equal to the sum of trackedPrincipalDelta,
242 // trackedInterestPart(), and trackedManagementFeeDelta.
244
245 // The change in principal outstanding for this payment.
246 // This amount will be subtracted from sfPrincipalOutstanding in the Loan
247 // object, representing the portion of the payment that reduces the
248 // original loan amount.
250
251 // The change in management fee outstanding for this payment.
252 // This amount will be subtracted from sfManagementFeeOutstanding in the
253 // Loan object. This represents only the tracked management fees from the
254 // amortization schedule and does not include additional untracked fees
255 // (such as late payment fees) that go directly to the broker.
257
258 // Indicates if this payment has special handling requirements.
259 // - none: Regular scheduled payment
260 // - final: The last payment that closes out the loan
261 // - extra: An additional payment beyond the regular schedule (overpayment)
263
264 // Calculates the tracked interest portion of this payment.
265 // This is derived from the other components as:
266 // trackedValueDelta - trackedPrincipalDelta - trackedManagementFeeDelta
267 //
268 // @return The amount of tracked interest included in this payment that
269 // will be paid to the vault.
270 Number
271 trackedInterestPart() const;
272};
273
274/* Extends PaymentComponents with untracked payment amounts.
275 *
276 * This structure adds untracked fees and interest to the base
277 * PaymentComponents, representing amounts that don't affect the Loan object's
278 * tracked state but are still part of the total payment due from the borrower.
279 *
280 * Untracked amounts include:
281 * - Late payment fees that go directly to the Broker
282 * - Late payment penalty interest that goes directly to the Vault
283 * - Service fees
284 *
285 * The key distinction is that tracked amounts reduce the Loan object's state
286 * (sfTotalValueOutstanding, sfPrincipalOutstanding,
287 * sfManagementFeeOutstanding), while untracked amounts are paid directly to the
288 * recipient without affecting the loan's amortization schedule.
289 */
291{
292 // Additional management fees that go directly to the Broker.
293 // This includes fees not part of the standard amortization schedule
294 // (e.g., late fees, service fees, origination fees).
295 // This value may be negative, though the final value returned in
296 // LoanPaymentParts.feePaid will never be negative.
298
299 // Additional interest that goes directly to the Vault.
300 // This includes interest not part of the standard amortization schedule
301 // (e.g., late payment penalty interest).
302 // This value may be negative, though the final value returned in
303 // LoanPaymentParts.interestPaid will never be negative.
305
306 // The complete amount due from the borrower for this payment.
307 // Calculated as: trackedValueDelta + untrackedInterest +
308 // untrackedManagementFee
309 //
310 // This value is used to validate that the payment amount provided by the
311 // borrower is sufficient to cover all components of the payment.
313
321};
322
323/* Represents the differences between two loan states.
324 *
325 * This structure is used to capture the change in each component of a loan's
326 * state, typically when computing the difference between two LoanState objects
327 * (e.g., before and after a payment). It is a convenient way to capture changes
328 * in each component. How that difference is used depends on the context.
329 */
331{
332 // The difference in principal outstanding between two loan states.
334
335 // The difference in interest due between two loan states.
337
338 // The difference in management fee outstanding between two loan states.
340
341 /* Calculates the total change across all components.
342 * @return The sum of principal, interest, and management fee deltas.
343 */
344 Number
345 total() const
346 {
348 }
349
350 // Ensures all delta values are non-negative.
351 void
352 nonNegative();
353};
354
357 Asset const& asset,
358 std::int32_t loanScale,
359 ExtendedPaymentComponents const& overpaymentComponents,
360 LoanState const& roundedLoanState,
361 Number const& periodicPayment,
362 Number const& periodicRate,
363 std::uint32_t paymentRemaining,
364 TenthBips16 const managementFeeRate,
366
367Number
368computeRaisedRate(Number const& periodicRate, std::uint32_t paymentsRemaining);
369
370Number
371computePaymentFactor(Number const& periodicRate, std::uint32_t paymentsRemaining);
372
375 Asset const& asset,
376 Number const& interest,
377 TenthBips16 managementFeeRate,
378 std::int32_t loanScale);
379
380Number
382 Number const& principalOutstanding,
383 Number const& periodicRate,
384 std::uint32_t paymentsRemaining);
385
386Number
388 Number const& periodicPayment,
389 Number const& periodicRate,
390 std::uint32_t paymentsRemaining);
391
392Number
394 Number const& principalOutstanding,
395 TenthBips32 lateInterestRate,
396 NetClock::time_point parentCloseTime,
397 std::uint32_t nextPaymentDueDate);
398
399Number
401 Number const& principalOutstanding,
402 Number const& periodicRate,
403 NetClock::time_point parentCloseTime,
404 std::uint32_t startDate,
405 std::uint32_t prevPaymentDate,
406 std::uint32_t paymentInterval);
407
408ExtendedPaymentComponents
410 Asset const& asset,
411 int32_t const loanScale,
412 Number const& overpayment,
413 TenthBips32 const overpaymentInterestRate,
414 TenthBips32 const overpaymentFeeRate,
415 TenthBips16 const managementFeeRate);
416
417PaymentComponents
419 Asset const& asset,
420 std::int32_t scale,
421 Number const& totalValueOutstanding,
422 Number const& principalOutstanding,
423 Number const& managementFeeOutstanding,
424 Number const& periodicPayment,
425 Number const& periodicRate,
426 std::uint32_t paymentRemaining,
427 TenthBips16 managementFeeRate);
428
429} // namespace detail
430
431detail::LoanStateDeltas
432operator-(LoanState const& lhs, LoanState const& rhs);
433
434LoanState
435operator-(LoanState const& lhs, detail::LoanStateDeltas const& rhs);
436
437LoanState
438operator+(LoanState const& lhs, detail::LoanStateDeltas const& rhs);
439
440LoanProperties
442 Asset const& asset,
443 Number const& principalOutstanding,
444 TenthBips32 interestRate,
445 std::uint32_t paymentInterval,
446 std::uint32_t paymentsRemaining,
447 TenthBips32 managementFeeRate,
448 std::int32_t minimumScale);
449
450LoanProperties
452 Asset const& asset,
453 Number const& principalOutstanding,
454 Number const& periodicRate,
455 std::uint32_t paymentsRemaining,
456 TenthBips32 managementFeeRate,
457 std::int32_t minimumScale);
458
459bool
460isRounded(Asset const& asset, Number const& value, std::int32_t scale);
461
462// Indicates what type of payment is being made.
463// regular, late, and full are mutually exclusive.
464// overpayment is an "add on" to a regular payment, and follows that path with
465// potential extra work at the end.
467
468Expected<LoanPaymentParts, TER>
470 Asset const& asset,
471 ApplyView& view,
472 SLE::ref loan,
473 SLE::const_ref brokerSle,
474 STAmount const& amount,
475 LoanPaymentType const paymentType,
477
478} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:40
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:207
static constexpr int minExponent
Definition Number.h:217
std::shared_ptr< STLedgerEntry > const & ref
std::shared_ptr< STLedgerEntry const > const & const_ref
PaymentComponents computePaymentComponents(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)
Number computeRaisedRate(Number const &periodicRate, std::uint32_t paymentsRemaining)
Number loanPeriodicPayment(Number const &principalOutstanding, Number const &periodicRate, std::uint32_t paymentsRemaining)
ExtendedPaymentComponents computeOverpaymentComponents(Asset const &asset, int32_t const loanScale, Number const &overpayment, TenthBips32 const overpaymentInterestRate, TenthBips32 const overpaymentFeeRate, TenthBips16 const managementFeeRate)
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)
Number computePaymentFactor(Number const &periodicRate, std::uint32_t paymentsRemaining)
Number loanPrincipalFromPeriodicPayment(Number const &periodicPayment, Number const &periodicRate, std::uint32_t paymentsRemaining)
Expected< std::pair< LoanPaymentParts, LoanProperties >, TER > tryOverpayment(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)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
Number loanPeriodicRate(TenthBips32 interestRate, std::uint32_t paymentInterval)
constexpr base_uint< Bits, Tag > operator+(base_uint< Bits, Tag > const &a, base_uint< Bits, Tag > const &b)
Definition base_uint.h:594
Number operator-(Number const &x, Number const &y)
Definition Number.h:648
static constexpr Number numZero
Definition Number.h:524
int getAssetsTotalScale(SLE::const_ref vaultSle)
void adjustImpreciseNumber(NumberProxy value, Number const &adjustment, Asset const &asset, int vaultScale)
bool checkLendingProtocolDependencies(PreflightContext const &ctx)
static constexpr std::uint32_t secondsInYear
LoanState computeTheoreticalLoanState(Number const &periodicPayment, Number const &periodicRate, std::uint32_t const paymentRemaining, TenthBips32 const managementFeeRate)
TenthBips< std::uint32_t > TenthBips32
Definition Units.h:437
TER checkLoanGuards(Asset const &vaultAsset, Number const &principalRequested, bool expectInterest, std::uint32_t paymentTotal, LoanProperties const &properties, beast::Journal j)
TERSubset< CanCvtToTER > TER
Definition TER.h:622
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)
void roundToAsset(A const &asset, Number &value)
Round an arbitrary precision Number IN PLACE to the precision of a given Asset.
Definition STAmount.h:695
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)
LoanProperties computeLoanProperties(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 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=numZero)