From 5da586bffc272afb4dcac1a2d41b982503bc466f Mon Sep 17 00:00:00 2001 From: Ed Hennis Date: Tue, 21 Oct 2025 23:27:48 -0400 Subject: [PATCH] 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. --- include/xrpl/protocol/TxFlags.h | 8 ++- src/test/app/Loan_test.cpp | 78 +++++++++++++++++++++++++++++ src/xrpld/app/misc/LendingHelpers.h | 5 +- src/xrpld/app/tx/detail/LoanPay.cpp | 23 ++++++++- src/xrpld/app/tx/detail/LoanPay.h | 3 ++ 5 files changed, 111 insertions(+), 6 deletions(-) diff --git a/include/xrpl/protocol/TxFlags.h b/include/xrpl/protocol/TxFlags.h index 3f2ac2c1ce..f74039ccfd 100644 --- a/include/xrpl/protocol/TxFlags.h +++ b/include/xrpl/protocol/TxFlags.h @@ -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; diff --git a/src/test/app/Loan_test.cpp b/src/test/app/Loan_test.cpp index d5b1d6bb41..4617050388 100644 --- a/src/test/app/Loan_test.cpp +++ b/src/test/app/Loan_test.cpp @@ -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 diff --git a/src/xrpld/app/misc/LendingHelpers.h b/src/xrpld/app/misc/LendingHelpers.h index 4c7d073fc3..b843bfc344 100644 --- a/src/xrpld/app/misc/LendingHelpers.h +++ b/src/xrpld/app/misc/LendingHelpers.h @@ -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)}; diff --git a/src/xrpld/app/tx/detail/LoanPay.cpp b/src/xrpld/app/tx/detail/LoanPay.cpp index 8c36ddc9a6..54e79a2cb7 100644 --- a/src/xrpld/app/tx/detail/LoanPay.cpp +++ b/src/xrpld/app/tx/detail/LoanPay.cpp @@ -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 paymentParts = - loanMakePayment(asset, view, loanSle, brokerSle, amount, j_); + Expected paymentParts = loanMakePayment( + asset, + view, + loanSle, + brokerSle, + amount, + tx.isFlag(tfLoanOverpayment), + j_); if (!paymentParts) { diff --git a/src/xrpld/app/tx/detail/LoanPay.h b/src/xrpld/app/tx/detail/LoanPay.h index 11045f90da..f6255e5bc7 100644 --- a/src/xrpld/app/tx/detail/LoanPay.h +++ b/src/xrpld/app/tx/detail/LoanPay.h @@ -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);