972 using namespace std::chrono_literals;
975 bool const showStepBalances = paymentParams.showStepBalances;
979 auto const baseFee = env.
current()->fees().base;
984 verifyLoanStatus(state);
998 auto const periodicRate =
loanPeriodicRate(state.interestRate, state.paymentInterval);
999 STAmount const roundedPeriodicPayment{
1003 if (!showStepBalances)
1005 log << currencyLabel <<
" Payment components: "
1006 <<
"Payments remaining, "
1007 <<
"rawInterest, rawPrincipal, "
1009 <<
"trackedValueDelta, trackedPrincipalDelta, "
1010 "trackedInterestDelta, trackedMgmtFeeDelta, special"
1019 state.totalValue, state.principalOutstanding, state.managementFeeOutstanding);
1023 state.periodicPayment,
1025 state.paymentRemaining,
1028 if (showStepBalances)
1030 log << currencyLabel <<
" Starting loan balances: "
1031 <<
"\n\tTotal value: " << currentRoundedState.valueOutstanding
1032 <<
"\n\tPrincipal: " << currentRoundedState.principalOutstanding
1033 <<
"\n\tInterest: " << currentRoundedState.interestDue
1034 <<
"\n\tMgmt fee: " << currentRoundedState.managementFeeDue
1035 <<
"\n\tPayments remaining " << state.paymentRemaining <<
std::endl;
1039 log << currencyLabel <<
" Loan starting state: " << state.paymentRemaining <<
", "
1040 << raw.interestDue <<
", " << raw.principalOutstanding <<
", "
1041 << raw.managementFeeDue <<
", " << currentRoundedState.valueOutstanding <<
", "
1042 << currentRoundedState.principalOutstanding <<
", "
1043 << currentRoundedState.interestDue <<
", "
1044 << currentRoundedState.managementFeeDue <<
std::endl;
1050 auto const extraAmount = paymentParams.overpaymentExtra
1051 ? broker.
asset(*paymentParams.overpaymentExtra).value()
1055 STAmount{broker.
asset, totalDue * paymentParams.overpaymentFactor} + extraAmount;
1058 auto const initialState = state;
1060 .trackedValueDelta = 0, .trackedPrincipalDelta = 0, .trackedManagementFeeDelta = 0};
1061 Number totalInterestPaid = 0;
1062 Number totalFeesPaid = 0;
1067 state.periodicPayment,
1069 state.paymentRemaining,
1072 auto validateBorrowerBalance = [&]() {
1073 if (borrower == issuer || !paymentParams.validateBalances)
1075 auto const totalSpent =
1080 borrowerInitialBalance - totalSpent);
1085 auto const p = places.value_or(defaultRound);
1088 auto const factor =
Number{1, p};
1089 return (n * factor).
truncate() / factor;
1091 while (state.paymentRemaining > 0)
1093 validateBorrowerBalance();
1100 state.principalOutstanding,
1101 state.managementFeeOutstanding,
1102 state.periodicPayment,
1104 state.paymentRemaining,
1108 paymentComponents.trackedValueDelta <= roundedPeriodicPayment ||
1110 paymentComponents.trackedValueDelta >= roundedPeriodicPayment));
1112 paymentComponents.trackedValueDelta ==
1113 paymentComponents.trackedPrincipalDelta + paymentComponents.trackedInterestPart() +
1114 paymentComponents.trackedManagementFeeDelta);
1118 state.periodicPayment,
1120 state.paymentRemaining - 1,
1127 deltas.
total() == state.periodicPayment ||
1128 (state.loanScale - (deltas.
total() - state.periodicPayment).exponent()) > 14);
1130 if (!showStepBalances)
1132 log << currencyLabel <<
" Payment components: " << state.paymentRemaining <<
", "
1135 <<
", " << paymentComponents.trackedValueDelta <<
", "
1136 << paymentComponents.trackedPrincipalDelta <<
", "
1137 << paymentComponents.trackedInterestPart() <<
", "
1138 << paymentComponents.trackedManagementFeeDelta <<
", " << [&]() ->
char const* {
1147 auto const totalDueAmount =
1148 STAmount{broker.
asset, paymentComponents.trackedValueDelta + serviceFee};
1150 if (paymentParams.validateBalances)
1158 Number const diff = totalDue - totalDueAmount;
1164 (state.loanScale - diff.
exponent() > 13))));
1167 paymentComponents.trackedPrincipalDelta >=
beast::kZero &&
1168 paymentComponents.trackedPrincipalDelta <= state.principalOutstanding);
1171 paymentComponents.trackedPrincipalDelta == state.principalOutstanding);
1174 auto const borrowerBalanceBeforePayment = env.
balance(borrower, broker.
asset);
1177 env(
pay(borrower, loanKeylet.
key, transactionAmount, paymentParams.flags));
1179 env.
close(d{state.paymentInterval / 2});
1181 if (paymentParams.validateBalances)
1187 adjustment = env.
current()->fees().base;
1194 borrowerBalanceBeforePayment,
1199 if (showStepBalances)
1201 auto const loanSle = env.
le(loanKeylet);
1202 if (!BEAST_EXPECT(loanSle))
1208 auto const errors = nextTrueState - current;
1209 log << currencyLabel <<
" Loan balances: "
1210 <<
"\n\tAmount taken: " << paymentComponents.trackedValueDelta
1211 <<
"\n\tTotal value: " << current.valueOutstanding
1213 <<
", error: " << truncate(errors.total())
1214 <<
")\n\tPrincipal: " << current.principalOutstanding
1216 <<
", error: " << truncate(errors.principal)
1217 <<
")\n\tInterest: " << current.interestDue
1218 <<
" (true: " << truncate(nextTrueState.
interestDue)
1219 <<
", error: " << truncate(errors.interest)
1220 <<
")\n\tMgmt fee: " << current.managementFeeDue
1222 <<
", error: " << truncate(errors.managementFee) <<
")\n\tPayments remaining "
1223 << loanSle->at(sfPaymentRemaining) <<
std::endl;
1225 currentRoundedState = current;
1228 --state.paymentRemaining;
1229 state.previousPaymentDate = state.nextPaymentDate;
1232 state.paymentRemaining = 0;
1233 state.nextPaymentDate = 0;
1237 state.nextPaymentDate += state.paymentInterval;
1239 state.principalOutstanding -= paymentComponents.trackedPrincipalDelta;
1240 state.managementFeeOutstanding -= paymentComponents.trackedManagementFeeDelta;
1241 state.totalValue -= paymentComponents.trackedValueDelta;
1243 if (paymentParams.validateBalances)
1244 verifyLoanStatus(state);
1249 totalInterestPaid += paymentComponents.trackedInterestPart();
1250 totalFeesPaid += serviceFee;
1251 ++totalPaymentsMade;
1253 currentTrueState = nextTrueState;
1255 validateBorrowerBalance();
1258 BEAST_EXPECT(state.paymentRemaining == 0);
1259 BEAST_EXPECT(state.principalOutstanding == 0);
1261 auto const initialInterestDue = initialState.totalValue -
1262 (initialState.principalOutstanding + initialState.managementFeeOutstanding);
1263 if (paymentParams.validateBalances)
1272 BEAST_EXPECT(totalInterestPaid == initialInterestDue);
1273 BEAST_EXPECT(totalPaymentsMade == initialState.paymentRemaining);
1276 if (showStepBalances)
1278 auto const loanSle = env.
le(loanKeylet);
1279 if (!BEAST_EXPECT(loanSle))
1284 log << currencyLabel <<
" Total amounts paid: "
1286 <<
" (initial: " << truncate(initialState.totalValue)
1287 <<
", error: " << truncate(initialState.totalValue - totalPaid.
trackedValueDelta)
1289 <<
" (initial: " << truncate(initialState.principalOutstanding) <<
", error: "
1291 <<
")\n\tInterest: " << totalInterestPaid
1292 <<
" (initial: " << truncate(initialInterestDue)
1293 <<
", error: " << truncate(initialInterestDue - totalInterestPaid)
1295 <<
" (initial: " << truncate(initialState.managementFeeOutstanding) <<
", error: "
1298 <<
")\n\tTotal payments made: " << totalPaymentsMade <<
std::endl;
1356 Number const& loanAmount,
1357 int interestExponent,
1369 auto const [
keylet, loanSequence] = [&]() {
1371 if (!BEAST_EXPECT(brokerSle))
1378 BEAST_EXPECT(brokerSle->at(sfOwnerCount) == 0);
1382 auto const loanSequence = brokerSle->at(sfLoanSequence);
1392 if (!BEAST_EXPECT(loanSequence != 0))
1395 testcase << caseLabel <<
" " << label;
1397 using namespace jtx;
1398 using namespace loan;
1399 using namespace std::chrono_literals;
1401 auto applyExponent = [interestExponent,
this](
TenthBips32 value)
mutable {
1403 while (interestExponent > 0)
1405 auto const oldValue = value;
1408 BEAST_EXPECT(value / 10 == oldValue);
1410 while (interestExponent < 0)
1412 auto const oldValue = value;
1415 BEAST_EXPECT(value * 10 == oldValue);
1420 auto const borrowerOwnerCount = env.
ownerCount(borrower);
1422 auto const loanSetFee = env.
current()->fees().base * 2;
1424 .account = borrower,
1426 .counterpartyExplicit =
false,
1427 .principalRequest = loanAmount,
1428 .setFee = loanSetFee,
1429 .originationFee = 1,
1446 auto const serviceFeeAmount = broker.
asset(*loanParams.
serviceFee).value();
1447 auto const lateFeeAmount = broker.
asset(*loanParams.
lateFee).value();
1448 auto const closeFeeAmount = broker.
asset(*loanParams.
closeFee).value();
1450 auto const borrowerStartbalance = env.
balance(borrower, broker.
asset);
1452 auto createJtx = loanParams(env, broker);
1458 auto const startDate = env.
current()->header().parentCloseTime.time_since_epoch().count();
1461 BEAST_EXPECT(brokerSle))
1463 BEAST_EXPECT(brokerSle->at(sfOwnerCount) == 1);
1471 adjustment = 2 * env.
current()->fees().base;
1476 borrowerStartbalance.
value() + principalRequestAmount - originationFeeAmount -
1477 adjustment.
value());
1480 auto const loanFlags =
1488 loan->isFlag(lsfLoanOverpayment) == createJtx.stx->isFlag(tfLoanOverpayment));
1489 BEAST_EXPECT(
loan->at(sfLoanSequence) == loanSequence);
1490 BEAST_EXPECT(
loan->at(sfBorrower) == borrower.
id());
1491 BEAST_EXPECT(
loan->at(sfLoanBrokerID) == broker.
brokerID);
1492 BEAST_EXPECT(
loan->at(sfLoanOriginationFee) == originationFeeAmount);
1493 BEAST_EXPECT(
loan->at(sfLoanServiceFee) == serviceFeeAmount);
1494 BEAST_EXPECT(
loan->at(sfLatePaymentFee) == lateFeeAmount);
1495 BEAST_EXPECT(
loan->at(sfClosePaymentFee) == closeFeeAmount);
1496 BEAST_EXPECT(
loan->at(sfOverpaymentFee) == *loanParams.
overFee);
1497 BEAST_EXPECT(
loan->at(sfInterestRate) == *loanParams.
interest);
1501 BEAST_EXPECT(
loan->at(sfStartDate) == startDate);
1503 BEAST_EXPECT(
loan->at(sfGracePeriod) == *loanParams.
gracePd);
1504 BEAST_EXPECT(
loan->at(sfPreviousPaymentDueDate) == 0);
1505 BEAST_EXPECT(
loan->at(sfNextPaymentDueDate) == startDate + *loanParams.
payInterval);
1506 BEAST_EXPECT(
loan->at(sfPaymentRemaining) == *loanParams.
payTotal);
1508 loan->at(sfLoanScale) >=
1512 BEAST_EXPECT(
loan->at(sfPrincipalOutstanding) == principalRequestAmount);
1520 state.principalOutstanding,
1522 state.paymentInterval,
1523 state.paymentRemaining,
1532 loanProperties.loanState.valueOutstanding,
1533 principalRequestAmount,
1534 loanProperties.loanState.managementFeeDue,
1535 loanProperties.periodicPayment,
1540 env(manage(lender,
keylet.key, 0));
1543 auto jt = manage(lender,
keylet.key, 0);
1544 jt.removeMember(sfFlags.getName());
1555 env(manage(lender,
keylet.key, tfLoanUnimpair | tfLoanImpair | tfLoanDefault),
1568 env(manage(lender,
keylet.key, tfLoanImpair),
1571 env(manage(lender,
keylet.key, tfLoanUnimpair),
1574 auto const nextDueDate = startDate + *loanParams.
payInterval;
1582 loanProperties.loanScale,
1583 loanProperties.loanState.valueOutstanding,
1584 principalRequestAmount,
1585 loanProperties.loanState.managementFeeDue,
1586 loanProperties.periodicPayment,
1592 if (BEAST_EXPECT(toEndOfLife))
1593 toEndOfLife(
keylet, verifyLoanStatus);
1599 BEAST_EXPECT(
loan->at(sfPaymentRemaining) == 0);
1600 BEAST_EXPECT(
loan->at(sfPrincipalOutstanding) == 0);
1602 auto const borrowerStartingBalance = env.
balance(borrower, broker.
asset);
1618 static unsigned kDeleteCounter = 0;
1619 auto const deleter = ((++kDeleteCounter % 2) != 0u) ? lender : borrower;
1620 env(del(deleter,
keylet.key));
1624 if (deleter == borrower)
1629 adjustment = env.
current()->fees().base;
1638 borrowerStartingBalance.
value() - adjustment);
1639 BEAST_EXPECT(env.
ownerCount(borrower) == borrowerOwnerCount);
1642 BEAST_EXPECT(brokerSle))
1644 BEAST_EXPECT(brokerSle->at(sfOwnerCount) == 0);
1676 Number const& loanAmount,
1677 int interestExponent)
1679 using namespace jtx;
1682 auto const& asset = broker.
asset.
raw();
1684 auto const caseLabel = [&]() {
1686 ss <<
"Lifecycle: " << loanAmount <<
" " << currencyLabel
1687 <<
" Scale interest to: " << interestExponent <<
" ";
1692 using namespace loan;
1693 using namespace std::chrono_literals;
1697 Account const issuer{
"issuer"};
1700 Account const lender{
"lender"};
1702 Account const borrower{
"borrower"};
1708 Number const principalRequest = broker.
asset(loanAmount).value();
1710 BEAST_EXPECT(maxCoveredLoanValue == 1000 * 100 / 10);
1711 Number const maxCoveredLoanRequest = broker.
asset(maxCoveredLoanValue).value();
1715 auto const loanSetFee =
Fee(env.
current()->fees().base * 2);
1719 auto const baseFee = env.
current()->fees().base;
1724 env(
set(evan, broker.
brokerID, principalRequest, tfLoanSetMask),
1725 Sig(sfCounterpartySignature, lender),
1732 Sig(sfCounterpartySignature, borrower),
1738 Sig(sfCounterpartySignature, lender),
1746 Sig(sfCounterpartySignature, borrower),
1747 kOverpaymentFee(kMaxOverpaymentFee),
1752 Sig(sfCounterpartySignature, lender),
1753 kOverpaymentFee(kMaxOverpaymentFee + 1),
1759 Sig(sfCounterpartySignature, borrower),
1760 kInterestRate(kMaxInterestRate),
1764 Sig(sfCounterpartySignature, borrower),
1770 Sig(sfCounterpartySignature, lender),
1771 kInterestRate(kMaxInterestRate + 1),
1776 Sig(sfCounterpartySignature, lender),
1783 Sig(sfCounterpartySignature, borrower),
1784 kLateInterestRate(kMaxLateInterestRate),
1788 Sig(sfCounterpartySignature, borrower),
1794 Sig(sfCounterpartySignature, lender),
1795 kLateInterestRate(kMaxLateInterestRate + 1),
1800 Sig(sfCounterpartySignature, lender),
1807 Sig(sfCounterpartySignature, borrower),
1808 kCloseInterestRate(kMaxCloseInterestRate),
1812 Sig(sfCounterpartySignature, borrower),
1818 Sig(sfCounterpartySignature, lender),
1819 kCloseInterestRate(kMaxCloseInterestRate + 1),
1823 Sig(sfCounterpartySignature, lender),
1830 Sig(sfCounterpartySignature, borrower),
1831 kOverpaymentInterestRate(kMaxOverpaymentInterestRate),
1835 Sig(sfCounterpartySignature, borrower),
1841 Sig(sfCounterpartySignature, lender),
1842 kOverpaymentInterestRate(kMaxOverpaymentInterestRate + 1),
1846 Sig(sfCounterpartySignature, lender),
1853 Sig(sfCounterpartySignature, borrower),
1859 Sig(sfCounterpartySignature, lender),
1866 Sig(sfCounterpartySignature, borrower),
1872 Sig(sfCounterpartySignature, lender),
1879 Sig(sfCounterpartySignature, borrower),
1886 Sig(sfCounterpartySignature, lender),
1893 env(
set(borrower, broker.
brokerID, principalRequest),
1894 Sig(sfCounterpartySignature, lender),
1897 env(
signers(lender, 2, {{evan, 1}, {borrower, 1}}));
1898 env(
signers(borrower, 2, {{evan, 1}, {lender, 1}}));
1899 env(
set(borrower, broker.
brokerID, principalRequest),
1900 kCounterparty(lender),
1902 Msig(sfCounterpartySignature, evan, borrower),
1906 env(
set(borrower, broker.
brokerID, principalRequest),
1907 kCounterparty(lender),
1908 Msig(alice, issuer),
1909 Msig(sfCounterpartySignature, evan, borrower),
1913 env(
set(borrower, broker.
brokerID, principalRequest),
1914 kCounterparty(lender),
1916 Msig(sfCounterpartySignature, alice, issuer),
1922 env(
set(borrower, broker.
brokerID, principalRequest),
1923 kCounterparty(lender),
1925 Msig(sfCounterpartySignature, evan, borrower),
1930 env(
set(borrower, broker.
brokerID, principalRequest),
1931 Sig(sfCounterpartySignature, evan),
1936 kCounterparty(borrower),
1937 Sig(sfCounterpartySignature, borrower),
1941 env(
set(lender, badKeylet.key, principalRequest),
1942 Sig(sfCounterpartySignature, evan),
1946 env(
set(lender, badKeylet.key, principalRequest),
1947 kCounterparty(borrower),
1948 Sig(sfCounterpartySignature, borrower),
1952 env(
set(lender, broker.
brokerID, principalRequest),
1953 kCounterparty(alice),
1954 Sig(sfCounterpartySignature, alice),
1959 env(
set(evan, broker.
brokerID, totalVaultRequest + 1),
1960 Sig(sfCounterpartySignature, lender),
1966 env(
set(evan, broker.
brokerID, maxCoveredLoanRequest + 1),
1967 Sig(sfCounterpartySignature, lender),
1976 if (!BEAST_EXPECT(brokerSle))
1979 auto const vaultPseudo = [&]() {
1980 auto const vaultSle = env.
le(
keylet::vault(brokerSle->at(sfVaultID)));
1981 if (!BEAST_EXPECT(vaultSle))
1986 auto vaultPseudo =
Account(
"Vault pseudo-account", vaultSle->at(sfAccount));
1990 auto const [freeze, deepfreeze, unfreeze, expectedResult] =
2005 auto freeze = [&](
Account const& holder) {
2008 auto deepfreeze = [&](
Account const& holder) {
2011 auto unfreeze = [&](
Account const& holder) {
2013 issuer, holder[
iouCurrency_](0), tfClearFreeze | tfClearDeepFreeze));
2018 auto freeze = [&](
Account const& holder) {
2019 mptt.
set({.account = issuer, .holder = holder, .flags = tfMPTLock});
2021 auto unfreeze = [&](
Account const& holder) {
2022 mptt.
set({.account = issuer, .holder = holder, .flags = tfMPTUnlock});
2030 for (
auto const& account : {vaultPseudo, evan})
2036 env(
set(evan, broker.
brokerID, debtMaximumRequest),
2037 Sig(sfCounterpartySignature, lender),
2039 Ter(expectedResult));
2042 BEAST_EXPECT(unfreeze);
2048 env(
set(evan, broker.
brokerID, debtMaximumRequest + 1),
2049 Sig(sfCounterpartySignature, lender),
2065 for (
auto const& account : {
2073 deepfreeze(account);
2076 env(
set(evan, broker.
brokerID, debtMaximumRequest),
2077 Sig(sfCounterpartySignature, lender),
2079 Ter(expectedResult));
2082 BEAST_EXPECT(unfreeze);
2088 env(
set(evan, broker.
brokerID, debtMaximumRequest + 1),
2089 Sig(sfCounterpartySignature, lender),
2098 auto coverAvailable = [&env,
this](
uint256 const& brokerID,
Number const& expected) {
2100 BEAST_EXPECT(brokerSle))
2102 auto const available = brokerSle->at(sfCoverAvailable);
2103 BEAST_EXPECT(available == expected);
2110 BEAST_EXPECT(brokerSle))
2117 broker.
vaultScale(env), state.principalOutstanding.exponent())));
2126 state.totalValue - state.managementFeeOutstanding),
2132 auto replenishCover = [&env, &coverAvailable](
2135 Number const& startingCoverAvailable,
2136 Number const& amountToBeCovered) {
2137 coverAvailable(broker.
brokerID, startingCoverAvailable - amountToBeCovered);
2140 coverAvailable(broker.
brokerID, startingCoverAvailable);
2144 auto defaultImmediately = [&](
std::uint32_t baseFlag,
bool impair =
true) {
2145 return [&, impair, baseFlag](
2152 auto state =
getCurrentState(env, broker, loanKeylet, verifyLoanStatus);
2153 BEAST_EXPECT(state.flags == baseFlag);
2155 auto const& broker = verifyLoanStatus.
broker;
2156 auto const startingCoverAvailable = coverAvailable(
2164 env(manage(lender, loanKeylet.key, tfLoanImpair),
2169 state.flags |= tfLoanImpair;
2175 verifyLoanStatus(state);
2178 auto const nextDueDate = tp{d{state.nextPaymentDate}};
2182 env(manage(lender, loanKeylet.key, tfLoanDefault),
Ter(
tecTOO_SOON));
2186 env.
close(nextDueDate + 60s);
2188 auto const [amountToBeCovered, brokerAcct] = getDefaultInfo(state, broker);
2191 env(manage(lender, loanKeylet.key, tfLoanDefault));
2196 replenishCover(broker, brokerAcct, startingCoverAvailable, amountToBeCovered);
2198 state.flags |= tfLoanDefault;
2199 state.paymentRemaining = 0;
2200 state.totalValue = 0;
2201 state.principalOutstanding = 0;
2202 state.managementFeeOutstanding = 0;
2203 state.nextPaymentDate = 0;
2204 verifyLoanStatus(state);
2214 auto singlePayment = [&](
Keylet const& loanKeylet,
2223 verifyLoanStatus(state);
2236 if (!(state.flags & lsfLoanOverpayment))
2245 bool const hasFix313 = env.
current()->rules().enabled(fixCleanup3_1_3);
2248 baseFee * (
Number{15, -1} / kLoanPaymentsPerFeeIncrement + 1)};
2249 env(
pay(borrower, loanKeylet.
key, overpayAmount, tfLoanOverpayment),
2256 env(
pay(borrower, loanKeylet.
key, overpayAmount, tfLoanOverpayment),
2267 broker.
asset(state.periodicPayment * 2),
2268 tfLoanLatePayment | tfLoanFullPayment),
2272 broker.
asset(state.periodicPayment * 2),
2273 tfLoanLatePayment | tfLoanOverpayment),
2277 broker.
asset(state.periodicPayment * 2),
2278 tfLoanOverpayment | tfLoanFullPayment),
2282 broker.
asset(state.periodicPayment * 2),
2283 tfLoanLatePayment | tfLoanOverpayment | tfLoanFullPayment),
2287 auto const otherAsset =
2288 broker.
asset.
raw() == assets[0].raw() ? assets[1] : assets[0];
2293 env(
pay(borrower, loanKeylet.
key,
STAmount{broker.asset, 1}, txFlags),
2298 auto const borrowerBalanceBeforePayment = env.
balance(borrower, broker.
asset);
2300 BEAST_EXPECT(payoffAmount > state.principalOutstanding);
2303 auto const transactionAmount = payoffAmount + broker.
asset(10);
2309 (borrowerBalanceBeforePayment.number() * 2 / state.periodicPayment /
2310 kLoanPaymentsPerFeeIncrement +
2314 STAmount{broker.asset, borrowerBalanceBeforePayment.number() * 2},
2319 XRPAmount const goodFee{baseFee * (numPayments / kLoanPaymentsPerFeeIncrement + 1)};
2320 env(
pay(borrower, loanKeylet.
key, transactionAmount, txFlags),
Fee(goodFee));
2330 adjustment = badFee + goodFee;
2333 state.paymentRemaining = 0;
2334 state.principalOutstanding = 0;
2335 state.totalValue = 0;
2336 state.managementFeeOutstanding = 0;
2337 state.previousPaymentDate =
2338 state.nextPaymentDate + (state.paymentInterval * (numPayments - 1));
2339 state.nextPaymentDate = 0;
2340 verifyLoanStatus(state);
2342 verifyLoanStatus.checkPayment(
2343 state.loanScale, borrower, borrowerBalanceBeforePayment, payoffAmount, adjustment);
2351 return [&, baseFlag](
2355 auto state =
getCurrentState(env, broker, loanKeylet, verifyLoanStatus);
2356 env.
close(state.startDate + 20s);
2357 auto const loanAge = (env.
now() - state.startDate).count();
2358 BEAST_EXPECT(loanAge == 30);
2368 Number const interval = state.paymentInterval;
2372 STAmount const principalOutstanding{broker.
asset, state.principalOutstanding};
2374 broker.
asset, state.principalOutstanding * periodicRate * loanAge / interval};
2375 BEAST_EXPECT(accruedInterest == broker.
asset(
Number(1141552511415525, -19)));
2377 broker.
asset, state.principalOutstanding *
Number(36, -3)};
2378 BEAST_EXPECT(prepaymentPenalty == broker.
asset(36));
2381 principalOutstanding + accruedInterest + prepaymentPenalty + closePaymentFee,
2394 state.paymentRemaining * (state.periodicPayment + broker.
asset(2).value()));
2413 auto state =
getCurrentState(env, broker, loanKeylet, verifyLoanStatus);
2422 auto const startingPayments = state.paymentRemaining;
2423 STAmount const payoffAmount = [&]() {
2425 auto const rawPayoff =
2426 startingPayments * (state.periodicPayment + broker.
asset(2).value());
2429 payoffAmount == broker.
asset(
Number(1024014840244721, -12)),
2431 BEAST_EXPECT(payoffAmount > state.principalOutstanding);
2433 payoffAmount =
roundToScale(payoffAmount, state.loanScale);
2435 return payoffAmount;
2438 auto const totalPayoffValue =
2439 state.totalValue + startingPayments * broker.
asset(2).value();
2440 STAmount const totalPayoffAmount{broker.
asset, totalPayoffValue};
2443 totalPayoffAmount == payoffAmount,
2444 "Payoff amount: " +
to_string(payoffAmount) +
2445 ". Total Value: " +
to_string(totalPayoffAmount));
2452 state.paymentRemaining,
2464 "Loan overpayment allowed - Impair and Default",
2474 defaultImmediately(lsfLoanOverpayment));
2478 "Loan overpayment prohibited - Impair and Default",
2488 defaultImmediately(0));
2492 "Loan overpayment allowed - Default without Impair",
2502 defaultImmediately(lsfLoanOverpayment,
false));
2506 "Loan overpayment prohibited - Default without Impair",
2516 defaultImmediately(0,
false));
2520 "Loan overpayment prohibited - Pay off immediately",
2534 "Loan overpayment allowed - Pay off immediately",
2544 fullPayment(lsfLoanOverpayment));
2548 "Loan overpayment prohibited - Combine all payments",
2558 combineAllPayments(0));
2562 "Loan overpayment allowed - Combine all payments",
2572 combineAllPayments(lsfLoanOverpayment));
2576 "Loan overpayment prohibited - Make payments",
2590 auto state =
getCurrentState(env, broker, loanKeylet, verifyLoanStatus);
2591 BEAST_EXPECT(state.flags == 0);
2594 verifyLoanStatus(state);
2596 env.
close(state.startDate + 20s);
2597 auto const loanAge = (env.
now() - state.startDate).count();
2598 BEAST_EXPECT(loanAge == 30);
2608 Number const interval = state.paymentInterval;
2612 STAmount const roundedPeriodicPayment{
2616 testcase << currencyLabel <<
" Payment components: "
2617 <<
"Payments remaining, rawInterest, rawPrincipal, "
2618 "rawMFee, trackedValueDelta, trackedPrincipalDelta, "
2619 "trackedInterestDelta, trackedMgmtFeeDelta, special";
2621 auto const serviceFee = broker.
asset(2);
2624 roundedPeriodicPayment ==
2633 roundedPeriodicPayment + serviceFee,
2649 state.periodicPayment,
2651 state.paymentRemaining,
2655 state.principalOutstanding,
2656 state.managementFeeOutstanding);
2657 testcase << currencyLabel <<
" Loan starting state: " << state.paymentRemaining
2658 <<
", " << raw.interestDue <<
", " << raw.principalOutstanding <<
", "
2659 << raw.managementFeeDue <<
", " << rounded.valueOutstanding <<
", "
2660 << rounded.principalOutstanding <<
", " << rounded.interestDue <<
", "
2661 << rounded.managementFeeDue;
2671 transactionAmount ==
2677 auto const initialState = state;
2679 .trackedValueDelta = 0,
2680 .trackedPrincipalDelta = 0,
2681 .trackedManagementFeeDelta = 0};
2682 Number totalInterestPaid = 0;
2687 state.periodicPayment,
2689 state.paymentRemaining,
2692 while (state.paymentRemaining > 0)
2700 state.principalOutstanding,
2701 state.managementFeeOutstanding,
2702 state.periodicPayment,
2704 state.paymentRemaining,
2709 paymentComponents.trackedValueDelta <= roundedPeriodicPayment,
2710 "Delta: " +
to_string(paymentComponents.trackedValueDelta) +
2711 ", periodic payment: " +
to_string(roundedPeriodicPayment));
2715 state.periodicPayment,
2717 state.paymentRemaining - 1,
2721 testcase << currencyLabel <<
" Payment components: " << state.paymentRemaining
2723 << deltas.
managementFee <<
", " << paymentComponents.trackedValueDelta
2724 <<
", " << paymentComponents.trackedPrincipalDelta <<
", "
2725 << paymentComponents.trackedInterestPart() <<
", "
2726 << paymentComponents.trackedManagementFeeDelta <<
", "
2727 << [&]() ->
char const* {
2728 if (paymentComponents.specialCase ==
2731 if (paymentComponents.specialCase ==
2737 auto const totalDueAmount =
STAmount{
2738 broker.
asset, paymentComponents.trackedValueDelta + serviceFee.number()};
2746 Number const diff = totalDue - totalDueAmount;
2752 (state.loanScale - diff.
exponent() > 13))));
2755 paymentComponents.trackedValueDelta ==
2756 paymentComponents.trackedPrincipalDelta +
2757 paymentComponents.trackedInterestPart() +
2758 paymentComponents.trackedManagementFeeDelta);
2761 paymentComponents.trackedValueDelta <= roundedPeriodicPayment);
2764 state.paymentRemaining < 12 ||
2776 paymentComponents.trackedPrincipalDelta >=
beast::kZero &&
2777 paymentComponents.trackedPrincipalDelta <= state.principalOutstanding);
2780 paymentComponents.trackedPrincipalDelta == state.principalOutstanding);
2783 (state.periodicPayment.exponent() -
2785 state.periodicPayment)
2788 auto const borrowerBalanceBeforePayment = env.
balance(borrower, broker.
asset);
2793 env(manage(lender, loanKeylet.
key, tfLoanImpair));
2799 env(
pay(borrower, loanKeylet.
key, transactionAmount));
2807 adjustment = env.
current()->fees().base;
2811 verifyLoanStatus.checkPayment(
2814 borrowerBalanceBeforePayment,
2818 --state.paymentRemaining;
2819 state.previousPaymentDate = state.nextPaymentDate;
2822 state.paymentRemaining = 0;
2823 state.nextPaymentDate = 0;
2827 state.nextPaymentDate += state.paymentInterval;
2829 state.principalOutstanding -= paymentComponents.trackedPrincipalDelta;
2830 state.managementFeeOutstanding -= paymentComponents.trackedManagementFeeDelta;
2831 state.totalValue -= paymentComponents.trackedValueDelta;
2833 verifyLoanStatus(state);
2838 paymentComponents.trackedManagementFeeDelta;
2839 totalInterestPaid += paymentComponents.trackedInterestPart();
2840 ++totalPaymentsMade;
2842 currentTrueState = nextTrueState;
2846 BEAST_EXPECT(state.paymentRemaining == 0);
2847 BEAST_EXPECT(state.principalOutstanding == 0);
2857 totalInterestPaid ==
2858 initialState.totalValue -
2859 (initialState.principalOutstanding +
2860 initialState.managementFeeOutstanding));
2861 BEAST_EXPECT(totalPaymentsMade == initialState.paymentRemaining);
2880 if (!BEAST_EXPECT(timed))
2886 auto const start = clock_type::now();
2911 using namespace loan;
2913 auto const state =
getCurrentState(env, broker, verifyLoanStatus.keylet);
2914 auto const serviceFee = broker.
asset(2).value();
2919 broker.
asset, state.periodicPayment + serviceFee, state.loanScale)};
2922 time(
"single payment", [&]() { env(
pay(borrower, loanKeylet.
key, totalDue)); });
2926 auto const numPayments = (state.paymentRemaining - 2);
2927 STAmount const bigPayment{broker.
asset, totalDue * numPayments};
2928 XRPAmount const bigFee{baseFee * (numPayments / kLoanPaymentsPerFeeIncrement + 1)};
2929 time(
"ten payments", [&]() {
2930 env(
pay(borrower, loanKeylet.
key, bigPayment),
Fee(bigFee));
2934 time(
"final payment", [&]() {
2936 env(
pay(borrower, loanKeylet.
key, totalDue +
STAmount{broker.asset, 1}));
2943 "Loan overpayment allowed - Explicit overpayment",
2957 "Loan overpayment prohibited - Late payment",
2971 "Loan overpayment allowed - Late payment",
2985 "Loan overpayment allowed - Late payment and overpayment",