Compute Loan unit test values dynamically

- Not quite working
This commit is contained in:
Ed Hennis
2025-07-14 20:28:31 -04:00
parent 7925cc4052
commit 822faa13e3

View File

@@ -476,6 +476,35 @@ class Loan_test : public beast::unit_test::suite
.paymentInterval = loan->at(sfPaymentInterval),
.interestRate = TenthBips32{loan->at(sfInterestRate)},
};
BEAST_EXPECT(state.previousPaymentDate == 0);
BEAST_EXPECT(
tp{d{state.nextPaymentDate}} == state.startDate + 600s);
BEAST_EXPECT(state.paymentRemaining == 12);
BEAST_EXPECT(
state.principalOutstanding == broker.asset(loanAmount).value());
BEAST_EXPECT(
state.loanScale ==
(broker.asset.integral()
? 0
: state.principalOutstanding.exponent()));
BEAST_EXPECT(state.paymentInterval == 600);
BEAST_EXPECT(
state.totalValue ==
roundToAsset(
broker.asset,
state.periodicPayment * state.paymentRemaining,
state.loanScale));
BEAST_EXPECT(
state.managementFeeOutstanding ==
computeFee(
broker.asset,
state.totalValue - state.principalOutstanding,
managementFeeRateParameter,
state.loanScale));
verifyLoanStatus(state);
return state;
}
return LoanState{};
}
@@ -487,6 +516,7 @@ class Loan_test : public beast::unit_test::suite
jtx::Env const& env,
BrokerInfo const& broker,
Keylet const& loanKeylet,
Number const& loanAmount,
VerifyLoanStatus const& verifyLoanStatus)
{
using namespace std::chrono_literals;
@@ -615,10 +645,12 @@ class Loan_test : public beast::unit_test::suite
auto const loanSetFee = env.current()->fees().base * 2;
Number const principalRequest = broker.asset(loanAmount).value();
auto const originationFee = broker.asset(1).value();
auto const serviceFee = broker.asset(2).value();
auto const lateFee = broker.asset(3).value();
auto const closeFee = broker.asset(4).value();
auto const originationFee =
broker.asset(loanAmount * Number(1, -3)).value();
auto const serviceFee =
broker.asset(loanAmount * Number(2, -3)).value();
auto const lateFee = broker.asset(loanAmount * Number(3, -3)).value();
auto const closeFee = broker.asset(loanAmount * Number(4, -3)).value();
auto applyExponent = [interestExponent,
this](TenthBips32 value) mutable {
@@ -764,7 +796,8 @@ class Loan_test : public beast::unit_test::suite
BEAST_EXPECT(loan->at(sfPrincipalOutstanding) == principalRequest);
}
auto state = getCurrentState(env, broker, keylet, verifyLoanStatus);
auto state =
getCurrentState(env, broker, keylet, loanAmount, verifyLoanStatus);
auto const loanProperties = computeLoanProperties(
broker.asset.raw(),
@@ -925,12 +958,13 @@ class Loan_test : public beast::unit_test::suite
auto const& asset = broker.asset.raw();
auto const caseLabel = [&]() {
std::stringstream ss;
ss << "Lifecycle: " << loanAmount << " "
ss << "Lifecycle: "
<< (asset.native() ? "XRP"
: asset.holds<Issue>() ? "IOU"
: asset.holds<MPTIssue>() ? "MPT"
: "Unknown")
<< " Scale interest to: " << interestExponent << " ";
<< " Amount: " << loanAmount
<< " Interest scale: " << Number(1, interestExponent);
return ss.str();
}();
testcase << caseLabel;
@@ -1425,8 +1459,8 @@ class Loan_test : public beast::unit_test::suite
// Default the loan
// Initialize values with the current state
auto state =
getCurrentState(env, broker, loanKeylet, verifyLoanStatus);
auto state = getCurrentState(
env, broker, loanKeylet, loanAmount, verifyLoanStatus);
BEAST_EXPECT(state.flags == baseFlag);
auto const& broker = verifyLoanStatus.broker;
@@ -1637,8 +1671,9 @@ class Loan_test : public beast::unit_test::suite
VerifyLoanStatus const& verifyLoanStatus) {
// toEndOfLife
//
auto state =
getCurrentState(env, broker, loanKeylet, verifyLoanStatus);
auto state = getCurrentState(
env, broker, loanKeylet, loanAmount, verifyLoanStatus);
BEAST_EXPECT(state.flags == baseFlag);
env.close(state.startDate + 20s);
auto const loanAge = (env.now() - state.startDate).count();
BEAST_EXPECT(loanAge == 30);
@@ -1665,18 +1700,32 @@ class Loan_test : public beast::unit_test::suite
interval};
BEAST_EXPECT(
accruedInterest ==
broker.asset(Number(1141552511415525, -19)));
broker.asset(loanAmount * Number(1141552511415525, -22)));
STAmount const prepaymentPenalty{
broker.asset, state.principalOutstanding * Number(36, -3)};
BEAST_EXPECT(prepaymentPenalty == broker.asset(36));
STAmount const closePaymentFee = broker.asset(4);
broker.asset,
state.principalOutstanding *
Number(36, interestExponent - 3)};
BEAST_EXPECT(
prepaymentPenalty ==
broker.asset(
loanAmount * Number(36, interestExponent - 3)));
STAmount const closePaymentFee =
broker.asset(loanAmount * Number(4, -3));
auto const payoffAmount = roundToScale(
principalOutstanding + accruedInterest + prepaymentPenalty +
closePaymentFee,
state.loanScale);
// TODO: Figure out what's wrong with this calculation
// STAmount{broker.asset, state.principalOutstanding} +
// accruedInterest + prepaymentPenalty + closePaymentFee;
BEAST_EXPECT(
payoffAmount ==
broker.asset(Number(1040000114155251, -12)));
broker.asset(loanAmount * Number(1040000114155251, -15)));
// Try to pay a little extra to show that it's _not_
// taken
auto const transactionAmount =
payoffAmount + broker.asset(loanAmount * Number(1, -2));
env(pay(borrower, loanKeylet.key, transactionAmount));
// The terms of this loan actually make the early payoff
// more expensive than just making payments
@@ -1861,8 +1910,8 @@ class Loan_test : public beast::unit_test::suite
// toEndOfLife
//
// Draw and make multiple payments
auto state =
getCurrentState(env, broker, loanKeylet, verifyLoanStatus);
auto state = getCurrentState(
env, broker, loanKeylet, loanAmount, verifyLoanStatus);
BEAST_EXPECT(state.flags == 0);
env.close();
@@ -1972,6 +2021,21 @@ class Loan_test : public beast::unit_test::suite
while (state.paymentRemaining > 0)
{
// Try to pay a little extra to show that it's _not_
// taken
STAmount const transactionAmount =
STAmount{broker.asset, totalDue} +
broker.asset(loanAmount * Number(1, -2));
// Only check the first payment since the rounding may
// drift as payments are made
BEAST_EXPECT(
transactionAmount ==
roundToScale(
broker.asset(
Number(9533457001162141, -14), Number::upward),
state.loanScale,
Number::upward));
// Compute the expected principal amount
auto const paymentComponents =
detail::computePaymentComponents(
@@ -2054,7 +2118,7 @@ class Loan_test : public beast::unit_test::suite
Number::upward) ==
roundToScale(
broker.asset(
Number(8333228695260180, -14),
loanAmount * Number(8333228695260180, -17),
Number::upward),
state.loanScale,
Number::upward));
@@ -2162,6 +2226,10 @@ class Loan_test : public beast::unit_test::suite
ter(tecNO_PERMISSION));
env(manage(lender, loanKeylet.key, tfLoanDefault),
ter(tecNO_PERMISSION));
// Can't make a payment on it either
env(pay(borrower, loanKeylet.key, broker.asset(loanAmount)),
ter(tecKILLED));
});
#if LOANCOMPLETE
@@ -2892,19 +2960,23 @@ class Loan_test : public beast::unit_test::suite
// Create and update Loans
for (auto const& broker : brokers)
{
for (int amountExponent = 3; amountExponent >= 3; --amountExponent)
for (int amountMantissa : {1, 3, 7})
{
Number const loanAmount{1, amountExponent};
for (int interestExponent = 0; interestExponent >= 0;
--interestExponent)
for (int amountExponent = 3; amountExponent >= -5;
amountExponent -= 4)
{
testCaseWrapper(
env,
mptt,
assets,
broker,
loanAmount,
interestExponent);
Number const loanAmount{amountMantissa, amountExponent};
for (int interestExponent = 1 - 1; interestExponent >= -2;
--interestExponent)
{
testCaseWrapper(
env,
mptt,
assets,
broker,
loanAmount,
interestExponent);
}
}
}