48 using namespace std::chrono_literals;
53 testcase <<
"LoanPay xrpl::detail::computePeriodicPayment : "
56 Env env(*
this, features);
60 Account const borrower{
"borrower"};
71 auto const loanSetFee =
Fee(env.
current()->fees().base * 2);
72 Number const principalRequest{640562, -5};
74 Number const serviceFee{2462611968};
77 auto createJson = env.
json(
80 kLoanServiceFee(serviceFee),
81 kPaymentTotal(numPayments),
84 createJson[
"CloseInterestRate"] = 55374;
85 createJson[
"ClosePaymentFee"] =
"3825205248";
86 createJson[
"LatePaymentFee"] =
"237";
87 createJson[
"LoanOriginationFee"] =
"0";
88 createJson[
"OverpaymentFee"] = 35167;
89 createJson[
"OverpaymentInterestRate"] = 1360;
90 createJson[
"PaymentInterval"] = 727;
94 createJson = env.
json(createJson,
Sig(sfCounterpartySignature, lender));
102 Number const actualPrincipal{6};
104 createJson[sfPrincipalRequested] = actualPrincipal;
105 createJson.removeMember(sfSequence.jsonName);
106 createJson = env.
json(createJson,
Sig(sfCounterpartySignature, lender));
115 testcase <<
"xrpl::loanComputePaymentParts : valid total interest";
117 Env env(*
this, features);
119 Account const issuer{
"issuer"};
120 Account const lender{
"lender"};
121 Account const borrower{
"borrower"};
127 using namespace loan;
129 auto const loanSetFee =
Fee(env.
current()->fees().base * 2);
130 Number const principalRequest{1, 3};
132 auto createJson = env.
json(
137 createJson[
"CloseInterestRate"] = 47299;
138 createJson[
"ClosePaymentFee"] =
"3985819770";
139 createJson[
"InterestRate"] = 92;
140 createJson[
"LatePaymentFee"] =
"3866894865";
141 createJson[
"LoanOriginationFee"] =
"0";
142 createJson[
"LoanServiceFee"] =
"2348810240";
143 createJson[
"OverpaymentFee"] = 58545;
144 createJson[
"PaymentInterval"] = 60;
145 createJson[
"PaymentTotal"] = 1;
146 createJson[
"PrincipalRequested"] =
"0.000763058";
150 createJson = env.
json(createJson,
Sig(sfCounterpartySignature, lender));
155 loanPayTx[
"Amount"][
"value"] =
"0.000281284125490196";
162 testcase <<
"xrpl::loanComputePaymentParts : totalPrincipalPaid "
165 Env env(*
this, features);
167 Account const issuer{
"issuer"};
168 Account const lender{
"lender"};
169 Account const borrower{
"borrower"};
175 using namespace loan;
177 auto const loanSetFee = Fee(env.current()->fees().base * 2);
178 Number
const principalRequest{1, 3};
180 auto createJson = env.json(
181 set(borrower, broker.brokerID, principalRequest),
185 createJson[
"ClosePaymentFee"] =
"0";
186 createJson[
"InterestRate"] = 24346;
187 createJson[
"LateInterestRate"] = 65535;
188 createJson[
"LatePaymentFee"] =
"0";
189 createJson[
"LoanOriginationFee"] =
"218";
190 createJson[
"LoanServiceFee"] =
"0";
191 createJson[
"PaymentInterval"] = 60;
192 createJson[
"PaymentTotal"] = 5678;
193 createJson[
"PrincipalRequested"] =
"9924.81";
197 createJson = env.json(createJson, Sig(sfCounterpartySignature, lender));
201 auto const baseFee = env.current()->fees().base;
207 env.json(
pay(borrower,
keylet.
key, STAmount{broker.asset, Number{}}));
208 Number
const amount{3074'745'058'823'529, -12};
209 BEAST_EXPECT(
to_string(amount) ==
"3074.745058823529");
212 (amount / stateBefore.periodicPayment / kLoanPaymentsPerFeeIncrement + 1)};
213 loanPayTx[
"Amount"][
"value"] =
to_string(amount);
220 env.json(
pay(borrower,
keylet.
key, STAmount{broker.asset, Number{}}));
221 Number
const amount{6732'118'170'944'051, -12};
222 BEAST_EXPECT(
to_string(amount) ==
"6732.118170944051");
225 (amount / stateBefore.periodicPayment / kLoanPaymentsPerFeeIncrement + 1)};
226 loanPayTx[
"Amount"][
"value"] =
to_string(amount);
231 auto const stateAfter = getCurrentState(env, broker, keylet);
233 BEAST_EXPECT(stateAfter.totalValue >= stateAfter.principalOutstanding);
235 BEAST_EXPECT(stateBefore.principalOutstanding >= stateAfter.principalOutstanding);
237 BEAST_EXPECT(stateBefore.totalValue >= stateAfter.totalValue);
241 (stateBefore.totalValue - stateAfter.totalValue) >=
242 (stateBefore.principalOutstanding - stateAfter.principalOutstanding));
247 testcase <<
"xrpl::loanComputePaymentParts : loanValueChange rounded";
249 Env env(*
this, features);
251 Account const issuer{
"issuer"};
252 Account const lender{
"lender"};
253 Account const borrower{
"borrower"};
255 PrettyAsset
const iouAsset =
256 createFundedIouAsset(env, issuer, lender, borrower, 100'000'000, 10'000'000);
258 BrokerInfo
const broker{createVaultAndBroker(env, iouAsset, lender)};
260 auto const coverDepositValue =
261 broker.asset(broker.params.coverDeposit * 10).value();
262 env(loan_broker::coverDeposit(lender, broker.brokerID, coverDepositValue));
266 using namespace loan;
268 auto const loanSetFee = Fee(env.current()->fees().base * 2);
269 Number
const principalRequest{1, 3};
271 auto createJson = env.json(
272 set(borrower, broker.brokerID, principalRequest),
276 createJson[
"ClosePaymentFee"] =
"0";
277 createJson[
"InterestRate"] = 12833;
278 createJson[
"LateInterestRate"] = 77048;
279 createJson[
"LatePaymentFee"] =
"0";
280 createJson[
"LoanOriginationFee"] =
"218";
281 createJson[
"LoanServiceFee"] =
"0";
282 createJson[
"PaymentInterval"] = 752;
283 createJson[
"PaymentTotal"] = 5678;
284 createJson[
"PrincipalRequested"] =
"9924.81";
286 auto const keylet = nextLoanKeylet(env, broker);
288 createJson = env.json(createJson, Sig(sfCounterpartySignature, lender));
289 env(createJson, Ter(tesSUCCESS));
292 auto const baseFee = env.current()->fees().base;
294 auto const stateBefore = getCurrentState(env, broker, keylet);
295 BEAST_EXPECT(stateBefore.paymentRemaining == 5678);
296 BEAST_EXPECT(stateBefore.paymentRemaining > kLoanMaximumPaymentsPerTransaction);
298 auto loanPayTx = env.json(
pay(borrower,
keylet.
key, STAmount{broker.asset, Number{}}));
299 Number
const amount{9924'81, -2};
300 BEAST_EXPECT(
to_string(amount) ==
"9924.81");
303 (amount / stateBefore.periodicPayment / kLoanPaymentsPerFeeIncrement + 1)};
304 loanPayTx[
"Amount"][
"value"] =
to_string(amount);
305 env(loanPayTx, Fee(payFee), Ter(tesSUCCESS));
308 auto const stateAfter = getCurrentState(env, broker, keylet);
310 stateAfter.paymentRemaining ==
311 stateBefore.paymentRemaining - kLoanMaximumPaymentsPerTransaction);
319 testcase <<
"LoanPay xrpl::LoanPay::doApply : debtDecrease "
323 using namespace std::chrono_literals;
325 Env env(*
this, features);
327 Account const issuer{
"issuer"};
328 Account const lender{
"lender"};
329 Account const borrower{
"borrower"};
335 using namespace loan;
337 auto const baseFee = env.
current()->fees().base;
338 auto const loanSetFee =
Fee(baseFee * 2);
339 Number const principalRequest{1, 3};
341 auto createJson = env.
json(
346 createJson[
"ClosePaymentFee"] =
"0";
347 createJson[
"GracePeriod"] = 60;
348 createJson[
"InterestRate"] = 24346;
349 createJson[
"LateInterestRate"] = 65535;
350 createJson[
"LatePaymentFee"] =
"0";
351 createJson[
"LoanOriginationFee"] =
"218";
352 createJson[
"LoanServiceFee"] =
"0";
353 createJson[
"PaymentInterval"] = 60;
354 createJson[
"PaymentTotal"] = 5678;
355 createJson[
"PrincipalRequested"] =
"9924.81";
359 createJson = env.
json(createJson,
Sig(sfCounterpartySignature, lender));
367 verifyLoanStatus(originalState);
369 Number const payment{3'269'349'176'470'588, -12};
372 ((payment / originalState.periodicPayment) / kLoanPaymentsPerFeeIncrement + 1)};
375 BEAST_EXPECT(
to_string(payment) ==
"3269.349176470588");
381 isRounded(broker.
asset, newState.managementFeeOutstanding, originalState.loanScale));
382 BEAST_EXPECT(newState.managementFeeOutstanding < originalState.managementFeeOutstanding);
383 BEAST_EXPECT(
isRounded(broker.
asset, newState.totalValue, originalState.loanScale));
385 isRounded(broker.
asset, newState.principalOutstanding, originalState.loanScale));
395 testcase(
"LoanPay overpayment schedule advancement");
398 using namespace loan;
400 Env env{*
this, features | featureLendingProtocolV1_1};
402 Account const lender{
"lender"};
403 Account const borrower{
"borrower"};
405 env.
fund(
XRP(10'000'000), lender, borrower);
415 .vaultDeposit = asset(100'000).value(),
419 auto const loanSetFee =
Fee(env.
current()->fees().base * 2);
424 env(
loan::set(borrower, broker.
brokerID, asset(10'000).value(), tfLoanOverpayment),
425 Sig(sfCounterpartySignature, lender),
433 BEAST_EXPECT(before.paymentRemaining == 3);
435 STAmount const belowOnePayment = asset(1'000).value();
436 BEAST_EXPECT((belowOnePayment <
STAmount{asset, before.periodicPayment}));
438 auto const payFee =
Fee(env.
current()->fees().base * 2);
444 env(
pay(borrower, loanKeylet.key, belowOnePayment, tfLoanOverpayment),
450 BEAST_EXPECT(afterReject.paymentRemaining == before.paymentRemaining);
451 BEAST_EXPECT(afterReject.principalOutstanding == before.principalOutstanding);
452 BEAST_EXPECT(afterReject.nextPaymentDate == before.nextPaymentDate);
457 STAmount const onePaymentPlusExtra = asset(5'000).value();
458 env(
pay(borrower, loanKeylet.key, onePaymentPlusExtra, tfLoanOverpayment), payFee);
462 BEAST_EXPECT(afterPay.paymentRemaining == before.paymentRemaining - 1);
463 BEAST_EXPECT(afterPay.principalOutstanding < before.principalOutstanding);
464 BEAST_EXPECT(afterPay.nextPaymentDate == before.nextPaymentDate + before.paymentInterval);
536 using namespace loan;
539 bool const withAmendment = features[fixCleanup3_2_0];
556 auto runTest = [&](
Number vaultDeposit,
auto&& body) {
557 Env env(*
this, features);
559 Account const issuer{
"issuer"};
560 Account const lender{
"lender"};
561 Account const borrower{
"borrower"};
563 env.
fund(
XRP(1'000'000'000), issuer, lender, borrower);
568 env(
fset(issuer, asfAllowTrustLineClawback));
572 env(
trust(lender, iou(1'000'000'000)));
573 env(
trust(borrower, iou(1'000'000'000)));
575 env(
pay(issuer, lender, iou(100'000'000)));
576 env(
pay(issuer, borrower, iou(100'000'000)));
581 .vaultDeposit = vaultDeposit,
584 .coverDeposit = 5'000,
591 Ctx{.issuer = issuer,
593 .borrower = borrower,
596 .brokerParams = brokerParams});
605 testcase(
"LoanPay minimum cover scale consistency");
618 Asset const asset{c.iou};
624 if (!BEAST_EXPECT(brokerSle1))
626 auto const tinyLoanSeq = brokerSle1->at(sfLoanSequence);
627 auto const tinyLoanKeylet =
630 env(
set(c.borrower, c.broker.brokerID,
Number{1, -2}),
631 Sig(sfCounterpartySignature, c.lender),
634 kPaymentInterval(86400 * 365),
642 if (!BEAST_EXPECT(brokerSle2))
644 auto const bigLoanSeq = brokerSle2->at(sfLoanSequence);
645 auto const bigLoanKeylet =
648 env(
set(c.borrower, c.broker.brokerID,
Number{500}),
649 Sig(sfCounterpartySignature, c.lender),
652 kPaymentInterval(86400 * 365),
660 auto const tinyLoanSle = env.
le(tinyLoanKeylet);
661 auto const bigLoanSle = env.
le(bigLoanKeylet);
663 if (!BEAST_EXPECT(tinyLoanSle) || !BEAST_EXPECT(bigLoanSle) ||
664 !BEAST_EXPECT(vaultSle))
666 if (!BEAST_EXPECT(tinyLoanSle->at(sfLoanScale) == -12) ||
667 !BEAST_EXPECT(bigLoanSle->at(sfLoanScale) == -11) ||
683 Number const expectedCoverAfter = withAmendment ?
Number{1330651855688460000, -15}
684 :
Number{1330651855688458000, -15};
686 Number{c.brokerParams.coverDeposit} - expectedCoverAfter;
688 env(coverClawback(c.issuer),
689 kLoanBrokerId(c.broker.brokerID),
694 if (!BEAST_EXPECT(brokerSle) ||
695 !BEAST_EXPECT(brokerSle->at(sfCoverAvailable) == expectedCoverAfter))
698 return LoanKeylets{.tiny = tinyLoanKeylet, .big = bigLoanKeylet};
704 auto feeGoesToPseudo = [&](
Env& env, Ctx
const& c,
Keylet const& loanKeylet) ->
bool {
705 Asset const asset{c.iou};
707 if (!BEAST_EXPECT(brokerSle))
709 auto const pseudoAcct =
Account(
"pseudo", brokerSle->at(sfAccount));
710 auto const pseudoBefore = env.
balance(pseudoAcct, c.iou);
712 auto const payLoan = env.
le(loanKeylet);
713 if (!BEAST_EXPECT(payLoan))
715 auto const periodicPayment = payLoan->at(sfPeriodicPayment);
716 auto const serviceFee = payLoan->at(sfLoanServiceFee);
717 std::int32_t const loanScale = payLoan->at(sfLoanScale);
720 auto const payAmt =
STAmount{asset, payment + serviceFee};
725 auto const pseudoAfter = env.
balance(pseudoAcct, c.iou);
726 return pseudoAfter.
number() > pseudoBefore.number();
735 runTest(1'000, [&](
Env& env, Ctx
const& c) {
736 auto const loans = setupLoansAndClawback(env, c);
739 BEAST_EXPECT(feeGoesToPseudo(env, c, loans->big) == !withAmendment);
748 runTest(1'000, [&](
Env& env, Ctx
const& c) {
749 auto const loans = setupLoansAndClawback(env, c);
752 BEAST_EXPECT(!feeGoesToPseudo(env, c, loans->tiny));
771 testcase(
"CoverWithdraw minimum cover scale consistency");
773 100'000, [&](
Env& env, Ctx
const& c) {
774 Asset const asset{c.iou};
779 env(
set(c.borrower, c.broker.brokerID,
Number{500}),
780 Sig(sfCounterpartySignature, c.lender),
783 kPaymentInterval(86400 * 365),
790 if (!BEAST_EXPECT(brokerSle) || !BEAST_EXPECT(vaultSle))
793 auto const coverAvail = brokerSle->at(sfCoverAvailable);
794 auto const debtTotal = brokerSle->at(sfDebtTotal);
796 auto const debtScale =
scale(debtTotal, asset);
799 BEAST_EXPECT(debtScale < vaultScale);
801 auto const oldMin = [&]() {
809 debtTotal,
TenthBips32{c.brokerParams.coverRateMin}, vaultSle);
814 Number const expectedNewMin{1330650518688500000, -15};
815 Number const expectedOldMin{1330650518688472000, -15};
816 BEAST_EXPECT(newMin == expectedNewMin);
817 BEAST_EXPECT(oldMin == expectedOldMin);
821 auto const target = oldMin + (newMin - oldMin) / 2;
822 auto const withdrawAmount =
STAmount{asset, coverAvail - target};
828 env(coverWithdraw(c.lender, c.broker.brokerID, withdrawAmount),
835 env(coverWithdraw(c.lender, c.broker.brokerID, withdrawAmount));
849 testcase(
"LoanSet minimum cover scale consistency");
851 1'000, [&](
Env& env, Ctx
const& c) {
860 env(
set(c.borrower, c.broker.brokerID,
Number{1, -2}),
861 Sig(sfCounterpartySignature, c.lender),
864 kPaymentInterval(86400 * 365),
868 env(
set(c.borrower, c.broker.brokerID,
Number{500}),
869 Sig(sfCounterpartySignature, c.lender),
872 kPaymentInterval(86400 * 365),
880 Number const expectedCoverAfter = withAmendment ?
Number{1330651855688460000, -15}
881 :
Number{1330651855688458000, -15};
883 Number{c.brokerParams.coverDeposit} - expectedCoverAfter;
884 env(coverClawback(c.issuer),
885 kLoanBrokerId(c.broker.brokerID),
891 if (!BEAST_EXPECT(vaultSle))
894 BEAST_EXPECT(vaultScale == -11);
910 auto const tinyPrincipal =
Number{1, -11};
914 env(
set(c.borrower, c.broker.brokerID, tinyPrincipal),
915 Sig(sfCounterpartySignature, c.lender),
918 kPaymentInterval(86400 * 365),
923 env(
set(c.borrower, c.broker.brokerID, tinyPrincipal),
924 Sig(sfCounterpartySignature, c.lender),
927 kPaymentInterval(86400 * 365),
939 using namespace loan;
941 auto const runCase = [&](
bool coarseBorrower) {
943 coarseBorrower ?
"LoanSet borrower balance uses coarsest scale"
944 :
"LoanSet broker owner balance uses coarsest scale");
946 Env env(*
this,
all_ | featureLendingProtocolV1_1);
947 Account const issuer{
"issuer"};
948 Account const lender{
"lender"};
949 Account const borrower{
"borrower"};
951 Number const coarseBalance{100'000'000'000LL};
952 Number const regularBalance{100'000'000};
958 coarseBorrower ? regularBalance : coarseBalance,
959 coarseBorrower ? coarseBalance : regularBalance);
962 .vaultDeposit = 1'000'000,
970 Number const principal{1'012'345, -5};
971 Number const originationFee{123'456, -6};
972 Account const& recipient = coarseBorrower ? borrower : lender;
973 Number const expected = coarseBorrower ? principal : originationFee;
974 auto const before = env.
balance(recipient, asset);
979 kCounterparty(lender),
980 Sig(sfCounterpartySignature, lender),
989 kCounterparty(lender),
990 Sig(sfCounterpartySignature, lender),
991 kLoanOriginationFee(originationFee),
1000 Number const received =
after.number() - before.number();
1001 auto const recipientScale =
1002 std::max(before.value().exponent(),
after.value().exponent());
1003 auto const vaultScale = broker.
vaultScale(env);
1004 Number const tolerance{1, recipientScale};
1006 BEAST_EXPECT(recipientScale > vaultScale);
1007 BEAST_EXPECT(received != expected);
1010 roundToAsset(asset, expected, recipientScale)) <= tolerance);
1028 testcase(
"LoanPay interest-due non-negative invariant");
1030 using namespace jtx;
1031 using namespace loan;
1033 Env env(*
this,
all_ | featureLendingProtocolV1_1);
1035 Account const issuer{
"issuer"};
1036 Account const lender{
"lender"};
1037 Account const borrower{
"borrower"};
1046 auto const loanSetFee =
Fee(env.
current()->fees().base * 2);
1053 Sig(sfCounterpartySignature, lender),
1056 kPaymentInterval(86400 * 30),
1060 auto const payFee =
Fee(env.
current()->fees().base * 2);
1065 initial.totalValue >=
1066 initial.principalOutstanding + initial.managementFeeOutstanding);
1073 for (
int i = 0; i < 6; ++i)
1075 auto const loanSle = env.
le(loanKeylet);
1076 if (!BEAST_EXPECT(loanSle))
1084 iouAsset, loanSle->at(sfPeriodicPayment), loanSle->at(sfLoanScale)) +
1085 loanSle->at(sfLoanServiceFee)};
1091 state.totalValue >= state.principalOutstanding + state.managementFeeOutstanding);
1092 BEAST_EXPECT(state.paymentRemaining == prevPaymentRemaining - 1);
1093 prevPaymentRemaining = state.paymentRemaining;