46 using namespace std::chrono_literals;
51 testcase <<
"LoanPay xrpl::detail::computePeriodicPayment : "
54 Env env(*
this, features);
58 Account const borrower{
"borrower"};
69 auto const loanSetFee =
Fee(env.
current()->fees().base * 2);
70 Number const principalRequest{640562, -5};
72 Number const serviceFee{2462611968};
75 auto createJson = env.
json(
78 kLoanServiceFee(serviceFee),
79 kPaymentTotal(numPayments),
82 createJson[
"CloseInterestRate"] = 55374;
83 createJson[
"ClosePaymentFee"] =
"3825205248";
84 createJson[
"LatePaymentFee"] =
"237";
85 createJson[
"LoanOriginationFee"] =
"0";
86 createJson[
"OverpaymentFee"] = 35167;
87 createJson[
"OverpaymentInterestRate"] = 1360;
88 createJson[
"PaymentInterval"] = 727;
92 createJson = env.
json(createJson,
Sig(sfCounterpartySignature, lender));
100 Number const actualPrincipal{6};
102 createJson[sfPrincipalRequested] = actualPrincipal;
103 createJson.removeMember(sfSequence.jsonName);
104 createJson = env.
json(createJson,
Sig(sfCounterpartySignature, lender));
113 testcase <<
"xrpl::loanComputePaymentParts : valid total interest";
115 Env env(*
this, features);
117 Account const issuer{
"issuer"};
118 Account const lender{
"lender"};
119 Account const borrower{
"borrower"};
125 using namespace loan;
127 auto const loanSetFee =
Fee(env.
current()->fees().base * 2);
128 Number const principalRequest{1, 3};
130 auto createJson = env.
json(
135 createJson[
"CloseInterestRate"] = 47299;
136 createJson[
"ClosePaymentFee"] =
"3985819770";
137 createJson[
"InterestRate"] = 92;
138 createJson[
"LatePaymentFee"] =
"3866894865";
139 createJson[
"LoanOriginationFee"] =
"0";
140 createJson[
"LoanServiceFee"] =
"2348810240";
141 createJson[
"OverpaymentFee"] = 58545;
142 createJson[
"PaymentInterval"] = 60;
143 createJson[
"PaymentTotal"] = 1;
144 createJson[
"PrincipalRequested"] =
"0.000763058";
148 createJson = env.
json(createJson,
Sig(sfCounterpartySignature, lender));
153 loanPayTx[
"Amount"][
"value"] =
"0.000281284125490196";
160 testcase <<
"xrpl::loanComputePaymentParts : totalPrincipalPaid "
163 Env env(*
this, features);
165 Account const issuer{
"issuer"};
166 Account const lender{
"lender"};
167 Account const borrower{
"borrower"};
173 using namespace loan;
175 auto const loanSetFee = Fee(env.current()->fees().base * 2);
176 Number
const principalRequest{1, 3};
178 auto createJson = env.json(
179 set(borrower, broker.brokerID, principalRequest),
183 createJson[
"ClosePaymentFee"] =
"0";
184 createJson[
"InterestRate"] = 24346;
185 createJson[
"LateInterestRate"] = 65535;
186 createJson[
"LatePaymentFee"] =
"0";
187 createJson[
"LoanOriginationFee"] =
"218";
188 createJson[
"LoanServiceFee"] =
"0";
189 createJson[
"PaymentInterval"] = 60;
190 createJson[
"PaymentTotal"] = 5678;
191 createJson[
"PrincipalRequested"] =
"9924.81";
195 createJson = env.json(createJson, Sig(sfCounterpartySignature, lender));
199 auto const baseFee = env.current()->fees().base;
205 env.json(
pay(borrower,
keylet.
key, STAmount{broker.asset, Number{}}));
206 Number
const amount{3074'745'058'823'529, -12};
207 BEAST_EXPECT(
to_string(amount) ==
"3074.745058823529");
210 (amount / stateBefore.periodicPayment / kLoanPaymentsPerFeeIncrement + 1)};
211 loanPayTx[
"Amount"][
"value"] =
to_string(amount);
218 env.json(
pay(borrower,
keylet.
key, STAmount{broker.asset, Number{}}));
219 Number
const amount{6732'118'170'944'051, -12};
220 BEAST_EXPECT(
to_string(amount) ==
"6732.118170944051");
223 (amount / stateBefore.periodicPayment / kLoanPaymentsPerFeeIncrement + 1)};
224 loanPayTx[
"Amount"][
"value"] =
to_string(amount);
229 auto const stateAfter = getCurrentState(env, broker, keylet);
231 BEAST_EXPECT(stateAfter.totalValue >= stateAfter.principalOutstanding);
233 BEAST_EXPECT(stateBefore.principalOutstanding >= stateAfter.principalOutstanding);
235 BEAST_EXPECT(stateBefore.totalValue >= stateAfter.totalValue);
239 (stateBefore.totalValue - stateAfter.totalValue) >=
240 (stateBefore.principalOutstanding - stateAfter.principalOutstanding));
245 testcase <<
"xrpl::loanComputePaymentParts : loanValueChange rounded";
247 Env env(*
this, features);
249 Account const issuer{
"issuer"};
250 Account const lender{
"lender"};
251 Account const borrower{
"borrower"};
253 PrettyAsset
const iouAsset =
254 createFundedIouAsset(env, issuer, lender, borrower, 100'000'000, 10'000'000);
256 BrokerInfo
const broker{createVaultAndBroker(env, iouAsset, lender)};
258 auto const coverDepositValue =
259 broker.asset(broker.params.coverDeposit * 10).value();
260 env(loan_broker::coverDeposit(lender, broker.brokerID, coverDepositValue));
264 using namespace loan;
266 auto const loanSetFee = Fee(env.current()->fees().base * 2);
267 Number
const principalRequest{1, 3};
269 auto createJson = env.json(
270 set(borrower, broker.brokerID, principalRequest),
274 createJson[
"ClosePaymentFee"] =
"0";
275 createJson[
"InterestRate"] = 12833;
276 createJson[
"LateInterestRate"] = 77048;
277 createJson[
"LatePaymentFee"] =
"0";
278 createJson[
"LoanOriginationFee"] =
"218";
279 createJson[
"LoanServiceFee"] =
"0";
280 createJson[
"PaymentInterval"] = 752;
281 createJson[
"PaymentTotal"] = 5678;
282 createJson[
"PrincipalRequested"] =
"9924.81";
284 auto const keylet = nextLoanKeylet(env, broker);
286 createJson = env.json(createJson, Sig(sfCounterpartySignature, lender));
287 env(createJson, Ter(tesSUCCESS));
290 auto const baseFee = env.current()->fees().base;
292 auto const stateBefore = getCurrentState(env, broker, keylet);
293 BEAST_EXPECT(stateBefore.paymentRemaining == 5678);
294 BEAST_EXPECT(stateBefore.paymentRemaining > kLoanMaximumPaymentsPerTransaction);
296 auto loanPayTx = env.json(
pay(borrower,
keylet.
key, STAmount{broker.asset, Number{}}));
297 Number
const amount{9924'81, -2};
298 BEAST_EXPECT(
to_string(amount) ==
"9924.81");
301 (amount / stateBefore.periodicPayment / kLoanPaymentsPerFeeIncrement + 1)};
302 loanPayTx[
"Amount"][
"value"] =
to_string(amount);
303 env(loanPayTx, Fee(payFee), Ter(tesSUCCESS));
306 auto const stateAfter = getCurrentState(env, broker, keylet);
308 stateAfter.paymentRemaining ==
309 stateBefore.paymentRemaining - kLoanMaximumPaymentsPerTransaction);
317 testcase <<
"LoanPay xrpl::LoanPay::doApply : debtDecrease "
321 using namespace std::chrono_literals;
323 Env env(*
this, features);
325 Account const issuer{
"issuer"};
326 Account const lender{
"lender"};
327 Account const borrower{
"borrower"};
333 using namespace loan;
335 auto const baseFee = env.
current()->fees().base;
336 auto const loanSetFee =
Fee(baseFee * 2);
337 Number const principalRequest{1, 3};
339 auto createJson = env.
json(
344 createJson[
"ClosePaymentFee"] =
"0";
345 createJson[
"GracePeriod"] = 60;
346 createJson[
"InterestRate"] = 24346;
347 createJson[
"LateInterestRate"] = 65535;
348 createJson[
"LatePaymentFee"] =
"0";
349 createJson[
"LoanOriginationFee"] =
"218";
350 createJson[
"LoanServiceFee"] =
"0";
351 createJson[
"PaymentInterval"] = 60;
352 createJson[
"PaymentTotal"] = 5678;
353 createJson[
"PrincipalRequested"] =
"9924.81";
357 createJson = env.
json(createJson,
Sig(sfCounterpartySignature, lender));
365 verifyLoanStatus(originalState);
367 Number const payment{3'269'349'176'470'588, -12};
370 ((payment / originalState.periodicPayment) / kLoanPaymentsPerFeeIncrement + 1)};
373 BEAST_EXPECT(
to_string(payment) ==
"3269.349176470588");
379 isRounded(broker.
asset, newState.managementFeeOutstanding, originalState.loanScale));
380 BEAST_EXPECT(newState.managementFeeOutstanding < originalState.managementFeeOutstanding);
381 BEAST_EXPECT(
isRounded(broker.
asset, newState.totalValue, originalState.loanScale));
383 isRounded(broker.
asset, newState.principalOutstanding, originalState.loanScale));
455 using namespace loan;
458 bool const withAmendment = features[fixCleanup3_2_0];
475 auto runTest = [&](
Number vaultDeposit,
auto&& body) {
476 Env env(*
this, features);
478 Account const issuer{
"issuer"};
479 Account const lender{
"lender"};
480 Account const borrower{
"borrower"};
482 env.
fund(
XRP(1'000'000'000), issuer, lender, borrower);
487 env(
fset(issuer, asfAllowTrustLineClawback));
491 env(
trust(lender, iou(1'000'000'000)));
492 env(
trust(borrower, iou(1'000'000'000)));
494 env(
pay(issuer, lender, iou(100'000'000)));
495 env(
pay(issuer, borrower, iou(100'000'000)));
500 .vaultDeposit = vaultDeposit,
503 .coverDeposit = 5'000,
510 Ctx{.issuer = issuer,
512 .borrower = borrower,
515 .brokerParams = brokerParams});
524 testcase(
"LoanPay minimum cover scale consistency");
537 Asset const asset{c.iou};
543 if (!BEAST_EXPECT(brokerSle1))
545 auto const tinyLoanSeq = brokerSle1->at(sfLoanSequence);
546 auto const tinyLoanKeylet =
549 env(
set(c.borrower, c.broker.brokerID,
Number{1, -2}),
550 Sig(sfCounterpartySignature, c.lender),
553 kPaymentInterval(86400 * 365),
561 if (!BEAST_EXPECT(brokerSle2))
563 auto const bigLoanSeq = brokerSle2->at(sfLoanSequence);
564 auto const bigLoanKeylet =
567 env(
set(c.borrower, c.broker.brokerID,
Number{500}),
568 Sig(sfCounterpartySignature, c.lender),
571 kPaymentInterval(86400 * 365),
579 auto const tinyLoanSle = env.
le(tinyLoanKeylet);
580 auto const bigLoanSle = env.
le(bigLoanKeylet);
582 if (!BEAST_EXPECT(tinyLoanSle) || !BEAST_EXPECT(bigLoanSle) ||
583 !BEAST_EXPECT(vaultSle))
585 if (!BEAST_EXPECT(tinyLoanSle->at(sfLoanScale) == -12) ||
586 !BEAST_EXPECT(bigLoanSle->at(sfLoanScale) == -11) ||
602 Number const expectedCoverAfter = withAmendment ?
Number{1330651855688460000, -15}
603 :
Number{1330651855688458000, -15};
605 Number{c.brokerParams.coverDeposit} - expectedCoverAfter;
607 env(coverClawback(c.issuer),
608 kLoanBrokerId(c.broker.brokerID),
613 if (!BEAST_EXPECT(brokerSle) ||
614 !BEAST_EXPECT(brokerSle->at(sfCoverAvailable) == expectedCoverAfter))
617 return LoanKeylets{.tiny = tinyLoanKeylet, .big = bigLoanKeylet};
623 auto feeGoesToPseudo = [&](
Env& env, Ctx
const& c,
Keylet const& loanKeylet) ->
bool {
624 Asset const asset{c.iou};
626 if (!BEAST_EXPECT(brokerSle))
628 auto const pseudoAcct =
Account(
"pseudo", brokerSle->at(sfAccount));
629 auto const pseudoBefore = env.
balance(pseudoAcct, c.iou);
631 auto const payLoan = env.
le(loanKeylet);
632 if (!BEAST_EXPECT(payLoan))
634 auto const periodicPayment = payLoan->at(sfPeriodicPayment);
635 auto const serviceFee = payLoan->at(sfLoanServiceFee);
636 std::int32_t const loanScale = payLoan->at(sfLoanScale);
639 auto const payAmt =
STAmount{asset, payment + serviceFee};
644 auto const pseudoAfter = env.
balance(pseudoAcct, c.iou);
645 return pseudoAfter.
number() > pseudoBefore.number();
654 runTest(1'000, [&](
Env& env, Ctx
const& c) {
655 auto const loans = setupLoansAndClawback(env, c);
658 BEAST_EXPECT(feeGoesToPseudo(env, c, loans->big) == !withAmendment);
667 runTest(1'000, [&](
Env& env, Ctx
const& c) {
668 auto const loans = setupLoansAndClawback(env, c);
671 BEAST_EXPECT(!feeGoesToPseudo(env, c, loans->tiny));
690 testcase(
"CoverWithdraw minimum cover scale consistency");
692 100'000, [&](
Env& env, Ctx
const& c) {
693 Asset const asset{c.iou};
698 env(
set(c.borrower, c.broker.brokerID,
Number{500}),
699 Sig(sfCounterpartySignature, c.lender),
702 kPaymentInterval(86400 * 365),
709 if (!BEAST_EXPECT(brokerSle) || !BEAST_EXPECT(vaultSle))
712 auto const coverAvail = brokerSle->at(sfCoverAvailable);
713 auto const debtTotal = brokerSle->at(sfDebtTotal);
715 auto const debtScale =
scale(debtTotal, asset);
718 BEAST_EXPECT(debtScale < vaultScale);
720 auto const oldMin = [&]() {
728 debtTotal,
TenthBips32{c.brokerParams.coverRateMin}, vaultSle);
733 Number const expectedNewMin{1330650518688500000, -15};
734 Number const expectedOldMin{1330650518688472000, -15};
735 BEAST_EXPECT(newMin == expectedNewMin);
736 BEAST_EXPECT(oldMin == expectedOldMin);
740 auto const target = oldMin + (newMin - oldMin) / 2;
741 auto const withdrawAmount =
STAmount{asset, coverAvail - target};
747 env(coverWithdraw(c.lender, c.broker.brokerID, withdrawAmount),
754 env(coverWithdraw(c.lender, c.broker.brokerID, withdrawAmount));
768 testcase(
"LoanSet minimum cover scale consistency");
770 1'000, [&](
Env& env, Ctx
const& c) {
779 env(
set(c.borrower, c.broker.brokerID,
Number{1, -2}),
780 Sig(sfCounterpartySignature, c.lender),
783 kPaymentInterval(86400 * 365),
787 env(
set(c.borrower, c.broker.brokerID,
Number{500}),
788 Sig(sfCounterpartySignature, c.lender),
791 kPaymentInterval(86400 * 365),
799 Number const expectedCoverAfter = withAmendment ?
Number{1330651855688460000, -15}
800 :
Number{1330651855688458000, -15};
802 Number{c.brokerParams.coverDeposit} - expectedCoverAfter;
803 env(coverClawback(c.issuer),
804 kLoanBrokerId(c.broker.brokerID),
810 if (!BEAST_EXPECT(vaultSle))
813 BEAST_EXPECT(vaultScale == -11);
829 auto const tinyPrincipal =
Number{1, -11};
833 env(
set(c.borrower, c.broker.brokerID, tinyPrincipal),
834 Sig(sfCounterpartySignature, c.lender),
837 kPaymentInterval(86400 * 365),
842 env(
set(c.borrower, c.broker.brokerID, tinyPrincipal),
843 Sig(sfCounterpartySignature, c.lender),
846 kPaymentInterval(86400 * 365),