1#include <xrpl/beast/unit_test/suite.h>
3#include <test/jtx/Account.h>
4#include <test/jtx/Env.h>
5#include <test/jtx/TestHelpers.h>
6#include <test/jtx/amount.h>
7#include <test/jtx/batch.h>
8#include <test/jtx/credentials.h>
9#include <test/jtx/envconfig.h>
10#include <test/jtx/fee.h>
11#include <test/jtx/flags.h>
12#include <test/jtx/jtx_json.h>
13#include <test/jtx/mpt.h>
14#include <test/jtx/multisign.h>
15#include <test/jtx/noop.h>
16#include <test/jtx/offer.h>
17#include <test/jtx/pay.h>
18#include <test/jtx/permissioned_domains.h>
19#include <test/jtx/seq.h>
20#include <test/jtx/sig.h>
21#include <test/jtx/tags.h>
22#include <test/jtx/ter.h>
23#include <test/jtx/trust.h>
24#include <test/jtx/txflags.h>
25#include <test/jtx/utility.h>
26#include <test/jtx/vault.h>
28#include <xrpld/rpc/detail/Handler.h>
30#include <xrpl/basics/Number.h>
31#include <xrpl/basics/base_uint.h>
32#include <xrpl/basics/chrono.h>
33#include <xrpl/basics/strHex.h>
34#include <xrpl/beast/utility/Journal.h>
35#include <xrpl/beast/utility/Zero.h>
36#include <xrpl/beast/xor_shift_engine.h>
37#include <xrpl/json/json_value.h>
38#include <xrpl/json/to_string.h>
39#include <xrpl/ledger/helpers/LendingHelpers.h>
40#include <xrpl/protocol/Asset.h>
41#include <xrpl/protocol/Feature.h>
42#include <xrpl/protocol/HashPrefix.h>
43#include <xrpl/protocol/Indexes.h>
44#include <xrpl/protocol/Issue.h>
45#include <xrpl/protocol/KeyType.h>
46#include <xrpl/protocol/LedgerFormats.h>
47#include <xrpl/protocol/MPTIssue.h>
48#include <xrpl/protocol/Protocol.h>
49#include <xrpl/protocol/SField.h>
50#include <xrpl/protocol/STAmount.h>
51#include <xrpl/protocol/STTx.h>
52#include <xrpl/protocol/SecretKey.h>
53#include <xrpl/protocol/Serializer.h>
54#include <xrpl/protocol/TER.h>
55#include <xrpl/protocol/TxFlags.h>
56#include <xrpl/protocol/TxFormats.h>
57#include <xrpl/protocol/Units.h>
58#include <xrpl/protocol/XRPAmount.h>
59#include <xrpl/protocol/jss.h>
60#include <xrpl/server/LoadFeeTrack.h>
61#include <xrpl/tx/transactors/lending/LoanSet.h>
62#include <xrpl/tx/transactors/system/Batch.h>
107 Env env(*
this, features);
111 env.
fund(
XRP(10000), alice, bob);
115 using namespace std::chrono_literals;
116 using namespace loan;
134 env(manage(alice, loanKeylet.key, tfLoanImpair),
Ter(
temDISABLED));
138 failAll(
all_ - featureMPTokensV1);
139 failAll(
all_ - featureSingleAssetVault - featureLendingProtocol);
140 failAll(
all_ - featureSingleAssetVault);
141 failAll(
all_ - featureLendingProtocol);
165 return debtLimit - currentDebt;
245 template <
class... FN>
258 Sig(sfCounterpartySignature,
counter)(env, jt);
263 kCounterparty(
counter)(env, jt);
265 kLoanOriginationFee(broker.
asset(*originationFee).number())(env, jt);
267 kLoanServiceFee(broker.
asset(*serviceFee).number())(env, jt);
269 kLatePaymentFee(broker.
asset(*lateFee).number())(env, jt);
271 kClosePaymentFee(broker.
asset(*closeFee).number())(env, jt);
273 kOverpaymentFee (*
overFee)(env, jt);
287 kGracePeriod (*
gracePd)(env, jt);
289 return env.
jt(jt, fN...);
349 Number const& principalOutstanding,
350 Number const& interestOwed,
358 env.test.BEAST_EXPECT(brokerSle))
360 TenthBips16 const managementFeeRate{brokerSle->at(sfManagementFeeRate)};
361 auto const brokerDebt = brokerSle->at(sfDebtTotal);
362 auto const expectedDebt = principalOutstanding + interestOwed;
363 env.test.BEAST_EXPECT(brokerDebt == expectedDebt);
364 env.test.BEAST_EXPECT(
366 brokerSle->at(sfCoverAvailable));
367 env.test.BEAST_EXPECT(brokerSle->at(sfOwnerCount) ==
ownerCount);
370 env.test.BEAST_EXPECT(vaultSle))
372 Account const vaultPseudo{
"vaultPseudoAccount", vaultSle->at(sfAccount)};
373 env.test.BEAST_EXPECT(
374 vaultSle->at(sfAssetsAvailable) ==
375 env.balance(vaultPseudo,
broker.asset).number());
380 auto const total = vaultSle->at(sfAssetsTotal);
381 auto const available = vaultSle->at(sfAssetsAvailable);
382 env.test.BEAST_EXPECT(total == available);
383 env.test.BEAST_EXPECT(vaultSle->at(sfLossUnrealized) == 0);
404 env.balance(account,
broker.asset) - (balanceBefore - balanceChangeAmount),
409 ", expected change: " +
to_string(balanceChangeAmount) +
410 ", difference (balance after - expected): " +
to_string(difference),
424 Number const& principalOutstanding,
425 Number const& managementFeeOutstanding,
426 Number const& periodicPayment,
432 env.test.BEAST_EXPECT(
loan->at(sfPreviousPaymentDueDate) == previousPaymentDate);
433 env.test.BEAST_EXPECT(
loan->at(sfPaymentRemaining) == paymentRemaining);
434 env.test.BEAST_EXPECT(
loan->at(sfNextPaymentDueDate) == nextPaymentDate);
435 env.test.BEAST_EXPECT(
loan->at(sfLoanScale) == loanScale);
436 env.test.BEAST_EXPECT(
loan->at(sfTotalValueOutstanding) == totalValue);
437 env.test.BEAST_EXPECT(
loan->at(sfPrincipalOutstanding) == principalOutstanding);
438 env.test.BEAST_EXPECT(
439 loan->at(sfManagementFeeOutstanding) == managementFeeOutstanding);
440 env.test.BEAST_EXPECT(
loan->at(sfPeriodicPayment) == periodicPayment);
441 env.test.BEAST_EXPECT(
loan->at(sfFlags) == flags);
446 auto const paymentInterval =
loan->at(sfPaymentInterval);
448 principalOutstanding,
456 env.test.BEAST_EXPECT(brokerSle))
459 env.test.BEAST_EXPECT(vaultSle))
461 if (((flags & lsfLoanImpaired) != 0u) && ((flags & lsfLoanDefault) == 0u))
463 env.test.BEAST_EXPECT(
464 vaultSle->at(sfLossUnrealized) ==
465 totalValue - managementFeeOutstanding);
469 env.test.BEAST_EXPECT(vaultSle->at(sfLossUnrealized) == 0);
502 Vault const vault{env};
504 auto const deposit = asset(params.vaultDeposit);
505 auto const debtMaximumValue = asset(params.debtMax).value();
506 auto const coverDepositValue = asset(params.coverDeposit).value();
508 auto const coverRateMinValue = params.coverRateMin;
510 auto [tx, vaultKeylet] = vault.create({.owner = lender, .asset = asset});
511 if (params.vaultScale)
512 tx[sfScale] = *params.vaultScale;
515 BEAST_EXPECT(env.
le(vaultKeylet));
517 env(vault.deposit({.depositor = lender, .id = vaultKeylet.key, .amount = deposit}));
519 if (
auto const vault = env.
le(
keylet::vault(vaultKeylet.key)); BEAST_EXPECT(vault))
521 BEAST_EXPECT(vault->at(sfAssetsAvailable) ==
deposit.value());
527 env(
set(lender, vaultKeylet.key, params.flags),
529 kManagementFeeRate(params.managementFeeRate),
530 kDebtMaximum(debtMaximumValue),
531 kCoverRateMinimum(coverRateMinValue),
532 kCoverRateLiquidation(
TenthBips32(params.coverRateLiquidation)));
534 if (coverDepositValue != beast::kZero)
535 env(coverDeposit(lender,
keylet.key, coverDepositValue));
539 return {asset,
keylet, vaultKeylet, params};
550 if (
auto loan = env.
le(loanKeylet); BEAST_EXPECT(
loan))
553 .previousPaymentDate =
loan->at(sfPreviousPaymentDueDate),
554 .startDate = tp{d{
loan->at(sfStartDate)}},
555 .nextPaymentDate =
loan->at(sfNextPaymentDueDate),
556 .paymentRemaining =
loan->at(sfPaymentRemaining),
557 .loanScale =
loan->at(sfLoanScale),
558 .totalValue =
loan->at(sfTotalValueOutstanding),
559 .principalOutstanding =
loan->at(sfPrincipalOutstanding),
560 .managementFeeOutstanding =
loan->at(sfManagementFeeOutstanding),
561 .periodicPayment =
loan->at(sfPeriodicPayment),
562 .flags =
loan->at(sfFlags),
563 .paymentInterval =
loan->at(sfPaymentInterval),
579 using namespace std::chrono_literals;
584 BEAST_EXPECT(state.previousPaymentDate == 0);
585 BEAST_EXPECT(tp{d{state.nextPaymentDate}} == state.startDate + 600s);
586 BEAST_EXPECT(state.paymentRemaining == 12);
587 BEAST_EXPECT(state.principalOutstanding == broker.
asset(1000).value());
593 BEAST_EXPECT(state.paymentInterval == 600);
599 broker.
asset, state.periodicPayment * state.paymentRemaining, state.loanScale));
602 state.managementFeeOutstanding ==
605 state.totalValue - state.principalOutstanding,
609 verifyLoanStatus(state);
618 BEAST_EXPECT(brokerSle))
620 if (
auto const vaultSle = env.
le(
keylet::vault(brokerSle->at(sfVaultID)));
621 BEAST_EXPECT(vaultSle))
624 auto const assetsUnavailable =
625 vaultSle->at(sfAssetsTotal) - vaultSle->at(sfAssetsAvailable);
626 auto const unrealizedLoss = vaultSle->at(sfLossUnrealized) + state.
totalValue -
629 if (!BEAST_EXPECT(unrealizedLoss <= assetsUnavailable))
663 if (lender != issuer)
664 env(
trust(lender, limit));
665 if (borrower != issuer)
666 env(
trust(borrower, limit));
674 env.
fund(env.
current()->fees().accountReserve(10) * 10, issuer);
681 mptt.
create({.flags = tfMPTCanClawback | tfMPTCanTransfer | tfMPTCanLock});
686 if (lender != issuer)
688 if (borrower != issuer)
713 auto const asset =
createAsset(env, assetType, brokerParams, issuer, lender, borrower);
728 log <<
"Loan properties:\n"
729 <<
"\tPrincipal: " << principal <<
std::endl
730 <<
"\tInterest rate: " << interest <<
std::endl
731 <<
"\tPayment interval: " << interval <<
std::endl
732 <<
"\tManagement Fee Rate: " << feeRate <<
std::endl
733 <<
"\tTotal Payments: " << total <<
std::endl
734 <<
"\tPeriodic Payment: " << props.periodicPayment <<
std::endl
735 <<
"\tTotal Value: " << props.loanState.valueOutstanding <<
std::endl
736 <<
"\tManagement Fee: " << props.loanState.managementFeeDue <<
std::endl
737 <<
"\tLoan Scale: " << props.loanScale <<
std::endl
738 <<
"\tFirst payment principal: " << props.firstPaymentPrincipal <<
std::endl;
763 env.
fund(env.
current()->fees().accountReserve(10) * 10, issuer);
764 if (lender != issuer)
766 if (borrower != issuer && borrower != lender)
769 describeLoan(env, brokerParams, loanParams, assetType, issuer, lender, borrower);
772 auto const asset =
createAsset(env, assetType, brokerParams, issuer, lender, borrower);
775 if (asset.
native() || lender != issuer)
789 if (!BEAST_EXPECT(brokerSle))
791 auto const brokerPseudo = brokerSle->at(sfAccount);
792 return Account(
"Broker pseudo-account", brokerPseudo);
796 Account const& pseudoAcct = *pseudoAcctOpt;
800 if (!BEAST_EXPECT(brokerSle))
804 BEAST_EXPECT(brokerSle->at(sfOwnerCount) == 0);
808 auto const loanSequence = brokerSle->at(sfLoanSequence);
813 Keylet const& loanKeylet = *loanKeyletOpt;
815 env(loanParams(env, broker));
837 auto const borrowerBalance = env.
balance(borrower, broker.
asset);
839 auto const baseFee = env.
current()->fees().base;
848 : broker.
asset(15).number());
850 auto const shortage = totalNeeded - borrowerBalance.number();
852 if (shortage > beast::kZero && (broker.
asset.
native() || issuer != borrower))
876 using namespace std::chrono_literals;
879 bool const showStepBalances = paymentParams.showStepBalances;
883 auto const baseFee = env.
current()->fees().base;
888 verifyLoanStatus(state);
902 auto const periodicRate =
loanPeriodicRate(state.interestRate, state.paymentInterval);
903 STAmount const roundedPeriodicPayment{
907 if (!showStepBalances)
909 log << currencyLabel <<
" Payment components: "
910 <<
"Payments remaining, "
911 <<
"rawInterest, rawPrincipal, "
913 <<
"trackedValueDelta, trackedPrincipalDelta, "
914 "trackedInterestDelta, trackedMgmtFeeDelta, special"
923 state.totalValue, state.principalOutstanding, state.managementFeeOutstanding);
927 state.periodicPayment,
929 state.paymentRemaining,
932 if (showStepBalances)
934 log << currencyLabel <<
" Starting loan balances: "
935 <<
"\n\tTotal value: " << currentRoundedState.valueOutstanding
936 <<
"\n\tPrincipal: " << currentRoundedState.principalOutstanding
937 <<
"\n\tInterest: " << currentRoundedState.interestDue
938 <<
"\n\tMgmt fee: " << currentRoundedState.managementFeeDue
939 <<
"\n\tPayments remaining " << state.paymentRemaining <<
std::endl;
943 log << currencyLabel <<
" Loan starting state: " << state.paymentRemaining <<
", "
944 << raw.interestDue <<
", " << raw.principalOutstanding <<
", "
945 << raw.managementFeeDue <<
", " << currentRoundedState.valueOutstanding <<
", "
946 << currentRoundedState.principalOutstanding <<
", "
947 << currentRoundedState.interestDue <<
", "
948 << currentRoundedState.managementFeeDue <<
std::endl;
954 auto const extraAmount = paymentParams.overpaymentExtra
955 ? broker.
asset(*paymentParams.overpaymentExtra).value()
959 STAmount{broker.
asset, totalDue * paymentParams.overpaymentFactor} + extraAmount;
962 auto const initialState = state;
964 .trackedValueDelta = 0, .trackedPrincipalDelta = 0, .trackedManagementFeeDelta = 0};
965 Number totalInterestPaid = 0;
971 state.periodicPayment,
973 state.paymentRemaining,
976 auto validateBorrowerBalance = [&]() {
977 if (borrower == issuer || !paymentParams.validateBalances)
979 auto const totalSpent =
984 borrowerInitialBalance - totalSpent);
989 auto const p = places.value_or(defaultRound);
992 auto const factor =
Number{1, p};
993 return (n * factor).
truncate() / factor;
995 while (state.paymentRemaining > 0)
997 validateBorrowerBalance();
1004 state.principalOutstanding,
1005 state.managementFeeOutstanding,
1006 state.periodicPayment,
1008 state.paymentRemaining,
1012 paymentComponents.trackedValueDelta <= roundedPeriodicPayment ||
1014 paymentComponents.trackedValueDelta >= roundedPeriodicPayment));
1016 paymentComponents.trackedValueDelta ==
1017 paymentComponents.trackedPrincipalDelta + paymentComponents.trackedInterestPart() +
1018 paymentComponents.trackedManagementFeeDelta);
1022 state.periodicPayment,
1024 state.paymentRemaining - 1,
1031 deltas.
total() == state.periodicPayment ||
1032 (state.loanScale - (deltas.
total() - state.periodicPayment).exponent()) > 14);
1034 if (!showStepBalances)
1036 log << currencyLabel <<
" Payment components: " << state.paymentRemaining <<
", "
1039 <<
", " << paymentComponents.trackedValueDelta <<
", "
1040 << paymentComponents.trackedPrincipalDelta <<
", "
1041 << paymentComponents.trackedInterestPart() <<
", "
1042 << paymentComponents.trackedManagementFeeDelta <<
", " << [&]() ->
char const* {
1051 auto const totalDueAmount =
1052 STAmount{broker.
asset, paymentComponents.trackedValueDelta + serviceFee};
1054 if (paymentParams.validateBalances)
1062 Number const diff = totalDue - totalDueAmount;
1065 diff == beast::kZero ||
1066 (diff > beast::kZero &&
1068 (state.loanScale - diff.
exponent() > 13))));
1071 paymentComponents.trackedPrincipalDelta >= beast::kZero &&
1072 paymentComponents.trackedPrincipalDelta <= state.principalOutstanding);
1075 paymentComponents.trackedPrincipalDelta == state.principalOutstanding);
1078 auto const borrowerBalanceBeforePayment = env.
balance(borrower, broker.
asset);
1081 env(
pay(borrower, loanKeylet.
key, transactionAmount, paymentParams.flags));
1083 env.
close(d{state.paymentInterval / 2});
1085 if (paymentParams.validateBalances)
1091 adjustment = env.
current()->fees().base;
1098 borrowerBalanceBeforePayment,
1103 if (showStepBalances)
1105 auto const loanSle = env.
le(loanKeylet);
1106 if (!BEAST_EXPECT(loanSle))
1112 auto const errors = nextTrueState - current;
1113 log << currencyLabel <<
" Loan balances: "
1114 <<
"\n\tAmount taken: " << paymentComponents.trackedValueDelta
1115 <<
"\n\tTotal value: " << current.valueOutstanding
1117 <<
", error: " << truncate(errors.total())
1118 <<
")\n\tPrincipal: " << current.principalOutstanding
1120 <<
", error: " << truncate(errors.principal)
1121 <<
")\n\tInterest: " << current.interestDue
1122 <<
" (true: " << truncate(nextTrueState.
interestDue)
1123 <<
", error: " << truncate(errors.interest)
1124 <<
")\n\tMgmt fee: " << current.managementFeeDue
1126 <<
", error: " << truncate(errors.managementFee) <<
")\n\tPayments remaining "
1127 << loanSle->at(sfPaymentRemaining) <<
std::endl;
1129 currentRoundedState = current;
1132 --state.paymentRemaining;
1133 state.previousPaymentDate = state.nextPaymentDate;
1136 state.paymentRemaining = 0;
1137 state.nextPaymentDate = 0;
1141 state.nextPaymentDate += state.paymentInterval;
1143 state.principalOutstanding -= paymentComponents.trackedPrincipalDelta;
1144 state.managementFeeOutstanding -= paymentComponents.trackedManagementFeeDelta;
1145 state.totalValue -= paymentComponents.trackedValueDelta;
1147 if (paymentParams.validateBalances)
1148 verifyLoanStatus(state);
1153 totalInterestPaid += paymentComponents.trackedInterestPart();
1154 totalFeesPaid += serviceFee;
1155 ++totalPaymentsMade;
1157 currentTrueState = nextTrueState;
1159 validateBorrowerBalance();
1162 BEAST_EXPECT(state.paymentRemaining == 0);
1163 BEAST_EXPECT(state.principalOutstanding == 0);
1165 auto const initialInterestDue = initialState.totalValue -
1166 (initialState.principalOutstanding + initialState.managementFeeOutstanding);
1167 if (paymentParams.validateBalances)
1176 BEAST_EXPECT(totalInterestPaid == initialInterestDue);
1177 BEAST_EXPECT(totalPaymentsMade == initialState.paymentRemaining);
1180 if (showStepBalances)
1182 auto const loanSle = env.
le(loanKeylet);
1183 if (!BEAST_EXPECT(loanSle))
1188 log << currencyLabel <<
" Total amounts paid: "
1190 <<
" (initial: " << truncate(initialState.totalValue)
1191 <<
", error: " << truncate(initialState.totalValue - totalPaid.
trackedValueDelta)
1193 <<
" (initial: " << truncate(initialState.principalOutstanding) <<
", error: "
1195 <<
")\n\tInterest: " << totalInterestPaid
1196 <<
" (initial: " << truncate(initialInterestDue)
1197 <<
", error: " << truncate(initialInterestDue - totalInterestPaid)
1199 <<
" (initial: " << truncate(initialState.managementFeeOutstanding) <<
", error: "
1202 <<
")\n\tTotal payments made: " << totalPaymentsMade <<
std::endl;
1213 using namespace jtx;
1215 Account const issuer(
"issuer");
1216 Account const lender(
"lender");
1217 Account const borrower(
"borrower");
1219 Env env(*
this, features);
1222 createLoan(env, assetType, brokerParams, loanParams, issuer, lender, borrower);
1223 if (BEAST_EXPECT(loanResult); !loanResult.has_value())
1226 auto broker = std::get<BrokerInfo>(*loanResult);
1227 auto loanKeylet = std::get<Keylet>(*loanResult);
1228 auto pseudoAcct = std::get<Account>(*loanResult);
1230 VerifyLoanStatus const verifyLoanStatus(env, broker, pseudoAcct, loanKeylet);
1259 Number const& loanAmount,
1260 int interestExponent,
1272 auto const [
keylet, loanSequence] = [&]() {
1274 if (!BEAST_EXPECT(brokerSle))
1281 BEAST_EXPECT(brokerSle->at(sfOwnerCount) == 0);
1285 auto const loanSequence = brokerSle->at(sfLoanSequence);
1294 if (!BEAST_EXPECT(loanSequence != 0))
1297 testcase << caseLabel <<
" " << label;
1299 using namespace jtx;
1300 using namespace loan;
1301 using namespace std::chrono_literals;
1303 auto applyExponent = [interestExponent,
this](
TenthBips32 value)
mutable {
1305 while (interestExponent > 0)
1307 auto const oldValue = value;
1310 BEAST_EXPECT(value / 10 == oldValue);
1312 while (interestExponent < 0)
1314 auto const oldValue = value;
1317 BEAST_EXPECT(value * 10 == oldValue);
1322 auto const borrowerOwnerCount = env.
ownerCount(borrower);
1324 auto const loanSetFee = env.
current()->fees().base * 2;
1326 .account = borrower,
1328 .counterpartyExplicit =
false,
1329 .principalRequest = loanAmount,
1330 .setFee = loanSetFee,
1331 .originationFee = 1,
1348 auto const serviceFeeAmount = broker.
asset(*loanParams.
serviceFee).value();
1349 auto const lateFeeAmount = broker.
asset(*loanParams.
lateFee).value();
1350 auto const closeFeeAmount = broker.
asset(*loanParams.
closeFee).value();
1352 auto const borrowerStartbalance = env.
balance(borrower, broker.
asset);
1354 auto createJtx = loanParams(env, broker);
1360 auto const startDate = env.
current()->header().parentCloseTime.time_since_epoch().count();
1363 BEAST_EXPECT(brokerSle))
1365 BEAST_EXPECT(brokerSle->at(sfOwnerCount) == 1);
1373 adjustment = 2 * env.
current()->fees().base;
1378 borrowerStartbalance.
value() + principalRequestAmount - originationFeeAmount -
1379 adjustment.
value());
1382 auto const loanFlags =
1390 loan->isFlag(lsfLoanOverpayment) == createJtx.stx->isFlag(tfLoanOverpayment));
1391 BEAST_EXPECT(
loan->at(sfLoanSequence) == loanSequence);
1392 BEAST_EXPECT(
loan->at(sfBorrower) == borrower.
id());
1393 BEAST_EXPECT(
loan->at(sfLoanBrokerID) == broker.
brokerID);
1394 BEAST_EXPECT(
loan->at(sfLoanOriginationFee) == originationFeeAmount);
1395 BEAST_EXPECT(
loan->at(sfLoanServiceFee) == serviceFeeAmount);
1396 BEAST_EXPECT(
loan->at(sfLatePaymentFee) == lateFeeAmount);
1397 BEAST_EXPECT(
loan->at(sfClosePaymentFee) == closeFeeAmount);
1398 BEAST_EXPECT(
loan->at(sfOverpaymentFee) == *loanParams.
overFee);
1399 BEAST_EXPECT(
loan->at(sfInterestRate) == *loanParams.
interest);
1403 BEAST_EXPECT(
loan->at(sfStartDate) == startDate);
1405 BEAST_EXPECT(
loan->at(sfGracePeriod) == *loanParams.
gracePd);
1406 BEAST_EXPECT(
loan->at(sfPreviousPaymentDueDate) == 0);
1407 BEAST_EXPECT(
loan->at(sfNextPaymentDueDate) == startDate + *loanParams.
payInterval);
1408 BEAST_EXPECT(
loan->at(sfPaymentRemaining) == *loanParams.
payTotal);
1410 loan->at(sfLoanScale) >=
1414 BEAST_EXPECT(
loan->at(sfPrincipalOutstanding) == principalRequestAmount);
1422 state.principalOutstanding,
1424 state.paymentInterval,
1425 state.paymentRemaining,
1434 loanProperties.loanState.valueOutstanding,
1435 principalRequestAmount,
1436 loanProperties.loanState.managementFeeDue,
1437 loanProperties.periodicPayment,
1442 env(manage(lender,
keylet.key, 0));
1445 auto jt = manage(lender,
keylet.key, 0);
1446 jt.removeMember(sfFlags.getName());
1457 env(manage(lender,
keylet.key, tfLoanUnimpair | tfLoanImpair | tfLoanDefault),
1470 env(manage(lender,
keylet.key, tfLoanImpair),
1473 env(manage(lender,
keylet.key, tfLoanUnimpair),
1476 auto const nextDueDate = startDate + *loanParams.
payInterval;
1484 loanProperties.loanScale,
1485 loanProperties.loanState.valueOutstanding,
1486 principalRequestAmount,
1487 loanProperties.loanState.managementFeeDue,
1488 loanProperties.periodicPayment,
1494 if (BEAST_EXPECT(toEndOfLife))
1495 toEndOfLife(
keylet, verifyLoanStatus);
1501 BEAST_EXPECT(
loan->at(sfPaymentRemaining) == 0);
1502 BEAST_EXPECT(
loan->at(sfPrincipalOutstanding) == 0);
1504 auto const borrowerStartingBalance = env.
balance(borrower, broker.
asset);
1520 static unsigned kDeleteCounter = 0;
1521 auto const deleter = ((++kDeleteCounter % 2) != 0u) ? lender : borrower;
1522 env(del(deleter,
keylet.key));
1526 if (deleter == borrower)
1531 adjustment = env.
current()->fees().base;
1540 borrowerStartingBalance.
value() - adjustment);
1541 BEAST_EXPECT(env.
ownerCount(borrower) == borrowerOwnerCount);
1544 BEAST_EXPECT(brokerSle))
1546 BEAST_EXPECT(brokerSle->at(sfOwnerCount) == 0);
1570 template <
class TAsset, std::
size_t NAsset>
1577 Number const& loanAmount,
1578 int interestExponent)
1580 using namespace jtx;
1583 auto const& asset = broker.
asset.
raw();
1585 auto const caseLabel = [&]() {
1587 ss <<
"Lifecycle: " << loanAmount <<
" " << currencyLabel
1588 <<
" Scale interest to: " << interestExponent <<
" ";
1593 using namespace loan;
1594 using namespace std::chrono_literals;
1598 Account const issuer{
"issuer"};
1601 Account const lender{
"lender"};
1603 Account const borrower{
"borrower"};
1609 Number const principalRequest = broker.
asset(loanAmount).value();
1611 BEAST_EXPECT(maxCoveredLoanValue == 1000 * 100 / 10);
1612 Number const maxCoveredLoanRequest = broker.
asset(maxCoveredLoanValue).value();
1616 auto const loanSetFee =
Fee(env.
current()->fees().base * 2);
1618 auto const pseudoAcct = [&]() {
1620 if (!BEAST_EXPECT(brokerSle))
1622 auto const brokerPseudo = brokerSle->at(sfAccount);
1623 return Account(
"Broker pseudo-account", brokerPseudo);
1626 auto const baseFee = env.
current()->fees().base;
1631 env(
set(evan, broker.
brokerID, principalRequest, tfLoanSetMask),
1632 Sig(sfCounterpartySignature, lender),
1639 Sig(sfCounterpartySignature, borrower),
1645 Sig(sfCounterpartySignature, lender),
1653 Sig(sfCounterpartySignature, borrower),
1654 kOverpaymentFee(kMaxOverpaymentFee),
1659 Sig(sfCounterpartySignature, lender),
1660 kOverpaymentFee(kMaxOverpaymentFee + 1),
1666 Sig(sfCounterpartySignature, borrower),
1667 kInterestRate(kMaxInterestRate),
1671 Sig(sfCounterpartySignature, borrower),
1677 Sig(sfCounterpartySignature, lender),
1678 kInterestRate(kMaxInterestRate + 1),
1683 Sig(sfCounterpartySignature, lender),
1690 Sig(sfCounterpartySignature, borrower),
1691 kLateInterestRate(kMaxLateInterestRate),
1695 Sig(sfCounterpartySignature, borrower),
1701 Sig(sfCounterpartySignature, lender),
1702 kLateInterestRate(kMaxLateInterestRate + 1),
1707 Sig(sfCounterpartySignature, lender),
1714 Sig(sfCounterpartySignature, borrower),
1715 kCloseInterestRate(kMaxCloseInterestRate),
1719 Sig(sfCounterpartySignature, borrower),
1725 Sig(sfCounterpartySignature, lender),
1726 kCloseInterestRate(kMaxCloseInterestRate + 1),
1730 Sig(sfCounterpartySignature, lender),
1737 Sig(sfCounterpartySignature, borrower),
1738 kOverpaymentInterestRate(kMaxOverpaymentInterestRate),
1742 Sig(sfCounterpartySignature, borrower),
1748 Sig(sfCounterpartySignature, lender),
1749 kOverpaymentInterestRate(kMaxOverpaymentInterestRate + 1),
1753 Sig(sfCounterpartySignature, lender),
1760 Sig(sfCounterpartySignature, borrower),
1766 Sig(sfCounterpartySignature, lender),
1773 Sig(sfCounterpartySignature, borrower),
1779 Sig(sfCounterpartySignature, lender),
1786 Sig(sfCounterpartySignature, borrower),
1793 Sig(sfCounterpartySignature, lender),
1800 env(
set(borrower, broker.
brokerID, principalRequest),
1801 Sig(sfCounterpartySignature, lender),
1804 env(
signers(lender, 2, {{evan, 1}, {borrower, 1}}));
1805 env(
signers(borrower, 2, {{evan, 1}, {lender, 1}}));
1806 env(
set(borrower, broker.
brokerID, principalRequest),
1807 kCounterparty(lender),
1809 Msig(sfCounterpartySignature, evan, borrower),
1813 env(
set(borrower, broker.
brokerID, principalRequest),
1814 kCounterparty(lender),
1815 Msig(alice, issuer),
1816 Msig(sfCounterpartySignature, evan, borrower),
1820 env(
set(borrower, broker.
brokerID, principalRequest),
1821 kCounterparty(lender),
1823 Msig(sfCounterpartySignature, alice, issuer),
1829 env(
set(borrower, broker.
brokerID, principalRequest),
1830 kCounterparty(lender),
1832 Msig(sfCounterpartySignature, evan, borrower),
1837 env(
set(borrower, broker.
brokerID, principalRequest),
1838 Sig(sfCounterpartySignature, evan),
1843 kCounterparty(borrower),
1844 Sig(sfCounterpartySignature, borrower),
1848 env(
set(lender, badKeylet.key, principalRequest),
1849 Sig(sfCounterpartySignature, evan),
1853 env(
set(lender, badKeylet.key, principalRequest),
1854 kCounterparty(borrower),
1855 Sig(sfCounterpartySignature, borrower),
1859 env(
set(lender, broker.
brokerID, principalRequest),
1860 kCounterparty(alice),
1861 Sig(sfCounterpartySignature, alice),
1866 env(
set(evan, broker.
brokerID, totalVaultRequest + 1),
1867 Sig(sfCounterpartySignature, lender),
1873 env(
set(evan, broker.
brokerID, maxCoveredLoanRequest + 1),
1874 Sig(sfCounterpartySignature, lender),
1883 if (!BEAST_EXPECT(brokerSle))
1886 auto const vaultPseudo = [&]() {
1887 auto const vaultSle = env.
le(
keylet::vault(brokerSle->at(sfVaultID)));
1888 if (!BEAST_EXPECT(vaultSle))
1893 auto vaultPseudo =
Account(
"Vault pseudo-account", vaultSle->at(sfAccount));
1897 auto const [freeze, deepfreeze, unfreeze, expectedResult] =
1912 auto freeze = [&](
Account const& holder) {
1915 auto deepfreeze = [&](
Account const& holder) {
1918 auto unfreeze = [&](
Account const& holder) {
1920 issuer, holder[
iouCurrency_](0), tfClearFreeze | tfClearDeepFreeze));
1925 auto freeze = [&](
Account const& holder) {
1926 mptt.
set({.account = issuer, .holder = holder, .flags = tfMPTLock});
1928 auto unfreeze = [&](
Account const& holder) {
1929 mptt.
set({.account = issuer, .holder = holder, .flags = tfMPTUnlock});
1937 for (
auto const& account : {vaultPseudo, evan})
1943 env(
set(evan, broker.
brokerID, debtMaximumRequest),
1944 Sig(sfCounterpartySignature, lender),
1946 Ter(expectedResult));
1949 BEAST_EXPECT(unfreeze);
1955 env(
set(evan, broker.
brokerID, debtMaximumRequest + 1),
1956 Sig(sfCounterpartySignature, lender),
1972 for (
auto const& account : {
1980 deepfreeze(account);
1983 env(
set(evan, broker.
brokerID, debtMaximumRequest),
1984 Sig(sfCounterpartySignature, lender),
1986 Ter(expectedResult));
1989 BEAST_EXPECT(unfreeze);
1995 env(
set(evan, broker.
brokerID, debtMaximumRequest + 1),
1996 Sig(sfCounterpartySignature, lender),
2005 auto coverAvailable = [&env,
this](
uint256 const& brokerID,
Number const& expected) {
2007 BEAST_EXPECT(brokerSle))
2009 auto const available = brokerSle->at(sfCoverAvailable);
2010 BEAST_EXPECT(available == expected);
2017 BEAST_EXPECT(brokerSle))
2024 broker.
vaultScale(env), state.principalOutstanding.exponent())));
2033 state.totalValue - state.managementFeeOutstanding),
2039 auto replenishCover = [&env, &coverAvailable](
2042 Number const& startingCoverAvailable,
2043 Number const& amountToBeCovered) {
2044 coverAvailable(broker.
brokerID, startingCoverAvailable - amountToBeCovered);
2047 coverAvailable(broker.
brokerID, startingCoverAvailable);
2051 auto defaultImmediately = [&](
std::uint32_t baseFlag,
bool impair =
true) {
2052 return [&, impair, baseFlag](
2059 auto state =
getCurrentState(env, broker, loanKeylet, verifyLoanStatus);
2060 BEAST_EXPECT(state.flags == baseFlag);
2062 auto const& broker = verifyLoanStatus.
broker;
2063 auto const startingCoverAvailable = coverAvailable(
2071 env(manage(lender, loanKeylet.key, tfLoanImpair),
2076 state.flags |= tfLoanImpair;
2082 verifyLoanStatus(state);
2085 auto const nextDueDate = tp{d{state.nextPaymentDate}};
2089 env(manage(lender, loanKeylet.key, tfLoanDefault),
Ter(
tecTOO_SOON));
2093 env.
close(nextDueDate + 60s);
2095 auto const [amountToBeCovered, brokerAcct] = getDefaultInfo(state, broker);
2098 env(manage(lender, loanKeylet.key, tfLoanDefault));
2103 replenishCover(broker, brokerAcct, startingCoverAvailable, amountToBeCovered);
2105 state.flags |= tfLoanDefault;
2106 state.paymentRemaining = 0;
2107 state.totalValue = 0;
2108 state.principalOutstanding = 0;
2109 state.managementFeeOutstanding = 0;
2110 state.nextPaymentDate = 0;
2111 verifyLoanStatus(state);
2121 auto singlePayment = [&](
Keylet const& loanKeylet,
2130 verifyLoanStatus(state);
2143 if (!(state.flags & lsfLoanOverpayment))
2152 bool const hasFix313 = env.
current()->rules().enabled(fixCleanup3_1_3);
2155 baseFee * (
Number{15, -1} / kLoanPaymentsPerFeeIncrement + 1)};
2156 env(
pay(borrower, loanKeylet.
key, overpayAmount, tfLoanOverpayment),
2163 env(
pay(borrower, loanKeylet.
key, overpayAmount, tfLoanOverpayment),
2174 broker.
asset(state.periodicPayment * 2),
2175 tfLoanLatePayment | tfLoanFullPayment),
2179 broker.
asset(state.periodicPayment * 2),
2180 tfLoanLatePayment | tfLoanOverpayment),
2184 broker.
asset(state.periodicPayment * 2),
2185 tfLoanOverpayment | tfLoanFullPayment),
2189 broker.
asset(state.periodicPayment * 2),
2190 tfLoanLatePayment | tfLoanOverpayment | tfLoanFullPayment),
2194 auto const otherAsset =
2195 broker.
asset.
raw() == assets[0].raw() ? assets[1] : assets[0];
2200 env(
pay(borrower, loanKeylet.
key,
STAmount{broker.asset, 1}, txFlags),
2205 auto const borrowerBalanceBeforePayment = env.
balance(borrower, broker.
asset);
2207 BEAST_EXPECT(payoffAmount > state.principalOutstanding);
2210 auto const transactionAmount = payoffAmount + broker.
asset(10);
2216 (borrowerBalanceBeforePayment.number() * 2 / state.periodicPayment /
2217 kLoanPaymentsPerFeeIncrement +
2221 STAmount{broker.asset, borrowerBalanceBeforePayment.number() * 2},
2226 XRPAmount const goodFee{baseFee * (numPayments / kLoanPaymentsPerFeeIncrement + 1)};
2227 env(
pay(borrower, loanKeylet.
key, transactionAmount, txFlags),
Fee(goodFee));
2237 adjustment = badFee + goodFee;
2240 state.paymentRemaining = 0;
2241 state.principalOutstanding = 0;
2242 state.totalValue = 0;
2243 state.managementFeeOutstanding = 0;
2244 state.previousPaymentDate =
2245 state.nextPaymentDate + (state.paymentInterval * (numPayments - 1));
2246 state.nextPaymentDate = 0;
2247 verifyLoanStatus(state);
2249 verifyLoanStatus.checkPayment(
2250 state.loanScale, borrower, borrowerBalanceBeforePayment, payoffAmount, adjustment);
2258 return [&, baseFlag](
2262 auto state =
getCurrentState(env, broker, loanKeylet, verifyLoanStatus);
2263 env.
close(state.startDate + 20s);
2264 auto const loanAge = (env.
now() - state.startDate).count();
2265 BEAST_EXPECT(loanAge == 30);
2275 Number const interval = state.paymentInterval;
2279 STAmount const principalOutstanding{broker.
asset, state.principalOutstanding};
2281 broker.
asset, state.principalOutstanding * periodicRate * loanAge / interval};
2282 BEAST_EXPECT(accruedInterest == broker.
asset(
Number(1141552511415525, -19)));
2284 broker.
asset, state.principalOutstanding *
Number(36, -3)};
2285 BEAST_EXPECT(prepaymentPenalty == broker.
asset(36));
2288 principalOutstanding + accruedInterest + prepaymentPenalty + closePaymentFee,
2301 state.paymentRemaining * (state.periodicPayment + broker.
asset(2).value()));
2320 auto state =
getCurrentState(env, broker, loanKeylet, verifyLoanStatus);
2329 auto const startingPayments = state.paymentRemaining;
2330 STAmount const payoffAmount = [&]() {
2332 auto const rawPayoff =
2333 startingPayments * (state.periodicPayment + broker.
asset(2).value());
2336 payoffAmount == broker.
asset(
Number(1024014840244721, -12)),
2338 BEAST_EXPECT(payoffAmount > state.principalOutstanding);
2340 payoffAmount =
roundToScale(payoffAmount, state.loanScale);
2342 return payoffAmount;
2345 auto const totalPayoffValue =
2346 state.totalValue + startingPayments * broker.
asset(2).value();
2347 STAmount const totalPayoffAmount{broker.
asset, totalPayoffValue};
2350 totalPayoffAmount == payoffAmount,
2351 "Payoff amount: " +
to_string(payoffAmount) +
2352 ". Total Value: " +
to_string(totalPayoffAmount));
2359 state.paymentRemaining,
2371 "Loan overpayment allowed - Impair and Default",
2381 defaultImmediately(lsfLoanOverpayment));
2385 "Loan overpayment prohibited - Impair and Default",
2395 defaultImmediately(0));
2399 "Loan overpayment allowed - Default without Impair",
2409 defaultImmediately(lsfLoanOverpayment,
false));
2413 "Loan overpayment prohibited - Default without Impair",
2423 defaultImmediately(0,
false));
2427 "Loan overpayment prohibited - Pay off immediately",
2441 "Loan overpayment allowed - Pay off immediately",
2451 fullPayment(lsfLoanOverpayment));
2455 "Loan overpayment prohibited - Combine all payments",
2465 combineAllPayments(0));
2469 "Loan overpayment allowed - Combine all payments",
2479 combineAllPayments(lsfLoanOverpayment));
2483 "Loan overpayment prohibited - Make payments",
2497 auto state =
getCurrentState(env, broker, loanKeylet, verifyLoanStatus);
2498 BEAST_EXPECT(state.flags == 0);
2501 verifyLoanStatus(state);
2503 env.
close(state.startDate + 20s);
2504 auto const loanAge = (env.
now() - state.startDate).count();
2505 BEAST_EXPECT(loanAge == 30);
2515 Number const interval = state.paymentInterval;
2519 STAmount const roundedPeriodicPayment{
2523 testcase << currencyLabel <<
" Payment components: "
2524 <<
"Payments remaining, rawInterest, rawPrincipal, "
2525 "rawMFee, trackedValueDelta, trackedPrincipalDelta, "
2526 "trackedInterestDelta, trackedMgmtFeeDelta, special";
2528 auto const serviceFee = broker.
asset(2);
2531 roundedPeriodicPayment ==
2540 roundedPeriodicPayment + serviceFee,
2556 state.periodicPayment,
2558 state.paymentRemaining,
2562 state.principalOutstanding,
2563 state.managementFeeOutstanding);
2564 testcase << currencyLabel <<
" Loan starting state: " << state.paymentRemaining
2565 <<
", " << raw.interestDue <<
", " << raw.principalOutstanding <<
", "
2566 << raw.managementFeeDue <<
", " << rounded.valueOutstanding <<
", "
2567 << rounded.principalOutstanding <<
", " << rounded.interestDue <<
", "
2568 << rounded.managementFeeDue;
2578 transactionAmount ==
2584 auto const initialState = state;
2586 .trackedValueDelta = 0,
2587 .trackedPrincipalDelta = 0,
2588 .trackedManagementFeeDelta = 0};
2589 Number totalInterestPaid = 0;
2594 state.periodicPayment,
2596 state.paymentRemaining,
2599 while (state.paymentRemaining > 0)
2607 state.principalOutstanding,
2608 state.managementFeeOutstanding,
2609 state.periodicPayment,
2611 state.paymentRemaining,
2616 paymentComponents.trackedValueDelta <= roundedPeriodicPayment,
2617 "Delta: " +
to_string(paymentComponents.trackedValueDelta) +
2618 ", periodic payment: " +
to_string(roundedPeriodicPayment));
2622 state.periodicPayment,
2624 state.paymentRemaining - 1,
2628 testcase << currencyLabel <<
" Payment components: " << state.paymentRemaining
2630 << deltas.
managementFee <<
", " << paymentComponents.trackedValueDelta
2631 <<
", " << paymentComponents.trackedPrincipalDelta <<
", "
2632 << paymentComponents.trackedInterestPart() <<
", "
2633 << paymentComponents.trackedManagementFeeDelta <<
", "
2634 << [&]() ->
char const* {
2635 if (paymentComponents.specialCase ==
2638 if (paymentComponents.specialCase ==
2644 auto const totalDueAmount =
STAmount{
2645 broker.
asset, paymentComponents.trackedValueDelta + serviceFee.number()};
2653 Number const diff = totalDue - totalDueAmount;
2656 diff == beast::kZero ||
2657 (diff > beast::kZero &&
2659 (state.loanScale - diff.
exponent() > 13))));
2662 paymentComponents.trackedValueDelta ==
2663 paymentComponents.trackedPrincipalDelta +
2664 paymentComponents.trackedInterestPart() +
2665 paymentComponents.trackedManagementFeeDelta);
2668 paymentComponents.trackedValueDelta <= roundedPeriodicPayment);
2671 state.paymentRemaining < 12 ||
2683 paymentComponents.trackedPrincipalDelta >= beast::kZero &&
2684 paymentComponents.trackedPrincipalDelta <= state.principalOutstanding);
2687 paymentComponents.trackedPrincipalDelta == state.principalOutstanding);
2690 (state.periodicPayment.exponent() -
2692 state.periodicPayment)
2695 auto const borrowerBalanceBeforePayment = env.
balance(borrower, broker.
asset);
2700 env(manage(lender, loanKeylet.
key, tfLoanImpair));
2706 env(
pay(borrower, loanKeylet.
key, transactionAmount));
2714 adjustment = env.
current()->fees().base;
2718 verifyLoanStatus.checkPayment(
2721 borrowerBalanceBeforePayment,
2725 --state.paymentRemaining;
2726 state.previousPaymentDate = state.nextPaymentDate;
2729 state.paymentRemaining = 0;
2730 state.nextPaymentDate = 0;
2734 state.nextPaymentDate += state.paymentInterval;
2736 state.principalOutstanding -= paymentComponents.trackedPrincipalDelta;
2737 state.managementFeeOutstanding -= paymentComponents.trackedManagementFeeDelta;
2738 state.totalValue -= paymentComponents.trackedValueDelta;
2740 verifyLoanStatus(state);
2745 paymentComponents.trackedManagementFeeDelta;
2746 totalInterestPaid += paymentComponents.trackedInterestPart();
2747 ++totalPaymentsMade;
2749 currentTrueState = nextTrueState;
2753 BEAST_EXPECT(state.paymentRemaining == 0);
2754 BEAST_EXPECT(state.principalOutstanding == 0);
2764 totalInterestPaid ==
2765 initialState.totalValue -
2766 (initialState.principalOutstanding +
2767 initialState.managementFeeOutstanding));
2768 BEAST_EXPECT(totalPaymentsMade == initialState.paymentRemaining);
2787 if (!BEAST_EXPECT(timed))
2793 auto const start = clock_type::now();
2818 using namespace loan;
2820 auto const state =
getCurrentState(env, broker, verifyLoanStatus.keylet);
2821 auto const serviceFee = broker.
asset(2).value();
2826 broker.
asset, state.periodicPayment + serviceFee, state.loanScale)};
2829 time(
"single payment", [&]() { env(
pay(borrower, loanKeylet.
key, totalDue)); });
2833 auto const numPayments = (state.paymentRemaining - 2);
2834 STAmount const bigPayment{broker.
asset, totalDue * numPayments};
2835 XRPAmount const bigFee{baseFee * (numPayments / kLoanPaymentsPerFeeIncrement + 1)};
2836 time(
"ten payments", [&]() {
2837 env(
pay(borrower, loanKeylet.
key, bigPayment),
Fee(bigFee));
2841 time(
"final payment", [&]() {
2843 env(
pay(borrower, loanKeylet.
key, totalDue +
STAmount{broker.asset, 1}));
2850 "Loan overpayment allowed - Explicit overpayment",
2864 "Loan overpayment prohibited - Late payment",
2878 "Loan overpayment allowed - Late payment",
2892 "Loan overpayment allowed - Late payment and overpayment",
2910 using namespace jtx;
2912 Account const issuer{
"issuer"};
2913 Account const lender{
"lender"};
2914 Account const borrower{
"borrower"};
2919 bool authorizeBorrower =
false;
2920 int initialXRP = 1'000'000;
2923 auto const testCase = [&,
this](
2926 CaseArgs args = {}) {
2927 Env env(*
this, features);
2928 env.
fund(
XRP(args.initialXRP), issuer, lender, borrower);
2930 if (args.requireAuth)
2932 env(
fset(issuer, asfRequireAuth));
2942 {.flags = tfMPTCanTransfer | tfMPTCanLock |
2943 (args.requireAuth ? tfMPTRequireAuth :
kNone)});
2949 if (args.requireAuth)
2951 mptt.
authorize({.account = issuer, .holder = lender});
2952 if (args.authorizeBorrower)
2953 mptt.
authorize({.account = issuer, .holder = borrower});
2957 env(
pay(issuer, lender, mptAsset(10'000'000)));
2962 env(
trust(lender, iouAsset(10'000'000)));
2963 env(
trust(borrower, iouAsset(10'000'000)));
2965 if (args.requireAuth)
2967 env(
trust(issuer, iouAsset(0), lender, tfSetfAuth));
2968 env(
pay(issuer, lender, iouAsset(10'000'000)));
2969 if (args.authorizeBorrower)
2971 env(
trust(issuer, iouAsset(0), borrower, tfSetfAuth));
2972 env(
pay(issuer, borrower, iouAsset(10'000)));
2977 env(
pay(issuer, lender, iouAsset(10'000'000)));
2978 env(
pay(issuer, borrower, iouAsset(10'000)));
2986 for (
auto const& asset : assets)
2992 mptTest(env, brokers[0], mptt);
2994 iouTest(env, brokers[1]);
2999 using namespace loan;
3000 Number const principalRequest = broker.
asset(1'000).value();
3002 testcase(
"MPT issuer is borrower, issuer submits");
3003 env(
set(issuer, broker.
brokerID, principalRequest),
3004 kCounterparty(lender),
3005 Sig(sfCounterpartySignature, lender),
3008 testcase(
"MPT issuer is borrower, lender submits");
3009 env(
set(lender, broker.
brokerID, principalRequest),
3010 kCounterparty(issuer),
3011 Sig(sfCounterpartySignature, issuer),
3015 using namespace loan;
3016 Number const principalRequest = broker.
asset(1'000).value();
3018 testcase(
"IOU issuer is borrower, issuer submits");
3019 env(
set(issuer, broker.
brokerID, principalRequest),
3020 kCounterparty(lender),
3021 Sig(sfCounterpartySignature, lender),
3024 testcase(
"IOU issuer is borrower, lender submits");
3025 env(
set(lender, broker.
brokerID, principalRequest),
3026 kCounterparty(issuer),
3027 Sig(sfCounterpartySignature, issuer),
3030 CaseArgs{.requireAuth =
true});
3034 using namespace loan;
3035 Number const principalRequest = broker.
asset(1'000).value();
3037 testcase(
"MPT unauthorized borrower, borrower submits");
3038 env(
set(borrower, broker.
brokerID, principalRequest),
3039 kCounterparty(lender),
3040 Sig(sfCounterpartySignature, lender),
3044 testcase(
"MPT unauthorized borrower, lender submits");
3045 env(
set(lender, broker.
brokerID, principalRequest),
3046 kCounterparty(borrower),
3047 Sig(sfCounterpartySignature, borrower),
3052 using namespace loan;
3053 Number const principalRequest = broker.
asset(1'000).value();
3055 testcase(
"IOU unauthorized borrower, borrower submits");
3056 env(
set(borrower, broker.
brokerID, principalRequest),
3057 kCounterparty(lender),
3058 Sig(sfCounterpartySignature, lender),
3062 testcase(
"IOU unauthorized borrower, lender submits");
3063 env(
set(lender, broker.
brokerID, principalRequest),
3064 kCounterparty(borrower),
3065 Sig(sfCounterpartySignature, borrower),
3069 CaseArgs{.requireAuth =
true});
3080 using namespace loan;
3081 Number const principalRequest = broker.
asset(1'000).value();
3084 "MPT authorized borrower, borrower submits, borrower has "
3086 mptt.
authorize({.account = borrower, .flags = tfMPTUnauthorize});
3090 auto const sleMPT1 = env.
le(mptoken);
3091 BEAST_EXPECT(sleMPT1 ==
nullptr);
3094 env(
noop(borrower),
Fee(
XRP((acctReserve * 2) + (incReserve * 2))));
3098 env(
set(borrower, broker.
brokerID, principalRequest),
3099 kCounterparty(lender),
3100 Sig(sfCounterpartySignature, lender),
3102 Ter{tecINSUFFICIENT_RESERVE});
3106 env(
pay(issuer, borrower,
XRP(incReserve)));
3108 env(
set(borrower, broker.
brokerID, principalRequest),
3109 kCounterparty(lender),
3110 Sig(sfCounterpartySignature, lender),
3114 auto const sleMPT2 = env.
le(mptoken);
3115 BEAST_EXPECT(sleMPT2 !=
nullptr);
3118 CaseArgs{.initialXRP = (acctReserve * 2) + (incReserve * 8) + 1});
3123 using namespace loan;
3124 Number const principalRequest = broker.
asset(1'000).value();
3127 "IOU authorized borrower, borrower submits, borrower has "
3133 env(
pay(borrower, issuer, broker.
asset(10'000)));
3136 auto const sleLine1 = env.
le(trustline);
3137 BEAST_EXPECT(sleLine1 ==
nullptr);
3140 env(
noop(borrower),
Fee(
XRP((acctReserve * 2) + (incReserve * 2))));
3144 env(
set(borrower, broker.
brokerID, principalRequest),
3145 kCounterparty(lender),
3146 Sig(sfCounterpartySignature, lender),
3148 Ter{tecNO_LINE_INSUF_RESERVE});
3152 env(
pay(issuer, borrower,
XRP(incReserve)));
3154 env(
set(borrower, broker.
brokerID, principalRequest),
3155 kCounterparty(lender),
3156 Sig(sfCounterpartySignature, lender),
3160 auto const sleLine2 = env.
le(trustline);
3161 BEAST_EXPECT(sleLine2 !=
nullptr);
3163 CaseArgs{.initialXRP = (acctReserve * 2) + (incReserve * 8) + 1});
3167 using namespace loan;
3168 Number const principalRequest = broker.
asset(1'000).value();
3171 "MPT authorized borrower, borrower submits, lender has "
3174 auto const sleMPT1 = env.
le(mptoken);
3175 BEAST_EXPECT(sleMPT1 !=
nullptr);
3177 env(
pay(lender, issuer, broker.
asset(sleMPT1->at(sfMPTAmount))));
3180 mptt.
authorize({.account = lender, .flags = tfMPTUnauthorize});
3183 auto const sleMPT2 = env.
le(mptoken);
3184 BEAST_EXPECT(sleMPT2 ==
nullptr);
3191 env(
set(borrower, broker.
brokerID, principalRequest),
3192 kLoanOriginationFee(broker.
asset(1).value()),
3193 kCounterparty(lender),
3194 Sig(sfCounterpartySignature, lender),
3196 Ter{tecINSUFFICIENT_RESERVE});
3200 env(
pay(issuer, lender,
XRP(incReserve)));
3202 env(
set(borrower, broker.
brokerID, principalRequest),
3203 kLoanOriginationFee(broker.
asset(1).value()),
3204 kCounterparty(lender),
3205 Sig(sfCounterpartySignature, lender),
3209 auto const sleMPT3 = env.
le(mptoken);
3210 BEAST_EXPECT(sleMPT3 !=
nullptr);
3213 CaseArgs{.initialXRP = (acctReserve * 2) + (incReserve * 8) + 1});
3218 using namespace loan;
3219 Number const principalRequest = broker.
asset(1'000).value();
3222 "IOU authorized borrower, borrower submits, lender has no "
3229 auto const sleLine1 = env.
le(trustline);
3230 BEAST_EXPECT(sleLine1 !=
nullptr);
3232 env(
pay(lender, issuer, broker.
asset(
abs(sleLine1->at(sfBalance).value()))));
3234 auto const sleLine2 = env.
le(trustline);
3235 BEAST_EXPECT(sleLine2 ==
nullptr);
3242 env(
set(borrower, broker.
brokerID, principalRequest),
3243 kLoanOriginationFee(broker.
asset(1).value()),
3244 kCounterparty(lender),
3245 Sig(sfCounterpartySignature, lender),
3247 Ter{tecNO_LINE_INSUF_RESERVE});
3251 env(
pay(issuer, lender,
XRP(incReserve)));
3253 env(
set(borrower, broker.
brokerID, principalRequest),
3254 kLoanOriginationFee(broker.
asset(1).value()),
3255 kCounterparty(lender),
3256 Sig(sfCounterpartySignature, lender),
3260 auto const sleLine3 = env.
le(trustline);
3261 BEAST_EXPECT(sleLine3 !=
nullptr);
3263 CaseArgs{.initialXRP = (acctReserve * 2) + (incReserve * 8) + 1});
3267 using namespace loan;
3268 Number const principalRequest = broker.
asset(1'000).value();
3270 testcase(
"MPT authorized borrower, unauthorized lender");
3272 auto const sleMPT1 = env.
le(mptoken);
3273 BEAST_EXPECT(sleMPT1 !=
nullptr);
3275 env(
pay(lender, issuer, broker.
asset(sleMPT1->at(sfMPTAmount))));
3278 mptt.
authorize({.account = lender, .flags = tfMPTUnauthorize});
3281 auto const sleMPT2 = env.
le(mptoken);
3282 BEAST_EXPECT(sleMPT2 ==
nullptr);
3285 env(
set(borrower, broker.
brokerID, principalRequest),
3286 kLoanOriginationFee(broker.
asset(1).value()),
3287 kCounterparty(lender),
3288 Sig(sfCounterpartySignature, lender),
3294 env(
set(borrower, broker.
brokerID, principalRequest),
3295 kCounterparty(lender),
3296 Sig(sfCounterpartySignature, lender),
3302 auto const sleMPT3 = env.
le(mptoken);
3303 BEAST_EXPECT(sleMPT3 ==
nullptr);
3306 CaseArgs{.requireAuth =
true, .authorizeBorrower =
true});
3310 using namespace loan;
3311 Number const principalRequest = broker.
asset(1'000).value();
3313 testcase(
"MPT authorized borrower, borrower submits");
3314 env(
set(borrower, broker.
brokerID, principalRequest),
3315 kCounterparty(lender),
3316 Sig(sfCounterpartySignature, lender),
3320 using namespace loan;
3321 Number const principalRequest = broker.
asset(1'000).value();
3323 testcase(
"IOU authorized borrower, borrower submits");
3324 env(
set(borrower, broker.
brokerID, principalRequest),
3325 kCounterparty(lender),
3326 Sig(sfCounterpartySignature, lender),
3329 CaseArgs{.requireAuth =
true, .authorizeBorrower =
true});
3333 using namespace loan;
3334 Number const principalRequest = broker.
asset(1'000).value();
3336 testcase(
"MPT authorized borrower, lender submits");
3337 env(
set(lender, broker.
brokerID, principalRequest),
3338 kCounterparty(borrower),
3339 Sig(sfCounterpartySignature, borrower),
3343 using namespace loan;
3344 Number const principalRequest = broker.
asset(1'000).value();
3346 testcase(
"IOU authorized borrower, lender submits");
3347 env(
set(lender, broker.
brokerID, principalRequest),
3348 kCounterparty(borrower),
3349 Sig(sfCounterpartySignature, borrower),
3352 CaseArgs{.requireAuth =
true, .authorizeBorrower =
true});
3356 auto const msigSetup = [&](
Env& env,
Account const& account) {
3364 using namespace loan;
3365 msigSetup(env, lender);
3366 Number const principalRequest = broker.
asset(1'000).value();
3369 "MPT authorized borrower, borrower submits, lender "
3371 env(
set(borrower, broker.
brokerID, principalRequest),
3372 kCounterparty(lender),
3373 Msig(sfCounterpartySignature, alice, bella),
3377 using namespace loan;
3378 msigSetup(env, lender);
3379 Number const principalRequest = broker.
asset(1'000).value();
3382 "IOU authorized borrower, borrower submits, lender "
3384 env(
set(borrower, broker.
brokerID, principalRequest),
3385 kCounterparty(lender),
3386 Msig(sfCounterpartySignature, alice, bella),
3389 CaseArgs{.requireAuth =
true, .authorizeBorrower =
true});
3393 using namespace loan;
3394 msigSetup(env, borrower);
3395 Number const principalRequest = broker.
asset(1'000).value();
3398 "MPT authorized borrower, lender submits, borrower "
3400 env(
set(lender, broker.
brokerID, principalRequest),
3401 kCounterparty(borrower),
3402 Msig(sfCounterpartySignature, alice, bella),
3406 using namespace loan;
3407 msigSetup(env, borrower);
3408 Number const principalRequest = broker.
asset(1'000).value();
3411 "IOU authorized borrower, lender submits, borrower "
3413 env(
set(lender, broker.
brokerID, principalRequest),
3414 kCounterparty(borrower),
3415 Msig(sfCounterpartySignature, alice, bella),
3418 CaseArgs{.requireAuth =
true, .authorizeBorrower =
true});
3422 using namespace loan;
3423 Number const principalRequest = broker.
asset(1'000).value();
3424 Vault const vault{env};
3425 auto tx = vault.set({.owner = lender, .
id = broker.
vaultID});
3430 testcase(
"Vault at maximum value");
3431 env(
set(issuer, broker.
brokerID, principalRequest),
3432 kCounterparty(lender),
3434 Sig(sfCounterpartySignature, lender),
3442 using namespace loan;
3443 Number const principalRequest = broker.
asset(1'000).value();
3444 Vault const vault{env};
3445 auto tx = vault.set({.owner = lender, .
id = broker.
vaultID});
3446 tx[sfAssetsMaximum] =
3451 testcase(
"Vault maximum value exceeded");
3452 env(
set(issuer, broker.
brokerID, principalRequest),
3453 kCounterparty(lender),
3455 Sig(sfCounterpartySignature, lender),
3458 kPaymentInterval(3600 * 24),
3468 using namespace jtx;
3472 Env env(*
this, features);
3474 Account const issuer{
"issuer"};
3477 Account const lender{
"lender"};
3479 Account const borrower{
"borrower"};
3493 env(
trust(lender, iouAsset(10'000'000)));
3494 env(
trust(borrower, iouAsset(10'000'000)));
3495 env(
trust(evan, iouAsset(10'000'000)));
3496 env(
pay(issuer, evan, iouAsset(1'000'000)));
3497 env(
pay(issuer, lender, iouAsset(10'000'000)));
3499 env(
pay(issuer, borrower, iouAsset(10'000)));
3503 mptt.
create({.flags = tfMPTCanClawback | tfMPTCanTransfer | tfMPTCanLock});
3509 env(
pay(issuer, lender, mptAsset(10'000'000)));
3510 env(
pay(issuer, evan, mptAsset(1'000'000)));
3512 env(
pay(issuer, borrower, mptAsset(10'000)));
3515 std::array const assets{iouAsset, xrpAsset, mptAsset};
3520 for (
auto const& asset : assets)
3527 for (
auto const& broker : brokers)
3529 for (
int amountExponent = 3; amountExponent >= 3; --amountExponent)
3531 Number const loanAmount{1, amountExponent};
3532 for (
int interestExponent = 0; interestExponent >= 0; --interestExponent)
3534 testCaseWrapper(env, mptt, assets, broker, loanAmount, interestExponent);
3539 BEAST_EXPECT(brokerSle))
3541 BEAST_EXPECT(brokerSle->at(sfOwnerCount) == 0);
3542 BEAST_EXPECT(brokerSle->at(sfDebtTotal) == 0);
3544 auto const coverAvailable = brokerSle->at(sfCoverAvailable);
3550 BEAST_EXPECT(brokerSle && brokerSle->at(sfCoverAvailable) == 0);
3563 using namespace jtx;
3564 using namespace std::chrono_literals;
3567 Env env(*
this, features);
3569 Account const issuer{
"issuer"};
3572 Account const lender{
"lender"};
3585 using namespace loan;
3587 auto const loanSetFee =
Fee(env.
current()->fees().base * 2);
3588 Number const principalRequest{1, 3};
3592 auto createJson = env.
json(
3601 if (
auto const jt = env.
jt(createJson); BEAST_EXPECT(jt.stx))
3607 BEAST_EXPECT(jr.isMember(jss::result));
3608 auto const jResult = jr[jss::result];
3609 BEAST_EXPECT(jResult[jss::error] ==
"invalidTransaction");
3611 jResult[jss::error_exception] ==
3612 "fails local checks: Transaction has bad signature.");
3617 counterpartyJson[sfTxnSignature] = createJson[sfTxnSignature];
3618 counterpartyJson[sfSigningPubKey] = createJson[sfSigningPubKey];
3619 if (!BEAST_EXPECT(!createJson.isMember(jss::Signers)))
3620 counterpartyJson[sfSigners] = createJson[sfSigners];
3623 createJson = env.
json(createJson,
Json(sfCounterpartySignature, counterpartyJson));
3628 auto const startDate = env.
current()->header().parentCloseTime;
3632 auto const res = env.
rpc(
"account_objects", lender.
human());
3633 auto const objects = res[jss::result][jss::account_objects];
3636 BEAST_EXPECT(objects.size() == 4);
3637 for (
auto const&
object : objects)
3639 ++types[
object[sfLedgerEntryType].asString()];
3641 BEAST_EXPECT(types.
size() == 4);
3642 for (
std::string const type : {
"MPToken",
"Vault",
"LoanBroker",
"Loan"})
3644 BEAST_EXPECT(types[type] == 1);
3647 auto const loanID = [&]() {
3649 params[jss::account] = lender.
human();
3650 params[jss::type] =
"Loan";
3651 auto const res = env.
rpc(
"json",
"account_objects",
to_string(params));
3652 auto const objects = res[jss::result][jss::account_objects];
3654 BEAST_EXPECT(objects.size() == 1);
3656 auto const loan = objects[0u];
3657 BEAST_EXPECT(
loan[sfBorrower] == lender.
human());
3660 BEAST_EXPECT(!
loan.isMember(sfCloseInterestRate));
3661 BEAST_EXPECT(!
loan.isMember(sfClosePaymentFee));
3662 BEAST_EXPECT(
loan[sfFlags] == 0);
3663 BEAST_EXPECT(
loan[sfGracePeriod] == 60);
3664 BEAST_EXPECT(!
loan.isMember(sfInterestRate));
3665 BEAST_EXPECT(!
loan.isMember(sfLateInterestRate));
3666 BEAST_EXPECT(!
loan.isMember(sfLatePaymentFee));
3668 BEAST_EXPECT(!
loan.isMember(sfLoanOriginationFee));
3669 BEAST_EXPECT(
loan[sfLoanSequence] == 1);
3670 BEAST_EXPECT(!
loan.isMember(sfLoanServiceFee));
3671 BEAST_EXPECT(
loan[sfNextPaymentDueDate] ==
loan[sfStartDate].asUInt() + 60);
3672 BEAST_EXPECT(!
loan.isMember(sfOverpaymentFee));
3673 BEAST_EXPECT(!
loan.isMember(sfOverpaymentInterestRate));
3674 BEAST_EXPECT(
loan[sfPaymentInterval] == 60);
3675 BEAST_EXPECT(
loan[sfPeriodicPayment] ==
"1000000000");
3676 BEAST_EXPECT(
loan[sfPaymentRemaining] == 1);
3677 BEAST_EXPECT(!
loan.isMember(sfPreviousPaymentDueDate));
3678 BEAST_EXPECT(
loan[sfPrincipalOutstanding] ==
"1000000000");
3679 BEAST_EXPECT(
loan[sfTotalValueOutstanding] ==
"1000000000");
3680 BEAST_EXPECT(!
loan.isMember(sfLoanScale));
3681 BEAST_EXPECT(
loan[sfStartDate].asUInt() == startDate.time_since_epoch().count());
3683 return loan[
"index"].asString();
3687 env.
close(startDate);
3690 env(
pay(lender, loanKeylet.
key, broker.
asset(1000)));
3697 testcase <<
"Batch Bypass Counterparty";
3701 [](
auto const& disabled) {
return disabled == ttLOAN_BROKER_SET; });
3703 using namespace jtx;
3704 using namespace std::chrono_literals;
3705 Env env(*
this, features);
3707 Account const lender{
"lender"};
3708 Account const borrower{
"borrower"};
3718 using namespace loan;
3720 auto const loanSetFee =
Fee(env.
current()->fees().base * 2);
3721 Number const principalRequest{1, 3};
3723 auto forgedLoanSet =
set(borrower, broker.
brokerID, principalRequest, 0);
3728 sigObject[jss::SigningPubKey] =
strHex(lender.
pk().
slice());
3731 parse(randomData).addWithoutSigningFields(ss);
3733 sigObject[jss::TxnSignature] =
strHex(
Slice{sig.data(), sig.size()});
3740 auto const seq = env.
seq(borrower);
3743 env(
batch::outer(borrower, seq, batchFee, tfAllOrNothing),
3752 params[jss::account] = borrower.
human();
3753 params[jss::type] =
"Loan";
3754 auto const res = env.
rpc(
"json",
"account_objects",
to_string(params));
3755 auto const objects = res[jss::result][jss::account_objects];
3756 BEAST_EXPECT(objects.size() == 0);
3764 testcase <<
"Wrong Max Debt Behavior";
3766 using namespace jtx;
3767 using namespace std::chrono_literals;
3768 Env env(*
this, features);
3770 Account const issuer{
"issuer"};
3771 Account const lender{
"lender"};
3782 BEAST_EXPECT(brokerSle))
3784 BEAST_EXPECT(brokerSle->at(sfDebtMaximum) == 0);
3787 using namespace loan;
3789 auto const loanSetFee =
Fee(env.
current()->fees().base * 2);
3790 Number const principalRequest{1, 3};
3792 auto createJson = env.
json(
set(lender, broker.
brokerID, principalRequest),
Fee(loanSetFee));
3795 counterpartyJson[sfTxnSignature] = createJson[sfTxnSignature];
3796 counterpartyJson[sfSigningPubKey] = createJson[sfSigningPubKey];
3797 if (!BEAST_EXPECT(!createJson.isMember(jss::Signers)))
3798 counterpartyJson[sfSigners] = createJson[sfSigners];
3800 createJson = env.
json(createJson,
Json(sfCounterpartySignature, counterpartyJson));
3810 testcase <<
"LoanPay xrpl::detail::computePeriodicPayment : "
3813 using namespace jtx;
3814 using namespace std::chrono_literals;
3815 Env env(*
this, features);
3817 Account const issuer{
"issuer"};
3818 Account const lender{
"lender"};
3819 Account const borrower{
"borrower"};
3828 using namespace loan;
3830 auto const loanSetFee =
Fee(env.
current()->fees().base * 2);
3831 Number const principalRequest{640562, -5};
3833 Number const serviceFee{2462611968};
3836 auto createJson = env.
json(
3839 kLoanServiceFee(serviceFee),
3840 kPaymentTotal(numPayments),
3843 createJson[
"CloseInterestRate"] = 55374;
3844 createJson[
"ClosePaymentFee"] =
"3825205248";
3845 createJson[
"LatePaymentFee"] =
"237";
3846 createJson[
"LoanOriginationFee"] =
"0";
3847 createJson[
"OverpaymentFee"] = 35167;
3848 createJson[
"OverpaymentInterestRate"] = 1360;
3849 createJson[
"PaymentInterval"] = 727;
3852 auto const loanSequence = brokerStateBefore->at(sfLoanSequence);
3855 createJson = env.
json(createJson,
Sig(sfCounterpartySignature, lender));
3863 Number const actualPrincipal{6};
3865 createJson[sfPrincipalRequested] = actualPrincipal;
3866 createJson.removeMember(sfSequence.jsonName);
3867 createJson = env.
json(createJson,
Sig(sfCounterpartySignature, lender));
3881 using namespace jtx;
3883 Env env(*
this, features);
3885 auto lowerFee = [&]() {
3891 auto const baseFee = env.
current()->fees().base;
3896 auto const lenderPass =
"lender";
3899 env.
fund(
XRP(1'000'000), alice, lender, borrower);
3911 txJson[sfTransactionType] =
"AccountSet";
3912 txJson[sfAccount] = borrower.
human();
3914 auto const signParams = [&]() {
3916 signParams[jss::passphrase] = borrowerPass;
3917 signParams[jss::key_type] =
"ed25519";
3918 signParams[jss::tx_json] = txJson;
3921 auto const jSign = env.
rpc(
"json",
"sign",
to_string(signParams));
3922 BEAST_EXPECT(jSign.isMember(jss::result) && jSign[jss::result].isMember(jss::tx_json));
3923 auto txSignResult = jSign[jss::result][jss::tx_json];
3924 auto txSignBlob = jSign[jss::result][jss::tx_blob].asString();
3925 txSignResult.removeMember(jss::hash);
3927 auto const jtx = env.
jt(txJson,
Sig(borrower));
3928 BEAST_EXPECT(txSignResult ==
jtx.jv);
3931 auto const jSubmit = env.
rpc(
"submit", txSignBlob);
3933 jSubmit.isMember(jss::result) &&
3934 jSubmit[jss::result].isMember(jss::engine_result) &&
3935 jSubmit[jss::result][jss::engine_result].asString() ==
"tesSUCCESS");
3942 testcase(
"RPC LoanSet - illegal signature_target");
3945 txJson[sfTransactionType] =
"AccountSet";
3946 txJson[sfAccount] = borrower.
human();
3948 auto const borrowerSignParams = [&]() {
3950 params[jss::passphrase] = borrowerPass;
3951 params[jss::key_type] =
"ed25519";
3952 params[jss::signature_target] =
"Destination";
3953 params[jss::tx_json] = txJson;
3956 auto const jSignBorrower = env.
rpc(
"json",
"sign",
to_string(borrowerSignParams));
3958 jSignBorrower.isMember(jss::result) &&
3959 jSignBorrower[jss::result].isMember(jss::error) &&
3960 jSignBorrower[jss::result][jss::error] ==
"invalidParams" &&
3961 jSignBorrower[jss::result].isMember(jss::error_message) &&
3962 jSignBorrower[jss::result][jss::error_message] ==
"Destination");
3965 testcase(
"RPC LoanSet - sign and submit borrower initiated");
3968 txJson[sfTransactionType] =
"LoanSet";
3969 txJson[sfAccount] = borrower.
human();
3970 txJson[sfCounterparty] = lender.
human();
3971 txJson[sfLoanBrokerID] =
3972 "FF924CD18A236C2B49CF8E80A351CEAC6A10171DC9F110025646894FEC"
3975 txJson[sfPrincipalRequested] =
"100000000";
3976 txJson[sfPaymentTotal] = 10000;
3977 txJson[sfPaymentInterval] = 3600;
3978 txJson[sfGracePeriod] = 300;
3979 txJson[sfFlags] = 65536;
3980 txJson[sfFee] =
to_string(24 * baseFee / 10);
3983 auto const borrowerSignParams = [&]() {
3985 params[jss::passphrase] = borrowerPass;
3986 params[jss::key_type] =
"ed25519";
3987 params[jss::tx_json] = txJson;
3990 auto const jSignBorrower = env.
rpc(
"json",
"sign",
to_string(borrowerSignParams));
3992 jSignBorrower.isMember(jss::result) &&
3993 jSignBorrower[jss::result].isMember(jss::tx_json),
3995 auto const txBorrowerSignResult = jSignBorrower[jss::result][jss::tx_json];
3996 auto const txBorrowerSignBlob = jSignBorrower[jss::result][jss::tx_blob].asString();
4002 auto const jSubmitBlob = env.
rpc(
"submit", txBorrowerSignBlob);
4003 BEAST_EXPECT(jSubmitBlob.isMember(jss::result));
4004 auto const jSubmitBlobResult = jSubmitBlob[jss::result];
4005 BEAST_EXPECT(jSubmitBlobResult.isMember(jss::tx_json));
4009 jSubmitBlobResult.isMember(jss::engine_result) &&
4010 jSubmitBlobResult[jss::engine_result].asString() ==
"temBAD_SIGNER");
4015 auto const lenderSignParams = [&]() {
4017 params[jss::passphrase] = lenderPass;
4018 params[jss::key_type] =
"ed25519";
4019 params[jss::signature_target] =
"CounterpartySignature";
4020 params[jss::tx_json] = txBorrowerSignResult;
4023 auto const jSignLender = env.
rpc(
"json",
"sign",
to_string(lenderSignParams));
4025 jSignLender.isMember(jss::result) &&
4026 jSignLender[jss::result].isMember(jss::tx_json));
4027 auto const txLenderSignResult = jSignLender[jss::result][jss::tx_json];
4028 auto const txLenderSignBlob = jSignLender[jss::result][jss::tx_blob].asString();
4032 auto const jSubmitBlob = env.
rpc(
"submit", txLenderSignBlob);
4033 BEAST_EXPECT(jSubmitBlob.isMember(jss::result));
4034 auto const jSubmitBlobResult = jSubmitBlob[jss::result];
4035 BEAST_EXPECT(jSubmitBlobResult.isMember(jss::tx_json));
4036 auto const jSubmitBlobTx = jSubmitBlobResult[jss::tx_json];
4041 jSubmitBlobResult.isMember(jss::engine_result) &&
4042 jSubmitBlobResult[jss::engine_result].asString() ==
"tecNO_ENTRY",
4046 !jSubmitBlob.isMember(jss::error) && !jSubmitBlobResult.isMember(jss::error));
4053 auto const jSubmitJson = env.
rpc(
"json",
"submit",
to_string(lenderSignParams));
4054 BEAST_EXPECT(jSubmitJson.isMember(jss::result));
4055 auto const jSubmitJsonResult = jSubmitJson[jss::result];
4056 BEAST_EXPECT(jSubmitJsonResult.isMember(jss::tx_json));
4057 auto const jSubmitJsonTx = jSubmitJsonResult[jss::tx_json];
4061 jSubmitJsonResult.isMember(jss::engine_result) &&
4062 jSubmitJsonResult[jss::engine_result].asString() ==
"tefPAST_SEQ",
4066 !jSubmitJson.isMember(jss::error) && !jSubmitJsonResult.isMember(jss::error));
4068 BEAST_EXPECT(jSubmitBlobTx == jSubmitJsonTx);
4072 testcase(
"RPC LoanSet - sign and submit lender initiated");
4075 txJson[sfTransactionType] =
"LoanSet";
4076 txJson[sfAccount] = lender.
human();
4077 txJson[sfCounterparty] = borrower.
human();
4078 txJson[sfLoanBrokerID] =
4079 "FF924CD18A236C2B49CF8E80A351CEAC6A10171DC9F110025646894FEC"
4082 txJson[sfPrincipalRequested] =
"100000000";
4083 txJson[sfPaymentTotal] = 10000;
4084 txJson[sfPaymentInterval] = 3600;
4085 txJson[sfGracePeriod] = 300;
4086 txJson[sfFlags] = 65536;
4087 txJson[sfFee] =
to_string(24 * baseFee / 10);
4090 auto const lenderSignParams = [&]() {
4092 params[jss::passphrase] = lenderPass;
4093 params[jss::key_type] =
"ed25519";
4094 params[jss::tx_json] = txJson;
4097 auto const jSignLender = env.
rpc(
"json",
"sign",
to_string(lenderSignParams));
4099 jSignLender.isMember(jss::result) &&
4100 jSignLender[jss::result].isMember(jss::tx_json));
4101 auto const txLenderSignResult = jSignLender[jss::result][jss::tx_json];
4102 auto const txLenderSignBlob = jSignLender[jss::result][jss::tx_blob].asString();
4108 auto const jSubmitBlob = env.
rpc(
"submit", txLenderSignBlob);
4109 BEAST_EXPECT(jSubmitBlob.isMember(jss::result));
4110 auto const jSubmitBlobResult = jSubmitBlob[jss::result];
4111 BEAST_EXPECT(jSubmitBlobResult.isMember(jss::tx_json));
4115 jSubmitBlobResult.isMember(jss::engine_result) &&
4116 jSubmitBlobResult[jss::engine_result].asString() ==
"temBAD_SIGNER");
4121 auto const borrowerSignParams = [&]() {
4123 params[jss::passphrase] = borrowerPass;
4124 params[jss::key_type] =
"ed25519";
4125 params[jss::signature_target] =
"CounterpartySignature";
4126 params[jss::tx_json] = txLenderSignResult;
4129 auto const jSignBorrower = env.
rpc(
"json",
"sign",
to_string(borrowerSignParams));
4131 jSignBorrower.isMember(jss::result) &&
4132 jSignBorrower[jss::result].isMember(jss::tx_json));
4133 auto const txBorrowerSignResult = jSignBorrower[jss::result][jss::tx_json];
4134 auto const txBorrowerSignBlob = jSignBorrower[jss::result][jss::tx_blob].asString();
4138 auto const jSubmitBlob = env.
rpc(
"submit", txBorrowerSignBlob);
4139 BEAST_EXPECT(jSubmitBlob.isMember(jss::result));
4140 auto const jSubmitBlobResult = jSubmitBlob[jss::result];
4141 BEAST_EXPECT(jSubmitBlobResult.isMember(jss::tx_json));
4142 auto const jSubmitBlobTx = jSubmitBlobResult[jss::tx_json];
4147 jSubmitBlobResult.isMember(jss::engine_result) &&
4148 jSubmitBlobResult[jss::engine_result].asString() ==
"tecNO_ENTRY",
4152 !jSubmitBlob.isMember(jss::error) && !jSubmitBlobResult.isMember(jss::error));
4159 auto const jSubmitJson = env.
rpc(
"json",
"submit",
to_string(borrowerSignParams));
4160 BEAST_EXPECT(jSubmitJson.isMember(jss::result));
4161 auto const jSubmitJsonResult = jSubmitJson[jss::result];
4162 BEAST_EXPECT(jSubmitJsonResult.isMember(jss::tx_json));
4163 auto const jSubmitJsonTx = jSubmitJsonResult[jss::tx_json];
4167 jSubmitJsonResult.isMember(jss::engine_result) &&
4168 jSubmitJsonResult[jss::engine_result].asString() ==
"tefPAST_SEQ",
4172 !jSubmitJson.isMember(jss::error) && !jSubmitJsonResult.isMember(jss::error));
4174 BEAST_EXPECT(jSubmitBlobTx == jSubmitJsonTx);
4181 testcase <<
"Service Fee On Broker Deep Freeze";
4182 using namespace jtx;
4183 using namespace loan;
4184 Account const issuer(
"issuer");
4185 Account const borrower(
"borrower");
4186 Account const broker(
"broker");
4187 auto const iou = issuer[
"IOU"];
4189 for (
bool const deepFreeze : {
true,
false})
4193 auto getCoverBalance = [&](
BrokerInfo const& brokerInfo,
auto const& accountField) {
4197 auto const account = le->at(accountField);
4199 BEAST_EXPECT(sleLine))
4201 STAmount balance = sleLine->at(sfBalance);
4202 if (account > issuer.
id())
4210 env.
fund(
XRP(20'000), issuer, broker, borrower);
4213 env(
trust(broker, iou(20'000'000)));
4214 env(
pay(issuer, broker, iou(10'000'000)));
4219 BEAST_EXPECT(getCoverBalance(brokerInfo, sfAccount) == iou(1'000));
4224 Sig(sfCounterpartySignature, broker),
4225 kLoanServiceFee(iou(100).value()),
4226 kPaymentInterval(100),
4230 env(
trust(borrower, iou(20'000'000)));
4233 env(
pay(issuer, borrower, iou(500)));
4240 env(
trust(issuer, broker[
"IOU"](0), tfSetFreeze | tfSetDeepFreeze));
4250 BEAST_EXPECT(getCoverBalance(brokerInfo, sfAccount) == iou(1'100));
4251 BEAST_EXPECT(getCoverBalance(brokerInfo, sfOwner) == iou(8'999'000));
4256 BEAST_EXPECT(getCoverBalance(brokerInfo, sfOwner) == iou(8'999'100));
4257 BEAST_EXPECT(getCoverBalance(brokerInfo, sfAccount) == iou(1'000));
4268 using namespace jtx;
4269 using namespace loan;
4270 Account const issuer(
"issuer");
4271 Account const borrower = issuer;
4272 Account const lender(
"lender");
4275 env.
fund(
XRP(1'000), issuer, lender);
4277 static constexpr std::int64_t kIssuerBalance = 10'000'000;
4279 {.env = env, .issuer = issuer, .holders = {lender}, .pay = kIssuerBalance});
4285 auto const loanSetFee =
Fee(env.
current()->fees().base * 2);
4287 env(
set(borrower, broker.
brokerID, 200),
Sig(sfCounterpartySignature, lender), loanSetFee);
4292 BEAST_EXPECT(env.
balance(issuer, asset) == asset(-kIssuerBalance + 200));
4295 env(
pay(borrower, loanKeylet.
key, asset(200)));
4298 BEAST_EXPECT(env.
balance(issuer, asset) == asset(-kIssuerBalance));
4305 using namespace jtx;
4306 using namespace loan;
4322 using namespace jtx;
4323 using namespace loan;
4331 env(manage(alice, beast::kZero, tfLoanDefault),
Ter(
temINVALID));
4339 using namespace jtx;
4340 using namespace loan;
4341 Account const lender{
"lender"};
4342 Account const issuer{
"issuer"};
4343 Account const borrower{
"borrower"};
4344 auto const iou = issuer[
"IOU"];
4348 env.
fund(
XRP(1'000), lender, issuer, borrower);
4349 env(
trust(lender, iou(10'000'000)));
4350 env(
pay(issuer, lender, iou(5'000'000)));
4353 auto const loanSetFee =
Fee(env.
current()->fees().base * 2);
4354 STAmount const debtMaximumRequest = brokerInfo.
asset(1'000).value();
4356 env(
set(borrower, brokerInfo.
brokerID, debtMaximumRequest),
4357 Sig(sfCounterpartySignature, lender),
4365 env(
fset(issuer, asfGlobalFreeze));
4372 env(
fclear(issuer, asfGlobalFreeze));
4377 BEAST_EXPECT(brokerSle))
4379 return Account{
"pseudo", brokerSle->at(sfAccount)};
4382 return std::nullopt;
4388 env(
trust(issuer, lender[
"IOU"](1'000), lender, tfSetFreeze | tfSetDeepFreeze));
4390 issuer, (*pseudoBroker)[
"IOU"](1'000), *pseudoBroker, tfSetFreeze | tfSetDeepFreeze));
4398 env(
trust(issuer, lender[
"IOU"](1'000), tfClearFreeze | tfClearDeepFreeze));
4404 env(
pay(borrower, loanKeylet.
key, debtMaximumRequest, tfLoanLatePayment));
4417 using namespace jtx;
4418 using namespace loan;
4419 Account const lender{
"lender"};
4420 Account const issuer{
"issuer"};
4421 Account const borrower{
"borrower"};
4422 auto const iou = issuer[
"IOU"];
4424 auto testWrapper = [&](
auto&&
test) {
4426 env.
fund(
XRP(1'000), lender, issuer, borrower);
4427 env(
trust(lender, iou(10'000'000)));
4428 env(
pay(issuer, lender, iou(5'000'000)));
4431 auto const loanSetFee =
Fee(env.
current()->fees().base * 2);
4432 Number const debtMaximumRequest = brokerInfo.
asset(1'000).value();
4433 test(env, brokerInfo, loanSetFee, debtMaximumRequest);
4437 testWrapper([&](
Env& env,
4440 Number const& debtMaximumRequest) {
4445 env(
set(borrower, brokerInfo.
brokerID, debtMaximumRequest),
4446 Sig(sfCounterpartySignature, lender),
4452 env(
set(borrower, brokerInfo.
brokerID, debtMaximumRequest),
4453 Sig(sfCounterpartySignature, lender),
4459 env(
set(borrower, brokerInfo.
brokerID, debtMaximumRequest),
4460 Sig(sfCounterpartySignature, lender),
4461 kPaymentInterval(120),
4468 auto jv =
set(borrower,
uint256{}, debtMaximumRequest);
4472 jv[sfLoanBrokerID] = id;
4474 Sig(sfCounterpartySignature, lender),
4491 JTx const tx = env.
jt(
4492 set(borrower, brokerInfo.
brokerID, debtMaximumRequest),
4493 Sig(sfCounterpartySignature, lender),
4496 auto counterpartySig = local.
getFieldObject(sfCounterpartySignature);
4497 auto badPubKey = counterpartySig.
getFieldVL(sfSigningPubKey);
4498 badPubKey[20] ^= 0xAA;
4499 counterpartySig.setFieldVL(sfSigningPubKey, badPubKey);
4503 auto res = env.
rpc(
"json",
"submit",
to_string(jvResult))[
"result"];
4505 res[jss::error] ==
"invalidTransaction" &&
4506 res[jss::error_exception] ==
4507 "fails local checks: Counterparty: Invalid signature.");
4511 testWrapper([&](
Env& env,
4514 Number const& debtMaximumRequest) {
4518 env(
fclear(issuer, asfDefaultRipple));
4520 env(
set(borrower, brokerInfo.
brokerID, debtMaximumRequest),
4521 Sig(sfCounterpartySignature, lender),
4527 testWrapper([&](
Env& env,
4530 Number const& debtMaximumRequest) {
4531 auto const amt = env.
balance(borrower) -
4533 env(
pay(borrower, issuer, amt));
4536 env(
set(borrower, brokerInfo.
brokerID, debtMaximumRequest),
4537 Sig(sfCounterpartySignature, lender),
4542 env(
pay(issuer, borrower, amt));
4543 env(
fset(issuer, asfGlobalFreeze));
4546 env(
set(borrower, brokerInfo.
brokerID, debtMaximumRequest),
4547 Sig(sfCounterpartySignature, lender),
4557 testcase <<
"LoanSet trigger xrpl::accountSendMPT : minimum amount "
4560 using namespace jtx;
4561 using namespace std::chrono_literals;
4562 Env env(*
this, features);
4564 Account const issuer{
"issuer"};
4565 Account const lender{
"lender"};
4566 Account const borrower{
"borrower"};
4568 env.
fund(
XRP(1'000'000), issuer, lender, borrower);
4572 mptt.
create({.flags = tfMPTCanClawback | tfMPTCanTransfer | tfMPTCanLock});
4576 env(
pay(issuer, lender, mptAsset(2'000'000)));
4577 env(
pay(issuer, borrower, mptAsset(1'000)));
4582 using namespace loan;
4584 auto const loanSetFee =
Fee(env.
current()->fees().base * 2);
4585 Number const principalRequest{1, 3};
4587 auto createJson = env.
json(
4592 createJson[
"CloseInterestRate"] = 76671;
4593 createJson[
"ClosePaymentFee"] =
"2061925410";
4594 createJson[
"GracePeriod"] = 434;
4595 createJson[
"InterestRate"] = 50302;
4596 createJson[
"LateInterestRate"] = 30322;
4597 createJson[
"LatePaymentFee"] =
"294427911";
4598 createJson[
"LoanOriginationFee"] =
"3250635102";
4599 createJson[
"LoanServiceFee"] =
"9557386";
4600 createJson[
"OverpaymentFee"] = 51249;
4601 createJson[
"OverpaymentInterestRate"] = 14304;
4602 createJson[
"PaymentInterval"] = 434;
4603 createJson[
"PaymentTotal"] =
"2891743748";
4604 createJson[
"PrincipalRequested"] =
"8516.98";
4608 createJson = env.
json(createJson,
Sig(sfCounterpartySignature, lender));
4617 testcase <<
"LoanPay xrpl::LoanPay::doApply : debtDecrease "
4620 using namespace jtx;
4621 using namespace std::chrono_literals;
4623 Env env(*
this, features);
4625 Account const issuer{
"issuer"};
4626 Account const lender{
"lender"};
4627 Account const borrower{
"borrower"};
4629 env.
fund(
XRP(1'000'000), issuer, lender, borrower);
4633 auto trustLenderTx = env.
json(
trust(lender, iouAsset(1'000'000'000)));
4635 auto trustBorrowerTx = env.
json(
trust(borrower, iouAsset(1'000'000'000)));
4636 env(trustBorrowerTx);
4637 auto payLenderTx =
pay(issuer, lender, iouAsset(100'000'000));
4639 auto payIssuerTx =
pay(issuer, borrower, iouAsset(1'000'000));
4645 using namespace loan;
4647 auto const baseFee = env.
current()->fees().base;
4648 auto const loanSetFee =
Fee(baseFee * 2);
4649 Number const principalRequest{1, 3};
4651 auto createJson = env.
json(
4656 createJson[
"ClosePaymentFee"] =
"0";
4657 createJson[
"GracePeriod"] = 60;
4658 createJson[
"InterestRate"] = 24346;
4659 createJson[
"LateInterestRate"] = 65535;
4660 createJson[
"LatePaymentFee"] =
"0";
4661 createJson[
"LoanOriginationFee"] =
"218";
4662 createJson[
"LoanServiceFee"] =
"0";
4663 createJson[
"PaymentInterval"] = 60;
4664 createJson[
"PaymentTotal"] = 5678;
4665 createJson[
"PrincipalRequested"] =
"9924.81";
4668 auto const loanSequence = brokerStateBefore->at(sfLoanSequence);
4671 createJson = env.
json(createJson,
Sig(sfCounterpartySignature, lender));
4675 auto const pseudoAcct = [&]() {
4677 if (!BEAST_EXPECT(brokerSle))
4679 auto const brokerPseudo = brokerSle->at(sfAccount);
4680 return Account(
"Broker pseudo-account", brokerPseudo);
4685 verifyLoanStatus(originalState);
4687 Number const payment{3'269'349'176'470'588, -12};
4690 ((payment / originalState.periodicPayment) / kLoanPaymentsPerFeeIncrement + 1)};
4693 BEAST_EXPECT(
to_string(payment) ==
"3269.349176470588");
4699 isRounded(broker.
asset, newState.managementFeeOutstanding, originalState.loanScale));
4700 BEAST_EXPECT(newState.managementFeeOutstanding < originalState.managementFeeOutstanding);
4701 BEAST_EXPECT(
isRounded(broker.
asset, newState.totalValue, originalState.loanScale));
4703 isRounded(broker.
asset, newState.principalOutstanding, originalState.loanScale));
4710 testcase <<
"xrpl::loanComputePaymentParts : valid total interest";
4712 using namespace jtx;
4713 using namespace std::chrono_literals;
4714 Env env(*
this, features);
4716 Account const issuer{
"issuer"};
4717 Account const lender{
"lender"};
4718 Account const borrower{
"borrower"};
4720 env.
fund(
XRP(1'000'000), issuer, lender, borrower);
4724 auto trustLenderTx = env.
json(
trust(lender, iouAsset(1'000'000'000)));
4726 auto trustBorrowerTx = env.
json(
trust(borrower, iouAsset(1'000'000'000)));
4727 env(trustBorrowerTx);
4728 auto payLenderTx =
pay(issuer, lender, iouAsset(100'000'000));
4730 auto payIssuerTx =
pay(issuer, borrower, iouAsset(1'000'000));
4736 using namespace loan;
4738 auto const loanSetFee =
Fee(env.
current()->fees().base * 2);
4739 Number const principalRequest{1, 3};
4741 auto createJson = env.
json(
4746 createJson[
"CloseInterestRate"] = 47299;
4747 createJson[
"ClosePaymentFee"] =
"3985819770";
4748 createJson[
"InterestRate"] = 92;
4749 createJson[
"LatePaymentFee"] =
"3866894865";
4750 createJson[
"LoanOriginationFee"] =
"0";
4751 createJson[
"LoanServiceFee"] =
"2348810240";
4752 createJson[
"OverpaymentFee"] = 58545;
4753 createJson[
"PaymentInterval"] = 60;
4754 createJson[
"PaymentTotal"] = 1;
4755 createJson[
"PrincipalRequested"] =
"0.000763058";
4758 auto const loanSequence = brokerStateBefore->at(sfLoanSequence);
4761 createJson = env.
json(createJson,
Sig(sfCounterpartySignature, lender));
4766 loanPayTx[
"Amount"][
"value"] =
"0.000281284125490196";
4774 bool const feeCapped = features[fixCleanup3_1_3];
4777 testcase <<
"DoS LoanPay: fee calculation " << (feeCapped ?
"capped" :
"uncapped");
4779 using namespace jtx;
4780 using namespace std::chrono_literals;
4782 Env env(*
this, features);
4784 Account const issuer{
"issuer"};
4785 Account const lender{
"lender"};
4786 Account const borrower{
"borrower"};
4788 env.
fund(
XRP(1'000'000), issuer, lender, borrower);
4791 BEAST_EXPECT(feeCapped == env.
current()->rules().enabled(fixCleanup3_1_3));
4794 env(
trust(lender, iouAsset(100'000'000)));
4795 env(
trust(borrower, iouAsset(100'000'000)));
4796 env(
pay(issuer, lender, iouAsset(10'000'000)));
4797 env(
pay(issuer, borrower, iouAsset(1'000)));
4802 using namespace loan;
4804 auto const loanSetFee =
Fee(env.
current()->fees().base * 2);
4805 Number const principalRequest{3959'37, -2};
4806 auto const baseFee = env.
current()->fees().base;
4808 auto const createJson = env.
json(
4812 kClosePaymentFee(0),
4820 kPaymentInterval(60),
4821 kPaymentTotal(3239184));
4827 auto const loanSequence = brokerStateBefore->at(sfLoanSequence);
4830 env(createJson,
Sig(sfCounterpartySignature, lender));
4833 auto const roundedPayment = [&]() {
4835 BEAST_EXPECT(stateBefore.paymentRemaining == 3239184);
4836 BEAST_EXPECT(stateBefore.paymentRemaining > kLoanMaximumPaymentsPerTransaction);
4840 stateBefore.periodicPayment,
4841 stateBefore.loanScale,
4845 auto test = [&](
int const payFactor,
4846 int const feeFactor,
4849 BEAST_EXPECT(stateBefore.paymentRemaining <= 3239184);
4850 BEAST_EXPECT(stateBefore.paymentRemaining > kLoanMaximumPaymentsPerTransaction);
4852 Number const amount = roundedPayment * payFactor;
4854 XRPAmount const payFee{baseFee * feeFactor};
4855 env(loanPayTx,
Ter(expectedTer),
Fee(payFee));
4858 ?
std::min(kLoanMaximumPaymentsPerTransaction, payFactor)
4863 stateAfter.paymentRemaining == stateBefore.paymentRemaining - expectedChange);
4867 kLoanMaximumPaymentsPerTransaction / kLoanPaymentsPerFeeIncrement;
4873 test(1819878, 363976);
4875 test(1819878, kMaxFeeIncrements, failWithoutFix);
4878 test(kLoanMaximumPaymentsPerTransaction, kMaxFeeIncrements);
4880 test(kLoanMaximumPaymentsPerTransaction, kMaxFeeIncrements + 10);
4884 test(kLoanPaymentsPerFeeIncrement * 2, 2);
4889 kLoanMaximumPaymentsPerTransaction - 10,
4890 ((kLoanMaximumPaymentsPerTransaction - 10) / kLoanPaymentsPerFeeIncrement) - 1,
4893 kLoanMaximumPaymentsPerTransaction - 10,
4894 ((kLoanMaximumPaymentsPerTransaction - 10) / kLoanPaymentsPerFeeIncrement));
4897 kLoanMaximumPaymentsPerTransaction - 10,
4898 ((kLoanMaximumPaymentsPerTransaction - 10) / kLoanPaymentsPerFeeIncrement) + 3);
4900 for (
int under = 1; under < kLoanPaymentsPerFeeIncrement; ++under)
4902 test(kLoanMaximumPaymentsPerTransaction - under, kMaxFeeIncrements - 1,
telINSUF_FEE_P);
4903 test(kLoanMaximumPaymentsPerTransaction - under, kMaxFeeIncrements);
4907 kLoanMaximumPaymentsPerTransaction - kLoanPaymentsPerFeeIncrement,
4908 kMaxFeeIncrements - 1);
4910 test(kLoanMaximumPaymentsPerTransaction - kLoanPaymentsPerFeeIncrement, kMaxFeeIncrements);
4917 testcase <<
"xrpl::loanComputePaymentParts : totalPrincipalPaid "
4920 using namespace jtx;
4921 using namespace std::chrono_literals;
4923 Env env(*
this, features);
4925 Account const issuer{
"issuer"};
4926 Account const lender{
"lender"};
4927 Account const borrower{
"borrower"};
4929 env.
fund(
XRP(1'000'000), issuer, lender, borrower);
4933 auto trustLenderTx = env.
json(
trust(lender, iouAsset(1'000'000'000)));
4935 auto trustBorrowerTx = env.
json(
trust(borrower, iouAsset(1'000'000'000)));
4936 env(trustBorrowerTx);
4937 auto payLenderTx =
pay(issuer, lender, iouAsset(100'000'000));
4939 auto payIssuerTx =
pay(issuer, borrower, iouAsset(1'000'000));
4945 using namespace loan;
4947 auto const loanSetFee =
Fee(env.
current()->fees().base * 2);
4948 Number const principalRequest{1, 3};
4950 auto createJson = env.
json(
4955 createJson[
"ClosePaymentFee"] =
"0";
4956 createJson[
"InterestRate"] = 24346;
4957 createJson[
"LateInterestRate"] = 65535;
4958 createJson[
"LatePaymentFee"] =
"0";
4959 createJson[
"LoanOriginationFee"] =
"218";
4960 createJson[
"LoanServiceFee"] =
"0";
4961 createJson[
"PaymentInterval"] = 60;
4962 createJson[
"PaymentTotal"] = 5678;
4963 createJson[
"PrincipalRequested"] =
"9924.81";
4966 auto const loanSequence = brokerStateBefore->at(sfLoanSequence);
4969 createJson = env.
json(createJson,
Sig(sfCounterpartySignature, lender));
4973 auto const baseFee = env.
current()->fees().base;
4979 Number const amount{3074'745'058'823'529, -12};
4980 BEAST_EXPECT(
to_string(amount) ==
"3074.745058823529");
4983 (amount / stateBefore.periodicPayment / kLoanPaymentsPerFeeIncrement + 1)};
4984 loanPayTx[
"Amount"][
"value"] =
to_string(amount);
4991 Number const amount{6732'118'170'944'051, -12};
4992 BEAST_EXPECT(
to_string(amount) ==
"6732.118170944051");
4995 (amount / stateBefore.periodicPayment / kLoanPaymentsPerFeeIncrement + 1)};
4996 loanPayTx[
"Amount"][
"value"] =
to_string(amount);
5001 auto const stateAfter = getCurrentState(env, broker, keylet);
5003 BEAST_EXPECT(stateAfter.totalValue >= stateAfter.principalOutstanding);
5005 BEAST_EXPECT(stateBefore.principalOutstanding >= stateAfter.principalOutstanding);
5007 BEAST_EXPECT(stateBefore.totalValue >= stateAfter.totalValue);
5011 (stateBefore.totalValue - stateAfter.totalValue) >=
5012 (stateBefore.principalOutstanding - stateAfter.principalOutstanding));
5019 testcase <<
"xrpl::loanComputePaymentParts : loanValueChange rounded";
5021 using namespace jtx;
5022 using namespace std::chrono_literals;
5024 Env env(*
this, features);
5026 Account const issuer{
"issuer"};
5027 Account const lender{
"lender"};
5028 Account const borrower{
"borrower"};
5030 env.
fund(
XRP(1'000'000), issuer, lender, borrower);
5034 auto trustLenderTx = env.
json(
trust(lender, iouAsset(1'000'000'000)));
5036 auto trustBorrowerTx = env.
json(
trust(borrower, iouAsset(1'000'000'000)));
5037 env(trustBorrowerTx);
5038 auto payLenderTx =
pay(issuer, lender, iouAsset(100'000'000));
5040 auto payIssuerTx =
pay(issuer, borrower, iouAsset(10'000'000));
5051 using namespace loan;
5053 auto const loanSetFee =
Fee(env.
current()->fees().base * 2);
5054 Number const principalRequest{1, 3};
5056 auto createJson = env.
json(
5061 createJson[
"ClosePaymentFee"] =
"0";
5062 createJson[
"InterestRate"] = 12833;
5063 createJson[
"LateInterestRate"] = 77048;
5064 createJson[
"LatePaymentFee"] =
"0";
5065 createJson[
"LoanOriginationFee"] =
"218";
5066 createJson[
"LoanServiceFee"] =
"0";
5067 createJson[
"PaymentInterval"] = 752;
5068 createJson[
"PaymentTotal"] = 5678;
5069 createJson[
"PrincipalRequested"] =
"9924.81";
5072 auto const loanSequence = brokerStateBefore->at(sfLoanSequence);
5075 createJson = env.
json(createJson,
Sig(sfCounterpartySignature, lender));
5079 auto const baseFee = env.
current()->fees().base;
5082 BEAST_EXPECT(stateBefore.paymentRemaining == 5678);
5083 BEAST_EXPECT(stateBefore.paymentRemaining > kLoanMaximumPaymentsPerTransaction);
5086 Number const amount{9924'81, -2};
5087 BEAST_EXPECT(
to_string(amount) ==
"9924.81");
5089 baseFee * (amount / stateBefore.periodicPayment / kLoanPaymentsPerFeeIncrement + 1)};
5090 loanPayTx[
"Amount"][
"value"] = to_string(amount);
5091 env(loanPayTx, Fee(payFee), Ter(tesSUCCESS));
5094 auto const stateAfter = getCurrentState(env, broker, keylet);
5096 stateAfter.paymentRemaining ==
5097 stateBefore.paymentRemaining - kLoanMaximumPaymentsPerTransaction);
5104 testcase <<
"Prevent nextPaymentDueDate overflow";
5106 using namespace jtx;
5107 using namespace std::chrono_literals;
5109 Env env{*
this, features};
5111 Account const issuer{
"issuer"};
5112 Account const lender{
"lender"};
5113 Account const borrower{
"borrower"};
5115 env.
fund(
XRP(1'000'000), issuer, lender, borrower);
5119 auto trustLenderTx = env.
json(
trust(lender, iouAsset(1'000'000'000)));
5121 auto trustBorrowerTx = env.
json(
trust(borrower, iouAsset(1'000'000'000)));
5122 env(trustBorrowerTx);
5123 auto payLenderTx =
pay(issuer, lender, iouAsset(100'000'000));
5125 auto payIssuerTx =
pay(issuer, borrower, iouAsset(10'000'000));
5132 using namespace loan;
5134 auto const loanSetFee =
Fee(env.
current()->fees().base * 2);
5136 using timeType =
decltype(sfNextPaymentDueDate)::type::value_type;
5139 static_assert(kMaxTime == 4'294'967'295);
5141 auto const baseJson = [&]() {
5142 auto createJson = env.
json(
5145 kClosePaymentFee(0),
5150 kLoanOriginationFee(218),
5158 auto const baseFee = env.
current()->fees().base;
5160 auto parentCloseTime = [&]() {
5161 return env.
current()->parentCloseTime().time_since_epoch().count();
5163 auto maxLoanTime = [&]() {
5164 auto const startDate = parentCloseTime();
5166 BEAST_EXPECT(startDate >= 50);
5168 return kMaxTime - startDate;
5173 auto const interval = maxLoanTime() + 1;
5174 auto const total = 1;
5175 auto createJson = env.
json(baseJson, kPaymentInterval(interval), kPaymentTotal(total));
5177 env(createJson,
Sig(sfCounterpartySignature, lender),
Ter(
tecKILLED));
5183 auto const interval = 60;
5184 auto const total = maxLoanTime() + 1;
5185 auto createJson = env.
json(baseJson, kPaymentInterval(interval), kPaymentTotal(total));
5187 env(createJson,
Sig(sfCounterpartySignature, lender),
Ter(
tecKILLED));
5193 auto const interval = maxLoanTime() + 1;
5194 auto const total = 1;
5195 auto const grace = interval;
5196 auto createJson = env.
json(
5197 baseJson, kPaymentInterval(interval), kPaymentTotal(total), kGracePeriod(grace));
5200 env(createJson,
Sig(sfCounterpartySignature, lender),
Ter(
tecKILLED));
5205 auto const interval = 1'000'000'000;
5206 auto const total = 10;
5207 auto createJson = env.
json(baseJson, kPaymentInterval(interval), kPaymentTotal(total));
5209 env(createJson,
Sig(sfCounterpartySignature, lender),
Ter(
tecKILLED));
5215 auto const interval = 60;
5216 auto const total = 1'000'000'000;
5217 auto createJson = env.
json(baseJson, kPaymentInterval(interval), kPaymentTotal(total));
5219 env(createJson,
Sig(sfCounterpartySignature, lender),
Ter(
tecKILLED));
5225 auto const total = 60;
5226 auto const interval = (maxLoanTime() - total) / total;
5227 auto const grace = interval;
5228 auto createJson = env.
json(
5229 baseJson, kPaymentInterval(interval), kPaymentTotal(total), kGracePeriod(grace));
5231 env(createJson,
Sig(sfCounterpartySignature, lender),
Ter(
tecKILLED));
5237 auto const loanSequence = brokerStateBefore->at(sfLoanSequence);
5240 auto const grace = 100;
5241 auto const interval = maxLoanTime() - grace;
5242 auto const total = 1;
5243 auto createJson = env.
json(
5244 baseJson, kPaymentInterval(interval), kPaymentTotal(total), kGracePeriod(grace));
5250 auto const meta = env.
meta();
5251 if (BEAST_EXPECT(meta))
5253 BEAST_EXPECT(meta->at(sfTransactionResult) ==
tecKILLED);
5257 auto const loanSle = env.
le(
keylet);
5259 BEAST_EXPECT(!loanSle);
5264 auto const loanSequence = brokerStateBefore->at(sfLoanSequence);
5267 auto const closeStartDate = ((parentCloseTime() / 10) + 1) * 10;
5268 auto const grace = 5'000;
5269 auto const interval = kMaxTime - closeStartDate - grace;
5270 auto const total = 1;
5271 auto createJson = env.
json(
5272 baseJson, kPaymentInterval(interval), kPaymentTotal(total), kGracePeriod(grace));
5278 auto const meta = env.
meta();
5279 if (BEAST_EXPECT(meta))
5281 BEAST_EXPECT(meta->at(sfTransactionResult) ==
tesSUCCESS);
5286 BEAST_EXPECT(afterState.nextPaymentDate == kMaxTime - grace);
5287 BEAST_EXPECT(afterState.previousPaymentDate == 0);
5288 BEAST_EXPECT(afterState.paymentRemaining == 1);
5293 env(
pay(issuer, borrower, iouAsset(
Number{1'055'524'81, -2})));
5296 auto const closeStartDate = ((parentCloseTime() / 10) + 1) * 10;
5297 auto const grace = 5'000;
5298 auto const maxLoanTime = kMaxTime - closeStartDate - grace;
5299 auto const total = [&]() {
5300 if (maxLoanTime % 5 == 0)
5302 if (maxLoanTime % 3 == 0)
5304 if (maxLoanTime % 2 == 0)
5308 if (!BEAST_EXPECT(total != 0))
5313 auto const loanSequence = brokerState->at(sfLoanSequence);
5316 auto const interval = maxLoanTime / total;
5317 auto createJson = env.
json(
5318 baseJson, kPaymentInterval(interval), kPaymentTotal(total), kGracePeriod(grace));
5325 BEAST_EXPECT(beforeState.nextPaymentDate == closeStartDate + interval);
5326 BEAST_EXPECT(beforeState.previousPaymentDate == 0);
5327 BEAST_EXPECT(beforeState.paymentRemaining == total);
5328 BEAST_EXPECT(beforeState.periodicPayment > 0);
5333 Number const payment = beforeState.periodicPayment * (total - 1);
5334 XRPAmount const payFee{baseFee * ((total - 1) / kLoanPaymentsPerFeeIncrement + 1)};
5337 auto loanPayTx = env.
json(
pay(borrower,
keylet.key, paymentAmount),
Fee(payFee));
5344 BEAST_EXPECT(afterState.paymentRemaining == 1);
5345 BEAST_EXPECT(afterState.nextPaymentDate == kMaxTime - grace);
5346 BEAST_EXPECT(afterState.previousPaymentDate == kMaxTime - grace - interval);
5353 testcase(
"Require Auth - Implicit Pseudo-account authorization");
5354 using namespace jtx;
5355 using namespace loan;
5356 Account const lender{
"lender"};
5357 Account const issuer{
"issuer"};
5358 Account const borrower{
"borrower"};
5361 env.
fund(
XRP(100'000), issuer, lender, borrower);
5367 .holders = {lender, borrower},
5368 .flags =
kMptDexFlags | tfMPTRequireAuth | tfMPTCanClawback | tfMPTCanLock,
5372 env(
pay(issuer, lender, asset(5'000'000)));
5375 auto const loanSetFee =
Fee(env.
current()->fees().base * 2);
5376 STAmount const debtMaximumRequest = brokerInfo.
asset(1'000).value();
5378 auto forUnauthAuth = [&](
auto&& doTx) {
5379 for (
auto const flag : {tfMPTUnauthorize, 0u})
5381 asset.authorize({.account = issuer, .holder = borrower, .flags = flag});
5391 env(
set(borrower, brokerInfo.
brokerID, debtMaximumRequest),
5392 Sig(sfCounterpartySignature, lender),
5403 env(
pay(borrower, loanKeylet.
key, debtMaximumRequest), err);
5410 testcase(
"Lending: CanTrade disabled has no impact");
5411 using namespace jtx;
5412 using namespace loan;
5417 Account const issuer{
"issuer"};
5418 Account const lender{
"lender"};
5419 Account const borrower{
"borrower"};
5421 env.
fund(
XRP(1'000'000), issuer, lender, borrower);
5427 .holders = {lender, borrower},
5428 .flags = tfMPTCanTransfer | tfMPTCanLock,
5431 env(
pay(issuer, lender, asset(10'000'000)));
5432 env(
pay(issuer, borrower, asset(100'000)));
5441 auto const loanSetFee =
Fee(env.
current()->fees().base * 2);
5444 env(coverDeposit(lender, broker.
brokerID, asset(100)));
5449 Sig(sfCounterpartySignature, lender),
5453 BEAST_EXPECT(env.
le(loanKeylet));
5456 env(
pay(borrower, loanKeylet.
key, asset(1'000)));
5460 env(coverWithdraw(lender, broker.
brokerID, asset(100)));
5467 env(
offer(lender,
XRP(1), asset(10)));
5473 testLoanPayLateFullPaymentBypassesPenalties(
FeatureBitset features)
5475 testcase(
"LoanPay full payment skips late penalties");
5476 using namespace jtx;
5477 using namespace loan;
5478 using namespace std::chrono_literals;
5480 Env env(*
this, features);
5482 Account const issuer{
"issuer"};
5483 Account const lender{
"lender"};
5484 Account
const borrower{
"borrower"};
5486 env.
fund(
XRP(1'000'000), issuer, lender, borrower);
5489 PrettyAsset
const asset = issuer[iouCurrency];
5490 env(
trust(lender, asset(100'000'000)));
5491 env(
trust(borrower, asset(100'000'000)));
5492 env(
pay(issuer, lender, asset(50'000'000)));
5493 env(
pay(issuer, borrower, asset(5'000'000)));
5496 BrokerInfo broker{createVaultAndBroker(env, asset, lender)};
5498 auto const loanSetFee = Fee(env.
current()->fees().base * 2);
5500 auto const brokerPreLoan = env.
le(keylet::loanBroker(broker.brokerID));
5501 if (BEAST_EXPECT(brokerPreLoan); !brokerPreLoan.has_value())
5504 auto const loanSequence = brokerPreLoan->at(sfLoanSequence);
5505 auto const loanKeylet = keylet::loan(broker.brokerID, loanSequence);
5507 Number
const principal = asset(1'000).value();
5508 Number
const serviceFee = asset(2).value();
5509 Number
const lateFee = asset(5).value();
5510 Number
const closeFee = asset(4).value();
5512 env(
set(borrower, broker.brokerID, principal),
5513 Sig(sfCounterpartySignature, lender),
5526 auto state1 = getCurrentState(env, broker, loanKeylet);
5527 if (!BEAST_EXPECT(state1.paymentRemaining > 1))
5530 using d = NetClock::duration;
5531 using tp = NetClock::time_point;
5532 auto const overdueClose = tp{d{state1.nextPaymentDate + state1.paymentInterval}};
5533 env.
close(overdueClose);
5535 auto const brokerSle = env.
le(keylet::loanBroker(broker.brokerID));
5536 auto const loanSle = env.
le(loanKeylet);
5537 if (!BEAST_EXPECT(brokerSle && loanSle))
5540 auto state = getCurrentState(env, broker, loanKeylet);
5542 TenthBips16 const managementFeeRate{brokerSle->at(sfManagementFeeRate)};
5543 TenthBips32 const interestRateValue{loanSle->at(sfInterestRate)};
5544 TenthBips32 const lateInterestRateValue{loanSle->at(sfLateInterestRate)};
5545 TenthBips32 const closeInterestRateValue{loanSle->at(sfCloseInterestRate)};
5547 Number
const closePaymentFeeRounded =
5548 roundToAsset(broker.asset, loanSle->at(sfClosePaymentFee), state.loanScale);
5549 Number
const latePaymentFeeRounded =
5550 roundToAsset(broker.asset, loanSle->at(sfLatePaymentFee), state.loanScale);
5553 state.totalValue, state.principalOutstanding, state.managementFeeOutstanding);
5554 Number
const totalInterestOutstanding = roundedLoanState.interestDue;
5556 auto const periodicRate =
loanPeriodicRate(interestRateValue, state.paymentInterval);
5559 state.periodicPayment,
5561 state.paymentRemaining,
5564 auto const parentCloseTime = env.
current()->parentCloseTime();
5565 auto const startDateSeconds =
5569 rawLoanState.principalOutstanding,
5572 state.paymentInterval,
5573 state.previousPaymentDate,
5575 closeInterestRateValue);
5577 Number
const roundedFullInterestAmount =
5578 roundToAsset(broker.asset, fullPaymentInterest, state.loanScale);
5580 broker.asset, roundedFullInterestAmount, managementFeeRate, state.loanScale);
5581 Number
const roundedFullInterest = roundedFullInterestAmount - roundedFullManagementFee;
5583 Number
const trackedValueDelta =
5584 state.principalOutstanding + totalInterestOutstanding + state.managementFeeOutstanding;
5585 Number
const untrackedManagementFee =
5586 closePaymentFeeRounded + roundedFullManagementFee - state.managementFeeOutstanding;
5587 Number
const untrackedInterest = roundedFullInterest - totalInterestOutstanding;
5589 Number
const baseFullDue = trackedValueDelta + untrackedInterest + untrackedManagementFee;
5590 BEAST_EXPECT(baseFullDue ==
roundToAsset(broker.asset, baseFullDue, state.loanScale));
5592 auto const overdueSeconds =
5593 parentCloseTime.time_since_epoch().count() - state.nextPaymentDate;
5594 if (!BEAST_EXPECT(overdueSeconds > 0))
5597 Number
const overdueRate =
loanPeriodicRate(lateInterestRateValue, overdueSeconds);
5598 Number
const lateInterestRaw = state.principalOutstanding * overdueRate;
5599 Number
const lateInterestRounded =
5600 roundToAsset(broker.asset, lateInterestRaw, state.loanScale);
5602 broker.asset, lateInterestRounded, managementFeeRate, state.loanScale);
5603 Number
const penaltyDue =
5604 lateInterestRounded + lateManagementFeeRounded + latePaymentFeeRounded;
5605 BEAST_EXPECT(penaltyDue > Number{});
5607 auto const balanceBefore = env.
balance(borrower, broker.asset).
number();
5609 STAmount
const paymentAmount{broker.asset.raw(), baseFullDue};
5610 env(
pay(borrower, loanKeylet.
key, paymentAmount, tfLoanFullPayment));
5613 if (
auto const meta = env.
meta(); BEAST_EXPECT(meta))
5614 BEAST_EXPECT(meta->at(sfTransactionResult) == tesSUCCESS);
5616 auto const balanceAfter = env.
balance(borrower, broker.asset).
number();
5617 Number
const actualPaid = balanceBefore - balanceAfter;
5618 BEAST_EXPECT(actualPaid == baseFullDue);
5620 Number
const expectedWithPenalty = baseFullDue + penaltyDue;
5621 BEAST_EXPECT(expectedWithPenalty > actualPaid);
5622 BEAST_EXPECT(expectedWithPenalty - actualPaid == penaltyDue);
5626 testLoanCoverMinimumRoundingExploit(FeatureBitset features)
5628 auto testLoanCoverMinimumRoundingExploit = [&,
this](Number
const& principalRequest) {
5629 testcase <<
"LoanBrokerCoverClawback drains cover via rounding"
5630 <<
" principalRequested=" <<
to_string(principalRequest);
5632 using namespace jtx;
5633 using namespace loan;
5636 Env env(*
this, features);
5638 Account const issuer{
"issuer"};
5639 Account const lender{
"lender"};
5640 Account const borrower{
"borrower"};
5642 env.
fund(
XRP(1'000'000'000), issuer, lender, borrower);
5645 env(
fset(issuer, asfAllowTrustLineClawback));
5648 PrettyAsset
const asset = issuer[iouCurrency];
5649 env(
trust(lender, asset(2'000'0000)));
5650 env(
trust(borrower, asset(2'000'0000)));
5653 env(
pay(issuer, lender, asset(2'000'0000)));
5656 BrokerParameters brokerParams{.debtMax = 0, .coverRateMin =
TenthBips32{10'000}};
5657 BrokerInfo broker{createVaultAndBroker(env, asset, lender, brokerParams)};
5659 auto const loanSetFee = Fee(env.
current()->fees().base * 2);
5661 set(borrower, broker.brokerID, principalRequest),
5662 Sig(sfCounterpartySignature, lender),
5670 auto const brokerBefore = env.
le(keylet::loanBroker(broker.brokerID));
5671 BEAST_EXPECT(brokerBefore);
5675 Number
const debtOutstanding = brokerBefore->at(sfDebtTotal);
5676 Number
const coverAvailableBefore = brokerBefore->at(sfCoverAvailable);
5678 BEAST_EXPECT(debtOutstanding > Number{});
5679 BEAST_EXPECT(coverAvailableBefore > Number{});
5682 <<
" cover_available=" <<
to_string(coverAvailableBefore);
5684 env(
coverClawback(issuer, 0), loanBrokerID(broker.brokerID));
5687 auto const brokerAfter = env.
le(keylet::loanBroker(broker.brokerID));
5688 BEAST_EXPECT(brokerAfter);
5692 Number
const debtAfter = brokerAfter->at(sfDebtTotal);
5694 BEAST_EXPECT(debtAfter == debtOutstanding);
5696 Number
const coverAvailableAfter = brokerAfter->at(sfCoverAvailable);
5700 BEAST_EXPECT(coverAvailableAfter != Number{});
5704 testLoanCoverMinimumRoundingExploit(Number{1, -30});
5705 testLoanCoverMinimumRoundingExploit(Number{1, -20});
5706 testLoanCoverMinimumRoundingExploit(Number{1, -10});
5707 testLoanCoverMinimumRoundingExploit(Number{1, 1});
5728 testcase(
"PoC: Unsigned-underflow full-pay accrual after early periodic");
5730 using namespace jtx;
5731 using namespace loan;
5732 using namespace std::chrono_literals;
5734 Env env{*
this, features};
5736 Account const lender{
"poc_lender4"};
5737 Account const borrower{
"poc_borrower4"};
5738 env.
fund(
XRP(3'000'000), lender, borrower);
5747 auto const loanSetFee =
Fee(env.
current()->fees().base * 2);
5748 Number const principalRequest = asset(1000).value();
5749 auto const originationFee = asset(0).value();
5750 auto const serviceFee = asset(1).value();
5751 auto const serviceFeePA = asset(1);
5752 auto const lateFee = asset(0).value();
5753 auto const closeFee = asset(0).value();
5758 auto const total = 3u;
5759 auto const interval = 600u;
5760 auto const grace = 60u;
5762 auto createJtx = env.
jt(
5763 set(borrower, broker.brokerID, principalRequest, 0),
5764 Sig(sfCounterpartySignature, lender),
5765 kLoanOriginationFee(originationFee),
5766 kLoanServiceFee(serviceFee),
5767 kLatePaymentFee(lateFee),
5768 kClosePaymentFee(closeFee),
5770 kInterestRate(interest),
5771 kLateInterestRate(lateInterest),
5772 kCloseInterestRate(closeInterest),
5773 kOverpaymentInterestRate(overpaymentInterest),
5774 kPaymentTotal(total),
5775 kPaymentInterval(interval),
5776 kGracePeriod(grace),
5780 BEAST_EXPECT(brokerSle);
5781 auto const loanSequence = brokerSle ? brokerSle->at(sfLoanSequence) : 0;
5782 auto const loanKeylet =
keylet::loan(broker.brokerID, loanSequence);
5795 state.principalOutstanding,
5796 state.managementFeeOutstanding,
5797 state.periodicPayment,
5799 state.paymentRemaining,
5801 STAmount const regularDue{asset, components.trackedValueDelta + serviceFeePA.number()};
5803 env(
pay(borrower, loanKeylet.
key, regularDue));
5809 auto const loanSle = env.
le(loanKeylet);
5810 BEAST_EXPECT(loanSle);
5812 BEAST_EXPECT(brokerSle2);
5814 auto const closePaymentFee = loanSle ? loanSle->at(sfClosePaymentFee) :
Number{};
5815 auto const closeInterestRate =
5817 auto const managementFeeRate =
5826 after.periodicPayment,
5828 after.paymentRemaining),
5830 env.
current()->parentCloseTime(),
5831 after.paymentInterval,
5832 after.previousPaymentDate,
5837 auto const roundedInterest =
5839 Number const roundedFullMgmtFee =
5841 Number const roundedFullInterest = roundedInterest - roundedFullMgmtFee;
5844 auto const nowSecs =
5846 auto const startSecs =
5848 auto const lastPaymentDate =
std::max(
after.previousPaymentDate, startSecs);
5849 auto const signedDelta =
5851 auto const unsignedDelta =
static_cast<std::uint32_t>(nowSecs - lastPaymentDate);
5852 log <<
"PoC window: prev=" <<
after.previousPaymentDate <<
" start=" << startSecs
5853 <<
" now=" << nowSecs <<
" signedDelta=" << signedDelta
5854 <<
" unsignedDelta=" << unsignedDelta <<
std::endl;
5858 auto const prevClamped =
std::min(
after.previousPaymentDate, nowSecs);
5862 after.periodicPayment,
5864 after.paymentRemaining),
5866 env.
current()->parentCloseTime(),
5867 after.paymentInterval,
5871 auto const roundedInterestClamped =
5874 asset.
raw(), roundedInterestClamped, managementFeeRate,
after.loanScale);
5875 Number const roundedFullInterestClamped =
5876 roundedInterestClamped - roundedFullMgmtFeeClamped;
5879 after.principalOutstanding + roundedFullInterestClamped + roundedFullMgmtFeeClamped +
5883 auto const vaultId2 = brokerSle2 ? brokerSle2->at(sfVaultID) :
uint256{};
5885 auto const vaultBefore = env.
le(vaultKey2);
5886 BEAST_EXPECT(vaultBefore);
5887 Number const assetsTotalBefore = vaultBefore ? vaultBefore->at(sfAssetsTotal) :
Number{};
5891 after.principalOutstanding + roundedFullInterest + roundedFullMgmtFee +
5894 log <<
"PoC payoff: principalOutstanding=" <<
after.principalOutstanding
5895 <<
" roundedFullInterest=" << roundedFullInterest
5896 <<
" roundedFullMgmtFee=" << roundedFullMgmtFee <<
" closeFee=" << closePaymentFee
5898 log <<
"PoC reference (clamped): roundedFullInterestClamped=" << roundedFullInterestClamped
5899 <<
" roundedFullMgmtFeeClamped=" << roundedFullMgmtFeeClamped
5902 env(
pay(borrower, loanKeylet.
key, fullDue),
Txflags(tfLoanFullPayment));
5907 BEAST_EXPECT(unsignedDelta >
after.paymentInterval);
5910 auto const vaultAfter = env.
le(vaultKey2);
5911 BEAST_EXPECT(vaultAfter);
5914 auto const assetsTotalAfter = vaultAfter->at(sfAssetsTotal);
5915 log <<
"PoC NAV: assetsTotalBefore=" << assetsTotalBefore
5916 <<
" assetsTotalAfter=" << assetsTotalAfter
5917 <<
" delta=" << (assetsTotalAfter - assetsTotalBefore) <<
std::endl;
5921 BEAST_EXPECT(fullDue == fullDueClamped);
5922 if (fullDue > fullDueClamped)
5923 log <<
"PoC delta: overcharge (fullDue > clamped)" <<
std::endl;
5927 auto const finalLoan = env.
le(loanKeylet);
5928 BEAST_EXPECT(finalLoan);
5931 BEAST_EXPECT(finalLoan->at(sfPaymentRemaining) == 0);
5932 BEAST_EXPECT(finalLoan->at(sfPrincipalOutstanding) == 0);
5941 using namespace jtx;
5942 using namespace std::chrono_literals;
5943 Env env{*
this, features};
5946 Account const issuer{
"issuer"};
5947 Account const lender{
"lender"};
5948 Account const borrower{
"borrower"};
5949 Account const victim{
"victim"};
5951 env.
fund(
XRP(1'000'000'00), issuer, lender, borrower, victim);
5955 auto asset = issuer[
"USD"];
5956 env(
trust(lender, asset(100000)));
5957 env(
trust(borrower, asset(100000)));
5958 env(
trust(victim, asset(100000)));
5959 env(
pay(issuer, lender, asset(50000)));
5960 env(
pay(issuer, borrower, asset(50000)));
5961 env(
pay(issuer, victim, asset(50000)));
5965 .vaultDeposit = 10000,
5974 if (!BEAST_EXPECT(brokerSle))
5975 return std::nullopt;
5978 BEAST_EXPECT(brokerSle->at(sfOwnerCount) == 0);
5982 auto const loanSequence = brokerSle->at(sfLoanSequence);
5988 auto const& vaultKeylet = broker.vaultKeylet();
5991 auto const vaultSle = env.
le(vaultKeylet);
5992 Number const assetsTotal = vaultSle->at(sfAssetsTotal);
5993 Number const assetsAvail = vaultSle->at(sfAssetsAvailable);
5997 log <<
" AssetsAvailable: " << assetsAvail <<
std::endl;
5998 log <<
" Difference: " << (assetsTotal - assetsAvail) <<
std::endl;
6001 BEAST_EXPECT(assetsAvail == assetsTotal);
6002 BEAST_EXPECT(assetsAvail == broker.asset(brokerParams.
vaultDeposit).number());
6005 Keylet const& loanKeylet = *loanKeyletOpt;
6009 .counter = borrower,
6010 .principalRequest =
Number{100},
6013 .payInterval = 86400 * 6,
6014 .gracePd = 86400 * 5,
6017 env(loanParams(env, broker));
6024 auto const vaultSle = env.
le(vaultKeylet);
6025 Number const assetsTotal = vaultSle->at(sfAssetsTotal);
6026 Number const assetsAvail = vaultSle->at(sfAssetsAvailable);
6030 log <<
" AssetsAvailable: " << assetsAvail <<
std::endl;
6031 log <<
" Difference: " << (assetsTotal - assetsAvail) <<
std::endl;
6033 auto const loanSle = env.
le(loanKeylet);
6034 if (!BEAST_EXPECT(loanSle))
6039 log <<
" ValueOutstanding: " << state.valueOutstanding <<
std::endl;
6040 log <<
" PrincipalOutstanding: " << state.principalOutstanding <<
std::endl;
6041 log <<
" InterestOutstanding: " << state.interestOutstanding() <<
std::endl;
6042 log <<
" InterestDue: " << state.interestDue <<
std::endl;
6043 log <<
" FeeDue: " << state.managementFeeDue <<
std::endl;
6047 BEAST_EXPECT(assetsAvail < assetsTotal);
6053 broker.asset(brokerParams.
vaultDeposit + state.interestDue).number());
6062 auto const vaultSle2 = env.
le(vaultKeylet);
6063 Number const assetsTotal2 = vaultSle2->at(sfAssetsTotal);
6064 Number const assetsAvail2 = vaultSle2->at(sfAssetsAvailable);
6068 log <<
" AssetsAvailable: " << assetsAvail2 <<
std::endl;
6069 log <<
" Difference: " << (assetsTotal2 - assetsAvail2) <<
std::endl;
6072 BEAST_EXPECT(assetsAvail2 == assetsTotal2);
6079 using namespace jtx;
6083 Account const issuer(
"issuer");
6084 Account const lender(
"lender");
6085 Account const borrower(
"borrower");
6088 .vaultDeposit = 100000,
6095 .counter = borrower,
6096 .principalRequest =
Number{200'000, -6},
6097 .lateFee =
Number{200, -6},
6100 .payInterval = 150};
6104 Env env{*
this, features};
6107 createLoan(env, assetType, brokerParams, loanParams, issuer, lender, borrower);
6109 if (BEAST_EXPECT(loanResult); !loanResult.has_value())
6112 auto broker = std::get<BrokerInfo>(*loanResult);
6113 auto loanKeylet = std::get<Keylet>(*loanResult);
6119 if (
auto loan = env.
le(loanKeylet); BEAST_EXPECT(
loan))
6121 env.
close(tp{d{
loan->at(sfNextPaymentDueDate) +
loan->at(sfGracePeriod) + 1}});
6132 auto const jr = env.
rpc(
"submit", borrower.
name(), submitParam);
6134 BEAST_EXPECT(jr.isMember(jss::result));
6135 auto const jResult = jr[jss::result];
6141 env(
noop(borrower));
6152 testcase(
"RIPD-3459 - LoanBroker incorrect debt total");
6154 using namespace jtx;
6156 Account const issuer(
"issuer");
6157 Account const lender(
"lender");
6158 Account const borrower(
"borrower");
6161 .vaultDeposit = 200'000,
6168 .counter = borrower,
6169 .principalRequest =
Number{100'000, -4},
6175 Env env{*
this, features};
6178 createLoan(env, assetType, brokerParams, loanParams, issuer, lender, borrower);
6180 if (BEAST_EXPECT(loanResult); !loanResult.has_value())
6183 auto broker = std::get<BrokerInfo>(*loanResult);
6184 auto loanKeylet = std::get<Keylet>(*loanResult);
6185 auto pseudoAcct = std::get<Account>(*loanResult);
6187 VerifyLoanStatus const verifyLoanStatus(env, broker, pseudoAcct, loanKeylet);
6189 if (
auto const brokerSle = env.
le(broker.brokerKeylet()); BEAST_EXPECT(brokerSle))
6191 if (
auto const loanSle = env.
le(loanKeylet); BEAST_EXPECT(loanSle))
6193 BEAST_EXPECT(brokerSle->at(sfDebtTotal) == loanSle->at(sfTotalValueOutstanding));
6208 if (
auto const brokerSle = env.
le(broker.brokerKeylet()); BEAST_EXPECT(brokerSle))
6210 if (
auto const loanSle = env.
le(loanKeylet); BEAST_EXPECT(loanSle))
6212 BEAST_EXPECT(brokerSle->at(sfDebtTotal) == loanSle->at(sfTotalValueOutstanding));
6213 BEAST_EXPECT(brokerSle->at(sfDebtTotal) == beast::kZero);
6221 testcase(
"Crash with tfLoanOverpayment");
6222 using namespace jtx;
6223 using namespace loan;
6224 Account const lender{
"lender"};
6225 Account const issuer{
"issuer"};
6226 Account const borrower{
"borrower"};
6227 Account const depositor{
"depositor"};
6231 Vault const vault(env);
6233 env.
fund(
XRP(10'000), lender, issuer, borrower, depositor);
6236 auto [tx, vaultKeyLet] = vault.create({.owner = lender, .asset =
xrpIssue()});
6240 env(vault.deposit({.depositor = depositor, .id = vaultKeyLet.key, .amount = XRP(1'000)}),
6253 env(
set(borrower, brokerKeyLet.key, debtMaximumRequest),
6254 Sig(sfCounterpartySignature, lender),
6257 kPaymentInterval(150),
6263 auto const loanKeylet =
keylet::loan(brokerKeyLet.key, loanSequence);
6277 testcase(
"Minimum cover rounding allows undercoverage (XRP)");
6279 using namespace jtx;
6282 Env env{*
this, features};
6284 Account const lender{
"lender"};
6285 Account const borrower{
"borrower"};
6287 env.
fund(
XRP(200'000), lender, borrower);
6291 Vault const vault{env};
6292 auto [vaultCreate, vaultKeylet] = vault.create({.owner = lender, .asset =
xrpIssue()});
6295 BEAST_EXPECT(env.
le(vaultKeylet));
6301 .vaultDeposit = 1'000,
6310 env(
loan::set(borrower, brokerInfo.brokerID, xrpAsset(804).value()),
6312 Sig(sfCounterpartySignature, lender),
6319 BEAST_EXPECT(brokerSle))
6322 BEAST_EXPECT(brokerSle->at(sfDebtTotal) ==
Number(804));
6327 env(coverWithdraw(lender, brokerInfo.brokerID, xrpAsset(2).value()),
6334 env(coverWithdraw(lender, brokerInfo.brokerID, xrpAsset(1).value()));
6340 BEAST_EXPECT(brokerSle))
6343 BEAST_EXPECT(brokerSle->at(sfCoverAvailable) == xrpAsset(81).value());
6344 BEAST_EXPECT(brokerSle->at(sfDebtTotal) ==
Number(804));
6348 log <<
"Theoretical min cover: " << theoreticalMin <<
std::endl;
6349 BEAST_EXPECT(
Number(804, -1) == theoreticalMin);
6356 testcase(
"RIPD-3902 - 1 IOU loan payments");
6358 using namespace jtx;
6360 Account const issuer(
"issuer");
6361 Account const lender(
"lender");
6362 Account const borrower(
"borrower");
6372 .counter = borrower,
6373 .principalRequest =
Number{1, 0},
6381 Env env{*
this, features};
6384 createLoan(env, assetType, brokerParams, loanParams, issuer, lender, borrower);
6386 if (BEAST_EXPECT(loanResult); !loanResult.has_value())
6389 auto broker = std::get<BrokerInfo>(*loanResult);
6390 auto loanKeylet = std::get<Keylet>(*loanResult);
6391 auto pseudoAcct = std::get<Account>(*loanResult);
6393 VerifyLoanStatus const verifyLoanStatus(env, broker, pseudoAcct, loanKeylet);
6410 testcase(
"Test Borrower is Broker");
6411 using namespace jtx;
6412 using namespace loan;
6413 Account const broker{
"broker"};
6414 Account const issuer{
"issuer"};
6415 Account const borrower{
"borrower"};
6416 Account const depositor{
"depositor"};
6418 auto testLoanAsset = [&](
auto&& getMaxDebt,
auto const& borrower) {
6420 Vault const vault(env);
6422 if (borrower == broker)
6424 env.
fund(
XRP(10'000), broker, issuer, depositor);
6428 env.
fund(
XRP(10'000), broker, borrower, issuer, depositor);
6432 auto const xrpFee =
XRP(100);
6435 STAmount const debtMaximumRequest = getMaxDebt(env);
6437 auto const& asset = debtMaximumRequest.
asset();
6438 auto const initialVault = asset(debtMaximumRequest * 100);
6440 auto [tx, vaultKeylet] = vault.create({.owner = broker, .asset = asset});
6445 {.depositor = depositor, .id = vaultKeylet.key, .amount = initialVault}),
6454 auto const serviceFee = 101;
6456 env(
set(broker, brokerKeylet.key, debtMaximumRequest),
6457 kCounterparty(borrower),
6458 Sig(sfCounterpartySignature, borrower),
6459 kLoanServiceFee(serviceFee),
6465 auto const loanKeylet =
keylet::loan(brokerKeylet.key, loanSequence);
6467 auto const brokerBalanceBefore = env.
balance(broker, asset);
6469 if (
auto const loanSle = env.
le(loanKeylet); env.
test.BEAST_EXPECT(loanSle))
6471 auto const payment = loanSle->at(sfPeriodicPayment);
6472 auto const totalPayment = payment + serviceFee;
6475 if (
auto const vaultSle = env.
le(vaultKeylet); BEAST_EXPECT(vaultSle))
6477 auto const expected = [&]() {
6480 if (borrower != broker)
6481 return brokerBalanceBefore.number() + serviceFee;
6487 return brokerBalanceBefore.number() - payment - xrpFee.number();
6488 return brokerBalanceBefore.number() - payment;
6490 BEAST_EXPECT(env.
balance(broker, asset).
value() == asset(expected).value());
6496 for (
auto const& borrowerAcct : {broker, borrower})
6502 auto const iou = issuer[
"USD"];
6503 env(
trust(broker, iou(1'000'000'000)));
6504 env(
trust(depositor, iou(1'000'000'000)));
6505 env(
pay(issuer, broker, iou(100'000'000)));
6506 env(
pay(issuer, depositor, iou(100'000'000)));
6508 return iou(200'000);
6516 .holders = {broker, depositor},
6517 .pay = 100'000'000});
6518 return mpt(200'000);
6527 testcase(
"RIPD-4096 - Issuer as borrower");
6529 using namespace jtx;
6531 Account const issuer(
"issuer");
6532 Account const lender(
"lender");
6535 .vaultDeposit = 100'000,
6541 .account = lender, .counter = issuer, .principalRequest =
Number{10000}};
6545 Env env{*
this, features};
6548 createLoan(env, assetType, brokerParams, loanParams, issuer, lender, issuer);
6550 if (BEAST_EXPECT(loanResult); !loanResult.has_value())
6553 auto broker = std::get<BrokerInfo>(*loanResult);
6554 auto loanKeylet = std::get<Keylet>(*loanResult);
6555 auto pseudoAcct = std::get<Account>(*loanResult);
6557 VerifyLoanStatus const verifyLoanStatus(env, broker, pseudoAcct, loanKeylet);
6574 testcase(
"RIPD-4125 - overpayment");
6576 using namespace jtx;
6578 Account const issuer(
"issuer");
6579 Account const lender(
"lender");
6580 Account const borrower(
"borrower");
6583 .vaultDeposit = 100'000,
6590 .counter = borrower,
6591 .principalRequest =
Number{200000, -6},
6596 .flags = tfLoanOverpayment,
6604 createLoan(env, assetType, brokerParams, loanParams, issuer, lender, borrower);
6606 if (BEAST_EXPECT(loanResult); !loanResult.has_value())
6609 auto broker = std::get<BrokerInfo>(*loanResult);
6610 auto loanKeylet = std::get<Keylet>(*loanResult);
6611 auto pseudoAcct = std::get<Account>(*loanResult);
6613 VerifyLoanStatus const verifyLoanStatus(env, broker, pseudoAcct, loanKeylet);
6620 STAmount{broker.asset, state.periodicPayment * 3 / 2 + 1},
6621 tfLoanOverpayment));
6625 .showStepBalances =
false,
6626 .validateBalances =
true,
6644 testcase(
"testOverpaymentManagementFee");
6646 using namespace jtx;
6647 using namespace loan;
6649 Env env{*
this, features};
6651 Account const lender{
"lender"}, borrower{
"borrower"};
6653 env.
fund(
XRP(10'000'000), lender, borrower);
6663 .vaultDeposit = asset(100'000).value(),
6667 auto const loanSetFee =
Fee(env.
current()->fees().base * 2);
6670 result.brokerKeylet().key, (env.
le(result.brokerKeylet()))->at(sfLoanSequence));
6672 borrower, result.brokerKeylet().key, asset(10'000).value(), tfLoanOverpayment),
6673 Sig(sfCounterpartySignature, lender),
6680 auto const expectedOverpaymentManagementFee =
Number{33333, 0};
6681 auto const loanBrokerBalanceBefore = env.
balance(lender);
6683 auto const loanPayFee =
Fee(env.
current()->fees().base * 2);
6684 env(
pay(borrower, loanKeylet.
key, asset(5'000).value(), tfLoanOverpayment), loanPayFee);
6688 env.
balance(lender) - loanBrokerBalanceBefore == expectedOverpaymentManagementFee,
6689 "overpayment management fee missmatch; expected:" +
6690 to_string(expectedOverpaymentManagementFee) +
6697 testcase <<
"LoanPay Broker Owner Missing Trustline (PoC)";
6698 using namespace jtx;
6699 using namespace loan;
6700 Account const issuer(
"issuer");
6701 Account const borrower(
"borrower");
6702 Account const broker(
"broker");
6703 auto const iou = issuer[
"IOU"];
6704 Env env(*
this, features);
6705 env.
fund(
XRP(20'000), issuer, broker, borrower);
6708 env(
trust(broker, iou(20'000'000)));
6709 env(
trust(borrower, iou(20'000'000)));
6710 env(
pay(issuer, broker, iou(10'000'000)));
6711 env(
pay(issuer, borrower, iou(1'000)));
6717 env(
set(borrower, brokerInfo.brokerID, 10'000),
6718 Sig(sfCounterpartySignature, broker),
6719 kLoanServiceFee(iou(100).value()),
6720 kPaymentInterval(100),
6729 auto const additionalCover = iou(50'000).value();
6734 BEAST_EXPECT(env.
le(brokerTrustline) !=
nullptr);
6737 auto const brokerBalance = env.
balance(broker, iou);
6738 env(
pay(broker, issuer, brokerBalance));
6741 env(
trust(broker, iou(0)));
6744 BEAST_EXPECT(env.
le(brokerTrustline) ==
nullptr);
6750 BEAST_EXPECT(env.
le(brokerTrustline) ==
nullptr);
6753 BEAST_EXPECT(brokerSle))
6755 Account const pseudo(
"pseudo-account", brokerSle->at(sfAccount));
6756 auto const balance = env.
balance(pseudo, iou);
6765 testcase <<
"LoanPay Broker Owner MPT unauthorized";
6766 using namespace jtx;
6767 using namespace loan;
6769 Account const issuer(
"issuer");
6770 Account const borrower(
"borrower");
6771 Account const broker(
"broker");
6773 Env env{*
this, features};
6774 env.
fund(
XRP(20'000), issuer, broker, borrower);
6778 mptt.
create({.flags = tfMPTCanClawback | tfMPTCanTransfer | tfMPTCanLock});
6789 env(
pay(issuer, broker, mpt(10'000'000)));
6790 env(
pay(issuer, borrower, mpt(1'000)));
6797 env(
set(borrower, brokerInfo.brokerID, 10'000),
6798 Sig(sfCounterpartySignature, broker),
6799 kLoanServiceFee(mpt(100).value()),
6800 kPaymentInterval(100),
6809 auto const additionalCover = mpt(50'000).value();
6814 BEAST_EXPECT(env.
le(brokerMpt) !=
nullptr);
6817 auto const brokerBalance = env.
balance(broker, mpt);
6818 env(
pay(broker, issuer, brokerBalance));
6821 mptt.
authorize({.account = broker, .flags = tfMPTUnauthorize});
6824 BEAST_EXPECT(env.
le(brokerMpt) ==
nullptr);
6827 auto const borrowerBalance = env.
balance(borrower, mpt);
6831 BEAST_EXPECT(env.
le(brokerMpt) ==
nullptr);
6834 BEAST_EXPECT(brokerSle))
6836 Account const pseudo(
"pseudo-account", brokerSle->at(sfAccount));
6837 auto const balance = env.
balance(pseudo, mpt);
6846 testcase <<
"LoanPay Broker Owner without permissioned domain of the MPT";
6847 using namespace jtx;
6848 using namespace loan;
6850 Account const issuer(
"issuer");
6851 Account const borrower(
"borrower");
6852 Account const broker(
"broker");
6854 Env env{*
this, features};
6855 env.
fund(
XRP(20'000), issuer, broker, borrower);
6858 auto credType =
"credential1";
6876 .flags = tfMPTCanClawback | tfMPTRequireAuth | tfMPTCanTransfer | tfMPTCanLock,
6877 .domainID = domainID,
6889 env(
pay(issuer, broker, mpt(10'000'000)));
6890 env(
pay(issuer, borrower, mpt(1'000)));
6897 env(
set(borrower, brokerInfo.brokerID, 10'000),
6898 Sig(sfCounterpartySignature, broker),
6899 kLoanServiceFee(mpt(100).value()),
6900 kPaymentInterval(100),
6909 auto const additionalCover = mpt(50'000).value();
6914 BEAST_EXPECT(env.
le(brokerMpt) !=
nullptr);
6917 auto const brokerBalance = env.
balance(broker, mpt);
6918 env(
pay(broker, issuer, brokerBalance));
6930 auto const borrowerBalance = env.
balance(borrower, mpt);
6937 BEAST_EXPECT(brokerSle))
6939 Account const pseudo(
"pseudo-account", brokerSle->at(sfAccount));
6940 auto const balance = env.
balance(pseudo, mpt);
6949 testcase <<
"LoanSet Broker Owner without permissioned domain of the MPT";
6950 using namespace jtx;
6951 using namespace loan;
6953 Account const issuer(
"issuer");
6954 Account const borrower(
"borrower");
6955 Account const broker(
"broker");
6957 Env env{*
this, features};
6958 env.
fund(
XRP(20'000), issuer, broker, borrower);
6961 auto credType =
"credential1";
6980 .flags = tfMPTCanClawback | tfMPTRequireAuth | tfMPTCanTransfer | tfMPTCanLock,
6981 .domainID = domainID,
6992 env(
pay(issuer, broker, mpt(10'000'000)));
6993 env(
pay(issuer, borrower, mpt(1'000)));
7001 auto const brokerBalance = env.
balance(broker, mpt);
7002 env(
pay(broker, issuer, brokerBalance));
7009 env(
set(borrower, brokerInfo.brokerID, 10'000),
7010 Sig(sfCounterpartySignature, broker),
7011 kLoanServiceFee(mpt(100).value()),
7012 kPaymentInterval(100),
7021 testcase <<
"First-Loss Capital Depletion on Sequential Defaults";
7023 using namespace jtx;
7024 using namespace loan;
7027 Env env{*
this, features};
7029 Account const issuer{
"issuer"};
7030 Account const lender{
"lender"};
7031 Account const borrowerA{
"borrowerA"};
7032 Account const borrowerB{
"borrowerB"};
7034 env.
fund(
XRP(1'000'000), issuer, lender, borrowerA, borrowerB);
7038 auto const vaultDepositAmount =
7046 .vaultDeposit = vaultDepositAmount.value(),
7049 .coverDeposit = 21'000,
7053 auto const brokerKeylet = brokerInfo.brokerKeylet();
7060 auto const principalAmount =
Number(50'000);
7061 auto const loanPaymentInterval = 2592000;
7062 auto const loanGracePeriod = 604800;
7065 auto loanATx = env.
jt(
7066 set(borrowerA, brokerKeylet.key, principalAmount),
7067 Sig(sfCounterpartySignature, lender),
7076 auto const loanAKeylet =
keylet::loan(brokerKeylet.key, 1);
7079 auto loanBTx = env.
jt(
7080 set(borrowerB, brokerKeylet.key, principalAmount),
7081 Sig(sfCounterpartySignature, lender),
7090 auto const loanBKeylet =
keylet::loan(brokerKeylet.key, 2);
7092 auto loanASle = env.
le(loanAKeylet);
7093 if (!BEAST_EXPECT(loanASle))
7097 auto const loanANextDue = loanASle->at(sfNextPaymentDueDate);
7098 auto const loanAGrace = loanASle->at(sfGracePeriod);
7101 env(manage(lender, loanAKeylet.key, tfLoanDefault),
Ter(
tesSUCCESS));
7105 loanASle = env.
le(loanAKeylet);
7106 if (!BEAST_EXPECT(loanASle))
7108 BEAST_EXPECT(loanASle->isFlag(lsfLoanDefault));
7109 BEAST_EXPECT(loanASle->at(sfPaymentRemaining) == 0);
7112 auto brokerSle = env.
le(brokerKeylet);
7113 if (!BEAST_EXPECT(brokerSle))
7115 auto const afterFirstDebtTotal = brokerSle->at(sfDebtTotal);
7116 auto const afterFirstCoverAvailable = brokerSle->at(sfCoverAvailable);
7119 BEAST_EXPECT(afterFirstDebtTotal == 50'134);
7122 BEAST_EXPECT(afterFirstCoverAvailable == 946);
7124 env(manage(lender, loanBKeylet.key, tfLoanDefault),
Ter(
tesSUCCESS));
7126 brokerSle = env.
le(brokerKeylet);
7127 if (!BEAST_EXPECT(brokerSle))
7129 auto const afterSecondDebtTotal = brokerSle->at(sfDebtTotal);
7130 auto const afterSecondCoverAvailable = brokerSle->at(sfCoverAvailable);
7132 BEAST_EXPECT(afterSecondDebtTotal == 0);
7134 BEAST_EXPECT(afterSecondCoverAvailable == 0);
7140 testcase(
"Rounding manipulation does not permit yield theft");
7141 using namespace jtx;
7142 using namespace loan;
7146 Account const issuer{
"issuer"};
7147 Account const lender{
"lender"};
7148 Account const borrower{
"borrower"};
7150 env.
fund(
XRP(1000), issuer, lender, borrower);
7155 env(
trust(lender, iou(100'000'000)));
7156 env(
trust(borrower, iou(100'000'000)));
7157 env(
pay(issuer, lender, iou(100'000'000)));
7158 env(
pay(issuer, borrower, iou(100'000'000)));
7167 .vaultDeposit = 5'000'000,
7168 .debtMax =
Number{100'000'000},
7169 .coverDeposit = 500'000,
7171 auto const [currentSeq, vaultKeylet] = [&]() {
7173 if (!BEAST_EXPECT(brokerSle))
7175 auto const currentSeq = brokerSle->at(sfLoanSequence);
7176 auto const vaultKeylet =
keylet::vault(brokerSle->at(sfVaultID));
7181 Number const principal = 1'000'000;
7186 auto const loanSetFee =
Fee(env.
current()->fees().base * 2);
7187 env(
set(borrower, brokerInfo.brokerID, iou(principal).value(), flags),
7188 Sig(sfCounterpartySignature, lender),
7197 auto borrowerBalance = [&]() {
return env.
balance(borrower, iou); };
7198 auto const borrowerScale =
static_cast<STAmount const&
>(borrowerBalance()).exponent();
7200 auto const loanKeylet =
keylet::loan(brokerInfo.brokerID, currentSeq);
7202 auto const loanSle = env.
le(loanKeylet);
7203 if (!BEAST_EXPECT(loanSle))
7204 return std::nullopt;
7206 return STAmount{iou, loanSle->at(sfPeriodicPayment)};
7208 if (!maybePeriodicPayment)
7210 auto const periodicPayment = *maybePeriodicPayment;
7211 auto const roundedPayment =
7216 STAmount const attackPayment = periodicPayment + paymentBuffer;
7219 auto const vault = env.
le(vaultKeylet);
7220 if (!BEAST_EXPECT(vault))
7221 return std::nullopt;
7222 return vault->at(sfAssetsTotal);
7224 if (!maybeInitialVaultAssets)
7226 auto const initialVaultAssets = *maybeInitialVaultAssets;
7229 int yieldTheftCount = 0;
7230 auto previousAssetsTotal = initialVaultAssets;
7232 for (
int i = 0; i < 100; ++i)
7234 auto const balanceBefore = borrowerBalance();
7235 env(
pay(borrower, loanKeylet.
key, attackPayment, flags));
7237 auto const borrowerDelta = balanceBefore - borrowerBalance();
7238 BEAST_EXPECT(borrowerDelta.signum() == roundedPayment.signum());
7240 auto const loanSle = env.
le(loanKeylet);
7241 if (!BEAST_EXPECT(loanSle))
7243 auto const updatedPayment =
STAmount{iou, loanSle->at(sfPeriodicPayment)};
7248 (updatedPayment == periodicPayment) ||
7249 (flags == tfLoanOverpayment && i >= 2 && updatedPayment < periodicPayment));
7251 auto const currentVaultSle = env.
le(vaultKeylet);
7252 if (!BEAST_EXPECT(currentVaultSle))
7255 auto const currentAssetsTotal = currentVaultSle->at(sfAssetsTotal);
7256 auto const delta = currentAssetsTotal - previousAssetsTotal;
7259 (delta == beast::kZero && borrowerDelta <= roundedPayment) ||
7260 (delta > beast::kZero && borrowerDelta > roundedPayment));
7264 if (delta == beast::kZero && borrowerDelta > roundedPayment)
7269 previousAssetsTotal = currentAssetsTotal;
7272 BEAST_EXPECTS(yieldTheftCount == 0,
std::to_string(yieldTheftCount));
7281 using namespace jtx;
7282 using namespace loan;
7283 using namespace std::chrono_literals;
7285 testcase(
"Vault withdraw reflects sfLossUnrealized");
7288 static constexpr std::int64_t kInitialFunding = 1'000'000;
7289 static constexpr std::int64_t kLenderInitialIou = 5'000'000;
7290 static constexpr std::int64_t kDepositorInitialIou = 1'000'000;
7291 static constexpr std::int64_t kBorrowerInitialIou = 100'000;
7294 static constexpr std::uint64_t kExpectedSharesPerDepositor = 5'000'000'000;
7298 Env env{*
this, features};
7301 Account const issuer{
"issuer"};
7302 Account const lender{
"lender"};
7303 Account const depositorA{
"lpA"};
7304 Account const depositorB{
"lpB"};
7305 Account const borrower{
"borrowerA"};
7307 env.
fund(
XRP(kInitialFunding), issuer, lender, depositorA, depositorB, borrower);
7312 env(
trust(lender, iouAsset(10'000'000)));
7313 env(
trust(depositorA, iouAsset(10'000'000)));
7314 env(
trust(depositorB, iouAsset(10'000'000)));
7315 env(
trust(borrower, iouAsset(10'000'000)));
7319 env(
pay(issuer, lender, iouAsset(kLenderInitialIou)));
7320 env(
pay(issuer, depositorA, iouAsset(kDepositorInitialIou)));
7321 env(
pay(issuer, depositorB, iouAsset(kDepositorInitialIou)));
7322 env(
pay(issuer, borrower, iouAsset(kBorrowerInitialIou)));
7330 .depositor = depositorA,
7331 .id = broker.vaultKeylet().key,
7332 .amount = iouAsset(kDepositAmount),
7336 .depositor = depositorB,
7337 .id = broker.vaultKeylet().key,
7338 .amount = iouAsset(kDepositAmount),
7345 if (!BEAST_EXPECT(sleBroker))
7348 auto const loanKeylet =
keylet::loan(broker.brokerID, sleBroker->at(sfLoanSequence));
7350 env(
set(borrower, broker.brokerID, kPrincipalAmount),
7351 Sig(sfCounterpartySignature, lender),
7352 kPaymentTotal(kLocalPaymentTotal),
7353 kPaymentInterval(kLocalPaymentInterval),
7363 auto const vaultAfterImpair = env.
le(broker.vaultKeylet());
7364 if (!BEAST_EXPECT(vaultAfterImpair))
7368 vaultAfterImpair->at(sfLossUnrealized) == broker.asset(kPrincipalAmount).value());
7371 auto const shareAsset = vaultAfterImpair->at(sfShareMPTID);
7374 return token ?
token->getFieldU64(sfMPTAmount) : 0;
7378 auto const sharesLpA = getShareBalance(depositorA);
7379 auto const sharesLpB = getShareBalance(depositorB);
7380 BEAST_EXPECT(sharesLpA == kExpectedSharesPerDepositor);
7381 BEAST_EXPECT(sharesLpB == kExpectedSharesPerDepositor);
7382 BEAST_EXPECT(sharesLpA == sharesLpB);
7385 auto const attemptWithdrawShares = [&](
Account const& depositor,
7390 {.depositor = depositor, .id = broker.vaultKeylet().key, .amount = shareAmt}),
7401 attemptWithdrawShares(depositorA, sharesLpA,
tesSUCCESS);
7402 attemptWithdrawShares(depositorB, sharesLpB,
tesSUCCESS);
7439 testcase(
"bug: doOverpayment asserts 'principal change agrees'");
7441 using namespace jtx;
7442 using namespace loan;
7460 Number principalOutstanding;
7461 Number expectedNewPrincipal;
7462 Number managementFeeChange;
7467 Env env(*
this, features);
7469 Account const issuer{
"issuer"};
7470 Account const lender{
"vaultOwner"};
7471 Account const borrower{
"borrower"};
7473 env.
fund(
XRP(1'000'000), issuer, lender, borrower);
7474 env(
fset(issuer, asfDefaultRipple));
7478 Asset const asset = iouAsset.
raw();
7480 env(
trust(lender, iouLimit));
7481 env(
trust(borrower, iouLimit));
7482 env(
pay(issuer, lender, iouAsset(1'000'000)));
7483 env(
pay(issuer, borrower, iouAsset(1'000'000)));
7490 {.vaultDeposit = 900'000,
7492 .managementFeeRate = p.managementFeeRate,
7493 .vaultScale = p.vaultScale});
7495 auto const brokerSle = env.
le(broker.brokerKeylet());
7496 BEAST_EXPECT(brokerSle);
7497 auto const loanSequence = brokerSle ? brokerSle->at(sfLoanSequence) : 0;
7498 auto const loanKeylet =
keylet::loan(broker.brokerID, loanSequence);
7500 env(
set(borrower, broker.brokerID,
Number{p.principal}, tfLoanOverpayment),
7501 Sig(sfCounterpartySignature, lender),
7502 kInterestRate(p.interestRate),
7503 kPaymentTotal(p.paymentTotal),
7504 kPaymentInterval(p.paymentInterval),
7505 kGracePeriod(p.paymentInterval),
7506 kOverpaymentFee(p.overpaymentFeeRate),
7507 kOverpaymentInterestRate(p.overpaymentInterestRate),
7516 auto const periodicRate =
loanPeriodicRate(s.interestRate, s.paymentInterval);
7522 s.principalOutstanding,
7523 s.managementFeeOutstanding,
7527 p.managementFeeRate);
7541 p.overpaymentInterestRate,
7542 p.overpaymentFeeRate,
7543 p.managementFeeRate);
7544 Number const expectedNewPrincipal = s.principalOutstanding -
7545 onePeriod.trackedPrincipalDelta - overpaymentComponents.trackedPrincipalDelta;
7547 Number const managementFeeBefore = s.managementFeeOutstanding;
7549 STAmount const payAmount{asset, onePeriod.trackedValueDelta + p.overpayment};
7550 env(
pay(borrower, loanKeylet.
key, payAmount),
7555 auto const loanSle = env.
le(loanKeylet);
7556 BEAST_EXPECT(loanSle);
7559 .principalOutstanding = loanSle ?
Number{loanSle->at(sfPrincipalOutstanding)} : 0,
7560 .expectedNewPrincipal = expectedNewPrincipal,
7561 .managementFeeChange =
7562 (loanSle ?
Number{loanSle->at(sfManagementFeeOutstanding)} :
Number{0}) -
7563 managementFeeBefore,
7570 Params
const principalCase{
7574 .paymentInterval = 60,
7576 .overpayment =
Number{49999998, -9},
7589 fixed.principalOutstanding ==
fixed.expectedNewPrincipal,
7590 "fixed principal " +
to_string(
fixed.principalOutstanding) +
" != expected " +
7597 Result const legacy = runScenario(
all_ - fixCleanup3_2_0, principalCase);
7599 legacy.principalOutstanding == legacy.expectedNewPrincipal,
7600 "legacy principal " +
to_string(legacy.principalOutstanding) +
" != expected " +
7601 to_string(legacy.expectedNewPrincipal));
7608 Params
const feeCase{
7612 .paymentInterval = 30u * 24 * 60 * 60,
7614 .overpayment =
Number{214367363, -10},
7617 .vaultScale = std::nullopt};
7619 Result const feeFixed = runScenario(
all_, feeCase);
7620 Result const feeLegacy = runScenario(
all_ - fixCleanup3_2_0, feeCase);
7625 feeFixed.principalOutstanding == feeFixed.expectedNewPrincipal,
7626 "fee-case fixed principal " +
to_string(feeFixed.principalOutstanding) +
7627 " != expected " +
to_string(feeFixed.expectedNewPrincipal));
7629 feeLegacy.principalOutstanding == feeLegacy.expectedNewPrincipal + feeLegacy.unit,
7630 "fee-case legacy principal " +
to_string(feeLegacy.principalOutstanding) +
7631 " != expected " +
to_string(feeLegacy.expectedNewPrincipal + feeLegacy.unit));
7645 BEAST_EXPECT(feeFixed.managementFeeChange == feeLegacy.managementFeeChange);
7647 (feeFixed.managementFeeChange ==
Number{-8219709543, -10}),
7648 "fee-case mgmt fee change " +
to_string(feeFixed.managementFeeChange));
7667 testcase(
"LoanSet near-zero interest rate covers principal");
7669 using namespace jtx;
7670 using namespace loan;
7672 Number const principalRequested{1000};
7677 bool created =
false;
7686 Env env(*
this, features);
7688 Account const issuer{
"issuer"};
7689 Account const lender{
"vaultOwner"};
7690 Account const borrower{
"borrower"};
7692 env.
fund(
XRP(1'000'000), issuer, lender, borrower);
7693 env(
fset(issuer, asfDefaultRipple));
7698 env(
trust(lender, iouLimit));
7699 env(
trust(borrower, iouLimit));
7700 env(
pay(issuer, lender, iouAsset(1'000'000)));
7701 env(
pay(issuer, borrower, iouAsset(1'000'000)));
7708 {.vaultDeposit = 100'000, .debtMax = 0, .managementFeeRate =
TenthBips16{0}});
7710 auto const brokerSle = env.
le(broker.brokerKeylet());
7711 BEAST_EXPECT(brokerSle);
7712 auto const loanSequence = brokerSle ? brokerSle->at(sfLoanSequence) : 0;
7713 auto const loanKeylet =
keylet::loan(broker.brokerID, loanSequence);
7715 env(
set(borrower, broker.brokerID, principalRequested),
7716 Sig(sfCounterpartySignature, lender),
7719 kPaymentInterval(400),
7726 if (
auto const loanSle = env.
le(loanKeylet))
7729 r.loanScale = loanSle->at(sfLoanScale);
7730 r.principal = loanSle->at(sfPrincipalOutstanding);
7731 r.totalValue = loanSle->at(sfTotalValueOutstanding);
7732 r.managementFee = loanSle->at(sfManagementFeeOutstanding);
7733 r.periodicPayment = loanSle->at(sfPeriodicPayment);
7746 BEAST_EXPECT(!legacy.created);
7751 BEAST_EXPECT(
fixed.created);
7752 BEAST_EXPECT(
fixed.loanScale == -10);
7753 BEAST_EXPECT(
fixed.principal == principalRequested);
7754 BEAST_EXPECT((
fixed.totalValue ==
Number{10000000001903, -10}));
7755 BEAST_EXPECT(
fixed.managementFee == beast::kZero);
7760 BEAST_EXPECT((
fixed.periodicPayment ==
Number{5000000000951293762, -16}));
7761 BEAST_EXPECT((
fixed.periodicPayment * 2 ==
Number{1000000000190258752, -15}));
7762 BEAST_EXPECT(
fixed.periodicPayment * 2 > principalRequested);
7775 testcase(
"bug: computeOverpaymentComponents isRounded assertion");
7777 using namespace jtx;
7778 using namespace loan;
7781 Account const issuer{
"issuer"};
7782 Account const lender{
"vaultOwner"};
7783 Account const borrower{
"borrower"};
7785 env.
fund(
XRP(1'000'000), issuer, lender, borrower);
7786 env(
fset(issuer, asfDefaultRipple));
7791 env(
trust(lender, iouLimit));
7792 env(
trust(borrower, iouLimit));
7793 env(
pay(issuer, lender, iouAsset(1'000'000)));
7794 env(
pay(issuer, borrower, iouAsset(1'000'000)));
7801 {.vaultDeposit = 100'000,
7806 auto const sleBroker = env.
le(broker.brokerKeylet());
7807 if (!BEAST_EXPECT(sleBroker))
7809 auto const loanSequence = sleBroker->at(sfLoanSequence);
7810 auto const loanKeylet =
keylet::loan(broker.brokerID, loanSequence);
7812 using namespace loan;
7813 env(
set(borrower, broker.brokerID,
Number{1000}, tfLoanOverpayment),
7814 Sig(sfCounterpartySignature, lender),
7817 kPaymentInterval(60),
7851 if (!features[fixCleanup3_2_0])
7854 testcase(
"edge: integer MPT principal stuck mid-loan completes via final");
7856 using namespace jtx;
7857 Env env(*
this, features);
7859 Account const issuer{
"issuer"};
7860 Account const lender{
"lender"};
7861 Account const borrower{
"borrower"};
7863 env.
fund(
XRP(100'000), issuer, lender, borrower);
7867 mptt.
create({.maxAmt = 100'000, .flags = tfMPTCanTransfer});
7873 env(
pay(issuer, lender, asset(10'000)));
7874 env(
pay(issuer, borrower, asset(10'000)));
7877 Vault const vault{env};
7878 auto [vaultTx, vaultKeylet] = vault.create({.owner = lender, .asset = asset});
7882 env(vault.deposit({.depositor = lender, .id = vaultKeylet.key, .amount = asset(5'000)}));
7891 auto const brokerStateBefore = env.
le(brokerKeylet);
7892 if (!BEAST_EXPECT(brokerStateBefore))
7894 auto const loanSequence = brokerStateBefore->at(sfLoanSequence);
7895 auto const loanKeylet =
keylet::loan(brokerKeylet.key, loanSequence);
7898 Sig(sfCounterpartySignature, lender),
7905 auto const borrowerStart = env.
balance(borrower, asset).
value();
7918 for (
int i = 0; i < 3; ++i)
7923 auto const sle = env.
le(loanKeylet);
7924 if (!BEAST_EXPECT(sle))
7926 BEAST_EXPECT(sle->at(sfPrincipalOutstanding) == expectedPO[i]);
7927 BEAST_EXPECT(sle->at(sfTotalValueOutstanding) == expectedTVO[i]);
7928 BEAST_EXPECT(sle->at(sfPaymentRemaining) == expectedRemaining[i]);
7933 auto const borrowerEnd = env.
balance(borrower, asset).
value();
7934 BEAST_EXPECT(borrowerStart - borrowerEnd == asset(3).value());
7946 testcase(
"bug: LoanPay asserts 'interest due delta' on near-zero rate");
7948 using namespace jtx;
7949 using namespace std::chrono_literals;
7952 Account const issuer{
"issuer"};
7953 Account const lender{
"lender"};
7954 Account const borrower{
"borrower"};
7956 env.
fund(
XRP(1'000'000), issuer, lender, borrower);
7958 env(
fset(issuer, asfDefaultRipple));
7962 env(
trust(lender, iouAsset(1'000'000'000)));
7963 env(
trust(borrower, iouAsset(1'000'000'000)));
7964 env(
pay(issuer, lender, iouAsset(5'000'000)));
7965 env(
pay(issuer, borrower, iouAsset(5'000'000)));
7969 .vaultDeposit = 1'000'000,
7970 .debtMax = 1'000'000,
7978 using namespace loan;
7980 auto const loanSetFee =
Fee(env.
current()->fees().base * 2);
7981 Number const principalRequest{100};
7983 auto createJson = env.
json(
7988 createJson[
"InterestRate"] = 1;
7989 createJson[
"PaymentTotal"] = 3;
7990 createJson[
"PaymentInterval"] = 600;
7993 auto const loanSequence = brokerStateBefore->at(sfLoanSequence);
7996 createJson = env.
json(createJson,
Sig(sfCounterpartySignature, lender));
8021 testcase(
"integration: full loan lifecycle, vault interest at near-zero rate");
8023 using namespace jtx;
8025 using namespace std::chrono_literals;
8028 Account const issuer{
"issuer"};
8029 Account const lender{
"lender"};
8030 Account const borrower{
"borrower"};
8032 env.
fund(
XRP(1'000'000), issuer, lender, borrower);
8034 env(
fset(issuer, asfDefaultRipple));
8039 env(
trust(lender, trustLimit));
8040 env(
trust(borrower, trustLimit));
8042 env(
pay(issuer, lender, iouAsset(5'000'000'000LL)));
8043 env(
pay(issuer, borrower, iouAsset(5'000'000'000LL)));
8046 auto usdBalance = [&](
Account const& a) {
8049 STAmount const borrowerStartBal = usdBalance(borrower);
8052 .vaultDeposit =
Number{2, 9},
8061 BEAST_EXPECT(vaultBefore);
8062 Number const vaultAvailableBefore = vaultBefore->at(sfAssetsAvailable);
8065 auto const loanSetFee =
Fee(env.
current()->fees().base * 2);
8066 Number const principalRequest{1, 9};
8067 auto createJson = env.
json(
8071 createJson[
"InterestRate"] = 1;
8072 createJson[
"PaymentTotal"] = 3;
8073 createJson[
"PaymentInterval"] = 600;
8076 auto const loanSequence = brokerStateBefore->at(sfLoanSequence);
8078 createJson = env.
json(createJson,
Sig(sfCounterpartySignature, lender));
8082 auto const loanSle = env.
le(loanKeylet);
8083 BEAST_EXPECT(loanSle);
8084 Number const expectedTotalInterest =
8085 loanSle->at(sfTotalValueOutstanding) - loanSle->at(sfPrincipalOutstanding);
8091 Number const vaultAvailableAfter = vaultAfter->at(sfAssetsAvailable);
8092 Number const vaultGain = vaultAvailableAfter - vaultAvailableBefore;
8094 STAmount const borrowerEndBal = usdBalance(borrower);
8095 STAmount const borrowerNetOut = borrowerStartBal - borrowerEndBal;
8099 BEAST_EXPECT(vaultGain == expectedTotalInterest);
8100 BEAST_EXPECT(
Number(borrowerNetOut) == expectedTotalInterest);
8109 Number const decimalReference{38051750382930729LL, -17};
8110 Number const tolerance{1, -6};
8111 Number const error =
abs(vaultGain - decimalReference);
8114 "vault gain " +
to_string(vaultGain) +
" differs from Decimal reference " +
8115 to_string(decimalReference) +
" by " +
to_string(error) +
" — exceeds tolerance " +
8129 using namespace jtx;
8130 using namespace loan;
8133 bool const withAmendment = features[fixCleanup3_2_0];
8150 auto runTest = [&](
Number vaultDeposit,
auto&& body) {
8151 Env env(*
this, features);
8153 Account const issuer{
"issuer"};
8154 Account const lender{
"lender"};
8155 Account const borrower{
"borrower"};
8157 env.
fund(
XRP(1'000'000'000), issuer, lender, borrower);
8162 env(
fset(issuer, asfAllowTrustLineClawback));
8166 env(
trust(lender, iou(1'000'000'000)));
8167 env(
trust(borrower, iou(1'000'000'000)));
8169 env(
pay(issuer, lender, iou(100'000'000)));
8170 env(
pay(issuer, borrower, iou(100'000'000)));
8175 .vaultDeposit = vaultDeposit,
8178 .coverDeposit = 5'000,
8185 Ctx{.issuer = issuer,
8187 .borrower = borrower,
8190 .brokerParams = brokerParams});
8199 testcase(
"LoanPay minimum cover scale consistency");
8212 Asset const asset{c.iou};
8218 if (!BEAST_EXPECT(brokerSle1))
8219 return std::nullopt;
8220 auto const tinyLoanSeq = brokerSle1->at(sfLoanSequence);
8221 auto const tinyLoanKeylet =
keylet::loan(c.broker.brokerID, tinyLoanSeq);
8223 env(
set(c.borrower, c.broker.brokerID,
Number{1, -2}),
8224 Sig(sfCounterpartySignature, c.lender),
8227 kPaymentInterval(86400 * 365),
8235 if (!BEAST_EXPECT(brokerSle2))
8236 return std::nullopt;
8237 auto const bigLoanSeq = brokerSle2->at(sfLoanSequence);
8238 auto const bigLoanKeylet =
keylet::loan(c.broker.brokerID, bigLoanSeq);
8240 env(
set(c.borrower, c.broker.brokerID,
Number{500}),
8241 Sig(sfCounterpartySignature, c.lender),
8244 kPaymentInterval(86400 * 365),
8252 auto const tinyLoanSle = env.
le(tinyLoanKeylet);
8253 auto const bigLoanSle = env.
le(bigLoanKeylet);
8255 if (!BEAST_EXPECT(tinyLoanSle) || !BEAST_EXPECT(bigLoanSle) ||
8256 !BEAST_EXPECT(vaultSle))
8257 return std::nullopt;
8258 if (!BEAST_EXPECT(tinyLoanSle->at(sfLoanScale) == -12) ||
8259 !BEAST_EXPECT(bigLoanSle->at(sfLoanScale) == -11) ||
8261 return std::nullopt;
8275 Number const expectedCoverAfter = withAmendment ?
Number{1330651855688460000, -15}
8276 :
Number{1330651855688458000, -15};
8278 Number{c.brokerParams.coverDeposit} - expectedCoverAfter;
8280 env(coverClawback(c.issuer),
8281 kLoanBrokerId(c.broker.brokerID),
8286 if (!BEAST_EXPECT(brokerSle) ||
8287 !BEAST_EXPECT(brokerSle->at(sfCoverAvailable) == expectedCoverAfter))
8288 return std::nullopt;
8290 return LoanKeylets{.tiny = tinyLoanKeylet, .big = bigLoanKeylet};
8296 auto feeGoesToPseudo = [&](
Env& env, Ctx
const& c,
Keylet const& loanKeylet) ->
bool {
8297 Asset const asset{c.iou};
8299 if (!BEAST_EXPECT(brokerSle))
8301 auto const pseudoAcct =
Account(
"pseudo", brokerSle->at(sfAccount));
8302 auto const pseudoBefore = env.
balance(pseudoAcct, c.iou);
8304 auto const payLoan = env.
le(loanKeylet);
8305 if (!BEAST_EXPECT(payLoan))
8307 auto const periodicPayment = payLoan->at(sfPeriodicPayment);
8308 auto const serviceFee = payLoan->at(sfLoanServiceFee);
8309 std::int32_t const loanScale = payLoan->at(sfLoanScale);
8312 auto const payAmt =
STAmount{asset, payment + serviceFee};
8317 auto const pseudoAfter = env.
balance(pseudoAcct, c.iou);
8318 return pseudoAfter.
number() > pseudoBefore.number();
8327 runTest(1'000, [&](
Env& env, Ctx
const& c) {
8328 auto const loans = setupLoansAndClawback(env, c);
8331 BEAST_EXPECT(feeGoesToPseudo(env, c, loans->big) == !withAmendment);
8340 runTest(1'000, [&](
Env& env, Ctx
const& c) {
8341 auto const loans = setupLoansAndClawback(env, c);
8344 BEAST_EXPECT(!feeGoesToPseudo(env, c, loans->tiny));
8363 testcase(
"CoverWithdraw minimum cover scale consistency");
8365 100'000, [&](
Env& env, Ctx
const& c) {
8366 Asset const asset{c.iou};
8371 env(
set(c.borrower, c.broker.brokerID,
Number{500}),
8372 Sig(sfCounterpartySignature, c.lender),
8375 kPaymentInterval(86400 * 365),
8382 if (!BEAST_EXPECT(brokerSle) || !BEAST_EXPECT(vaultSle))
8385 auto const coverAvail = brokerSle->at(sfCoverAvailable);
8386 auto const debtTotal = brokerSle->at(sfDebtTotal);
8388 auto const debtScale =
scale(debtTotal, asset);
8391 BEAST_EXPECT(debtScale < vaultScale);
8393 auto const oldMin = [&]() {
8401 debtTotal,
TenthBips32{c.brokerParams.coverRateMin}, vaultSle);
8406 Number const expectedNewMin{1330650518688500000, -15};
8407 Number const expectedOldMin{1330650518688472000, -15};
8408 BEAST_EXPECT(newMin == expectedNewMin);
8409 BEAST_EXPECT(oldMin == expectedOldMin);
8413 auto const target = oldMin + (newMin - oldMin) / 2;
8414 auto const withdrawAmount =
STAmount{asset, coverAvail - target};
8420 env(coverWithdraw(c.lender, c.broker.brokerID, withdrawAmount),
8427 env(coverWithdraw(c.lender, c.broker.brokerID, withdrawAmount));
8441 testcase(
"LoanSet minimum cover scale consistency");
8443 1'000, [&](
Env& env, Ctx
const& c) {
8452 env(
set(c.borrower, c.broker.brokerID,
Number{1, -2}),
8453 Sig(sfCounterpartySignature, c.lender),
8456 kPaymentInterval(86400 * 365),
8460 env(
set(c.borrower, c.broker.brokerID,
Number{500}),
8461 Sig(sfCounterpartySignature, c.lender),
8464 kPaymentInterval(86400 * 365),
8472 Number const expectedCoverAfter = withAmendment ?
Number{1330651855688460000, -15}
8473 :
Number{1330651855688458000, -15};
8475 Number{c.brokerParams.coverDeposit} - expectedCoverAfter;
8476 env(coverClawback(c.issuer),
8477 kLoanBrokerId(c.broker.brokerID),
8483 if (!BEAST_EXPECT(vaultSle))
8486 BEAST_EXPECT(vaultScale == -11);
8502 auto const tinyPrincipal =
Number{1, -11};
8506 env(
set(c.borrower, c.broker.brokerID, tinyPrincipal),
8507 Sig(sfCounterpartySignature, c.lender),
8510 kPaymentInterval(86400 * 365),
8515 env(
set(c.borrower, c.broker.brokerID, tinyPrincipal),
8516 Sig(sfCounterpartySignature, c.lender),
8519 kPaymentInterval(86400 * 365),
8545 for (
auto const flags : {0u, tfLoanOverpayment})
8557 testLoanPayLateFullPaymentBypassesPenalties(features);
8558 testLoanCoverMinimumRoundingExploit(features);
8611 {fixCleanup3_1_3, fixCleanup3_2_0, featureMPTokensV2},
all_))
8647 using namespace jtx;
8649 Account const issuer(
"issuer");
8650 Account const lender(
"lender");
8651 Account const borrower(
"borrower");
8663 .vaultDeposit = principalRequest * 10,
8666 .managementFeeRate = managementFeeRate};
8669 .counter = borrower,
8670 .principalRequest = principalRequest,
8671 .serviceFee = serviceFee,
8672 .interest = interest,
8673 .payTotal = payTotal,
8674 .payInterval = payInterval,
8677 runLoan(assetType, brokerParams, loanParams,
all_);
8684 auto const numIterations = [s =
arg()]() ->
int {
8685 int const defaultNum = 5;
8691 auto const r = stoi(s, &pos);
8692 if (pos != s.size())
8702 using namespace jtx;
8704 auto const updateInterval =
std::min(numIterations / 5, 100);
8706 for (
int i = 0; i < numIterations; ++i)
8708 if (i % updateInterval == 0)
8709 testcase <<
"Random Loan Test iteration " << (i + 1) <<
"/" << numIterations;
8720 using namespace jtx;
8723 .vaultDeposit = 10000,
8730 .counter =
Account(
"borrower"),
8731 .principalRequest =
Number{200000, -6},
8734 .payInterval = 200};
LogOs< char > log
Logging output stream.
TestcaseT testcase
Memberspace for declaring test cases.
std::string const & arg() const
Return the argument associated with the runner.
Lightweight wrapper to tag static string.
Value removeMember(char const *key)
Remove and return the named member.
constexpr TIss const & get() const
constexpr bool native() const
constexpr bool holds() const
static constexpr auto kDisabledTxTypes
A currency issued by an account.
static constexpr std::uint32_t kDefaultPaymentTotal
static constexpr std::uint32_t kMinPaymentTotal
static constexpr std::uint32_t kDefaultPaymentInterval
static constexpr std::uint32_t kMinPaymentInterval
static constexpr std::uint32_t kDefaultGracePeriod
std::chrono::time_point< NetClock > time_point
std::chrono::duration< rep, period > duration
Number is a floating point type that can represent a wide range of values.
Number truncate() const noexcept
constexpr int exponent() const noexcept
Returns the exponent of the external view of the Number.
Slice slice() const noexcept
Asset const & asset() const
json::Value getJson(JsonOptions=JsonOptions::Values::None) const override
STAmount const & value() const noexcept
Blob getFieldVL(SField const &field) const
Serializer getSerializer() const
void setFieldObject(SField const &field, STObject const &v)
STObject getFieldObject(SField const &field) const
Slice slice() const noexcept
virtual LoadFeeTrack & getFeeTrack()=0
An immutable linear range of bytes.
void run() override
Runs the suite.
beast::xor_shift_engine engine_
std::uniform_int_distribution paymentIntervalDist_
std::uniform_int_distribution paymentTotalDist_
std::uniform_int_distribution< std::int64_t > principalDist_
void run() override
Runs the suite.
std::uniform_int_distribution< std::uint16_t > managementFeeRateDist_
std::uniform_int_distribution serviceFeeDist_
std::uniform_int_distribution< std::uint32_t > interestRateDist_
std::uniform_int_distribution assetDist_
void testWrongMaxDebtBehavior(FeatureBitset features)
void testCaseWrapper(jtx::Env &env, jtx::MPTTester &mptt, std::array< TAsset, NAsset > const &assets, BrokerInfo const &broker, Number const &loanAmount, int interestExponent)
Wrapper to run a series of lifecycle tests for a given asset and loan amount.
void describeLoan(jtx::Env &env, BrokerParameters const &brokerParams, LoanParameters const &loanParams, AssetType assetType, jtx::Account const &issuer, jtx::Account const &lender, jtx::Account const &borrower)
void testDosLoanPay(FeatureBitset features)
void testRIPD3902(FeatureBitset features)
void runAmendmentSensitive(FeatureBitset features)
void testBorrowerIsBroker()
void runLoan(AssetType assetType, BrokerParameters const &brokerParams, LoanParameters const &loanParams, FeatureBitset features)
static void topUpBorrower(jtx::Env &env, BrokerInfo const &broker, jtx::Account const &issuer, jtx::Account const &borrower, LoanState const &state, std::optional< Number > const &servFee)
void testBugOverpaymentPrincipalChange()
void runAmendmentIndependent()
void testRIPD3831(FeatureBitset features)
void testBatchBypassCounterparty(FeatureBitset features)
void testLoanPayComputePeriodicPaymentValidTotalInterestInvariant(FeatureBitset features)
void run() override
Runs the suite.
void testRPC(FeatureBitset features)
void testLoanPayComputePeriodicPaymentValidRateInvariant(FeatureBitset features)
bool canImpairLoan(jtx::Env const &env, BrokerInfo const &broker, LoanState const &state)
void lifecycle(std::string const &caseLabel, char const *label, jtx::Env &env, Number const &loanAmount, int interestExponent, jtx::Account const &lender, jtx::Account const &borrower, jtx::Account const &evan, BrokerInfo const &broker, jtx::Account const &pseudoAcct, std::uint32_t flags, std::function< void(Keylet const &loanKeylet, VerifyLoanStatus const &verifyLoanStatus)> toEndOfLife)
Runs through the complete lifecycle of a loan.
static std::string getCurrencyLabel(Asset const &asset)
void testInvalidLoanPay()
void testLoanPayDebtDecreaseInvariant(FeatureBitset features)
void testBugInterestDueDeltaCrash()
void testWithdrawReflectsUnrealizedLoss(FeatureBitset features)
void testYieldTheftRounding(std::uint32_t flags)
void testInvalidLoanDelete()
void testLoanNextPaymentDueDateOverflow(FeatureBitset features)
void testLoanPayBrokerOwnerMissingTrustline(FeatureBitset features)
void testLoanSetNearZeroInterestRateSucceeds()
void testLoanSetBrokerOwnerNoPermissionedDomainMPT(FeatureBitset features)
void testLoanPayComputePeriodicPaymentValidTotalPrincipalPaidInvariant(FeatureBitset features)
void testFullLifecycleVaultPnLNearZeroRate()
void testInvalidLoanSet()
void testRoundingAllowsUndercoverage(FeatureBitset features)
void testLoanSet(FeatureBitset features)
LoanState getCurrentState(jtx::Env const &env, BrokerInfo const &broker, Keylet const &loanKeylet)
Get the state without checking anything.
void makeLoanPayments(jtx::Env &env, BrokerInfo const &broker, LoanParameters const &loanParams, Keylet const &loanKeylet, VerifyLoanStatus const &verifyLoanStatus, jtx::Account const &issuer, jtx::Account const &lender, jtx::Account const &borrower, PaymentParameters const &paymentParams=PaymentParameters::defaults())
void testRIPD3459(FeatureBitset features)
void testInvalidLoanManage()
void testAccountSendMptMinAmountInvariant(FeatureBitset features)
void testMinimumBrokerCoverConsistency(FeatureBitset features)
void testLifecycle(FeatureBitset features)
void testSelfLoan(FeatureBitset features)
void testSequentialFLCDepletion(FeatureBitset features)
void testLoanPayBrokerOwnerNoPermissionedDomainMPT(FeatureBitset features)
BrokerInfo createVaultAndBroker(jtx::Env &env, jtx::PrettyAsset const &asset, jtx::Account const &lender, BrokerParameters const ¶ms=BrokerParameters::defaults())
void testPoCUnsignedUnderflowOnFullPayAfterEarlyPeriodic(FeatureBitset features)
void testLoanPayComputePeriodicPaymentValidTotalInterestPaidInvariant(FeatureBitset features)
LoanState getCurrentState(jtx::Env const &env, BrokerInfo const &broker, Keylet const &loanKeylet, VerifyLoanStatus const &verifyLoanStatus)
Get the state and check the values against the parameters used in lifecycle.
jtx::PrettyAsset createAsset(jtx::Env &env, AssetType assetType, BrokerParameters const &brokerParams, jtx::Account const &issuer, jtx::Account const &lender, jtx::Account const &borrower)
void testIssuerIsBorrower(FeatureBitset features)
void testDustManipulation(FeatureBitset features)
std::optional< std::tuple< BrokerInfo, Keylet, jtx::Account > > createLoan(jtx::Env &env, AssetType assetType, BrokerParameters const &brokerParams, LoanParameters const &loanParams, jtx::Account const &issuer, jtx::Account const &lender, jtx::Account const &borrower)
void testBugOverpayUnroundedAmount()
void testIntegerScalePrincipalSticks(FeatureBitset features)
void testLendingCanTradeDisabledNoImpact()
std::string const iouCurrency_
void testOverpaymentManagementFee(FeatureBitset features)
void testLoanPayBrokerOwnerUnauthorizedMPT(FeatureBitset features)
void testServiceFeeOnBrokerDeepFreeze()
Immutable cryptographic account descriptor.
SecretKey const & sk() const
Return the secret key.
std::string const & human() const
Returns the human readable public key.
std::string const & name() const
Return the name.
PublicKey const & pk() const
Return the public key.
AccountID id() const
Returns the Account ID.
A transaction testing environment.
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
TER ter() const
Return the TER for the last JTx.
json::Value json(JsonValue &&jv, FN const &... fN)
Create JSON from parameters.
SLE::const_pointer le(Account const &account) const
Return an account root.
std::uint32_t ownerCount(Account const &account) const
Return the number of objects owned by an account.
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
void enableFeature(uint256 const feature)
void disableFeature(uint256 const feature)
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
JTx jt(JsonValue &&jv, FN const &... fN)
Create a JTx from parameters.
PrettyAmount balance(Account const &account) const
Returns the XRP balance on an account.
beast::unit_test::Suite & test
void trust(STAmount const &amount, Account const &account)
Establish trust lines.
std::shared_ptr< STObject const > meta()
Return metadata for the last JTx.
beast::Journal const journal
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
NetClock::time_point now()
Returns the current network time.
Converts to IOU Issue or STAmount.
Test helper for creating, mutating, and asserting MPT and confidential MPT ledger state.
void set(MPTSet const &set={})
void create(MPTCreate const &arg=MPTCreate{})
void authorize(MPTAuthorize const &arg=MPTAuthorize{})
MPTID const & issuanceID() const
Converts to MPT Issue or STAmount.
Set a multisignature on a JTx.
Set the regular signature on a JTx.
Set the expected result code for a JTx The test will fail if the code doesn't match.
Adds an inner Batch transaction to a JTx and autofills it.
T duration_cast(T... args)
T emplace_back(T... args)
detail::XorShiftEngine<> xor_shift_engine
XOR-shift Generator.
@ Object
object value (collection of name/value pairs).
Number loanPrincipalFromPeriodicPayment(Rules const &rules, Number const &periodicPayment, Number const &periodicRate, std::uint32_t paymentsRemaining)
ExtendedPaymentComponents computeOverpaymentComponents(Rules const &rules, Asset const &asset, int32_t const loanScale, Number const &overpayment, TenthBips32 const overpaymentInterestRate, TenthBips32 const overpaymentFeeRate, TenthBips16 const managementFeeRate)
PaymentComponents computePaymentComponents(Rules const &rules, Asset const &asset, std::int32_t scale, Number const &totalValueOutstanding, Number const &principalOutstanding, Number const &managementFeeOutstanding, Number const &periodicPayment, Number const &periodicRate, std::uint32_t paymentRemaining, TenthBips16 managementFeeRate)
Keylet computation functions.
Keylet loanBroker(AccountID const &owner, std::uint32_t seq) noexcept
Keylet unchecked(uint256 const &key) noexcept
Any ledger entry.
Keylet loan(uint256 const &loanBrokerID, std::uint32_t loanSeq) noexcept
Keylet vault(AccountID const &owner, std::uint32_t seq) noexcept
Keylet mptoken(MPTID const &issuanceID, AccountID const &holder) noexcept
Keylet account(AccountID const &id) noexcept
AccountID root.
Keylet trustLine(AccountID const &id0, AccountID const &id1, Currency const ¤cy) noexcept
The index of a trust line for a given currency.
json::Value outer(jtx::Account const &account, uint32_t seq, STAmount const &fee, std::uint32_t flags)
Build an outer Batch transaction JSON object.
XRPAmount calcBatchFee(jtx::Env const &env, uint32_t const &numSigners, uint32_t const &txns=0)
Calculate the expected outer Batch transaction fee.
json::Value deleteCred(jtx::Account const &acc, jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
json::Value accept(jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
json::Value create(jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Deposit preauthorize operations.
json::Value set(AccountID const &account, uint256 const &vaultId, uint32_t flags)
json::Value coverWithdraw(AccountID const &account, uint256 const &brokerID, STAmount const &amount, uint32_t flags)
json::Value coverDeposit(AccountID const &account, uint256 const &brokerID, STAmount const &amount, uint32_t flags)
json::Value coverClawback(AccountID const &account, std::uint32_t flags)
json::Value del(AccountID const &account, uint256 const &brokerID, uint32_t flags)
json::Value set(AccountID const &account, uint256 const &loanBrokerID, Number principalRequested, std::uint32_t flags)
auto const kPaymentInterval
json::Value manage(AccountID const &account, uint256 const &loanID, std::uint32_t flags)
auto const kLoanServiceFee
auto const kOverpaymentInterestRate
auto const kCloseInterestRate
auto const kClosePaymentFee
auto const kLatePaymentFee
json::Value pay(AccountID const &account, uint256 const &loanID, STAmount const &amount, std::uint32_t flags)
auto const kLateInterestRate
std::vector< Credential > Credentials
json::Value setTx(AccountID const &account, Credentials const &credentials, std::optional< uint256 > domain)
uint256 getNewDomain(std::shared_ptr< STObject const > const &meta)
json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
std::vector< FeatureBitset > amendmentCombinations(std::initializer_list< uint256 > features, FeatureBitset seed)
Returns all 2^N permutations of a seed FeatureBitset with each subset of the given features excluded.
XrpT const XRP
Converts to XRP Issue or STAmount.
json::Value noop(Account const &account)
The null transaction.
std::uint32_t ownerCount(Env const &env, Account const &account)
XRPAmount txFee(Env const &env, std::uint16_t n)
std::unique_ptr< Config > makeConfig(std::map< std::string, std::string > extraTxQ={}, std::map< std::string, std::string > extraVoting={})
static Number number(STAmount const &a)
json::Value fclear(Account const &account, std::uint32_t off)
Remove account flag.
FeatureBitset testableAmendments()
auto const kData
General field definitions, or fields used in multiple transaction namespaces.
std::array< Account, 1+sizeof...(Args)> noripple(Account const &account, Args const &... args)
Designate accounts as no-ripple in Env::fund.
json::Value offer(Account const &account, STAmount const &takerPays, STAmount const &takerGets, std::uint32_t flags)
Create an offer.
json::Value trust(Account const &account, STAmount const &amount, std::uint32_t flags)
Modify a trust line.
PrettyAmount drops(Integer i)
Returns an XRP PrettyAmount, which is trivially convertible to STAmount.
json::Value signers(Account const &account, std::uint32_t quorum, std::vector< Signer > const &v)
json::Value fset(Account const &account, std::uint32_t on, std::uint32_t off=0)
Add and/or remove flag.
static MPTInit const kMptInitNoFund
BEAST_DEFINE_TESTSUITE(AMMClawback, app, xrpl)
BEAST_DEFINE_TESTSUITE_MANUAL(AMMCalc, app, xrpl)
STTx createTx(bool disabling, LedgerIndex seq, PublicKey const &txKey)
Create ttUNL_MODIFY Tx.
constexpr XRPAmount
Convert XRP to drops (integral types).
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
static constexpr Number kNumZero
bool set(T &target, std::string const &name, Section const §ion)
Set a value from a configuration Section If the named value is not found or doesn't parse as a T,...
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
Number loanPeriodicRate(TenthBips32 interestRate, std::uint32_t paymentInterval)
std::string strHex(FwdIt begin, FwdIt end)
constexpr TenthBips32 percentageToTenthBips(std::uint32_t percentage)
constexpr T tenthBipsOfValue(T value, TenthBips< TBips > bips)
int getAssetsTotalScale(SLE::const_ref vaultSle)
constexpr XRPAmount kDropsPerXrp
Number of drops per 1 XRP.
int scale(Number const &number, Asset const &asset)
Get the scale of a Number for a given asset.
Number minimumBrokerCover(Number const &debtTotal, TenthBips32 coverRateMinimum, SLE::const_ref vaultSle)
static bool authorized(Port const &port, std::map< std::string, std::string > const &h)
constexpr FlagValue tmfMPTSetCanTrade
TenthBips< std::uint32_t > TenthBips32
static FunctionType fixed(Keylet const &keylet)
TenthBips< std::uint16_t > TenthBips16
boost::outcome_v2::result< T, std::error_code > Result
std::string to_string(BaseUInt< Bits, Tag > const &a)
constexpr std::size_t kMaxDataPayloadLength
The maximum length of Data payload.
TER checkLoanGuards(Asset const &vaultAsset, Number const &principalRequested, bool expectInterest, std::uint32_t paymentTotal, LoanProperties const &properties, beast::Journal j)
STAmount roundToScale(STAmount const &value, std::int32_t scale, Number::RoundingMode rounding=Number::getround())
Round an arbitrary precision Amount to the precision of an STAmount that has a given exponent.
constexpr TenthBips32 kTenthBipsPerUnity(kBipsPerUnity.value() *10)
STAmount clawbackAmount(SLE::const_ref vault, std::optional< STAmount > const &maybeAmount, AccountID const &account)
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
LoanState computeTheoreticalLoanState(Rules const &rules, Number const &periodicPayment, Number const &periodicRate, std::uint32_t const paymentRemaining, TenthBips32 const managementFeeRate)
constexpr Number abs(Number x) noexcept
void roundToAsset(A const &asset, Number &value)
Round an arbitrary precision Number IN PLACE to the precision of a given Asset.
BaseUInt< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Number roundPeriodicPayment(Asset const &asset, Number const &periodicPayment, std::int32_t scale)
Ensure the periodic payment is always rounded consistently.
@ TxSign
inner transaction to sign
LoanState constructRoundedLoanState(SLE::const_ref loan)
bool isTesSuccess(TER x) noexcept
Buffer sign(PublicKey const &pk, SecretKey const &sk, Slice const &message)
Generate a signature for a message.
Number computeManagementFee(Asset const &asset, Number const &interest, TenthBips32 managementFeeRate, std::int32_t scale)
TERSubset< CanCvtToTER > TER
constexpr FlagValue tmfMPTCanEnableCanTrade
TER requireAuth(ReadView const &view, MPTIssue const &mptIssue, AccountID const &account, AuthType authType=AuthType::Legacy, std::uint8_t depth=0)
Check if the account lacks required authorization for MPT.
@ tecINSUFFICIENT_RESERVE
@ tecINSUFFICIENT_PAYMENT
static constexpr std::uint32_t kSecondsInYear
LoanProperties computeLoanProperties(Rules const &rules, Asset const &asset, Number const &principalOutstanding, TenthBips32 interestRate, std::uint32_t paymentInterval, std::uint32_t paymentsRemaining, TenthBips32 managementFeeRate, std::int32_t minimumScale)
LoanState constructLoanState(Number const &totalValueOutstanding, Number const &principalOutstanding, Number const &managementFeeOutstanding)
constexpr FlagValue tfFullyCanonicalSig
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)
bool isRounded(Asset const &asset, Number const &value, std::int32_t scale)
A pair of SHAMap key and LedgerEntryType.
This structure captures the parts of a loan state.
Number principalOutstanding
Number trackedPrincipalDelta
Number trackedManagementFeeDelta
BrokerInfo(jtx::PrettyAsset const &asset, Keylet const &brokerKeylet, Keylet const &vaultKeylet, BrokerParameters p)
int vaultScale(jtx::Env const &env) const
Keylet vaultKeylet() const
Keylet brokerKeylet() const
Number maxCoveredLoanValue(Number const ¤tDebt) const
std::optional< std::uint8_t > vaultScale
static BrokerParameters const & defaults()
TenthBips16 managementFeeRate
TenthBips32 coverRateLiquidation
std::optional< TenthBips32 > lateInterest
std::optional< STAmount > setFee
std::optional< Number > lateFee
std::optional< std::uint32_t > flags
std::optional< std::uint32_t > payTotal
std::optional< std::uint32_t > payInterval
std::optional< TenthBips32 > interest
std::optional< Number > closeFee
jtx::JTx operator()(jtx::Env &env, BrokerInfo const &broker, FN const &... fN) const
std::optional< Number > serviceFee
std::optional< TenthBips32 > overpaymentInterest
bool counterpartyExplicit
std::optional< std::uint32_t > gracePd
std::optional< TenthBips32 > overFee
std::optional< TenthBips32 > closeInterest
std::optional< Number > originationFee
std::uint32_t previousPaymentDate
TenthBips32 const interestRate
NetClock::time_point startDate
std::uint32_t const paymentInterval
std::uint32_t paymentRemaining
std::int32_t const loanScale
Number principalOutstanding
std::uint32_t nextPaymentDate
Number managementFeeOutstanding
std::optional< Number > overpaymentExtra
static PaymentParameters const & defaults()
Helper class to compare the expected state of a loan and loan broker against the data in the ledger.
void checkPayment(std::int32_t loanScale, jtx::Account const &account, jtx::PrettyAmount const &balanceBefore, STAmount const &expectedPayment, jtx::PrettyAmount const &adjustment) const
void checkBroker(Number const &principalOutstanding, Number const &interestOwed, TenthBips32 interestRate, std::uint32_t paymentInterval, std::uint32_t paymentsRemaining, std::uint32_t ownerCount) const
Checks the expected broker state against the ledger.
BrokerInfo const & broker
jtx::Account const & pseudoAccount
Keylet const & loanKeylet
VerifyLoanStatus(jtx::Env const &env, BrokerInfo const &broker, jtx::Account const &pseudo, Keylet const &keylet)
void operator()(std::uint32_t previousPaymentDate, std::uint32_t nextPaymentDate, std::uint32_t paymentRemaining, Number const &loanScale, Number const &totalValue, Number const &principalOutstanding, Number const &managementFeeOutstanding, Number const &periodicPayment, std::uint32_t flags) const
Checks both the loan and broker expect states against the ledger.
void operator()(LoanState const &state) const
Checks both the loan and broker expect states against the ledger.
Execution context for applying a JSON transaction.
std::shared_ptr< STTx const > stx
Represents an XRP, IOU, or MPT quantity This customizes the string conversion and supports XRP conver...
STAmount const & value() const
Asset const & raw() const
Set the sequence number on a JTx.
static json::Value withdraw(WithdrawArgs const &args)
static json::Value deposit(DepositArgs const &args)
T time_since_epoch(T... args)