-
Notifications
You must be signed in to change notification settings - Fork 1.6k
fix: Enforce aggregate MaximumAmount in multi-send MPT #6644
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1155,57 +1155,82 @@ | |
| beast::Journal j, | ||
| WaiveTransferFee waiveFee) | ||
| { | ||
| // Safe to get MPT since rippleSendMultiMPT is only called by | ||
| // accountSendMultiMPT | ||
| auto const& issuer = mptIssue.getIssuer(); | ||
|
|
||
| auto const sle = view.read(keylet::mptIssuance(mptIssue.getMptID())); | ||
| if (!sle) | ||
| return tecOBJECT_NOT_FOUND; | ||
|
|
||
| // These may diverge | ||
| // For the issuer-as-sender case, track the running total to validate | ||
| // against MaximumAmount. The read-only SLE (view.read) is not updated | ||
| // by rippleCreditMPT, so a per-iteration SLE read would be stale. | ||
| Number totalSendAmount; | ||
| auto const maximumAmount = sle->at(~sfMaximumAmount).value_or(maxMPTokenAmount); | ||
| auto const outstandingAmount = sle->getFieldU64(sfOutstandingAmount); | ||
|
|
||
| // actual accumulates the total cost to the sender (includes transfer | ||
| // fees for third-party transit sends). takeFromSender accumulates only | ||
| // the transit portion that is debited to the issuer in bulk after the | ||
| // loop. They diverge when there are transfer fees. | ||
| STAmount takeFromSender{mptIssue}; | ||
| actual = takeFromSender; | ||
|
|
||
| for (auto const& r : receivers) | ||
| for (auto const& [receiverID, amt] : receivers) | ||
| { | ||
| auto const& receiverID = r.first; | ||
| STAmount amount{mptIssue, r.second}; | ||
| STAmount const amount{mptIssue, amt}; | ||
|
|
||
| if (amount < beast::zero) | ||
| { | ||
| return tecINTERNAL; // LCOV_EXCL_LINE | ||
| } | ||
|
|
||
| /* If we aren't sending anything or if the sender is the same as the | ||
| * receiver then we don't need to do anything. | ||
| */ | ||
| if (!amount || (senderID == receiverID)) | ||
| if (!amount || senderID == receiverID) | ||
| continue; | ||
|
|
||
| if (senderID == issuer || receiverID == issuer) | ||
| { | ||
| // if sender is issuer, check that the new OutstandingAmount will | ||
| // not exceed MaximumAmount | ||
| if (senderID == issuer) | ||
| { | ||
| XRPL_ASSERT_PARTS( | ||
| takeFromSender == beast::zero, | ||
| "xrpl::rippleSendMultiMPT", | ||
Tapanito marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| "sender == issuer, takeFromSender == zero"); | ||
|
|
||
| auto const sendAmount = amount.mpt().value(); | ||
Tapanito marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| auto const maximumAmount = sle->at(~sfMaximumAmount).value_or(maxMPTokenAmount); | ||
| if (sendAmount > maximumAmount || | ||
| sle->getFieldU64(sfOutstandingAmount) > maximumAmount - sendAmount) | ||
| return tecPATH_DRY; | ||
|
|
||
| if (view.rules().enabled(fixSecurity3_1_3)) | ||
| { | ||
| // Post-fixSecurity3_1_3: aggregate MaximumAmount | ||
| // check. Each condition guards the subtraction | ||
| // in the next to prevent underflow. | ||
| auto const exceedsMaximumAmount = | ||
| // This send alone exceeds the max cap | ||
| sendAmount > maximumAmount || | ||
| // The aggregate of all sends exceeds the max cap | ||
| totalSendAmount > maximumAmount - sendAmount || | ||
| // Outstanding + aggregate exceeds the max cap | ||
| outstandingAmount > maximumAmount - sendAmount - totalSendAmount; | ||
|
|
||
| if (exceedsMaximumAmount) | ||
| return tecPATH_DRY; | ||
| totalSendAmount += sendAmount; | ||
|
Comment on lines
+1167
to
+1214
|
||
| } | ||
| else | ||
| { | ||
| // Pre-fixSecurity3_1_3: per-iteration MaximumAmount | ||
| // check. Reads sfOutstandingAmount from a stale | ||
| // view.read() snapshot — incorrect for multi-destination | ||
| // sends but retained for ledger replay compatibility. | ||
| if (sendAmount > maximumAmount || | ||
| outstandingAmount > maximumAmount - sendAmount) | ||
| return tecPATH_DRY; | ||
|
Comment on lines
+1199
to
+1224
|
||
| } | ||
| } | ||
|
|
||
| // Direct send: redeeming MPTs and/or sending own MPTs. | ||
| if (auto const ter = rippleCreditMPT(view, senderID, receiverID, amount, j)) | ||
| return ter; | ||
| actual += amount; | ||
| // Do not add amount to takeFromSender, because rippleCreditMPT took | ||
| // it | ||
| // Do not add amount to takeFromSender, because rippleCreditMPT | ||
| // took it. | ||
|
|
||
| continue; | ||
| } | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.