Skip to content

Commit 5aab5de

Browse files
authored
Merge branch 'ximinez/lending-XLS-66-ongoing' into ximinez/lending-shortages
2 parents 89d1744 + 5ceb915 commit 5aab5de

File tree

10 files changed

+816
-154
lines changed

10 files changed

+816
-154
lines changed

src/test/app/LendingHelpers_test.cpp

Lines changed: 642 additions & 0 deletions
Large diffs are not rendered by default.

src/test/app/Loan_test.cpp

Lines changed: 34 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ class Loan_test : public beast::unit_test::suite
141141
using namespace jtx;
142142

143143
auto const vaultSle = env.le(keylet::vault(vaultID));
144-
return getVaultScale(vaultSle);
144+
return getAssetsTotalScale(vaultSle);
145145
}
146146
};
147147

@@ -551,12 +551,15 @@ class Loan_test : public beast::unit_test::suite
551551
broker.vaultScale(env),
552552
state.principalOutstanding.exponent())));
553553
BEAST_EXPECT(state.paymentInterval == 600);
554-
BEAST_EXPECT(
555-
state.totalValue ==
556-
roundToAsset(
557-
broker.asset,
558-
state.periodicPayment * state.paymentRemaining,
559-
state.loanScale));
554+
{
555+
NumberRoundModeGuard mg(Number::upward);
556+
BEAST_EXPECT(
557+
state.totalValue ==
558+
roundToAsset(
559+
broker.asset,
560+
state.periodicPayment * state.paymentRemaining,
561+
state.loanScale));
562+
}
560563
BEAST_EXPECT(
561564
state.managementFeeOutstanding ==
562565
computeManagementFee(
@@ -697,7 +700,8 @@ class Loan_test : public beast::unit_test::suite
697700
interval,
698701
total,
699702
feeRate,
700-
asset(brokerParams.vaultDeposit).number().exponent());
703+
asset(brokerParams.vaultDeposit).number().exponent(),
704+
env.journal);
701705
log << "Loan properties:\n"
702706
<< "\tPrincipal: " << principal << std::endl
703707
<< "\tInterest rate: " << interest << std::endl
@@ -1478,7 +1482,8 @@ class Loan_test : public beast::unit_test::suite
14781482
state.paymentInterval,
14791483
state.paymentRemaining,
14801484
broker.params.managementFeeRate,
1481-
state.loanScale);
1485+
state.loanScale,
1486+
env.journal);
14821487

14831488
verifyLoanStatus(
14841489
0,
@@ -2449,13 +2454,18 @@ class Loan_test : public beast::unit_test::suite
24492454
// Make all the payments in one transaction
24502455
// service fee is 2
24512456
auto const startingPayments = state.paymentRemaining;
2452-
auto const rawPayoff = startingPayments *
2453-
(state.periodicPayment + broker.asset(2).value());
2454-
STAmount const payoffAmount{broker.asset, rawPayoff};
2455-
BEAST_EXPECT(
2456-
payoffAmount ==
2457-
broker.asset(Number(1024014840139457, -12)));
2458-
BEAST_EXPECT(payoffAmount > state.principalOutstanding);
2457+
STAmount const payoffAmount = [&]() {
2458+
NumberRoundModeGuard mg(Number::upward);
2459+
auto const rawPayoff = startingPayments *
2460+
(state.periodicPayment + broker.asset(2).value());
2461+
STAmount const payoffAmount{broker.asset, rawPayoff};
2462+
BEAST_EXPECTS(
2463+
payoffAmount ==
2464+
broker.asset(Number(1024014840139457, -12)),
2465+
to_string(payoffAmount));
2466+
BEAST_EXPECT(payoffAmount > state.principalOutstanding);
2467+
return payoffAmount;
2468+
}();
24592469

24602470
singlePayment(
24612471
loanKeylet,
@@ -4014,7 +4024,7 @@ class Loan_test : public beast::unit_test::suite
40144024
createJson = env.json(createJson, sig(sfCounterpartySignature, lender));
40154025
// Fails in preclaim because principal requested can't be
40164026
// represented as XRP
4017-
env(createJson, ter(tecPRECISION_LOSS));
4027+
env(createJson, ter(tecPRECISION_LOSS), THISLINE);
40184028
env.close();
40194029

40204030
BEAST_EXPECT(!env.le(keylet));
@@ -4026,7 +4036,7 @@ class Loan_test : public beast::unit_test::suite
40264036
createJson = env.json(createJson, sig(sfCounterpartySignature, lender));
40274037
// Fails in doApply because the payment is too small to be
40284038
// represented as XRP.
4029-
env(createJson, ter(tecPRECISION_LOSS));
4039+
env(createJson, ter(tecPRECISION_LOSS), THISLINE);
40304040
env.close();
40314041
}
40324042

@@ -5001,7 +5011,7 @@ class Loan_test : public beast::unit_test::suite
50015011
auto const keylet = keylet::loan(broker.brokerID, loanSequence);
50025012

50035013
createJson = env.json(createJson, sig(sfCounterpartySignature, lender));
5004-
env(createJson, ter(tecPRECISION_LOSS));
5014+
env(createJson, ter(tecPRECISION_LOSS), THISLINE);
50055015
env.close(startDate);
50065016

50075017
auto loanPayTx = env.json(
@@ -6149,15 +6159,16 @@ class Loan_test : public beast::unit_test::suite
61496159
// Accrued + prepayment-penalty interest based on current periodic
61506160
// schedule
61516161
auto const fullPaymentInterest = computeFullPaymentInterest(
6152-
after.periodicPayment,
6162+
detail::loanPrincipalFromPeriodicPayment(
6163+
after.periodicPayment, periodicRate2, after.paymentRemaining),
61536164
periodicRate2,
6154-
after.paymentRemaining,
61556165
env.current()->parentCloseTime(),
61566166
after.paymentInterval,
61576167
after.previousPaymentDate,
61586168
static_cast<std::uint32_t>(
61596169
after.startDate.time_since_epoch().count()),
61606170
closeInterestRate);
6171+
61616172
// Round to asset scale and split interest/fee parts
61626173
auto const roundedInterest =
61636174
roundToAsset(asset.raw(), fullPaymentInterest, after.loanScale);
@@ -6185,9 +6196,9 @@ class Loan_test : public beast::unit_test::suite
61856196
// window by clamping prevPaymentDate to 'now' for the full-pay path.
61866197
auto const prevClamped = std::min(after.previousPaymentDate, nowSecs);
61876198
auto const fullPaymentInterestClamped = computeFullPaymentInterest(
6188-
after.periodicPayment,
6199+
detail::loanPrincipalFromPeriodicPayment(
6200+
after.periodicPayment, periodicRate2, after.paymentRemaining),
61896201
periodicRate2,
6190-
after.paymentRemaining,
61916202
env.current()->parentCloseTime(),
61926203
after.paymentInterval,
61936204
prevClamped,

src/xrpld/app/misc/LendingHelpers.h

Lines changed: 57 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -179,11 +179,12 @@ adjustImpreciseNumber(
179179
}
180180

181181
inline int
182-
getVaultScale(SLE::const_ref vaultSle)
182+
getAssetsTotalScale(SLE::const_ref vaultSle)
183183
{
184184
if (!vaultSle)
185185
return Number::minExponent - 1; // LCOV_EXCL_LINE
186-
return vaultSle->at(sfAssetsTotal).exponent();
186+
return STAmount{vaultSle->at(sfAsset), vaultSle->at(sfAssetsTotal)}
187+
.exponent();
187188
}
188189

189190
TER
@@ -202,14 +203,6 @@ computeRawLoanState(
202203
std::uint32_t const paymentRemaining,
203204
TenthBips32 const managementFeeRate);
204205

205-
LoanState
206-
computeRawLoanState(
207-
Number const& periodicPayment,
208-
TenthBips32 interestRate,
209-
std::uint32_t paymentInterval,
210-
std::uint32_t const paymentRemaining,
211-
TenthBips32 const managementFeeRate);
212-
213206
// Constructs a valid LoanState object from arbitrary inputs
214207
LoanState
215208
constructLoanState(
@@ -239,17 +232,6 @@ computeFullPaymentInterest(
239232
std::uint32_t startDate,
240233
TenthBips32 closeInterestRate);
241234

242-
Number
243-
computeFullPaymentInterest(
244-
Number const& periodicPayment,
245-
Number const& periodicRate,
246-
std::uint32_t paymentRemaining,
247-
NetClock::time_point parentCloseTime,
248-
std::uint32_t paymentInterval,
249-
std::uint32_t prevPaymentDate,
250-
std::uint32_t startDate,
251-
TenthBips32 closeInterestRate);
252-
253235
namespace detail {
254236
// These classes and functions should only be accessed by LendingHelper
255237
// functions and unit tests
@@ -387,6 +369,58 @@ struct LoanStateDeltas
387369
nonNegative();
388370
};
389371

372+
Number
373+
computeRaisedRate(Number const& periodicRate, std::uint32_t paymentsRemaining);
374+
375+
Number
376+
computePaymentFactor(
377+
Number const& periodicRate,
378+
std::uint32_t paymentsRemaining);
379+
380+
std::pair<Number, Number>
381+
computeInterestAndFeeParts(
382+
Asset const& asset,
383+
Number const& interest,
384+
TenthBips16 managementFeeRate,
385+
std::int32_t loanScale);
386+
387+
Number
388+
loanPeriodicPayment(
389+
Number const& principalOutstanding,
390+
Number const& periodicRate,
391+
std::uint32_t paymentsRemaining);
392+
393+
Number
394+
loanPrincipalFromPeriodicPayment(
395+
Number const& periodicPayment,
396+
Number const& periodicRate,
397+
std::uint32_t paymentsRemaining);
398+
399+
Number
400+
loanLatePaymentInterest(
401+
Number const& principalOutstanding,
402+
TenthBips32 lateInterestRate,
403+
NetClock::time_point parentCloseTime,
404+
std::uint32_t nextPaymentDueDate);
405+
406+
Number
407+
loanAccruedInterest(
408+
Number const& principalOutstanding,
409+
Number const& periodicRate,
410+
NetClock::time_point parentCloseTime,
411+
std::uint32_t startDate,
412+
std::uint32_t prevPaymentDate,
413+
std::uint32_t paymentInterval);
414+
415+
ExtendedPaymentComponents
416+
computeOverpaymentComponents(
417+
Asset const& asset,
418+
int32_t const loanScale,
419+
Number const& overpayment,
420+
TenthBips32 const overpaymentInterestRate,
421+
TenthBips32 const overpaymentFeeRate,
422+
TenthBips16 const managementFeeRate);
423+
390424
PaymentComponents
391425
computePaymentComponents(
392426
Asset const& asset,
@@ -418,7 +452,8 @@ computeLoanProperties(
418452
std::uint32_t paymentInterval,
419453
std::uint32_t paymentsRemaining,
420454
TenthBips32 managementFeeRate,
421-
std::int32_t minimumScale);
455+
std::int32_t minimumScale,
456+
beast::Journal j);
422457

423458
bool
424459
isRounded(Asset const& asset, Number const& value, std::int32_t scale);

0 commit comments

Comments
 (0)