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 abb05cc684
commit d0c4adf202

View File

@@ -510,6 +510,35 @@ protected:
.paymentInterval = loan->at(sfPaymentInterval), .paymentInterval = loan->at(sfPaymentInterval),
.interestRate = TenthBips32{loan->at(sfInterestRate)}, .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{}; return LoanState{};
} }
@@ -521,6 +550,7 @@ protected:
jtx::Env const& env, jtx::Env const& env,
BrokerInfo const& broker, BrokerInfo const& broker,
Keylet const& loanKeylet, Keylet const& loanKeylet,
Number const& loanAmount,
VerifyLoanStatus const& verifyLoanStatus) VerifyLoanStatus const& verifyLoanStatus)
{ {
using namespace std::chrono_literals; using namespace std::chrono_literals;
@@ -1308,10 +1338,10 @@ protected:
.counterpartyExplicit = false, .counterpartyExplicit = false,
.principalRequest = loanAmount, .principalRequest = loanAmount,
.setFee = loanSetFee, .setFee = loanSetFee,
.originationFee = 1, .originationFee = loanAmount * Number(1, -3),
.serviceFee = 2, .serviceFee = loanAmount * Number(2, -3),
.lateFee = 3, .lateFee = loanAmount * Number(3, -3),
.closeFee = 4, .closeFee = loanAmount * Number(4, -3),
.overFee = applyExponent(percentageToTenthBips(5) / 10), .overFee = applyExponent(percentageToTenthBips(5) / 10),
.interest = applyExponent(percentageToTenthBips(12)), .interest = applyExponent(percentageToTenthBips(12)),
// 2.4% // 2.4%
@@ -1434,7 +1464,8 @@ protected:
loan->at(sfPrincipalOutstanding) == principalRequestAmount); loan->at(sfPrincipalOutstanding) == principalRequestAmount);
} }
auto state = getCurrentState(env, broker, keylet, verifyLoanStatus); auto state =
getCurrentState(env, broker, keylet, loanAmount, verifyLoanStatus);
auto const loanProperties = computeLoanProperties( auto const loanProperties = computeLoanProperties(
broker.asset.raw(), broker.asset.raw(),
@@ -1607,8 +1638,8 @@ protected:
auto const currencyLabel = getCurrencyLabel(asset); auto const currencyLabel = getCurrencyLabel(asset);
auto const caseLabel = [&]() { auto const caseLabel = [&]() {
std::stringstream ss; std::stringstream ss;
ss << "Lifecycle: " << loanAmount << " " << currencyLabel ss << "Lifecycle: " << " " << currencyLabel << " Interest scale: "
<< " Scale interest to: " << interestExponent << " "; << Number(1, interestExponent) " Amount: " << loanAmount;
return ss.str(); return ss.str();
}(); }();
testcase << caseLabel; testcase << caseLabel;
@@ -2109,8 +2140,8 @@ protected:
// Default the loan // Default the loan
// Initialize values with the current state // Initialize values with the current state
auto state = auto state = getCurrentState(
getCurrentState(env, broker, loanKeylet, verifyLoanStatus); env, broker, loanKeylet, loanAmount, verifyLoanStatus);
BEAST_EXPECT(state.flags == baseFlag); BEAST_EXPECT(state.flags == baseFlag);
auto const& broker = verifyLoanStatus.broker; auto const& broker = verifyLoanStatus.broker;
@@ -2321,8 +2352,9 @@ protected:
VerifyLoanStatus const& verifyLoanStatus) { VerifyLoanStatus const& verifyLoanStatus) {
// toEndOfLife // toEndOfLife
// //
auto state = auto state = getCurrentState(
getCurrentState(env, broker, loanKeylet, verifyLoanStatus); env, broker, loanKeylet, loanAmount, verifyLoanStatus);
BEAST_EXPECT(state.flags == baseFlag);
env.close(state.startDate + 20s); env.close(state.startDate + 20s);
auto const loanAge = (env.now() - state.startDate).count(); auto const loanAge = (env.now() - state.startDate).count();
BEAST_EXPECT(loanAge == 30); BEAST_EXPECT(loanAge == 30);
@@ -2349,22 +2381,38 @@ protected:
interval}; interval};
BEAST_EXPECT( BEAST_EXPECT(
accruedInterest == accruedInterest ==
broker.asset(Number(1141552511415525, -19))); broker.asset(loanAmount * Number(1141552511415525, -22)));
STAmount const prepaymentPenalty{ STAmount const prepaymentPenalty{
broker.asset, state.principalOutstanding * Number(36, -3)}; broker.asset,
BEAST_EXPECT(prepaymentPenalty == broker.asset(36)); state.principalOutstanding *
STAmount const closePaymentFee = broker.asset(4); 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( auto const payoffAmount = roundToScale(
principalOutstanding + accruedInterest + prepaymentPenalty + principalOutstanding + accruedInterest + prepaymentPenalty +
closePaymentFee, closePaymentFee,
state.loanScale); state.loanScale);
// TODO: Figure out what's wrong with this calculation
// STAmount{broker.asset, state.principalOutstanding} +
// accruedInterest + prepaymentPenalty + closePaymentFee;
BEAST_EXPECT( BEAST_EXPECT(
payoffAmount == payoffAmount ==
roundToAsset( roundToAsset(
broker.asset, broker.asset,
broker.asset(Number(1040000114155251, -12)).number(), broker.asset(loanAmount * Number(1040000114155251, -15))
.number(),
state.loanScale)); state.loanScale));
// 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 // The terms of this loan actually make the early payoff
// more expensive than just making payments // more expensive than just making payments
BEAST_EXPECT( BEAST_EXPECT(
@@ -2548,8 +2596,8 @@ protected:
// toEndOfLife // toEndOfLife
// //
// Draw and make multiple payments // Draw and make multiple payments
auto state = auto state = getCurrentState(
getCurrentState(env, broker, loanKeylet, verifyLoanStatus); env, broker, loanKeylet, loanAmount, verifyLoanStatus);
BEAST_EXPECT(state.flags == 0); BEAST_EXPECT(state.flags == 0);
env.close(); env.close();
@@ -2661,6 +2709,21 @@ protected:
while (state.paymentRemaining > 0) 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 // Compute the expected principal amount
auto const paymentComponents = auto const paymentComponents =
detail::computePaymentComponents( detail::computePaymentComponents(
@@ -2744,7 +2807,7 @@ protected:
Number::upward) == Number::upward) ==
roundToScale( roundToScale(
broker.asset( broker.asset(
Number(8333228695260180, -14), loanAmount * Number(8333228695260180, -17),
Number::upward), Number::upward),
state.loanScale, state.loanScale,
Number::upward)); Number::upward));
@@ -2855,6 +2918,10 @@ protected:
ter(tecNO_PERMISSION)); ter(tecNO_PERMISSION));
env(manage(lender, loanKeylet.key, tfLoanDefault), env(manage(lender, loanKeylet.key, tfLoanDefault),
ter(tecNO_PERMISSION)); ter(tecNO_PERMISSION));
// Can't make a payment on it either
env(pay(borrower, loanKeylet.key, broker.asset(loanAmount)),
ter(tecKILLED));
}); });
#if LOANTODO #if LOANTODO
@@ -3597,10 +3664,13 @@ protected:
// Create and update Loans // Create and update Loans
for (auto const& broker : brokers) 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 amountExponent = 3; amountExponent >= -5;
for (int interestExponent = 0; interestExponent >= 0; amountExponent -= 4)
{
Number const loanAmount{amountMantissa, amountExponent};
for (int interestExponent = 1 - 1; interestExponent >= -2;
--interestExponent) --interestExponent)
{ {
testCaseWrapper( testCaseWrapper(
@@ -3612,6 +3682,7 @@ protected:
interestExponent); interestExponent);
} }
} }
}
if (auto brokerSle = env.le(keylet::loanbroker(broker.brokerID)); if (auto brokerSle = env.le(keylet::loanbroker(broker.brokerID));
BEAST_EXPECT(brokerSle)) BEAST_EXPECT(brokerSle))