Compare commits

...

1 Commits

Author SHA1 Message Date
Ed Hennis
f6f3542b7e Refactor the sendMulti functions into a common method with callbacks 2026-01-15 12:01:23 -05:00

View File

@@ -2167,6 +2167,105 @@ rippleSendIOU(
return terResult; return terResult;
} }
template <class TAsset>
static TER
doSendMulti(
std::string const& name,
ApplyView& view,
AccountID const& senderID,
TAsset const& issue,
MultiplePaymentDestinations const& receivers,
STAmount& actual,
beast::Journal j,
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();
// These values may not stay in sync
STAmount takeFromSender{issue};
actual = takeFromSender;
// Failures return immediately.
for (auto const& r : receivers)
{
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::"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 = doCredit(senderID, receiverID, amount, false))
return ter;
actual += amount;
// Do not add amount to takeFromSender, because doCredit took
// it.
continue;
}
// 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 || issue.native())
? amount
: multiply(amount, transferRate(view, amount));
actual += actualSend;
takeFromSender += actualSend;
JLOG(j.debug()) << name << "> " << to_string(senderID) << " - > "
<< to_string(receiverID)
<< " : deliver=" << amount.getFullText()
<< " cost=" << actualSend.getFullText();
if (TER const terResult = doCredit(issuer, receiverID, amount, true))
return terResult;
}
if (senderID != issuer && takeFromSender)
{
if (TER const terResult =
doCredit(senderID, issuer, takeFromSender, true))
return terResult;
}
return tesSUCCESS;
}
// Send regardless of limits. // Send regardless of limits.
// --> receivers: Amount/currency/issuer to deliver to receivers. // --> receivers: Amount/currency/issuer to deliver to receivers.
// <-- saActual: Amount actually cost to sender. Sender pays fees. // <-- saActual: Amount actually cost to sender. Sender pays fees.
@@ -2180,72 +2279,28 @@ rippleSendMultiIOU(
beast::Journal j, beast::Journal j,
WaiveTransferFee waiveFee) WaiveTransferFee waiveFee)
{ {
auto const& issuer = issue.getIssuer();
XRPL_ASSERT( XRPL_ASSERT(
!isXRP(senderID), "xrpl::rippleSendMultiIOU : sender is not XRP"); !isXRP(senderID), "xrpl::rippleSendMultiIOU : sender is not XRP");
// These may diverge auto doCredit = [&view, j](
STAmount takeFromSender{issue}; AccountID const& senderID,
actual = takeFromSender; AccountID const& receiverID,
STAmount const& amount,
bool checkIssuer) {
return rippleCreditIOU(
view, senderID, receiverID, amount, checkIssuer, j);
};
// Failures return immediately. return doSendMulti(
for (auto const& r : receivers) "rippleSendMultiIOU",
{ view,
auto const& receiverID = r.first; senderID,
STAmount amount{issue, r.second}; issue,
receivers,
/* If we aren't sending anything or if the sender is the same as the actual,
* receiver then we don't need to do anything. j,
*/ waiveFee,
if (!amount || (senderID == receiverID)) doCredit);
continue;
XRPL_ASSERT(
!isXRP(receiverID),
"xrpl::rippleSendMultiIOU : receiver is not XRP");
if (senderID == issuer || receiverID == issuer || issuer == noAccount())
{
// Direct send: redeeming IOUs and/or sending own IOUs.
if (auto const ter = rippleCreditIOU(
view, senderID, receiverID, amount, false, j))
return ter;
actual += amount;
// Do not add amount to takeFromSender, because rippleCreditIOU took
// it.
continue;
}
// Sending 3rd party IOUs: transit.
// Calculate the amount to transfer accounting
// for any transfer fees if the fee is not waived:
STAmount actualSend = (waiveFee == WaiveTransferFee::Yes)
? amount
: multiply(amount, transferRate(view, issuer));
actual += actualSend;
takeFromSender += actualSend;
JLOG(j.debug()) << "rippleSendMultiIOU> " << to_string(senderID)
<< " - > " << to_string(receiverID)
<< " : deliver=" << amount.getFullText()
<< " cost=" << actual.getFullText();
if (TER const terResult =
rippleCreditIOU(view, issuer, receiverID, amount, true, j))
return terResult;
}
if (senderID != issuer && takeFromSender)
{
if (TER const terResult = rippleCreditIOU(
view, senderID, issuer, takeFromSender, true, j))
return terResult;
}
return tesSUCCESS;
} }
static TER static TER
@@ -2386,9 +2441,9 @@ accountSendMultiIOU(
"xrpl::accountSendMultiIOU", "xrpl::accountSendMultiIOU",
"multiple recipients provided"); "multiple recipients provided");
STAmount actual;
if (!issue.native()) if (!issue.native())
{ {
STAmount actual;
JLOG(j.trace()) << "accountSendMultiIOU: " << to_string(senderID) JLOG(j.trace()) << "accountSendMultiIOU: " << to_string(senderID)
<< " sending " << receivers.size() << " IOUs"; << " sending " << receivers.size() << " IOUs";
@@ -2417,6 +2472,97 @@ accountSendMultiIOU(
<< sender_bal << ") -> " << receivers.size() << " receivers."; << 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. // Failures return immediately.
STAmount takeFromSender{issue}; STAmount takeFromSender{issue};
for (auto const& r : receivers) for (auto const& r : receivers)
@@ -2648,90 +2794,51 @@ rippleSendMultiMPT(
beast::Journal j, beast::Journal j,
WaiveTransferFee waiveFee) 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())); auto const sle = view.read(keylet::mptIssuance(mptIssue.getMptID()));
if (!sle) if (!sle)
return tecOBJECT_NOT_FOUND; return tecOBJECT_NOT_FOUND;
// These may diverge auto preMint = [&](AccountID const& issuer,
STAmount takeFromSender{mptIssue}; STAmount const& takeFromSender,
actual = takeFromSender; STAmount const& amount) -> TER {
// if sender is issuer, check that the new OutstandingAmount will
for (auto const& r : receivers) // not exceed MaximumAmount
{ if (senderID == issuer)
auto const& receiverID = r.first;
STAmount amount{mptIssue, r.second};
if (amount < beast::zero)
{ {
return tecINTERNAL; // LCOV_EXCL_LINE 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;
} }
/* If we aren't sending anything or if the sender is the same as the return tesSUCCESS;
* receiver then we don't need to do anything. };
*/ auto doCredit = [&view, j](
if (!amount || (senderID == receiverID)) AccountID const& senderID,
continue; AccountID const& receiverID,
STAmount const& amount,
bool) {
return rippleCreditMPT(view, senderID, receiverID, amount, j);
};
if (senderID == issuer || receiverID == issuer) return doSendMulti(
{ "rippleSendMultiMPT",
// if sender is issuer, check that the new OutstandingAmount will view,
// not exceed MaximumAmount senderID,
if (senderID == issuer) mptIssue,
{ receivers,
XRPL_ASSERT_PARTS( actual,
takeFromSender == beast::zero, j,
"rippler::rippleSendMultiMPT", waiveFee,
"sender == issuer, takeFromSender == zero"); doCredit,
auto const sendAmount = amount.mpt().value(); preMint);
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;
}
// Sending 3rd party MPTs: transit.
STAmount actualSend = (waiveFee == WaiveTransferFee::Yes)
? amount
: multiply(
amount,
transferRate(view, amount.get<MPTIssue>().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;
} }
static TER static TER