Skip to content

feat: Add a new closed ended vault to extend SAV - #7921

Merged
bthomee merged 21 commits into
developfrom
a1q123456/add-term-vault
Aug 12, 2026
Merged

feat: Add a new closed ended vault to extend SAV#7921
bthomee merged 21 commits into
developfrom
a1q123456/add-term-vault

Conversation

@a1q123456

@a1q123456 a1q123456 commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

High Level Overview of Change

Spec: XRPLF/XRPL-Standards#587

Introduces a new closed-ended vault kind that extends the Single Asset Vault (XLS-65) with a deterministic, date-driven lifecycle: Subscription → Investment → Redemption. The vault's phase is derived from the parent ledger close time and two immutable dates stored on the Vault ledger entry, and phase enforcement is added to the vault and lending transactors so capital is locked for the advertised term. Open-ended vaults are behaviourally unchanged.

Concretely, this PR:

  • Adds three fields to the Vault ledger entry and to VaultCreate: sfVaultKind (UINT8), sfSubscriptionDate (UINT32), sfRedemptionDate (UINT32). All are immutable after creation.
  • Adds two enums (VaultKind, VaultPhase) and two protocol constants (kMinInvestmentPeriod = 60s, kMaxInvestmentPeriod = 30 years) in include/xrpl/protocol/Protocol.h.
  • Adds VaultHelpers for phase derivation (getVaultPhase, getVaultKind) shared by transactors and invariants.
  • Enforces the phase permission matrix:
    • VaultDeposit: rejected outside Subscription (closed-ended) with tecNO_PERMISSION.
    • VaultWithdraw: rejected during Investment with tecTOO_SOON.
    • LoanSet / LoanAccept: permitted only during Investment; LoanSet additionally requires the loan's final scheduled payment to fall strictly before RedemptionDate.
  • Adds VaultInvariant and extends LoanInvariant to assert the closed-ended invariants (immutability of the three fields, MIN <= RedemptionDate - SubscriptionDate < MAX, per-transaction phase invariants, loan-maturity bound).
  • Extends vault_info and ledger_entry responses to include the three new fields for closed-ended vaults (omitted for open-ended vaults, which callers MUST interpret as VaultKind = OpenEnded).

Context of Change

This is a new feature gated behind an amendment; open-ended vaults are inert with respect to it. The design choice worth calling out is date-driven phases rather than a maintained LoanCount:

  • A LoanCount would couple the vault to individual loans and require every lending transactor to keep the counter symmetric across activation and resolution paths — a single missed increment or double decrement mis-signals the phase.
  • Two immutable dates make the phase a pure function of SubscriptionDate, RedemptionDate, and the parent ledger close time. The vault stays unaware of loans, the lifecycle is monotonic and deterministic, and depositors see the exact subscription window and lock-up term before they commit capital.

The trade-off — the Investment lock-up begins on a fixed calendar date rather than on actual capital deployment — is accepted as a more predictable contract for depositors. Rationale, alternatives, and FAQ are covered in §11 and Appendix A of the spec.

API Impact

  • Public API: New feature (new methods and/or new fields)
  • Public API: Breaking change (in general, breaking changes should only impact the next api_version)
  • libxrpl change (any change that may affect libxrpl or dependents of libxrpl)
  • Peer protocol change (must be backward compatible or bump the peer protocol version)

@a1q123456
a1q123456 force-pushed the a1q123456/add-term-vault branch from e23bf46 to c053476 Compare July 31, 2026 12:44
@a1q123456
a1q123456 force-pushed the a1q123456/add-term-vault branch from c053476 to c71682a Compare July 31, 2026 12:47
@a1q123456
a1q123456 marked this pull request as ready for review July 31, 2026 12:52
Comment thread src/libxrpl/tx/invariants/LoanInvariant.cpp Outdated
Comment thread src/libxrpl/tx/invariants/VaultInvariant.cpp
Comment thread src/test/app/Vault_test.cpp
Comment thread src/test/app/Vault_test.cpp Outdated
Comment thread src/test/app/Vault_test.cpp Outdated

@gregtatcam gregtatcam left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unit-tests coverage gaps:

  • add a doInvariantCheck that creates a closed-ended loan with final payment ≥ RedemptionDate and asserts the ValidLoan invariant fires
  • RedemptionDate < SubscriptionDate is untested. The invariant "reversed-date" case (Invariants_test.cpp:4503) uses red = sub + kMinInvestmentPeriod - 1 — that's red > sub (gap 59), exercising the gap-too-small branch, not the red <= sub guard its comment (:4501) claims. The strict red < sub case is absent at both the transactor and invariant level, so the unsigned-subtraction guard is never exercised. Add red < sub → temMALFORMED in the VaultCreate test and a matching invariant fire

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

This PR has conflicts, please resolve them in order for the PR to be reviewed.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a new closed-ended vault kind with a deterministic, date-driven lifecycle (Subscription → Investment → Redemption) and enforces phase-based permissions across vault and lending transactions, gated behind featureLendingProtocolV1_1.

Changes:

  • Introduces VaultKind / VaultPhase, protocol bounds (kMinInvestmentPeriod, kMaxInvestmentPeriod), and new Vault fields (sfVaultKind, sfSubscriptionDate, sfRedemptionDate) with immutability enforcement.
  • Adds shared phase/kind derivation (VaultHelpers) and applies phase gating in VaultDeposit, VaultWithdraw, and LoanSet (including loan maturity bound vs. RedemptionDate).
  • Expands invariants and test coverage for creation rules, phase transitions, permission matrix, maturity bounds, and RPC field visibility.

Reviewed changes

Copilot reviewed 22 out of 22 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/test/jtx/vault.h Extends JTx vault create args with kind/subscription/redemption fields.
src/test/jtx/impl/vault.cpp Emits new vault fields into VaultCreate JSON when provided.
src/test/app/Vault_test.cpp Adds closed-ended vault lifecycle/phase gating/RPC tests.
src/test/app/Loan_test.cpp Adds closed-ended vault setup path and LoanSet phase/maturity tests.
src/test/app/Invariants_test.cpp Adds invariant tests for closed-ended immutability and phase rules.
src/libxrpl/tx/transactors/vault/VaultWithdraw.cpp Enforces “no withdraw during Investment” for closed-ended vaults (amendment-gated).
src/libxrpl/tx/transactors/vault/VaultDeposit.cpp Enforces “deposit only during Subscription” for closed-ended vaults (amendment-gated).
src/libxrpl/tx/transactors/vault/VaultCreate.cpp Validates kind/date rules, enforces expiry, and writes new fields on creation.
src/libxrpl/tx/transactors/lending/LoanSet.cpp Adds closed-ended phase gating and maturity bound check vs. RedemptionDate.
src/libxrpl/tx/invariants/VaultInvariant.cpp Adds closed-ended create/phase invariants + LoanSet phase invariant hook.
src/libxrpl/tx/invariants/LoanInvariant.cpp Adds closed-ended maturity invariant on loan creation.
src/libxrpl/tx/invariants/InvariantCheck.cpp Enforces immutability of vault kind/dates via NoModifiedUnmodifiableFields.
src/libxrpl/ledger/View.cpp Extends hasExpired with inclusive/exclusive boundary behavior.
src/libxrpl/ledger/helpers/VaultHelpers.cpp Adds getVaultKind / getVaultPhase helpers.
include/xrpl/tx/invariants/VaultInvariant.h Documents and declares new closed-ended vault invariants and helpers.
include/xrpl/tx/invariants/LoanInvariant.h Documents new closed-ended maturity invariant.
include/xrpl/protocol/Protocol.h Adds enums and protocol constants for closed-ended vaults.
include/xrpl/protocol/detail/transactions.macro Adds new optional fields to ttVAULT_CREATE format.
include/xrpl/protocol/detail/sfields.macro Defines new SFields: sfVaultKind, sfSubscriptionDate, sfRedemptionDate.
include/xrpl/protocol/detail/ledger_entries.macro Adds new fields to ltVAULT schema (kind default, dates optional).
include/xrpl/ledger/View.h Declares ExpiryComparison and updates hasExpired signature/defaults.
include/xrpl/ledger/helpers/VaultHelpers.h Declares new Vault kind/phase helper APIs.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/test/app/Invariants_test.cpp

@xrplf-ai-reviewer xrplf-ai-reviewer Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Crash risk, invariant gating logic error, and integer overflow issues flagged inline.

Comment thread src/libxrpl/tx/invariants/VaultInvariant.cpp
Comment thread src/libxrpl/tx/invariants/InvariantCheck.cpp Outdated
Comment thread src/libxrpl/tx/transactors/lending/LoanSet.cpp Outdated
Comment thread src/libxrpl/tx/invariants/LoanInvariant.cpp Outdated

@Tapanito Tapanito left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you also, for now, remove references to the XLS please?

We are updating the XLS process for updating the specs, so there'll be some changes coming to it.

Comment thread include/xrpl/ledger/View.h Outdated
Comment thread src/libxrpl/ledger/helpers/VaultHelpers.cpp Outdated
Comment thread src/libxrpl/tx/invariants/InvariantCheck.cpp Outdated
Comment thread src/libxrpl/tx/invariants/LoanInvariant.cpp
Comment thread src/libxrpl/tx/invariants/InvariantCheck.cpp Outdated
Comment thread src/libxrpl/tx/transactors/vault/VaultCreate.cpp Outdated
Comment thread src/libxrpl/tx/transactors/vault/VaultCreate.cpp
Comment thread src/libxrpl/tx/transactors/vault/VaultCreate.cpp Outdated
Comment thread src/libxrpl/tx/transactors/vault/VaultDeposit.cpp Outdated
Comment thread src/test/app/Vault_test.cpp
@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown

All conflicts have been resolved. Assigned reviewers can now start or resume their review.

@xrplf-ai-reviewer xrplf-ai-reviewer Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Base vault invariant enforcement now amendment-gated; confirm if intentional—see inline.

Comment thread src/libxrpl/tx/invariants/InvariantCheck.cpp Outdated

@xrplf-ai-reviewer xrplf-ai-reviewer Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Vault invariant incorrectly gates core immutability checks on amendment — see inline.

Comment thread src/libxrpl/tx/invariants/InvariantCheck.cpp Outdated
@codecov

codecov Bot commented Aug 7, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 28 out of 28 changed files in this pull request and generated no new comments.

Suppressed comments (2)

src/libxrpl/tx/invariants/InvariantCheck.cpp:1195

  • XRPL_ASSERT(!bad || enforce, ...) will fire in debug builds if bad is detected while the amendment is not enforced. That contradicts the intent stated above (detection/logging even when not enforced) and can turn a would-be logged condition into a hard assertion failure.
        XRPL_ASSERT(
            !bad || enforce,
            "xrpl::NoModifiedUnmodifiableFields::finalize : no bad "
            "changes or enforce invariant");

src/libxrpl/tx/transactors/vault/VaultDeposit.cpp:83

  • The PR description says VaultDeposit should be rejected outside Subscription for closed-ended vaults with tecNO_PERMISSION, but this preclaim gate returns tecEXPIRED. This is an externally visible behavior difference (and tests in this PR also expect tecEXPIRED), so either the implementation or the documented permission matrix needs to be aligned.
    if (ctx.view.rules().enabled(featureLendingProtocolV1_1))
    {
        auto const phase = getVaultPhase(ctx.view, vault);
        if (phase == VaultPhase::Investment || phase == VaultPhase::Redemption)
        {
            JLOG(ctx.j.debug()) << "VaultDeposit: vault deposit is not allowed in the investment "
                                   "or redemption phase.";
            return tecEXPIRED;
        }

a1q123456 and others added 2 commits August 11, 2026 13:19
@github-actions

Copy link
Copy Markdown

All conflicts have been resolved. Assigned reviewers can now start or resume their review.

@xrplf-ai-reviewer xrplf-ai-reviewer Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate variable declaration breaks compilation.

Comment thread src/libxrpl/tx/invariants/LoanInvariant.cpp Outdated
@a1q123456 a1q123456 added the Ready to merge *PR author* thinks it's ready to merge. Has passed code review. Perf sign-off may still be required. label Aug 11, 2026

@xrplf-ai-reviewer xrplf-ai-reviewer Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feature gate misalignment on ltVAULT immutability - see inline.

Comment thread src/libxrpl/tx/invariants/InvariantCheck.cpp
Comment thread src/libxrpl/tx/transactors/lending/LoanSet.cpp

@xrplf-ai-reviewer xrplf-ai-reviewer Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a large, well-structured feature addition (closed-ended vaults) with extensive test coverage across invariants, transactors, and RPC. The phase-derivation logic (getVaultPhase/hasExpired boundary semantics), the gap validation, and the permission-matrix enforcement in VaultDeposit/VaultWithdraw/LoanSet all line up consistently with the documented boundary semantics (Subscription inclusive of now==SubscriptionDate, Investment strictly after, Redemption inclusive of now==RedemptionDate), and the redundant loan-maturity check duplicated in LoanSet::preclaim and LoanInvariant is a reasonable defense-in-depth. The main item worth a second look is the refactored NoModifiedUnmodifiableFields switch in InvariantCheck.cpp, where the new ltVAULT immutability gate is tied to featureLendingProtocolV1_1 while the shared enforce flag (used in the trailing XRPL_ASSERT) is tied only to the base featureLendingProtocol amendment — this silently assumes V1_1 amendments can never be enabled without the base amendment.

@kennyzlei kennyzlei added this to the Lending Protocol 1.1 milestone Aug 11, 2026
@a1q123456
a1q123456 removed the request for review from tyalymov August 11, 2026 17:20

@xrplf-ai-reviewer xrplf-ai-reviewer Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a large, well-structured feature addition (closed-ended vaults) with thorough test coverage across preflight/preclaim gating, invariants, and RPC. I traced the phase-derivation logic (VaultHelpers::getVaultPhase), the new immutability enforcement in NoModifiedUnmodifiableFields, and the new LoanSet/VaultDeposit/VaultWithdraw phase gates against the accompanying tests and didn't find correctness bugs in the changed lines — boundary semantics (inclusive Subscription, exclusive Investment start, inclusive Redemption start), the overflow-safe gap arithmetic, and the amendment-gating in preflight/preclaim/doApply are all internally consistent and exercised by the new tests. One design assumption in the new invariant code is worth a quick sanity check from the author (see below), but nothing here looks blocking.

@xrplf-ai-reviewer xrplf-ai-reviewer Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall this is a well-structured, thoroughly-tested implementation of the closed-ended vault lifecycle. Phase derivation (getVaultPhase), the SubscriptionDate/RedemptionDate gap validation, and the phase-gating added to VaultDeposit/VaultWithdraw/LoanSet are internally consistent with each other and with the mirrored invariant checks (ValidVault::finalizeLoanSet, LoanInvariant's maturity check). Boundary semantics (inclusive Subscription at now==SubscriptionDate, inclusive Redemption at now==RedemptionDate) are correctly implemented via the new ExpiryComparison parameter and validated by dedicated boundary tests. I found one discrepancy worth double-checking against the spec: VaultDeposit returns tecEXPIRED for phase violations, but the MR description states it should return tecNO_PERMISSION — worth confirming which is intended since external tooling built against the XLS-587 spec may depend on the exact code. I did not find correctness, security, or resource-management bugs in the added code.

@bthomee bthomee changed the title feat: Closed ended vault feat: Adds a new cllosed ended vault to extend SAV Aug 12, 2026
@bthomee bthomee changed the title feat: Adds a new cllosed ended vault to extend SAV feat: Add a new cllosed ended vault to extend SAV Aug 12, 2026
@bthomee bthomee changed the title feat: Add a new cllosed ended vault to extend SAV feat: Add a new closed ended vault to extend SAV Aug 12, 2026
@bthomee
bthomee added this pull request to the merge queue Aug 12, 2026
Merged via the queue into develop with commit 8e9b179 Aug 12, 2026
56 checks passed
@bthomee
bthomee deleted the a1q123456/add-term-vault branch August 12, 2026 17:43
@bthomee bthomee modified the milestones: Lending Protocol 1.1, 3.4.0 Aug 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Ready to merge *PR author* thinks it's ready to merge. Has passed code review. Perf sign-off may still be required.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants