fix: Add 60s buffer before closed-ended vault redemption - #8151
Conversation
Loan origination could schedule a final payment at RedemptionDate, leaving no time to settle before the vault enters Redemption. Reject LoanSet unless the final payment is at least 60s before RedemptionDate, and raise the minimum Investment window to 180s so a min-interval loan can still fit.
clang-tidy misc-include-cleaner requires a direct include for LoanSet::kMinPaymentInterval.
There was a problem hiding this comment.
This MR introduces a kLoanRedemptionBuffer (60s) that closed-ended loans must clear before RedemptionDate, and raises kMinInvestmentPeriod from 60s to 180s so a minimum-interval loan can still fit. I traced the arithmetic in LoanSet::preclaim, ValidLoan::finalize, and the new static_assert, and it is internally consistent: preclaim rejects when finalPayment + kLoanRedemptionBuffer > RedemptionDate, ValidLoan mirrors the same bound, and the static_assert (kMinInvestmentPeriod >= kMinPaymentInterval + kLoanRedemptionBuffer + 1) correctly guarantees a minimum-interval loan starting strictly after SubscriptionDate can still clear the buffer within a minimum-length Investment window (121s required vs. 180s chosen, leaving headroom). The updated tests (LoanSet_test boundary case 5, new case 6, and InvariantsVault_test's buffer-zone synthetic loan) check out arithmetically against the new inequality. No correctness, security, or resource issues found in the changed lines; documentation/comments were updated consistently across all touched files.
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Pull request overview
This PR tightens closed-ended vault loan origination rules by requiring a 60-second safety buffer between a loan’s final scheduled payment and the vault’s RedemptionDate, and increases the minimum Investment window to ensure minimum-interval loans can still be originated early in Investment.
Changes:
- Add
kLoanRedemptionBufferand enforcefinalPayment + kLoanRedemptionBuffer <= RedemptionDateinLoanSetpreclaim andValidLoaninvariant checks. - Raise
kMinInvestmentPeriodfrom 60s to 180s and add a compile-time guard ensuring the minimum window can accommodate min payment interval + buffer. - Update and extend unit/invariant tests to cover the new boundary behavior and min-gap origination cases.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
src/libxrpl/tx/transactors/lending/LoanSet.cpp |
Enforces the new redemption buffer rule in LoanSet::preclaim and adds a static assertion tying Investment-window minimums to loan schedule minimums. |
src/libxrpl/tx/invariants/LoanInvariant.cpp |
Mirrors the buffer rule in ValidLoan to catch bypassed preclaim cases at invariant time. |
include/xrpl/protocol/Protocol.h |
Introduces kLoanRedemptionBuffer and increases kMinInvestmentPeriod to 180s (protocol constant change). |
include/xrpl/tx/invariants/VaultInvariant.h |
Updates invariant documentation to describe the buffered maturity bound. |
src/test/app/lending/LoanSet_test.cpp |
Adds boundary tests for the buffer rule and a min-gap Investment-window origination/regression case. |
src/test/app/lending/LoanTestBase.h |
Updates test-base documentation to reflect the buffered final-payment constraint. |
src/test/app/invariants/InvariantsVault_test.cpp |
Updates invariant tests to construct an “inside-buffer” loan and expect the updated invariant failure text. |
src/test/app/vault/VaultClosedEnded_test.cpp |
Updates comments and cross-references to reflect kMinInvestmentPeriod and the new buffer reasoning. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
a1q123456
left a comment
There was a problem hiding this comment.
Generally looks good. Just a small issue that copilot commented.
Set StartDate to RedemptionDate - kLoanRedemptionBuffer so interval 1 lands the final payment one second inside the buffer, matching the test comment.
There was a problem hiding this comment.
This MR introduces kLoanRedemptionBuffer (60s) and raises kMinInvestmentPeriod from 60s to 180s so a minimum-interval loan can still be originated inside the Investment window with the new buffer. I traced the arithmetic through LoanSet::preclaim, ValidLoan::finalize, and the new static_assert, and the boundary conditions are consistent: finalPayment + kLoanRedemptionBuffer > redemption is used uniformly in both the transactor and the invariant, and the constants satisfy kMinPaymentInterval + kLoanRedemptionBuffer + 1 <= kMinInvestmentPeriod as required. The accompanying tests (LoanSet_test boundary cases, InvariantsVault_test buffer-zone synthesis, VaultClosedEnded_test comment updates) match the new formula correctly (e.g., the accept/reject boundary tests land exactly on redemptionDate - buffer and redemptionDate - buffer + 1). No correctness, security, or consistency issues were found in the changed lines.
There was a problem hiding this comment.
This change consistently reworks the closed-ended loan/redemption boundary check from a strict '>=' comparison to a buffered '+ kLoanRedemptionBuffer >' comparison across LoanSet::preclaim and ValidLoan::finalize, and raises kMinInvestmentPeriod from 60s to 180s with a static_assert tying it to kMinPaymentInterval + kLoanRedemptionBuffer + 1. I traced the arithmetic in both production sites and in the updated tests (InvariantsVault_test.cpp, LoanSet_test.cpp boundary cases, and the new min-gap origination test in case 6) and the math checks out: the accept/reject boundaries line up exactly with the documented buffer semantics, and the two enforcement points (preclaim gate and invariant) mirror each other. No correctness, security, or consistency issues found in the changed lines.
There was a problem hiding this comment.
This is a well-scoped, internally consistent fix. The new kLoanRedemptionBuffer constant is applied identically in LoanSet::preclaim and ValidLoan::finalize (both use finalPayment + buffer > redemptionDate), the raised kMinInvestmentPeriod is backed by a compile-time static_assert that correctly accounts for StartDate being strictly after SubscriptionDate, and the boundary tests in LoanSet_test.cpp/InvariantsVault_test.cpp correctly exercise the new off-by-buffer edge (accept at exactly the buffer, reject one second inside it). I traced the arithmetic through all four production sites and didn't find a logic error or an inconsistency between the preclaim check and the invariant check.
Summary
Closed-ended LoanSet could originate a loan whose last scheduled payment lands on RedemptionDate. Once the vault enters Redemption, deposits are already closed and the remaining Investment window is gone, so there is no time left to collect that payment before the vault starts paying out LPs.
Reject LoanSet when the final payment is fewer than 60 seconds before RedemptionDate (
finalPayment + kLoanRedemptionBuffer > RedemptionDate). RaisekMinInvestmentPeriodfrom 60s to 180s so a minimum-interval loan can still fit: StartDate is strictly after SubscriptionDate,kMinPaymentIntervalis 60s, and the new 60s buffer must all sit inside the Investment window. ValidLoan mirrors the same bound.Test plan
LoanSet(closed-ended phase and maturity cases, including min-gap origination)InvariantsVault(buffer-zone loan creation)VaultClosedEnded(create-time 180s floor)