diff --git a/LendingHelpers__test_8cpp_source.html b/LendingHelpers__test_8cpp_source.html index 3082de5d6f..2dc7b80bae 100644 --- a/LendingHelpers__test_8cpp_source.html +++ b/LendingHelpers__test_8cpp_source.html @@ -695,576 +695,564 @@ $(document).ready(function() { init_codefold(0); });
592 auto const periodicRate = loanPeriodicRate(loanInterestRate, paymentInterval);
593 Number const overpaymentAmount{50};
594
-
595 ExtendedPaymentComponents const overpaymentComponents = computeOverpaymentComponents(
+
595 auto const overpaymentComponents = computeOverpaymentComponents(
596 asset, loanScale, overpaymentAmount, TenthBips32(0), TenthBips32(0), managementFeeRate);
597
-
598 auto const loanProperites = computeLoanProperties(
+
598 auto const loanProperties = computeLoanProperties(
599 asset, loanPrincipal, loanInterestRate, paymentInterval, paymentsRemaining, managementFeeRate, loanScale);
600
-
601 Number const periodicPayment = loanProperites.periodicPayment;
-
602
-
603 auto const ret = tryOverpayment(
-
604 asset,
-
605 loanScale,
-
606 overpaymentComponents,
-
607 loanProperites.loanState,
-
608 periodicPayment,
-
609 periodicRate,
-
610 paymentsRemaining,
-
611 managementFeeRate,
-
612 env.journal);
+
601 auto const ret = tryOverpayment(
+
602 asset,
+
603 loanScale,
+
604 overpaymentComponents,
+
605 loanProperties.loanState,
+
606 loanProperties.periodicPayment,
+
607 periodicRate,
+
608 paymentsRemaining,
+
609 managementFeeRate,
+
610 env.journal);
+
611
+
612 BEAST_EXPECT(ret);
613
-
614 BEAST_EXPECT(ret);
-
615
-
616 auto const& [actualPaymentParts, newLoanProperties] = *ret;
-
617 auto const& newState = newLoanProperties.loanState;
-
618
-
619 // =========== VALIDATE PAYMENT PARTS ===========
-
620 BEAST_EXPECTS(
-
621 actualPaymentParts.valueChange == 0,
-
622 " valueChange mismatch: expected 0, got " + to_string(actualPaymentParts.valueChange));
-
623
-
624 BEAST_EXPECTS(
-
625 actualPaymentParts.feePaid == 0,
-
626 " feePaid mismatch: expected 0, got " + to_string(actualPaymentParts.feePaid));
-
627
-
628 BEAST_EXPECTS(
-
629 actualPaymentParts.interestPaid == 0,
-
630 " interestPaid mismatch: expected 0, got " + to_string(actualPaymentParts.interestPaid));
-
631
-
632 BEAST_EXPECTS(
-
633 actualPaymentParts.principalPaid == overpaymentAmount,
-
634 " principalPaid mismatch: expected " + to_string(overpaymentAmount) + ", got " +
-
635 to_string(actualPaymentParts.principalPaid));
-
636
-
637 // =========== VALIDATE STATE CHANGES ===========
-
638 BEAST_EXPECTS(
-
639 loanProperites.loanState.interestDue - newState.interestDue == 0,
-
640 " interest change mismatch: expected 0, got " +
-
641 to_string(loanProperites.loanState.interestDue - newState.interestDue));
-
642
-
643 BEAST_EXPECTS(
-
644 loanProperites.loanState.managementFeeDue - newState.managementFeeDue == 0,
-
645 " management fee change mismatch: expected 0, got " +
-
646 to_string(loanProperites.loanState.managementFeeDue - newState.managementFeeDue));
-
647
-
648 BEAST_EXPECTS(
-
649 actualPaymentParts.principalPaid ==
-
650 loanProperites.loanState.principalOutstanding - newState.principalOutstanding,
-
651 " principalPaid mismatch: expected " +
-
652 to_string(loanProperites.loanState.principalOutstanding - newState.principalOutstanding) + ", got " +
-
653 to_string(actualPaymentParts.principalPaid));
-
654 }
+
614 auto const& [actualPaymentParts, newLoanProperties] = *ret;
+
615 auto const& newState = newLoanProperties.loanState;
+
616
+
617 // =========== VALIDATE PAYMENT PARTS ===========
+
618 BEAST_EXPECTS(
+
619 actualPaymentParts.valueChange == 0,
+
620 " valueChange mismatch: expected 0, got " + to_string(actualPaymentParts.valueChange));
+
621
+
622 BEAST_EXPECTS(
+
623 actualPaymentParts.feePaid == 0,
+
624 " feePaid mismatch: expected 0, got " + to_string(actualPaymentParts.feePaid));
+
625
+
626 BEAST_EXPECTS(
+
627 actualPaymentParts.interestPaid == 0,
+
628 " interestPaid mismatch: expected 0, got " + to_string(actualPaymentParts.interestPaid));
+
629
+
630 BEAST_EXPECTS(
+
631 actualPaymentParts.principalPaid == overpaymentAmount,
+
632 " principalPaid mismatch: expected " + to_string(overpaymentAmount) + ", got " +
+
633 to_string(actualPaymentParts.principalPaid));
+
634
+
635 // =========== VALIDATE STATE CHANGES ===========
+
636 BEAST_EXPECTS(
+
637 loanProperties.loanState.interestDue - newState.interestDue == 0,
+
638 " interest change mismatch: expected 0, got " +
+
639 to_string(loanProperties.loanState.interestDue - newState.interestDue));
+
640
+
641 BEAST_EXPECTS(
+
642 loanProperties.loanState.managementFeeDue - newState.managementFeeDue == 0,
+
643 " management fee change mismatch: expected 0, got " +
+
644 to_string(loanProperties.loanState.managementFeeDue - newState.managementFeeDue));
+
645
+
646 BEAST_EXPECTS(
+
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));
+
652 }
-
655
-
656 void
-
-
657 testTryOverpaymentNoInterestOverpaymentFee()
-
658 {
-
659 testcase("tryOverpayment - No Interest With Overpayment Fee");
-
660
-
661 using namespace jtx;
-
662 using namespace xrpl::detail;
-
663
-
664 Env env{*this};
-
665 Account const issuer{"issuer"};
-
666 PrettyAsset const asset = issuer["USD"];
-
667 std::int32_t const loanScale = -5;
-
668 TenthBips16 const managementFeeRate{0}; // 0%
-
669 TenthBips32 const loanInterestRate{0}; // 0%
-
670 Number const loanPrincipal{1'000};
-
671 std::uint32_t const paymentInterval = 30 * 24 * 60 * 60;
-
672 std::uint32_t const paymentsRemaining = 10;
-
673 auto const periodicRate = loanPeriodicRate(loanInterestRate, paymentInterval);
-
674
-
675 ExtendedPaymentComponents const overpaymentComponents = computeOverpaymentComponents(
-
676 asset,
-
677 loanScale,
-
678 Number{50, 0},
-
679 TenthBips32(0),
-
680 TenthBips32(10'000), // 10% overpayment fee
-
681 managementFeeRate);
-
682
-
683 auto const loanProperites = computeLoanProperties(
-
684 asset, loanPrincipal, loanInterestRate, paymentInterval, paymentsRemaining, managementFeeRate, loanScale);
-
685
-
686 Number const periodicPayment = loanProperites.periodicPayment;
-
687
-
688 auto const ret = tryOverpayment(
-
689 asset,
-
690 loanScale,
-
691 overpaymentComponents,
-
692 loanProperites.loanState,
-
693 periodicPayment,
-
694 periodicRate,
-
695 paymentsRemaining,
-
696 managementFeeRate,
-
697 env.journal);
-
698
-
699 BEAST_EXPECT(ret);
-
700
-
701 auto const& [actualPaymentParts, newLoanProperties] = *ret;
-
702 auto const& newState = newLoanProperties.loanState;
-
703
-
704 // =========== VALIDATE PAYMENT PARTS ===========
+
653
+
654 void
+
+ +
656 {
+
657 testcase("tryOverpayment - No Interest With Overpayment Fee");
+
658
+
659 using namespace jtx;
+
660 using namespace xrpl::detail;
+
661
+
662 Env env{*this};
+
663 Account const issuer{"issuer"};
+
664 PrettyAsset const asset = issuer["USD"];
+
665 std::int32_t const loanScale = -5;
+
666 TenthBips16 const managementFeeRate{0}; // 0%
+
667 TenthBips32 const loanInterestRate{0}; // 0%
+
668 Number const loanPrincipal{1'000};
+
669 std::uint32_t const paymentInterval = 30 * 24 * 60 * 60;
+
670 std::uint32_t const paymentsRemaining = 10;
+
671 auto const periodicRate = loanPeriodicRate(loanInterestRate, paymentInterval);
+
672
+
673 auto const overpaymentComponents = computeOverpaymentComponents(
+
674 asset,
+
675 loanScale,
+
676 Number{50, 0},
+
677 TenthBips32(0),
+
678 TenthBips32(10'000), // 10% overpayment fee
+
679 managementFeeRate);
+
680
+
681 auto const loanProperties = computeLoanProperties(
+
682 asset, loanPrincipal, loanInterestRate, paymentInterval, paymentsRemaining, managementFeeRate, loanScale);
+
683
+
684 auto const ret = tryOverpayment(
+
685 asset,
+
686 loanScale,
+
687 overpaymentComponents,
+
688 loanProperties.loanState,
+
689 loanProperties.periodicPayment,
+
690 periodicRate,
+
691 paymentsRemaining,
+
692 managementFeeRate,
+
693 env.journal);
+
694
+
695 BEAST_EXPECT(ret);
+
696
+
697 auto const& [actualPaymentParts, newLoanProperties] = *ret;
+
698 auto const& newState = newLoanProperties.loanState;
+
699
+
700 // =========== VALIDATE PAYMENT PARTS ===========
+
701 BEAST_EXPECTS(
+
702 actualPaymentParts.valueChange == 0,
+
703 " valueChange mismatch: expected 0, got " + to_string(actualPaymentParts.valueChange));
+
704
705 BEAST_EXPECTS(
-
706 actualPaymentParts.valueChange == 0,
-
707 " valueChange mismatch: expected 0, got " + to_string(actualPaymentParts.valueChange));
+
706 actualPaymentParts.feePaid == 5,
+
707 " feePaid mismatch: expected 5, got " + to_string(actualPaymentParts.feePaid));
708
709 BEAST_EXPECTS(
-
710 actualPaymentParts.feePaid == 5,
-
711 " feePaid mismatch: expected 5, got " + to_string(actualPaymentParts.feePaid));
+
710 actualPaymentParts.principalPaid == 45,
+
711 " principalPaid mismatch: expected 45, got `" + to_string(actualPaymentParts.principalPaid));
712
713 BEAST_EXPECTS(
-
714 actualPaymentParts.principalPaid == 45,
-
715 " principalPaid mismatch: expected 45, got `" + to_string(actualPaymentParts.principalPaid));
+
714 actualPaymentParts.interestPaid == 0,
+
715 " interestPaid mismatch: expected 0, got " + to_string(actualPaymentParts.interestPaid));
716
-
717 BEAST_EXPECTS(
-
718 actualPaymentParts.interestPaid == 0,
-
719 " interestPaid mismatch: expected 0, got " + to_string(actualPaymentParts.interestPaid));
-
720
-
721 // =========== VALIDATE STATE CHANGES ===========
-
722 // With no Loan interest, interest outstanding should not change
-
723 BEAST_EXPECTS(
-
724 loanProperites.loanState.interestDue - newState.interestDue == 0,
-
725 " interest change mismatch: expected 0, got " +
-
726 to_string(loanProperites.loanState.interestDue - newState.interestDue));
-
727
-
728 // With no Loan management fee, management fee due should not change
-
729 BEAST_EXPECTS(
-
730 loanProperites.loanState.managementFeeDue - newState.managementFeeDue == 0,
-
731 " management fee change mismatch: expected 0, got " +
-
732 to_string(loanProperites.loanState.managementFeeDue - newState.managementFeeDue));
-
733
-
734 BEAST_EXPECTS(
-
735 actualPaymentParts.principalPaid ==
-
736 loanProperites.loanState.principalOutstanding - newState.principalOutstanding,
-
737 " principalPaid mismatch: expected " +
-
738 to_string(loanProperites.loanState.principalOutstanding - newState.principalOutstanding) + ", got " +
-
739 to_string(actualPaymentParts.principalPaid));
-
740 }
+
717 // =========== VALIDATE STATE CHANGES ===========
+
718 // With no Loan interest, interest outstanding should not change
+
719 BEAST_EXPECTS(
+
720 loanProperties.loanState.interestDue - newState.interestDue == 0,
+
721 " interest change mismatch: expected 0, got " +
+
722 to_string(loanProperties.loanState.interestDue - newState.interestDue));
+
723
+
724 // With no Loan management fee, management fee due should not change
+
725 BEAST_EXPECTS(
+
726 loanProperties.loanState.managementFeeDue - newState.managementFeeDue == 0,
+
727 " management fee change mismatch: expected 0, got " +
+
728 to_string(loanProperties.loanState.managementFeeDue - newState.managementFeeDue));
+
729
+
730 BEAST_EXPECTS(
+
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));
+
736 }
-
741
-
742 void
-
- -
744 {
-
745 testcase("tryOverpayment - Loan Interest, No Overpayment Fees");
-
746
-
747 using namespace jtx;
-
748 using namespace xrpl::detail;
-
749
-
750 Env env{*this};
-
751 Account const issuer{"issuer"};
-
752 PrettyAsset const asset = issuer["USD"];
-
753 std::int32_t const loanScale = -5;
-
754 TenthBips16 const managementFeeRate{0}; // 0%
-
755 TenthBips32 const loanInterestRate{10'000}; // 10%
-
756 Number const loanPrincipal{1'000};
-
757 std::uint32_t const paymentInterval = 30 * 24 * 60 * 60;
-
758 std::uint32_t const paymentsRemaining = 10;
-
759 auto const periodicRate = loanPeriodicRate(loanInterestRate, paymentInterval);
-
760
-
761 ExtendedPaymentComponents const overpaymentComponents = computeOverpaymentComponents(
-
762 asset,
-
763 loanScale,
-
764 Number{50, 0},
-
765 TenthBips32(0), // no overpayment interest
-
766 TenthBips32(0), // 0% overpayment fee
-
767 managementFeeRate);
-
768
-
769 auto const loanProperites = computeLoanProperties(
-
770 asset, loanPrincipal, loanInterestRate, paymentInterval, paymentsRemaining, managementFeeRate, loanScale);
-
771
-
772 Number const periodicPayment = loanProperites.periodicPayment;
-
773
-
774 auto const ret = tryOverpayment(
-
775 asset,
-
776 loanScale,
-
777 overpaymentComponents,
-
778 loanProperites.loanState,
-
779 periodicPayment,
-
780 periodicRate,
-
781 paymentsRemaining,
-
782 managementFeeRate,
-
783 env.journal);
-
784
-
785 BEAST_EXPECT(ret);
-
786
-
787 auto const& [actualPaymentParts, newLoanProperties] = *ret;
-
788 auto const& newState = newLoanProperties.loanState;
-
789
-
790 // =========== VALIDATE PAYMENT PARTS ===========
-
791 // with no overpayment interest portion, value change should equal
-
792 // interest decrease
+
737
+
738 void
+
+ +
740 {
+
741 testcase("tryOverpayment - Loan Interest, No Overpayment Fees");
+
742
+
743 using namespace jtx;
+
744 using namespace xrpl::detail;
+
745
+
746 Env env{*this};
+
747 Account const issuer{"issuer"};
+
748 PrettyAsset const asset = issuer["USD"];
+
749 std::int32_t const loanScale = -5;
+
750 TenthBips16 const managementFeeRate{0}; // 0%
+
751 TenthBips32 const loanInterestRate{10'000}; // 10%
+
752 Number const loanPrincipal{1'000};
+
753 std::uint32_t const paymentInterval = 30 * 24 * 60 * 60;
+
754 std::uint32_t const paymentsRemaining = 10;
+
755 auto const periodicRate = loanPeriodicRate(loanInterestRate, paymentInterval);
+
756
+
757 auto const overpaymentComponents = computeOverpaymentComponents(
+
758 asset,
+
759 loanScale,
+
760 Number{50, 0},
+
761 TenthBips32(0), // no overpayment interest
+
762 TenthBips32(0), // 0% overpayment fee
+
763 managementFeeRate);
+
764
+
765 auto const loanProperties = computeLoanProperties(
+
766 asset, loanPrincipal, loanInterestRate, paymentInterval, paymentsRemaining, managementFeeRate, loanScale);
+
767
+
768 auto const ret = tryOverpayment(
+
769 asset,
+
770 loanScale,
+
771 overpaymentComponents,
+
772 loanProperties.loanState,
+
773 loanProperties.periodicPayment,
+
774 periodicRate,
+
775 paymentsRemaining,
+
776 managementFeeRate,
+
777 env.journal);
+
778
+
779 BEAST_EXPECT(ret);
+
780
+
781 auto const& [actualPaymentParts, newLoanProperties] = *ret;
+
782 auto const& newState = newLoanProperties.loanState;
+
783
+
784 // =========== VALIDATE PAYMENT PARTS ===========
+
785 // with no overpayment interest portion, value change should equal
+
786 // interest decrease
+
787 BEAST_EXPECTS(
+
788 (actualPaymentParts.valueChange == Number{-228802, -5}),
+
789 " valueChange mismatch: expected " + to_string(Number{-228802, -5}) + ", got " +
+
790 to_string(actualPaymentParts.valueChange));
+
791
+
792 // with no fee portion, fee paid should be zero
793 BEAST_EXPECTS(
-
794 (actualPaymentParts.valueChange == Number{-228802, -5}),
-
795 " valueChange mismatch: expected " + to_string(Number{-228802, -5}) + ", got " +
-
796 to_string(actualPaymentParts.valueChange));
-
797
-
798 // with no fee portion, fee paid should be zero
-
799 BEAST_EXPECTS(
-
800 actualPaymentParts.feePaid == 0,
-
801 " feePaid mismatch: expected 0, got " + to_string(actualPaymentParts.feePaid));
-
802
-
803 BEAST_EXPECTS(
-
804 actualPaymentParts.principalPaid == 50,
-
805 " principalPaid mismatch: expected 50, got `" + to_string(actualPaymentParts.principalPaid));
-
806
-
807 // with no interest portion, interest paid should be zero
-
808 BEAST_EXPECTS(
-
809 actualPaymentParts.interestPaid == 0,
-
810 " interestPaid mismatch: expected 0, got " + to_string(actualPaymentParts.interestPaid));
-
811
-
812 // =========== VALIDATE STATE CHANGES ===========
-
813 BEAST_EXPECTS(
-
814 actualPaymentParts.principalPaid ==
-
815 loanProperites.loanState.principalOutstanding - newState.principalOutstanding,
-
816 " principalPaid mismatch: expected " +
-
817 to_string(loanProperites.loanState.principalOutstanding - newState.principalOutstanding) + ", got " +
-
818 to_string(actualPaymentParts.principalPaid));
+
794 actualPaymentParts.feePaid == 0,
+
795 " feePaid mismatch: expected 0, got " + to_string(actualPaymentParts.feePaid));
+
796
+
797 BEAST_EXPECTS(
+
798 actualPaymentParts.principalPaid == 50,
+
799 " principalPaid mismatch: expected 50, got `" + to_string(actualPaymentParts.principalPaid));
+
800
+
801 // with no interest portion, interest paid should be zero
+
802 BEAST_EXPECTS(
+
803 actualPaymentParts.interestPaid == 0,
+
804 " interestPaid mismatch: expected 0, got " + to_string(actualPaymentParts.interestPaid));
+
805
+
806 // =========== VALIDATE STATE CHANGES ===========
+
807 BEAST_EXPECTS(
+
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));
+
813
+
814 BEAST_EXPECTS(
+
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));
819
-
820 BEAST_EXPECTS(
-
821 actualPaymentParts.valueChange == newState.interestDue - loanProperites.loanState.interestDue,
-
822 " valueChange mismatch: expected " +
-
823 to_string(newState.interestDue - loanProperites.loanState.interestDue) + ", got " +
-
824 to_string(actualPaymentParts.valueChange));
-
825
-
826 // With no Loan management fee, management fee due should not change
-
827 BEAST_EXPECTS(
-
828 loanProperites.loanState.managementFeeDue - newState.managementFeeDue == 0,
-
829 " management fee change mismatch: expected 0, got " +
-
830 to_string(loanProperites.loanState.managementFeeDue - newState.managementFeeDue));
-
831 }
+
820 // With no Loan management fee, management fee due should not change
+
821 BEAST_EXPECTS(
+
822 loanProperties.loanState.managementFeeDue - newState.managementFeeDue == 0,
+
823 " management fee change mismatch: expected 0, got " +
+
824 to_string(loanProperties.loanState.managementFeeDue - newState.managementFeeDue));
+
825 }
-
832
-
833 void
-
- -
835 {
-
836 testcase("tryOverpayment - Loan Interest, Overpayment Interest, No Fee");
-
837
-
838 using namespace jtx;
-
839 using namespace xrpl::detail;
-
840
-
841 Env env{*this};
-
842 Account const issuer{"issuer"};
-
843 PrettyAsset const asset = issuer["USD"];
-
844 std::int32_t const loanScale = -5;
-
845 TenthBips16 const managementFeeRate{0}; // 0%
-
846 TenthBips32 const loanInterestRate{10'000}; // 10%
-
847 Number const loanPrincipal{1'000};
-
848 std::uint32_t const paymentInterval = 30 * 24 * 60 * 60;
-
849 std::uint32_t const paymentsRemaining = 10;
-
850 auto const periodicRate = loanPeriodicRate(loanInterestRate, paymentInterval);
-
851
-
852 ExtendedPaymentComponents const overpaymentComponents = computeOverpaymentComponents(
-
853 asset,
-
854 loanScale,
-
855 Number{50, 0},
-
856 TenthBips32(10'000), // 10% overpayment interest
-
857 TenthBips32(0), // 0% overpayment fee
-
858 managementFeeRate);
-
859
-
860 auto const loanProperites = computeLoanProperties(
-
861 asset, loanPrincipal, loanInterestRate, paymentInterval, paymentsRemaining, managementFeeRate, loanScale);
-
862
-
863 Number const periodicPayment = loanProperites.periodicPayment;
-
864
-
865 auto const ret = tryOverpayment(
-
866 asset,
-
867 loanScale,
-
868 overpaymentComponents,
-
869 loanProperites.loanState,
-
870 periodicPayment,
-
871 periodicRate,
-
872 paymentsRemaining,
-
873 managementFeeRate,
-
874 env.journal);
-
875
-
876 BEAST_EXPECT(ret);
-
877
-
878 auto const& [actualPaymentParts, newLoanProperties] = *ret;
-
879 auto const& newState = newLoanProperties.loanState;
-
880
-
881 // =========== VALIDATE PAYMENT PARTS ===========
-
882 // with overpayment interest portion, interest paid should be 5
-
883 BEAST_EXPECTS(
-
884 actualPaymentParts.interestPaid == 5,
-
885 " interestPaid mismatch: expected 5, got " + to_string(actualPaymentParts.interestPaid));
+
826
+
827 void
+
+ +
829 {
+
830 testcase("tryOverpayment - Loan Interest, Overpayment Interest, No Fee");
+
831
+
832 using namespace jtx;
+
833 using namespace xrpl::detail;
+
834
+
835 Env env{*this};
+
836 Account const issuer{"issuer"};
+
837 PrettyAsset const asset = issuer["USD"];
+
838 std::int32_t const loanScale = -5;
+
839 TenthBips16 const managementFeeRate{0}; // 0%
+
840 TenthBips32 const loanInterestRate{10'000}; // 10%
+
841 Number const loanPrincipal{1'000};
+
842 std::uint32_t const paymentInterval = 30 * 24 * 60 * 60;
+
843 std::uint32_t const paymentsRemaining = 10;
+
844 auto const periodicRate = loanPeriodicRate(loanInterestRate, paymentInterval);
+
845
+
846 auto const overpaymentComponents = computeOverpaymentComponents(
+
847 asset,
+
848 loanScale,
+
849 Number{50, 0},
+
850 TenthBips32(10'000), // 10% overpayment interest
+
851 TenthBips32(0), // 0% overpayment fee
+
852 managementFeeRate);
+
853
+
854 auto const loanProperties = computeLoanProperties(
+
855 asset, loanPrincipal, loanInterestRate, paymentInterval, paymentsRemaining, managementFeeRate, loanScale);
+
856
+
857 auto const ret = tryOverpayment(
+
858 asset,
+
859 loanScale,
+
860 overpaymentComponents,
+
861 loanProperties.loanState,
+
862 loanProperties.periodicPayment,
+
863 periodicRate,
+
864 paymentsRemaining,
+
865 managementFeeRate,
+
866 env.journal);
+
867
+
868 BEAST_EXPECT(ret);
+
869
+
870 auto const& [actualPaymentParts, newLoanProperties] = *ret;
+
871 auto const& newState = newLoanProperties.loanState;
+
872
+
873 // =========== VALIDATE PAYMENT PARTS ===========
+
874 // with overpayment interest portion, interest paid should be 5
+
875 BEAST_EXPECTS(
+
876 actualPaymentParts.interestPaid == 5,
+
877 " interestPaid mismatch: expected 5, got " + to_string(actualPaymentParts.interestPaid));
+
878
+
879 // With overpayment interest portion, value change should equal the
+
880 // interest decrease plus overpayment interest portion
+
881 BEAST_EXPECTS(
+
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));
886
-
887 // With overpayment interest portion, value change should equal the
-
888 // interest decrease plus overpayment interest portion
-
889 BEAST_EXPECTS(
-
890 (actualPaymentParts.valueChange == Number{-205922, -5} + actualPaymentParts.interestPaid),
-
891 " valueChange mismatch: expected " +
-
892 to_string(actualPaymentParts.valueChange - actualPaymentParts.interestPaid) + ", got " +
-
893 to_string(actualPaymentParts.valueChange));
-
894
-
895 // with no fee portion, fee paid should be zero
-
896 BEAST_EXPECTS(
-
897 actualPaymentParts.feePaid == 0,
-
898 " feePaid mismatch: expected 0, got " + to_string(actualPaymentParts.feePaid));
-
899
-
900 BEAST_EXPECTS(
-
901 actualPaymentParts.principalPaid == 45,
-
902 " principalPaid mismatch: expected 45, got `" + to_string(actualPaymentParts.principalPaid));
+
887 // with no fee portion, fee paid should be zero
+
888 BEAST_EXPECTS(
+
889 actualPaymentParts.feePaid == 0,
+
890 " feePaid mismatch: expected 0, got " + to_string(actualPaymentParts.feePaid));
+
891
+
892 BEAST_EXPECTS(
+
893 actualPaymentParts.principalPaid == 45,
+
894 " principalPaid mismatch: expected 45, got `" + to_string(actualPaymentParts.principalPaid));
+
895
+
896 // =========== VALIDATE STATE CHANGES ===========
+
897 BEAST_EXPECTS(
+
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));
903
-
904 // =========== VALIDATE STATE CHANGES ===========
-
905 BEAST_EXPECTS(
-
906 actualPaymentParts.principalPaid ==
-
907 loanProperites.loanState.principalOutstanding - newState.principalOutstanding,
-
908 " principalPaid mismatch: expected " +
-
909 to_string(loanProperites.loanState.principalOutstanding - newState.principalOutstanding) + ", got " +
-
910 to_string(actualPaymentParts.principalPaid));
-
911
-
912 // The change in interest is equal to the value change sans the
-
913 // overpayment interest
-
914 BEAST_EXPECTS(
-
915 actualPaymentParts.valueChange - actualPaymentParts.interestPaid ==
-
916 newState.interestDue - loanProperites.loanState.interestDue,
-
917 " valueChange mismatch: expected " +
-
918 to_string(
-
919 newState.interestDue - loanProperites.loanState.interestDue + actualPaymentParts.interestPaid) +
-
920 ", got " + to_string(actualPaymentParts.valueChange));
-
921
-
922 // With no Loan management fee, management fee due should not change
-
923 BEAST_EXPECTS(
-
924 loanProperites.loanState.managementFeeDue - newState.managementFeeDue == 0,
-
925 " management fee change mismatch: expected 0, got " +
-
926 to_string(loanProperites.loanState.managementFeeDue - newState.managementFeeDue));
-
927 }
+
904 // The change in interest is equal to the value change sans the
+
905 // overpayment interest
+
906 BEAST_EXPECTS(
+
907 actualPaymentParts.valueChange - actualPaymentParts.interestPaid ==
+
908 newState.interestDue - loanProperties.loanState.interestDue,
+
909 " valueChange mismatch: expected " +
+
910 to_string(
+
911 newState.interestDue - loanProperties.loanState.interestDue + actualPaymentParts.interestPaid) +
+
912 ", got " + to_string(actualPaymentParts.valueChange));
+
913
+
914 // With no Loan management fee, management fee due should not change
+
915 BEAST_EXPECTS(
+
916 loanProperties.loanState.managementFeeDue - newState.managementFeeDue == 0,
+
917 " management fee change mismatch: expected 0, got " +
+
918 to_string(loanProperties.loanState.managementFeeDue - newState.managementFeeDue));
+
919 }
-
928
-
929 void
-
- -
931 {
-
932 testcase(
-
933 "tryOverpayment - Loan Interest and Fee, Overpayment Interest, No "
-
934 "Fee");
-
935
-
936 using namespace jtx;
-
937 using namespace xrpl::detail;
-
938
-
939 Env env{*this};
-
940 Account const issuer{"issuer"};
-
941 PrettyAsset const asset = issuer["USD"];
-
942 std::int32_t const loanScale = -5;
-
943 TenthBips16 const managementFeeRate{10'000}; // 10%
-
944 TenthBips32 const loanInterestRate{10'000}; // 10%
-
945 Number const loanPrincipal{1'000};
-
946 std::uint32_t const paymentInterval = 30 * 24 * 60 * 60;
-
947 std::uint32_t const paymentsRemaining = 10;
-
948 auto const periodicRate = loanPeriodicRate(loanInterestRate, paymentInterval);
+
920
+
921 void
+
+ +
923 {
+
924 testcase(
+
925 "tryOverpayment - Loan Interest and Fee, Overpayment Interest, No "
+
926 "Fee");
+
927
+
928 using namespace jtx;
+
929 using namespace xrpl::detail;
+
930
+
931 Env env{*this};
+
932 Account const issuer{"issuer"};
+
933 PrettyAsset const asset = issuer["USD"];
+
934 std::int32_t const loanScale = -5;
+
935 TenthBips16 const managementFeeRate{10'000}; // 10%
+
936 TenthBips32 const loanInterestRate{10'000}; // 10%
+
937 Number const loanPrincipal{1'000};
+
938 std::uint32_t const paymentInterval = 30 * 24 * 60 * 60;
+
939 std::uint32_t const paymentsRemaining = 10;
+
940 auto const periodicRate = loanPeriodicRate(loanInterestRate, paymentInterval);
+
941
+
942 auto const overpaymentComponents = computeOverpaymentComponents(
+
943 asset,
+
944 loanScale,
+
945 Number{50, 0},
+
946 TenthBips32(10'000), // 10% overpayment interest
+
947 TenthBips32(0), // 0% overpayment fee
+
948 managementFeeRate);
949
-
950 ExtendedPaymentComponents const overpaymentComponents = computeOverpaymentComponents(
-
951 asset,
-
952 loanScale,
-
953 Number{50, 0},
-
954 TenthBips32(10'000), // 10% overpayment interest
-
955 TenthBips32(0), // 0% overpayment fee
-
956 managementFeeRate);
-
957
-
958 auto const loanProperites = computeLoanProperties(
-
959 asset, loanPrincipal, loanInterestRate, paymentInterval, paymentsRemaining, managementFeeRate, loanScale);
-
960
-
961 Number const periodicPayment = loanProperites.periodicPayment;
-
962
-
963 auto const ret = tryOverpayment(
-
964 asset,
-
965 loanScale,
-
966 overpaymentComponents,
-
967 loanProperites.loanState,
-
968 periodicPayment,
-
969 periodicRate,
-
970 paymentsRemaining,
-
971 managementFeeRate,
-
972 env.journal);
-
973
-
974 BEAST_EXPECT(ret);
-
975
-
976 auto const& [actualPaymentParts, newLoanProperties] = *ret;
-
977 auto const& newState = newLoanProperties.loanState;
-
978
-
979 // =========== VALIDATE PAYMENT PARTS ===========
-
980
-
981 // Since there is loan management fee, the fee is charged against
-
982 // overpayment interest portion first, so interest paid remains 4.5
-
983 BEAST_EXPECTS(
-
984 (actualPaymentParts.interestPaid == Number{45, -1}),
-
985 " interestPaid mismatch: expected 4.5, got " + to_string(actualPaymentParts.interestPaid));
-
986
-
987 // With overpayment interest portion, value change should equal the
-
988 // interest decrease plus overpayment interest portion
-
989 BEAST_EXPECTS(
-
990 (actualPaymentParts.valueChange == Number{-18533, -4} + actualPaymentParts.interestPaid),
-
991 " valueChange mismatch: expected " + to_string(Number{-18533, -4} + actualPaymentParts.interestPaid) +
-
992 ", got " + to_string(actualPaymentParts.valueChange));
+
950 auto const loanProperties = computeLoanProperties(
+
951 asset, loanPrincipal, loanInterestRate, paymentInterval, paymentsRemaining, managementFeeRate, loanScale);
+
952
+
953 auto const ret = tryOverpayment(
+
954 asset,
+
955 loanScale,
+
956 overpaymentComponents,
+
957 loanProperties.loanState,
+
958 loanProperties.periodicPayment,
+
959 periodicRate,
+
960 paymentsRemaining,
+
961 managementFeeRate,
+
962 env.journal);
+
963
+
964 BEAST_EXPECT(ret);
+
965
+
966 auto const& [actualPaymentParts, newLoanProperties] = *ret;
+
967 auto const& newState = newLoanProperties.loanState;
+
968
+
969 // =========== VALIDATE PAYMENT PARTS ===========
+
970
+
971 // Since there is loan management fee, the fee is charged against
+
972 // overpayment interest portion first, so interest paid remains 4.5
+
973 BEAST_EXPECTS(
+
974 (actualPaymentParts.interestPaid == Number{45, -1}),
+
975 " interestPaid mismatch: expected 4.5, got " + to_string(actualPaymentParts.interestPaid));
+
976
+
977 // With overpayment interest portion, value change should equal the
+
978 // interest decrease plus overpayment interest portion
+
979 BEAST_EXPECTS(
+
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));
+
983
+
984 // While there is no overpayment fee, fee paid should equal the
+
985 // management fee charged against the overpayment interest portion
+
986 BEAST_EXPECTS(
+
987 (actualPaymentParts.feePaid == Number{5, -1}),
+
988 " feePaid mismatch: expected 0.5, got " + to_string(actualPaymentParts.feePaid));
+
989
+
990 BEAST_EXPECTS(
+
991 actualPaymentParts.principalPaid == 45,
+
992 " principalPaid mismatch: expected 45, got `" + to_string(actualPaymentParts.principalPaid));
993
-
994 // While there is no overpayment fee, fee paid should equal the
-
995 // management fee charged against the overpayment interest portion
-
996 BEAST_EXPECTS(
-
997 (actualPaymentParts.feePaid == Number{5, -1}),
-
998 " feePaid mismatch: expected 0.5, got " + to_string(actualPaymentParts.feePaid));
-
999
-
1000 BEAST_EXPECTS(
-
1001 actualPaymentParts.principalPaid == 45,
-
1002 " principalPaid mismatch: expected 45, got `" + to_string(actualPaymentParts.principalPaid));
-
1003
-
1004 // =========== VALIDATE STATE CHANGES ===========
-
1005 BEAST_EXPECTS(
-
1006 actualPaymentParts.principalPaid ==
-
1007 loanProperites.loanState.principalOutstanding - newState.principalOutstanding,
-
1008 " principalPaid mismatch: expected " +
-
1009 to_string(loanProperites.loanState.principalOutstanding - newState.principalOutstanding) + ", got " +
-
1010 to_string(actualPaymentParts.principalPaid));
-
1011
-
1012 // Note that the management fee value change is not captured, as this
-
1013 // value is not needed to correctly update the Vault state.
-
1014 BEAST_EXPECTS(
-
1015 (newState.managementFeeDue - loanProperites.loanState.managementFeeDue == Number{-20592, -5}),
-
1016 " management fee change mismatch: expected " + to_string(Number{-20592, -5}) + ", got " +
-
1017 to_string(newState.managementFeeDue - loanProperites.loanState.managementFeeDue));
-
1018
-
1019 BEAST_EXPECTS(
-
1020 actualPaymentParts.valueChange - actualPaymentParts.interestPaid ==
-
1021 newState.interestDue - loanProperites.loanState.interestDue,
-
1022 " valueChange mismatch: expected " +
-
1023 to_string(newState.interestDue - loanProperites.loanState.interestDue) + ", got " +
-
1024 to_string(actualPaymentParts.valueChange - actualPaymentParts.interestPaid));
-
1025 }
+
994 // =========== VALIDATE STATE CHANGES ===========
+
995 BEAST_EXPECTS(
+
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));
+
1001
+
1002 // Note that the management fee value change is not captured, as this
+
1003 // value is not needed to correctly update the Vault state.
+
1004 BEAST_EXPECTS(
+
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));
+
1008
+
1009 BEAST_EXPECTS(
+
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));
+
1015 }
-
1026
-
1027 void
-
- -
1029 {
-
1030 testcase("tryOverpayment - Loan Interest, Fee, Overpayment Interest, Fee");
-
1031
-
1032 using namespace jtx;
-
1033 using namespace xrpl::detail;
-
1034
-
1035 Env env{*this};
-
1036 Account const issuer{"issuer"};
-
1037 PrettyAsset const asset = issuer["USD"];
-
1038 std::int32_t const loanScale = -5;
-
1039 TenthBips16 const managementFeeRate{10'000}; // 10%
-
1040 TenthBips32 const loanInterestRate{10'000}; // 10%
-
1041 Number const loanPrincipal{1'000};
-
1042 std::uint32_t const paymentInterval = 30 * 24 * 60 * 60;
-
1043 std::uint32_t const paymentsRemaining = 10;
-
1044 auto const periodicRate = loanPeriodicRate(loanInterestRate, paymentInterval);
-
1045
-
1046 ExtendedPaymentComponents const overpaymentComponents = computeOverpaymentComponents(
-
1047 asset,
-
1048 loanScale,
-
1049 Number{50, 0},
-
1050 TenthBips32(10'000), // 10% overpayment interest
-
1051 TenthBips32(10'000), // 10% overpayment fee
-
1052 managementFeeRate);
-
1053
-
1054 auto const loanProperites = computeLoanProperties(
-
1055 asset, loanPrincipal, loanInterestRate, paymentInterval, paymentsRemaining, managementFeeRate, loanScale);
-
1056
-
1057 Number const periodicPayment = loanProperites.periodicPayment;
-
1058
-
1059 auto const ret = tryOverpayment(
-
1060 asset,
-
1061 loanScale,
-
1062 overpaymentComponents,
-
1063 loanProperites.loanState,
-
1064 periodicPayment,
-
1065 periodicRate,
-
1066 paymentsRemaining,
-
1067 managementFeeRate,
-
1068 env.journal);
-
1069
-
1070 BEAST_EXPECT(ret);
-
1071
-
1072 auto const& [actualPaymentParts, newLoanProperties] = *ret;
-
1073 auto const& newState = newLoanProperties.loanState;
-
1074
-
1075 // =========== VALIDATE PAYMENT PARTS ===========
-
1076
-
1077 // Since there is loan management fee, the fee is charged against
-
1078 // overpayment interest portion first, so interest paid remains 4.5
-
1079 BEAST_EXPECTS(
-
1080 (actualPaymentParts.interestPaid == Number{45, -1}),
-
1081 " interestPaid mismatch: expected 4.5, got " + to_string(actualPaymentParts.interestPaid));
-
1082
-
1083 // With overpayment interest portion, value change should equal the
-
1084 // interest decrease plus overpayment interest portion
-
1085 BEAST_EXPECTS(
-
1086 (actualPaymentParts.valueChange == Number{-164737, -5} + actualPaymentParts.interestPaid),
-
1087 " valueChange mismatch: expected " + to_string(Number{-164737, -5} + actualPaymentParts.interestPaid) +
-
1088 ", got " + to_string(actualPaymentParts.valueChange));
+
1016
+
1017 void
+
+ +
1019 {
+
1020 testcase("tryOverpayment - Loan Interest, Fee, Overpayment Interest, Fee");
+
1021
+
1022 using namespace jtx;
+
1023 using namespace xrpl::detail;
+
1024
+
1025 Env env{*this};
+
1026 Account const issuer{"issuer"};
+
1027 PrettyAsset const asset = issuer["USD"];
+
1028 std::int32_t const loanScale = -5;
+
1029 TenthBips16 const managementFeeRate{10'000}; // 10%
+
1030 TenthBips32 const loanInterestRate{10'000}; // 10%
+
1031 Number const loanPrincipal{1'000};
+
1032 std::uint32_t const paymentInterval = 30 * 24 * 60 * 60;
+
1033 std::uint32_t const paymentsRemaining = 10;
+
1034 auto const periodicRate = loanPeriodicRate(loanInterestRate, paymentInterval);
+
1035
+
1036 auto const overpaymentComponents = computeOverpaymentComponents(
+
1037 asset,
+
1038 loanScale,
+
1039 Number{50, 0},
+
1040 TenthBips32(10'000), // 10% overpayment interest
+
1041 TenthBips32(10'000), // 10% overpayment fee
+
1042 managementFeeRate);
+
1043
+
1044 auto const loanProperties = computeLoanProperties(
+
1045 asset, loanPrincipal, loanInterestRate, paymentInterval, paymentsRemaining, managementFeeRate, loanScale);
+
1046
+
1047 auto const ret = tryOverpayment(
+
1048 asset,
+
1049 loanScale,
+
1050 overpaymentComponents,
+
1051 loanProperties.loanState,
+
1052 loanProperties.periodicPayment,
+
1053 periodicRate,
+
1054 paymentsRemaining,
+
1055 managementFeeRate,
+
1056 env.journal);
+
1057
+
1058 BEAST_EXPECT(ret);
+
1059
+
1060 auto const& [actualPaymentParts, newLoanProperties] = *ret;
+
1061 auto const& newState = newLoanProperties.loanState;
+
1062
+
1063 // =========== VALIDATE PAYMENT PARTS ===========
+
1064
+
1065 // Since there is loan management fee, the fee is charged against
+
1066 // overpayment interest portion first, so interest paid remains 4.5
+
1067 BEAST_EXPECTS(
+
1068 (actualPaymentParts.interestPaid == Number{45, -1}),
+
1069 " interestPaid mismatch: expected 4.5, got " + to_string(actualPaymentParts.interestPaid));
+
1070
+
1071 // With overpayment interest portion, value change should equal the
+
1072 // interest decrease plus overpayment interest portion
+
1073 BEAST_EXPECTS(
+
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));
+
1077
+
1078 // While there is no overpayment fee, fee paid should equal the
+
1079 // management fee charged against the overpayment interest portion
+
1080 BEAST_EXPECTS(
+
1081 (actualPaymentParts.feePaid == Number{55, -1}),
+
1082 " feePaid mismatch: expected 5.5, got " + to_string(actualPaymentParts.feePaid));
+
1083
+
1084 BEAST_EXPECTS(
+
1085 actualPaymentParts.principalPaid == 40,
+
1086 " principalPaid mismatch: expected 40, got `" + to_string(actualPaymentParts.principalPaid));
+
1087
+
1088 // =========== VALIDATE STATE CHANGES ===========
1089
-
1090 // While there is no overpayment fee, fee paid should equal the
-
1091 // management fee charged against the overpayment interest portion
-
1092 BEAST_EXPECTS(
-
1093 (actualPaymentParts.feePaid == Number{55, -1}),
-
1094 " feePaid mismatch: expected 5.5, got " + to_string(actualPaymentParts.feePaid));
-
1095
-
1096 BEAST_EXPECTS(
-
1097 actualPaymentParts.principalPaid == 40,
-
1098 " principalPaid mismatch: expected 40, got `" + to_string(actualPaymentParts.principalPaid));
-
1099
-
1100 // =========== VALIDATE STATE CHANGES ===========
-
1101
-
1102 BEAST_EXPECTS(
-
1103 actualPaymentParts.principalPaid ==
-
1104 loanProperites.loanState.principalOutstanding - newState.principalOutstanding,
-
1105 " principalPaid mismatch: expected " +
-
1106 to_string(loanProperites.loanState.principalOutstanding - newState.principalOutstanding) + ", got " +
-
1107 to_string(actualPaymentParts.principalPaid));
-
1108
-
1109 // Note that the management fee value change is not captured, as this
-
1110 // value is not needed to correctly update the Vault state.
-
1111 BEAST_EXPECTS(
-
1112 (newState.managementFeeDue - loanProperites.loanState.managementFeeDue == Number{-18304, -5}),
-
1113 " management fee change mismatch: expected " + to_string(Number{-18304, -5}) + ", got " +
-
1114 to_string(newState.managementFeeDue - loanProperites.loanState.managementFeeDue));
-
1115
-
1116 BEAST_EXPECTS(
-
1117 actualPaymentParts.valueChange - actualPaymentParts.interestPaid ==
-
1118 newState.interestDue - loanProperites.loanState.interestDue,
-
1119 " valueChange mismatch: expected " +
-
1120 to_string(newState.interestDue - loanProperites.loanState.interestDue) + ", got " +
-
1121 to_string(actualPaymentParts.valueChange - actualPaymentParts.interestPaid));
-
1122 }
+
1090 BEAST_EXPECTS(
+
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));
+
1096
+
1097 // Note that the management fee value change is not captured, as this
+
1098 // value is not needed to correctly update the Vault state.
+
1099 BEAST_EXPECTS(
+
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));
+
1103
+
1104 BEAST_EXPECTS(
+
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));
+
1110 }
+
+
1111
+
1112public:
+
1113 void
+ +
1133};
-
1123
-
1124public:
-
1125 void
- -
1145};
-
-
1146
-
1147BEAST_DEFINE_TESTSUITE(LendingHelpers, app, xrpl);
-
1148
-
1149} // namespace test
-
1150} // namespace xrpl
+
1135BEAST_DEFINE_TESTSUITE(LendingHelpers, app, xrpl);
+
1136
+
1137} // namespace test
+
1138} // namespace xrpl
A testsuite class.
Definition suite.h:51
testcase_t testcase
Memberspace for declaring test cases.
Definition suite.h:147
@@ -1273,17 +1261,17 @@ $(document).ready(function() { init_codefold(0); }); - + - + - - + + - -
void run() override
Runs the suite.
+ +
void run() override
Runs the suite.
Immutable cryptographic account descriptor.
Definition Account.h:19
@@ -1302,7 +1290,6 @@ $(document).ready(function() { init_codefold(0); });
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)
- diff --git a/Manifest_8cpp_source.html b/Manifest_8cpp_source.html index 0b5e958128..75cd1f2a88 100644 --- a/Manifest_8cpp_source.html +++ b/Manifest_8cpp_source.html @@ -577,107 +577,111 @@ $(document).ready(function() { init_codefold(0); });
459
460 auto masterKey = m.masterKey;
461 map_.emplace(std::move(masterKey), std::move(m));
- -
463 }
-
464
-
465 // An ephemeral key was revoked and superseded by a new key. This is
-
466 // expected, but should happen infrequently.
-
467 if (auto stream = j_.info())
-
468 logMftAct(stream, "AcceptedUpdate", m.masterKey, m.sequence, iter->second.sequence);
-
469
-
470 signingToMasterKeys_.erase(*iter->second.signingKey);
-
471
-
472 if (!revoked)
- -
474
-
475 iter->second = std::move(m);
-
476
-
477 // Something has changed. Keep track of it.
-
478 seq_++;
-
479
- -
481}
+
462
+
463 // Something has changed. Keep track of it.
+
464 seq_++;
+
465
+ +
467 }
+
468
+
469 // An ephemeral key was revoked and superseded by a new key. This is
+
470 // expected, but should happen infrequently.
+
471 if (auto stream = j_.info())
+
472 logMftAct(stream, "AcceptedUpdate", m.masterKey, m.sequence, iter->second.sequence);
+
473
+
474 signingToMasterKeys_.erase(*iter->second.signingKey);
+
475
+
476 if (!revoked)
+ +
478
+
479 iter->second = std::move(m);
+
480
+
481 // Something has changed. Keep track of it.
+
482 seq_++;
+
483
+ +
485}
-
482
-
483void
-
- -
485{
-
486 auto db = dbCon.checkoutDb();
-
487 xrpl::getManifests(*db, dbTable, *this, j_);
-
488}
+
486
+
487void
+
+ +
489{
+
490 auto db = dbCon.checkoutDb();
+
491 xrpl::getManifests(*db, dbTable, *this, j_);
+
492}
-
489
-
490bool
-
- -
492 DatabaseCon& dbCon,
-
493 std::string const& dbTable,
-
494 std::string const& configManifest,
-
495 std::vector<std::string> const& configRevocation)
-
496{
-
497 load(dbCon, dbTable);
-
498
-
499 if (!configManifest.empty())
-
500 {
-
501 auto mo = deserializeManifest(base64_decode(configManifest));
-
502 if (!mo)
-
503 {
-
504 JLOG(j_.error()) << "Malformed validator_token in config";
-
505 return false;
-
506 }
-
507
-
508 if (mo->revoked())
-
509 {
-
510 JLOG(j_.warn()) << "Configured manifest revokes public key";
-
511 }
-
512
-
513 if (applyManifest(std::move(*mo)) == ManifestDisposition::invalid)
-
514 {
-
515 JLOG(j_.error()) << "Manifest in config was rejected";
-
516 return false;
-
517 }
-
518 }
-
519
-
520 if (!configRevocation.empty())
-
521 {
-
522 std::string revocationStr;
-
523 revocationStr.reserve(std::accumulate(
-
524 configRevocation.cbegin(),
-
525 configRevocation.cend(),
-
526 std::size_t(0),
-
527 [](std::size_t init, std::string const& s) { return init + s.size(); }));
-
528
-
529 for (auto const& line : configRevocation)
-
530 revocationStr += boost::algorithm::trim_copy(line);
-
531
-
532 auto mo = deserializeManifest(base64_decode(revocationStr));
-
533
-
534 if (!mo || !mo->revoked() || applyManifest(std::move(*mo)) == ManifestDisposition::invalid)
-
535 {
-
536 JLOG(j_.error()) << "Invalid validator key revocation in config";
-
537 return false;
-
538 }
-
539 }
-
540
-
541 return true;
-
542}
+
493
+
494bool
+
+ +
496 DatabaseCon& dbCon,
+
497 std::string const& dbTable,
+
498 std::string const& configManifest,
+
499 std::vector<std::string> const& configRevocation)
+
500{
+
501 load(dbCon, dbTable);
+
502
+
503 if (!configManifest.empty())
+
504 {
+
505 auto mo = deserializeManifest(base64_decode(configManifest));
+
506 if (!mo)
+
507 {
+
508 JLOG(j_.error()) << "Malformed validator_token in config";
+
509 return false;
+
510 }
+
511
+
512 if (mo->revoked())
+
513 {
+
514 JLOG(j_.warn()) << "Configured manifest revokes public key";
+
515 }
+
516
+
517 if (applyManifest(std::move(*mo)) == ManifestDisposition::invalid)
+
518 {
+
519 JLOG(j_.error()) << "Manifest in config was rejected";
+
520 return false;
+
521 }
+
522 }
+
523
+
524 if (!configRevocation.empty())
+
525 {
+
526 std::string revocationStr;
+
527 revocationStr.reserve(std::accumulate(
+
528 configRevocation.cbegin(),
+
529 configRevocation.cend(),
+
530 std::size_t(0),
+
531 [](std::size_t init, std::string const& s) { return init + s.size(); }));
+
532
+
533 for (auto const& line : configRevocation)
+
534 revocationStr += boost::algorithm::trim_copy(line);
+
535
+
536 auto mo = deserializeManifest(base64_decode(revocationStr));
+
537
+
538 if (!mo || !mo->revoked() || applyManifest(std::move(*mo)) == ManifestDisposition::invalid)
+
539 {
+
540 JLOG(j_.error()) << "Invalid validator key revocation in config";
+
541 return false;
+
542 }
+
543 }
+
544
+
545 return true;
+
546}
-
543
-
544void
-
- -
546 DatabaseCon& dbCon,
-
547 std::string const& dbTable,
-
548 std::function<bool(PublicKey const&)> const& isTrusted)
-
549{
- -
551 auto db = dbCon.checkoutDb();
-
552
-
553 saveManifests(*db, dbTable, isTrusted, map_, j_);
-
554}
+
547
+
548void
+
+ +
550 DatabaseCon& dbCon,
+
551 std::string const& dbTable,
+
552 std::function<bool(PublicKey const&)> const& isTrusted)
+
553{
+ +
555 auto db = dbCon.checkoutDb();
+
556
+
557 saveManifests(*db, dbTable, isTrusted, map_, j_);
+
558}
-
555} // namespace xrpl
+
559} // namespace xrpl
T accumulate(T... args)
T assign(T... args)
@@ -695,7 +699,7 @@ $(document).ready(function() { init_codefold(0); });
LockedSociSession checkoutDb()
std::atomic< std::uint32_t > seq_
Definition Manifest.h:235
std::shared_mutex mutex_
Definition Manifest.h:227
-
bool load(DatabaseCon &dbCon, std::string const &dbTable, std::string const &configManifest, std::vector< std::string > const &configRevocation)
Populate manifest cache with manifests in database and config.
Definition Manifest.cpp:491
+
bool load(DatabaseCon &dbCon, std::string const &dbTable, std::string const &configManifest, std::vector< std::string > const &configRevocation)
Populate manifest cache with manifests in database and config.
Definition Manifest.cpp:495
std::optional< PublicKey > getSigningKey(PublicKey const &pk) const
Returns master key's current signing key.
Definition Manifest.cpp:273
ManifestDisposition applyManifest(Manifest m)
Add manifest to cache.
Definition Manifest.cpp:344
std::optional< std::string > getDomain(PublicKey const &pk) const
Returns domain claimed by a given public key.
Definition Manifest.cpp:308
@@ -704,7 +708,7 @@ $(document).ready(function() { init_codefold(0); });
std::optional< std::string > getManifest(PublicKey const &pk) const
Returns manifest corresponding to a given public key.
Definition Manifest.cpp:320
hash_map< PublicKey, Manifest > map_
Active manifests stored by master public key.
Definition Manifest.h:230
std::optional< std::uint32_t > getSequence(PublicKey const &pk) const
Returns master key's current manifest sequence.
Definition Manifest.cpp:296
-
void save(DatabaseCon &dbCon, std::string const &dbTable, std::function< bool(PublicKey const &)> const &isTrusted)
Save cached manifests to database.
Definition Manifest.cpp:545
+
void save(DatabaseCon &dbCon, std::string const &dbTable, std::function< bool(PublicKey const &)> const &isTrusted)
Save cached manifests to database.
Definition Manifest.cpp:549
beast::Journal j_
Definition Manifest.h:226
bool revoked(PublicKey const &pk) const
Returns true if master key has been revoked in a manifest.
Definition Manifest.cpp:332
A public key.
Definition PublicKey.h:42
diff --git a/Manifest_8h_source.html b/Manifest_8h_source.html index a8168f34d2..91b5bba39d 100644 --- a/Manifest_8h_source.html +++ b/Manifest_8h_source.html @@ -395,7 +395,7 @@ $(document).ready(function() { init_codefold(0); });
std::atomic< std::uint32_t > seq_
Definition Manifest.h:235
std::shared_mutex mutex_
Definition Manifest.h:227
void for_each_manifest(PreFun &&pf, EachFun &&f) const
Invokes the callback once for every populated manifest.
Definition Manifest.h:416
-
bool load(DatabaseCon &dbCon, std::string const &dbTable, std::string const &configManifest, std::vector< std::string > const &configRevocation)
Populate manifest cache with manifests in database and config.
Definition Manifest.cpp:491
+
bool load(DatabaseCon &dbCon, std::string const &dbTable, std::string const &configManifest, std::vector< std::string > const &configRevocation)
Populate manifest cache with manifests in database and config.
Definition Manifest.cpp:495
std::optional< PublicKey > getSigningKey(PublicKey const &pk) const
Returns master key's current signing key.
Definition Manifest.cpp:273
ManifestDisposition applyManifest(Manifest m)
Add manifest to cache.
Definition Manifest.cpp:344
ManifestCache(beast::Journal j=beast::Journal(beast::Journal::getNullSink()))
Definition Manifest.h:238
@@ -406,7 +406,7 @@ $(document).ready(function() { init_codefold(0); });
std::optional< std::string > getManifest(PublicKey const &pk) const
Returns manifest corresponding to a given public key.
Definition Manifest.cpp:320
hash_map< PublicKey, Manifest > map_
Active manifests stored by master public key.
Definition Manifest.h:230
std::optional< std::uint32_t > getSequence(PublicKey const &pk) const
Returns master key's current manifest sequence.
Definition Manifest.cpp:296
-
void save(DatabaseCon &dbCon, std::string const &dbTable, std::function< bool(PublicKey const &)> const &isTrusted)
Save cached manifests to database.
Definition Manifest.cpp:545
+
void save(DatabaseCon &dbCon, std::string const &dbTable, std::function< bool(PublicKey const &)> const &isTrusted)
Save cached manifests to database.
Definition Manifest.cpp:549
beast::Journal j_
Definition Manifest.h:226
A public key.
Definition PublicKey.h:42
A secret key.
Definition SecretKey.h:18
diff --git a/Manifest__test_8cpp_source.html b/Manifest__test_8cpp_source.html index 334035a027..85a3e670e3 100644 --- a/Manifest__test_8cpp_source.html +++ b/Manifest__test_8cpp_source.html @@ -950,52 +950,57 @@ $(document).ready(function() { init_codefold(0); });
827
828 // applyManifest should accept new manifests with
829 // higher sequence numbers
-
830 BEAST_EXPECT(cache.applyManifest(clone(s_a0)) == ManifestDisposition::accepted);
-
831 BEAST_EXPECT(cache.applyManifest(clone(s_a0)) == ManifestDisposition::stale);
-
832
-
833 BEAST_EXPECT(cache.applyManifest(clone(s_a1)) == ManifestDisposition::accepted);
-
834 BEAST_EXPECT(cache.applyManifest(clone(s_a1)) == ManifestDisposition::stale);
+
830 auto const seq0 = cache.sequence();
+
831 BEAST_EXPECT(cache.applyManifest(clone(s_a0)) == ManifestDisposition::accepted);
+
832 BEAST_EXPECT(cache.sequence() > seq0);
+
833
+
834 auto const seq1 = cache.sequence();
835 BEAST_EXPECT(cache.applyManifest(clone(s_a0)) == ManifestDisposition::stale);
-
836
-
837 BEAST_EXPECT(cache.applyManifest(clone(s_a2)) == ManifestDisposition::badEphemeralKey);
-
838
-
839 // applyManifest should accept manifests with max sequence numbers
-
840 // that revoke the master public key
-
841 BEAST_EXPECT(!cache.revoked(pk_a));
-
842 BEAST_EXPECT(s_aMax.revoked());
-
843 BEAST_EXPECT(cache.applyManifest(clone(s_aMax)) == ManifestDisposition::accepted);
-
844 BEAST_EXPECT(cache.applyManifest(clone(s_aMax)) == ManifestDisposition::stale);
-
845 BEAST_EXPECT(cache.applyManifest(clone(s_a1)) == ManifestDisposition::stale);
-
846 BEAST_EXPECT(cache.applyManifest(clone(s_a0)) == ManifestDisposition::stale);
-
847 BEAST_EXPECT(cache.revoked(pk_a));
-
848
-
849 // applyManifest should reject manifests with invalid signatures
-
850 BEAST_EXPECT(cache.applyManifest(clone(s_b0)) == ManifestDisposition::accepted);
-
851 BEAST_EXPECT(cache.applyManifest(clone(s_b0)) == ManifestDisposition::stale);
-
852 BEAST_EXPECT(!deserializeManifest(fake));
-
853 BEAST_EXPECT(cache.applyManifest(clone(s_b1)) == ManifestDisposition::invalid);
-
854 BEAST_EXPECT(cache.applyManifest(clone(s_b2)) == ManifestDisposition::accepted);
-
855
-
856 auto const s_c0 = makeManifest(kp_b2.second, KeyType::ed25519, randomSecretKey(), KeyType::ed25519, 47);
-
857 BEAST_EXPECT(cache.applyManifest(clone(s_c0)) == ManifestDisposition::badMasterKey);
-
858 }
-
859
-
860 testLoadStore(cache);
- -
862 testGetKeys();
- - - - -
867 }
+
836 BEAST_EXPECT(cache.sequence() == seq1);
+
837
+
838 BEAST_EXPECT(cache.applyManifest(clone(s_a1)) == ManifestDisposition::accepted);
+
839 BEAST_EXPECT(cache.applyManifest(clone(s_a1)) == ManifestDisposition::stale);
+
840 BEAST_EXPECT(cache.applyManifest(clone(s_a0)) == ManifestDisposition::stale);
+
841
+
842 BEAST_EXPECT(cache.applyManifest(clone(s_a2)) == ManifestDisposition::badEphemeralKey);
+
843
+
844 // applyManifest should accept manifests with max sequence numbers
+
845 // that revoke the master public key
+
846 BEAST_EXPECT(!cache.revoked(pk_a));
+
847 BEAST_EXPECT(s_aMax.revoked());
+
848 BEAST_EXPECT(cache.applyManifest(clone(s_aMax)) == ManifestDisposition::accepted);
+
849 BEAST_EXPECT(cache.applyManifest(clone(s_aMax)) == ManifestDisposition::stale);
+
850 BEAST_EXPECT(cache.applyManifest(clone(s_a1)) == ManifestDisposition::stale);
+
851 BEAST_EXPECT(cache.applyManifest(clone(s_a0)) == ManifestDisposition::stale);
+
852 BEAST_EXPECT(cache.revoked(pk_a));
+
853
+
854 // applyManifest should reject manifests with invalid signatures
+
855 BEAST_EXPECT(cache.applyManifest(clone(s_b0)) == ManifestDisposition::accepted);
+
856 BEAST_EXPECT(cache.applyManifest(clone(s_b0)) == ManifestDisposition::stale);
+
857 BEAST_EXPECT(!deserializeManifest(fake));
+
858 BEAST_EXPECT(cache.applyManifest(clone(s_b1)) == ManifestDisposition::invalid);
+
859 BEAST_EXPECT(cache.applyManifest(clone(s_b2)) == ManifestDisposition::accepted);
+
860
+
861 auto const s_c0 = makeManifest(kp_b2.second, KeyType::ed25519, randomSecretKey(), KeyType::ed25519, 47);
+
862 BEAST_EXPECT(cache.applyManifest(clone(s_c0)) == ManifestDisposition::badMasterKey);
+
863 }
+
864
+
865 testLoadStore(cache);
+ +
867 testGetKeys();
+ + + + +
872 }
-
868};
+
873};
-
869
-
870BEAST_DEFINE_TESTSUITE(Manifest, app, xrpl);
-
871
-
872} // namespace test
-
873} // namespace xrpl
+
874
+
875BEAST_DEFINE_TESTSUITE(Manifest, app, xrpl);
+
876
+
877} // namespace test
+
878} // namespace xrpl
T begin(T... args)
@@ -1003,11 +1008,12 @@ $(document).ready(function() { init_codefold(0); });
testcase_t testcase
Memberspace for declaring test cases.
Definition suite.h:147
void fail(String const &reason, char const *file, int line)
Record a failure.
Definition suite.h:516
Remembers manifests with the highest sequence number.
Definition Manifest.h:224
-
bool load(DatabaseCon &dbCon, std::string const &dbTable, std::string const &configManifest, std::vector< std::string > const &configRevocation)
Populate manifest cache with manifests in database and config.
Definition Manifest.cpp:491
+
bool load(DatabaseCon &dbCon, std::string const &dbTable, std::string const &configManifest, std::vector< std::string > const &configRevocation)
Populate manifest cache with manifests in database and config.
Definition Manifest.cpp:495
std::optional< PublicKey > getSigningKey(PublicKey const &pk) const
Returns master key's current signing key.
Definition Manifest.cpp:273
ManifestDisposition applyManifest(Manifest m)
Add manifest to cache.
Definition Manifest.cpp:344
PublicKey getMasterKey(PublicKey const &pk) const
Returns ephemeral signing key's master public key.
Definition Manifest.cpp:285
-
void save(DatabaseCon &dbCon, std::string const &dbTable, std::function< bool(PublicKey const &)> const &isTrusted)
Save cached manifests to database.
Definition Manifest.cpp:545
+
std::uint32_t sequence() const
A monotonically increasing number used to detect new manifests.
Definition Manifest.h:244
+
void save(DatabaseCon &dbCon, std::string const &dbTable, std::function< bool(PublicKey const &)> const &isTrusted)
Save cached manifests to database.
Definition Manifest.cpp:549
bool revoked(PublicKey const &pk) const
Returns true if master key has been revoked in a manifest.
Definition Manifest.cpp:332
A public key.
Definition PublicKey.h:42
diff --git a/classxrpl_1_1ManifestCache.html b/classxrpl_1_1ManifestCache.html index a0c8371dac..2fe6ccbb90 100644 --- a/classxrpl_1_1ManifestCache.html +++ b/classxrpl_1_1ManifestCache.html @@ -484,7 +484,7 @@ Private Attributes
Thread Safety

May be called concurrently

-

Definition at line 491 of file Manifest.cpp.

+

Definition at line 495 of file Manifest.cpp.

@@ -525,7 +525,7 @@ Private Attributes
Thread Safety

May be called concurrently

-

Definition at line 484 of file Manifest.cpp.

+

Definition at line 488 of file Manifest.cpp.

@@ -572,7 +572,7 @@ Private Attributes
Thread Safety

May be called concurrently

-

Definition at line 545 of file Manifest.cpp.

+

Definition at line 549 of file Manifest.cpp.

diff --git a/classxrpl_1_1test_1_1LendingHelpers__test.html b/classxrpl_1_1test_1_1LendingHelpers__test.html index 155e8e7751..19c29ba2ad 100644 --- a/classxrpl_1_1test_1_1LendingHelpers__test.html +++ b/classxrpl_1_1test_1_1LendingHelpers__test.html @@ -565,7 +565,7 @@ Private Attributes
-

Definition at line 657 of file LendingHelpers_test.cpp.

+

Definition at line 655 of file LendingHelpers_test.cpp.

@@ -592,7 +592,7 @@ Private Attributes
-

Definition at line 743 of file LendingHelpers_test.cpp.

+

Definition at line 739 of file LendingHelpers_test.cpp.

@@ -619,7 +619,7 @@ Private Attributes
-

Definition at line 834 of file LendingHelpers_test.cpp.

+

Definition at line 828 of file LendingHelpers_test.cpp.

@@ -646,7 +646,7 @@ Private Attributes
-

Definition at line 930 of file LendingHelpers_test.cpp.

+

Definition at line 922 of file LendingHelpers_test.cpp.

@@ -673,7 +673,7 @@ Private Attributes
-

Definition at line 1028 of file LendingHelpers_test.cpp.

+

Definition at line 1018 of file LendingHelpers_test.cpp.

@@ -704,7 +704,7 @@ Private Attributes

Implements beast::unit_test::suite.

-

Definition at line 1126 of file LendingHelpers_test.cpp.

+

Definition at line 1114 of file LendingHelpers_test.cpp.