feat: Add xrpl::vault_dust:: overlay, wire dispatchers, relax ValidVault - #8018
Open
Tapanito wants to merge 8 commits into
Open
feat: Add xrpl::vault_dust:: overlay, wire dispatchers, relax ValidVault#8018Tapanito wants to merge 8 commits into
Tapanito wants to merge 8 commits into
Conversation
5 tasks
Two protocol-level tests for the sfDust field: - Absent sfDust reads as zero on an untouched trust line (SoeDefault read path). - SoeDefault byte-encoding contract: absent and explicit-zero encodings are byte-identical, so a pre-amendment ledger and a post-amendment ledger stay hash-compatible for any RippleState that has never carried non-zero dust. Both tests use only SLE / trust-line primitives — no DustSplit and no vault_dust:: consumer (both land in follow-up PRs). Also picks up 'reserialise*' in cspell, used in the byte-compat test's docstring.
Introduces DustSplit, a per-leg dust policy struct threaded through directSendNoFeeIOU / accountSend / accountSendMulti / doWithdraw, plus the read-side creditBalanceExact helper. DustSplit models an IOU payment as two independent trust-line touches (sender leg, receiver leg). Each leg carries an optional LegPolicy in one of two modes: - Override: caller supplies the target STAmount scale; the credit path computes the extended balance (sfBalance + sfDust) in the leg's non-issuer party's terms, applies the delta, and re-splits at the requested scale. Any stranded sfDust above one quantum at the new scale is promoted into sfBalance as part of the same re-split, so scale-refinement is fold-in-place. - Drain (sender-leg only): folds all sender-line sfDust into the outgoing transfer, leaving sfBalance == 0 and sfDust == 0. Used for terminal removals. Reports back the observed per-leg deltas (balanceDelta and signed dustDelta) in the leg-party's own sign convention, letting the caller reconcile its own bookkeeping without re-reading the trust line. directSendNoFeeIOU is the single site where sfDust writes happen. Feature-gated: directSendNoFeeIOU asserts featureLendingProtocolV1_1 on any non-empty DustSplit; without the amendment the classic code path runs unchanged. creditBalanceExact reads the extended balance in the holder's terms; classic accountHolds remains STAmount-only. No consumers wired in yet; that lands in follow-ups.
Three trust-line-layer tests for the DustSplit primitive and its credit-path threading: - Ordinary payments (nullptr policy) never acquire sfDust — the feature-gated code path is inert without a caller-supplied DustSplit. - Plain accountSend across a line seeded with non-zero sfDust must preserve that reservoir byte-for-byte; the nullptr-policy path is dust-agnostic in both directions. - removeEmptyHolding returns tecHAS_OBLIGATIONS while sfDust != 0 and tesSUCCESS once sfDust is zero, pinning both sides of the deletion guard. Uses only the trust-line layer (Sandbox-level SLE mutation + accountSend/removeEmptyHolding). No vault_dust:: consumer is exercised here — that lands in the follow-up overlay PR.
Introduces the Vault-side consumer of DustSplit and its invariant adjustments, wired via a single eligibility gate. Overlay (xrpl::vault_dust::): - useVaultDust(view, vault): eligibility gate — featureLendingProtocolV1_1 + VaultVersion::CashBasis + !asset.integral(). Also the dispatcher used by the base xrpl::addVaultAssets / clawbackVaultAssets / removeVaultAssets / moveVaultAssets to route eligible vaults into vault_dust:: implementations. - addVaultAssets: receiver-leg Override at posteriorScale(vault, delta). - clawbackVaultAssets / non-terminal removeVaultAssets / moveVaultAssets: sender-leg Override at scale(sfAssetsTotal_post_op). - Terminal removeVaultAssets (FinalRemoval::Yes): sender-leg Drain, landing sfDust and sfBalance at zero on the vault's custody line. No trust-line writes anywhere in vault_dust:: code. Renormalisation is implicit: the credit path re-splits the extended balance at every accountSend under Override mode. Invariant relaxation: - ValidVault::DeltaInfo gains a signed dustDelta alongside delta. ttVAULT_WITHDRAW now compares the extended pseudo-account delta (delta + dustDelta) against the destination's extended delta, so a scale-refinement dust promotion (an internal move on the pseudo-account's line) doesn't spuriously fail "vault and destination balance must change by equal amount". - O2 "dust bounded by one quantum" relaxed to 10 x quantum(scale(sfAssetsTotal)) to accept up-to-one-decade drift under Override mode when the ex-ante scale estimate lands on a decade boundary; drift is caught on the next accountSend. Wiring: - LoanPay routes through the vault_dust:: overlay when useVaultDust is true. - VaultDeposit / VaultWithdraw pass the DustSplit through into the base helpers. Docs: - docs/dust-mechanism.md describes the model, DustSplit shape, Override vs Drain semantics, reporting sign convention, contract asserts, how to add a new consumer, amendment-gate policy, and the ValidVault sign-handling.
Adds the Vault-flow test suites that exercise the vault_dust:: overlay, the ValidVault relaxation, and the LoanPay / VaultHelpers wiring introduced in this PR. New suites: - VaultRoundingTrustlineDust_test: 12 dust-lifecycle tests driven through real transactions (deposits, loan payments, withdrawals, terminal drain, invariant regressions, multi-loan interleaving), guarded by a floor on dust-observing runs so the suite can't go silently vacuous. - VaultRounding_test: 16 Vault-facing rounding scenarios (O1-O8 identities, dust accumulation and promotion, scale-crossing, terminal drain, LoanManage::defaultLoan interaction, dust characterisation on the base branch). - VaultDustProbe: shared instrumentation used by both suites to read raw sfDust in the Vault's sign convention. Additional coverage: - VaultHelpers_test: new "moveVaultAssets: sender-leg Override renormalises, recipients dust-unaware" case for the overlay dispatcher. - LoanPay_test: new "broker fee debit shares the borrower's trust line with the vault debit" case for the LoanPay wiring. Also adds 'parameterise*' to .cspell.config.yaml, used across the new suites. Protocol-layer and DustSplit-primitive tests live earlier in the stack (RippleStateSfDust and DustSplitCreditPath suites).
Tapanito
force-pushed
the
tapanito/vault-dust-primitive
branch
from
August 13, 2026 08:31
7b55250 to
b67490a
Compare
Tapanito
force-pushed
the
tapanito/vault-dust-overlay
branch
from
August 13, 2026 08:31
1036e47 to
c4e80e7
Compare
This was referenced Aug 13, 2026
Tapanito
force-pushed
the
tapanito/vault-dust-primitive
branch
2 times, most recently
from
August 13, 2026 09:31
a629008 to
cc34b81
Compare
|
This PR has conflicts, please resolve them in order for the PR to be reviewed. |
Tapanito
force-pushed
the
tapanito/vault-dust-primitive
branch
from
August 13, 2026 12:13
cc34b81 to
81c9462
Compare
…-overlay # Conflicts: # include/xrpl/ledger/helpers/VaultHelpers.h # src/libxrpl/ledger/helpers/RippleStateHelpers.cpp # src/libxrpl/ledger/helpers/TokenHelpers.cpp # src/libxrpl/ledger/helpers/VaultHelpers.cpp # src/libxrpl/tx/transactors/lending/LoanPay.cpp
|
All conflicts have been resolved. Assigned reviewers can now start or resume their review. |
Replace the per-transactor `if (!useDust)` gate around
`associateAsset(*vaultSle, asset)` and the pre-truncation
`- split.receiver->dustDelta` compensation in the dust-aware
Vault writes with a metadata-driven exemption on sfAssetsTotal
itself. Under featureLendingProtocolV1_1, sfAssetsTotal now
retains full Number (19-digit) precision instead of being
truncated to STAmount (16-digit) precision at end of transactor,
so it can absorb the same sub-quantum residual that sfDust
absorbs on the custody line. The receivable invariant becomes
`sfAssetsTotal − (sfAssetsAvailable + custodyLine.sfDust) =
Σ PrincipalOutstanding`, which reduces to the classic form
pre-amendment (dust always zero) and holds byte-for-byte across
arbitrarily long dust-bearing sequences post-amendment.
Design (no signature changes, no plumbing at any of the ~19
associateAsset call sites):
- Introduce `SField::kSmdAssetPreLend11 = 0x100`, semantically
"swept only when featureLendingProtocolV1_1 is not enabled."
- Retag sfAssetsTotal from `kSmdNeedsAsset | kSmdDefault` to
`kSmdAssetPreLend11 | kSmdDefault`. sfAssetsAvailable,
sfAssetsMaximum, sfLossUnrealized keep kSmdNeedsAsset.
- `associateAsset(SLE&, Asset const&)` gate becomes the union
`kSmdNeedsAsset || (kSmdAssetPreLend11 &&
!isFeatureEnabled(featureLendingProtocolV1_1))`, read from the
thread-local CurrentTransactionRulesGuard. No rules installed
→ returns false → sweep runs (safe legacy behavior).
- Widen the Debug-only sanity assertion in
`STNumber::associateAsset` to accept either bit. The
serialisation-path check at STNumber::add remains keyed on
kSmdNeedsAsset only — that is what keeps sfAssetsTotal at full
precision on the wire post-amendment.
Consequences:
- addVaultAssets: `sfAssetsTotal += Number{valueDelta}` (no
`- dustDelta` compensation).
- reconcileSenderDust: only sfAssetsAvailable moves on a
sender-leg dust reshuffle; sfAssetsTotal is unchanged because
the underlying quantity on the custody line is unchanged.
- LoanPay: unconditional `associateAsset(*vaultSle, asset)`; the
20-line docblock explaining the local skip is gone.
- VaultInvariant: sfAssetsTotal delta comparisons in the
deposit / withdraw / clawback branches now use the extended
delta `delta + dustDelta`. Pre-amendment dustDelta is always
zero, so the check reduces to the classic form there.
Tests:
- New src/tests/libxrpl/protocol/STTakesAsset.cpp — 6 targeted
gtest cases covering the sweep for (kSmdNeedsAsset-only,
kSmdAssetPreLend11-only, both, neither) × (rules on, rules
off, no rules installed).
- Existing Vault/Loan suites re-baselined to the dust-inclusive
invariant via `readVaultDust()`; the shared VaultRounding_test
fixtures now degrade cleanly to the classic RED contract on
the base branch where readVaultDust() returns zero.
Tapanito
marked this pull request as ready for review
August 20, 2026 15:24
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Third and final slice of the Vault-dust custody design, split out from #7970. Wires the Vault as the sole consumer of
DustSplittoday, and relaxesValidVaultto accept the extended-balance semantics introduced by scale-refinement dust promotion.Also lands
docs/dust-mechanism.md, which describes the full mechanism (model,DustSplitshape,Override/Drainsemantics, reporting sign convention, contract asserts, how to add a new consumer, amendment-gate policy,ValidVaultsign-handling).Stack:
sfDustfield on RippleStatexrpl::vault_dust::overlay, dispatcher wiring,ValidVaultrelaxationEach slice ships its own tests. This PR ships the Vault-flow / invariant / integration tests.
Design
xrpl::vault_dust::overlayuseVaultDust(view, vault): eligibility gate —featureLendingProtocolV1_1+VaultVersion::CashBasis+!asset.integral(). Also the dispatcher used by the basexrpl::addVaultAssets/clawbackVaultAssets/removeVaultAssets/moveVaultAssetsto route eligible vaults through thevault_dust::implementations.addVaultAssets): receiver-legOverrideatposteriorScale(vault, valueDelta). Vault reconcilessfAssetsAvailable += split.receiver->balanceDelta,sfAssetsTotal += valueDelta - split.receiver->dustDelta.Overrideatscale(sfAssetsTotal_post_op). Vault reconciles the promoted amount (-split.sender->dustDelta) back intosfAssetsAvailableandsfAssetsTotal.FinalRemoval::Yes): sender-legDrain.sfDustandsfBalanceland at zero on the vault's custody line;sfAssetsTotalandsfAssetsAvailablehard-reset to zero.vault_dust::code. Renormalisation is implicit: the credit path re-splits the extended balance on everyaccountSendunderOverridemode.ValidVaultrelaxationDeltaInfogains a signeddustDeltaalongsidedelta.ttVAULT_WITHDRAWnow compares the extended pseudo-account delta (delta + dustDelta) against the destination's extended delta, so a scale-refinement dust promotion (an internal move on the pseudo-account's line) does not spuriously fail "vault and destination balance must change by equal amount".O2 dust bounded by one quantumrelaxed to10 × quantum(scale(sfAssetsTotal))to accept up-to-one-decade drift underOverridemode when the caller's ex-antescaleestimate lands on a decade boundary. Drift is caught on the nextaccountSend.Wiring
LoanPayroutes through thevault_dust::overlay whenuseVaultDustis true.VaultDeposit/VaultWithdrawpass theDustSplitthrough into the base helpers.Tests
Three suite additions and two augmentations, exercising the overlay, invariant, and wiring together:
src/test/app/lending/VaultRoundingTrustlineDust_test.cpp— 12 dust-lifecycle tests driven through real transactions (deposits, loan payments, withdrawals, terminal drain, invariant regressions, multi-loan interleaving), guarded by a floor on dust-observing runs so the suite can't go silently vacuous.src/test/app/lending/VaultRounding_test.cpp— 16 Vault-facing rounding scenarios (O1-O8 identities, dust accumulation and promotion, scale-crossing, terminal drain,LoanManage::defaultLoaninteraction).src/test/app/lending/VaultDustProbe.h— shared instrumentation used by both suites to read rawsfDustin the Vault's sign convention.src/test/app/VaultHelpers_test.cpp— new "moveVaultAssets: sender-leg Override renormalises, recipients dust-unaware" case for the overlay dispatcher.src/test/app/lending/LoanPay_test.cpp— new "broker fee debit shares the borrower's trust line with the vault debit" case for the LoanPay wiring.Protocol-layer and DustSplit-primitive tests live earlier in the stack (#8016
RippleStateSfDust_test, #8017DustSplitCreditPath_test).Extensibility
The
DustSplitstruct is consumer-agnostic — no Vault-specific fields, noPurposeenum. Enforcement of "Vault-only today" is layered:useVaultDust(view, vault)is the sole caller-side gate today; onlyvault_dust::code constructs aDustSplit.directSendNoFeeIOUassertsfeatureLendingProtocolV1_1on any non-emptyDustSplit.DustSplitfrom its transactor helpers, and reconciles its own bookkeeping. No trust-line-layer code changes required per new consumer.Test plan
VaultRoundingTrustlineDust_test,VaultRounding_test,VaultHelpers_test,LoanPay_test)featureLendingProtocolV1_1disabledNon-goals
DustSplit.RIPPLE_STATEfield-order changes.featureLendingProtocolV1_1).NaturalDustSplitmode (deferred).