diff --git a/src/libxrpl/ledger/View.cpp b/src/libxrpl/ledger/View.cpp index f32dbe51935..355939a9c3d 100644 --- a/src/libxrpl/ledger/View.cpp +++ b/src/libxrpl/ledger/View.cpp @@ -2165,25 +2165,33 @@ rippleSendIOU( return terResult; } -// Send regardless of limits. -// --> receivers: Amount/currency/issuer to deliver to receivers. -// <-- saActual: Amount actually cost to sender. Sender pays fees. +template static TER -rippleSendMultiIOU( +doSendMulti( + std::string const& name, ApplyView& view, AccountID const& senderID, - Issue const& issue, + TAsset const& issue, MultiplePaymentDestinations const& receivers, STAmount& actual, beast::Journal j, - WaiveTransferFee waiveFee) -{ + WaiveTransferFee waiveFee, + // Don't pass back parameters that the caller already has + std::function< + TER(AccountID const& senderID, + AccountID const& receiverID, + STAmount const& amount, + bool checkIssuer)> doCredit, + std::function< + TER(AccountID const& issuer, + STAmount const& takeFromSender, + STAmount const& amount)> preMint = {}) +{ + // Use the same pattern for all the SendMulti functions to help avoid + // divergence and copy/paste errors. auto const& issuer = issue.getIssuer(); - XRPL_ASSERT( - !isXRP(senderID), "xrpl::rippleSendMultiIOU : sender is not XRP"); - - // These may diverge + // These values may not stay in sync STAmount takeFromSender{issue}; actual = takeFromSender; @@ -2193,59 +2201,106 @@ rippleSendMultiIOU( auto const& receiverID = r.first; STAmount amount{issue, r.second}; + 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)) continue; + using namespace std::string_literals; XRPL_ASSERT( !isXRP(receiverID), - "xrpl::rippleSendMultiIOU : receiver is not XRP"); + ("xrpl::"s + name + " : receiver is not XRP").c_str()); if (senderID == issuer || receiverID == issuer || issuer == noAccount()) { + if (preMint) + { + if (auto const ter = preMint(issuer, takeFromSender, amount)) + return ter; + } // Direct send: redeeming IOUs and/or sending own IOUs. - if (auto const ter = rippleCreditIOU( - view, senderID, receiverID, amount, false, j)) + if (auto const ter = doCredit(senderID, receiverID, amount, false)) return ter; actual += amount; - // Do not add amount to takeFromSender, because rippleCreditIOU took + // Do not add amount to takeFromSender, because doCredit took // it. continue; } - // Sending 3rd party IOUs: transit. + // Sending 3rd party: transit. // Calculate the amount to transfer accounting // for any transfer fees if the fee is not waived: - STAmount actualSend = (waiveFee == WaiveTransferFee::Yes) + STAmount actualSend = + (waiveFee == WaiveTransferFee::Yes || issue.native()) ? amount - : multiply(amount, transferRate(view, issuer)); + : multiply(amount, transferRate(view, amount)); actual += actualSend; takeFromSender += actualSend; - JLOG(j.debug()) << "rippleSendMultiIOU> " << to_string(senderID) - << " - > " << to_string(receiverID) + JLOG(j.debug()) << name << "> " << to_string(senderID) << " - > " + << to_string(receiverID) << " : deliver=" << amount.getFullText() - << " cost=" << actual.getFullText(); + << " cost=" << actualSend.getFullText(); - if (TER const terResult = - rippleCreditIOU(view, issuer, receiverID, amount, true, j)) + if (TER const terResult = doCredit(issuer, receiverID, amount, true)) return terResult; } if (senderID != issuer && takeFromSender) { - if (TER const terResult = rippleCreditIOU( - view, senderID, issuer, takeFromSender, true, j)) + if (TER const terResult = + doCredit(senderID, issuer, takeFromSender, true)) return terResult; } return tesSUCCESS; } +// Send regardless of limits. +// --> receivers: Amount/currency/issuer to deliver to receivers. +// <-- saActual: Amount actually cost to sender. Sender pays fees. +static TER +rippleSendMultiIOU( + ApplyView& view, + AccountID const& senderID, + Issue const& issue, + MultiplePaymentDestinations const& receivers, + STAmount& actual, + beast::Journal j, + WaiveTransferFee waiveFee) +{ + XRPL_ASSERT( + !isXRP(senderID), "xrpl::rippleSendMultiIOU : sender is not XRP"); + + auto doCredit = [&view, j]( + AccountID const& senderID, + AccountID const& receiverID, + STAmount const& amount, + bool checkIssuer) { + return rippleCreditIOU( + view, senderID, receiverID, amount, checkIssuer, j); + }; + + return doSendMulti( + "rippleSendMultiIOU", + view, + senderID, + issue, + receivers, + actual, + j, + waiveFee, + doCredit); +} + static TER accountSendIOU( ApplyView& view, @@ -2384,9 +2439,9 @@ accountSendMultiIOU( "xrpl::accountSendMultiIOU", "multiple recipients provided"); + STAmount actual; if (!issue.native()) { - STAmount actual; JLOG(j.trace()) << "accountSendMultiIOU: " << to_string(senderID) << " sending " << receivers.size() << " IOUs"; @@ -2415,6 +2470,97 @@ accountSendMultiIOU( << sender_bal << ") -> " << receivers.size() << " receivers."; } + auto doCredit = [&view, &sender, &receivers, j]( + AccountID const& senderID, + AccountID const& receiverID, + STAmount const& amount, + bool /*checkIssuer*/) -> TER { + if (!senderID) + { + SLE::pointer receiver = receiverID != beast::zero + ? view.peek(keylet::account(receiverID)) + : SLE::pointer(); + + if (auto stream = j.trace()) + { + std::string receiver_bal("-"); + + if (receiver) + receiver_bal = + receiver->getFieldAmount(sfBalance).getFullText(); + + stream << "accountSendMultiIOU> " << to_string(senderID) + << " -> " << to_string(receiverID) << " (" + << receiver_bal << ") : " << amount.getFullText(); + } + + if (receiver) + { + // Increment XRP balance. + auto const rcvBal = receiver->getFieldAmount(sfBalance); + receiver->setFieldAmount(sfBalance, rcvBal + amount); + view.creditHook(xrpAccount(), receiverID, amount, -rcvBal); + + view.update(receiver); + } + + if (auto stream = j.trace()) + { + std::string receiver_bal("-"); + + if (receiver) + receiver_bal = + receiver->getFieldAmount(sfBalance).getFullText(); + + stream << "accountSendMultiIOU< " << to_string(senderID) + << " -> " << to_string(receiverID) << " (" + << receiver_bal << ") : " << amount.getFullText(); + } + return tesSUCCESS; + } + // Sender + if (sender) + { + if (sender->getFieldAmount(sfBalance) < amount) + { + return TER{tecFAILED_PROCESSING}; + } + else + { + auto const sndBal = sender->getFieldAmount(sfBalance); + view.creditHook(senderID, xrpAccount(), amount, sndBal); + + // Decrement XRP balance. + sender->setFieldAmount(sfBalance, sndBal - amount); + view.update(sender); + } + } + + if (auto stream = j.trace()) + { + std::string sender_bal("-"); + + if (sender) + sender_bal = sender->getFieldAmount(sfBalance).getFullText(); + + stream << "accountSendMultiIOU< " << to_string(senderID) << " (" + << sender_bal << ") -> " << receivers.size() + << " receivers."; + } + + return tesSUCCESS; + }; + return doSendMulti( + "accountSendMultiIOU", + view, + senderID, + issue, + receivers, + actual, + j, + waiveFee, + doCredit); + // Failures return immediately. STAmount takeFromSender{issue}; for (auto const& r : receivers) @@ -2646,90 +2792,51 @@ rippleSendMultiMPT( 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 - STAmount takeFromSender{mptIssue}; - actual = takeFromSender; - - for (auto const& r : receivers) - { - auto const& receiverID = r.first; - STAmount amount{mptIssue, r.second}; - - 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)) - continue; - - if (senderID == issuer || receiverID == issuer) + auto preMint = [&](AccountID const& issuer, + STAmount const& takeFromSender, + STAmount const& amount) -> TER { + // if sender is issuer, check that the new OutstandingAmount will + // not exceed MaximumAmount + if (senderID == issuer) { - // if sender is issuer, check that the new OutstandingAmount will - // not exceed MaximumAmount - if (senderID == issuer) - { - XRPL_ASSERT_PARTS( - takeFromSender == beast::zero, - "rippler::rippleSendMultiMPT", - "sender == issuer, takeFromSender == zero"); - auto const sendAmount = amount.mpt().value(); - auto const maximumAmount = - sle->at(~sfMaximumAmount).value_or(maxMPTokenAmount); - if (sendAmount > maximumAmount || - sle->getFieldU64(sfOutstandingAmount) > - maximumAmount - sendAmount) - return tecPATH_DRY; - } - - // 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 - - continue; + XRPL_ASSERT_PARTS( + takeFromSender == beast::zero, + "rippler::rippleSendMultiMPT", + "sender == issuer, takeFromSender == zero"); + auto const sendAmount = amount.mpt().value(); + auto const maximumAmount = + sle->at(~sfMaximumAmount).value_or(maxMPTokenAmount); + if (sendAmount > maximumAmount || + sle->getFieldU64(sfOutstandingAmount) > + maximumAmount - sendAmount) + return tecPATH_DRY; } - // Sending 3rd party MPTs: transit. - STAmount actualSend = (waiveFee == WaiveTransferFee::Yes) - ? amount - : multiply( - amount, - transferRate(view, amount.get().getMptID())); - actual += actualSend; - takeFromSender += actualSend; - - JLOG(j.debug()) << "rippleSendMultiMPT> " << to_string(senderID) - << " - > " << to_string(receiverID) - << " : deliver=" << amount.getFullText() - << " cost=" << actualSend.getFullText(); - - if (auto const terResult = - rippleCreditMPT(view, issuer, receiverID, amount, j)) - return terResult; - } - if (senderID != issuer && takeFromSender) - { - if (TER const terResult = - rippleCreditMPT(view, senderID, issuer, takeFromSender, j)) - return terResult; - } + return tesSUCCESS; + }; + auto doCredit = [&view, j]( + AccountID const& senderID, + AccountID const& receiverID, + STAmount const& amount, + bool) { + return rippleCreditMPT(view, senderID, receiverID, amount, j); + }; - return tesSUCCESS; + return doSendMulti( + "rippleSendMultiMPT", + view, + senderID, + mptIssue, + receivers, + actual, + j, + waiveFee, + doCredit, + preMint); } static TER