1#include <xrpl/beast/unit_test/suite.h>
4#include <test/jtx/Account.h>
5#include <test/jtx/amount.h>
6#include <test/jtx/mpt.h>
8#include <xrpl/beast/xor_shift_engine.h>
9#include <xrpl/protocol/SField.h>
10#include <xrpl/server/LoadFeeTrack.h>
11#include <xrpl/tx/transactors/lending/LendingHelpers.h>
12#include <xrpl/tx/transactors/lending/LoanSet.h>
13#include <xrpl/tx/transactors/system/Batch.h>
38 .name =
"Zero payments remaining",
39 .periodicRate =
Number{5, -2},
40 .paymentsRemaining = 0,
41 .expectedRaisedRate =
Number{1},
44 .name =
"One payment remaining",
45 .periodicRate =
Number{5, -2},
46 .paymentsRemaining = 1,
47 .expectedRaisedRate =
Number{105, -2},
50 .name =
"Multiple payments remaining",
51 .periodicRate =
Number{5, -2},
52 .paymentsRemaining = 3,
53 .expectedRaisedRate =
Number{1157625, -6},
56 .name =
"Zero periodic rate",
58 .paymentsRemaining = 5,
59 .expectedRaisedRate =
Number{1},
62 for (
auto const& tc : testCases)
64 testcase(
"computeRaisedRate: " + tc.name);
66 auto const computedRaisedRate =
67 computeRaisedRate(tc.periodicRate, tc.paymentsRemaining);
69 computedRaisedRate == tc.expectedRaisedRate,
70 "Raised rate mismatch: expected " +
to_string(tc.expectedRaisedRate) +
", got " +
85 Number expectedPaymentFactor;
90 .name =
"Zero periodic rate",
92 .paymentsRemaining = 4,
93 .expectedPaymentFactor =
Number{25, -2},
96 .name =
"One payment remaining",
97 .periodicRate =
Number{5, -2},
98 .paymentsRemaining = 1,
99 .expectedPaymentFactor =
Number{105, -2},
102 .name =
"Multiple payments remaining",
103 .periodicRate =
Number{5, -2},
104 .paymentsRemaining = 3,
105 .expectedPaymentFactor =
Number{3672085646312450436, -19},
108 .name =
"Zero payments remaining",
109 .periodicRate =
Number{5, -2},
110 .paymentsRemaining = 0,
111 .expectedPaymentFactor =
Number{0},
115 for (
auto const& tc : testCases)
117 testcase(
"computePaymentFactor: " + tc.name);
119 auto const computedPaymentFactor =
120 computePaymentFactor(tc.periodicRate, tc.paymentsRemaining);
122 computedPaymentFactor == tc.expectedPaymentFactor,
123 "Payment factor mismatch: expected " +
to_string(tc.expectedPaymentFactor) +
124 ", got " +
to_string(computedPaymentFactor));
137 Number principalOutstanding;
140 Number expectedPeriodicPayment;
145 .name =
"Zero principal outstanding",
146 .principalOutstanding =
Number{0},
147 .periodicRate =
Number{5, -2},
148 .paymentsRemaining = 5,
149 .expectedPeriodicPayment =
Number{0},
152 .name =
"Zero payments remaining",
153 .principalOutstanding =
Number{1'000},
154 .periodicRate =
Number{5, -2},
155 .paymentsRemaining = 0,
156 .expectedPeriodicPayment =
Number{0},
159 .name =
"Zero periodic rate",
160 .principalOutstanding =
Number{1'000},
161 .periodicRate =
Number{0},
162 .paymentsRemaining = 4,
163 .expectedPeriodicPayment =
Number{250},
166 .name =
"Standard case",
167 .principalOutstanding =
Number{1'000},
169 .paymentsRemaining = 3,
170 .expectedPeriodicPayment =
Number{389569066396123265, -15},
174 for (
auto const& tc : testCases)
176 testcase(
"loanPeriodicPayment: " + tc.name);
178 auto const computedPeriodicPayment =
179 loanPeriodicPayment(tc.principalOutstanding, tc.periodicRate, tc.paymentsRemaining);
181 computedPeriodicPayment == tc.expectedPeriodicPayment,
182 "Periodic payment mismatch: expected " +
to_string(tc.expectedPeriodicPayment) +
183 ", got " +
to_string(computedPeriodicPayment));
199 Number expectedPrincipalOutstanding;
204 .name =
"Zero periodic payment",
205 .periodicPayment =
Number{0},
206 .periodicRate =
Number{5, -2},
207 .paymentsRemaining = 5,
208 .expectedPrincipalOutstanding =
Number{0},
211 .name =
"Zero payments remaining",
212 .periodicPayment =
Number{1'000},
213 .periodicRate =
Number{5, -2},
214 .paymentsRemaining = 0,
215 .expectedPrincipalOutstanding =
Number{0},
218 .name =
"Zero periodic rate",
219 .periodicPayment =
Number{250},
220 .periodicRate =
Number{0},
221 .paymentsRemaining = 4,
222 .expectedPrincipalOutstanding =
Number{1'000},
225 .name =
"Standard case",
226 .periodicPayment =
Number{389569066396123265, -15},
228 .paymentsRemaining = 3,
229 .expectedPrincipalOutstanding =
Number{1'000},
233 for (
auto const& tc : testCases)
235 testcase(
"loanPrincipalFromPeriodicPayment: " + tc.name);
237 auto const computedPrincipalOutstanding = loanPrincipalFromPeriodicPayment(
238 tc.periodicPayment, tc.periodicRate, tc.paymentsRemaining);
240 computedPrincipalOutstanding == tc.expectedPrincipalOutstanding,
241 "Principal outstanding mismatch: expected " +
242 to_string(tc.expectedPrincipalOutstanding) +
", got " +
243 to_string(computedPrincipalOutstanding));
250 testcase(
"computeOverpaymentComponents");
254 Account const issuer{
"issuer"};
256 int32_t
const loanScale = 1;
258 auto const overpaymentInterestRate =
TenthBips32{10'000};
259 auto const overpaymentFeeRate =
TenthBips32{50'000};
260 auto const managementFeeRate =
TenthBips16{10'000};
262 auto const expectedOverpaymentFee =
Number{500};
263 auto const expectedOverpaymentInterestGross =
Number{100};
264 auto const expectedOverpaymentInterestNet =
Number{90};
265 auto const expectedOverpaymentManagementFee =
Number{10};
266 auto const expectedPrincipalPortion =
Number{400};
272 overpaymentInterestRate,
276 BEAST_EXPECT(components.untrackedManagementFee == expectedOverpaymentFee);
278 BEAST_EXPECT(components.untrackedInterest == expectedOverpaymentInterestNet);
280 BEAST_EXPECT(components.trackedInterestPart() == expectedOverpaymentInterestNet);
282 BEAST_EXPECT(components.trackedManagementFeeDelta == expectedOverpaymentManagementFee);
283 BEAST_EXPECT(components.trackedPrincipalDelta == expectedPrincipalPortion);
285 components.trackedManagementFeeDelta + components.untrackedInterest ==
286 expectedOverpaymentInterestGross);
289 components.trackedManagementFeeDelta + components.untrackedInterest +
290 components.trackedPrincipalDelta + components.untrackedManagementFee ==
305 Number expectedInterestPart;
309 Account const issuer{
"issuer"};
314 {.name =
"Zero interest",
317 .expectedInterestPart =
Number{0},
318 .expectedFeePart =
Number{0}},
319 {.name =
"Zero fee rate",
320 .interest =
Number{1'000},
322 .expectedInterestPart =
Number{1'000},
323 .expectedFeePart =
Number{0}},
324 {.name =
"10% fee rate",
325 .interest =
Number{1'000},
327 .expectedInterestPart =
Number{900},
328 .expectedFeePart =
Number{100}},
331 for (
auto const& tc : testCases)
333 testcase(
"computeInterestAndFeeParts: " + tc.name);
335 auto const [computedInterestPart, computedFeePart] =
336 computeInterestAndFeeParts(
IOU, tc.interest, tc.managementFeeRate, loanScale);
338 computedInterestPart == tc.expectedInterestPart,
339 "Interest part mismatch: expected " +
to_string(tc.expectedInterestPart) +
340 ", got " +
to_string(computedInterestPart));
342 computedFeePart == tc.expectedFeePart,
343 "Fee part mismatch: expected " +
to_string(tc.expectedFeePart) +
", got " +
356 Number principalOutstanding;
360 Number expectedLateInterest;
365 .name =
"On-time payment",
366 .principalOutstanding =
Number{1'000},
369 .nextPaymentDueDate = 3'000,
370 .expectedLateInterest =
Number{0},
373 .name =
"Early payment",
374 .principalOutstanding =
Number{1'000},
377 .nextPaymentDueDate = 4'000,
378 .expectedLateInterest =
Number{0},
381 .name =
"No principal outstanding",
382 .principalOutstanding =
Number{0},
385 .nextPaymentDueDate = 2'000,
386 .expectedLateInterest =
Number{0},
389 .name =
"No late interest rate",
390 .principalOutstanding =
Number{1'000},
393 .nextPaymentDueDate = 2'000,
394 .expectedLateInterest =
Number{0},
397 .name =
"Late payment",
398 .principalOutstanding =
Number{1'000},
401 .nextPaymentDueDate = 2'000,
402 .expectedLateInterest =
Number{317097919837645865, -19},
406 for (
auto const& tc : testCases)
408 testcase(
"loanLatePaymentInterest: " + tc.name);
410 auto const computedLateInterest = loanLatePaymentInterest(
411 tc.principalOutstanding,
414 tc.nextPaymentDueDate);
416 computedLateInterest == tc.expectedLateInterest,
417 "Late interest mismatch: expected " +
to_string(tc.expectedLateInterest) +
418 ", got " +
to_string(computedLateInterest));
430 Number principalOutstanding;
436 Number expectedAccruedInterest;
441 .name =
"Zero principal outstanding",
442 .principalOutstanding =
Number{0},
443 .periodicRate =
Number{5, -2},
446 .prevPaymentDate = 2'500,
447 .paymentInterval = 30 * 24 * 60 * 60,
448 .expectedAccruedInterest =
Number{0},
451 .name =
"Before start date",
452 .principalOutstanding =
Number{1'000},
453 .periodicRate =
Number{5, -2},
456 .prevPaymentDate = 1'500,
457 .paymentInterval = 30 * 24 * 60 * 60,
458 .expectedAccruedInterest =
Number{0},
461 .name =
"Zero periodic rate",
462 .principalOutstanding =
Number{1'000},
463 .periodicRate =
Number{0},
466 .prevPaymentDate = 2'500,
467 .paymentInterval = 30 * 24 * 60 * 60,
468 .expectedAccruedInterest =
Number{0},
471 .name =
"Zero payment interval",
472 .principalOutstanding =
Number{1'000},
473 .periodicRate =
Number{5, -2},
476 .prevPaymentDate = 2'500,
477 .paymentInterval = 0,
478 .expectedAccruedInterest =
Number{0},
481 .name =
"Standard case",
482 .principalOutstanding =
Number{1'000},
483 .periodicRate =
Number{5, -2},
486 .prevPaymentDate = 2'000,
487 .paymentInterval = 30 * 24 * 60 * 60,
488 .expectedAccruedInterest =
Number{1929012345679012346, -20},
492 for (
auto const& tc : testCases)
494 testcase(
"loanAccruedInterest: " + tc.name);
496 auto const computedAccruedInterest = loanAccruedInterest(
497 tc.principalOutstanding,
504 computedAccruedInterest == tc.expectedAccruedInterest,
505 "Accrued interest mismatch: expected " +
to_string(tc.expectedAccruedInterest) +
506 ", got " +
to_string(computedAccruedInterest));
521 Number rawPrincipalOutstanding;
528 Number expectedFullPaymentInterest;
533 .name =
"Zero principal outstanding",
534 .rawPrincipalOutstanding =
Number{0},
535 .periodicRate =
Number{5, -2},
537 .paymentInterval = 30 * 24 * 60 * 60,
538 .prevPaymentDate = 2'000,
541 .expectedFullPaymentInterest =
Number{0},
544 .name =
"Zero close interest rate",
545 .rawPrincipalOutstanding =
Number{1'000},
546 .periodicRate =
Number{5, -2},
548 .paymentInterval = 30 * 24 * 60 * 60,
549 .prevPaymentDate = 2'000,
552 .expectedFullPaymentInterest =
Number{1929012345679012346, -20},
555 .name =
"Standard case",
556 .rawPrincipalOutstanding =
Number{1'000},
557 .periodicRate =
Number{5, -2},
559 .paymentInterval = 30 * 24 * 60 * 60,
560 .prevPaymentDate = 2'000,
563 .expectedFullPaymentInterest =
Number{1000192901234567901, -16},
567 for (
auto const& tc : testCases)
569 testcase(
"computeFullPaymentInterest: " + tc.name);
572 tc.rawPrincipalOutstanding,
578 tc.closeInterestRate);
580 computedFullPaymentInterest == tc.expectedFullPaymentInterest,
581 "Full payment interest mismatch: expected " +
582 to_string(tc.expectedFullPaymentInterest) +
", got " +
591 testcase(
"tryOverpayment - No Interest No Fee");
596 Env const env{*
this};
597 Account const issuer{
"issuer"};
602 Number const loanPrincipal{1'000};
605 auto const periodicRate =
loanPeriodicRate(loanInterestRate, paymentInterval);
606 Number const overpaymentAmount{50};
608 auto const overpaymentComponents = computeOverpaymentComponents(
620 auto const ret = tryOverpayment(
623 overpaymentComponents,
624 loanProperties.loanState,
625 loanProperties.periodicPayment,
633 auto const& [actualPaymentParts, newLoanProperties] = *ret;
634 auto const& newState = newLoanProperties.loanState;
638 actualPaymentParts.valueChange == 0,
639 " valueChange mismatch: expected 0, got " +
to_string(actualPaymentParts.valueChange));
642 actualPaymentParts.feePaid == 0,
643 " feePaid mismatch: expected 0, got " +
to_string(actualPaymentParts.feePaid));
646 actualPaymentParts.interestPaid == 0,
647 " interestPaid mismatch: expected 0, got " +
648 to_string(actualPaymentParts.interestPaid));
651 actualPaymentParts.principalPaid == overpaymentAmount,
652 " principalPaid mismatch: expected " +
to_string(overpaymentAmount) +
", got " +
653 to_string(actualPaymentParts.principalPaid));
657 loanProperties.loanState.interestDue - newState.interestDue == 0,
658 " interest change mismatch: expected 0, got " +
659 to_string(loanProperties.loanState.interestDue - newState.interestDue));
662 loanProperties.loanState.managementFeeDue - newState.managementFeeDue == 0,
663 " management fee change mismatch: expected 0, got " +
664 to_string(loanProperties.loanState.managementFeeDue - newState.managementFeeDue));
667 actualPaymentParts.principalPaid ==
668 loanProperties.loanState.principalOutstanding - newState.principalOutstanding,
669 " principalPaid mismatch: expected " +
671 loanProperties.loanState.principalOutstanding - newState.principalOutstanding) +
672 ", got " +
to_string(actualPaymentParts.principalPaid));
678 testcase(
"tryOverpayment - No Interest With Overpayment Fee");
683 Env const env{*
this};
684 Account const issuer{
"issuer"};
689 Number const loanPrincipal{1'000};
692 auto const periodicRate =
loanPeriodicRate(loanInterestRate, paymentInterval);
694 auto const overpaymentComponents = computeOverpaymentComponents(
711 auto const ret = tryOverpayment(
714 overpaymentComponents,
715 loanProperties.loanState,
716 loanProperties.periodicPayment,
724 auto const& [actualPaymentParts, newLoanProperties] = *ret;
725 auto const& newState = newLoanProperties.loanState;
729 actualPaymentParts.valueChange == 0,
730 " valueChange mismatch: expected 0, got " +
to_string(actualPaymentParts.valueChange));
733 actualPaymentParts.feePaid == 5,
734 " feePaid mismatch: expected 5, got " +
to_string(actualPaymentParts.feePaid));
737 actualPaymentParts.principalPaid == 45,
738 " principalPaid mismatch: expected 45, got `" +
739 to_string(actualPaymentParts.principalPaid));
742 actualPaymentParts.interestPaid == 0,
743 " interestPaid mismatch: expected 0, got " +
744 to_string(actualPaymentParts.interestPaid));
749 loanProperties.loanState.interestDue - newState.interestDue == 0,
750 " interest change mismatch: expected 0, got " +
751 to_string(loanProperties.loanState.interestDue - newState.interestDue));
755 loanProperties.loanState.managementFeeDue - newState.managementFeeDue == 0,
756 " management fee change mismatch: expected 0, got " +
757 to_string(loanProperties.loanState.managementFeeDue - newState.managementFeeDue));
760 actualPaymentParts.principalPaid ==
761 loanProperties.loanState.principalOutstanding - newState.principalOutstanding,
762 " principalPaid mismatch: expected " +
764 loanProperties.loanState.principalOutstanding - newState.principalOutstanding) +
765 ", got " +
to_string(actualPaymentParts.principalPaid));
771 testcase(
"tryOverpayment - Loan Interest, No Overpayment Fees");
776 Env const env{*
this};
777 Account const issuer{
"issuer"};
782 Number const loanPrincipal{1'000};
785 auto const periodicRate =
loanPeriodicRate(loanInterestRate, paymentInterval);
787 auto const overpaymentComponents = computeOverpaymentComponents(
804 auto const ret = tryOverpayment(
807 overpaymentComponents,
808 loanProperties.loanState,
809 loanProperties.periodicPayment,
817 auto const& [actualPaymentParts, newLoanProperties] = *ret;
818 auto const& newState = newLoanProperties.loanState;
824 (actualPaymentParts.valueChange ==
Number{-228802, -5}),
825 " valueChange mismatch: expected " +
to_string(
Number{-228802, -5}) +
", got " +
826 to_string(actualPaymentParts.valueChange));
830 actualPaymentParts.feePaid == 0,
831 " feePaid mismatch: expected 0, got " +
to_string(actualPaymentParts.feePaid));
834 actualPaymentParts.principalPaid == 50,
835 " principalPaid mismatch: expected 50, got `" +
836 to_string(actualPaymentParts.principalPaid));
840 actualPaymentParts.interestPaid == 0,
841 " interestPaid mismatch: expected 0, got " +
842 to_string(actualPaymentParts.interestPaid));
846 actualPaymentParts.principalPaid ==
847 loanProperties.loanState.principalOutstanding - newState.principalOutstanding,
848 " principalPaid mismatch: expected " +
850 loanProperties.loanState.principalOutstanding - newState.principalOutstanding) +
851 ", got " +
to_string(actualPaymentParts.principalPaid));
854 actualPaymentParts.valueChange ==
855 newState.interestDue - loanProperties.loanState.interestDue,
856 " valueChange mismatch: expected " +
857 to_string(newState.interestDue - loanProperties.loanState.interestDue) +
", got " +
858 to_string(actualPaymentParts.valueChange));
862 loanProperties.loanState.managementFeeDue - newState.managementFeeDue == 0,
863 " management fee change mismatch: expected 0, got " +
864 to_string(loanProperties.loanState.managementFeeDue - newState.managementFeeDue));
870 testcase(
"tryOverpayment - Loan Interest, Overpayment Interest, No Fee");
875 Env const env{*
this};
876 Account const issuer{
"issuer"};
881 Number const loanPrincipal{1'000};
884 auto const periodicRate =
loanPeriodicRate(loanInterestRate, paymentInterval);
886 auto const overpaymentComponents = computeOverpaymentComponents(
903 auto const ret = tryOverpayment(
906 overpaymentComponents,
907 loanProperties.loanState,
908 loanProperties.periodicPayment,
916 auto const& [actualPaymentParts, newLoanProperties] = *ret;
917 auto const& newState = newLoanProperties.loanState;
922 actualPaymentParts.interestPaid == 5,
923 " interestPaid mismatch: expected 5, got " +
924 to_string(actualPaymentParts.interestPaid));
929 (actualPaymentParts.valueChange ==
930 Number{-205922, -5} + actualPaymentParts.interestPaid),
931 " valueChange mismatch: expected " +
932 to_string(actualPaymentParts.valueChange - actualPaymentParts.interestPaid) +
933 ", got " +
to_string(actualPaymentParts.valueChange));
937 actualPaymentParts.feePaid == 0,
938 " feePaid mismatch: expected 0, got " +
to_string(actualPaymentParts.feePaid));
941 actualPaymentParts.principalPaid == 45,
942 " principalPaid mismatch: expected 45, got `" +
943 to_string(actualPaymentParts.principalPaid));
947 actualPaymentParts.principalPaid ==
948 loanProperties.loanState.principalOutstanding - newState.principalOutstanding,
949 " principalPaid mismatch: expected " +
951 loanProperties.loanState.principalOutstanding - newState.principalOutstanding) +
952 ", got " +
to_string(actualPaymentParts.principalPaid));
957 actualPaymentParts.valueChange - actualPaymentParts.interestPaid ==
958 newState.interestDue - loanProperties.loanState.interestDue,
959 " valueChange mismatch: expected " +
961 newState.interestDue - loanProperties.loanState.interestDue +
962 actualPaymentParts.interestPaid) +
963 ", got " +
to_string(actualPaymentParts.valueChange));
967 loanProperties.loanState.managementFeeDue - newState.managementFeeDue == 0,
968 " management fee change mismatch: expected 0, got " +
969 to_string(loanProperties.loanState.managementFeeDue - newState.managementFeeDue));
976 "tryOverpayment - Loan Interest and Fee, Overpayment Interest, No "
982 Env const env{*
this};
983 Account const issuer{
"issuer"};
988 Number const loanPrincipal{1'000};
991 auto const periodicRate =
loanPeriodicRate(loanInterestRate, paymentInterval);
993 auto const overpaymentComponents = computeOverpaymentComponents(
1010 auto const ret = tryOverpayment(
1013 overpaymentComponents,
1014 loanProperties.loanState,
1015 loanProperties.periodicPayment,
1023 auto const& [actualPaymentParts, newLoanProperties] = *ret;
1024 auto const& newState = newLoanProperties.loanState;
1031 (actualPaymentParts.interestPaid ==
Number{45, -1}),
1032 " interestPaid mismatch: expected 4.5, got " +
1033 to_string(actualPaymentParts.interestPaid));
1038 (actualPaymentParts.valueChange ==
1039 Number{-18533, -4} + actualPaymentParts.interestPaid),
1040 " valueChange mismatch: expected " +
1041 to_string(
Number{-18533, -4} + actualPaymentParts.interestPaid) +
", got " +
1042 to_string(actualPaymentParts.valueChange));
1047 (actualPaymentParts.feePaid ==
Number{5, -1}),
1048 " feePaid mismatch: expected 0.5, got " +
to_string(actualPaymentParts.feePaid));
1051 actualPaymentParts.principalPaid == 45,
1052 " principalPaid mismatch: expected 45, got `" +
1053 to_string(actualPaymentParts.principalPaid));
1057 actualPaymentParts.principalPaid ==
1058 loanProperties.loanState.principalOutstanding - newState.principalOutstanding,
1059 " principalPaid mismatch: expected " +
1061 loanProperties.loanState.principalOutstanding - newState.principalOutstanding) +
1062 ", got " +
to_string(actualPaymentParts.principalPaid));
1067 (newState.managementFeeDue - loanProperties.loanState.managementFeeDue ==
1069 " management fee change mismatch: expected " +
to_string(
Number{-20592, -5}) +
1071 to_string(newState.managementFeeDue - loanProperties.loanState.managementFeeDue));
1074 actualPaymentParts.valueChange - actualPaymentParts.interestPaid ==
1075 newState.interestDue - loanProperties.loanState.interestDue,
1076 " valueChange mismatch: expected " +
1077 to_string(newState.interestDue - loanProperties.loanState.interestDue) +
", got " +
1078 to_string(actualPaymentParts.valueChange - actualPaymentParts.interestPaid));
1084 testcase(
"tryOverpayment - Loan Interest, Fee, Overpayment Interest, Fee");
1086 using namespace jtx;
1089 Env const env{*
this};
1090 Account const issuer{
"issuer"};
1095 Number const loanPrincipal{1'000};
1098 auto const periodicRate =
loanPeriodicRate(loanInterestRate, paymentInterval);
1100 auto const overpaymentComponents = computeOverpaymentComponents(
1117 auto const ret = tryOverpayment(
1120 overpaymentComponents,
1121 loanProperties.loanState,
1122 loanProperties.periodicPayment,
1130 auto const& [actualPaymentParts, newLoanProperties] = *ret;
1131 auto const& newState = newLoanProperties.loanState;
1138 (actualPaymentParts.interestPaid ==
Number{45, -1}),
1139 " interestPaid mismatch: expected 4.5, got " +
1140 to_string(actualPaymentParts.interestPaid));
1145 (actualPaymentParts.valueChange ==
1146 Number{-164737, -5} + actualPaymentParts.interestPaid),
1147 " valueChange mismatch: expected " +
1148 to_string(
Number{-164737, -5} + actualPaymentParts.interestPaid) +
", got " +
1149 to_string(actualPaymentParts.valueChange));
1154 (actualPaymentParts.feePaid ==
Number{55, -1}),
1155 " feePaid mismatch: expected 5.5, got " +
to_string(actualPaymentParts.feePaid));
1158 actualPaymentParts.principalPaid == 40,
1159 " principalPaid mismatch: expected 40, got `" +
1160 to_string(actualPaymentParts.principalPaid));
1165 actualPaymentParts.principalPaid ==
1166 loanProperties.loanState.principalOutstanding - newState.principalOutstanding,
1167 " principalPaid mismatch: expected " +
1169 loanProperties.loanState.principalOutstanding - newState.principalOutstanding) +
1170 ", got " +
to_string(actualPaymentParts.principalPaid));
1175 (newState.managementFeeDue - loanProperties.loanState.managementFeeDue ==
1177 " management fee change mismatch: expected " +
to_string(
Number{-18304, -5}) +
1179 to_string(newState.managementFeeDue - loanProperties.loanState.managementFeeDue));
1182 actualPaymentParts.valueChange - actualPaymentParts.interestPaid ==
1183 newState.interestDue - loanProperties.loanState.interestDue,
1184 " valueChange mismatch: expected " +
1185 to_string(newState.interestDue - loanProperties.loanState.interestDue) +
", got " +
1186 to_string(actualPaymentParts.valueChange - actualPaymentParts.interestPaid));
1212BEAST_DEFINE_TESTSUITE(LendingHelpers, app,
xrpl);
testcase_t testcase
Memberspace for declaring test cases.
Number is a floating point type that can represent a wide range of values.
void testComputePaymentFactor()
void testComputeRaisedRate()
void testLoanPrincipalFromPeriodicPayment()
void testTryOverpaymentLoanInterestNoOverpaymentFees()
void testComputeInterestAndFeeParts()
void testTryOverpaymentNoInterestOverpaymentFee()
void testTryOverpaymentNoInterestNoFee()
void testTryOverpaymentLoanInterestFeeOverpaymentInterestNoFee()
void testTryOverpaymentLoanInterestFeeOverpaymentInterestFee()
void testComputeFullPaymentInterest()
void testLoanPeriodicPayment()
void testComputeOverpaymentComponents()
void testTryOverpaymentLoanInterestOverpaymentInterest()
void run() override
Runs the suite.
void testLoanAccruedInterest()
void testLoanLatePaymentInterest()
Immutable cryptographic account descriptor.
A transaction testing environment.
Converts to IOU Issue or STAmount.
ExtendedPaymentComponents computeOverpaymentComponents(Asset const &asset, int32_t const loanScale, Number const &overpayment, TenthBips32 const overpaymentInterestRate, TenthBips32 const overpaymentFeeRate, TenthBips16 const managementFeeRate)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Number loanPeriodicRate(TenthBips32 interestRate, std::uint32_t paymentInterval)
std::string to_string(base_uint< Bits, Tag > const &a)
TenthBips< std::uint32_t > TenthBips32
LoanProperties computeLoanProperties(Asset const &asset, Number const &principalOutstanding, TenthBips32 interestRate, std::uint32_t paymentInterval, std::uint32_t paymentsRemaining, TenthBips32 managementFeeRate, std::int32_t minimumScale)
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)