fix: Enforce aggregate MaximumAmount in multi-send MPT - #6644
Conversation
rippleSendMultiMPT used a read-only SLE snapshot (view.read) to check MaximumAmount per iteration. Since rippleCreditMPT updates a separate mutable copy (view.peek), the snapshot's sfOutstandingAmount was stale after the first iteration, allowing the aggregate to exceed MaximumAmount. Replace the per-iteration check with a running total that validates the aggregate against MaximumAmount within the send loop. The old per-iteration check is retained behind a !fixAssortedFixes gate for ledger replay compatibility.
|
This PR has conflicts, please resolve them in order for the PR to be reviewed. |
…-send-max-amount # Conflicts: # include/xrpl/protocol/detail/features.macro # src/libxrpl/ledger/View.cpp
|
All conflicts have been resolved. Assigned reviewers can now start or resume their review. |
|
/ai-review |
There was a problem hiding this comment.
Went through the changes
The core fix is correct, but four issues need attention: a uint64_t wrap-around risk in the pre-amendment arithmetic (line 1215), a semantic divergence from the original subtraction-underflow behavior that may break ledger replay fidelity (line 1215), missing // KNOWN BUG comments at the broken-behavior assertion sites in the test (line 3351), and a reminder to queue fixSecurity3_1_3 for prompt activation and audit other multi-send paths for the same stale-snapshot pattern. See inline comments.
Review by ReviewBot 🤖
Review by Claude Opus 4.6 · Prompt: V12
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## develop #6644 +/- ##
=========================================
- Coverage 81.5% 81.4% -0.0%
=========================================
Files 999 999
Lines 74458 74467 +9
Branches 7553 7557 +4
=========================================
- Hits 60648 60646 -2
- Misses 13810 13821 +11
🚀 New features to boost your workflow:
|
Use subtraction-based guards instead of addition to prevent uint64_t overflow in both the post-amendment aggregate check and the pre-amendment per-iteration check. Each condition in the cascade protects the subtraction in the next from underflow. Move totalSendAmount accumulation after the check so the guard operates on the pre-addition value.
There was a problem hiding this comment.
Looked through this one
One high-severity security note flagged inline: the pre-fixSecurity3_1_3 path allows issuer to bypass MaximumAmount via stale snapshot — activate the amendment promptly.
Review by ReviewBot 🤖
Review by Claude Opus 4.6 · Prompt: V12
There was a problem hiding this comment.
Pull request overview
Fixes a correctness gap in MPToken multi-destination sends where MaximumAmount could be exceeded because per-iteration checks observed a stale view.read() snapshot instead of the updated outstanding amount.
Changes:
- Update
rippleSendMultiMPTto enforceMaximumAmountusing an aggregate/running-total check inside the send loop (with pre-amendment behavior retained for replay compatibility). - Add a unit test covering multi-send aggregate
MaximumAmountenforcement, including a pre-amendment “known bug” case.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
src/libxrpl/ledger/helpers/TokenHelpers.cpp |
Implements aggregate MaximumAmount enforcement for issuer multi-send, with amendment gating for replay compatibility. |
src/test/app/MPToken_test.cpp |
Adds a regression test validating correct aggregate enforcement and preserving pre-amendment behavior expectations. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Went through the changes
Two correctness/security issues flagged inline: an unsigned underflow risk in the aggregate cap check (line 1210) and a missing explicit security warning on the live pre-amendment bypass path (line 1216). The test also mutates the shared Env feature set without re-enabling the amendment — see line 3353.
Review by ReviewBot 🤖
Review by Claude Opus 4.6 · Prompt: V12
| // exceeds MaximumAmount. Preserved for ledger replay. | ||
| { | ||
| // KNOWN BUG (pre-fixSecurity3_1_3): preserved for ledger replay only | ||
| env.disableFeature(fixSecurity3_1_3); |
There was a problem hiding this comment.
Mutating the shared env feature set is fragile — isolate the pre-amendment test in its own Env to prevent amendment-disabled state from potentially affecting later tests:
Replace Number/STAmount with std::int64_t for the totalSendAmount accumulator. STAmount implicitly converts to Number, whose small-scale mantissa (~16 digits) can lose precision for values near maxMPTokenAmount (19 digits), potentially producing incorrect MaximumAmount comparisons. Also fix test: re-enable fixSecurity3_1_3 after the pre-amendment block to avoid leaking disabled state into subsequent tests.
There was a problem hiding this comment.
Two security issues flagged inline: a signed-overflow in totalSendAmount that can bypass the MaximumAmount cap (high), a signed/unsigned mismatch in the comparison chain (medium), and a test-correctness risk where disableFeature may not affect the already-constructed view — see inline comments.
Review by Claude Opus 4.6 · Prompt: V12
Co-authored-by: xrplf-ai-reviewer[bot] <266832837+xrplf-ai-reviewer[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Reviewed this MR
The fix is logically correct, but three safety concerns flagged inline: the pre-amendment supply-cap bypass path needs a runtime visibility guard; the unsigned subtraction chain relies on fragile short-circuit ordering; and the test lacks a paired negative assertion after re-enabling the amendment.
Review by ReviewBot 🤖
Review by Claude Opus 4.6 · Prompt: V12
|
@Tapanito I can merge this PR once the conflict has been resolved. |
|
All conflicts have been resolved. Assigned reviewers can now start or resume their review. |
…ount Exercise the third guard condition (outstandingAmount + sendAmount + totalSendAmount > maximumAmount) by issuing tokens before the multi-send, so the check runs against a nonzero baseline.
Co-authored-by: xrplf-ai-reviewer[bot] <266832837+xrplf-ai-reviewer[bot]@users.noreply.github.com>
Co-authored-by: xrplf-ai-reviewer[bot] <266832837+xrplf-ai-reviewer[bot]@users.noreply.github.com>
Co-authored-by: xrplf-ai-reviewer[bot] <266832837+xrplf-ai-reviewer[bot]@users.noreply.github.com>
Co-authored-by: xrplf-ai-reviewer[bot] <266832837+xrplf-ai-reviewer[bot]@users.noreply.github.com>
Co-authored-by: xrplf-ai-reviewer[bot] <266832837+xrplf-ai-reviewer[bot]@users.noreply.github.com>
rippleSendMultiMPT used a read-only SLE snapshot (view.read) to check MaximumAmount per iteration. Since rippleCreditMPT updates a separate mutable copy (view.peek), the snapshot's sfOutstandingAmount was stale after the first iteration, allowing the aggregate to exceed MaximumAmount.
Replace the per-iteration check with a running total that validates the aggregate against MaximumAmount within the send loop. The old per-iteration check is retained behind a !fixAssortedFixes gate for ledger replay compatibility.
High Level Overview of Change
Context of Change
API Impact
libxrplchange (any change that may affectlibxrplor dependents oflibxrpl)