802 using namespace jtx::loan;
803 using namespace std::chrono_literals;
806 bool const showStepBalances = paymentParams.showStepBalances;
810 auto const baseFee = env.
current()->fees().base;
815 verifyLoanStatus(state);
829 auto const periodicRate =
loanPeriodicRate(state.interestRate, state.paymentInterval);
830 STAmount const roundedPeriodicPayment{
834 if (!showStepBalances)
836 log << currencyLabel <<
" Payment components: "
837 <<
"Payments remaining, "
838 <<
"rawInterest, rawPrincipal, "
840 <<
"trackedValueDelta, trackedPrincipalDelta, "
841 "trackedInterestDelta, trackedMgmtFeeDelta, special"
850 state.totalValue, state.principalOutstanding, state.managementFeeOutstanding);
853 state.periodicPayment,
855 state.paymentRemaining,
858 if (showStepBalances)
860 log << currencyLabel <<
" Starting loan balances: "
861 <<
"\n\tTotal value: " << currentRoundedState.valueOutstanding
862 <<
"\n\tPrincipal: " << currentRoundedState.principalOutstanding
863 <<
"\n\tInterest: " << currentRoundedState.interestDue
864 <<
"\n\tMgmt fee: " << currentRoundedState.managementFeeDue
865 <<
"\n\tPayments remaining " << state.paymentRemaining <<
std::endl;
869 log << currencyLabel <<
" Loan starting state: " << state.paymentRemaining <<
", "
870 << raw.interestDue <<
", " << raw.principalOutstanding <<
", "
871 << raw.managementFeeDue <<
", " << currentRoundedState.valueOutstanding <<
", "
872 << currentRoundedState.principalOutstanding <<
", "
873 << currentRoundedState.interestDue <<
", "
874 << currentRoundedState.managementFeeDue <<
std::endl;
880 auto const extraAmount = paymentParams.overpaymentExtra
881 ? broker.
asset(*paymentParams.overpaymentExtra).value()
885 STAmount{broker.
asset, totalDue * paymentParams.overpaymentFactor} + extraAmount;
888 auto const initialState = state;
890 .
trackedValueDelta = 0, .trackedPrincipalDelta = 0, .trackedManagementFeeDelta = 0};
891 Number totalInterestPaid = 0;
896 state.periodicPayment,
898 state.paymentRemaining,
901 auto validateBorrowerBalance = [&]() {
902 if (borrower == issuer || !paymentParams.validateBalances)
904 auto const totalSpent =
905 (totalPaid.trackedValueDelta + totalFeesPaid +
909 borrowerInitialBalance - totalSpent);
914 auto const p = places.value_or(defaultRound);
917 auto const factor =
Number{1, p};
918 return (n * factor).
truncate() / factor;
920 while (state.paymentRemaining > 0)
922 validateBorrowerBalance();
928 state.principalOutstanding,
929 state.managementFeeOutstanding,
930 state.periodicPayment,
932 state.paymentRemaining,
936 paymentComponents.trackedValueDelta <= roundedPeriodicPayment ||
938 paymentComponents.trackedValueDelta >= roundedPeriodicPayment));
940 paymentComponents.trackedValueDelta ==
941 paymentComponents.trackedPrincipalDelta + paymentComponents.trackedInterestPart() +
942 paymentComponents.trackedManagementFeeDelta);
945 state.periodicPayment,
947 state.paymentRemaining - 1,
954 deltas.
total() == state.periodicPayment ||
955 (state.loanScale - (deltas.
total() - state.periodicPayment).exponent()) > 14);
957 if (!showStepBalances)
959 log << currencyLabel <<
" Payment components: " << state.paymentRemaining <<
", "
962 <<
", " << paymentComponents.trackedValueDelta <<
", "
963 << paymentComponents.trackedPrincipalDelta <<
", "
964 << paymentComponents.trackedInterestPart() <<
", "
965 << paymentComponents.trackedManagementFeeDelta <<
", " << [&]() ->
char const* {
974 auto const totalDueAmount =
975 STAmount{broker.
asset, paymentComponents.trackedValueDelta + serviceFee};
977 if (paymentParams.validateBalances)
985 Number const diff = totalDue - totalDueAmount;
988 diff == beast::zero ||
989 (diff > beast::zero &&
991 (state.loanScale - diff.
exponent() > 13))));
994 paymentComponents.trackedPrincipalDelta >= beast::zero &&
995 paymentComponents.trackedPrincipalDelta <= state.principalOutstanding);
998 paymentComponents.trackedPrincipalDelta == state.principalOutstanding);
1001 auto const borrowerBalanceBeforePayment = env.
balance(borrower, broker.
asset);
1004 env(
pay(borrower, loanKeylet.
key, transactionAmount, paymentParams.flags));
1006 env.
close(d{state.paymentInterval / 2});
1008 if (paymentParams.validateBalances)
1014 adjustment = env.
current()->fees().base;
1021 borrowerBalanceBeforePayment,
1026 if (showStepBalances)
1028 auto const loanSle = env.
le(loanKeylet);
1029 if (!BEAST_EXPECT(loanSle))
1035 auto const errors = nextTrueState -
current;
1036 log << currencyLabel <<
" Loan balances: "
1037 <<
"\n\tAmount taken: " << paymentComponents.trackedValueDelta
1038 <<
"\n\tTotal value: " <<
current.valueOutstanding
1040 <<
", error: " << truncate(errors.total())
1041 <<
")\n\tPrincipal: " <<
current.principalOutstanding
1043 <<
", error: " << truncate(errors.principal)
1044 <<
")\n\tInterest: " <<
current.interestDue
1045 <<
" (true: " << truncate(nextTrueState.
interestDue)
1046 <<
", error: " << truncate(errors.interest)
1047 <<
")\n\tMgmt fee: " <<
current.managementFeeDue
1049 <<
", error: " << truncate(errors.managementFee) <<
")\n\tPayments remaining "
1050 << loanSle->at(sfPaymentRemaining) <<
std::endl;
1052 currentRoundedState =
current;
1055 --state.paymentRemaining;
1056 state.previousPaymentDate = state.nextPaymentDate;
1059 state.paymentRemaining = 0;
1060 state.nextPaymentDate = 0;
1064 state.nextPaymentDate += state.paymentInterval;
1066 state.principalOutstanding -= paymentComponents.trackedPrincipalDelta;
1067 state.managementFeeOutstanding -= paymentComponents.trackedManagementFeeDelta;
1068 state.totalValue -= paymentComponents.trackedValueDelta;
1070 if (paymentParams.validateBalances)
1071 verifyLoanStatus(state);
1073 totalPaid.trackedValueDelta += paymentComponents.trackedValueDelta;
1074 totalPaid.trackedPrincipalDelta += paymentComponents.trackedPrincipalDelta;
1075 totalPaid.trackedManagementFeeDelta += paymentComponents.trackedManagementFeeDelta;
1076 totalInterestPaid += paymentComponents.trackedInterestPart();
1077 totalFeesPaid += serviceFee;
1078 ++totalPaymentsMade;
1080 currentTrueState = nextTrueState;
1082 validateBorrowerBalance();
1085 BEAST_EXPECT(state.paymentRemaining == 0);
1086 BEAST_EXPECT(state.principalOutstanding == 0);
1088 auto const initialInterestDue = initialState.totalValue -
1089 (initialState.principalOutstanding + initialState.managementFeeOutstanding);
1090 if (paymentParams.validateBalances)
1093 BEAST_EXPECT(totalPaid.trackedValueDelta == initialState.totalValue);
1094 BEAST_EXPECT(totalPaid.trackedPrincipalDelta == initialState.principalOutstanding);
1096 totalPaid.trackedManagementFeeDelta == initialState.managementFeeOutstanding);
1099 BEAST_EXPECT(totalInterestPaid == initialInterestDue);
1100 BEAST_EXPECT(totalPaymentsMade == initialState.paymentRemaining);
1103 if (showStepBalances)
1105 auto const loanSle = env.
le(loanKeylet);
1106 if (!BEAST_EXPECT(loanSle))
1111 log << currencyLabel <<
" Total amounts paid: "
1112 <<
"\n\tTotal value: " << totalPaid.trackedValueDelta
1113 <<
" (initial: " << truncate(initialState.totalValue)
1114 <<
", error: " << truncate(initialState.totalValue - totalPaid.trackedValueDelta)
1115 <<
")\n\tPrincipal: " << totalPaid.trackedPrincipalDelta
1116 <<
" (initial: " << truncate(initialState.principalOutstanding) <<
", error: "
1117 << truncate(initialState.principalOutstanding - totalPaid.trackedPrincipalDelta)
1118 <<
")\n\tInterest: " << totalInterestPaid
1119 <<
" (initial: " << truncate(initialInterestDue)
1120 <<
", error: " << truncate(initialInterestDue - totalInterestPaid)
1121 <<
")\n\tMgmt fee: " << totalPaid.trackedManagementFeeDelta
1122 <<
" (initial: " << truncate(initialState.managementFeeOutstanding) <<
", error: "
1124 initialState.managementFeeOutstanding - totalPaid.trackedManagementFeeDelta)
1125 <<
")\n\tTotal payments made: " << totalPaymentsMade <<
std::endl;
1181 Number const& loanAmount,
1182 int interestExponent,
1194 auto const [keylet, loanSequence] = [&]() {
1196 if (!BEAST_EXPECT(brokerSle))
1203 BEAST_EXPECT(brokerSle->at(sfOwnerCount) == 0);
1207 auto const loanSequence = brokerSle->at(sfLoanSequence);
1216 if (!BEAST_EXPECT(loanSequence != 0))
1219 testcase << caseLabel <<
" " << label;
1221 using namespace jtx;
1222 using namespace loan;
1223 using namespace std::chrono_literals;
1225 auto applyExponent = [interestExponent,
this](
TenthBips32 value)
mutable {
1227 while (interestExponent > 0)
1229 auto const oldValue = value;
1232 BEAST_EXPECT(value / 10 == oldValue);
1234 while (interestExponent < 0)
1236 auto const oldValue = value;
1239 BEAST_EXPECT(value * 10 == oldValue);
1244 auto const borrowerOwnerCount = env.
ownerCount(borrower);
1246 auto const loanSetFee = env.
current()->fees().base * 2;
1250 .counterpartyExplicit =
false,
1251 .principalRequest = loanAmount,
1252 .setFee = loanSetFee,
1253 .originationFee = 1,
1270 auto const serviceFeeAmount = broker.
asset(*loanParams.
serviceFee).value();
1271 auto const lateFeeAmount = broker.
asset(*loanParams.
lateFee).value();
1272 auto const closeFeeAmount = broker.
asset(*loanParams.
closeFee).value();
1274 auto const borrowerStartbalance = env.
balance(borrower, broker.
asset);
1276 auto createJtx = loanParams(env, broker);
1282 auto const startDate = env.
current()->header().parentCloseTime.time_since_epoch().count();
1285 BEAST_EXPECT(brokerSle))
1287 BEAST_EXPECT(brokerSle->at(sfOwnerCount) == 1);
1295 adjustment = 2 * env.
current()->fees().base;
1300 borrowerStartbalance.
value() + principalRequestAmount - originationFeeAmount -
1301 adjustment.
value());
1304 auto const loanFlags =
1307 if (
auto loan = env.
le(keylet); BEAST_EXPECT(loan))
1312 loan->isFlag(lsfLoanOverpayment) == createJtx.stx->isFlag(tfLoanOverpayment));
1313 BEAST_EXPECT(loan->at(sfLoanSequence) == loanSequence);
1314 BEAST_EXPECT(loan->at(sfBorrower) == borrower.
id());
1315 BEAST_EXPECT(loan->at(sfLoanBrokerID) == broker.
brokerID);
1316 BEAST_EXPECT(loan->at(sfLoanOriginationFee) == originationFeeAmount);
1317 BEAST_EXPECT(loan->at(sfLoanServiceFee) == serviceFeeAmount);
1318 BEAST_EXPECT(loan->at(sfLatePaymentFee) == lateFeeAmount);
1319 BEAST_EXPECT(loan->at(sfClosePaymentFee) == closeFeeAmount);
1320 BEAST_EXPECT(loan->at(sfOverpaymentFee) == *loanParams.
overFee);
1321 BEAST_EXPECT(loan->at(sfInterestRate) == *loanParams.
interest);
1322 BEAST_EXPECT(loan->at(sfLateInterestRate) == *loanParams.
lateInterest);
1323 BEAST_EXPECT(loan->at(sfCloseInterestRate) == *loanParams.
closeInterest);
1325 BEAST_EXPECT(loan->at(sfStartDate) == startDate);
1326 BEAST_EXPECT(loan->at(sfPaymentInterval) == *loanParams.
payInterval);
1327 BEAST_EXPECT(loan->at(sfGracePeriod) == *loanParams.
gracePd);
1328 BEAST_EXPECT(loan->at(sfPreviousPaymentDueDate) == 0);
1329 BEAST_EXPECT(loan->at(sfNextPaymentDueDate) == startDate + *loanParams.
payInterval);
1330 BEAST_EXPECT(loan->at(sfPaymentRemaining) == *loanParams.
payTotal);
1332 loan->at(sfLoanScale) >=
1336 BEAST_EXPECT(loan->at(sfPrincipalOutstanding) == principalRequestAmount);
1343 state.principalOutstanding,
1345 state.paymentInterval,
1346 state.paymentRemaining,
1355 loanProperties.loanState.valueOutstanding,
1356 principalRequestAmount,
1357 loanProperties.loanState.managementFeeDue,
1358 loanProperties.periodicPayment,
1363 env(manage(lender, keylet.
key, 0));
1366 auto jt = manage(lender, keylet.
key, 0);
1367 jt.removeMember(sfFlags.getName());
1378 env(manage(lender, keylet.
key, tfLoanUnimpair | tfLoanImpair | tfLoanDefault),
1391 env(manage(lender, keylet.
key, tfLoanImpair),
1394 env(manage(lender, keylet.
key, tfLoanUnimpair),
1397 auto const nextDueDate = startDate + *loanParams.
payInterval;
1405 loanProperties.loanScale,
1406 loanProperties.loanState.valueOutstanding,
1407 principalRequestAmount,
1408 loanProperties.loanState.managementFeeDue,
1409 loanProperties.periodicPayment,
1415 if (BEAST_EXPECT(toEndOfLife))
1416 toEndOfLife(keylet, verifyLoanStatus);
1420 if (
auto loan = env.le(keylet); BEAST_EXPECT(loan))
1422 BEAST_EXPECT(loan->at(sfPaymentRemaining) == 0);
1423 BEAST_EXPECT(loan->at(sfPrincipalOutstanding) == 0);
1425 auto const borrowerStartingBalance = env.balance(borrower, broker.
asset);
1441 static unsigned deleteCounter = 0;
1442 auto const deleter = ((++deleteCounter % 2) != 0u) ? lender : borrower;
1443 env(del(deleter, keylet.
key));
1447 if (deleter == borrower)
1452 adjustment = env.current()->fees().base;
1460 env.balance(borrower, broker.
asset).value() ==
1461 borrowerStartingBalance.value() - adjustment);
1462 BEAST_EXPECT(env.ownerCount(borrower) == borrowerOwnerCount);
1465 BEAST_EXPECT(brokerSle))
1467 BEAST_EXPECT(brokerSle->at(sfOwnerCount) == 0);
1498 Number const& loanAmount,
1499 int interestExponent)
1501 using namespace jtx;
1502 using namespace Lending;
1504 auto const& asset = broker.
asset.
raw();
1506 auto const caseLabel = [&]() {
1508 ss <<
"Lifecycle: " << loanAmount <<
" " << currencyLabel
1509 <<
" Scale interest to: " << interestExponent <<
" ";
1514 using namespace loan;
1515 using namespace std::chrono_literals;
1519 Account const issuer{
"issuer"};
1522 Account const lender{
"lender"};
1524 Account const borrower{
"borrower"};
1530 Number const principalRequest = broker.
asset(loanAmount).value();
1532 BEAST_EXPECT(maxCoveredLoanValue == 1000 * 100 / 10);
1533 Number const maxCoveredLoanRequest = broker.
asset(maxCoveredLoanValue).value();
1537 auto const loanSetFee =
fee(env.
current()->fees().base * 2);
1539 auto const pseudoAcct = [&]() {
1541 if (!BEAST_EXPECT(brokerSle))
1543 auto const brokerPseudo = brokerSle->at(sfAccount);
1544 return Account(
"Broker pseudo-account", brokerPseudo);
1547 auto const baseFee = env.
current()->fees().base;
1552 env(
set(evan, broker.
brokerID, principalRequest, tfLoanSetMask),
1553 sig(sfCounterpartySignature, lender),
1560 sig(sfCounterpartySignature, borrower),
1566 sig(sfCounterpartySignature, lender),
1574 sig(sfCounterpartySignature, borrower),
1575 overpaymentFee(maxOverpaymentFee),
1580 sig(sfCounterpartySignature, lender),
1581 overpaymentFee(maxOverpaymentFee + 1),
1587 sig(sfCounterpartySignature, borrower),
1588 interestRate(maxInterestRate),
1592 sig(sfCounterpartySignature, borrower),
1598 sig(sfCounterpartySignature, lender),
1599 interestRate(maxInterestRate + 1),
1604 sig(sfCounterpartySignature, lender),
1611 sig(sfCounterpartySignature, borrower),
1612 lateInterestRate(maxLateInterestRate),
1616 sig(sfCounterpartySignature, borrower),
1622 sig(sfCounterpartySignature, lender),
1623 lateInterestRate(maxLateInterestRate + 1),
1628 sig(sfCounterpartySignature, lender),
1635 sig(sfCounterpartySignature, borrower),
1636 closeInterestRate(maxCloseInterestRate),
1640 sig(sfCounterpartySignature, borrower),
1646 sig(sfCounterpartySignature, lender),
1647 closeInterestRate(maxCloseInterestRate + 1),
1651 sig(sfCounterpartySignature, lender),
1658 sig(sfCounterpartySignature, borrower),
1659 overpaymentInterestRate(maxOverpaymentInterestRate),
1663 sig(sfCounterpartySignature, borrower),
1669 sig(sfCounterpartySignature, lender),
1670 overpaymentInterestRate(maxOverpaymentInterestRate + 1),
1674 sig(sfCounterpartySignature, lender),
1681 sig(sfCounterpartySignature, borrower),
1687 sig(sfCounterpartySignature, lender),
1694 sig(sfCounterpartySignature, borrower),
1700 sig(sfCounterpartySignature, lender),
1707 sig(sfCounterpartySignature, borrower),
1714 sig(sfCounterpartySignature, lender),
1721 env(
set(borrower, broker.
brokerID, principalRequest),
1722 sig(sfCounterpartySignature, lender),
1725 env(
signers(lender, 2, {{evan, 1}, {borrower, 1}}));
1726 env(
signers(borrower, 2, {{evan, 1}, {lender, 1}}));
1727 env(
set(borrower, broker.
brokerID, principalRequest),
1728 counterparty(lender),
1730 msig(sfCounterpartySignature, evan, borrower),
1734 env(
set(borrower, broker.
brokerID, principalRequest),
1735 counterparty(lender),
1736 msig(alice, issuer),
1737 msig(sfCounterpartySignature, evan, borrower),
1741 env(
set(borrower, broker.
brokerID, principalRequest),
1742 counterparty(lender),
1744 msig(sfCounterpartySignature, alice, issuer),
1750 env(
set(borrower, broker.
brokerID, principalRequest),
1751 counterparty(lender),
1753 msig(sfCounterpartySignature, evan, borrower),
1758 env(
set(borrower, broker.
brokerID, principalRequest),
1759 sig(sfCounterpartySignature, evan),
1764 counterparty(borrower),
1765 sig(sfCounterpartySignature, borrower),
1769 env(
set(lender, badKeylet.key, principalRequest),
1770 sig(sfCounterpartySignature, evan),
1774 env(
set(lender, badKeylet.key, principalRequest),
1775 counterparty(borrower),
1776 sig(sfCounterpartySignature, borrower),
1780 env(
set(lender, broker.
brokerID, principalRequest),
1781 counterparty(alice),
1782 sig(sfCounterpartySignature, alice),
1787 env(
set(evan, broker.
brokerID, totalVaultRequest + 1),
1788 sig(sfCounterpartySignature, lender),
1794 env(
set(evan, broker.
brokerID, maxCoveredLoanRequest + 1),
1795 sig(sfCounterpartySignature, lender),
1804 if (!BEAST_EXPECT(brokerSle))
1807 auto const vaultPseudo = [&]() {
1808 auto const vaultSle = env.
le(
keylet::vault(brokerSle->at(sfVaultID)));
1809 if (!BEAST_EXPECT(vaultSle))
1814 auto vaultPseudo =
Account(
"Vault pseudo-account", vaultSle->at(sfAccount));
1818 auto const [
freeze, deepfreeze, unfreeze, expectedResult] =
1836 auto deepfreeze = [&](
Account const& holder) {
1839 auto unfreeze = [&](
Account const& holder) {
1841 issuer, holder[
iouCurrency](0), tfClearFreeze | tfClearDeepFreeze));
1847 mptt.
set({.account = issuer, .holder = holder, .flags = tfMPTLock});
1849 auto unfreeze = [&](
Account const& holder) {
1850 mptt.
set({.account = issuer, .holder = holder, .flags = tfMPTUnlock});
1858 for (
auto const& account : {vaultPseudo, evan})
1864 env(
set(evan, broker.
brokerID, debtMaximumRequest),
1865 sig(sfCounterpartySignature, lender),
1867 ter(expectedResult));
1870 BEAST_EXPECT(unfreeze);
1876 env(
set(evan, broker.
brokerID, debtMaximumRequest + 1),
1877 sig(sfCounterpartySignature, lender),
1893 for (
auto const& account : {
1901 deepfreeze(account);
1904 env(
set(evan, broker.
brokerID, debtMaximumRequest),
1905 sig(sfCounterpartySignature, lender),
1907 ter(expectedResult));
1910 BEAST_EXPECT(unfreeze);
1916 env(
set(evan, broker.
brokerID, debtMaximumRequest + 1),
1917 sig(sfCounterpartySignature, lender),
1926 auto coverAvailable = [&env,
this](
uint256 const& brokerID,
Number const& expected) {
1928 BEAST_EXPECT(brokerSle))
1930 auto const available = brokerSle->at(sfCoverAvailable);
1938 BEAST_EXPECT(brokerSle))
1945 broker.
vaultScale(env), state.principalOutstanding.exponent())));
1954 state.totalValue - state.managementFeeOutstanding),
1960 auto replenishCover = [&env, &coverAvailable](
1963 Number const& startingCoverAvailable,
1964 Number const& amountToBeCovered) {
1965 coverAvailable(broker.
brokerID, startingCoverAvailable - amountToBeCovered);
1968 coverAvailable(broker.
brokerID, startingCoverAvailable);
1972 auto defaultImmediately = [&](
std::uint32_t baseFlag,
bool impair =
true) {
1973 return [&, impair, baseFlag](
1980 auto state =
getCurrentState(env, broker, loanKeylet, verifyLoanStatus);
1981 BEAST_EXPECT(state.flags == baseFlag);
1983 auto const& broker = verifyLoanStatus.
broker;
1984 auto const startingCoverAvailable = coverAvailable(
1992 env(manage(lender, loanKeylet.key, tfLoanImpair),
1997 state.flags |= tfLoanImpair;
2003 verifyLoanStatus(state);
2006 auto const nextDueDate = tp{d{state.nextPaymentDate}};
2010 env(manage(lender, loanKeylet.key, tfLoanDefault),
ter(
tecTOO_SOON));
2014 env.
close(nextDueDate + 60s);
2016 auto const [amountToBeCovered, brokerAcct] = getDefaultInfo(state, broker);
2019 env(manage(lender, loanKeylet.key, tfLoanDefault));
2024 replenishCover(broker, brokerAcct, startingCoverAvailable, amountToBeCovered);
2026 state.flags |= tfLoanDefault;
2027 state.paymentRemaining = 0;
2028 state.totalValue = 0;
2029 state.principalOutstanding = 0;
2030 state.managementFeeOutstanding = 0;
2031 state.nextPaymentDate = 0;
2032 verifyLoanStatus(state);
2042 auto singlePayment = [&](
Keylet const& loanKeylet,
2051 verifyLoanStatus(state);
2064 if (!(state.flags & lsfLoanOverpayment))
2071 STAmount{broker.asset, state.periodicPayment * Number{15, -1}},
2080 STAmount{broker.asset, state.periodicPayment * Number{15, -1}},
2083 baseFee * (
Number{15, -1} / loanPaymentsPerFeeIncrement + 1)}),
2093 broker.
asset(state.periodicPayment * 2),
2094 tfLoanLatePayment | tfLoanFullPayment),
2098 broker.
asset(state.periodicPayment * 2),
2099 tfLoanLatePayment | tfLoanOverpayment),
2103 broker.
asset(state.periodicPayment * 2),
2104 tfLoanOverpayment | tfLoanFullPayment),
2108 broker.
asset(state.periodicPayment * 2),
2109 tfLoanLatePayment | tfLoanOverpayment | tfLoanFullPayment),
2113 auto const otherAsset =
2114 broker.
asset.
raw() == assets[0].raw() ? assets[1] : assets[0];
2119 env(
pay(borrower, loanKeylet.
key,
STAmount{broker.asset, 1}, txFlags),
2124 auto const borrowerBalanceBeforePayment = env.
balance(borrower, broker.
asset);
2126 BEAST_EXPECT(payoffAmount > state.principalOutstanding);
2129 auto const transactionAmount = payoffAmount + broker.
asset(10);
2135 (borrowerBalanceBeforePayment.number() * 2 / state.periodicPayment /
2136 loanPaymentsPerFeeIncrement +
2140 STAmount{broker.asset, borrowerBalanceBeforePayment.number() * 2},
2145 XRPAmount
const goodFee{baseFee * (numPayments / loanPaymentsPerFeeIncrement + 1)};
2146 env(
pay(borrower, loanKeylet.
key, transactionAmount, txFlags), fee(goodFee));
2153 PrettyAmount adjustment = broker.
asset(0);
2156 adjustment = badFee + goodFee;
2159 state.paymentRemaining = 0;
2160 state.principalOutstanding = 0;
2161 state.totalValue = 0;
2162 state.managementFeeOutstanding = 0;
2163 state.previousPaymentDate =
2164 state.nextPaymentDate + (state.paymentInterval * (numPayments - 1));
2165 state.nextPaymentDate = 0;
2166 verifyLoanStatus(state);
2169 state.loanScale, borrower, borrowerBalanceBeforePayment, payoffAmount, adjustment);
2177 return [&, baseFlag](
2178 Keylet
const& loanKeylet, VerifyLoanStatus
const& verifyLoanStatus) {
2181 auto state = getCurrentState(env, broker, loanKeylet, verifyLoanStatus);
2182 env.
close(state.startDate + 20s);
2183 auto const loanAge = (env.
now() - state.startDate).
count();
2184 BEAST_EXPECT(loanAge == 30);
2194 Number
const interval = state.paymentInterval;
2195 auto const periodicRate = interval * Number(12, -2) /
secondsInYear;
2197 periodicRate == Number(2283105022831050228ULL, -24, Number::normalized{}));
2198 STAmount
const principalOutstanding{broker.
asset, state.principalOutstanding};
2199 STAmount
const accruedInterest{
2200 broker.
asset, state.principalOutstanding * periodicRate * loanAge / interval};
2201 BEAST_EXPECT(accruedInterest == broker.
asset(Number(1141552511415525, -19)));
2202 STAmount
const prepaymentPenalty{
2203 broker.
asset, state.principalOutstanding * Number(36, -3)};
2204 BEAST_EXPECT(prepaymentPenalty == broker.
asset(36));
2207 principalOutstanding + accruedInterest + prepaymentPenalty + closePaymentFee,
2213 broker.
asset(Number(1040000114155251, -12)).
number(),
2220 state.paymentRemaining * (state.periodicPayment + broker.
asset(2).value()));
2235 [&, baseFlag](Keylet
const& loanKeylet, VerifyLoanStatus
const& verifyLoanStatus) {
2239 auto state = getCurrentState(env, broker, loanKeylet, verifyLoanStatus);
2243 STAmount(broker.
asset, state.periodicPayment) ==
2244 broker.
asset(Number(8333457002039338267, -17)));
2248 auto const startingPayments = state.paymentRemaining;
2249 STAmount
const payoffAmount = [&]() {
2251 auto const rawPayoff =
2252 startingPayments * (state.periodicPayment + broker.
asset(2).value());
2253 STAmount payoffAmount{broker.
asset, rawPayoff};
2255 payoffAmount == broker.
asset(Number(1024014840244721, -12)),
2257 BEAST_EXPECT(payoffAmount > state.principalOutstanding);
2259 payoffAmount =
roundToScale(payoffAmount, state.loanScale);
2261 return payoffAmount;
2264 auto const totalPayoffValue =
2265 state.totalValue + startingPayments * broker.
asset(2).value();
2266 STAmount
const totalPayoffAmount{broker.
asset, totalPayoffValue};
2269 totalPayoffAmount == payoffAmount,
2270 "Payoff amount: " +
to_string(payoffAmount) +
2271 ". Total Value: " +
to_string(totalPayoffAmount));
2278 state.paymentRemaining,
2290 "Loan overpayment allowed - Impair and Default",
2300 defaultImmediately(lsfLoanOverpayment));
2304 "Loan overpayment prohibited - Impair and Default",
2314 defaultImmediately(0));
2318 "Loan overpayment allowed - Default without Impair",
2328 defaultImmediately(lsfLoanOverpayment,
false));
2332 "Loan overpayment prohibited - Default without Impair",
2342 defaultImmediately(0,
false));
2346 "Loan overpayment prohibited - Pay off immediately",
2360 "Loan overpayment allowed - Pay off immediately",
2370 fullPayment(lsfLoanOverpayment));
2374 "Loan overpayment prohibited - Combine all payments",
2384 combineAllPayments(0));
2388 "Loan overpayment allowed - Combine all payments",
2398 combineAllPayments(lsfLoanOverpayment));
2402 "Loan overpayment prohibited - Make payments",
2412 [&](Keylet
const& loanKeylet, VerifyLoanStatus
const& verifyLoanStatus) {
2416 auto state = getCurrentState(env, broker, loanKeylet, verifyLoanStatus);
2417 BEAST_EXPECT(state.flags == 0);
2420 verifyLoanStatus(state);
2422 env.
close(state.startDate + 20s);
2423 auto const loanAge = (env.
now() - state.startDate).
count();
2424 BEAST_EXPECT(loanAge == 30);
2434 Number
const interval = state.paymentInterval;
2435 auto const periodicRate = interval * Number(12, -2) /
secondsInYear;
2437 periodicRate == Number(2283105022831050228, -24, Number::normalized{}));
2438 STAmount
const roundedPeriodicPayment{
2442 testcase << currencyLabel <<
" Payment components: "
2443 <<
"Payments remaining, rawInterest, rawPrincipal, "
2444 "rawMFee, trackedValueDelta, trackedPrincipalDelta, "
2445 "trackedInterestDelta, trackedMgmtFeeDelta, special";
2447 auto const serviceFee = broker.
asset(2);
2450 roundedPeriodicPayment ==
2458 roundedPeriodicPayment + serviceFee, state.loanScale,
Number::upward);
2470 state.periodicPayment,
2472 state.paymentRemaining,
2476 state.principalOutstanding,
2477 state.managementFeeOutstanding);
2478 testcase << currencyLabel <<
" Loan starting state: " << state.paymentRemaining
2479 <<
", " << raw.interestDue <<
", " << raw.principalOutstanding <<
", "
2480 << raw.managementFeeDue <<
", " << rounded.valueOutstanding <<
", "
2481 << rounded.principalOutstanding <<
", " << rounded.interestDue <<
", "
2482 << rounded.managementFeeDue;
2487 STAmount
const transactionAmount =
2488 STAmount{broker.
asset, totalDue} + broker.
asset(10);
2492 transactionAmount ==
2498 auto const initialState = state;
2499 detail::PaymentComponents totalPaid{
2500 .trackedValueDelta = 0,
2501 .trackedPrincipalDelta = 0,
2502 .trackedManagementFeeDelta = 0};
2503 Number totalInterestPaid = 0;
2507 state.periodicPayment,
2509 state.paymentRemaining,
2512 while (state.paymentRemaining > 0)
2519 state.principalOutstanding,
2520 state.managementFeeOutstanding,
2521 state.periodicPayment,
2523 state.paymentRemaining,
2528 paymentComponents.trackedValueDelta <= roundedPeriodicPayment,
2529 "Delta: " +
to_string(paymentComponents.trackedValueDelta) +
2530 ", periodic payment: " +
to_string(roundedPeriodicPayment));
2533 state.periodicPayment,
2535 state.paymentRemaining - 1,
2537 detail::LoanStateDeltas
const deltas = currentTrueState - nextTrueState;
2539 testcase << currencyLabel <<
" Payment components: " << state.paymentRemaining
2540 <<
", " << deltas.interest <<
", " << deltas.principal <<
", "
2541 << deltas.managementFee <<
", " << paymentComponents.trackedValueDelta
2542 <<
", " << paymentComponents.trackedPrincipalDelta <<
", "
2543 << paymentComponents.trackedInterestPart() <<
", "
2544 << paymentComponents.trackedManagementFeeDelta <<
", "
2545 << [&]() ->
char const* {
2553 auto const totalDueAmount = STAmount{
2554 broker.
asset, paymentComponents.trackedValueDelta + serviceFee.number()};
2562 Number
const diff = totalDue - totalDueAmount;
2565 diff == beast::zero ||
2566 (diff > beast::zero &&
2567 ((broker.
asset.
integral() && (
static_cast<Number
>(diff) < 3)) ||
2568 (state.loanScale - diff.exponent() > 13))));
2571 paymentComponents.trackedValueDelta ==
2572 paymentComponents.trackedPrincipalDelta +
2573 paymentComponents.trackedInterestPart() +
2574 paymentComponents.trackedManagementFeeDelta);
2577 paymentComponents.trackedValueDelta <= roundedPeriodicPayment);
2580 state.paymentRemaining < 12 ||
2588 paymentComponents.trackedPrincipalDelta >= beast::zero &&
2589 paymentComponents.trackedPrincipalDelta <= state.principalOutstanding);
2592 paymentComponents.trackedPrincipalDelta == state.principalOutstanding);
2595 (state.periodicPayment.exponent() -
2596 (deltas.principal + deltas.interest + deltas.managementFee -
2597 state.periodicPayment)
2600 auto const borrowerBalanceBeforePayment = env.
balance(borrower, broker.
asset);
2602 if (canImpairLoan(env, broker, state))
2605 env(
manage(lender, loanKeylet.key, tfLoanImpair));
2611 env(
pay(borrower, loanKeylet.key, transactionAmount));
2616 PrettyAmount adjustment = broker.
asset(0);
2619 adjustment = env.
current()->fees().base;
2623 verifyLoanStatus.checkPayment(
2626 borrowerBalanceBeforePayment,
2630 --state.paymentRemaining;
2631 state.previousPaymentDate = state.nextPaymentDate;
2634 state.paymentRemaining = 0;
2635 state.nextPaymentDate = 0;
2639 state.nextPaymentDate += state.paymentInterval;
2641 state.principalOutstanding -= paymentComponents.trackedPrincipalDelta;
2642 state.managementFeeOutstanding -= paymentComponents.trackedManagementFeeDelta;
2643 state.totalValue -= paymentComponents.trackedValueDelta;
2645 verifyLoanStatus(state);
2647 totalPaid.trackedValueDelta += paymentComponents.trackedValueDelta;
2648 totalPaid.trackedPrincipalDelta += paymentComponents.trackedPrincipalDelta;
2649 totalPaid.trackedManagementFeeDelta +=
2650 paymentComponents.trackedManagementFeeDelta;
2651 totalInterestPaid += paymentComponents.trackedInterestPart();
2652 ++totalPaymentsMade;
2654 currentTrueState = nextTrueState;
2658 BEAST_EXPECT(state.paymentRemaining == 0);
2659 BEAST_EXPECT(state.principalOutstanding == 0);
2662 BEAST_EXPECT(totalPaid.trackedValueDelta == initialState.totalValue);
2663 BEAST_EXPECT(totalPaid.trackedPrincipalDelta == initialState.principalOutstanding);
2665 totalPaid.trackedManagementFeeDelta == initialState.managementFeeOutstanding);
2669 totalInterestPaid ==
2670 initialState.totalValue -
2671 (initialState.principalOutstanding +
2672 initialState.managementFeeOutstanding));
2673 BEAST_EXPECT(totalPaymentsMade == initialState.paymentRemaining);
2692 if (!BEAST_EXPECT(timed))
2698 auto const start = clock_type::now();
2700 auto const duration =
2701 std::chrono::duration_cast<duration_type>(clock_type::now() - start);
2703 log << label <<
" took " << duration.count() <<
"ms" <<
std::endl;
2720 [&](Keylet
const& loanKeylet, VerifyLoanStatus
const& verifyLoanStatus) {
2723 using namespace loan;
2725 auto const state = getCurrentState(env, broker, verifyLoanStatus.keylet);
2726 auto const serviceFee = broker.
asset(2).value();
2728 STAmount
const totalDue{
2731 broker.
asset, state.periodicPayment + serviceFee, state.loanScale)};
2734 time(
"single payment", [&]() { env(
pay(borrower, loanKeylet.key, totalDue)); });
2738 auto const numPayments = (state.paymentRemaining - 2);
2739 STAmount
const bigPayment{broker.
asset, totalDue * numPayments};
2740 XRPAmount
const bigFee{baseFee * (numPayments / loanPaymentsPerFeeIncrement + 1)};
2741 time(
"ten payments", [&]() {
2742 env(
pay(borrower, loanKeylet.key, bigPayment), fee(bigFee));
2746 time(
"final payment", [&]() {
2748 env(
pay(borrower, loanKeylet.key, totalDue + STAmount{broker.asset, 1}));
2755 "Loan overpayment allowed - Explicit overpayment",
2765 [&](Keylet
const& loanKeylet, VerifyLoanStatus
const& verifyLoanStatus) {
throw 0; });
2769 "Loan overpayment prohibited - Late payment",
2779 [&](Keylet
const& loanKeylet, VerifyLoanStatus
const& verifyLoanStatus) {
throw 0; });
2783 "Loan overpayment allowed - Late payment",
2793 [&](Keylet
const& loanKeylet, VerifyLoanStatus
const& verifyLoanStatus) {
throw 0; });
2797 "Loan overpayment allowed - Late payment and overpayment",
2807 [&](Keylet
const& loanKeylet, VerifyLoanStatus
const& verifyLoanStatus) {
throw 0; });
2815 using namespace jtx;
2817 Account const issuer{
"issuer"};
2818 Account const lender{
"lender"};
2819 Account const borrower{
"borrower"};
2824 bool authorizeBorrower =
false;
2825 int initialXRP = 1'000'000;
2828 auto const testCase = [&,
this](
2831 CaseArgs args = {}) {
2832 Env env(*
this, all);
2833 env.
fund(
XRP(args.initialXRP), issuer, lender, borrower);
2835 if (args.requireAuth)
2837 env(
fset(issuer, asfRequireAuth));
2843 MPTTester mptt{env, issuer, mptInitNoFund};
2847 {.flags = tfMPTCanTransfer | tfMPTCanLock |
2848 (args.requireAuth ? tfMPTRequireAuth : none)});
2851 mptt.authorize({.account = lender});
2852 mptt.authorize({.account = borrower});
2854 if (args.requireAuth)
2856 mptt.authorize({.account = issuer, .holder = lender});
2857 if (args.authorizeBorrower)
2858 mptt.authorize({.account = issuer, .holder = borrower});
2862 env(pay(issuer, lender, mptAsset(10'000'000)));
2867 env(trust(lender, iouAsset(10'000'000)));
2868 env(trust(borrower, iouAsset(10'000'000)));
2870 if (args.requireAuth)
2872 env(trust(issuer, iouAsset(0), lender, tfSetfAuth));
2873 env(pay(issuer, lender, iouAsset(10'000'000)));
2874 if (args.authorizeBorrower)
2876 env(trust(issuer, iouAsset(0), borrower, tfSetfAuth));
2877 env(pay(issuer, borrower, iouAsset(10'000)));
2882 env(pay(issuer, lender, iouAsset(10'000'000)));
2883 env(pay(issuer, borrower, iouAsset(10'000)));
2890 brokers.
reserve(assets.size());
2891 for (
auto const& asset : assets)
2893 brokers.
emplace_back(createVaultAndBroker(env, asset, lender));
2897 (mptTest)(env, brokers[0], mptt);
2899 (iouTest)(env, brokers[1]);
2904 using namespace loan;
2905 Number const principalRequest = broker.
asset(1'000).value();
2907 testcase(
"MPT issuer is borrower, issuer submits");
2908 env(
set(issuer, broker.
brokerID, principalRequest),
2909 counterparty(lender),
2910 sig(sfCounterpartySignature, lender),
2913 testcase(
"MPT issuer is borrower, lender submits");
2914 env(
set(lender, broker.
brokerID, principalRequest),
2915 counterparty(issuer),
2916 sig(sfCounterpartySignature, issuer),
2920 using namespace loan;
2921 Number const principalRequest = broker.
asset(1'000).value();
2923 testcase(
"IOU issuer is borrower, issuer submits");
2924 env(
set(issuer, broker.
brokerID, principalRequest),
2925 counterparty(lender),
2926 sig(sfCounterpartySignature, lender),
2929 testcase(
"IOU issuer is borrower, lender submits");
2930 env(
set(lender, broker.
brokerID, principalRequest),
2931 counterparty(issuer),
2932 sig(sfCounterpartySignature, issuer),
2935 CaseArgs{.requireAuth =
true});
2939 using namespace loan;
2940 Number const principalRequest = broker.
asset(1'000).value();
2942 testcase(
"MPT unauthorized borrower, borrower submits");
2943 env(
set(borrower, broker.
brokerID, principalRequest),
2944 counterparty(lender),
2945 sig(sfCounterpartySignature, lender),
2949 testcase(
"MPT unauthorized borrower, lender submits");
2950 env(
set(lender, broker.
brokerID, principalRequest),
2951 counterparty(borrower),
2952 sig(sfCounterpartySignature, borrower),
2957 using namespace loan;
2958 Number const principalRequest = broker.
asset(1'000).value();
2960 testcase(
"IOU unauthorized borrower, borrower submits");
2961 env(
set(borrower, broker.
brokerID, principalRequest),
2962 counterparty(lender),
2963 sig(sfCounterpartySignature, lender),
2967 testcase(
"IOU unauthorized borrower, lender submits");
2968 env(
set(lender, broker.
brokerID, principalRequest),
2969 counterparty(borrower),
2970 sig(sfCounterpartySignature, borrower),
2974 CaseArgs{.requireAuth =
true});
2985 using namespace loan;
2986 Number const principalRequest = broker.
asset(1'000).value();
2989 "MPT authorized borrower, borrower submits, borrower has "
2991 mptt.
authorize({.account = borrower, .flags = tfMPTUnauthorize});
2995 auto const sleMPT1 = env.
le(mptoken);
2996 BEAST_EXPECT(sleMPT1 ==
nullptr);
2999 env(
noop(borrower),
fee(
XRP((acctReserve * 2) + (incReserve * 2))));
3003 env(
set(borrower, broker.
brokerID, principalRequest),
3004 counterparty(lender),
3005 sig(sfCounterpartySignature, lender),
3007 ter{tecINSUFFICIENT_RESERVE});
3011 env(pay(issuer, borrower,
XRP(incReserve)));
3013 env(
set(borrower, broker.
brokerID, principalRequest),
3014 counterparty(lender),
3015 sig(sfCounterpartySignature, lender),
3019 auto const sleMPT2 = env.
le(mptoken);
3020 BEAST_EXPECT(sleMPT2 !=
nullptr);
3023 CaseArgs{.initialXRP = (acctReserve * 2) + (incReserve * 8) + 1});
3028 using namespace loan;
3029 Number const principalRequest = broker.
asset(1'000).value();
3032 "IOU authorized borrower, borrower submits, borrower has "
3038 env(pay(borrower, issuer, broker.
asset(10'000)));
3041 auto const sleLine1 = env.
le(trustline);
3042 BEAST_EXPECT(sleLine1 ==
nullptr);
3045 env(
noop(borrower),
fee(
XRP((acctReserve * 2) + (incReserve * 2))));
3049 env(
set(borrower, broker.
brokerID, principalRequest),
3050 counterparty(lender),
3051 sig(sfCounterpartySignature, lender),
3053 ter{tecNO_LINE_INSUF_RESERVE});
3057 env(pay(issuer, borrower,
XRP(incReserve)));
3059 env(
set(borrower, broker.
brokerID, principalRequest),
3060 counterparty(lender),
3061 sig(sfCounterpartySignature, lender),
3065 auto const sleLine2 = env.
le(trustline);
3066 BEAST_EXPECT(sleLine2 !=
nullptr);
3068 CaseArgs{.initialXRP = (acctReserve * 2) + (incReserve * 8) + 1});
3072 using namespace loan;
3073 Number const principalRequest = broker.
asset(1'000).value();
3076 "MPT authorized borrower, borrower submits, lender has "
3079 auto const sleMPT1 = env.
le(mptoken);
3080 BEAST_EXPECT(sleMPT1 !=
nullptr);
3082 env(pay(lender, issuer, broker.
asset(sleMPT1->at(sfMPTAmount))));
3085 mptt.
authorize({.account = lender, .flags = tfMPTUnauthorize});
3088 auto const sleMPT2 = env.
le(mptoken);
3089 BEAST_EXPECT(sleMPT2 ==
nullptr);
3096 env(
set(borrower, broker.
brokerID, principalRequest),
3097 loanOriginationFee(broker.
asset(1).value()),
3098 counterparty(lender),
3099 sig(sfCounterpartySignature, lender),
3101 ter{tecINSUFFICIENT_RESERVE});
3105 env(pay(issuer, lender,
XRP(incReserve)));
3107 env(
set(borrower, broker.
brokerID, principalRequest),
3108 loanOriginationFee(broker.
asset(1).value()),
3109 counterparty(lender),
3110 sig(sfCounterpartySignature, lender),
3114 auto const sleMPT3 = env.
le(mptoken);
3115 BEAST_EXPECT(sleMPT3 !=
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, lender has no "
3134 auto const sleLine1 = env.
le(trustline);
3135 BEAST_EXPECT(sleLine1 !=
nullptr);
3137 env(pay(lender, issuer, broker.
asset(
abs(sleLine1->at(sfBalance).value()))));
3139 auto const sleLine2 = env.
le(trustline);
3140 BEAST_EXPECT(sleLine2 ==
nullptr);
3147 env(
set(borrower, broker.
brokerID, principalRequest),
3148 loanOriginationFee(broker.
asset(1).value()),
3149 counterparty(lender),
3150 sig(sfCounterpartySignature, lender),
3152 ter{tecNO_LINE_INSUF_RESERVE});
3156 env(pay(issuer, lender,
XRP(incReserve)));
3158 env(
set(borrower, broker.
brokerID, principalRequest),
3159 loanOriginationFee(broker.
asset(1).value()),
3160 counterparty(lender),
3161 sig(sfCounterpartySignature, lender),
3165 auto const sleLine3 = env.
le(trustline);
3166 BEAST_EXPECT(sleLine3 !=
nullptr);
3168 CaseArgs{.initialXRP = (acctReserve * 2) + (incReserve * 8) + 1});
3172 using namespace loan;
3173 Number const principalRequest = broker.
asset(1'000).value();
3175 testcase(
"MPT authorized borrower, unauthorized lender");
3177 auto const sleMPT1 = env.
le(mptoken);
3178 BEAST_EXPECT(sleMPT1 !=
nullptr);
3180 env(pay(lender, issuer, broker.
asset(sleMPT1->at(sfMPTAmount))));
3183 mptt.
authorize({.account = lender, .flags = tfMPTUnauthorize});
3186 auto const sleMPT2 = env.
le(mptoken);
3187 BEAST_EXPECT(sleMPT2 ==
nullptr);
3190 env(
set(borrower, broker.
brokerID, principalRequest),
3191 loanOriginationFee(broker.
asset(1).value()),
3192 counterparty(lender),
3193 sig(sfCounterpartySignature, lender),
3199 env(
set(borrower, broker.
brokerID, principalRequest),
3200 counterparty(lender),
3201 sig(sfCounterpartySignature, lender),
3207 auto const sleMPT3 = env.
le(mptoken);
3208 BEAST_EXPECT(sleMPT3 ==
nullptr);
3211 CaseArgs{.requireAuth =
true, .authorizeBorrower =
true});
3215 using namespace loan;
3216 Number const principalRequest = broker.
asset(1'000).value();
3218 testcase(
"MPT authorized borrower, borrower submits");
3219 env(
set(borrower, broker.
brokerID, principalRequest),
3220 counterparty(lender),
3221 sig(sfCounterpartySignature, lender),
3225 using namespace loan;
3226 Number const principalRequest = broker.
asset(1'000).value();
3228 testcase(
"IOU authorized borrower, borrower submits");
3229 env(
set(borrower, broker.
brokerID, principalRequest),
3230 counterparty(lender),
3231 sig(sfCounterpartySignature, lender),
3234 CaseArgs{.requireAuth =
true, .authorizeBorrower =
true});
3238 using namespace loan;
3239 Number const principalRequest = broker.
asset(1'000).value();
3241 testcase(
"MPT authorized borrower, lender submits");
3242 env(
set(lender, broker.
brokerID, principalRequest),
3243 counterparty(borrower),
3244 sig(sfCounterpartySignature, borrower),
3248 using namespace loan;
3249 Number const principalRequest = broker.
asset(1'000).value();
3251 testcase(
"IOU authorized borrower, lender submits");
3252 env(
set(lender, broker.
brokerID, principalRequest),
3253 counterparty(borrower),
3254 sig(sfCounterpartySignature, borrower),
3257 CaseArgs{.requireAuth =
true, .authorizeBorrower =
true});
3261 auto const msigSetup = [&](
Env& env,
Account const& account) {
3262 Json::Value const tx1 = signers(account, 2, {{alice, 1}, {bella, 1}});
3269 using namespace loan;
3270 msigSetup(env, lender);
3271 Number const principalRequest = broker.
asset(1'000).value();
3274 "MPT authorized borrower, borrower submits, lender "
3276 env(
set(borrower, broker.
brokerID, principalRequest),
3277 counterparty(lender),
3278 msig(sfCounterpartySignature, alice, bella),
3282 using namespace loan;
3283 msigSetup(env, lender);
3284 Number const principalRequest = broker.
asset(1'000).value();
3287 "IOU authorized borrower, borrower submits, lender "
3289 env(
set(borrower, broker.
brokerID, principalRequest),
3290 counterparty(lender),
3291 msig(sfCounterpartySignature, alice, bella),
3294 CaseArgs{.requireAuth =
true, .authorizeBorrower =
true});
3298 using namespace loan;
3299 msigSetup(env, borrower);
3300 Number const principalRequest = broker.
asset(1'000).value();
3303 "MPT authorized borrower, lender submits, borrower "
3305 env(
set(lender, broker.
brokerID, principalRequest),
3306 counterparty(borrower),
3307 msig(sfCounterpartySignature, alice, bella),
3311 using namespace loan;
3312 msigSetup(env, borrower);
3313 Number const principalRequest = broker.
asset(1'000).value();
3316 "IOU authorized borrower, lender submits, borrower "
3318 env(
set(lender, broker.
brokerID, principalRequest),
3319 counterparty(borrower),
3320 msig(sfCounterpartySignature, alice, bella),
3323 CaseArgs{.requireAuth =
true, .authorizeBorrower =
true});
3327 using namespace loan;
3328 Number const principalRequest = broker.
asset(1'000).value();
3329 Vault const vault{env};
3330 auto tx = vault.set({.owner = lender, .
id = broker.
vaultID});
3331 tx[sfAssetsMaximum] = BrokerParameters::defaults().vaultDeposit;
3335 testcase(
"Vault at maximum value");
3336 env(
set(issuer, broker.
brokerID, principalRequest),
3337 counterparty(lender),
3339 sig(sfCounterpartySignature, lender),
3347 using namespace loan;
3348 Number const principalRequest = broker.
asset(1'000).value();
3349 Vault const vault{env};
3350 auto tx = vault.set({.owner = lender, .
id = broker.
vaultID});
3351 tx[sfAssetsMaximum] =
3352 BrokerParameters::defaults().vaultDeposit + broker.
asset(1).number();
3356 testcase(
"Vault maximum value exceeded");
3357 env(
set(issuer, broker.
brokerID, principalRequest),
3358 counterparty(lender),
3360 sig(sfCounterpartySignature, lender),
3363 paymentInterval(3600 * 24),
3787 using namespace jtx;
3789 Env env(*
this, all);
3791 auto lowerFee = [&]() {
3797 auto const baseFee = env.
current()->fees().base;
3802 auto const lenderPass =
"lender";
3805 env.
fund(
XRP(1'000'000), alice, lender, borrower);
3815 testcase(
"RPC AccountSet");
3817 txJson[sfTransactionType] =
"AccountSet";
3818 txJson[sfAccount] = borrower.
human();
3820 auto const signParams = [&]() {
3822 signParams[jss::passphrase] = borrowerPass;
3823 signParams[jss::key_type] =
"ed25519";
3824 signParams[jss::tx_json] = txJson;
3827 auto const jSign = env.
rpc(
"json",
"sign",
to_string(signParams));
3828 BEAST_EXPECT(jSign.isMember(jss::result) && jSign[jss::result].isMember(jss::tx_json));
3829 auto txSignResult = jSign[jss::result][jss::tx_json];
3830 auto txSignBlob = jSign[jss::result][jss::tx_blob].asString();
3831 txSignResult.removeMember(jss::hash);
3833 auto const jtx = env.
jt(txJson,
sig(borrower));
3834 BEAST_EXPECT(txSignResult == jtx.jv);
3837 auto const jSubmit = env.
rpc(
"submit", txSignBlob);
3839 jSubmit.isMember(jss::result) &&
3840 jSubmit[jss::result].isMember(jss::engine_result) &&
3841 jSubmit[jss::result][jss::engine_result].asString() ==
"tesSUCCESS");
3848 testcase(
"RPC LoanSet - illegal signature_target");
3851 txJson[sfTransactionType] =
"AccountSet";
3852 txJson[sfAccount] = borrower.
human();
3854 auto const borrowerSignParams = [&]() {
3856 params[jss::passphrase] = borrowerPass;
3857 params[jss::key_type] =
"ed25519";
3858 params[jss::signature_target] =
"Destination";
3859 params[jss::tx_json] = txJson;
3862 auto const jSignBorrower = env.
rpc(
"json",
"sign",
to_string(borrowerSignParams));
3864 jSignBorrower.isMember(jss::result) &&
3865 jSignBorrower[jss::result].isMember(jss::error) &&
3866 jSignBorrower[jss::result][jss::error] ==
"invalidParams" &&
3867 jSignBorrower[jss::result].isMember(jss::error_message) &&
3868 jSignBorrower[jss::result][jss::error_message] ==
"Destination");
3871 testcase(
"RPC LoanSet - sign and submit borrower initiated");
3874 txJson[sfTransactionType] =
"LoanSet";
3875 txJson[sfAccount] = borrower.
human();
3876 txJson[sfCounterparty] = lender.human();
3877 txJson[sfLoanBrokerID] =
3878 "FF924CD18A236C2B49CF8E80A351CEAC6A10171DC9F110025646894FEC"
3881 txJson[sfPrincipalRequested] =
"100000000";
3882 txJson[sfPaymentTotal] = 10000;
3883 txJson[sfPaymentInterval] = 3600;
3884 txJson[sfGracePeriod] = 300;
3885 txJson[sfFlags] = 65536;
3886 txJson[sfFee] =
to_string(24 * baseFee / 10);
3889 auto const borrowerSignParams = [&]() {
3891 params[jss::passphrase] = borrowerPass;
3892 params[jss::key_type] =
"ed25519";
3893 params[jss::tx_json] = txJson;
3896 auto const jSignBorrower = env.
rpc(
"json",
"sign",
to_string(borrowerSignParams));
3898 jSignBorrower.isMember(jss::result) &&
3899 jSignBorrower[jss::result].isMember(jss::tx_json),
3901 auto const txBorrowerSignResult = jSignBorrower[jss::result][jss::tx_json];
3902 auto const txBorrowerSignBlob = jSignBorrower[jss::result][jss::tx_blob].asString();
3908 auto const jSubmitBlob = env.
rpc(
"submit", txBorrowerSignBlob);
3909 BEAST_EXPECT(jSubmitBlob.isMember(jss::result));
3910 auto const jSubmitBlobResult = jSubmitBlob[jss::result];
3911 BEAST_EXPECT(jSubmitBlobResult.isMember(jss::tx_json));
3915 jSubmitBlobResult.isMember(jss::engine_result) &&
3916 jSubmitBlobResult[jss::engine_result].asString() ==
"temBAD_SIGNER");
3921 auto const lenderSignParams = [&]() {
3923 params[jss::passphrase] = lenderPass;
3924 params[jss::key_type] =
"ed25519";
3925 params[jss::signature_target] =
"CounterpartySignature";
3926 params[jss::tx_json] = txBorrowerSignResult;
3929 auto const jSignLender = env.
rpc(
"json",
"sign",
to_string(lenderSignParams));
3931 jSignLender.isMember(jss::result) &&
3932 jSignLender[jss::result].isMember(jss::tx_json));
3933 auto const txLenderSignResult = jSignLender[jss::result][jss::tx_json];
3934 auto const txLenderSignBlob = jSignLender[jss::result][jss::tx_blob].asString();
3938 auto const jSubmitBlob = env.
rpc(
"submit", txLenderSignBlob);
3939 BEAST_EXPECT(jSubmitBlob.isMember(jss::result));
3940 auto const jSubmitBlobResult = jSubmitBlob[jss::result];
3941 BEAST_EXPECT(jSubmitBlobResult.isMember(jss::tx_json));
3942 auto const jSubmitBlobTx = jSubmitBlobResult[jss::tx_json];
3947 jSubmitBlobResult.isMember(jss::engine_result) &&
3948 jSubmitBlobResult[jss::engine_result].asString() ==
"tecNO_ENTRY",
3952 !jSubmitBlob.isMember(jss::error) && !jSubmitBlobResult.isMember(jss::error));
3959 auto const jSubmitJson = env.
rpc(
"json",
"submit",
to_string(lenderSignParams));
3960 BEAST_EXPECT(jSubmitJson.isMember(jss::result));
3961 auto const jSubmitJsonResult = jSubmitJson[jss::result];
3962 BEAST_EXPECT(jSubmitJsonResult.isMember(jss::tx_json));
3963 auto const jSubmitJsonTx = jSubmitJsonResult[jss::tx_json];
3967 jSubmitJsonResult.isMember(jss::engine_result) &&
3968 jSubmitJsonResult[jss::engine_result].asString() ==
"tefPAST_SEQ",
3972 !jSubmitJson.isMember(jss::error) && !jSubmitJsonResult.isMember(jss::error));
3974 BEAST_EXPECT(jSubmitBlobTx == jSubmitJsonTx);
3978 testcase(
"RPC LoanSet - sign and submit lender initiated");
3981 txJson[sfTransactionType] =
"LoanSet";
3982 txJson[sfAccount] = lender.human();
3983 txJson[sfCounterparty] = borrower.
human();
3984 txJson[sfLoanBrokerID] =
3985 "FF924CD18A236C2B49CF8E80A351CEAC6A10171DC9F110025646894FEC"
3988 txJson[sfPrincipalRequested] =
"100000000";
3989 txJson[sfPaymentTotal] = 10000;
3990 txJson[sfPaymentInterval] = 3600;
3991 txJson[sfGracePeriod] = 300;
3992 txJson[sfFlags] = 65536;
3993 txJson[sfFee] =
to_string(24 * baseFee / 10);
3996 auto const lenderSignParams = [&]() {
3998 params[jss::passphrase] = lenderPass;
3999 params[jss::key_type] =
"ed25519";
4000 params[jss::tx_json] = txJson;
4003 auto const jSignLender = env.
rpc(
"json",
"sign",
to_string(lenderSignParams));
4005 jSignLender.isMember(jss::result) &&
4006 jSignLender[jss::result].isMember(jss::tx_json));
4007 auto const txLenderSignResult = jSignLender[jss::result][jss::tx_json];
4008 auto const txLenderSignBlob = jSignLender[jss::result][jss::tx_blob].asString();
4014 auto const jSubmitBlob = env.
rpc(
"submit", txLenderSignBlob);
4015 BEAST_EXPECT(jSubmitBlob.isMember(jss::result));
4016 auto const jSubmitBlobResult = jSubmitBlob[jss::result];
4017 BEAST_EXPECT(jSubmitBlobResult.isMember(jss::tx_json));
4021 jSubmitBlobResult.isMember(jss::engine_result) &&
4022 jSubmitBlobResult[jss::engine_result].asString() ==
"temBAD_SIGNER");
4027 auto const borrowerSignParams = [&]() {
4029 params[jss::passphrase] = borrowerPass;
4030 params[jss::key_type] =
"ed25519";
4031 params[jss::signature_target] =
"CounterpartySignature";
4032 params[jss::tx_json] = txLenderSignResult;
4035 auto const jSignBorrower = env.
rpc(
"json",
"sign",
to_string(borrowerSignParams));
4037 jSignBorrower.isMember(jss::result) &&
4038 jSignBorrower[jss::result].isMember(jss::tx_json));
4039 auto const txBorrowerSignResult = jSignBorrower[jss::result][jss::tx_json];
4040 auto const txBorrowerSignBlob = jSignBorrower[jss::result][jss::tx_blob].asString();
4044 auto const jSubmitBlob = env.
rpc(
"submit", txBorrowerSignBlob);
4045 BEAST_EXPECT(jSubmitBlob.isMember(jss::result));
4046 auto const jSubmitBlobResult = jSubmitBlob[jss::result];
4047 BEAST_EXPECT(jSubmitBlobResult.isMember(jss::tx_json));
4048 auto const jSubmitBlobTx = jSubmitBlobResult[jss::tx_json];
4053 jSubmitBlobResult.isMember(jss::engine_result) &&
4054 jSubmitBlobResult[jss::engine_result].asString() ==
"tecNO_ENTRY",
4058 !jSubmitBlob.isMember(jss::error) && !jSubmitBlobResult.isMember(jss::error));
4065 auto const jSubmitJson = env.
rpc(
"json",
"submit",
to_string(borrowerSignParams));
4066 BEAST_EXPECT(jSubmitJson.isMember(jss::result));
4067 auto const jSubmitJsonResult = jSubmitJson[jss::result];
4068 BEAST_EXPECT(jSubmitJsonResult.isMember(jss::tx_json));
4069 auto const jSubmitJsonTx = jSubmitJsonResult[jss::tx_json];
4073 jSubmitJsonResult.isMember(jss::engine_result) &&
4074 jSubmitJsonResult[jss::engine_result].asString() ==
"tefPAST_SEQ",
4078 !jSubmitJson.isMember(jss::error) && !jSubmitJsonResult.isMember(jss::error));
4080 BEAST_EXPECT(jSubmitBlobTx == jSubmitJsonTx);
4943 testcase <<
"Prevent nextPaymentDueDate overflow";
4945 using namespace jtx;
4946 using namespace std::chrono_literals;
4947 using namespace Lending;
4948 Env env(*
this, all);
4950 Account const issuer{
"issuer"};
4951 Account const lender{
"lender"};
4952 Account const borrower{
"borrower"};
4954 env.
fund(
XRP(1'000'000), issuer, lender, borrower);
4958 auto trustLenderTx = env.
json(trust(lender, iouAsset(1'000'000'000)));
4960 auto trustBorrowerTx = env.
json(trust(borrower, iouAsset(1'000'000'000)));
4961 env(trustBorrowerTx);
4962 auto payLenderTx = pay(issuer, lender, iouAsset(100'000'000));
4964 auto payIssuerTx = pay(issuer, borrower, iouAsset(10'000'000));
4969 BrokerInfo broker{createVaultAndBroker(env, iouAsset, lender, brokerParams)};
4971 using namespace loan;
4973 auto const loanSetFee =
fee(env.
current()->fees().base * 2);
4975 using timeType =
decltype(sfNextPaymentDueDate)::type::value_type;
4978 static_assert(maxTime == 4'294'967'295);
4980 auto const baseJson = [&]() {
4981 auto createJson = env.
json(
4985 gracePeriod(LoanSet::defaultGracePeriod),
4989 loanOriginationFee(218),
4997 auto const baseFee = env.
current()->fees().base;
4999 auto parentCloseTime = [&]() {
5000 return env.
current()->parentCloseTime().time_since_epoch().count();
5002 auto maxLoanTime = [&]() {
5003 auto const startDate = parentCloseTime();
5005 BEAST_EXPECT(startDate >= 50);
5007 return maxTime - startDate;
5012 auto const interval = maxLoanTime() + 1;
5013 auto const total = 1;
5014 auto createJson = env.
json(baseJson, paymentInterval(interval), paymentTotal(total));
5016 env(createJson, sig(sfCounterpartySignature, lender), ter(tecKILLED));
5022 auto const interval = 60;
5023 auto const total = maxLoanTime() + 1;
5026 env(createJson, sig(sfCounterpartySignature, lender), ter(tecKILLED));
5032 auto const interval = maxLoanTime() + 1;
5033 auto const total = 1;
5034 auto const grace = interval;
5035 auto createJson = env.
json(
5039 env(createJson, sig(sfCounterpartySignature, lender), ter(tecKILLED));
5044 auto const interval = 1'000'000'000;
5045 auto const total = 10;
5048 env(createJson, sig(sfCounterpartySignature, lender), ter(tecKILLED));
5054 auto const interval = 60;
5055 auto const total = 1'000'000'000;
5058 env(createJson, sig(sfCounterpartySignature, lender), ter(tecKILLED));
5064 auto const total = 60;
5065 auto const interval = (maxLoanTime() - total) / total;
5066 auto const grace = interval;
5067 auto createJson = env.
json(
5070 env(createJson, sig(sfCounterpartySignature, lender), ter(tecKILLED));
5075 auto const brokerStateBefore = env.
le(keylet::loanbroker(broker.
brokerID));
5076 auto const loanSequence = brokerStateBefore->at(sfLoanSequence);
5079 auto const grace = 100;
5080 auto const interval = maxLoanTime() - grace;
5081 auto const total = 1;
5082 auto createJson = env.
json(
5085 env(createJson, sig(sfCounterpartySignature, lender), ter(tesSUCCESS));
5089 auto const meta = env.
meta();
5090 if (BEAST_EXPECT(meta))
5092 BEAST_EXPECT(meta->at(sfTransactionResult) == tecKILLED);
5096 auto const loanSle = env.
le(keylet);
5098 BEAST_EXPECT(!loanSle);
5102 auto const brokerStateBefore = env.
le(keylet::loanbroker(broker.
brokerID));
5103 auto const loanSequence = brokerStateBefore->at(sfLoanSequence);
5106 auto const closeStartDate = (parentCloseTime() / 10 + 1) * 10;
5107 auto const grace = 5'000;
5108 auto const interval = maxTime - closeStartDate - grace;
5109 auto const total = 1;
5110 auto createJson = env.
json(
5113 env(createJson, sig(sfCounterpartySignature, lender), ter(tesSUCCESS));
5117 auto const meta = env.
meta();
5118 if (BEAST_EXPECT(meta))
5120 BEAST_EXPECT(meta->at(sfTransactionResult) == tesSUCCESS);
5124 auto const afterState = getCurrentState(env, broker, keylet);
5125 BEAST_EXPECT(afterState.nextPaymentDate == maxTime - grace);
5126 BEAST_EXPECT(afterState.previousPaymentDate == 0);
5127 BEAST_EXPECT(afterState.paymentRemaining == 1);
5132 env(
pay(issuer, borrower, iouAsset(Number{1'055'524'81, -2})));
5135 auto const closeStartDate = (parentCloseTime() / 10 + 1) * 10;
5136 auto const grace = 5'000;
5137 auto const maxLoanTime = maxTime - closeStartDate - grace;
5138 auto const total = [&]() {
5139 if (maxLoanTime % 5 == 0)
5141 if (maxLoanTime % 3 == 0)
5143 if (maxLoanTime % 2 == 0)
5147 if (!BEAST_EXPECT(total != 0))
5150 auto const brokerState = env.
le(keylet::loanbroker(broker.
brokerID));
5152 auto const loanSequence = brokerState->at(sfLoanSequence);
5155 auto const interval = maxLoanTime / total;
5156 auto createJson = env.
json(
5159 env(createJson, sig(sfCounterpartySignature, lender), ter(tesSUCCESS));
5163 auto const beforeState = getCurrentState(env, broker, keylet);
5164 BEAST_EXPECT(beforeState.nextPaymentDate == closeStartDate + interval);
5165 BEAST_EXPECT(beforeState.previousPaymentDate == 0);
5166 BEAST_EXPECT(beforeState.paymentRemaining == total);
5167 BEAST_EXPECT(beforeState.periodicPayment > 0);
5171 NumberRoundModeGuard
const mg{Number::upward};
5172 Number
const payment = beforeState.periodicPayment * (total - 1);
5173 XRPAmount
const payFee{baseFee * ((total - 1) / loanPaymentsPerFeeIncrement + 1)};
5174 STAmount
const paymentAmount =
5176 auto loanPayTx = env.
json(
pay(borrower,
keylet.
key, paymentAmount), fee(payFee));
5177 env(loanPayTx, ter(tesSUCCESS));
5182 auto const afterState = getCurrentState(env, broker, keylet);
5183 BEAST_EXPECT(afterState.paymentRemaining == 1);
5184 BEAST_EXPECT(afterState.nextPaymentDate == maxTime - grace);
5185 BEAST_EXPECT(afterState.previousPaymentDate == maxTime - grace - interval);
5597 testcase(
"PoC: Unsigned-underflow full-pay accrual after early periodic");
5599 using namespace jtx;
5600 using namespace loan;
5601 using namespace std::chrono_literals;
5603 Env env(*
this, all);
5605 Account const lender{
"poc_lender4"};
5606 Account const borrower{
"poc_borrower4"};
5607 env.
fund(
XRP(3'000'000), lender, borrower);
5612 auto const broker = createVaultAndBroker(env, asset, lender, brokerParams);
5616 auto const loanSetFee =
fee(env.
current()->fees().base * 2);
5617 Number const principalRequest = asset(1000).value();
5618 auto const originationFee = asset(0).value();
5619 auto const serviceFee = asset(1).value();
5620 auto const serviceFeePA = asset(1);
5621 auto const lateFee = asset(0).value();
5622 auto const closeFee = asset(0).value();
5627 auto const total = 3u;
5628 auto const interval = 600u;
5629 auto const grace = 60u;
5631 auto createJtx = env.
jt(
5632 set(borrower, broker.
brokerID, principalRequest, 0),
5633 sig(sfCounterpartySignature, lender),
5634 loanOriginationFee(originationFee),
5635 loanServiceFee(serviceFee),
5636 latePaymentFee(lateFee),
5637 closePaymentFee(closeFee),
5639 interestRate(interest),
5640 lateInterestRate(lateInterest),
5641 closeInterestRate(closeInterest),
5642 overpaymentInterestRate(overpaymentInterest),
5643 paymentTotal(total),
5644 paymentInterval(interval),
5648 auto const brokerSle = env.
le(keylet::loanbroker(broker.
brokerID));
5649 BEAST_EXPECT(brokerSle);
5650 auto const loanSequence = brokerSle ? brokerSle->at(sfLoanSequence) : 0;
5651 auto const loanKeylet = keylet::loan(broker.
brokerID, loanSequence);
5657 auto state = getCurrentState(env, broker, loanKeylet);
5659 auto const components = detail::computePaymentComponents(
5663 state.principalOutstanding,
5664 state.managementFeeOutstanding,
5665 state.periodicPayment,
5667 state.paymentRemaining,
5668 brokerParams.managementFeeRate);
5669 STAmount const regularDue{asset, components.trackedValueDelta + serviceFeePA.number()};
5671 env(pay(borrower, loanKeylet.key, regularDue));
5676 auto after = getCurrentState(env, broker, loanKeylet);
5677 auto const loanSle = env.
le(loanKeylet);
5678 BEAST_EXPECT(loanSle);
5679 auto const brokerSle2 = env.
le(keylet::loanbroker(broker.
brokerID));
5680 BEAST_EXPECT(brokerSle2);
5682 auto const closePaymentFee = loanSle ? loanSle->at(sfClosePaymentFee) :
Number{};
5683 auto const closeInterestRate =
5685 auto const managementFeeRate =
5692 detail::loanPrincipalFromPeriodicPayment(
5693 after.periodicPayment, periodicRate2,
after.paymentRemaining),
5695 env.
current()->parentCloseTime(),
5696 after.paymentInterval,
5697 after.previousPaymentDate,
5702 auto const roundedInterest =
5704 Number const roundedFullMgmtFee =
5706 Number const roundedFullInterest = roundedInterest - roundedFullMgmtFee;
5709 auto const nowSecs =
5711 auto const startSecs =
5713 auto const lastPaymentDate =
std::max(
after.previousPaymentDate, startSecs);
5714 auto const signedDelta =
5716 auto const unsignedDelta =
static_cast<std::uint32_t>(nowSecs - lastPaymentDate);
5717 log <<
"PoC window: prev=" <<
after.previousPaymentDate <<
" start=" << startSecs
5718 <<
" now=" << nowSecs <<
" signedDelta=" << signedDelta
5719 <<
" unsignedDelta=" << unsignedDelta <<
std::endl;
5723 auto const prevClamped =
std::min(
after.previousPaymentDate, nowSecs);
5725 detail::loanPrincipalFromPeriodicPayment(
5726 after.periodicPayment, periodicRate2,
after.paymentRemaining),
5728 env.
current()->parentCloseTime(),
5729 after.paymentInterval,
5733 auto const roundedInterestClamped =
5736 asset.raw(), roundedInterestClamped, managementFeeRate,
after.loanScale);
5737 Number const roundedFullInterestClamped =
5738 roundedInterestClamped - roundedFullMgmtFeeClamped;
5741 after.principalOutstanding + roundedFullInterestClamped + roundedFullMgmtFeeClamped +
5745 auto const vaultId2 = brokerSle2 ? brokerSle2->at(sfVaultID) :
uint256{};
5746 auto const vaultKey2 = keylet::vault(vaultId2);
5747 auto const vaultBefore = env.
le(vaultKey2);
5748 BEAST_EXPECT(vaultBefore);
5749 Number const assetsTotalBefore = vaultBefore ? vaultBefore->at(sfAssetsTotal) :
Number{};
5753 after.principalOutstanding + roundedFullInterest + roundedFullMgmtFee +
5756 log <<
"PoC payoff: principalOutstanding=" <<
after.principalOutstanding
5757 <<
" roundedFullInterest=" << roundedFullInterest
5758 <<
" roundedFullMgmtFee=" << roundedFullMgmtFee <<
" closeFee=" << closePaymentFee
5759 <<
" fullDue=" << to_string(fullDue.getJson()) <<
std::endl;
5760 log <<
"PoC reference (clamped): roundedFullInterestClamped=" << roundedFullInterestClamped
5761 <<
" roundedFullMgmtFeeClamped=" << roundedFullMgmtFeeClamped
5762 <<
" fullDueClamped=" << to_string(fullDueClamped.getJson()) <<
std::endl;
5764 env(pay(borrower, loanKeylet.key, fullDue),
txflags(tfLoanFullPayment));
5769 BEAST_EXPECT(unsignedDelta >
after.paymentInterval);
5772 auto const vaultAfter = env.
le(vaultKey2);
5773 BEAST_EXPECT(vaultAfter);
5776 auto const assetsTotalAfter = vaultAfter->at(sfAssetsTotal);
5777 log <<
"PoC NAV: assetsTotalBefore=" << assetsTotalBefore
5778 <<
" assetsTotalAfter=" << assetsTotalAfter
5779 <<
" delta=" << (assetsTotalAfter - assetsTotalBefore) <<
std::endl;
5783 BEAST_EXPECT(fullDue == fullDueClamped);
5784 if (fullDue > fullDueClamped)
5785 log <<
"PoC delta: overcharge (fullDue > clamped)" <<
std::endl;
5789 auto const finalLoan = env.
le(loanKeylet);
5790 BEAST_EXPECT(finalLoan);
5793 BEAST_EXPECT(finalLoan->at(sfPaymentRemaining) == 0);
5794 BEAST_EXPECT(finalLoan->at(sfPrincipalOutstanding) == 0);