1012 Keylet const& loanKeylet,
1020 using namespace jtx;
1022 using namespace std::chrono_literals;
1025 bool const showStepBalances = paymentParams.showStepBalances;
1029 auto const baseFee = env.
current()->fees().base;
1034 verifyLoanStatus(state);
1048 auto const periodicRate =
loanPeriodicRate(state.interestRate, state.paymentInterval);
1049 STAmount const roundedPeriodicPayment{
1053 if (!showStepBalances)
1055 log << currencyLabel <<
" Payment components: "
1056 <<
"Payments remaining, "
1057 <<
"rawInterest, rawPrincipal, "
1059 <<
"trackedValueDelta, trackedPrincipalDelta, "
1060 "trackedInterestDelta, trackedMgmtFeeDelta, special"
1069 state.totalValue, state.principalOutstanding, state.managementFeeOutstanding);
1073 state.periodicPayment,
1075 state.paymentRemaining,
1078 if (showStepBalances)
1080 log << currencyLabel <<
" Starting loan balances: "
1081 <<
"\n\tTotal value: " << currentRoundedState.valueOutstanding
1082 <<
"\n\tPrincipal: " << currentRoundedState.principalOutstanding
1083 <<
"\n\tInterest: " << currentRoundedState.interestDue
1084 <<
"\n\tMgmt fee: " << currentRoundedState.managementFeeDue
1085 <<
"\n\tPayments remaining " << state.paymentRemaining <<
std::endl;
1089 log << currencyLabel <<
" Loan starting state: " << state.paymentRemaining <<
", "
1090 << raw.interestDue <<
", " << raw.principalOutstanding <<
", "
1091 << raw.managementFeeDue <<
", " << currentRoundedState.valueOutstanding <<
", "
1092 << currentRoundedState.principalOutstanding <<
", "
1093 << currentRoundedState.interestDue <<
", "
1094 << currentRoundedState.managementFeeDue <<
std::endl;
1100 auto const extraAmount = paymentParams.overpaymentExtra
1101 ? broker.
asset(*paymentParams.overpaymentExtra).value()
1105 STAmount{broker.
asset, totalDue * paymentParams.overpaymentFactor} + extraAmount;
1108 auto const initialState = state;
1110 .trackedValueDelta = 0, .trackedPrincipalDelta = 0, .trackedManagementFeeDelta = 0};
1111 Number totalInterestPaid = 0;
1112 Number totalFeesPaid = 0;
1117 state.periodicPayment,
1119 state.paymentRemaining,
1122 auto validateBorrowerBalance = [&]() {
1123 if (borrower == issuer || !paymentParams.validateBalances)
1125 auto const totalSpent =
1130 borrowerInitialBalance - totalSpent);
1135 auto const p = places.value_or(defaultRound);
1138 auto const factor =
Number{1, p};
1139 return (n * factor).
truncate() / factor;
1141 while (state.paymentRemaining > 0)
1143 validateBorrowerBalance();
1150 state.principalOutstanding,
1151 state.managementFeeOutstanding,
1152 state.periodicPayment,
1154 state.paymentRemaining,
1158 paymentComponents.trackedValueDelta <= roundedPeriodicPayment ||
1160 paymentComponents.trackedValueDelta >= roundedPeriodicPayment));
1162 paymentComponents.trackedValueDelta ==
1163 paymentComponents.trackedPrincipalDelta + paymentComponents.trackedInterestPart() +
1164 paymentComponents.trackedManagementFeeDelta);
1168 state.periodicPayment,
1170 state.paymentRemaining - 1,
1177 deltas.
total() == state.periodicPayment ||
1178 (state.loanScale - (deltas.
total() - state.periodicPayment).exponent()) > 14);
1180 if (!showStepBalances)
1182 log << currencyLabel <<
" Payment components: " << state.paymentRemaining <<
", "
1185 <<
", " << paymentComponents.trackedValueDelta <<
", "
1186 << paymentComponents.trackedPrincipalDelta <<
", "
1187 << paymentComponents.trackedInterestPart() <<
", "
1188 << paymentComponents.trackedManagementFeeDelta <<
", " << [&]() ->
char const* {
1197 auto const totalDueAmount =
1198 STAmount{broker.
asset, paymentComponents.trackedValueDelta + serviceFee};
1200 if (paymentParams.validateBalances)
1208 Number const diff = totalDue - totalDueAmount;
1214 (state.loanScale - diff.
exponent() > 13))));
1217 paymentComponents.trackedPrincipalDelta >=
beast::kZero &&
1218 paymentComponents.trackedPrincipalDelta <= state.principalOutstanding);
1221 paymentComponents.trackedPrincipalDelta == state.principalOutstanding);
1224 auto const borrowerBalanceBeforePayment = env.
balance(borrower, broker.
asset);
1227 env(
pay(borrower, loanKeylet.
key, transactionAmount, paymentParams.flags));
1229 env.
close(D{state.paymentInterval / 2});
1231 if (paymentParams.validateBalances)
1237 adjustment = env.
current()->fees().base;
1244 borrowerBalanceBeforePayment,
1249 if (showStepBalances)
1251 auto const loanSle = env.
le(loanKeylet);
1252 if (!BEAST_EXPECT(loanSle))
1258 auto const errors = nextTrueState - current;
1259 log << currencyLabel <<
" Loan balances: "
1260 <<
"\n\tAmount taken: " << paymentComponents.trackedValueDelta
1261 <<
"\n\tTotal value: " << current.valueOutstanding
1263 <<
", error: " << truncate(errors.total())
1264 <<
")\n\tPrincipal: " << current.principalOutstanding
1266 <<
", error: " << truncate(errors.principal)
1267 <<
")\n\tInterest: " << current.interestDue
1268 <<
" (true: " << truncate(nextTrueState.
interestDue)
1269 <<
", error: " << truncate(errors.interest)
1270 <<
")\n\tMgmt fee: " << current.managementFeeDue
1272 <<
", error: " << truncate(errors.managementFee) <<
")\n\tPayments remaining "
1273 << loanSle->at(sfPaymentRemaining) <<
std::endl;
1275 currentRoundedState = current;
1278 --state.paymentRemaining;
1279 state.previousPaymentDate = state.nextPaymentDate;
1282 state.paymentRemaining = 0;
1283 state.nextPaymentDate = 0;
1287 state.nextPaymentDate += state.paymentInterval;
1289 state.principalOutstanding -= paymentComponents.trackedPrincipalDelta;
1290 state.managementFeeOutstanding -= paymentComponents.trackedManagementFeeDelta;
1291 state.totalValue -= paymentComponents.trackedValueDelta;
1293 if (paymentParams.validateBalances)
1294 verifyLoanStatus(state);
1299 totalInterestPaid += paymentComponents.trackedInterestPart();
1300 totalFeesPaid += serviceFee;
1301 ++totalPaymentsMade;
1303 currentTrueState = nextTrueState;
1305 validateBorrowerBalance();
1308 BEAST_EXPECT(state.paymentRemaining == 0);
1309 BEAST_EXPECT(state.principalOutstanding == 0);
1311 auto const initialInterestDue = initialState.totalValue -
1312 (initialState.principalOutstanding + initialState.managementFeeOutstanding);
1313 if (paymentParams.validateBalances)
1322 BEAST_EXPECT(totalInterestPaid == initialInterestDue);
1323 BEAST_EXPECT(totalPaymentsMade == initialState.paymentRemaining);
1326 if (showStepBalances)
1328 auto const loanSle = env.
le(loanKeylet);
1329 if (!BEAST_EXPECT(loanSle))
1334 log << currencyLabel <<
" Total amounts paid: "
1336 <<
" (initial: " << truncate(initialState.totalValue)
1337 <<
", error: " << truncate(initialState.totalValue - totalPaid.
trackedValueDelta)
1339 <<
" (initial: " << truncate(initialState.principalOutstanding) <<
", error: "
1341 <<
")\n\tInterest: " << totalInterestPaid
1342 <<
" (initial: " << truncate(initialInterestDue)
1343 <<
", error: " << truncate(initialInterestDue - totalInterestPaid)
1345 <<
" (initial: " << truncate(initialState.managementFeeOutstanding) <<
", error: "
1348 <<
")\n\tTotal payments made: " << totalPaymentsMade <<
std::endl;
1406 Number const& loanAmount,
1407 int interestExponent,
1419 auto const [
keylet, loanSequence] = [&]() {
1421 if (!BEAST_EXPECT(brokerSle))
1428 BEAST_EXPECT(brokerSle->at(sfOwnerCount) == 0);
1432 auto const loanSequence = brokerSle->at(sfLoanSequence);
1442 if (!BEAST_EXPECT(loanSequence != 0))
1445 testcase << caseLabel <<
" " << label;
1447 using namespace jtx;
1448 using namespace loan;
1449 using namespace std::chrono_literals;
1451 auto applyExponent = [interestExponent,
this](
TenthBips32 value)
mutable {
1453 while (interestExponent > 0)
1455 auto const oldValue = value;
1458 BEAST_EXPECT(value / 10 == oldValue);
1460 while (interestExponent < 0)
1462 auto const oldValue = value;
1465 BEAST_EXPECT(value * 10 == oldValue);
1470 auto const borrowerOwnerCount = env.
ownerCount(borrower);
1472 auto const loanSetFee = env.
current()->fees().base * 2;
1474 .account = borrower,
1476 .counterpartyExplicit =
false,
1477 .principalRequest = loanAmount,
1478 .setFee = loanSetFee,
1479 .originationFee = 1,
1496 auto const serviceFeeAmount = broker.
asset(*loanParams.
serviceFee).value();
1497 auto const lateFeeAmount = broker.
asset(*loanParams.
lateFee).value();
1498 auto const closeFeeAmount = broker.
asset(*loanParams.
closeFee).value();
1500 auto const borrowerStartbalance = env.
balance(borrower, broker.
asset);
1502 auto createJtx = loanParams(env, broker);
1508 auto const startDate = env.
current()->header().parentCloseTime.time_since_epoch().count();
1511 BEAST_EXPECT(brokerSle))
1513 BEAST_EXPECT(brokerSle->at(sfOwnerCount) == 1);
1521 adjustment = 2 * env.
current()->fees().base;
1526 borrowerStartbalance.
value() + principalRequestAmount - originationFeeAmount -
1527 adjustment.
value());
1530 auto const loanFlags =
1538 loan->isFlag(lsfLoanOverpayment) == createJtx.stx->isFlag(tfLoanOverpayment));
1539 BEAST_EXPECT(
loan->at(sfLoanSequence) == loanSequence);
1540 BEAST_EXPECT(
loan->at(sfBorrower) == borrower.
id());
1541 BEAST_EXPECT(
loan->at(sfLoanBrokerID) == broker.
brokerID);
1542 BEAST_EXPECT(
loan->at(sfLoanOriginationFee) == originationFeeAmount);
1543 BEAST_EXPECT(
loan->at(sfLoanServiceFee) == serviceFeeAmount);
1544 BEAST_EXPECT(
loan->at(sfLatePaymentFee) == lateFeeAmount);
1545 BEAST_EXPECT(
loan->at(sfClosePaymentFee) == closeFeeAmount);
1546 BEAST_EXPECT(
loan->at(sfOverpaymentFee) == *loanParams.
overFee);
1547 BEAST_EXPECT(
loan->at(sfInterestRate) == *loanParams.
interest);
1551 BEAST_EXPECT(
loan->at(sfStartDate) == startDate);
1553 BEAST_EXPECT(
loan->at(sfGracePeriod) == *loanParams.
gracePd);
1554 BEAST_EXPECT(
loan->at(sfPreviousPaymentDueDate) == 0);
1555 BEAST_EXPECT(
loan->at(sfNextPaymentDueDate) == startDate + *loanParams.
payInterval);
1556 BEAST_EXPECT(
loan->at(sfPaymentRemaining) == *loanParams.
payTotal);
1558 loan->at(sfLoanScale) >=
1562 BEAST_EXPECT(
loan->at(sfPrincipalOutstanding) == principalRequestAmount);
1570 state.principalOutstanding,
1572 state.paymentInterval,
1573 state.paymentRemaining,
1582 loanProperties.loanState.valueOutstanding,
1583 principalRequestAmount,
1584 loanProperties.loanState.managementFeeDue,
1585 loanProperties.periodicPayment,
1590 env(manage(lender,
keylet.key, 0));
1593 auto jt = manage(lender,
keylet.key, 0);
1594 jt.removeMember(sfFlags.getName());
1605 env(manage(lender,
keylet.key, tfLoanUnimpair | tfLoanImpair | tfLoanDefault),
1624 if (!env.
current()->rules().enabled(fixCleanup3_4_0))
1627 env(manage(lender,
keylet.key, tfLoanImpair),
1630 env(manage(lender,
keylet.key, tfLoanUnimpair),
1642 auto const nextDueDate = startDate + *loanParams.
payInterval;
1650 loanProperties.loanScale,
1651 loanProperties.loanState.valueOutstanding,
1652 principalRequestAmount,
1653 loanProperties.loanState.managementFeeDue,
1654 loanProperties.periodicPayment,
1660 if (BEAST_EXPECT(toEndOfLife))
1661 toEndOfLife(
keylet, verifyLoanStatus);
1667 BEAST_EXPECT(
loan->at(sfPaymentRemaining) == 0);
1668 BEAST_EXPECT(
loan->at(sfPrincipalOutstanding) == 0);
1670 auto const borrowerStartingBalance = env.
balance(borrower, broker.
asset);
1686 static unsigned kDeleteCounter = 0;
1687 auto const deleter = ((++kDeleteCounter % 2) != 0u) ? lender : borrower;
1688 env(del(deleter,
keylet.key));
1692 if (deleter == borrower)
1697 adjustment = env.
current()->fees().base;
1706 borrowerStartingBalance.
value() - adjustment);
1707 BEAST_EXPECT(env.
ownerCount(borrower) == borrowerOwnerCount);
1710 BEAST_EXPECT(brokerSle))
1712 BEAST_EXPECT(brokerSle->at(sfOwnerCount) == 0);
1744 Number const& loanAmount,
1745 int interestExponent)
1747 using namespace jtx;
1750 auto const& asset = broker.
asset.
raw();
1752 auto const caseLabel = [&]() {
1754 ss <<
"Lifecycle: " << loanAmount <<
" " << currencyLabel
1755 <<
" Scale interest to: " << interestExponent <<
" ";
1760 using namespace loan;
1761 using namespace std::chrono_literals;
1765 Account const issuer{
"issuer"};
1768 Account const lender{
"lender"};
1770 Account const borrower{
"borrower"};
1776 Number const principalRequest = broker.
asset(loanAmount).value();
1778 BEAST_EXPECT(maxCoveredLoanValue == 1000 * 100 / 10);
1779 Number const maxCoveredLoanRequest = broker.
asset(maxCoveredLoanValue).value();
1783 auto const loanSetFee =
Fee(env.
current()->fees().base * 2);
1787 auto const baseFee = env.
current()->fees().base;
1792 env(
set(evan, broker.
brokerID, principalRequest, tfLoanSetMask),
1793 Sig(sfCounterpartySignature, lender),
1800 Sig(sfCounterpartySignature, borrower),
1806 Sig(sfCounterpartySignature, lender),
1814 Sig(sfCounterpartySignature, borrower),
1815 kOverpaymentFee(kMaxOverpaymentFee),
1820 Sig(sfCounterpartySignature, lender),
1821 kOverpaymentFee(kMaxOverpaymentFee + 1),
1827 Sig(sfCounterpartySignature, borrower),
1828 kInterestRate(kMaxInterestRate),
1832 Sig(sfCounterpartySignature, borrower),
1838 Sig(sfCounterpartySignature, lender),
1839 kInterestRate(kMaxInterestRate + 1),
1844 Sig(sfCounterpartySignature, lender),
1851 Sig(sfCounterpartySignature, borrower),
1852 kLateInterestRate(kMaxLateInterestRate),
1856 Sig(sfCounterpartySignature, borrower),
1862 Sig(sfCounterpartySignature, lender),
1863 kLateInterestRate(kMaxLateInterestRate + 1),
1868 Sig(sfCounterpartySignature, lender),
1875 Sig(sfCounterpartySignature, borrower),
1876 kCloseInterestRate(kMaxCloseInterestRate),
1880 Sig(sfCounterpartySignature, borrower),
1886 Sig(sfCounterpartySignature, lender),
1887 kCloseInterestRate(kMaxCloseInterestRate + 1),
1891 Sig(sfCounterpartySignature, lender),
1898 Sig(sfCounterpartySignature, borrower),
1899 kOverpaymentInterestRate(kMaxOverpaymentInterestRate),
1903 Sig(sfCounterpartySignature, borrower),
1909 Sig(sfCounterpartySignature, lender),
1910 kOverpaymentInterestRate(kMaxOverpaymentInterestRate + 1),
1914 Sig(sfCounterpartySignature, lender),
1921 Sig(sfCounterpartySignature, borrower),
1927 Sig(sfCounterpartySignature, lender),
1934 Sig(sfCounterpartySignature, borrower),
1940 Sig(sfCounterpartySignature, lender),
1947 Sig(sfCounterpartySignature, borrower),
1954 Sig(sfCounterpartySignature, lender),
1961 env(
set(borrower, broker.
brokerID, principalRequest),
1962 Sig(sfCounterpartySignature, lender),
1965 env(
signers(lender, 2, {{evan, 1}, {borrower, 1}}));
1966 env(
signers(borrower, 2, {{evan, 1}, {lender, 1}}));
1967 env(
set(borrower, broker.
brokerID, principalRequest),
1968 kCounterparty(lender),
1970 Msig(sfCounterpartySignature, evan, borrower),
1974 env(
set(borrower, broker.
brokerID, principalRequest),
1975 kCounterparty(lender),
1976 Msig(alice, issuer),
1977 Msig(sfCounterpartySignature, evan, borrower),
1981 env(
set(borrower, broker.
brokerID, principalRequest),
1982 kCounterparty(lender),
1984 Msig(sfCounterpartySignature, alice, issuer),
1990 env(
set(borrower, broker.
brokerID, principalRequest),
1991 kCounterparty(lender),
1993 Msig(sfCounterpartySignature, evan, borrower),
1998 env(
set(borrower, broker.
brokerID, principalRequest),
1999 Sig(sfCounterpartySignature, evan),
2004 kCounterparty(borrower),
2005 Sig(sfCounterpartySignature, borrower),
2009 env(
set(lender, badKeylet.key, principalRequest),
2010 Sig(sfCounterpartySignature, evan),
2014 env(
set(lender, badKeylet.key, principalRequest),
2015 kCounterparty(borrower),
2016 Sig(sfCounterpartySignature, borrower),
2020 env(
set(lender, broker.
brokerID, principalRequest),
2021 kCounterparty(alice),
2022 Sig(sfCounterpartySignature, alice),
2027 env(
set(evan, broker.
brokerID, totalVaultRequest + 1),
2028 Sig(sfCounterpartySignature, lender),
2034 env(
set(evan, broker.
brokerID, maxCoveredLoanRequest + 1),
2035 Sig(sfCounterpartySignature, lender),
2044 if (!BEAST_EXPECT(brokerSle))
2047 auto const vaultPseudo = [&]() {
2048 auto const vaultSle = env.
le(
keylet::vault(brokerSle->at(sfVaultID)));
2049 if (!BEAST_EXPECT(vaultSle))
2054 auto vaultPseudo =
Account(
"Vault pseudo-account", vaultSle->at(sfAccount));
2058 auto const [freeze, deepfreeze, unfreeze, expectedResult] =
2073 auto freeze = [&](
Account const& holder) {
2076 auto deepfreeze = [&](
Account const& holder) {
2079 auto unfreeze = [&](
Account const& holder) {
2081 issuer, holder[
iouCurrency_](0), tfClearFreeze | tfClearDeepFreeze));
2086 auto freeze = [&](
Account const& holder) {
2087 mptt.
set({.account = issuer, .holder = holder, .flags = tfMPTLock});
2089 auto unfreeze = [&](
Account const& holder) {
2090 mptt.
set({.account = issuer, .holder = holder, .flags = tfMPTUnlock});
2098 for (
auto const& account : {vaultPseudo, evan})
2104 env(
set(evan, broker.
brokerID, debtMaximumRequest),
2105 Sig(sfCounterpartySignature, lender),
2107 Ter(expectedResult));
2110 BEAST_EXPECT(unfreeze);
2116 env(
set(evan, broker.
brokerID, debtMaximumRequest + 1),
2117 Sig(sfCounterpartySignature, lender),
2133 for (
auto const& account : {
2141 deepfreeze(account);
2144 env(
set(evan, broker.
brokerID, debtMaximumRequest),
2145 Sig(sfCounterpartySignature, lender),
2147 Ter(expectedResult));
2150 BEAST_EXPECT(unfreeze);
2156 env(
set(evan, broker.
brokerID, debtMaximumRequest + 1),
2157 Sig(sfCounterpartySignature, lender),
2166 auto coverAvailable = [&env,
this](
UInt256 const& brokerID,
Number const& expected) {
2168 BEAST_EXPECT(brokerSle))
2170 auto const available = brokerSle->at(sfCoverAvailable);
2171 BEAST_EXPECT(available == expected);
2178 BEAST_EXPECT(brokerSle))
2185 broker.
vaultScale(env), state.principalOutstanding.exponent())));
2194 state.totalValue - state.managementFeeOutstanding),
2200 auto replenishCover = [&env, &coverAvailable](
2203 Number const& startingCoverAvailable,
2204 Number const& amountToBeCovered) {
2205 coverAvailable(broker.
brokerID, startingCoverAvailable - amountToBeCovered);
2208 coverAvailable(broker.
brokerID, startingCoverAvailable);
2212 auto defaultImmediately = [&](
std::uint32_t baseFlag,
bool impair =
true) {
2213 return [&, impair, baseFlag](
2220 auto state =
getCurrentState(env, broker, loanKeylet, verifyLoanStatus);
2221 BEAST_EXPECT(state.flags == baseFlag);
2223 auto const& broker = verifyLoanStatus.
broker;
2224 auto const startingCoverAvailable = coverAvailable(
2237 env(manage(lender, loanKeylet.key, tfLoanImpair),
2242 state.flags |= tfLoanImpair;
2246 if (!env.
current()->rules().enabled(fixCleanup3_4_0))
2252 verifyLoanStatus(state);
2255 auto const nextDueDate = Tp{D{state.nextPaymentDate}};
2259 env(manage(lender, loanKeylet.key, tfLoanDefault),
Ter(
tecTOO_SOON));
2263 env.
close(nextDueDate + 60s);
2265 auto const [amountToBeCovered, brokerAcct] = getDefaultInfo(state, broker);
2268 env(manage(lender, loanKeylet.key, tfLoanDefault));
2273 replenishCover(broker, brokerAcct, startingCoverAvailable, amountToBeCovered);
2275 state.flags |= tfLoanDefault;
2276 state.paymentRemaining = 0;
2277 state.totalValue = 0;
2278 state.principalOutstanding = 0;
2279 state.managementFeeOutstanding = 0;
2280 state.nextPaymentDate = 0;
2281 verifyLoanStatus(state);
2291 auto singlePayment = [&](
Keylet const& loanKeylet,
2300 verifyLoanStatus(state);
2313 if (!(state.flags & lsfLoanOverpayment))
2322 bool const hasFix313 = env.
current()->rules().enabled(fixCleanup3_1_3);
2325 baseFee * (
Number{15, -1} / kLoanPaymentsPerFeeIncrement + 1)};
2326 env(
pay(borrower, loanKeylet.
key, overpayAmount, tfLoanOverpayment),
2333 env(
pay(borrower, loanKeylet.
key, overpayAmount, tfLoanOverpayment),
2344 broker.
asset(state.periodicPayment * 2),
2345 tfLoanLatePayment | tfLoanFullPayment),
2349 broker.
asset(state.periodicPayment * 2),
2350 tfLoanLatePayment | tfLoanOverpayment),
2354 broker.
asset(state.periodicPayment * 2),
2355 tfLoanOverpayment | tfLoanFullPayment),
2359 broker.
asset(state.periodicPayment * 2),
2360 tfLoanLatePayment | tfLoanOverpayment | tfLoanFullPayment),
2364 auto const otherAsset =
2365 broker.
asset.
raw() == assets[0].raw() ? assets[1] : assets[0];
2370 env(
pay(borrower, loanKeylet.
key,
STAmount{broker.asset, 1}, txFlags),
2375 auto const borrowerBalanceBeforePayment = env.
balance(borrower, broker.
asset);
2377 BEAST_EXPECT(payoffAmount > state.principalOutstanding);
2380 auto const transactionAmount = payoffAmount + broker.
asset(10);
2386 (borrowerBalanceBeforePayment.number() * 2 / state.periodicPayment /
2387 kLoanPaymentsPerFeeIncrement +
2391 STAmount{broker.asset, borrowerBalanceBeforePayment.number() * 2},
2396 XRPAmount const goodFee{baseFee * (numPayments / kLoanPaymentsPerFeeIncrement + 1)};
2397 env(
pay(borrower, loanKeylet.
key, transactionAmount, txFlags),
Fee(goodFee));
2407 adjustment = badFee + goodFee;
2410 state.paymentRemaining = 0;
2411 state.principalOutstanding = 0;
2412 state.totalValue = 0;
2413 state.managementFeeOutstanding = 0;
2414 state.previousPaymentDate =
2415 state.nextPaymentDate + (state.paymentInterval * (numPayments - 1));
2416 state.nextPaymentDate = 0;
2417 verifyLoanStatus(state);
2419 verifyLoanStatus.checkPayment(
2420 state.loanScale, borrower, borrowerBalanceBeforePayment, payoffAmount, adjustment);
2428 return [&, baseFlag](
2432 auto state =
getCurrentState(env, broker, loanKeylet, verifyLoanStatus);
2433 env.
close(state.startDate + 20s);
2434 auto const loanAge = (env.
now() - state.startDate).count();
2435 BEAST_EXPECT(loanAge == 30);
2445 Number const interval = state.paymentInterval;
2449 STAmount const principalOutstanding{broker.
asset, state.principalOutstanding};
2451 broker.
asset, state.principalOutstanding * periodicRate * loanAge / interval};
2452 BEAST_EXPECT(accruedInterest == broker.
asset(
Number(1141552511415525, -19)));
2454 broker.
asset, state.principalOutstanding *
Number(36, -3)};
2455 BEAST_EXPECT(prepaymentPenalty == broker.
asset(36));
2458 principalOutstanding + accruedInterest + prepaymentPenalty + closePaymentFee,
2471 state.paymentRemaining * (state.periodicPayment + broker.
asset(2).value()));
2490 auto state =
getCurrentState(env, broker, loanKeylet, verifyLoanStatus);
2499 auto const startingPayments = state.paymentRemaining;
2500 STAmount const payoffAmount = [&]() {
2502 auto const rawPayoff =
2503 startingPayments * (state.periodicPayment + broker.
asset(2).value());
2506 payoffAmount == broker.
asset(
Number(1024014840244721, -12)),
2508 BEAST_EXPECT(payoffAmount > state.principalOutstanding);
2510 payoffAmount =
roundToScale(payoffAmount, state.loanScale);
2512 return payoffAmount;
2515 auto const totalPayoffValue =
2516 state.totalValue + startingPayments * broker.
asset(2).value();
2517 STAmount const totalPayoffAmount{broker.
asset, totalPayoffValue};
2520 totalPayoffAmount == payoffAmount,
2521 "Payoff amount: " +
to_string(payoffAmount) +
2522 ". Total Value: " +
to_string(totalPayoffAmount));
2529 state.paymentRemaining,
2541 "Loan overpayment allowed - Impair and Default",
2551 defaultImmediately(lsfLoanOverpayment));
2555 "Loan overpayment prohibited - Impair and Default",
2565 defaultImmediately(0));
2569 "Loan overpayment allowed - Default without Impair",
2579 defaultImmediately(lsfLoanOverpayment,
false));
2583 "Loan overpayment prohibited - Default without Impair",
2593 defaultImmediately(0,
false));
2597 "Loan overpayment prohibited - Pay off immediately",
2611 "Loan overpayment allowed - Pay off immediately",
2621 fullPayment(lsfLoanOverpayment));
2625 "Loan overpayment prohibited - Combine all payments",
2635 combineAllPayments(0));
2639 "Loan overpayment allowed - Combine all payments",
2649 combineAllPayments(lsfLoanOverpayment));
2653 "Loan overpayment prohibited - Make payments",
2667 auto state =
getCurrentState(env, broker, loanKeylet, verifyLoanStatus);
2668 BEAST_EXPECT(state.flags == 0);
2671 verifyLoanStatus(state);
2673 env.
close(state.startDate + 20s);
2674 auto const loanAge = (env.
now() - state.startDate).count();
2675 BEAST_EXPECT(loanAge == 30);
2685 Number const interval = state.paymentInterval;
2689 STAmount const roundedPeriodicPayment{
2693 testcase << currencyLabel <<
" Payment components: "
2694 <<
"Payments remaining, rawInterest, rawPrincipal, "
2695 "rawMFee, trackedValueDelta, trackedPrincipalDelta, "
2696 "trackedInterestDelta, trackedMgmtFeeDelta, special";
2698 auto const serviceFee = broker.
asset(2);
2701 roundedPeriodicPayment ==
2710 roundedPeriodicPayment + serviceFee,
2726 state.periodicPayment,
2728 state.paymentRemaining,
2732 state.principalOutstanding,
2733 state.managementFeeOutstanding);
2734 testcase << currencyLabel <<
" Loan starting state: " << state.paymentRemaining
2735 <<
", " << raw.interestDue <<
", " << raw.principalOutstanding <<
", "
2736 << raw.managementFeeDue <<
", " << rounded.valueOutstanding <<
", "
2737 << rounded.principalOutstanding <<
", " << rounded.interestDue <<
", "
2738 << rounded.managementFeeDue;
2748 transactionAmount ==
2754 auto const initialState = state;
2756 .trackedValueDelta = 0,
2757 .trackedPrincipalDelta = 0,
2758 .trackedManagementFeeDelta = 0};
2759 Number totalInterestPaid = 0;
2764 state.periodicPayment,
2766 state.paymentRemaining,
2769 while (state.paymentRemaining > 0)
2777 state.principalOutstanding,
2778 state.managementFeeOutstanding,
2779 state.periodicPayment,
2781 state.paymentRemaining,
2786 paymentComponents.trackedValueDelta <= roundedPeriodicPayment,
2787 "Delta: " +
to_string(paymentComponents.trackedValueDelta) +
2788 ", periodic payment: " +
to_string(roundedPeriodicPayment));
2792 state.periodicPayment,
2794 state.paymentRemaining - 1,
2798 testcase << currencyLabel <<
" Payment components: " << state.paymentRemaining
2800 << deltas.
managementFee <<
", " << paymentComponents.trackedValueDelta
2801 <<
", " << paymentComponents.trackedPrincipalDelta <<
", "
2802 << paymentComponents.trackedInterestPart() <<
", "
2803 << paymentComponents.trackedManagementFeeDelta <<
", "
2804 << [&]() ->
char const* {
2805 if (paymentComponents.specialCase ==
2808 if (paymentComponents.specialCase ==
2814 auto const totalDueAmount =
STAmount{
2815 broker.
asset, paymentComponents.trackedValueDelta + serviceFee.number()};
2823 Number const diff = totalDue - totalDueAmount;
2829 (state.loanScale - diff.
exponent() > 13))));
2832 paymentComponents.trackedValueDelta ==
2833 paymentComponents.trackedPrincipalDelta +
2834 paymentComponents.trackedInterestPart() +
2835 paymentComponents.trackedManagementFeeDelta);
2838 paymentComponents.trackedValueDelta <= roundedPeriodicPayment);
2841 state.paymentRemaining < 12 ||
2853 paymentComponents.trackedPrincipalDelta >=
beast::kZero &&
2854 paymentComponents.trackedPrincipalDelta <= state.principalOutstanding);
2857 paymentComponents.trackedPrincipalDelta == state.principalOutstanding);
2860 (state.periodicPayment.exponent() -
2862 state.periodicPayment)
2865 auto const borrowerBalanceBeforePayment = env.
balance(borrower, broker.
asset);
2872 auto const loanSle = env.
le(loanKeylet);
2873 bool const impairAllowed = BEAST_EXPECT(loanSle) &&
2875 (!env.
current()->rules().enabled(fixCleanup3_4_0) ||
2880 env(manage(lender, loanKeylet.
key, tfLoanImpair));
2886 env(
pay(borrower, loanKeylet.
key, transactionAmount));
2894 adjustment = env.
current()->fees().base;
2898 verifyLoanStatus.checkPayment(
2901 borrowerBalanceBeforePayment,
2905 --state.paymentRemaining;
2906 state.previousPaymentDate = state.nextPaymentDate;
2909 state.paymentRemaining = 0;
2910 state.nextPaymentDate = 0;
2914 state.nextPaymentDate += state.paymentInterval;
2916 state.principalOutstanding -= paymentComponents.trackedPrincipalDelta;
2917 state.managementFeeOutstanding -= paymentComponents.trackedManagementFeeDelta;
2918 state.totalValue -= paymentComponents.trackedValueDelta;
2920 verifyLoanStatus(state);
2925 paymentComponents.trackedManagementFeeDelta;
2926 totalInterestPaid += paymentComponents.trackedInterestPart();
2927 ++totalPaymentsMade;
2929 currentTrueState = nextTrueState;
2933 BEAST_EXPECT(state.paymentRemaining == 0);
2934 BEAST_EXPECT(state.principalOutstanding == 0);
2944 totalInterestPaid ==
2945 initialState.totalValue -
2946 (initialState.principalOutstanding +
2947 initialState.managementFeeOutstanding));
2948 BEAST_EXPECT(totalPaymentsMade == initialState.paymentRemaining);
2967 if (!BEAST_EXPECT(timed))
2973 auto const start = ClockType::now();
2998 using namespace loan;
3000 auto const state =
getCurrentState(env, broker, verifyLoanStatus.keylet);
3001 auto const serviceFee = broker.
asset(2).value();
3006 broker.
asset, state.periodicPayment + serviceFee, state.loanScale)};
3009 time(
"single payment", [&]() { env(
pay(borrower, loanKeylet.
key, totalDue)); });
3013 auto const numPayments = (state.paymentRemaining - 2);
3014 STAmount const bigPayment{broker.
asset, totalDue * numPayments};
3015 XRPAmount const bigFee{baseFee * (numPayments / kLoanPaymentsPerFeeIncrement + 1)};
3016 time(
"ten payments", [&]() {
3017 env(
pay(borrower, loanKeylet.
key, bigPayment),
Fee(bigFee));
3021 time(
"final payment", [&]() {
3023 env(
pay(borrower, loanKeylet.
key, totalDue +
STAmount{broker.asset, 1}));
3030 "Loan overpayment allowed - Explicit overpayment",
3044 "Loan overpayment prohibited - Late payment",
3058 "Loan overpayment allowed - Late payment",
3072 "Loan overpayment allowed - Late payment and overpayment",