Skip to content

fix: Disable AMM creation with Vault shares#7666

Open
Tapanito wants to merge 4 commits into
developfrom
tapanito/smelly-vault-test
Open

fix: Disable AMM creation with Vault shares#7666
Tapanito wants to merge 4 commits into
developfrom
tapanito/smelly-vault-test

Conversation

@Tapanito

@Tapanito Tapanito commented Jun 29, 2026

Copy link
Copy Markdown
Collaborator

Disallow Vault Shares as an AMM asset

Summary

Vault shares (MPTs issued by a vault pseudo-account) must never be held inside another pseudo-account-backed object. Vaults already reject pseudo-account-issued assets at creation (VaultCreate), and LoanBrokers inherit this through the vault they wrap — but AMMs had no such guard, allowing a pool to be created with vault shares.

This led to a tangle of special-case freeze handling: the deposit/withdraw freeze helpers and the ValidMPTTransfer invariant carried a fixCleanup3_3_0-gated "transitive vault freeze" path to cope with frozen vault shares sitting inside an AMM.

This PR closes the gap at the source — vault shares are rejected at AMM creation — and removes the now-unreachable special casing, replacing it with defensive guards.

Changes

  • AMMCreate: reject AMM creation when either asset is a vault share, returning tecWRONG_ASSET (consistent with VaultCreate). Gated behind featureSingleAssetVault. Note, it does not require gating behind MPTV2 amendment, as it is not yet supported, and without it an MPT AMM cannot be created.
  • TokenHelpers (checkDepositFreeze / checkWithdrawFreeze): the transitive vault-share freeze branch is now unreachable for every caller (AMM, Vault, LoanBroker deposit/withdraw all operate on non-share assets). It is reduced to an UNREACHABLE + tecINTERNAL guard, and a new XRPL_ASSERT documents and enforces that the asset issuer is never a pseudo-account. Renamed the ambiguous srcAcct/dstAcct parameters to pseudoAcct and updated the Doxygen.
  • MPTInvariant (ValidMPTTransfer): removed the fixCleanup3_3_0/legacyAccountFrozen branch and the ttAMM_WITHDRAW receiver exemption. The check now unconditionally uses isFrozen(). This is behavior-preserving in all reachable states — for non-share MPTs isFrozen() already equals the legacy path, and the only case they differed (vault shares inside an AMM during ttAMM_WITHDRAW) can no longer occur.
  • MPTokenHelpers.h: added a doc comment on isIndividualFrozen clarifying it does not perform the transitive vault check, and directing callers to isFrozen.
  • Tests:
    • AMMMPT_test: testAMMWithVaultShares rewritten to assert AMM creation with vault shares fails with tecWRONG_ASSET.
    • Invariants_test: vault-share freeze cases reworked to drive the invariant directly (no longer rely on the now-impossible AMM-with-shares setup); pre-/post-fixCleanup3_3_0 variants dropped since behavior no longer branches on the amendment.
    • Vault_test: split testVaultDepositFreeze/testVaultWithdrawFreeze into per-asset (...IOU / ...MPT) cases with individual testcase labels for clearer failure reporting.

Amendment / consensus safety

The AMM rejection is gated behind featureMPTokensV2.

Test plan

  • Invariants, AMMMPT, and Vault suites pass
  • Full unit test run is green

@Tapanito Tapanito requested a review from shawnxie999 June 29, 2026 19:25
@Tapanito Tapanito removed the request for review from PeterChen13579 June 29, 2026 19:27
@Tapanito Tapanito added this to the 3.3.0 milestone Jun 29, 2026
@Tapanito Tapanito requested a review from PeterChen13579 June 29, 2026 19:27

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

Clean fix — no concerns.

Review by Claude Sonnet 4.6 · Prompt: V15

@Tapanito Tapanito requested review from a1q123456 and removed request for Kassaking7 and PeterChen13579 June 29, 2026 19:37
@codecov

codecov Bot commented Jun 29, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 82.3%. Comparing base (809a629) to head (e9d8f6d).
⚠️ Report is 1 commits behind head on develop.

Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff           @@
##           develop   #7666   +/-   ##
=======================================
  Coverage     82.3%   82.3%           
=======================================
  Files         1024    1024           
  Lines        78389   78394    +5     
  Branches      8973    8971    -2     
=======================================
+ Hits         64503   64510    +7     
+ Misses       13877   13875    -2     
  Partials         9       9           
Files with missing lines Coverage Δ
src/libxrpl/ledger/helpers/TokenHelpers.cpp 95.0% <100.0%> (+0.4%) ⬆️
src/libxrpl/tx/invariants/MPTInvariant.cpp 89.8% <100.0%> (-0.2%) ⬇️
src/libxrpl/tx/transactors/dex/AMMCreate.cpp 89.0% <100.0%> (+0.1%) ⬆️

... and 4 files with indirect coverage changes

Impacted file tree graph

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment on lines +195 to +196
if (asset.holds<MPTIssue>() &&
isVaultPseudoAccountFrozen(view, pseudoAcct, asset.get<MPTIssue>(), 0))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

unreachable code should probably moved as the last check, to allow necessary checks to be done earlier

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done.

Comment thread src/libxrpl/tx/transactors/dex/AMMCreate.cpp Outdated
Comment thread src/libxrpl/tx/transactors/dex/AMMCreate.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.

Gave this a review

Two UNREACHABLE-wrapped freeze checks in checkWithdrawFreeze and checkDepositFreeze create a silent bypass if the "no vault shares in pseudo-account-backed objects" invariant is ever broken in production — see inline comments.


Review by ReviewBot 🤖

Review by Claude Sonnet 4.6 · Prompt: V15

Comment thread src/libxrpl/ledger/helpers/TokenHelpers.cpp
Comment thread src/libxrpl/ledger/helpers/TokenHelpers.cpp

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

Disallows using Vault share MPTs (issued by Vault pseudo-accounts) as AMM pool assets, and removes now-unreachable “transitive vault freeze” special-casing by simplifying freeze/invariant handling and updating tests accordingly.

Changes:

  • AMMCreate now rejects AMM creation when either asset is a pseudo-account–issued MPT (intended to cover Vault shares), returning tecWRONG_ASSET.
  • Freeze helpers and the MPT transfer invariant are simplified to rely on the unified isFrozen() logic; legacy branching and AMM-with-shares special cases are removed and replaced with defensive guards/assertions.
  • Test suites are updated to reflect the new behavior (AMM-with-shares creation failure) and to exercise invariants/freeze scenarios directly without constructing impossible states.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/test/app/Vault_test.cpp Refactors/splits Vault freeze tests into IOU vs MPT cases and updates expectations/case labeling.
src/test/app/Invariants_test.cpp Reworks vault-share invariant tests to drive invariant checks directly; removes legacy AMM-with-shares scenario reliance.
src/test/app/AMMMPT_test.cpp Updates AMM+vault-shares test to assert AMM creation fails with tecWRONG_ASSET.
src/libxrpl/tx/transactors/dex/AMMCreate.cpp Adds preclaim rejection for pseudo-account–issued MPT assets (Vault shares) when creating an AMM.
src/libxrpl/tx/invariants/MPTInvariant.cpp Removes legacy fixCleanup3_3_0 branching and unconditionally uses isFrozen() for transfer validity checks.
src/libxrpl/ledger/helpers/TokenHelpers.cpp Simplifies deposit/withdraw freeze checks; removes transitive vault-freeze branch and adds defensive assertions/guards.
include/xrpl/ledger/helpers/TokenHelpers.h Updates documentation and parameter naming to match revised freeze-check semantics.
include/xrpl/ledger/helpers/MPTokenHelpers.h Clarifies isIndividualFrozen behavior and directs callers to use isFrozen for complete checks.

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

Comment thread src/test/app/Vault_test.cpp Outdated
Comment thread src/test/app/Vault_test.cpp Outdated
Comment thread src/libxrpl/ledger/helpers/TokenHelpers.cpp Outdated
Comment thread src/libxrpl/ledger/helpers/TokenHelpers.cpp Outdated
Comment thread src/libxrpl/tx/transactors/dex/AMMCreate.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.

No issues.

Review by Claude Sonnet 4.6 · Prompt: V15

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

Looks good overall. Just a small suggestion.

Comment on lines 4630 to +4631
shareID = env.le(vaultKeylet)->at(sfShareMPTID);

// Freeze a2's IOU trustline from the issuer side.
// a2 is the receiver in the simulated AMM withdraw; the
// distinction under test is that pre-fix330 the invariant
// does not apply the transitive vault freeze to receivers.
env(trust(gw, gw["IOU"](0), a2, tfSetFreeze));
env.close();
vaultPseudoID = env.le(vaultKeylet)->at(sfAccount);

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.

Would be cleaner if we could return those values from the lambda instead of updating local variable given that this function always returns true. I suggest something like this:

auto const setupVault = [&](Account const& a1, Account const& a2, Env& env) -> std::tuple<MPTID, AccountID> {

// ... the logic

return std::make_tuple(env.le(vaultKeylet)->at(sfShareMPTID), env.le(vaultKeylet)->at(sfAccount));
};


// And then, use the lambda in other lambdas:


auto const preclose = [&](Account const& a1, Account const& a2, Env& env) -> bool {
    auto const [shareID, vaultPseudoID] = setupVault(a1, a2, env);

};

};


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.

4 participants