54 testcase(
"cash-basis: LoanSet origination");
61 .vaultDeposit = 100'000,
68 Number const principalRequest{10'000};
78 Env env(*
this, features);
81 Account const borrower{
"borrower"};
82 env.
fund(
XRP(1'000'000), lender, borrower);
89 BEAST_EXPECT(vaultBefore && brokerBefore);
90 Number const assetsTotalBefore = vaultBefore->at(sfAssetsTotal);
91 Number const debtTotalBefore = brokerBefore->at(sfDebtTotal);
93 auto const loanSequence = brokerBefore->at(sfLoanSequence);
94 auto const loanKeylet =
97 env(
set(borrower, broker.
brokerID, xrpAsset(principalRequest).value()),
98 kCounterparty(lender),
99 kInterestRate(interestRate),
100 kPaymentTotal(paymentTotal),
101 kPaymentInterval(paymentInterval),
102 Sig(sfCounterpartySignature, lender),
107 auto const loanSle = env.
le(loanKeylet);
108 BEAST_EXPECT(loanSle);
109 Number const principalOutstanding = loanSle->at(sfPrincipalOutstanding);
110 Number const totalValueOutstanding = loanSle->at(sfTotalValueOutstanding);
111 Number const interestDue = totalValueOutstanding - principalOutstanding;
113 BEAST_EXPECT(principalOutstanding == xrpAsset(principalRequest).value());
117 BEAST_EXPECT(vaultAfter && brokerAfter);
118 Number const assetsTotalDelta =
119 Number(vaultAfter->at(sfAssetsTotal)) - assetsTotalBefore;
120 Number const debtTotalDelta =
Number(brokerAfter->at(sfDebtTotal)) - debtTotalBefore;
123 assetsTotalDelta, debtTotalDelta, interestDue, principalOutstanding);
127 Number principalOutstandingCash{};
129 auto const [assetsTotalDelta, debtTotalDelta, interestDue, principalOutstanding] =
130 runOrigination(
all_ | featureLendingProtocolV1_1);
131 interestDueCash = interestDue;
132 principalOutstandingCash = principalOutstanding;
136 "cash-basis origination must not change AssetsTotal; delta=" +
139 debtTotalDelta == principalOutstanding,
140 "cash-basis origination must add principal-only to DebtTotal; delta=" +
145 auto const [assetsTotalDelta, debtTotalDelta, interestDue, principalOutstanding] =
146 runOrigination(
all_);
149 assetsTotalDelta == interestDue,
150 "instant-recognition origination must add interestDue to AssetsTotal; delta=" +
153 debtTotalDelta == principalOutstanding + interestDue,
154 "instant-recognition origination must add principal+interest to DebtTotal; delta=" +
161 Env env(*
this, features);
163 Account const lender{
"lender"};
164 Account const borrower{
"borrower"};
165 env.
fund(
XRP(1'000'000), lender, borrower);
171 BEAST_EXPECT(vaultSle);
172 Number const assetsTotalBefore = vaultSle->at(sfAssetsTotal);
174 Vault const vault{env};
175 auto tx = vault.set({.owner = lender, .
id = broker.
vaultID});
176 tx[sfAssetsMaximum] = assetsTotalBefore + slack;
180 env(
set(borrower, broker.
brokerID, xrpAsset(principalRequest).value()),
181 kCounterparty(lender),
182 kInterestRate(interestRate),
183 kPaymentTotal(paymentTotal),
184 kPaymentInterval(paymentInterval),
185 Sig(sfCounterpartySignature, lender),
192 Env env(*
this, features);
194 Account const lender{
"lender"};
195 Account const borrower{
"borrower"};
196 env.
fund(
XRP(1'000'000), lender, borrower);
207 env(
set(borrower, broker.
brokerID, xrpAsset(principalRequest).value()),
208 kCounterparty(lender),
209 kInterestRate(interestRate),
210 kPaymentTotal(paymentTotal),
211 kPaymentInterval(paymentInterval),
212 Sig(sfCounterpartySignature, lender),
218 Number const oneDrop = xrpAsset(1).value();
221 "instant-recognition: LoanSet AssetsMaximum guard checks interestDue headroom");
230 testcase(
"cash-basis: LoanSet AssetsMaximum guard ignores interestDue headroom");
233 runVaultGuard(
all_ | featureLendingProtocolV1_1, oneDrop,
tesSUCCESS);
242 for (
auto const cashBasis : {
true,
false})
246 (cashBasis ?
"cash-basis)" :
"instant-recognition)"));
247 auto const features = cashBasis ?
all_ | featureLendingProtocolV1_1 :
all_;
248 Number const newDebtTotal =
249 principalOutstandingCash + (cashBasis ?
Number{} : interestDueCash);
251 runBrokerGuard(features, newDebtTotal,
tesSUCCESS);
266 using namespace loan;
267 using namespace std::chrono_literals;
272 .vaultDeposit = 1'000'000,
279 Number const principalRequest{12'000};
300 Env env(*
this, features);
302 Account const lender{
"lender"};
303 Account const borrower{
"borrower"};
304 env.
fund(
XRP(10'000'000), lender, borrower);
312 .principalRequest = principalRequest,
313 .interest = interestRate,
314 .payTotal = paymentTotal,
315 .payInterval = paymentInterval,
316 .gracePd = gracePeriod,
317 .flags = loanSetFlags,
321 BEAST_EXPECT(brokerBeforeLoan);
322 auto const loanSequence = brokerBeforeLoan->at(sfLoanSequence);
323 auto const loanKeylet =
326 env(loanParams(env, broker));
335 auto const loanBefore = env.
le(loanKeylet);
336 BEAST_EXPECT(vaultBefore && brokerBefore && loanBefore);
338 Number const principalBefore = loanBefore->at(sfPrincipalOutstanding);
339 Number const totalValueBefore = loanBefore->at(sfTotalValueOutstanding);
340 Number const assetsTotalBefore = vaultBefore->at(sfAssetsTotal);
341 Number const debtTotalBefore = brokerBefore->at(sfDebtTotal);
343 STAmount const amount = paymentAmount(state);
349 auto const loanAfter = env.
le(loanKeylet);
350 BEAST_EXPECT(vaultAfter && brokerAfter && loanAfter);
352 Number const principalAfter = loanAfter->at(sfPrincipalOutstanding);
353 Number const totalValueAfter = loanAfter->at(sfTotalValueOutstanding);
354 Number const assetsTotalAfter = vaultAfter->at(sfAssetsTotal);
355 Number const debtTotalAfter = brokerAfter->at(sfDebtTotal);
357 return PaymentDeltas{
358 .principalPaid = principalBefore - principalAfter,
359 .assetsTotalDelta = assetsTotalAfter - assetsTotalBefore,
360 .debtTotalDelta = debtTotalAfter - debtTotalBefore,
361 .totalValueDelta = totalValueAfter - totalValueBefore};
368 PaymentDeltas
const& off,
369 PaymentDeltas
const& on) {
370 testcase(
"cash-basis: LoanPay " + label);
375 off.principalPaid == on.principalPaid,
376 "principalPaid must be amendment-independent; off=" +
to_string(off.principalPaid) +
384 off.debtTotalDelta == off.totalValueDelta,
385 "instant-recognition DebtTotal delta must mirror TotalValueOutstanding delta; "
388 " totalValueDelta=" +
to_string(off.totalValueDelta));
395 Number const interestPaid =
396 off.assetsTotalDelta - off.debtTotalDelta - off.principalPaid;
399 "derived interestPaid must be non-negative: " +
to_string(interestPaid));
402 on.assetsTotalDelta == interestPaid,
403 "cash-basis AssetsTotal delta must equal interestPaid; delta=" +
406 on.debtTotalDelta == -on.principalPaid,
407 "cash-basis DebtTotal delta must equal -principalPaid; delta=" +
413 auto const noAdvance = [](
Env& env, Tp
const&) { env.
close(); };
414 auto const regularAmount = [&](
LoanState const& state) {
421 auto const off = runPayment(
all_, 0, 0, noAdvance, regularAmount);
423 runPayment(
all_ | featureLendingProtocolV1_1, 0, 0, noAdvance, regularAmount);
430 "regular on-time payment must not change AssetsTotal under instant recognition; "
434 checkScenario(
"regular payment", off, on);
439 auto const advancePastDue = [&](
Env& env, Tp
const& startDate) {
442 auto const lateAmount = [&](
LoanState const& state) {
449 auto const off = runPayment(
all_, 0, tfLoanLatePayment, advancePastDue, lateAmount);
450 auto const on = runPayment(
451 all_ | featureLendingProtocolV1_1,
457 checkScenario(
"late payment", off, on);
462 auto const noAdvance = [](
Env& env, Tp
const&) { env.
close(); };
463 auto const overpayAmount = [&](
LoanState const& state) {
469 xrpAsset(2'000).value()};
473 runPayment(
all_, tfLoanOverpayment, tfLoanOverpayment, noAdvance, overpayAmount);
474 auto const on = runPayment(
475 all_ | featureLendingProtocolV1_1,
481 checkScenario(
"overpayment", off, on);
486 auto const noAdvance = [](
Env& env, Tp
const&) { env.
close(); };
487 auto const fullAmount = [&](
LoanState const&) {
491 return STAmount{xrpAsset, xrpAsset(principalRequest).value() *
Number{2}};
494 auto const off = runPayment(
all_, 0, tfLoanFullPayment, noAdvance, fullAmount);
495 auto const on = runPayment(
496 all_ | featureLendingProtocolV1_1, 0, tfLoanFullPayment, noAdvance, fullAmount);
498 checkScenario(
"full payment", off, on);
511 using namespace loan;
515 .vaultDeposit = 1'000'000,
523 [&](
FeatureBitset features,
TER expectedOverCapSet,
bool native,
bool vaultPrivate) {
524 bool const fix340Enabled = features[fixCleanup3_4_0];
526 std::string(
"cash-basis: VaultSet while AssetsTotal exceeds AssetsMaximum") +
527 (native ?
" XRP" :
" IOU") + (vaultPrivate ?
" private" :
"") +
528 (fix340Enabled ?
" (fixCleanup3_4_0)" :
" (pre-fix)"));
530 Account const issuer{
"issuer"};
531 Account const lender{
"lender"};
532 Account const borrower{
"borrower"};
533 Env env(*
this, features);
542 env.
fund(
XRP(10'000'000), lender, borrower);
552 BEAST_EXPECT(vaultBefore);
557 Number const slack{1, -
static_cast<int>(vaultBefore->at(sfScale))};
558 Number const assetsMaximum =
Number(vaultBefore->at(sfAssetsTotal)) + slack;
560 Vault const vault{env};
562 auto tx = vault.set({.owner = lender, .
id = broker.
vaultID});
563 tx[sfAssetsMaximum] = assetsMaximum;
569 auto tx = vault.set({.owner = lender, .
id = broker.
vaultID});
576 BEAST_EXPECT(brokerBeforeLoan);
583 .principalRequest = 12'000,
589 env(loanParams(env, broker));
593 BEAST_EXPECT(vaultAfterLoan);
594 BEAST_EXPECT(vaultAfterLoan->at(sfAssetsTotal) <= assetsMaximum);
605 BEAST_EXPECT(vaultAboveMaximum);
606 BEAST_EXPECT(vaultAboveMaximum->at(sfAssetsTotal) > assetsMaximum);
607 BEAST_EXPECT(vaultAboveMaximum->at(sfAssetsMaximum) == assetsMaximum);
610 auto tx = vault.set({.owner = lender, .
id = broker.
vaultID});
612 env(tx,
Ter(expectedOverCapSet));
619 {.issuer = lender, .credType =
"credential"}};
622 auto tx = vault.set({.owner = lender, .
id = broker.
vaultID});
624 env(tx,
Ter(expectedOverCapSet));
632 auto tx = vault.set({.owner = lender, .
id = broker.
vaultID});
633 tx[sfAssetsMaximum] = assetsMaximum;
639 auto tx = vault.set({.owner = lender, .
id = broker.
vaultID});
640 tx[sfAssetsMaximum] =
Number{0};
658 testcase(
"cash-basis: LoanSet after interest pushes AssetsTotal past AssetsMaximum");
661 using namespace loan;
665 .vaultDeposit = 1'000'000,
672 Account const lender{
"lender"};
673 Account const borrower{
"borrower"};
674 Env env(*
this,
all_ | featureLendingProtocolV1_1);
675 env.
fund(
XRP(10'000'000), lender, borrower);
680 BEAST_EXPECT(vaultBefore);
681 Number const assetsMaximum =
Number(vaultBefore->at(sfAssetsTotal));
683 Vault const vault{env};
685 auto tx = vault.set({.owner = lender, .
id = broker.
vaultID});
686 tx[sfAssetsMaximum] = assetsMaximum;
692 BEAST_EXPECT(brokerBeforeLoan);
696 Number const firstPrincipal = xrpAsset(12'000).value();
697 env(
set(borrower, broker.
brokerID, firstPrincipal),
698 kCounterparty(lender),
701 kPaymentInterval(600),
702 Sig(sfCounterpartySignature, lender),
708 BEAST_EXPECT(vaultAfterFirst);
709 BEAST_EXPECT(vaultAfterFirst->at(sfAssetsTotal) == assetsMaximum);
710 BEAST_EXPECT(vaultAfterFirst->at(sfAssetsAvailable) == assetsMaximum - firstPrincipal);
721 BEAST_EXPECT(vaultAfterPay);
722 BEAST_EXPECT(vaultAfterPay->at(sfAssetsTotal) > assetsMaximum);
723 BEAST_EXPECT(vaultAfterPay->at(sfAssetsAvailable) >
beast::kZero);
726 BEAST_EXPECT(brokerAfterPay);
730 Number const secondPrincipal = xrpAsset(1'000).value();
731 env(
set(borrower, broker.
brokerID, secondPrincipal),
732 kCounterparty(lender),
735 kPaymentInterval(600),
736 Sig(sfCounterpartySignature, lender),
742 auto const secondLoan = env.
le(secondLoanKeylet);
743 BEAST_EXPECT(vaultAfterSecond && secondLoan);
744 BEAST_EXPECT(vaultAfterSecond->at(sfAssetsTotal) == vaultAfterPay->at(sfAssetsTotal));
745 BEAST_EXPECT(secondLoan->at(sfPrincipalOutstanding) == secondPrincipal);
753 using namespace loan;
754 using namespace std::chrono_literals;
758 .vaultDeposit = 1'000'000,
761 .coverDeposit = 5'000,
765 Number const principalRequest{10'000};
771 auto setupLoan = [&](
Env& env) {
772 Account const lender{
"lender"};
773 Account const borrower{
"borrower"};
774 env.fund(
XRP(10'000'000), lender, borrower);
782 .principalRequest = principalRequest,
783 .interest = interestRate,
784 .payTotal = paymentTotal,
785 .payInterval = paymentInterval,
786 .gracePd = gracePeriod,
789 auto const brokerBeforeLoan = env.le(broker.
brokerKeylet());
790 BEAST_EXPECT(brokerBeforeLoan);
791 auto const loanSequence = brokerBeforeLoan->at(sfLoanSequence);
792 auto const loanKeylet =
795 env(loanParams(env, broker));
803 Env env(*
this, features);
804 auto const [broker, loanKeylet, lender, borrower] = setupLoan(env);
806 auto const loanBefore = env.
le(loanKeylet);
807 BEAST_EXPECT(loanBefore);
808 Number const principalOutstanding = loanBefore->at(sfPrincipalOutstanding);
809 Number const totalValueOutstanding = loanBefore->at(sfTotalValueOutstanding);
810 Number const managementFeeOutstanding = loanBefore->at(sfManagementFeeOutstanding);
812 Number const expectedExposure =
813 env.
current()->rules().enabled(featureLendingProtocolV1_1)
814 ? principalOutstanding
815 : totalValueOutstanding - managementFeeOutstanding;
817 auto const vaultBeforeImpair = env.
le(broker.vaultKeylet());
818 BEAST_EXPECT(vaultBeforeImpair);
819 Number const lossBefore = vaultBeforeImpair->at(sfLossUnrealized);
822 env(manage(lender, loanKeylet.key, tfLoanImpair),
Ter(
tesSUCCESS));
825 auto const vaultAfterImpair = env.
le(broker.vaultKeylet());
826 BEAST_EXPECT(vaultAfterImpair);
827 Number const impairDelta =
Number(vaultAfterImpair->at(sfLossUnrealized)) - lossBefore;
829 env(manage(lender, loanKeylet.key, tfLoanUnimpair),
Ter(
tesSUCCESS));
832 auto const vaultAfterUnimpair = env.
le(broker.vaultKeylet());
833 BEAST_EXPECT(vaultAfterUnimpair);
834 Number const netDelta =
Number(vaultAfterUnimpair->at(sfLossUnrealized)) - lossBefore;
839 for (
auto const features : {
all_ | featureLendingProtocolV1_1,
all_})
842 std::string(
"cash-basis: LoanManage impair/unimpair (") +
843 (features[featureLendingProtocolV1_1] ?
"enabled)" :
"disabled)"));
844 auto const [expectedExposure, impairDelta, netDelta] = runImpairUnimpair(features);
847 impairDelta == expectedExposure,
848 "impair must add loanVaultExposure to LossUnrealized; delta=" +
852 "unimpair must be an exact reversal of impair; net=" +
to_string(netDelta));
857 Env env(*
this, features);
858 auto const [broker, loanKeylet, lender, borrower] = setupLoan(env);
860 auto const loanBeforeImpair = env.
le(loanKeylet);
861 BEAST_EXPECT(loanBeforeImpair);
862 Number const principalOutstanding = loanBeforeImpair->at(sfPrincipalOutstanding);
863 Number const totalValueOutstanding = loanBeforeImpair->at(sfTotalValueOutstanding);
864 Number const managementFeeOutstanding =
865 loanBeforeImpair->at(sfManagementFeeOutstanding);
867 Number const expectedExposure =
868 env.
current()->rules().enabled(featureLendingProtocolV1_1)
869 ? principalOutstanding
870 : totalValueOutstanding - managementFeeOutstanding;
873 env(manage(lender, loanKeylet.key, tfLoanImpair),
Ter(
tesSUCCESS));
881 auto const vaultBefore = env.
le(broker.vaultKeylet());
882 auto const brokerBefore = env.
le(broker.brokerKeylet());
883 BEAST_EXPECT(vaultBefore && brokerBefore);
884 Number const assetsTotalBefore = vaultBefore->at(sfAssetsTotal);
885 Number const debtTotalBefore = brokerBefore->at(sfDebtTotal);
886 Number const lossBefore = vaultBefore->at(sfLossUnrealized);
887 Number const coverAvailableBefore = brokerBefore->at(sfCoverAvailable);
889 env(manage(lender, loanKeylet.key, tfLoanDefault),
Ter(
tesSUCCESS));
892 auto const vaultAfter = env.
le(broker.vaultKeylet());
893 auto const brokerAfter = env.
le(broker.brokerKeylet());
894 BEAST_EXPECT(vaultAfter && brokerAfter);
895 Number const assetsTotalDelta =
896 Number(vaultAfter->at(sfAssetsTotal)) - assetsTotalBefore;
897 Number const debtTotalDelta =
Number(brokerAfter->at(sfDebtTotal)) - debtTotalBefore;
898 Number const lossDelta =
Number(vaultAfter->at(sfLossUnrealized)) - lossBefore;
899 Number const coverAvailableDelta =
900 Number(brokerAfter->at(sfCoverAvailable)) - coverAvailableBefore;
902 Number const defaultCovered = -coverAvailableDelta;
903 Number const vaultDefaultAmount = expectedExposure - defaultCovered;
906 expectedExposure, assetsTotalDelta, debtTotalDelta, lossDelta, vaultDefaultAmount);
909 for (
auto const features : {
all_ | featureLendingProtocolV1_1,
all_})
913 (features[featureLendingProtocolV1_1] ?
"enabled)" :
"disabled)"));
919 vaultDefaultAmount] = runDefault(features);
922 debtTotalDelta == -expectedExposure,
923 "default must reduce DebtTotal by the unified default amount; delta=" +
926 lossDelta == -expectedExposure,
927 "default must reverse the earlier impair's LossUnrealized exactly; delta=" +
930 assetsTotalDelta == -vaultDefaultAmount,
931 "default must reduce AssetsTotal by (defaultAmount - defaultCovered); delta=" +
945 "LEVersion: legacy vault keeps instant interest recognition after amendment enabled");
948 using namespace loan;
949 using namespace std::chrono_literals;
953 .vaultDeposit = 1'000'000,
956 .coverDeposit = 5'000,
960 Number const principalRequest{10'000};
969 Account const lender{
"lender"};
970 Account const borrower{
"borrower"};
971 env.
fund(
XRP(10'000'000), lender, borrower);
978 BEAST_EXPECT(vaultSle);
979 BEAST_EXPECT(!vaultSle->isFieldPresent(sfLEVersion));
991 .principalRequest = principalRequest,
992 .interest = interestRate,
993 .payTotal = paymentTotal,
994 .payInterval = paymentInterval,
995 .gracePd = gracePeriod,
999 BEAST_EXPECT(brokerBeforeLoan);
1000 auto const loanSequence = brokerBeforeLoan->at(sfLoanSequence);
1006 BEAST_EXPECT(vaultBeforeSet && brokerBeforeSet);
1007 Number const assetsTotalBeforeSet = vaultBeforeSet->at(sfAssetsTotal);
1008 Number const debtTotalBeforeSet = brokerBeforeSet->at(sfDebtTotal);
1010 env(loanParams(env, broker));
1013 auto const loanAfterSet = env.
le(loanKeylet);
1014 BEAST_EXPECT(loanAfterSet);
1015 Number const principalOutstanding = loanAfterSet->at(sfPrincipalOutstanding);
1016 Number const totalValueOutstanding = loanAfterSet->at(sfTotalValueOutstanding);
1017 Number const interestDue = totalValueOutstanding - principalOutstanding;
1022 BEAST_EXPECT(vaultAfterSet && brokerAfterSet);
1023 Number const assetsTotalDeltaSet =
1024 Number(vaultAfterSet->at(sfAssetsTotal)) - assetsTotalBeforeSet;
1025 Number const debtTotalDeltaSet =
1026 Number(brokerAfterSet->at(sfDebtTotal)) - debtTotalBeforeSet;
1029 assetsTotalDeltaSet == interestDue,
1030 "legacy vault origination must still add interestDue to AssetsTotal; delta=" +
1033 debtTotalDeltaSet == principalOutstanding + interestDue,
1034 "legacy vault origination must still add principal+interest to DebtTotal; delta=" +
1043 auto const loanBeforePay = env.
le(loanKeylet);
1044 BEAST_EXPECT(vaultBeforePay && brokerBeforePay && loanBeforePay);
1045 Number const totalValueBeforePay = loanBeforePay->at(sfTotalValueOutstanding);
1046 Number const assetsTotalBeforePay = vaultBeforePay->at(sfAssetsTotal);
1047 Number const debtTotalBeforePay = brokerBeforePay->at(sfDebtTotal);
1056 auto const loanAfterPay = env.
le(loanKeylet);
1057 BEAST_EXPECT(vaultAfterPay && brokerAfterPay && loanAfterPay);
1058 Number const totalValueAfterPay = loanAfterPay->at(sfTotalValueOutstanding);
1059 Number const assetsTotalDeltaPay =
1060 Number(vaultAfterPay->at(sfAssetsTotal)) - assetsTotalBeforePay;
1061 Number const debtTotalDeltaPay =
1062 Number(brokerAfterPay->at(sfDebtTotal)) - debtTotalBeforePay;
1063 Number const totalValueDeltaPay = totalValueAfterPay - totalValueBeforePay;
1069 "legacy vault regular payment must not change AssetsTotal; delta=" +
1072 debtTotalDeltaPay == totalValueDeltaPay,
1073 "legacy vault DebtTotal delta must mirror TotalValueOutstanding delta; "
1078 auto const loanBeforeImpair = env.
le(loanKeylet);
1079 BEAST_EXPECT(loanBeforeImpair);
1080 Number const totalValueBeforeImpair = loanBeforeImpair->at(sfTotalValueOutstanding);
1081 Number const managementFeeBeforeImpair = loanBeforeImpair->at(sfManagementFeeOutstanding);
1082 Number const expectedExposure = totalValueBeforeImpair - managementFeeBeforeImpair;
1087 std::uint32_t const dueDateBeforeImpair = loanBeforeImpair->at(sfNextPaymentDueDate);
1090 env(manage(lender, loanKeylet.key, tfLoanImpair),
Ter(
tesSUCCESS));
1097 auto const vaultBeforeDefault = env.
le(broker.
vaultKeylet());
1099 BEAST_EXPECT(vaultBeforeDefault && brokerBeforeDefault);
1100 Number const debtTotalBeforeDefault = brokerBeforeDefault->at(sfDebtTotal);
1101 Number const lossBeforeDefault = vaultBeforeDefault->at(sfLossUnrealized);
1103 env(manage(lender, loanKeylet.key, tfLoanDefault),
Ter(
tesSUCCESS));
1106 auto const vaultAfterDefault = env.
le(broker.
vaultKeylet());
1108 BEAST_EXPECT(vaultAfterDefault && brokerAfterDefault);
1109 Number const debtTotalDeltaDefault =
1110 Number(brokerAfterDefault->at(sfDebtTotal)) - debtTotalBeforeDefault;
1111 Number const lossDeltaDefault =
1112 Number(vaultAfterDefault->at(sfLossUnrealized)) - lossBeforeDefault;
1115 debtTotalDeltaDefault == -expectedExposure,
1116 "legacy vault default must reduce DebtTotal by instant-recognition exposure; delta=" +
1119 lossDeltaDefault == -expectedExposure,
1120 "legacy vault default must reverse the earlier impair's LossUnrealized exactly; "
1127 BEAST_EXPECT(vaultSle);
1128 BEAST_EXPECT(!vaultSle->isFieldPresent(sfLEVersion));
1143 testcase(
"cash-basis: end-to-end trajectory");
1145 using namespace jtx;
1146 using namespace loan;
1147 using namespace std::chrono_literals;
1151 .vaultDeposit = 100'000, .managementFeeRate =
TenthBips16{0}};
1153 Env env(*
this,
all_ | featureLendingProtocolV1_1);
1155 Account const lender{
"lender"};
1156 Account const borrower{
"borrower"};
1157 env.
fund(
XRP(10'000'000), lender, borrower);
1172 auto const checkVaultBroker = [&](
Number const& assetsTotalUnits,
1173 Number const& debtTotalUnits,
1174 Number const& lossUnrealizedUnits,
1175 Number const& coverAvailableUnits,
1177 Number const assetsTotal = xrpAsset(assetsTotalUnits).value();
1178 Number const debtTotal = xrpAsset(debtTotalUnits).value();
1179 Number const lossUnrealized = xrpAsset(lossUnrealizedUnits).value();
1180 Number const coverAvailable = xrpAsset(coverAvailableUnits).value();
1182 auto const vaultSle = env.
le(vaultKeylet);
1183 auto const brokerSle = env.
le(brokerKeylet);
1184 BEAST_EXPECT(vaultSle && brokerSle);
1186 vaultSle->at(sfAssetsTotal) == assetsTotal,
1190 brokerSle->at(sfDebtTotal) == debtTotal,
1194 vaultSle->at(sfLossUnrealized) == lossUnrealized,
1198 brokerSle->at(sfCoverAvailable) == coverAvailable,
1203 checkVaultBroker(100'000, 0, 0, 1'000,
"before LoanSet");
1206 Number const principalRequest{1'200};
1211 auto const brokerBeforeLoan = env.
le(brokerKeylet);
1212 BEAST_EXPECT(brokerBeforeLoan);
1213 auto const loanSequence = brokerBeforeLoan->at(sfLoanSequence);
1217 .account = borrower,
1219 .principalRequest = principalRequest,
1221 .payTotal = paymentTotal,
1222 .payInterval = paymentInterval,
1223 .gracePd = gracePeriod,
1225 env(loanParams(env, broker));
1229 checkVaultBroker(100'000, 1'200, 0, 1'000,
"after LoanSet");
1236 env(
pay(borrower, loanKeylet.key, xrpAsset(100).value()),
Ter(
tesSUCCESS));
1238 checkVaultBroker(100'000, 1'100, 0, 1'000,
"after payment 1");
1241 env(
pay(borrower, loanKeylet.key, xrpAsset(100).value()),
Ter(
tesSUCCESS));
1243 checkVaultBroker(100'000, 1'000, 0, 1'000,
"after payment 2");
1254 auto const loanBeforeDefault = env.
le(loanKeylet);
1255 BEAST_EXPECT(loanBeforeDefault);
1257 Number(loanBeforeDefault->at(sfPrincipalOutstanding)) == xrpAsset(1'000).value());
1261 env(manage(lender, loanKeylet.key, tfLoanDefault),
Ter(
tesSUCCESS));
1264 checkVaultBroker(99'025, 0, 0, 975,
"after LoanManage(default)");