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 = computePaymentFactor(tc.periodicRate, tc.paymentsRemaining);
121 computedPaymentFactor == tc.expectedPaymentFactor,
122 "Payment factor mismatch: expected " +
to_string(tc.expectedPaymentFactor) +
", got " +
136 Number principalOutstanding;
139 Number expectedPeriodicPayment;
144 .name =
"Zero principal outstanding",
145 .principalOutstanding =
Number{0},
146 .periodicRate =
Number{5, -2},
147 .paymentsRemaining = 5,
148 .expectedPeriodicPayment =
Number{0},
151 .name =
"Zero payments remaining",
152 .principalOutstanding =
Number{1'000},
153 .periodicRate =
Number{5, -2},
154 .paymentsRemaining = 0,
155 .expectedPeriodicPayment =
Number{0},
158 .name =
"Zero periodic rate",
159 .principalOutstanding =
Number{1'000},
160 .periodicRate =
Number{0},
161 .paymentsRemaining = 4,
162 .expectedPeriodicPayment =
Number{250},
165 .name =
"Standard case",
166 .principalOutstanding =
Number{1'000},
168 .paymentsRemaining = 3,
169 .expectedPeriodicPayment =
Number{389569066396123265, -15},
173 for (
auto const& tc : testCases)
175 testcase(
"loanPeriodicPayment: " + tc.name);
177 auto const computedPeriodicPayment =
178 loanPeriodicPayment(tc.principalOutstanding, tc.periodicRate, tc.paymentsRemaining);
180 computedPeriodicPayment == tc.expectedPeriodicPayment,
181 "Periodic payment mismatch: expected " +
to_string(tc.expectedPeriodicPayment) +
", got " +
198 Number expectedPrincipalOutstanding;
203 .name =
"Zero periodic payment",
204 .periodicPayment =
Number{0},
205 .periodicRate =
Number{5, -2},
206 .paymentsRemaining = 5,
207 .expectedPrincipalOutstanding =
Number{0},
210 .name =
"Zero payments remaining",
211 .periodicPayment =
Number{1'000},
212 .periodicRate =
Number{5, -2},
213 .paymentsRemaining = 0,
214 .expectedPrincipalOutstanding =
Number{0},
217 .name =
"Zero periodic rate",
218 .periodicPayment =
Number{250},
219 .periodicRate =
Number{0},
220 .paymentsRemaining = 4,
221 .expectedPrincipalOutstanding =
Number{1'000},
224 .name =
"Standard case",
225 .periodicPayment =
Number{389569066396123265, -15},
227 .paymentsRemaining = 3,
228 .expectedPrincipalOutstanding =
Number{1'000},
232 for (
auto const& tc : testCases)
234 testcase(
"loanPrincipalFromPeriodicPayment: " + tc.name);
236 auto const computedPrincipalOutstanding =
237 loanPrincipalFromPeriodicPayment(tc.periodicPayment, tc.periodicRate, tc.paymentsRemaining);
239 computedPrincipalOutstanding == tc.expectedPrincipalOutstanding,
240 "Principal outstanding mismatch: expected " +
to_string(tc.expectedPrincipalOutstanding) +
", got " +
241 to_string(computedPrincipalOutstanding));
248 testcase(
"computeOverpaymentComponents");
252 Account const issuer{
"issuer"};
254 int32_t
const loanScale = 1;
256 auto const overpaymentInterestRate =
TenthBips32{10'000};
257 auto const overpaymentFeeRate =
TenthBips32{50'000};
258 auto const managementFeeRate =
TenthBips16{10'000};
260 auto const expectedOverpaymentFee =
Number{500};
261 auto const expectedOverpaymentInterestGross =
Number{100};
262 auto const expectedOverpaymentInterestNet =
Number{90};
263 auto const expectedOverpaymentManagementFee =
Number{10};
264 auto const expectedPrincipalPortion =
Number{400};
267 IOU, loanScale,
overpayment, overpaymentInterestRate, overpaymentFeeRate, managementFeeRate);
269 BEAST_EXPECT(components.untrackedManagementFee == expectedOverpaymentFee);
271 BEAST_EXPECT(components.untrackedInterest == expectedOverpaymentInterestNet);
273 BEAST_EXPECT(components.trackedInterestPart() == expectedOverpaymentInterestNet);
275 BEAST_EXPECT(components.trackedManagementFeeDelta == expectedOverpaymentManagementFee);
276 BEAST_EXPECT(components.trackedPrincipalDelta == expectedPrincipalPortion);
278 components.trackedManagementFeeDelta + components.untrackedInterest == expectedOverpaymentInterestGross);
281 components.trackedManagementFeeDelta + components.untrackedInterest + components.trackedPrincipalDelta +
282 components.untrackedManagementFee ==
297 Number expectedInterestPart;
301 Account const issuer{
"issuer"};
306 {.name =
"Zero interest",
309 .expectedInterestPart =
Number{0},
310 .expectedFeePart =
Number{0}},
311 {.name =
"Zero fee rate",
312 .interest =
Number{1'000},
314 .expectedInterestPart =
Number{1'000},
315 .expectedFeePart =
Number{0}},
316 {.name =
"10% fee rate",
317 .interest =
Number{1'000},
319 .expectedInterestPart =
Number{900},
320 .expectedFeePart =
Number{100}},
323 for (
auto const& tc : testCases)
325 testcase(
"computeInterestAndFeeParts: " + tc.name);
327 auto const [computedInterestPart, computedFeePart] =
328 computeInterestAndFeeParts(
IOU, tc.interest, tc.managementFeeRate, loanScale);
330 computedInterestPart == tc.expectedInterestPart,
331 "Interest part mismatch: expected " +
to_string(tc.expectedInterestPart) +
", got " +
334 computedFeePart == tc.expectedFeePart,
335 "Fee part mismatch: expected " +
to_string(tc.expectedFeePart) +
", got " +
to_string(computedFeePart));
347 Number principalOutstanding;
351 Number expectedLateInterest;
356 .name =
"On-time payment",
357 .principalOutstanding =
Number{1'000},
360 .nextPaymentDueDate = 3'000,
361 .expectedLateInterest =
Number{0},
364 .name =
"Early payment",
365 .principalOutstanding =
Number{1'000},
368 .nextPaymentDueDate = 4'000,
369 .expectedLateInterest =
Number{0},
372 .name =
"No principal outstanding",
373 .principalOutstanding =
Number{0},
376 .nextPaymentDueDate = 2'000,
377 .expectedLateInterest =
Number{0},
380 .name =
"No late interest rate",
381 .principalOutstanding =
Number{1'000},
384 .nextPaymentDueDate = 2'000,
385 .expectedLateInterest =
Number{0},
388 .name =
"Late payment",
389 .principalOutstanding =
Number{1'000},
392 .nextPaymentDueDate = 2'000,
393 .expectedLateInterest =
Number{317097919837645865, -19},
397 for (
auto const& tc : testCases)
399 testcase(
"loanLatePaymentInterest: " + tc.name);
401 auto const computedLateInterest = loanLatePaymentInterest(
402 tc.principalOutstanding, tc.lateInterestRate, tc.parentCloseTime, tc.nextPaymentDueDate);
404 computedLateInterest == tc.expectedLateInterest,
405 "Late interest mismatch: expected " +
to_string(tc.expectedLateInterest) +
", got " +
418 Number principalOutstanding;
424 Number expectedAccruedInterest;
429 .name =
"Zero principal outstanding",
430 .principalOutstanding =
Number{0},
431 .periodicRate =
Number{5, -2},
434 .prevPaymentDate = 2'500,
435 .paymentInterval = 30 * 24 * 60 * 60,
436 .expectedAccruedInterest =
Number{0},
439 .name =
"Before start date",
440 .principalOutstanding =
Number{1'000},
441 .periodicRate =
Number{5, -2},
444 .prevPaymentDate = 1'500,
445 .paymentInterval = 30 * 24 * 60 * 60,
446 .expectedAccruedInterest =
Number{0},
449 .name =
"Zero periodic rate",
450 .principalOutstanding =
Number{1'000},
451 .periodicRate =
Number{0},
454 .prevPaymentDate = 2'500,
455 .paymentInterval = 30 * 24 * 60 * 60,
456 .expectedAccruedInterest =
Number{0},
459 .name =
"Zero payment interval",
460 .principalOutstanding =
Number{1'000},
461 .periodicRate =
Number{5, -2},
464 .prevPaymentDate = 2'500,
465 .paymentInterval = 0,
466 .expectedAccruedInterest =
Number{0},
469 .name =
"Standard case",
470 .principalOutstanding =
Number{1'000},
471 .periodicRate =
Number{5, -2},
474 .prevPaymentDate = 2'000,
475 .paymentInterval = 30 * 24 * 60 * 60,
476 .expectedAccruedInterest =
Number{1929012345679012346, -20},
480 for (
auto const& tc : testCases)
482 testcase(
"loanAccruedInterest: " + tc.name);
484 auto const computedAccruedInterest = loanAccruedInterest(
485 tc.principalOutstanding,
492 computedAccruedInterest == tc.expectedAccruedInterest,
493 "Accrued interest mismatch: expected " +
to_string(tc.expectedAccruedInterest) +
", got " +
509 Number rawPrincipalOutstanding;
516 Number expectedFullPaymentInterest;
521 .name =
"Zero principal outstanding",
522 .rawPrincipalOutstanding =
Number{0},
523 .periodicRate =
Number{5, -2},
525 .paymentInterval = 30 * 24 * 60 * 60,
526 .prevPaymentDate = 2'000,
529 .expectedFullPaymentInterest =
Number{0},
532 .name =
"Zero close interest rate",
533 .rawPrincipalOutstanding =
Number{1'000},
534 .periodicRate =
Number{5, -2},
536 .paymentInterval = 30 * 24 * 60 * 60,
537 .prevPaymentDate = 2'000,
540 .expectedFullPaymentInterest =
Number{1929012345679012346, -20},
543 .name =
"Standard case",
544 .rawPrincipalOutstanding =
Number{1'000},
545 .periodicRate =
Number{5, -2},
547 .paymentInterval = 30 * 24 * 60 * 60,
548 .prevPaymentDate = 2'000,
551 .expectedFullPaymentInterest =
Number{1000192901234567901, -16},
555 for (
auto const& tc : testCases)
557 testcase(
"computeFullPaymentInterest: " + tc.name);
560 tc.rawPrincipalOutstanding,
566 tc.closeInterestRate);
568 computedFullPaymentInterest == tc.expectedFullPaymentInterest,
569 "Full payment interest mismatch: expected " +
to_string(tc.expectedFullPaymentInterest) +
", got " +
578 testcase(
"tryOverpayment - No Interest No Fee");
584 Account const issuer{
"issuer"};
589 Number const loanPrincipal{1'000};
592 auto const periodicRate =
loanPeriodicRate(loanInterestRate, paymentInterval);
593 Number const overpaymentAmount{50};
595 auto const overpaymentComponents = computeOverpaymentComponents(
599 asset, loanPrincipal, loanInterestRate, paymentInterval, paymentsRemaining, managementFeeRate, loanScale);
601 auto const ret = tryOverpayment(
604 overpaymentComponents,
605 loanProperties.loanState,
606 loanProperties.periodicPayment,
614 auto const& [actualPaymentParts, newLoanProperties] = *ret;
615 auto const& newState = newLoanProperties.loanState;
619 actualPaymentParts.valueChange == 0,
620 " valueChange mismatch: expected 0, got " +
to_string(actualPaymentParts.valueChange));
623 actualPaymentParts.feePaid == 0,
624 " feePaid mismatch: expected 0, got " +
to_string(actualPaymentParts.feePaid));
627 actualPaymentParts.interestPaid == 0,
628 " interestPaid mismatch: expected 0, got " +
to_string(actualPaymentParts.interestPaid));
631 actualPaymentParts.principalPaid == overpaymentAmount,
632 " principalPaid mismatch: expected " +
to_string(overpaymentAmount) +
", got " +
633 to_string(actualPaymentParts.principalPaid));
637 loanProperties.loanState.interestDue - newState.interestDue == 0,
638 " interest change mismatch: expected 0, got " +
639 to_string(loanProperties.loanState.interestDue - newState.interestDue));
642 loanProperties.loanState.managementFeeDue - newState.managementFeeDue == 0,
643 " management fee change mismatch: expected 0, got " +
644 to_string(loanProperties.loanState.managementFeeDue - newState.managementFeeDue));
647 actualPaymentParts.principalPaid ==
648 loanProperties.loanState.principalOutstanding - newState.principalOutstanding,
649 " principalPaid mismatch: expected " +
650 to_string(loanProperties.loanState.principalOutstanding - newState.principalOutstanding) +
", got " +
651 to_string(actualPaymentParts.principalPaid));
657 testcase(
"tryOverpayment - No Interest With Overpayment Fee");
663 Account const issuer{
"issuer"};
668 Number const loanPrincipal{1'000};
671 auto const periodicRate =
loanPeriodicRate(loanInterestRate, paymentInterval);
673 auto const overpaymentComponents = computeOverpaymentComponents(
682 asset, loanPrincipal, loanInterestRate, paymentInterval, paymentsRemaining, managementFeeRate, loanScale);
684 auto const ret = tryOverpayment(
687 overpaymentComponents,
688 loanProperties.loanState,
689 loanProperties.periodicPayment,
697 auto const& [actualPaymentParts, newLoanProperties] = *ret;
698 auto const& newState = newLoanProperties.loanState;
702 actualPaymentParts.valueChange == 0,
703 " valueChange mismatch: expected 0, got " +
to_string(actualPaymentParts.valueChange));
706 actualPaymentParts.feePaid == 5,
707 " feePaid mismatch: expected 5, got " +
to_string(actualPaymentParts.feePaid));
710 actualPaymentParts.principalPaid == 45,
711 " principalPaid mismatch: expected 45, got `" +
to_string(actualPaymentParts.principalPaid));
714 actualPaymentParts.interestPaid == 0,
715 " interestPaid mismatch: expected 0, got " +
to_string(actualPaymentParts.interestPaid));
720 loanProperties.loanState.interestDue - newState.interestDue == 0,
721 " interest change mismatch: expected 0, got " +
722 to_string(loanProperties.loanState.interestDue - newState.interestDue));
726 loanProperties.loanState.managementFeeDue - newState.managementFeeDue == 0,
727 " management fee change mismatch: expected 0, got " +
728 to_string(loanProperties.loanState.managementFeeDue - newState.managementFeeDue));
731 actualPaymentParts.principalPaid ==
732 loanProperties.loanState.principalOutstanding - newState.principalOutstanding,
733 " principalPaid mismatch: expected " +
734 to_string(loanProperties.loanState.principalOutstanding - newState.principalOutstanding) +
", got " +
735 to_string(actualPaymentParts.principalPaid));
741 testcase(
"tryOverpayment - Loan Interest, No Overpayment Fees");
747 Account const issuer{
"issuer"};
752 Number const loanPrincipal{1'000};
755 auto const periodicRate =
loanPeriodicRate(loanInterestRate, paymentInterval);
757 auto const overpaymentComponents = computeOverpaymentComponents(
766 asset, loanPrincipal, loanInterestRate, paymentInterval, paymentsRemaining, managementFeeRate, loanScale);
768 auto const ret = tryOverpayment(
771 overpaymentComponents,
772 loanProperties.loanState,
773 loanProperties.periodicPayment,
781 auto const& [actualPaymentParts, newLoanProperties] = *ret;
782 auto const& newState = newLoanProperties.loanState;
788 (actualPaymentParts.valueChange ==
Number{-228802, -5}),
789 " valueChange mismatch: expected " +
to_string(
Number{-228802, -5}) +
", got " +
790 to_string(actualPaymentParts.valueChange));
794 actualPaymentParts.feePaid == 0,
795 " feePaid mismatch: expected 0, got " +
to_string(actualPaymentParts.feePaid));
798 actualPaymentParts.principalPaid == 50,
799 " principalPaid mismatch: expected 50, got `" +
to_string(actualPaymentParts.principalPaid));
803 actualPaymentParts.interestPaid == 0,
804 " interestPaid mismatch: expected 0, got " +
to_string(actualPaymentParts.interestPaid));
808 actualPaymentParts.principalPaid ==
809 loanProperties.loanState.principalOutstanding - newState.principalOutstanding,
810 " principalPaid mismatch: expected " +
811 to_string(loanProperties.loanState.principalOutstanding - newState.principalOutstanding) +
", got " +
812 to_string(actualPaymentParts.principalPaid));
815 actualPaymentParts.valueChange == newState.interestDue - loanProperties.loanState.interestDue,
816 " valueChange mismatch: expected " +
817 to_string(newState.interestDue - loanProperties.loanState.interestDue) +
", got " +
818 to_string(actualPaymentParts.valueChange));
822 loanProperties.loanState.managementFeeDue - newState.managementFeeDue == 0,
823 " management fee change mismatch: expected 0, got " +
824 to_string(loanProperties.loanState.managementFeeDue - newState.managementFeeDue));
830 testcase(
"tryOverpayment - Loan Interest, Overpayment Interest, No Fee");
836 Account const issuer{
"issuer"};
841 Number const loanPrincipal{1'000};
844 auto const periodicRate =
loanPeriodicRate(loanInterestRate, paymentInterval);
846 auto const overpaymentComponents = computeOverpaymentComponents(
855 asset, loanPrincipal, loanInterestRate, paymentInterval, paymentsRemaining, managementFeeRate, loanScale);
857 auto const ret = tryOverpayment(
860 overpaymentComponents,
861 loanProperties.loanState,
862 loanProperties.periodicPayment,
870 auto const& [actualPaymentParts, newLoanProperties] = *ret;
871 auto const& newState = newLoanProperties.loanState;
876 actualPaymentParts.interestPaid == 5,
877 " interestPaid mismatch: expected 5, got " +
to_string(actualPaymentParts.interestPaid));
882 (actualPaymentParts.valueChange ==
Number{-205922, -5} + actualPaymentParts.interestPaid),
883 " valueChange mismatch: expected " +
884 to_string(actualPaymentParts.valueChange - actualPaymentParts.interestPaid) +
", got " +
885 to_string(actualPaymentParts.valueChange));
889 actualPaymentParts.feePaid == 0,
890 " feePaid mismatch: expected 0, got " +
to_string(actualPaymentParts.feePaid));
893 actualPaymentParts.principalPaid == 45,
894 " principalPaid mismatch: expected 45, got `" +
to_string(actualPaymentParts.principalPaid));
898 actualPaymentParts.principalPaid ==
899 loanProperties.loanState.principalOutstanding - newState.principalOutstanding,
900 " principalPaid mismatch: expected " +
901 to_string(loanProperties.loanState.principalOutstanding - newState.principalOutstanding) +
", got " +
902 to_string(actualPaymentParts.principalPaid));
907 actualPaymentParts.valueChange - actualPaymentParts.interestPaid ==
908 newState.interestDue - loanProperties.loanState.interestDue,
909 " valueChange mismatch: expected " +
911 newState.interestDue - loanProperties.loanState.interestDue + actualPaymentParts.interestPaid) +
912 ", got " +
to_string(actualPaymentParts.valueChange));
916 loanProperties.loanState.managementFeeDue - newState.managementFeeDue == 0,
917 " management fee change mismatch: expected 0, got " +
918 to_string(loanProperties.loanState.managementFeeDue - newState.managementFeeDue));
925 "tryOverpayment - Loan Interest and Fee, Overpayment Interest, No "
932 Account const issuer{
"issuer"};
937 Number const loanPrincipal{1'000};
940 auto const periodicRate =
loanPeriodicRate(loanInterestRate, paymentInterval);
942 auto const overpaymentComponents = computeOverpaymentComponents(
951 asset, loanPrincipal, loanInterestRate, paymentInterval, paymentsRemaining, managementFeeRate, loanScale);
953 auto const ret = tryOverpayment(
956 overpaymentComponents,
957 loanProperties.loanState,
958 loanProperties.periodicPayment,
966 auto const& [actualPaymentParts, newLoanProperties] = *ret;
967 auto const& newState = newLoanProperties.loanState;
974 (actualPaymentParts.interestPaid ==
Number{45, -1}),
975 " interestPaid mismatch: expected 4.5, got " +
to_string(actualPaymentParts.interestPaid));
980 (actualPaymentParts.valueChange ==
Number{-18533, -4} + actualPaymentParts.interestPaid),
981 " valueChange mismatch: expected " +
to_string(
Number{-18533, -4} + actualPaymentParts.interestPaid) +
982 ", got " +
to_string(actualPaymentParts.valueChange));
987 (actualPaymentParts.feePaid ==
Number{5, -1}),
988 " feePaid mismatch: expected 0.5, got " +
to_string(actualPaymentParts.feePaid));
991 actualPaymentParts.principalPaid == 45,
992 " principalPaid mismatch: expected 45, got `" +
to_string(actualPaymentParts.principalPaid));
996 actualPaymentParts.principalPaid ==
997 loanProperties.loanState.principalOutstanding - newState.principalOutstanding,
998 " principalPaid mismatch: expected " +
999 to_string(loanProperties.loanState.principalOutstanding - newState.principalOutstanding) +
", got " +
1000 to_string(actualPaymentParts.principalPaid));
1005 (newState.managementFeeDue - loanProperties.loanState.managementFeeDue ==
Number{-20592, -5}),
1006 " management fee change mismatch: expected " +
to_string(
Number{-20592, -5}) +
", got " +
1007 to_string(newState.managementFeeDue - loanProperties.loanState.managementFeeDue));
1010 actualPaymentParts.valueChange - actualPaymentParts.interestPaid ==
1011 newState.interestDue - loanProperties.loanState.interestDue,
1012 " valueChange mismatch: expected " +
1013 to_string(newState.interestDue - loanProperties.loanState.interestDue) +
", got " +
1014 to_string(actualPaymentParts.valueChange - actualPaymentParts.interestPaid));
1020 testcase(
"tryOverpayment - Loan Interest, Fee, Overpayment Interest, Fee");
1022 using namespace jtx;
1026 Account const issuer{
"issuer"};
1031 Number const loanPrincipal{1'000};
1034 auto const periodicRate =
loanPeriodicRate(loanInterestRate, paymentInterval);
1036 auto const overpaymentComponents = computeOverpaymentComponents(
1045 asset, loanPrincipal, loanInterestRate, paymentInterval, paymentsRemaining, managementFeeRate, loanScale);
1047 auto const ret = tryOverpayment(
1050 overpaymentComponents,
1051 loanProperties.loanState,
1052 loanProperties.periodicPayment,
1060 auto const& [actualPaymentParts, newLoanProperties] = *ret;
1061 auto const& newState = newLoanProperties.loanState;
1068 (actualPaymentParts.interestPaid ==
Number{45, -1}),
1069 " interestPaid mismatch: expected 4.5, got " +
to_string(actualPaymentParts.interestPaid));
1074 (actualPaymentParts.valueChange ==
Number{-164737, -5} + actualPaymentParts.interestPaid),
1075 " valueChange mismatch: expected " +
to_string(
Number{-164737, -5} + actualPaymentParts.interestPaid) +
1076 ", got " +
to_string(actualPaymentParts.valueChange));
1081 (actualPaymentParts.feePaid ==
Number{55, -1}),
1082 " feePaid mismatch: expected 5.5, got " +
to_string(actualPaymentParts.feePaid));
1085 actualPaymentParts.principalPaid == 40,
1086 " principalPaid mismatch: expected 40, got `" +
to_string(actualPaymentParts.principalPaid));
1091 actualPaymentParts.principalPaid ==
1092 loanProperties.loanState.principalOutstanding - newState.principalOutstanding,
1093 " principalPaid mismatch: expected " +
1094 to_string(loanProperties.loanState.principalOutstanding - newState.principalOutstanding) +
", got " +
1095 to_string(actualPaymentParts.principalPaid));
1100 (newState.managementFeeDue - loanProperties.loanState.managementFeeDue ==
Number{-18304, -5}),
1101 " management fee change mismatch: expected " +
to_string(
Number{-18304, -5}) +
", got " +
1102 to_string(newState.managementFeeDue - loanProperties.loanState.managementFeeDue));
1105 actualPaymentParts.valueChange - actualPaymentParts.interestPaid ==
1106 newState.interestDue - loanProperties.loanState.interestDue,
1107 " valueChange mismatch: expected " +
1108 to_string(newState.interestDue - loanProperties.loanState.interestDue) +
", got " +
1109 to_string(actualPaymentParts.valueChange - actualPaymentParts.interestPaid));