xrpld
Loading...
Searching...
No Matches
TokenHelpers.cpp
1#include <xrpl/ledger/helpers/TokenHelpers.h>
2
3#include <xrpl/basics/Log.h>
4#include <xrpl/beast/utility/Journal.h>
5#include <xrpl/beast/utility/Zero.h>
6#include <xrpl/beast/utility/instrumentation.h>
7#include <xrpl/ledger/ApplyView.h>
8#include <xrpl/ledger/ReadView.h>
9#include <xrpl/ledger/View.h>
10#include <xrpl/ledger/helpers/AccountRootHelpers.h>
11#include <xrpl/ledger/helpers/MPTokenHelpers.h>
12#include <xrpl/ledger/helpers/RippleStateHelpers.h>
13#include <xrpl/ledger/helpers/SponsorHelpers.h>
14#include <xrpl/protocol/AccountID.h>
15#include <xrpl/protocol/Asset.h>
16#include <xrpl/protocol/Concepts.h>
17#include <xrpl/protocol/Feature.h>
18#include <xrpl/protocol/Indexes.h>
19#include <xrpl/protocol/Issue.h>
20#include <xrpl/protocol/LedgerFormats.h>
21#include <xrpl/protocol/MPTIssue.h>
22#include <xrpl/protocol/Protocol.h>
23#include <xrpl/protocol/Rate.h>
24#include <xrpl/protocol/SField.h>
25#include <xrpl/protocol/STAmount.h>
26#include <xrpl/protocol/STLedgerEntry.h>
27#include <xrpl/protocol/TER.h>
28#include <xrpl/protocol/UintTypes.h>
29#include <xrpl/protocol/XRPAmount.h>
30
31#include <algorithm>
32#include <cstdint>
33#include <initializer_list>
34#include <limits>
35#include <string>
36#include <variant>
37
38namespace xrpl {
39
40//------------------------------------------------------------------------------
41//
42// Freeze checking (Asset-based)
43//
44//------------------------------------------------------------------------------
45
46bool
47isGlobalFrozen(ReadView const& view, Asset const& asset)
48{
49 return asset.visit(
50 [&](Issue const& issue) { return isGlobalFrozen(view, issue.getIssuer()); },
51 [&](MPTIssue const& issue) { return isGlobalFrozen(view, issue); });
52}
53
54TER
55checkGlobalFrozen(ReadView const& view, Asset const& asset)
56{
57 if (isGlobalFrozen(view, asset))
58 return asset.holds<MPTIssue>() ? tecLOCKED : tecFROZEN;
59 return tesSUCCESS;
60}
61
62bool
63isIndividualFrozen(ReadView const& view, AccountID const& account, Asset const& asset)
64{
65 return std::visit(
66 [&](auto const& issue) { return isIndividualFrozen(view, account, issue); }, asset.value());
67}
68
69TER
70checkIndividualFrozen(ReadView const& view, AccountID const& account, Asset const& asset)
71{
72 if (isIndividualFrozen(view, account, asset))
73 return asset.holds<MPTIssue>() ? tecLOCKED : tecFROZEN;
74 return tesSUCCESS;
75}
76
77bool
78isFrozen(ReadView const& view, AccountID const& account, Asset const& asset, std::uint8_t depth)
79{
80 return std::visit(
81 [&](auto const& issue) { return isFrozen(view, account, issue, depth); }, asset.value());
82}
83
84TER
85checkFrozen(ReadView const& view, AccountID const& account, Issue const& issue)
86{
87 return isFrozen(view, account, issue) ? (TER)tecFROZEN : (TER)tesSUCCESS;
88}
89
90TER
91checkFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue)
92{
93 return isFrozen(view, account, mptIssue) ? (TER)tecLOCKED : (TER)tesSUCCESS;
94}
95
96TER
97checkFrozen(ReadView const& view, AccountID const& account, Asset const& asset)
98{
99 return std::visit(
100 [&](auto const& issue) { return checkFrozen(view, account, issue); }, asset.value());
101}
102
103bool
105 ReadView const& view,
106 std::initializer_list<AccountID> const& accounts,
107 Issue const& issue)
108{
109 return std::ranges::any_of(accounts, [&](auto const& account) {
110 return isFrozen(view, account, issue.currency, issue.account);
111 });
112}
113
114bool
116 ReadView const& view,
117 std::initializer_list<AccountID> const& accounts,
118 Asset const& asset,
119 std::uint8_t depth)
120{
121 return asset.visit(
122 [&](Issue const& issue) { return isAnyFrozen(view, accounts, issue); },
123 [&](MPTIssue const& issue) { return isAnyFrozen(view, accounts, issue, depth); });
124}
125
126bool
128 ReadView const& view,
129 AccountID const& account,
130 MPTIssue const& mptIssue,
131 std::uint8_t depth)
132{
133 // Unlike IOUs, frozen / locked MPTs are not allowed to send or receive
134 // funds, so checking "deep frozen" is the same as checking "frozen".
135 return isFrozen(view, account, mptIssue, depth);
136}
137
138bool
139isDeepFrozen(ReadView const& view, AccountID const& account, Asset const& asset, std::uint8_t depth)
140{
141 return std::visit(
142 [&](auto const& issue) { return isDeepFrozen(view, account, issue, depth); },
143 asset.value());
144}
145
146TER
147checkDeepFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue)
148{
149 return isDeepFrozen(view, account, mptIssue) ? (TER)tecLOCKED : (TER)tesSUCCESS;
150}
151
152TER
153checkDeepFrozen(ReadView const& view, AccountID const& account, Asset const& asset)
154{
155 return std::visit(
156 [&](auto const& issue) { return checkDeepFrozen(view, account, issue); }, asset.value());
157}
158
159[[nodiscard]] TER
161 ReadView const& view,
162 AccountID const& pseudoAcct,
163 AccountID const& submitterAcct,
164 AccountID const& dstAcct,
165 Asset const& asset)
166{
167 XRPL_ASSERT(
168 isPseudoAccount(view, pseudoAcct),
169 "xrpl::checkWithdrawFreeze : source is a pseudo-account");
170 XRPL_ASSERT(
171 !isPseudoAccount(view, submitterAcct),
172 "xrpl::checkWithdrawFreeze : submitter is not a pseudo-account");
173 XRPL_ASSERT(
174 !isPseudoAccount(view, dstAcct),
175 "xrpl::checkWithdrawFreeze : destination is not a pseudo-account");
176 // The asset being withdrawn must not be issued by a pseudo-account
177 XRPL_ASSERT(
178 !isPseudoAccount(view, asset.getIssuer()),
179 "xrpl::checkWithdrawFreeze : asset issuer cannot be a pseudo-account");
180
181 // Funds can always be sent to the issuer
182 if (dstAcct == asset.getIssuer())
183 return tesSUCCESS;
184
185 // If the asset is globally frozen, other checks are redundant
186 if (auto const ret = checkGlobalFrozen(view, asset); !isTesSuccess(ret))
187 return ret;
188
189 // The transfer is from Submitter to Destination via Source (pseudo-account)
190 // Both Source and Submitter must not be frozen to allow sending funds
191 if (auto const ret = checkIndividualFrozen(view, pseudoAcct, asset); !isTesSuccess(ret))
192 return ret;
193
194 // Check submitter's individual freeze only when Submitter != Destination (a regular freeze
195 // should not block self-withdrawal).
196 if (submitterAcct != dstAcct)
197 {
198 if (auto const ret = checkIndividualFrozen(view, submitterAcct, asset); !isTesSuccess(ret))
199 return ret;
200 }
201
202 // The destination account must not be deep frozen to receive the funds
203 if (auto const ret = checkDeepFrozen(view, dstAcct, asset); !isTesSuccess(ret))
204 return ret;
205
206 if (asset.holds<MPTIssue>() &&
207 isVaultPseudoAccountFrozen(view, pseudoAcct, asset.get<MPTIssue>(), 0))
208 {
209 // LCOV_EXCL_START
210 UNREACHABLE("xrpl::checkWithdrawFreeze : pseudo-account backed object holds shares");
211 return tecINTERNAL;
212 // LCOV_EXCL_STOP
213 }
214
215 return tesSUCCESS;
216}
217
218[[nodiscard]] TER
220 ReadView const& view,
221 AccountID const& srcAcct,
222 AccountID const& pseudoAcct,
223 Asset const& asset)
224{
225 XRPL_ASSERT(
226 isPseudoAccount(view, pseudoAcct),
227 "xrpl::checkDepositFreeze : destination is a pseudo-account");
228 XRPL_ASSERT(
229 !isPseudoAccount(view, srcAcct),
230 "xrpl::checkDepositFreeze : source is not a pseudo-account");
231 // The asset being deposited must not be issued by a pseudo-account
232 XRPL_ASSERT(
233 !isPseudoAccount(view, asset.getIssuer()),
234 "xrpl::checkDepositFreeze : asset issuer cannot be a pseudo-account");
235
236 if (auto const ret = checkGlobalFrozen(view, asset); !isTesSuccess(ret))
237 return ret;
238
239 if (srcAcct != asset.getIssuer())
240 {
241 if (auto const ret = checkIndividualFrozen(view, srcAcct, asset); !isTesSuccess(ret))
242 return ret;
243 }
244
245 // Unlike regular accounts, pseudo-accounts cannot receive deposits under a regular freeze
246 // because those funds cannot be later withdrawn
247 if (auto const ret = checkIndividualFrozen(view, pseudoAcct, asset); !isTesSuccess(ret))
248 return ret;
249
250 if (asset.holds<MPTIssue>() &&
251 isVaultPseudoAccountFrozen(view, pseudoAcct, asset.get<MPTIssue>(), 0))
252 {
253 // LCOV_EXCL_START
254 UNREACHABLE("xrpl::checkDepositFreeze : pseudo-account backed object holds shares");
255 return tecINTERNAL;
256 // LCOV_EXCL_STOP
257 }
258
259 return tesSUCCESS;
260}
261
262//------------------------------------------------------------------------------
263//
264// Account balance functions
265//
266//------------------------------------------------------------------------------
267
270 ReadView const& view,
271 AccountID const& account,
272 Currency const& currency,
273 AccountID const& issuer,
274 FreezeHandling zeroIfFrozen,
276{
277 auto sle = view.read(keylet::trustLine(account, issuer, currency));
278
279 if (!sle)
280 {
281 return nullptr;
282 }
283
284 if (zeroIfFrozen == FreezeHandling::ZeroIfFrozen)
285 {
286 if (isFrozen(view, account, currency, issuer) ||
287 isDeepFrozen(view, account, currency, issuer))
288 {
289 return nullptr;
290 }
291
292 // when fixFrozenLPTokenTransfer is enabled, if currency is lptoken,
293 // we need to check if the associated assets have been frozen
294 if (view.rules().enabled(fixFrozenLPTokenTransfer))
295 {
296 auto const sleIssuer = view.read(keylet::account(issuer));
297 if (!sleIssuer)
298 {
299 return nullptr; // LCOV_EXCL_LINE
300 }
301 if (sleIssuer->isFieldPresent(sfAMMID))
302 {
303 auto const sleAmm = view.read(keylet::amm((*sleIssuer)[sfAMMID]));
304
305 if (!sleAmm ||
306 isLPTokenFrozen(view, account, (*sleAmm)[sfAsset], (*sleAmm)[sfAsset2]))
307 {
308 return nullptr;
309 }
310 }
311 }
312 }
313
314 return sle;
315}
316
317static STAmount
319 ReadView const& view,
320 SLE::const_ref sle,
321 AccountID const& account,
322 Currency const& currency,
323 AccountID const& issuer,
324 bool includeOppositeLimit,
326{
327 STAmount amount;
328 if (sle)
329 {
330 amount = sle->getFieldAmount(sfBalance);
331 bool const accountHigh = account > issuer;
332 auto const& oppositeField = accountHigh ? sfLowLimit : sfHighLimit;
333 if (accountHigh)
334 {
335 // Put balance in account terms.
336 amount.negate();
337 }
338 if (includeOppositeLimit)
339 {
340 amount += sle->getFieldAmount(oppositeField);
341 }
342 amount.get<Issue>().account = issuer;
343 }
344 else
345 {
346 amount.clear(Issue{currency, issuer});
347 }
348
349 JLOG(j.trace()) << "getTrustLineBalance:" << " account=" << to_string(account)
350 << " amount=" << amount.getFullText();
351
352 return view.balanceHookIOU(account, issuer, amount);
353}
354
355STAmount
357 ReadView const& view,
358 AccountID const& account,
359 Currency const& currency,
360 AccountID const& issuer,
361 FreezeHandling zeroIfFrozen,
363 SpendableHandling includeFullBalance)
364{
365 STAmount const amount;
366 if (isXRP(currency))
367 {
368 return {xrpLiquid(view, account, 0, j)};
369 }
370
371 bool const returnSpendable = (includeFullBalance == SpendableHandling::FullBalance);
372 if (returnSpendable && account == issuer)
373 {
374 // If the account is the issuer, then their limit is effectively
375 // infinite
376 return STAmount{Issue{currency, issuer}, STAmount::kMaxValue, STAmount::kMaxOffset};
377 }
378
379 // IOU: Return balance on trust line modulo freeze
380 SLE::const_pointer const sle =
381 getLineIfUsable(view, account, currency, issuer, zeroIfFrozen, j);
382
383 return getTrustLineBalance(view, sle, account, currency, issuer, returnSpendable, j);
384}
385
386STAmount
388 ReadView const& view,
389 AccountID const& account,
390 Issue const& issue,
391 FreezeHandling zeroIfFrozen,
393 SpendableHandling includeFullBalance)
394{
395 return accountHolds(
396 view, account, issue.currency, issue.account, zeroIfFrozen, j, includeFullBalance);
397}
398
399STAmount
401 ReadView const& view,
402 AccountID const& account,
403 MPTIssue const& mptIssue,
404 FreezeHandling zeroIfFrozen,
405 AuthHandling zeroIfUnauthorized,
407 SpendableHandling includeFullBalance)
408{
409 bool const returnSpendable = (includeFullBalance == SpendableHandling::FullBalance);
410 STAmount amount{mptIssue};
411 auto const& issuer = mptIssue.getIssuer();
412 bool const mptokensV2 = view.rules().enabled(featureMPTokensV2);
413
414 if (returnSpendable && account == mptIssue.getIssuer())
415 {
416 // if the account is the issuer, and the issuance exists, their limit is
417 // the issuance limit minus the outstanding value
418 auto const issuance = view.read(keylet::mptokenIssuance(mptIssue.getMptID()));
419
420 if (!issuance)
421 {
422 return amount;
423 }
424 auto const available = availableMPTAmount(*issuance);
425 if (!mptokensV2)
426 return STAmount{mptIssue, available};
427 return view.balanceHookMPT(issuer, mptIssue, available);
428 }
429
430 auto const sleMpt = view.read(keylet::mptoken(mptIssue.getMptID(), account));
431
432 if (!sleMpt ||
433 (zeroIfFrozen == FreezeHandling::ZeroIfFrozen && isFrozen(view, account, mptIssue)))
434 {
435 amount.clear(mptIssue);
436 }
437 else
438 {
439 amount = STAmount{mptIssue, sleMpt->getFieldU64(sfMPTAmount)};
440
441 // Only if auth check is needed, as it needs to do an additional read
442 // operation. Note featureSingleAssetVault will affect error codes.
443 if (zeroIfUnauthorized == AuthHandling::ZeroIfUnauthorized &&
444 (view.rules().enabled(featureSingleAssetVault) ||
445 view.rules().enabled(featureConfidentialTransfer)))
446 {
447 if (auto const err = requireAuth(view, mptIssue, account, AuthType::StrongAuth);
448 !isTesSuccess(err))
449 amount.clear(mptIssue);
450 }
451 else if (zeroIfUnauthorized == AuthHandling::ZeroIfUnauthorized)
452 {
453 auto const sleIssuance = view.read(keylet::mptokenIssuance(mptIssue.getMptID()));
454
455 // if auth is enabled on the issuance and mpt is not authorized,
456 // clear amount
457 if (sleIssuance && sleIssuance->isFlag(lsfMPTRequireAuth) &&
458 !sleMpt->isFlag(lsfMPTAuthorized))
459 amount.clear(mptIssue);
460 }
461 }
462
463 if (view.rules().enabled(featureMPTokensV2))
464 return view.balanceHookMPT(account, mptIssue, amount.mpt().value());
465 return amount;
466}
467
468[[nodiscard]] STAmount
470 ReadView const& view,
471 AccountID const& account,
472 Asset const& asset,
473 FreezeHandling zeroIfFrozen,
474 AuthHandling zeroIfUnauthorized,
476 SpendableHandling includeFullBalance)
477{
478 return asset.visit(
479 [&](Issue const& issue) {
480 return accountHolds(view, account, issue, zeroIfFrozen, j, includeFullBalance);
481 },
482 [&](MPTIssue const& issue) {
483 return accountHolds(
484 view, account, issue, zeroIfFrozen, zeroIfUnauthorized, j, includeFullBalance);
485 });
486}
487
488STAmount
490 ReadView const& view,
491 AccountID const& id,
492 STAmount const& saDefault,
493 FreezeHandling freezeHandling,
495{
496 XRPL_ASSERT(saDefault.holds<Issue>(), "xrpl::accountFunds: saDefault holds Issue");
497
498 if (!saDefault.native() && saDefault.getIssuer() == id)
499 return saDefault;
500
501 return accountHolds(
502 view, id, saDefault.get<Issue>().currency, saDefault.getIssuer(), freezeHandling, j);
503}
504
505STAmount
507 ReadView const& view,
508 AccountID const& id,
509 STAmount const& saDefault,
510 FreezeHandling freezeHandling,
511 AuthHandling authHandling,
513{
514 return saDefault.asset().visit(
515 [&](Issue const&) { return accountFunds(view, id, saDefault, freezeHandling, j); },
516 [&](MPTIssue const&) {
517 return accountHolds(
518 view,
519 id,
520 saDefault.asset(),
521 freezeHandling,
522 authHandling,
523 j,
525 });
526}
527
528Rate
529transferRate(ReadView const& view, STAmount const& amount)
530{
531 return amount.asset().visit(
532 [&](Issue const& issue) { return transferRate(view, issue.getIssuer()); },
533 [&](MPTIssue const& issue) { return transferRate(view, issue.getMptID()); });
534}
535
536//------------------------------------------------------------------------------
537//
538// Holding operations
539//
540//------------------------------------------------------------------------------
541
542[[nodiscard]] TER
543canAddHolding(ReadView const& view, Issue const& issue)
544{
545 if (issue.native())
546 {
547 return tesSUCCESS; // No special checks for XRP
548 }
549
550 auto const issuer = view.read(keylet::account(issue.getIssuer()));
551 if (!issuer)
552 {
553 return terNO_ACCOUNT;
554 }
555 if (!issuer->isFlag(lsfDefaultRipple))
556 {
557 return terNO_RIPPLE;
558 }
559
560 return tesSUCCESS;
561}
562
563[[nodiscard]] TER
564canAddHolding(ReadView const& view, Asset const& asset)
565{
566 return std::visit(
567 [&]<ValidIssueType TIss>(TIss const& issue) -> TER { return canAddHolding(view, issue); },
568 asset.value());
569}
570
571TER
574 AccountID const& accountID,
575 XRPAmount priorBalance,
576 Asset const& asset,
577 beast::Journal journal)
578{
579 return std::visit(
580 [&]<ValidIssueType TIss>(TIss const& issue) -> TER {
581 return addEmptyHolding(ctx, accountID, priorBalance, issue, journal);
582 },
583 asset.value());
584}
585
586TER
589 AccountID const& accountID,
590 Asset const& asset,
591 beast::Journal journal)
592{
593 return std::visit(
594 [&]<ValidIssueType TIss>(TIss const& issue) -> TER {
595 return removeEmptyHolding(ctx, accountID, issue, journal);
596 },
597 asset.value());
598}
599
600//------------------------------------------------------------------------------
601//
602// Authorization and transfer checks
603//
604//------------------------------------------------------------------------------
605
606TER
607requireAuth(ReadView const& view, Asset const& asset, AccountID const& account, AuthType authType)
608{
609 return std::visit(
610 [&]<ValidIssueType TIss>(TIss const& issue) {
611 return requireAuth(view, issue, account, authType);
612 },
613 asset.value());
614}
615
616TER
618 ReadView const& view,
619 Asset const& asset,
620 AccountID const& from,
621 AccountID const& to,
623 std::uint8_t depth)
624{
625 return asset.visit(
626 [&](MPTIssue const& issue) -> TER {
627 return canTransfer(view, issue, from, to, waive, depth);
628 },
629 [&](Issue const& issue) -> TER { return canTransfer(view, issue, from, to); });
630}
631
632//------------------------------------------------------------------------------
633//
634// Money Transfers
635//
636//------------------------------------------------------------------------------
637
638// Direct send w/o fees:
639// - Redeeming IOUs and/or sending sender's own IOUs.
640// - Create trust line if needed.
641// --> bCheckIssuer : normally require issuer to be involved.
642static TER
644 ApplyView& view,
645 AccountID const& uSenderID,
646 AccountID const& uReceiverID,
647 STAmount const& saAmount,
648 bool bCheckIssuer,
649 SLE::ref sponsorSle,
651{
652 AccountID const& issuer = saAmount.getIssuer();
653 Currency const& currency = saAmount.get<Issue>().currency;
654
655 // Make sure issuer is involved.
656 XRPL_ASSERT(
657 !bCheckIssuer || uSenderID == issuer || uReceiverID == issuer,
658 "xrpl::directSendNoFeeIOU : matching issuer or don't care");
659 (void)issuer;
660
661 // Disallow sending to self.
662 XRPL_ASSERT(uSenderID != uReceiverID, "xrpl::directSendNoFeeIOU : sender is not receiver");
663
664 bool const bSenderHigh = uSenderID > uReceiverID;
665 auto const index = keylet::trustLine(uSenderID, uReceiverID, currency);
666
667 XRPL_ASSERT(
668 !isXRP(uSenderID) && uSenderID != noAccount(),
669 "xrpl::directSendNoFeeIOU : sender is not XRP");
670 XRPL_ASSERT(
671 !isXRP(uReceiverID) && uReceiverID != noAccount(),
672 "xrpl::directSendNoFeeIOU : receiver is not XRP");
673
674 // If the line exists, modify it accordingly.
675 if (auto const sleRippleState = view.peek(index))
676 {
677 STAmount saBalance = sleRippleState->getFieldAmount(sfBalance);
678
679 if (bSenderHigh)
680 saBalance.negate(); // Put balance in sender terms.
681
682 view.creditHookIOU(uSenderID, uReceiverID, saAmount, saBalance);
683
684 STAmount const saBefore = saBalance;
685
686 saBalance -= saAmount;
687
688 JLOG(j.trace()) << "directSendNoFeeIOU: " << to_string(uSenderID) << " -> "
689 << to_string(uReceiverID) << " : before=" << saBefore.getFullText()
690 << " amount=" << saAmount.getFullText()
691 << " after=" << saBalance.getFullText();
692
693 bool bDelete = false;
694
695 auto const senderReserveFlag = bSenderHigh ? lsfHighReserve : lsfLowReserve;
696 auto const senderNoRippleFlag = bSenderHigh ? lsfHighNoRipple : lsfLowNoRipple;
697 auto const senderFreezeFlag = bSenderHigh ? lsfHighFreeze : lsfLowFreeze;
698 auto const receiverReserveFlag = bSenderHigh ? lsfLowReserve : lsfHighReserve;
699
700 // FIXME This NEEDS to be cleaned up and simplified. It's impossible
701 // for anyone to understand.
702 if (saBefore > beast::kZero
703 // Sender balance was positive.
704 && saBalance <= beast::kZero
705 // Sender is zero or negative.
706 && sleRippleState->isFlag(senderReserveFlag)
707 // Sender reserve is set.
708 && sleRippleState->isFlag(senderNoRippleFlag) !=
709 view.read(keylet::account(uSenderID))->isFlag(lsfDefaultRipple) &&
710 !sleRippleState->isFlag(senderFreezeFlag) &&
711 !sleRippleState->getFieldAmount(bSenderHigh ? sfHighLimit : sfLowLimit)
712 // Sender trust limit is 0.
713 && (sleRippleState->getFieldU32(bSenderHigh ? sfHighQualityIn : sfLowQualityIn) == 0u)
714 // Sender quality in is 0.
715 &&
716 (sleRippleState->getFieldU32(bSenderHigh ? sfHighQualityOut : sfLowQualityOut) == 0u))
717 // Sender quality out is 0.
718 {
719 // Clear the reserve of the sender, possibly delete the line!
720 auto const currentSponsor = getLedgerEntryReserveSponsor(
721 view, sleRippleState, !bSenderHigh ? sfLowSponsor : sfHighSponsor);
722 decreaseOwnerCount(view, view.peek(keylet::account(uSenderID)), currentSponsor, 1, j);
723
725 sleRippleState, !bSenderHigh ? sfLowSponsor : sfHighSponsor);
726
727 // Clear reserve flag.
728 sleRippleState->clearFlag(senderReserveFlag);
729
730 // Balance is zero, receiver reserve is clear.
731 bDelete = !saBalance // Balance is zero.
732 && !sleRippleState->isFlag(receiverReserveFlag);
733 // Receiver reserve is clear.
734 }
735
736 if (bSenderHigh)
737 saBalance.negate();
738
739 // Want to reflect balance to zero even if we are deleting line.
740 sleRippleState->setFieldAmount(sfBalance, saBalance);
741 // ONLY: Adjust balance.
742
743 if (bDelete)
744 {
745 return trustDelete(
746 view,
747 sleRippleState,
748 bSenderHigh ? uReceiverID : uSenderID,
749 bSenderHigh ? uSenderID : uReceiverID,
750 j);
751 }
752
753 view.update(sleRippleState);
754 return tesSUCCESS;
755 }
756
757 STAmount const saReceiverLimit(Issue{currency, uReceiverID});
758 STAmount saBalance{saAmount};
759
760 saBalance.get<Issue>().account = noAccount();
761
762 JLOG(j.debug()) << "directSendNoFeeIOU: "
763 "create line: "
764 << to_string(uSenderID) << " -> " << to_string(uReceiverID) << " : "
765 << saAmount.getFullText();
766
767 auto const sleAccount = view.peek(keylet::account(uReceiverID));
768 if (!sleAccount)
769 return tefINTERNAL; // LCOV_EXCL_LINE
770
771 bool const noRipple = !sleAccount->isFlag(lsfDefaultRipple);
772
773 return trustCreate(
774 view,
775 bSenderHigh,
776 uSenderID,
777 uReceiverID,
778 index.key,
779 sleAccount,
780 false,
781 noRipple,
782 false,
783 false,
784 saBalance,
785 saReceiverLimit,
786 0,
787 0,
788 sponsorSle,
789 j);
790}
791
792// Send regardless of limits.
793// --> saAmount: Amount/currency/issuer to deliver to receiver.
794// <-- saActual: Amount actually cost. Sender pays fees.
795static TER
797 ApplyView& view,
798 AccountID const& uSenderID,
799 AccountID const& uReceiverID,
800 STAmount const& saAmount,
801 STAmount& saActual,
803 SLE::ref sponsorSle,
804 WaiveTransferFee waiveFee)
805{
806 auto const& issuer = saAmount.getIssuer();
807
808 XRPL_ASSERT(
809 !isXRP(uSenderID) && !isXRP(uReceiverID),
810 "xrpl::directSendNoLimitIOU : neither sender nor receiver is XRP");
811 XRPL_ASSERT(uSenderID != uReceiverID, "xrpl::directSendNoLimitIOU : sender is not receiver");
812
813 if (uSenderID == issuer || uReceiverID == issuer || issuer == noAccount())
814 {
815 // Direct send: redeeming IOUs and/or sending own IOUs.
816 auto const ter =
817 directSendNoFeeIOU(view, uSenderID, uReceiverID, saAmount, false, sponsorSle, j);
818 if (!isTesSuccess(ter))
819 return ter;
820 saActual = saAmount;
821 return tesSUCCESS;
822 }
823
824 // Sending 3rd party IOUs: transit.
825
826 // Calculate the amount to transfer accounting
827 // for any transfer fees if the fee is not waived:
828 saActual = (waiveFee == WaiveTransferFee::Yes) ? saAmount
829 : multiply(saAmount, transferRate(view, issuer));
830
831 JLOG(j.debug()) << "directSendNoLimitIOU> " << to_string(uSenderID) << " - > "
832 << to_string(uReceiverID) << " : deliver=" << saAmount.getFullText()
833 << " cost=" << saActual.getFullText();
834
835 TER terResult = directSendNoFeeIOU(view, issuer, uReceiverID, saAmount, true, sponsorSle, j);
836
837 if (tesSUCCESS == terResult)
838 {
839 terResult = directSendNoFeeIOU(view, uSenderID, issuer, saActual, true, sponsorSle, j);
840 }
841
842 return terResult;
843}
844
845// Send regardless of limits.
846// --> receivers: Amount/currency/issuer to deliver to receivers.
847// <-- saActual: Amount actually cost to sender. Sender pays fees.
848static TER
850 ApplyView& view,
851 AccountID const& senderID,
852 Issue const& issue,
853 MultiplePaymentDestinations const& receivers,
854 STAmount& actual,
856 WaiveTransferFee waiveFee)
857{
858 auto const& issuer = issue.getIssuer();
859
860 XRPL_ASSERT(!isXRP(senderID), "xrpl::directSendNoLimitMultiIOU : sender is not XRP");
861
862 // These may diverge
863 STAmount takeFromSender{issue};
864 actual = takeFromSender;
865
866 // Failures return immediately.
867 for (auto const& r : receivers)
868 {
869 auto const& receiverID = r.first;
870 STAmount const amount{issue, r.second};
871
872 /* If we aren't sending anything or if the sender is the same as the
873 * receiver then we don't need to do anything.
874 */
875 if (!amount || (senderID == receiverID))
876 continue;
877
878 XRPL_ASSERT(!isXRP(receiverID), "xrpl::directSendNoLimitMultiIOU : receiver is not XRP");
879
880 if (senderID == issuer || receiverID == issuer || issuer == noAccount())
881 {
882 // Direct send: redeeming IOUs and/or sending own IOUs.
883 if (auto const ter =
884 directSendNoFeeIOU(view, senderID, receiverID, amount, false, {}, j);
885 !isTesSuccess(ter))
886 return ter;
887 actual += amount;
888 // Do not add amount to takeFromSender, because directSendNoFeeIOU took
889 // it.
890
891 continue;
892 }
893
894 // Sending 3rd party IOUs: transit.
895
896 // Calculate the amount to transfer accounting
897 // for any transfer fees if the fee is not waived:
898 STAmount const actualSend = (waiveFee == WaiveTransferFee::Yes)
899 ? amount
900 : multiply(amount, transferRate(view, issuer));
901 actual += actualSend;
902 takeFromSender += actualSend;
903
904 JLOG(j.debug()) << "directSendNoLimitMultiIOU> " << to_string(senderID) << " - > "
905 << to_string(receiverID) << " : deliver=" << amount.getFullText()
906 << " cost=" << actual.getFullText();
907
908 if (TER const terResult = directSendNoFeeIOU(view, issuer, receiverID, amount, true, {}, j))
909 return terResult;
910 }
911
912 if (senderID != issuer && takeFromSender)
913 {
914 if (TER const terResult =
915 directSendNoFeeIOU(view, senderID, issuer, takeFromSender, true, {}, j))
916 return terResult;
917 }
918
919 return tesSUCCESS;
920}
921
922static TER
924 ApplyView& view,
925 AccountID const& uSenderID,
926 AccountID const& uReceiverID,
927 STAmount const& saAmount,
929 SLE::ref sponsorSle,
930 WaiveTransferFee waiveFee)
931{
932 if (view.rules().enabled(fixAMMv1_1))
933 {
934 if (saAmount < beast::kZero || saAmount.holds<MPTIssue>())
935 {
936 return tecINTERNAL; // LCOV_EXCL_LINE
937 }
938 }
939 else
940 {
941 // LCOV_EXCL_START
942 XRPL_ASSERT(
943 saAmount >= beast::kZero && !saAmount.holds<MPTIssue>(),
944 "xrpl::accountSendIOU : minimum amount and not MPT");
945 // LCOV_EXCL_STOP
946 }
947
948 /* If we aren't sending anything or if the sender is the same as the
949 * receiver then we don't need to do anything.
950 */
951 if (!saAmount || (uSenderID == uReceiverID))
952 return tesSUCCESS;
953
954 if (!saAmount.native())
955 {
956 STAmount saActual;
957
958 JLOG(j.trace()) << "accountSendIOU: " << to_string(uSenderID) << " -> "
959 << to_string(uReceiverID) << " : " << saAmount.getFullText();
960
962 view, uSenderID, uReceiverID, saAmount, saActual, j, sponsorSle, waiveFee);
963 }
964
965 /* XRP send which does not check reserve and can do pure adjustment.
966 * Note that sender or receiver may be null and this not a mistake; this
967 * setup is used during pathfinding and it is carefully controlled to
968 * ensure that transfers are balanced.
969 */
970 TER terResult(tesSUCCESS);
971
972 SLE::pointer const sender =
973 uSenderID != beast::kZero ? view.peek(keylet::account(uSenderID)) : SLE::pointer();
974 SLE::pointer const receiver =
975 uReceiverID != beast::kZero ? view.peek(keylet::account(uReceiverID)) : SLE::pointer();
976
977 if (auto stream = j.trace())
978 {
979 std::string senderBal("-");
980 std::string receiverBal("-");
981
982 if (sender)
983 senderBal = sender->getFieldAmount(sfBalance).getFullText();
984
985 if (receiver)
986 receiverBal = receiver->getFieldAmount(sfBalance).getFullText();
987
988 stream << "accountSendIOU> " << to_string(uSenderID) << " (" << senderBal << ") -> "
989 << to_string(uReceiverID) << " (" << receiverBal << ") : " << saAmount.getFullText();
990 }
991
992 if (sender)
993 {
994 if (sender->getFieldAmount(sfBalance) < saAmount)
995 {
996 // VFALCO Its laborious to have to mutate the
997 // TER based on params everywhere
998 // LCOV_EXCL_START
999 terResult = view.open() ? TER{telFAILED_PROCESSING} : TER{tecFAILED_PROCESSING};
1000 // LCOV_EXCL_STOP
1001 }
1002 else
1003 {
1004 auto const sndBal = sender->getFieldAmount(sfBalance);
1005 view.creditHookIOU(uSenderID, xrpAccount(), saAmount, sndBal);
1006
1007 // Decrement XRP balance.
1008 sender->setFieldAmount(sfBalance, sndBal - saAmount);
1009 view.update(sender);
1010 }
1011 }
1012
1013 if (tesSUCCESS == terResult && receiver)
1014 {
1015 // Increment XRP balance.
1016 auto const rcvBal = receiver->getFieldAmount(sfBalance);
1017 receiver->setFieldAmount(sfBalance, rcvBal + saAmount);
1018 view.creditHookIOU(xrpAccount(), uReceiverID, saAmount, -rcvBal);
1019
1020 view.update(receiver);
1021 }
1022
1023 if (auto stream = j.trace())
1024 {
1025 std::string senderBal("-");
1026 std::string receiverBal("-");
1027
1028 if (sender)
1029 senderBal = sender->getFieldAmount(sfBalance).getFullText();
1030
1031 if (receiver)
1032 receiverBal = receiver->getFieldAmount(sfBalance).getFullText();
1033
1034 stream << "accountSendIOU< " << to_string(uSenderID) << " (" << senderBal << ") -> "
1035 << to_string(uReceiverID) << " (" << receiverBal << ") : " << saAmount.getFullText();
1036 }
1037
1038 return terResult;
1039}
1040
1041static TER
1043 ApplyView& view,
1044 AccountID const& senderID,
1045 Issue const& issue,
1046 MultiplePaymentDestinations const& receivers,
1048 WaiveTransferFee waiveFee)
1049{
1050 XRPL_ASSERT_PARTS(
1051 receivers.size() > 1, "xrpl::accountSendMultiIOU", "multiple recipients provided");
1052
1053 if (!issue.native())
1054 {
1055 STAmount actual;
1056 JLOG(j.trace()) << "accountSendMultiIOU: " << to_string(senderID) << " sending "
1057 << receivers.size() << " IOUs";
1058
1059 return directSendNoLimitMultiIOU(view, senderID, issue, receivers, actual, j, waiveFee);
1060 }
1061
1062 /* XRP send which does not check reserve and can do pure adjustment.
1063 * Note that sender or receiver may be null and this not a mistake; this
1064 * setup could be used during pathfinding and it is carefully controlled to
1065 * ensure that transfers are balanced.
1066 */
1067
1068 SLE::pointer const sender =
1069 senderID != beast::kZero ? view.peek(keylet::account(senderID)) : SLE::pointer();
1070
1071 if (auto stream = j.trace())
1072 {
1073 std::string senderBal("-");
1074
1075 if (sender)
1076 senderBal = sender->getFieldAmount(sfBalance).getFullText();
1077
1078 stream << "accountSendMultiIOU> " << to_string(senderID) << " (" << senderBal << ") -> "
1079 << receivers.size() << " receivers.";
1080 }
1081
1082 // Failures return immediately.
1083 STAmount takeFromSender{issue};
1084 for (auto const& r : receivers)
1085 {
1086 auto const& receiverID = r.first;
1087 STAmount const amount{issue, r.second};
1088
1089 if (amount < beast::kZero)
1090 {
1091 return tecINTERNAL; // LCOV_EXCL_LINE
1092 }
1093
1094 /* If we aren't sending anything or if the sender is the same as the
1095 * receiver then we don't need to do anything.
1096 */
1097 if (!amount || (senderID == receiverID))
1098 continue;
1099
1100 SLE::pointer const receiver =
1101 receiverID != beast::kZero ? view.peek(keylet::account(receiverID)) : SLE::pointer();
1102
1103 if (auto stream = j.trace())
1104 {
1105 std::string receiverBal("-");
1106
1107 if (receiver)
1108 receiverBal = receiver->getFieldAmount(sfBalance).getFullText();
1109
1110 stream << "accountSendMultiIOU> " << to_string(senderID) << " -> "
1111 << to_string(receiverID) << " (" << receiverBal
1112 << ") : " << amount.getFullText();
1113 }
1114
1115 if (receiver)
1116 {
1117 // Increment XRP balance.
1118 auto const rcvBal = receiver->getFieldAmount(sfBalance);
1119 receiver->setFieldAmount(sfBalance, rcvBal + amount);
1120 view.creditHookIOU(xrpAccount(), receiverID, amount, -rcvBal);
1121
1122 view.update(receiver);
1123
1124 // Take what is actually sent
1125 takeFromSender += amount;
1126 }
1127
1128 if (auto stream = j.trace())
1129 {
1130 std::string receiverBal("-");
1131
1132 if (receiver)
1133 receiverBal = receiver->getFieldAmount(sfBalance).getFullText();
1134
1135 stream << "accountSendMultiIOU< " << to_string(senderID) << " -> "
1136 << to_string(receiverID) << " (" << receiverBal
1137 << ") : " << amount.getFullText();
1138 }
1139 }
1140
1141 if (sender)
1142 {
1143 if (sender->getFieldAmount(sfBalance) < takeFromSender)
1144 {
1145 return TER{tecFAILED_PROCESSING};
1146 }
1147 auto const sndBal = sender->getFieldAmount(sfBalance);
1148 view.creditHookIOU(senderID, xrpAccount(), takeFromSender, sndBal);
1149
1150 // Decrement XRP balance.
1151 sender->setFieldAmount(sfBalance, sndBal - takeFromSender);
1152 view.update(sender);
1153 }
1154
1155 if (auto stream = j.trace())
1156 {
1157 std::string senderBal("-");
1158
1159 if (sender)
1160 senderBal = sender->getFieldAmount(sfBalance).getFullText();
1161
1162 stream << "accountSendMultiIOU< " << to_string(senderID) << " (" << senderBal << ") -> "
1163 << receivers.size() << " receivers.";
1164 }
1165 return tesSUCCESS;
1166}
1167
1168static TER
1170 ApplyView& view,
1171 AccountID const& uSenderID,
1172 AccountID const& uReceiverID,
1173 STAmount const& saAmount,
1175{
1176 // Do not check MPT authorization here - it must have been checked earlier
1177 auto const mptID = keylet::mptokenIssuance(saAmount.get<MPTIssue>().getMptID());
1178 auto const& issuer = saAmount.getIssuer();
1179 auto sleIssuance = view.peek(mptID);
1180 if (!sleIssuance)
1181 return tecOBJECT_NOT_FOUND;
1182
1183 auto const maxAmount = maxMPTAmount(*sleIssuance);
1184 auto const outstanding = sleIssuance->getFieldU64(sfOutstandingAmount);
1185 auto const available = availableMPTAmount(*sleIssuance);
1186 auto const amt = saAmount.mpt().value();
1187
1188 if (uSenderID == issuer)
1189 {
1190 if (view.rules().enabled(featureMPTokensV2))
1191 {
1192 if (isMPTOverflow(amt, outstanding, maxAmount, AllowMPTOverflow::Yes))
1193 return tecPATH_DRY;
1194 }
1195 (*sleIssuance)[sfOutstandingAmount] += amt;
1196 view.update(sleIssuance);
1197 }
1198 else
1199 {
1200 auto const mptokenID = keylet::mptoken(mptID.key, uSenderID);
1201 if (auto sle = view.peek(mptokenID))
1202 {
1203 auto const senderBalance = sle->getFieldU64(sfMPTAmount);
1204 if (senderBalance < amt)
1205 return tecINSUFFICIENT_FUNDS;
1206 view.creditHookMPT(uSenderID, uReceiverID, saAmount, (*sle)[sfMPTAmount], available);
1207 (*sle)[sfMPTAmount] = senderBalance - amt;
1208 view.update(sle);
1209 }
1210 else
1211 {
1212 return tecNO_AUTH;
1213 }
1214 }
1215
1216 if (uReceiverID == issuer)
1217 {
1218 if (outstanding >= amt)
1219 {
1220 sleIssuance->setFieldU64(sfOutstandingAmount, outstanding - amt);
1221 view.update(sleIssuance);
1222 }
1223 else
1224 {
1225 return tecINTERNAL; // LCOV_EXCL_LINE
1226 }
1227 }
1228 else
1229 {
1230 auto const mptokenID = keylet::mptoken(mptID.key, uReceiverID);
1231 if (auto sle = view.peek(mptokenID))
1232 {
1233 if (view.rules().enabled(featureMPTokensV2))
1234 {
1235 if ((*sle)[sfMPTAmount] > (std::numeric_limits<std::uint64_t>::max() - amt))
1236 {
1237 return tecINTERNAL; // LCOV_EXCL_LINE
1238 }
1239 }
1240 view.creditHookMPT(uSenderID, uReceiverID, saAmount, (*sle)[sfMPTAmount], available);
1241 (*sle)[sfMPTAmount] += amt;
1242 view.update(sle);
1243 }
1244 else
1245 {
1246 return tecNO_AUTH;
1247 }
1248 }
1249
1250 return tesSUCCESS;
1251}
1252
1253static TER
1255 ApplyView& view,
1256 AccountID const& uSenderID,
1257 AccountID const& uReceiverID,
1258 STAmount const& saAmount,
1259 STAmount& saActual,
1261 WaiveTransferFee waiveFee,
1262 AllowMPTOverflow allowOverflow)
1263{
1264 XRPL_ASSERT(uSenderID != uReceiverID, "xrpl::directSendNoLimitMPT : sender is not receiver");
1265
1266 // Safe to get MPT since directSendNoLimitMPT is only called by accountSendMPT
1267 auto const& issuer = saAmount.getIssuer();
1268
1269 auto const sle = view.read(keylet::mptokenIssuance(saAmount.get<MPTIssue>().getMptID()));
1270 if (!sle)
1271 return tecOBJECT_NOT_FOUND;
1272
1273 if (uSenderID == issuer || uReceiverID == issuer)
1274 {
1275 // if sender is issuer, check that the new OutstandingAmount will not
1276 // exceed MaximumAmount
1277 if (uSenderID == issuer)
1278 {
1279 auto const sendAmount = saAmount.mpt().value();
1280 auto const maxAmount = maxMPTAmount(*sle);
1281 auto const outstanding = sle->getFieldU64(sfOutstandingAmount);
1282 auto const mptokensV2 = view.rules().enabled(featureMPTokensV2);
1283 allowOverflow = (allowOverflow == AllowMPTOverflow::Yes && mptokensV2)
1286 if (isMPTOverflow(sendAmount, outstanding, maxAmount, allowOverflow))
1287 return tecPATH_DRY;
1288 }
1289
1290 // Direct send: redeeming MPTs and/or sending own MPTs.
1291 auto const ter = directSendNoFeeMPT(view, uSenderID, uReceiverID, saAmount, j);
1292 if (!isTesSuccess(ter))
1293 return ter;
1294 saActual = saAmount;
1295 return tesSUCCESS;
1296 }
1297
1298 // Sending 3rd party MPTs: transit.
1299 saActual = (waiveFee == WaiveTransferFee::Yes)
1300 ? saAmount
1301 : multiply(saAmount, transferRate(view, saAmount.get<MPTIssue>().getMptID()));
1302
1303 JLOG(j.debug()) << "directSendNoLimitMPT> " << to_string(uSenderID) << " - > "
1304 << to_string(uReceiverID) << " : deliver=" << saAmount.getFullText()
1305 << " cost=" << saActual.getFullText();
1306
1307 if (auto const terResult = directSendNoFeeMPT(view, issuer, uReceiverID, saAmount, j);
1308 !isTesSuccess(terResult))
1309 return terResult;
1310
1311 return directSendNoFeeMPT(view, uSenderID, issuer, saActual, j);
1312}
1313
1314static TER
1316 ApplyView& view,
1317 AccountID const& senderID,
1318 MPTIssue const& mptIssue,
1319 MultiplePaymentDestinations const& receivers,
1320 STAmount& actual,
1322 WaiveTransferFee waiveFee)
1323{
1324 auto const& issuer = mptIssue.getIssuer();
1325
1326 auto const sle = view.read(keylet::mptokenIssuance(mptIssue.getMptID()));
1327 if (!sle)
1328 return tecOBJECT_NOT_FOUND;
1329
1330 // For the issuer-as-sender case, track the running total to validate
1331 // against MaximumAmount. The read-only SLE (view.read) is not updated
1332 // by directSendNoFeeMPT, so a per-iteration SLE read would be stale.
1333 // Use uint64_t, not STAmount, to keep MaximumAmount comparisons in exact
1334 // integer arithmetic. STAmount implicitly converts to Number, whose
1335 // small-scale mantissa (~16 digits) can lose precision for values near
1336 // kMaxMpTokenAmount (19 digits).
1337 std::uint64_t totalSendAmount{0};
1338 std::uint64_t const maximumAmount = sle->at(~sfMaximumAmount).value_or(kMaxMpTokenAmount);
1339 std::uint64_t const outstandingAmount = sle->getFieldU64(sfOutstandingAmount);
1340
1341 // actual accumulates the total cost to the sender (includes transfer
1342 // fees for third-party transit sends). takeFromSender accumulates only
1343 // the transit portion that is debited to the issuer in bulk after the
1344 // loop. They diverge when there are transfer fees.
1345 STAmount takeFromSender{mptIssue};
1346 actual = takeFromSender;
1347
1348 for (auto const& [receiverID, amt] : receivers)
1349 {
1350 STAmount const amount{mptIssue, amt};
1351
1352 if (amount < beast::kZero)
1353 return tecINTERNAL; // LCOV_EXCL_LINE
1354
1355 if (!amount || senderID == receiverID)
1356 continue;
1357
1358 if (senderID == issuer || receiverID == issuer)
1359 {
1360 if (senderID == issuer)
1361 {
1362 XRPL_ASSERT_PARTS(
1363 takeFromSender == beast::kZero,
1364 "xrpl::directSendNoLimitMultiMPT",
1365 "sender == issuer, takeFromSender == zero");
1366
1367 std::uint64_t const sendAmount = amount.mpt().value();
1368
1369 if (view.rules().enabled(fixCleanup3_1_3))
1370 {
1371 // Post-fixCleanup3_1_3: aggregate MaximumAmount
1372 // check. WARNING: the order of conditions is
1373 // critical — each guards the subtraction in the
1374 // next against unsigned underflow. Do not reorder.
1375 bool const exceedsMaximumAmount =
1376 // This send alone exceeds the max cap
1377 sendAmount > maximumAmount ||
1378 // The aggregate of all sends exceeds the max cap
1379 totalSendAmount > maximumAmount - sendAmount ||
1380 // Outstanding + aggregate exceeds the max cap
1381 outstandingAmount > maximumAmount - sendAmount - totalSendAmount;
1382
1383 if (exceedsMaximumAmount)
1384 return tecPATH_DRY;
1385 totalSendAmount += sendAmount;
1386 }
1387 else
1388 {
1389 // Pre-fixCleanup3_1_3: per-iteration MaximumAmount
1390 // check. Reads sfOutstandingAmount from a stale
1391 // view.read() snapshot — incorrect for multi-destination
1392 // sends but retained for ledger replay compatibility.
1393 if (sendAmount > maximumAmount ||
1394 outstandingAmount > maximumAmount - sendAmount)
1395 return tecPATH_DRY;
1396 }
1397 }
1398
1399 // Direct send: redeeming MPTs and/or sending own MPTs.
1400 if (auto const ter = directSendNoFeeMPT(view, senderID, receiverID, amount, j);
1401 !isTesSuccess(ter))
1402 return ter;
1403 actual += amount;
1404 // Do not add amount to takeFromSender, because directSendNoFeeMPT took it.
1405
1406 continue;
1407 }
1408
1409 // Sending 3rd party MPTs: transit.
1410 STAmount const actualSend = (waiveFee == WaiveTransferFee::Yes)
1411 ? amount
1412 : multiply(amount, transferRate(view, amount.get<MPTIssue>().getMptID()));
1413 actual += actualSend;
1414 takeFromSender += actualSend;
1415
1416 JLOG(j.debug()) << "directSendNoLimitMultiMPT> " << to_string(senderID) << " - > "
1417 << to_string(receiverID) << " : deliver=" << amount.getFullText()
1418 << " cost=" << actualSend.getFullText();
1419
1420 if (auto const ter = directSendNoFeeMPT(view, issuer, receiverID, amount, j);
1421 !isTesSuccess(ter))
1422 return ter;
1423 }
1424 if (senderID != issuer && takeFromSender)
1425 {
1426 if (auto const ter = directSendNoFeeMPT(view, senderID, issuer, takeFromSender, j);
1427 !isTesSuccess(ter))
1428 return ter;
1429 }
1430
1431 return tesSUCCESS;
1432}
1433
1434static TER
1436 ApplyView& view,
1437 AccountID const& uSenderID,
1438 AccountID const& uReceiverID,
1439 STAmount const& saAmount,
1441 WaiveTransferFee waiveFee,
1442 AllowMPTOverflow allowOverflow)
1443{
1444 XRPL_ASSERT(
1445 saAmount >= beast::kZero && saAmount.holds<MPTIssue>(),
1446 "xrpl::accountSendMPT : minimum amount and MPT");
1447
1448 /* If we aren't sending anything or if the sender is the same as the
1449 * receiver then we don't need to do anything.
1450 */
1451 if (!saAmount || (uSenderID == uReceiverID))
1452 return tesSUCCESS;
1453
1454 STAmount saActual{saAmount.asset()};
1455
1456 return directSendNoLimitMPT(
1457 view, uSenderID, uReceiverID, saAmount, saActual, j, waiveFee, allowOverflow);
1458}
1459
1460static TER
1462 ApplyView& view,
1463 AccountID const& senderID,
1464 MPTIssue const& mptIssue,
1465 MultiplePaymentDestinations const& receivers,
1467 WaiveTransferFee waiveFee)
1468{
1469 STAmount actual;
1470
1471 return directSendNoLimitMultiMPT(view, senderID, mptIssue, receivers, actual, j, waiveFee);
1472}
1473
1474//------------------------------------------------------------------------------
1475//
1476// Public Dispatcher Functions
1477//
1478//------------------------------------------------------------------------------
1479
1480TER
1482 ApplyView& view,
1483 AccountID const& uSenderID,
1484 AccountID const& uReceiverID,
1485 STAmount const& saAmount,
1486 bool bCheckIssuer,
1488{
1489 return saAmount.asset().visit(
1490 [&](Issue const&) {
1491 return directSendNoFeeIOU(view, uSenderID, uReceiverID, saAmount, bCheckIssuer, {}, j);
1492 },
1493 [&](MPTIssue const&) {
1494 XRPL_ASSERT(!bCheckIssuer, "xrpl::directSendNoFee : not checking issuer");
1495 return directSendNoFeeMPT(view, uSenderID, uReceiverID, saAmount, j);
1496 });
1497}
1498
1499TER
1501 ApplyView& view,
1502 AccountID const& uSenderID,
1503 AccountID const& uReceiverID,
1504 STAmount const& saAmount,
1506 SLE::ref sponsorSle,
1507 WaiveTransferFee waiveFee,
1508 AllowMPTOverflow allowOverflow)
1509{
1510 return saAmount.asset().visit(
1511 [&](Issue const&) {
1512 return accountSendIOU(view, uSenderID, uReceiverID, saAmount, j, sponsorSle, waiveFee);
1513 },
1514 [&](MPTIssue const&) {
1515 return accountSendMPT(
1516 view, uSenderID, uReceiverID, saAmount, j, waiveFee, allowOverflow);
1517 });
1518}
1519
1520TER
1522 ApplyView& view,
1523 AccountID const& senderID,
1524 Asset const& asset,
1525 MultiplePaymentDestinations const& receivers,
1527 WaiveTransferFee waiveFee)
1528{
1529 XRPL_ASSERT_PARTS(
1530 receivers.size() > 1, "xrpl::accountSendMulti", "multiple recipients provided");
1531 return asset.visit(
1532 [&](Issue const& issue) {
1533 return accountSendMultiIOU(view, senderID, issue, receivers, j, waiveFee);
1534 },
1535 [&](MPTIssue const& issue) {
1536 return accountSendMultiMPT(view, senderID, issue, receivers, j, waiveFee);
1537 });
1538}
1539
1540TER
1542 ApplyView& view,
1543 AccountID const& from,
1544 AccountID const& to,
1545 STAmount const& amount,
1547{
1548 XRPL_ASSERT(from != beast::kZero, "xrpl::transferXRP : nonzero from account");
1549 XRPL_ASSERT(to != beast::kZero, "xrpl::transferXRP : nonzero to account");
1550 XRPL_ASSERT(from != to, "xrpl::transferXRP : sender is not receiver");
1551 XRPL_ASSERT(amount.native(), "xrpl::transferXRP : amount is XRP");
1552
1553 SLE::pointer const sender = view.peek(keylet::account(from));
1554 SLE::pointer const receiver = view.peek(keylet::account(to));
1555 if (!sender || !receiver)
1556 return tefINTERNAL; // LCOV_EXCL_LINE
1557
1558 JLOG(j.trace()) << "transferXRP: " << to_string(from) << " -> " << to_string(to)
1559 << ") : " << amount.getFullText();
1560
1561 if (sender->getFieldAmount(sfBalance) < amount)
1562 {
1563 // VFALCO Its unfortunate we have to keep
1564 // mutating these TER everywhere
1565 // FIXME: this logic should be moved to callers maybe?
1566 // LCOV_EXCL_START
1568 // LCOV_EXCL_STOP
1569 }
1570
1571 // Decrement XRP balance.
1572 sender->setFieldAmount(sfBalance, sender->getFieldAmount(sfBalance) - amount);
1573 view.update(sender);
1574
1575 receiver->setFieldAmount(sfBalance, receiver->getFieldAmount(sfBalance) + amount);
1576 view.update(receiver);
1577
1578 return tesSUCCESS;
1579}
1580
1581} // namespace xrpl
T any_of(T... args)
A generic endpoint for log messages.
Definition Journal.h:44
Stream debug() const
Definition Journal.h:344
Stream trace() const
Severity stream access functions.
Definition Journal.h:338
Writeable view to a ledger, for applying a transaction.
Definition ApplyView.h:134
virtual SLE::pointer peek(Keylet const &k)=0
Prepare to modify the SLE associated with key.
virtual void creditHookIOU(AccountID const &from, AccountID const &to, STAmount const &amount, STAmount const &preCreditBalance)
Definition ApplyView.h:241
virtual void creditHookMPT(AccountID const &from, AccountID const &to, STAmount const &amount, std::uint64_t preCreditBalanceHolder, std::int64_t preCreditBalanceIssuer)
Definition ApplyView.h:251
virtual void update(SLE::ref sle)=0
Indicate changes to a peeked SLE.
constexpr auto visit(Visitors &&... visitors) const -> decltype(auto)
Definition Asset.h:117
constexpr TIss const & get() const
AccountID const & getIssuer() const
Definition Asset.cpp:21
constexpr bool holds() const
Definition Asset.h:177
constexpr value_type const & value() const
Definition Asset.h:201
A currency issued by an account.
Definition Issue.h:18
Currency currency
Definition Issue.h:20
AccountID account
Definition Issue.h:21
bool native() const
Definition Issue.cpp:54
AccountID const & getIssuer() const
Definition Issue.h:30
constexpr value_type value() const
Returns the underlying value.
Definition MPTAmount.h:134
constexpr MPTID const & getMptID() const
Definition MPTIssue.h:43
AccountID const & getIssuer() const
Definition MPTIssue.cpp:29
A view into a ledger.
Definition ReadView.h:41
virtual Rules const & rules() const =0
Returns the tx processing rules.
virtual STAmount balanceHookMPT(AccountID const &account, MPTIssue const &issue, std::int64_t amount) const
Definition ReadView.h:190
virtual SLE::const_pointer read(Keylet const &k) const =0
Return the state item associated with a key.
virtual bool open() const =0
Returns true if this reflects an open ledger.
virtual STAmount balanceHookIOU(AccountID const &account, AccountID const &issuer, STAmount const &amount) const
Definition ReadView.h:180
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition Rules.cpp:180
constexpr bool holds() const noexcept
Definition STAmount.h:478
constexpr TIss const & get() const
std::string getFullText() const override
Definition STAmount.cpp:636
void negate()
Definition STAmount.h:586
bool native() const noexcept
Definition STAmount.h:471
Asset const & asset() const
Definition STAmount.h:496
MPTAmount mpt() const
Definition STAmount.cpp:301
AccountID const & getIssuer() const
Definition STAmount.h:516
static constexpr std::uint64_t kMaxValue
Definition STAmount.h:67
static constexpr int kMaxOffset
Definition STAmount.h:62
std::shared_ptr< STLedgerEntry > const & ref
std::shared_ptr< STLedgerEntry > pointer
std::shared_ptr< STLedgerEntry const > const & const_ref
std::shared_ptr< STLedgerEntry const > const_pointer
void setFieldAmount(SField const &field, STAmount const &)
Definition STObject.cpp:803
STAmount const & getFieldAmount(SField const &field) const
Definition STObject.cpp:657
T max(T... args)
constexpr Zero kZero
Definition Zero.h:30
Keylet amm(Asset const &issue1, Asset const &issue2) noexcept
AMM entry.
Definition Indexes.cpp:441
Keylet mptoken(MPTID const &issuanceID, AccountID const &holder) noexcept
Definition Indexes.cpp:543
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:198
Keylet mptokenIssuance(MPTID const &issuanceID) noexcept
Definition Indexes.cpp:537
Keylet trustLine(AccountID const &id0, AccountID const &id1, Currency const &currency) noexcept
The index of a trust line for a given currency.
Definition Indexes.cpp:253
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
TER checkDeepFrozen(ReadView const &view, AccountID const &account, Issue const &issue)
@ telFAILED_PROCESSING
Definition TER.h:42
@ terNO_RIPPLE
Definition TER.h:220
@ terNO_ACCOUNT
Definition TER.h:213
std::int64_t maxMPTAmount(SLE const &sleIssuance)
XRPAmount xrpLiquid(ReadView const &view, AccountID const &id, std::int32_t ownerCountAdj, beast::Journal j)
Calculate liquid XRP balance for an account.
FreezeHandling
Controls the treatment of frozen account balances.
static TER directSendNoLimitMultiIOU(ApplyView &view, AccountID const &senderID, Issue const &issue, MultiplePaymentDestinations const &receivers, STAmount &actual, beast::Journal j, WaiveTransferFee waiveFee)
SpendableHandling
Controls whether to include the account's full spendable balance.
TER checkIndividualFrozen(ReadView const &view, AccountID const &account, Asset const &asset)
static TER directSendNoLimitIOU(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, STAmount &saActual, beast::Journal j, SLE::ref sponsorSle, WaiveTransferFee waiveFee)
bool isXRP(AccountID const &c)
Definition AccountID.h:84
bool isIndividualFrozen(ReadView const &view, AccountID const &account, MPTIssue const &mptIssue)
Returns true if account's MPToken for mptIssue carries the individual-lock flag (lsfMPTLocked).
TER accountSend(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &saAmount, beast::Journal j, SLE::ref sponsorSle={}, WaiveTransferFee waiveFee=WaiveTransferFee::No, AllowMPTOverflow allowOverflow=AllowMPTOverflow::No)
Calls static accountSendIOU if saAmount represents Issue.
TER checkDepositFreeze(ReadView const &view, AccountID const &srcAcct, AccountID const &pseudoAcct, Asset const &asset)
Checks freeze compliance for depositing an asset into a pseudo-account (e.g.
AllowMPTOverflow
Controls whether accountSend is allowed to overflow OutstandingAmount *.
static TER accountSendMultiIOU(ApplyView &view, AccountID const &senderID, Issue const &issue, MultiplePaymentDestinations const &receivers, beast::Journal j, WaiveTransferFee waiveFee)
TER removeEmptyHolding(ApplyViewContext ctx, AccountID const &accountID, MPTIssue const &mptIssue, beast::Journal journal)
TER checkFrozen(ReadView const &view, AccountID const &account, Issue const &issue)
@ tefINTERNAL
Definition TER.h:165
BaseUInt< 160, detail::CurrencyTag > Currency
Currency is a hash representing a specific currency.
Definition UintTypes.h:42
TER addEmptyHolding(ApplyViewContext ctx, AccountID const &accountID, XRPAmount priorBalance, MPTIssue const &mptIssue, beast::Journal journal)
WaiveTransferFee
TER trustDelete(ApplyView &view, SLE::ref sleRippleState, AccountID const &uLowAccountID, AccountID const &uHighAccountID, beast::Journal j)
TER canTransfer(ReadView const &view, MPTIssue const &mptIssue, AccountID const &from, AccountID const &to, WaiveMPTCanTransfer waive=WaiveMPTCanTransfer::No, std::uint8_t depth=0)
Check whether to may receive the given MPT from from.
TER trustCreate(ApplyView &view, bool const bSrcHigh, AccountID const &uSrcAccountID, AccountID const &uDstAccountID, uint256 const &uIndex, SLE::ref sleAccount, bool const bAuth, bool const bNoRipple, bool const bFreeze, bool bDeepFreeze, STAmount const &saBalance, STAmount const &saLimit, std::uint32_t uQualityIn, std::uint32_t uQualityOut, SLE::ref sponsorSle, beast::Journal j)
Create a trust line.
bool isDeepFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:651
bool isVaultPseudoAccountFrozen(ReadView const &view, AccountID const &account, MPTIssue const &mptShare, std::uint8_t depth)
Definition View.cpp:65
STAmount accountFunds(ReadView const &view, AccountID const &id, STAmount const &saDefault, FreezeHandling freezeHandling, beast::Journal j)
bool isGlobalFrozen(ReadView const &view, AccountID const &issuer)
Check if the issuer has the global freeze flag set.
TER canAddHolding(ReadView const &view, MPTIssue const &mptIssue)
static STAmount getTrustLineBalance(ReadView const &view, SLE::const_ref sle, AccountID const &account, Currency const &currency, AccountID const &issuer, bool includeOppositeLimit, beast::Journal j)
void removeSponsorFromLedgerEntry(SLE::ref sle, SF_ACCOUNT const &field=sfSponsor)
Remove the reserve sponsor field from a ledger entry.
AuthHandling
Controls the treatment of unauthorized MPT balances.
Rate transferRate(ReadView const &view, AccountID const &issuer)
Returns IOU issuer transfer fee as Rate.
static SLE::const_pointer getLineIfUsable(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j)
void decreaseOwnerCount(ApplyView &view, SLE::ref accountSle, SLE::ref sponsorSle, std::uint32_t count, beast::Journal j)
Decrease owner-count fields when the caller supplies the sponsor.
TER directSendNoFee(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, bool bCheckIssuer, beast::Journal j)
Calls static directSendNoFeeIOU if saAmount represents Issue.
std::int64_t availableMPTAmount(SLE const &sleIssuance)
TER transferXRP(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &amount, beast::Journal j)
WaiveMPTCanTransfer
Controls whether canTransfer enforces lsfMPTCanTransfer on MPTs.
bool isFrozen(ReadView const &view, AccountID const &account, MPTIssue const &mptIssue, std::uint8_t depth=0)
static TER accountSendIOU(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, beast::Journal j, SLE::ref sponsorSle, WaiveTransferFee waiveFee)
TER checkGlobalFrozen(ReadView const &view, Asset const &asset)
BaseUInt< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition AccountID.h:34
AccountID const & noAccount()
A placeholder for empty accounts.
static TER directSendNoLimitMPT(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, STAmount &saActual, beast::Journal j, WaiveTransferFee waiveFee, AllowMPTOverflow allowOverflow)
static TER accountSendMPT(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee, AllowMPTOverflow allowOverflow)
bool isTesSuccess(TER x) noexcept
Definition TER.h:676
bool isAnyFrozen(ReadView const &view, std::initializer_list< AccountID > const &accounts, MPTIssue const &mptIssue, std::uint8_t depth=0)
TERSubset< CanCvtToTER > TER
Definition TER.h:647
bool isLPTokenFrozen(ReadView const &view, AccountID const &account, Asset const &asset, Asset const &asset2)
Definition View.cpp:132
TER requireAuth(ReadView const &view, MPTIssue const &mptIssue, AccountID const &account, AuthType authType=AuthType::Legacy, std::uint8_t depth=0)
Check if the account lacks required authorization for MPT.
static TER directSendNoLimitMultiMPT(ApplyView &view, AccountID const &senderID, MPTIssue const &mptIssue, MultiplePaymentDestinations const &receivers, STAmount &actual, beast::Journal j, WaiveTransferFee waiveFee)
AccountID const & xrpAccount()
Compute AccountID from public key.
@ tecLOCKED
Definition TER.h:361
@ tecPATH_DRY
Definition TER.h:297
@ tecOBJECT_NOT_FOUND
Definition TER.h:329
@ tecNO_AUTH
Definition TER.h:303
@ tecINTERNAL
Definition TER.h:313
@ tecFAILED_PROCESSING
Definition TER.h:289
@ tecFROZEN
Definition TER.h:306
@ tecINSUFFICIENT_FUNDS
Definition TER.h:328
STAmount multiply(STAmount const &amount, Number const &frac, Number::RoundingMode rm)
static TER accountSendMultiMPT(ApplyView &view, AccountID const &senderID, MPTIssue const &mptIssue, MultiplePaymentDestinations const &receivers, beast::Journal j, WaiveTransferFee waiveFee)
SLE::pointer getLedgerEntryReserveSponsor(ApplyView &view, SLE::const_ref sle, SF_ACCOUNT const &field=sfSponsor)
Return a mutable SLE for the reserve sponsor recorded on a ledger entry.
std::vector< std::pair< AccountID, Number > > MultiplePaymentDestinations
bool isPseudoAccount(SLE::const_pointer sleAcct, std::set< SField const * > const &pseudoFieldFilter={})
Returns true if and only if sleAcct is a pseudo-account or specific pseudo-accounts in pseudoFieldFil...
TER accountSendMulti(ApplyView &view, AccountID const &senderID, Asset const &asset, MultiplePaymentDestinations const &receivers, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No)
Like accountSend, except one account is sending multiple payments (with the same asset!...
constexpr std::uint64_t kMaxMpTokenAmount
The maximum amount of MPTokenIssuance.
Definition Protocol.h:296
TER checkWithdrawFreeze(ReadView const &view, AccountID const &pseudoAcct, AccountID const &submitterAcct, AccountID const &dstAcct, Asset const &asset)
Checks freeze compliance for withdrawing an asset from a pseudo-account (e.g.
bool isMPTOverflow(std::int64_t sendAmount, std::uint64_t outstandingAmount, std::int64_t maximumAmount, AllowMPTOverflow allowOverflow)
Checks for two types of OutstandingAmount overflow during a send operation.
static TER directSendNoFeeIOU(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, bool bCheckIssuer, SLE::ref sponsorSle, beast::Journal j)
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j, SpendableHandling includeFullBalance=SpendableHandling::SimpleBalance)
@ tesSUCCESS
Definition TER.h:245
static TER directSendNoFeeMPT(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, beast::Journal j)
T size(T... args)
Bundles the mutable ledger view and the transaction being applied.
Definition ApplyView.h:444
T visit(T... args)