fix: Round vault deposit and withdraw conversions to prevent shareholder dilution#7824
Open
muhammadsalah wants to merge 1 commit into
Open
fix: Round vault deposit and withdraw conversions to prevent shareholder dilution#7824muhammadsalah wants to merge 1 commit into
muhammadsalah wants to merge 1 commit into
Conversation
|
If only the most recent commit is unsigned, you can run:
If multiple commits are unsigned, you can run:
If you're new to commit signing, there are different ways to set it up: Sign commits with
|
muhammadsalah
force-pushed
the
ci/fix-vault-deposit-rounding
branch
2 times, most recently
from
July 17, 2026 03:30
310dd72 to
81dbb1b
Compare
…der dilution Under the fixCleanup3_3_0 amendment, round the vault's share<->asset conversions in the pool's favor so that a single depositor or withdrawer can no longer dilute the other shareholders through round-to-nearest. Deposit: sharesToAssetsDeposit() rounds the assets charged for freshly minted shares Upward, so the depositor pays at least the fair value of those shares. Round-to-nearest could undercharge, overvaluing the new shares. VaultDeposit::doApply() passes Upward when the amendment is on. Withdraw: sharesToAssetsWithdraw() rounds the assets paid for burned shares Downward, so the withdrawer receives at most the fair value of those shares. Round-to-nearest could overpay, dropping the assets-per- share price for the remaining holders. VaultWithdraw::doApply() passes Downward on both the fixed-asset and fixed-share branches, and preclaim matches it so the withdrawal-limit check estimates the same amount. Both helpers take a rounding mode defaulting to ToNearest to preserve pre-amendment behavior; the quantization to the asset's precision honors the mode for integral (XRP/MPT) and IOU assets alike. Clawback is left unchanged. Direct unit tests on an MPT asset exercise each conversion under ToNearest vs the pool-favoring mode. This addresses catches found by the Common Prefix formal verification public project.
muhammadsalah
force-pushed
the
ci/fix-vault-deposit-rounding
branch
from
July 17, 2026 04:22
81dbb1b to
0d2ac58
Compare
muhammadsalah
marked this pull request as ready for review
July 17, 2026 04:37
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.
High Level Overview of Change
Under the
fixCleanup3_3_0amendment, the vault's share↔asset conversions are rounded in the pool's favor so that a single depositor or withdrawer can no longer dilute the other shareholders through round-to-nearest:sharesToAssetsDeposit()rounds the assets charged for freshly minted shares Upward, so the depositor pays at least the fair value of those shares.sharesToAssetsWithdraw()rounds the assets paid for burned shares Downward, so the withdrawer receives at most the fair value of those shares.Both helpers take a rounding-mode parameter defaulting to
ToNearest(pre-amendment behavior); the paired transactors pass the pool-favoring mode only when the amendment is enabled.Context of Change
Both defects predate this PR and are symmetric. The conversions computed
assetsTotal · shares / shareTotaland quantized to the asset's precision with the defaultToNearestrounding:Both are corrected only behind
fixCleanup3_3_0, so pre-amendment ledgers replay identically. Part of the ongoing vault/lending rounding cleanup (cf.fixCleanup3_2_0,fixCleanup3_1_3).Scope notes:
assetsToSharesDepositalready truncates (down) andassetsToSharesWithdrawneeds no change — flooring the withdraw payout alone guaranteesassetsWithdrawn ≤ fairValue(sharesBurned)regardless of how the shares were derived, so the "equal balance" invariant is preserved by construction (assetsWithdrawndrives both the vault decrement and the destination credit).sharesToAssetsWithdraware intentionally left onToNearest(different economic direction).VaultWithdraw::preclaimpasses the sameDownwardmode so the withdrawal-limit check estimates the same amount the apply will pay.Bug discovery — Common Prefix formal verification. These fixes address findings from Common Prefix's public formal verification of the XRPL single-asset vault / lending protocol. Their
testVaultDepositOvervaluedSharessnippet states the deposit finding directly:The withdraw payout corrected here is the exact symmetric case: round-to-nearest can overpay a withdrawer, diluting the holders who remain.
API Impact
libxrplchange —sharesToAssetsDeposit()andsharesToAssetsWithdraw()ininclude/xrpl/ledger/helpers/VaultHelpers.heach gain a defaultedNumber::RoundingModeparameter (source-compatible; the default preserves prior behavior).Transaction-processing behavior changes only when
fixCleanup3_3_0is enabled, so no peer-protocol/version impact.Before / After
Inexact rate, pool
100assets /3shares:ToNearest)33.33…33(33·3=99 < 100→ dilution)34Upward (102 ≥ 100)66.67…67(67·3=201 > 200→ dilution)66Downward (198 ≤ 200)Exact rates, and cases where
ToNearestalready rounds the safe way, are unchanged — the fix only ever moves the amount toward the pool.Test Plan
Direct unit tests of both conversion helpers (
src/test/app/Vault_test.cpp) build minimalltVAULT+ltMPTOKEN_ISSUANCESLEs and call the helper withToNearestvs the pool-favoring mode:testDepositRoundingFavorsPool— asserts the undercharge/dilution case (33vs34) and the invariantcharged · shareTotal ≥ assetsTotal · shares, plus no-op cases.testWithdrawRoundingFavorsPool— asserts the overpay/dilution case (67vs66) and the invariantpaid · shareTotal ≤ assetsTotal · shares, plus exact-rate and large awkward-rate cases.Both target MPT assets deliberately: for IOU the fair value fits within STAmount's 16-digit quantization so the rounding difference is sub-ULP and unobservable; on integral (MPT/XRP) assets a wrong rounding is a full unit.
Local regression run (Debug):
xrpl.app.Vault(409 cases / 11,358 tests),xrpl.tx.Loan(1,055 / 115,934 — the unrealized-loss withdraw path), andxrpl.app.Invariants(85 / 13,978) all pass with 0 failures.