Add support for explicit overpayment flag in LoanPay

- Uses the same name and value as for LoanSet: tfLoanOverpayment.
- Untested.
- Also create several placeholders for missing test cases.
This commit is contained in:
Ed Hennis
2025-10-21 23:27:48 -04:00
parent 70ae693821
commit 5da586bffc
5 changed files with 111 additions and 6 deletions

View File

@@ -285,10 +285,14 @@ constexpr std::uint32_t tfIndependent = 0x00080000;
constexpr std::uint32_t const tfBatchMask =
~(tfUniversal | tfAllOrNothing | tfOnlyOne | tfUntilFailure | tfIndependent) | tfInnerBatchTxn;
// LoanSet flags:
// True, indicates the loan supports overpayments
// LoanSet and LoanPay flags:
// LoanSet: True, indicates the loan supports overpayments
// LoanPay: True, indicates any excess in this payment can be used
// as an overpayment. False, no overpayments will be taken.
constexpr std::uint32_t const tfLoanOverpayment = 0x00010000;
// Use two separate mask variables in case the set of flags diverges
constexpr std::uint32_t const tfLoanSetMask = ~(tfUniversal | tfLoanOverpayment);
constexpr std::uint32_t const tfLoanPayMask = ~(tfUniversal | tfLoanOverpayment);
// LoanManage flags:
constexpr std::uint32_t const tfLoanDefault = 0x00010000;

View File

@@ -1330,6 +1330,19 @@ class Loan_test : public beast::unit_test::suite
env(pay(evan, loanKeylet.key, broker.asset(500)),
ter(tecNO_PERMISSION));
// TODO: Write a general "isFlag" function? See STObject::isFlag.
// Maybe add a static overloaded member?
if (!(state.flags & lsfLoanOverpayment))
{
// If the loan does not allow overpayments, send a payment that
// tries to make an overpayment
env(pay(borrower,
loanKeylet.key,
broker.asset(state.periodicPayment * 2),
tfLoanOverpayment),
ter(temINVALID_FLAG));
}
{
auto const otherAsset = broker.asset.raw() == assets[0].raw()
? assets[1]
@@ -1845,6 +1858,71 @@ class Loan_test : public beast::unit_test::suite
env(manage(lender, loanKeylet.key, tfLoanDefault),
ter(tecNO_PERMISSION));
});
#if LOANCOMPLETE
// TODO
lifecycle(
caseLabel,
"Loan overpayment allowed - Explicit overpayment",
env,
loanAmount,
interestExponent,
lender,
borrower,
evan,
broker,
pseudoAcct,
tfLoanOverpayment,
[&](Keylet const& loanKeylet,
VerifyLoanStatus const& verifyLoanStatus) { throw 0; });
lifecycle(
caseLabel,
"Loan overpayment prohibited - Late payment",
env,
loanAmount,
interestExponent,
lender,
borrower,
evan,
broker,
pseudoAcct,
tfLoanOverpayment,
[&](Keylet const& loanKeylet,
VerifyLoanStatus const& verifyLoanStatus) { throw 0; });
lifecycle(
caseLabel,
"Loan overpayment allowed - Late payment",
env,
loanAmount,
interestExponent,
lender,
borrower,
evan,
broker,
pseudoAcct,
tfLoanOverpayment,
[&](Keylet const& loanKeylet,
VerifyLoanStatus const& verifyLoanStatus) { throw 0; });
lifecycle(
caseLabel,
"Loan overpayment allowed - Late payment and overpayment",
env,
loanAmount,
interestExponent,
lender,
borrower,
evan,
broker,
pseudoAcct,
tfLoanOverpayment,
[&](Keylet const& loanKeylet,
VerifyLoanStatus const& verifyLoanStatus) { throw 0; });
#endif
}
void

View File

@@ -1410,6 +1410,7 @@ loanMakePayment(
SLE::ref loan,
SLE::const_ref brokerSle,
STAmount const& amount,
bool const overpaymentAllowed,
beast::Journal j)
{
/*
@@ -1648,8 +1649,8 @@ loanMakePayment(
// -------------------------------------------------------------
// overpayment handling
if (loan->isFlag(lsfLoanOverpayment) && paymentRemainingProxy > 0 &&
nextDueDateProxy && totalPaid < amount)
if (overpaymentAllowed && loan->isFlag(lsfLoanOverpayment) &&
paymentRemainingProxy > 0 && nextDueDateProxy && totalPaid < amount)
{
TenthBips32 const overpaymentInterestRate{
loan->at(sfOverpaymentInterestRate)};

View File

@@ -30,6 +30,12 @@ LoanPay::checkExtraFeatures(PreflightContext const& ctx)
return checkLendingProtocolDependencies(ctx);
}
std::uint32_t
LoanPay::getFlagsMask(PreflightContext const& ctx)
{
return tfLoanPayMask;
}
NotTEC
LoanPay::preflight(PreflightContext const& ctx)
{
@@ -139,6 +145,13 @@ LoanPay::preclaim(PreclaimContext const& ctx)
return tecNO_PERMISSION;
}
if (tx.isFlag(tfLoanOverpayment) && !loanSle->isFlag(lsfLoanOverpayment))
{
JLOG(ctx.j.warn())
<< "Requested overpayment on a loan that doesn't allow it";
return temINVALID_FLAG;
}
auto const principalOutstanding = loanSle->at(sfPrincipalOutstanding);
TenthBips32 const interestRate{loanSle->at(sfInterestRate)};
auto const paymentRemaining = loanSle->at(sfPaymentRemaining);
@@ -267,8 +280,14 @@ LoanPay::doApply()
LoanManage::unimpairLoan(view, loanSle, vaultSle, j_);
}
Expected<LoanPaymentParts, TER> paymentParts =
loanMakePayment(asset, view, loanSle, brokerSle, amount, j_);
Expected<LoanPaymentParts, TER> paymentParts = loanMakePayment(
asset,
view,
loanSle,
brokerSle,
amount,
tx.isFlag(tfLoanOverpayment),
j_);
if (!paymentParts)
{

View File

@@ -36,6 +36,9 @@ public:
static bool
checkExtraFeatures(PreflightContext const& ctx);
static std::uint32_t
getFlagsMask(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);