Skip to content

fix: Check permissioned domain on private vault withdrawal - #7977

Open
tyalymov wants to merge 3 commits into
developfrom
tialymov/FN-69-withdraw_destination_domain_check
Open

fix: Check permissioned domain on private vault withdrawal#7977
tyalymov wants to merge 3 commits into
developfrom
tialymov/FN-69-withdraw_destination_domain_check

Conversation

@tyalymov

@tyalymov tyalymov commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

High Level Overview of Change

A withdrawal from a private vault to a third party now requires both the submitter
and the destination to be members of the vault's permissioned domain. Gated behind
fixCleanup3_4_0.

Context of Change

A private vault restricts who may take part in it, but VaultWithdraw only ever
checked that the destination was allowed to hold the underlying asset, through
requireAuth. There was no domain check anywhere in the withdrawal path, so a
participant could withdraw to an account the domain owner never admitted and the
funds left the domain. VaultDeposit already applies the equivalent check on the
way in; this closes the gap on the way out. The domain is read from the share
issuance, the same source VaultDeposit::preclaim uses.

Two cases deliberately skip the check:

  • Withdrawal to self is never restricted. Losing vault access must not strand
    funds that were already deposited, so redeeming your own shares stays available
    regardless of domain membership.
  • The asset issuer is always allowed to receive, which keeps the return path
    for frozen assets open. This exemption covers both parties rather than just the
    recipient: otherwise a submitter who had lost domain access could not return a
    frozen asset to its issuer, which is precisely the situation that path exists for.
    Reviewers may want to look closely at this one, it is the one judgement call in
    the change rather than a direct reading of the access-control rules.

Public vaults, VaultClawback and LoanBrokerCoverWithdraw are unaffected.
VaultClawback sends to the issuer through a separate path and does not use
sfDestination at all.

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)

Consensus-affecting: a withdrawal that succeeds today can return tecNO_AUTH once
fixCleanup3_4_0 is enabled. Behavior is unchanged while the amendment is off.

Test Plan

New test testVaultWithdrawPrivateDestinationDomain in src/test/app/Vault_test.cpp,
run twice, with and without fixCleanup3_4_0. Every account in it holds the asset,
so anything blocked is blocked by the domain check alone. Covered: destination inside
the domain; destination outside it; destination is the issuer; destination is the vault
owner; withdrawal to self with and without an explicit Destination; a submitter who
lost domain access withdrawing to self, to a domain member, and to the issuer; and a
public vault paying out to a non-member.

A private vault restricts who may take part in it, but VaultWithdraw only
ever checked that the destination was allowed to hold the underlying asset.
A participant could therefore withdraw to an account the domain owner never
admitted, and the funds left the domain.

Under fixCleanup3_4_0, a withdrawal from a private vault to a third party now
requires both the submitter and the destination to be members of the vault's
permissioned domain, read from the share issuance as VaultDeposit does.

Withdrawing to self is not checked, so that losing vault access cannot strand
funds already deposited. The asset issuer is always allowed to receive, which
keeps the return path for frozen assets open even for a submitter who lost
access. Public vaults, VaultClawback and LoanBrokerCoverWithdraw are unaffected.
@tyalymov
tyalymov requested review from Tapanito and a1q123456 and removed request for Tapanito August 7, 2026 17:43

@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.

The change adds a permissioned-domain check to VaultWithdraw::preclaim, gated behind fixCleanup3_4_0, mirroring VaultDeposit's existing domain check. The logic is sound: it correctly skips the check for self-withdrawals and for payouts to the asset issuer (with the exemption bypassing both parties' domain checks, as explicitly justified in the MR description for the frozen-asset-return case), reads the domain from the share issuance for consistency with VaultDeposit, and returns tecNO_AUTH when a private vault has no domain configured. The new test covers destination inside/outside the domain, the issuer, the vault owner, self-withdrawal (implicit and explicit), a submitter who lost domain access, and confirms public vaults are unaffected. I did not find correctness, security, or resource-management issues in the added code — no gaps in the pattern were found elsewhere in the diff (VaultClawback/LoanBrokerCoverWithdraw are explicitly and correctly out of scope per the description).

Comments now describe the rule rather than the gate that carries it: the
rules.enabled condition already tells the reader which amendment applies,
and naming it in prose only rots once the amendment activates.

The two domain checks are also spelled out instead of looping over an
initializer list of the two accounts. With a three-line body and exactly
two elements the loop saved nothing and asked the reader to think about
temporaries.

@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.

Solid, well-scoped change: adds a permissioned-domain check to VaultWithdraw on the way out, gated behind fixCleanup3_4_0, mirroring the existing VaultDeposit check. The self-withdrawal and issuer-recipient exemptions are implemented consistently with the description, and the new test covers the described matrix (domain member, non-member, issuer, owner, self, and a submitter who lost domain access) plus a public-vault control case. Only one edge case caught my eye and is called out below for confirmation rather than as a confirmed bug; everything else in the diff looks correct.

@codecov

codecov Bot commented Aug 7, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.85714% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/libxrpl/tx/transactors/vault/VaultWithdraw.cpp 92.9% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant