Closed-ended Vault - #587
Conversation
|
| Directory | Self-Assigned Number |
|---|---|
XLS-0103-term-vault |
103 |
What to do
Please rename your directory to use the draft convention instead (e.g., XLS-draft-my-feature). An XLS number will be automatically assigned after a maintainer approves the PR.
This warning was automatically generated.
9af7400 to
30123ca
Compare
There was a problem hiding this comment.
🟡 Changes recommended
Critical specification conflicts and undefined state, amendment, and immutability behavior remain unresolved.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (4)
XLS-draft-closed-ended-vault/README.md:281
LoanAcceptis not defined by XLS-66: the parent requiresLoanSetto carryCounterpartySignatureand settle atomically (XLS-0066 README.md:1024-1048, 1510), and this section supplies neither fields nor state changes. As written, implementations cannot determine what pending state is being accepted or what this phase gate applies to. Define the two-step transaction/state in this XLS or link the companion amendment and specify the delta.
## 8. Transaction: `LoanAccept` (modified)
### 8.1. Fields
No changes.
XLS-draft-closed-ended-vault/README.md:262
- XLS-66 already assigns
tecKILLEDto a loan schedule that exceeds protocol time limits (§3.8.5.2, item 2). BecauseRedemptionDateis aUINT32, the new wide-arithmetic comparison is also true for those schedules, leaving two different results for the same input unless evaluation precedence is specified. State whether the inherited overflow failure runs first, or explicitly replace it, and limittecNO_PERMISSIONto representable schedules.
1. If the vault is closed-ended and `now <= SubscriptionDate` (`now` is the parent ledger close time), return `tecTOO_SOON`.
2. If the vault is closed-ended and `now >= RedemptionDate`, return `tecEXPIRED`.
3. If the vault is closed-ended and `StartDate + (PaymentInterval × PaymentTotal) + LOAN_REDEMPTION_BUFFER` is greater than `RedemptionDate`, where `StartDate` is `now` (XLS-66 sets a new loan's `StartDate` to the ledger close time), return `tecNO_PERMISSION`. Equivalently, the loan's final scheduled payment MUST fall at least `LOAN_REDEMPTION_BUFFER` seconds before `RedemptionDate`. The whole expression MUST be computed in arithmetic wide enough to hold it without overflow.
XLS-draft-closed-ended-vault/README.md:31
- The statement that existing open-ended vaults are “unaffected” conflicts with §9.2.1: creating a new
LoanBrokeragainst any open-ended vault is rejected, including an existing vault with no broker. Section 13 only grandfathers already-existing brokers. Scope this compatibility claim to the phase behavior/existing brokers, or document the new broker-creation restriction as an intentional incompatibility.
This proposal extends the [Single Asset Vault](../XLS-0065-single-asset-vault/README.md) (XLS-65) with a new `ClosedEnded` vault kind that enforces this lifecycle on-chain. Existing open-ended vaults are unaffected and continue to behave exactly as before.
XLS-draft-closed-ended-vault/README.md:62
- The table calls
946708560the maximum investment period, but the normative rule uses the strict inequalityRedemptionDate < SubscriptionDate + MAX_INVESTMENT_PERIODand the test plan rejects a gap equal toMAX_INVESTMENT_PERIOD(14.1). The largest accepted gap is therefore946708559seconds; clients treating the listed value as an inclusive maximum will submit a transaction the spec says is malformed.
| `MAX_INVESTMENT_PERIOD` | `946708560` | Maximum length, in seconds, of the Investment phase (30 Gregorian years of 365.2425 days). |
- Files reviewed: 1/1 changed files
- Comments generated: 4
- Review effort level: Lite
There was a problem hiding this comment.
🟡 Changes recommended
Several unresolved specification and documentation issues affect phase boundaries, amendment gating, tests, compatibility, and examples.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (6)
XLS-draft-closed-ended-vault/README.md:61
MAX_INVESTMENT_PERIODis used as an exclusive upper bound in the invariant and creation check (RedemptionDate < SubscriptionDate + MAX_INVESTMENT_PERIOD), and the test plan rejects a gap exactly equal to this value. Thus946708560is not the maximum permitted gap—the largest valid gap is one second smaller. Describing it as the “maximum length” is inconsistent and can lead an implementation to accept the rejected boundary; call it an exclusive upper bound or change the predicate if the 30-year boundary is intended to be valid.
| `MAX_INVESTMENT_PERIOD` | `946708560` | Maximum length, in seconds, of the Investment phase (30 Gregorian years of 365.2425 days). |
XLS-draft-closed-ended-vault/README.md:479
- This test case is not unconditional: because the loan's final scheduled payment is before
RedemptionDate, a payment submitted afterRedemptionDateis afterLoan.NextPaymentDueDate, and XLS-66's late-payment check rejects it unlesstfLoanLatePaymentis set and the amount covers the late-payment charges. Add those requirements to the test, or state only that the closed-ended phase does not add a rejection.
- A payment made after `RedemptionDate`, on a loan whose schedule ended before it, still succeeds and adds the proceeds to the vault. The phase does not block repayment (see A.5).
XLS-draft-closed-ended-vault/README.md:17
- The abstract says the vault leaves Subscription “at
SubscriptionDate,” but the normative phase table and transaction gates definenow == SubscriptionDateas Subscription (VaultDepositremains allowed andLoanSetremains rejected), with Investment beginning only whennow > SubscriptionDate. Since this exact boundary changes transaction outcomes, summarize the transition as occurring afterSubscriptionDateto avoid an off-by-one interpretation.
This proposal introduces a new **closed-ended** vault kind that moves through three deterministic phases - **Subscription**, **Investment**, and **Redemption** - and restricts deposits and withdrawals according to the current phase. It adds three fields to the `Vault` ledger entry (`VaultKind`, `SubscriptionDate`, `RedemptionDate`) plus phase enforcement in the vault and lending transactors. Both phase boundaries are _date-driven_ and immutable: a vault leaves Subscription for Investment at `SubscriptionDate` (after which new deposits are rejected and capital is locked), and leaves Investment for Redemption at `RedemptionDate`. Loans originated against a closed-ended vault must be scheduled to end a short buffer before `RedemptionDate`, so no payment is scheduled to fall due once Redemption has opened. Open-ended vaults are behaviourally unaffected.
XLS-draft-closed-ended-vault/README.md:288
- This condition needs the amendment gate. When
LendingProtocolV1_1is disabled, every existing vault is open-ended (VaultKindis absent), so the currentnot ClosedEndedtest rejects every newLoanBrokerSet, contradicting the statement in 2.3 that the parent behavior remains unchanged. Apply this rejection only whenLendingProtocolV1_1is enabled.
1. If `LoanBrokerID` is not specified (i.e. a new `LoanBroker` is being created) and the vault's `VaultKind` is not `ClosedEnded`, return `tecNO_PERMISSION`.
XLS-draft-closed-ended-vault/README.md:27
- Redemption is date-driven even when loans remain outstanding: 7.4 and A.5 explicitly allow a late loan/payment to continue past
RedemptionDate. Describing this phase as one where “the loan terms have ended” contradicts that behavior and can lead integrators to assume no loan obligations remain. Refer to the scheduled lending window instead.
- **Redemption** — the wind-down. The loan terms have ended, no new lending takes place, and depositors withdraw their share of whatever capital has been returned. Loans that were repaid late, or not at all, do not hold the phase open.
XLS-draft-closed-ended-vault/README.md:199
- These
VaultCreateexample dates are earlier than the current Ripple timestamp (about 811 million seconds on 2026-09-15), but 4.2.2 requires both dates to be strictly after the parent ledger close time. As written, copying this request cannot succeed; use future example values or explicitly identify a historical parent ledger.
"SubscriptionDate": 711232800,
"RedemptionDate": 721600800
- Files reviewed: 5/5 changed files
- Comments generated: 1
- Review effort level: Lite
Clarified the behavior of open-ended vaults and their interactions with new features and existing loan brokers. Emphasized that existing serialised vaults remain unchanged and outlined the conditions under which open-ended vaults operate. Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟡 Changes recommended
An unresolved critical activation-gate issue must be addressed before approval.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (2)
XLS-0065-single-asset-vault/65.1/README.md:27
- This new index row is plain text, so readers cannot navigate from XLS-65.1 to the closed-ended vault specification; the adjacent specification rows are links. Link this entry to the draft so the index exposes the normative document it describes.
| Closed-ended Vault | Adds the `ClosedEnded` vault kind with `SubscriptionDate` and `RedemptionDate`, phase-gates vault and lending transactions, and requires a closed-ended vault for new loan brokers. |
XLS-draft-closed-ended-vault/README.md:17
- This abstract says open-ended vaults are unaffected, but §8.2.1 changes
LoanBrokerSetunderLendingProtocolV1_1by rejecting creation of a new broker against an open-ended vault. Narrow this claim or mention that exception so readers do not infer that all existing open-ended use remains unchanged.
This proposal introduces a new **closed-ended** vault kind that moves through three deterministic phases - **Subscription**, **Investment**, and **Redemption** - and restricts deposits and withdrawals according to the current phase. It adds three fields to the `Vault` ledger entry (`VaultKind`, `SubscriptionDate`, `RedemptionDate`) plus phase enforcement in the vault and lending transactors. Both phase boundaries are _date-driven_ and immutable: a vault leaves Subscription for Investment at `SubscriptionDate` (after which new deposits are rejected and capital is locked), and leaves Investment for Redemption at `RedemptionDate`. Loans originated against a closed-ended vault must be scheduled to end a short buffer before `RedemptionDate`, so no payment is scheduled to fall due once Redemption has opened. Open-ended vaults are behaviourally unaffected.
- Files reviewed: 5/5 changed files
- Comments generated: 1
- Review effort level: Lite
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟡 Changes recommended
Four unresolved review findings remain in the closed-ended vault specification.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (3)
XLS-draft-closed-ended-vault/README.md:296
- This invariant is unconditional, but open-ended
LoanBrokerSetcreation is explicitly retained beforeLendingProtocolV1_1(see §2.3/§12 and XLS-66 §3.3.3). Enforcing 8.4 as written before activation would reject valid pre-amendment brokers, contradicting the stated backwards compatibility. Scope this invariant to the enabled-amendment behavior.
- No `LoanBroker` is created against a vault whose `VaultKind` is not `ClosedEnded`.
XLS-draft-closed-ended-vault/README.md:150
- The
VaultCreatetable repeats the same boundary ambiguity: §3.2 defines the timestamp as Subscription through equality, but "start of Investment phase" suggests Investment begins at equality. An implementer following this row could incorrectly reject a deposit or accept a loan atnow == SubscriptionDate; use the explicit last-second/strictly-after wording here as well.
| `SubscriptionDate` | Conditional | `number` | `UINT32` | `N/A` | **New.** End of Subscription / start of Investment phase. REQUIRED if `VaultKind == ClosedEnded`. Immutable after creation. |
XLS-draft-closed-ended-vault/README.md:272
- Because
fixCleanup3_4_0is explicitly not required, the legacy unimpairment behavior described here can moveNextPaymentDueDatebeyondRedemptionDateafter origination. That contradicts the abstract and rationale's guarantee that a closed-ended loan has no scheduled payment after Redemption opens. Preserve the maturity bound inLoanManage, require the cleanup amendment, or weaken the guarantee and update the related rationale/tests to make the check origination-only.
- No closed-ended `LoanSet` succeeds unless the loan's final scheduled payment is at least `LOAN_REDEMPTION_BUFFER` seconds before `RedemptionDate`. The bound is checked at origination only. `StartDate` and `PaymentInterval` are immutable, and `LoanPay` moves `NextPaymentDueDate` forward by exactly one `PaymentInterval` per scheduled payment, late or not, so repayment keeps the schedule in place. `LoanManage` is not phase-gated. Before `fixCleanup3_4_0`, XLS-66 unimpairment re-bases the due date of a loan that is already late to the current close time plus one `PaymentInterval`, which can push that loan's remaining schedule past `RedemptionDate`; impairment only ever moves a due date earlier, and from `fixCleanup3_4_0` onwards neither changes it. This proposal does not re-check the bound in that case. A loan that pays on time keeps its origination schedule, so the bound continues to hold for it, and a loan that is already late is the case 11.2 and A.5 already accept. The vault is unaffected either way: Redemption still opens on `RedemptionDate`, and a late payment is added when it arrives (see A.5).
- Files reviewed: 5/5 changed files
- Comments generated: 1
- Review effort level: Lite
There was a problem hiding this comment.
🔵 Needs a closer look
Two moderate findings remain unresolved around redemption liquidity semantics and LoanPay schedule behavior.
Review details
Suppressed comments (2)
XLS-draft-closed-ended-vault/README.md:542
- This describes redemption as cash-basis and says a depositor receives a pro-rata share of assets collected so far, but XLS-65 still computes a share redemption from
AssetsTotal - LossUnrealizedand rejects it whenAssetsAvailableis below that computed payout (seeXLS-0065-single-asset-vault/README.md:637, 668-681). If the vault has lent all of its cash, a late final payment can therefore make a full-share withdrawal fail until the payment arrives, rather than pay the depositor a smaller cash-based amount. Please reconcile this FAQ and the Redemption permission semantics with the inherited liquidity check, or specify the required change to the withdrawal algorithm.
Redemption begins unconditionally at `RedemptionDate`, and depositors may withdraw from that point on. Because the vault uses cash-basis accounting, a withdrawing depositor only ever receives a share of the assets the vault has actually collected at that moment, which excludes any payment that has not yet arrived. A payment that lands after `RedemptionDate` is simply added to the vault's assets when it is received; it is not accrued in advance.
XLS-draft-closed-ended-vault/README.md:272
XLS-66also automatically unimpairs an impaired loan at the start ofLoanPay(§3.11.5), using the same pre-fixCleanup3_4_0rebasing rule asLoanManage(§3.10.5). Therefore a successful payment can moveNextPaymentDueDateand the remaining schedule pastRedemptionDate; the claim here thatLoanPaypreserves the origination schedule, and the test plan's only exception viaLoanManage, are incomplete. State explicitly that the origination-only bound also permits thisLoanPaypath and cover it in the tests, or re-check/forbid it if that is not intended.
- No closed-ended `LoanSet` succeeds unless the loan's final scheduled payment is at least `LOAN_REDEMPTION_BUFFER` seconds before `RedemptionDate`. The bound is checked at origination only. `StartDate` and `PaymentInterval` are immutable, and `LoanPay` moves `NextPaymentDueDate` forward by exactly one `PaymentInterval` per scheduled payment, late or not, so repayment keeps the schedule in place. `LoanManage` is not phase-gated. Before `fixCleanup3_4_0`, XLS-66 unimpairment re-bases the due date of a loan that is already late to the current close time plus one `PaymentInterval`, which can push that loan's remaining schedule past `RedemptionDate`; impairment only ever moves a due date earlier, and from `fixCleanup3_4_0` onwards neither changes it. This proposal does not re-check the bound in that case. A loan that pays on time keeps its origination schedule, so the bound continues to hold for it, and a loan that is already late is the case 11.2 and A.5 already accept. The vault is unaffected either way: Redemption still opens on `RedemptionDate`, and a late payment is added when it arrives (see A.5).
- Files reviewed: 5/5 changed files
- Comments generated: 0 new
- Review effort level: Lite
## High Level Overview of Change <!-- Please include a summary/list of the changes. If too broad, please consider splitting into multiple PRs. If a relevant Asana task, please link it here. --> Support LendingProtocolV1_1 rippled: https://github.com/XRPLF/rippled/pulls?page=1&q=is%3Apr+label%3A%22feature%3A+LP1.1%22 Specs: XRPLF/XRPL-Standards#587 XRPLF/XRPL-Standards#582 ### Context of Change <!-- Please include the context of a change. If a bug fix, when was the bug introduced? What was the behavior? If a new feature, why was this architecture chosen? What were the alternatives? If a refactor, how is this better than the previous implementation? If there is a design document for this feature, please link it here. --> ### Type of Change <!-- Please check relevant options, delete irrelevant ones. --> - [ ] Bug fix (non-breaking change which fixes an issue) - [x] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) - [ ] Refactor (non-breaking change that only restructures code) - [ ] Tests (You added tests for code that already exists, or your new feature included in this PR) - [ ] Documentation Updates - [ ] Release ### Did you update HISTORY.md? - [x] Yes - [ ] No, this change does not impact library users
## High Level Overview of Change <!-- Please include a summary/list of the changes. If too broad, please consider splitting into multiple PRs. If a relevant Asana task, please link it here. --> Support LendingProtocolV1_1 rippled: https://github.com/XRPLF/rippled/pulls?page=1&q=is%3Apr+label%3A%22feature%3A+LP1.1%22 Specs: XRPLF/XRPL-Standards#587 XRPLF/XRPL-Standards#582 ### Type of Change <!-- Please check relevant options, delete irrelevant ones. --> - [ ] Bug fix (non-breaking change which fixes an issue) - [x] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) - [ ] Refactor (non-breaking change that only restructures code) - [ ] Tests (You added tests for code that already exists, or your new feature included in this PR) - [ ] Documentation Updates - [ ] Release ### Did you update CHANGELOG.md? - [x] Yes - [ ] No, this change does not impact library users
High Level Overview of Change
Context of Change
Type of Change