Skip to content

Commit 8eabad4

Browse files
authored
Merge branch 'ximinez/lending-XLS-66-ongoing' into ximinez/lending-vault-payments
2 parents 89de2b5 + 5ceb915 commit 8eabad4

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,
@@ -4010,7 +4020,7 @@ class Loan_test : public beast::unit_test::suite
40104020
createJson = env.json(createJson, sig(sfCounterpartySignature, lender));
40114021
// Fails in preclaim because principal requested can't be
40124022
// represented as XRP
4013-
env(createJson, ter(tecPRECISION_LOSS));
4023+
env(createJson, ter(tecPRECISION_LOSS), THISLINE);
40144024
env.close();
40154025

40164026
BEAST_EXPECT(!env.le(keylet));
@@ -4022,7 +4032,7 @@ class Loan_test : public beast::unit_test::suite
40224032
createJson = env.json(createJson, sig(sfCounterpartySignature, lender));
40234033
// Fails in doApply because the payment is too small to be
40244034
// represented as XRP.
4025-
env(createJson, ter(tecPRECISION_LOSS));
4035+
env(createJson, ter(tecPRECISION_LOSS), THISLINE);
40264036
env.close();
40274037
}
40284038

@@ -4997,7 +5007,7 @@ class Loan_test : public beast::unit_test::suite
49975007
auto const keylet = keylet::loan(broker.brokerID, loanSequence);
49985008

49995009
createJson = env.json(createJson, sig(sfCounterpartySignature, lender));
5000-
env(createJson, ter(tecPRECISION_LOSS));
5010+
env(createJson, ter(tecPRECISION_LOSS), THISLINE);
50015011
env.close(startDate);
50025012

50035013
auto loanPayTx = env.json(
@@ -6145,15 +6155,16 @@ class Loan_test : public beast::unit_test::suite
61456155
// Accrued + prepayment-penalty interest based on current periodic
61466156
// schedule
61476157
auto const fullPaymentInterest = computeFullPaymentInterest(
6148-
after.periodicPayment,
6158+
detail::loanPrincipalFromPeriodicPayment(
6159+
after.periodicPayment, periodicRate2, after.paymentRemaining),
61496160
periodicRate2,
6150-
after.paymentRemaining,
61516161
env.current()->parentCloseTime(),
61526162
after.paymentInterval,
61536163
after.previousPaymentDate,
61546164
static_cast<std::uint32_t>(
61556165
after.startDate.time_since_epoch().count()),
61566166
closeInterestRate);
6167+
61576168
// Round to asset scale and split interest/fee parts
61586169
auto const roundedInterest =
61596170
roundToAsset(asset.raw(), fullPaymentInterest, after.loanScale);
@@ -6181,9 +6192,9 @@ class Loan_test : public beast::unit_test::suite
61816192
// window by clamping prevPaymentDate to 'now' for the full-pay path.
61826193
auto const prevClamped = std::min(after.previousPaymentDate, nowSecs);
61836194
auto const fullPaymentInterestClamped = computeFullPaymentInterest(
6184-
after.periodicPayment,
6195+
detail::loanPrincipalFromPeriodicPayment(
6196+
after.periodicPayment, periodicRate2, after.paymentRemaining),
61856197
periodicRate2,
6186-
after.paymentRemaining,
61876198
env.current()->parentCloseTime(),
61886199
after.paymentInterval,
61896200
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)