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