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 // An LPToken whose AMM pool contains an MPT that forbids transfers is not
314 // spendable. Issuer is the LPToken's AMM account; canTransferLPToken is
315 // a no-op for non-AMM issuers and non-MPT pool assets, so this is implicitly
316 // gated by featureMPTokensV2.
317 if (!isTesSuccess(canTransferLPToken(view, account, account, issuer)))
318 {
319 return nullptr;
320 }
321 }
322
323 return sle;
324}
325
326static STAmount
328 ReadView const& view,
329 SLE::ConstRef sle,
330 AccountID const& account,
331 Currency const& currency,
332 AccountID const& issuer,
333 bool includeOppositeLimit,
335{
336 STAmount amount;
337 if (sle)
338 {
339 amount = sle->getFieldAmount(sfBalance);
340 bool const accountHigh = account > issuer;
341 auto const& oppositeField = accountHigh ? sfLowLimit : sfHighLimit;
342 if (accountHigh)
343 {
344 // Put balance in account terms.
345 amount.negate();
346 }
347 if (includeOppositeLimit)
348 {
349 amount += sle->getFieldAmount(oppositeField);
350 }
351 amount.get<Issue>().account = issuer;
352 }
353 else
354 {
355 amount.clear(Issue{currency, issuer});
356 }
357
358 JLOG(j.trace()) << "getTrustLineBalance:" << " account=" << to_string(account)
359 << " amount=" << amount.getFullText();
360
361 return view.balanceHookIOU(account, issuer, amount);
362}
363
364STAmount
366 ReadView const& view,
367 AccountID const& account,
368 Currency const& currency,
369 AccountID const& issuer,
370 FreezeHandling zeroIfFrozen,
372 SpendableHandling includeFullBalance)
373{
374 STAmount const amount;
375 if (isXRP(currency))
376 {
377 return {xrpLiquid(view, account, 0, j)};
378 }
379
380 bool const returnSpendable = (includeFullBalance == SpendableHandling::FullBalance);
381 if (returnSpendable && account == issuer)
382 {
383 // If the account is the issuer, then their limit is effectively
384 // infinite
385 return STAmount{Issue{currency, issuer}, STAmount::kMaxValue, STAmount::kMaxOffset};
386 }
387
388 // IOU: Return balance on trust line modulo freeze
389 SLE::const_pointer const sle =
390 getLineIfUsable(view, account, currency, issuer, zeroIfFrozen, j);
391
392 return getTrustLineBalance(view, sle, account, currency, issuer, returnSpendable, j);
393}
394
395STAmount
397 ReadView const& view,
398 AccountID const& account,
399 Issue const& issue,
400 FreezeHandling zeroIfFrozen,
402 SpendableHandling includeFullBalance)
403{
404 return accountHolds(
405 view, account, issue.currency, issue.account, zeroIfFrozen, j, includeFullBalance);
406}
407
408STAmount
410 ReadView const& view,
411 AccountID const& account,
412 MPTIssue const& mptIssue,
413 FreezeHandling zeroIfFrozen,
414 AuthHandling zeroIfUnauthorized,
416 SpendableHandling includeFullBalance)
417{
418 bool const returnSpendable = (includeFullBalance == SpendableHandling::FullBalance);
419 STAmount amount{mptIssue};
420 auto const& issuer = mptIssue.getIssuer();
421 bool const mptokensV2 = view.rules().enabled(featureMPTokensV2);
422
423 if (returnSpendable && account == mptIssue.getIssuer())
424 {
425 // if the account is the issuer, and the issuance exists, their limit is
426 // the issuance limit minus the outstanding value
427 auto const issuance = view.read(keylet::mptokenIssuance(mptIssue.getMptID()));
428
429 if (!issuance)
430 {
431 return amount;
432 }
433 auto const available = availableMPTAmount(*issuance);
434 if (!mptokensV2)
435 return STAmount{mptIssue, available};
436 return view.balanceHookMPT(issuer, mptIssue, available);
437 }
438
439 auto const sleMpt = view.read(keylet::mptoken(mptIssue.getMptID(), account));
440
441 if (!sleMpt ||
442 (zeroIfFrozen == FreezeHandling::ZeroIfFrozen && isFrozen(view, account, *sleMpt)))
443 {
444 amount.clear(mptIssue);
445 }
446 else
447 {
448 amount = STAmount{mptIssue, sleMpt->getFieldU64(sfMPTAmount)};
449
450 // Only if auth check is needed, as it needs to do an additional read
451 // operation. Note featureSingleAssetVault will affect error codes.
452 if (zeroIfUnauthorized == AuthHandling::ZeroIfUnauthorized &&
453 (view.rules().enabled(featureSingleAssetVault) ||
454 view.rules().enabled(featureConfidentialTransfer)))
455 {
456 if (auto const err = requireAuth(view, mptIssue, account, AuthType::StrongAuth);
457 !isTesSuccess(err))
458 amount.clear(mptIssue);
459 }
460 else if (zeroIfUnauthorized == AuthHandling::ZeroIfUnauthorized)
461 {
462 auto const sleIssuance = view.read(keylet::mptokenIssuance(mptIssue.getMptID()));
463
464 // if auth is enabled on the issuance and mpt is not authorized,
465 // clear amount
466 if (sleIssuance && sleIssuance->isFlag(lsfMPTRequireAuth) &&
467 !sleMpt->isFlag(lsfMPTAuthorized))
468 amount.clear(mptIssue);
469 }
470 }
471
472 if (view.rules().enabled(featureMPTokensV2))
473 return view.balanceHookMPT(account, mptIssue, amount.mpt().value());
474 return amount;
475}
476
477[[nodiscard]] STAmount
479 ReadView const& view,
480 AccountID const& account,
481 Asset const& asset,
482 FreezeHandling zeroIfFrozen,
483 AuthHandling zeroIfUnauthorized,
485 SpendableHandling includeFullBalance)
486{
487 return asset.visit(
488 [&](Issue const& issue) {
489 return accountHolds(view, account, issue, zeroIfFrozen, j, includeFullBalance);
490 },
491 [&](MPTIssue const& issue) {
492 return accountHolds(
493 view, account, issue, zeroIfFrozen, zeroIfUnauthorized, j, includeFullBalance);
494 });
495}
496
497STAmount
499 ReadView const& view,
500 AccountID const& id,
501 STAmount const& saDefault,
502 FreezeHandling freezeHandling,
504{
505 XRPL_ASSERT(saDefault.holds<Issue>(), "xrpl::accountFunds: saDefault holds Issue");
506
507 if (!saDefault.native() && saDefault.getIssuer() == id)
508 return saDefault;
509
510 return accountHolds(
511 view, id, saDefault.get<Issue>().currency, saDefault.getIssuer(), freezeHandling, j);
512}
513
514STAmount
516 ReadView const& view,
517 AccountID const& id,
518 STAmount const& saDefault,
519 FreezeHandling freezeHandling,
520 AuthHandling authHandling,
522{
523 return saDefault.asset().visit(
524 [&](Issue const&) { return accountFunds(view, id, saDefault, freezeHandling, j); },
525 [&](MPTIssue const&) {
526 return accountHolds(
527 view,
528 id,
529 saDefault.asset(),
530 freezeHandling,
531 authHandling,
532 j,
534 });
535}
536
537Rate
538transferRate(ReadView const& view, Asset const& asset)
539{
540 return asset.visit(
541 [&](Issue const& issue) { return transferRate(view, issue.getIssuer()); },
542 [&](MPTIssue const& issue) { return transferRate(view, issue.getMptID()); });
543}
544
545Rate
546transferRate(ReadView const& view, STAmount const& amount)
547{
548 return transferRate(view, amount.asset());
549}
550
551//------------------------------------------------------------------------------
552//
553// Holding operations
554//
555//------------------------------------------------------------------------------
556
557[[nodiscard]] TER
558canAddHolding(ReadView const& view, Issue const& issue)
559{
560 if (issue.native())
561 {
562 return tesSUCCESS; // No special checks for XRP
563 }
564
565 auto const issuer = view.read(keylet::account(issue.getIssuer()));
566 if (!issuer)
567 {
568 return terNO_ACCOUNT;
569 }
570 if (!issuer->isFlag(lsfDefaultRipple))
571 {
572 return terNO_RIPPLE;
573 }
574
575 return tesSUCCESS;
576}
577
578[[nodiscard]] TER
579canAddHolding(ReadView const& view, Asset const& asset)
580{
581 return std::visit(
582 [&]<ValidIssueType TIss>(TIss const& issue) -> TER { return canAddHolding(view, issue); },
583 asset.value());
584}
585
586[[nodiscard]] bool
587holdingExists(ReadView const& view, AccountID const& account, Issue const& issue)
588{
589 if (issue.native() || account == issue.getIssuer())
590 return true;
591 return view.exists(keylet::trustLine(account, issue));
592}
593
594[[nodiscard]] bool
595holdingExists(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue)
596{
597 if (account == mptIssue.getIssuer())
598 return true;
599 return view.exists(keylet::mptoken(mptIssue.getMptID(), account));
600}
601
602[[nodiscard]] bool
603holdingExists(ReadView const& view, AccountID const& account, Asset const& asset)
604{
605 return std::visit(
606 [&]<ValidIssueType TIss>(TIss const& issue) -> bool {
607 return holdingExists(view, account, issue);
608 },
609 asset.value());
610}
611
612TER
615 AccountID const& accountID,
616 XRPAmount priorBalance,
617 Asset const& asset,
618 beast::Journal journal)
619{
620 return std::visit(
621 [&]<ValidIssueType TIss>(TIss const& issue) -> TER {
622 return addEmptyHolding(ctx, accountID, priorBalance, issue, journal);
623 },
624 asset.value());
625}
626
627TER
630 AccountID const& accountID,
631 Asset const& asset,
632 beast::Journal journal)
633{
634 return std::visit(
635 [&]<ValidIssueType TIss>(TIss const& issue) -> TER {
636 return removeEmptyHolding(ctx, accountID, issue, journal);
637 },
638 asset.value());
639}
640
641//------------------------------------------------------------------------------
642//
643// Authorization and transfer checks
644//
645//------------------------------------------------------------------------------
646
647TER
648requireAuth(ReadView const& view, Asset const& asset, AccountID const& account, AuthType authType)
649{
650 return std::visit(
651 [&]<ValidIssueType TIss>(TIss const& issue) {
652 return requireAuth(view, issue, account, authType);
653 },
654 asset.value());
655}
656
657TER
659 ReadView const& view,
660 Asset const& asset,
661 AccountID const& from,
662 AccountID const& to,
664 std::uint8_t depth)
665{
666 return asset.visit(
667 [&](MPTIssue const& issue) -> TER {
668 return canTransfer(view, issue, from, to, waive, depth);
669 },
670 [&](Issue const& issue) -> TER { return canTransfer(view, issue, from, to); });
671}
672
673//------------------------------------------------------------------------------
674//
675// Money Transfers
676//
677//------------------------------------------------------------------------------
678
679// Direct send w/o fees:
680// - Redeeming IOUs and/or sending sender's own IOUs.
681// - Create trust line if needed.
682// --> bCheckIssuer : normally require issuer to be involved.
683static TER
685 ApplyView& view,
686 AccountID const& uSenderID,
687 AccountID const& uReceiverID,
688 STAmount const& saAmount,
689 bool bCheckIssuer,
690 SLE::Ref sponsorSle,
692{
693 AccountID const& issuer = saAmount.getIssuer();
694 Currency const& currency = saAmount.get<Issue>().currency;
695
696 // Make sure issuer is involved.
697 XRPL_ASSERT(
698 !bCheckIssuer || uSenderID == issuer || uReceiverID == issuer,
699 "xrpl::directSendNoFeeIOU : matching issuer or don't care");
700 (void)issuer;
701
702 // Disallow sending to self.
703 XRPL_ASSERT(uSenderID != uReceiverID, "xrpl::directSendNoFeeIOU : sender is not receiver");
704
705 bool const bSenderHigh = uSenderID > uReceiverID;
706 auto const index = keylet::trustLine(uSenderID, uReceiverID, currency);
707
708 XRPL_ASSERT(
709 !isXRP(uSenderID) && uSenderID != noAccount(),
710 "xrpl::directSendNoFeeIOU : sender is not XRP");
711 XRPL_ASSERT(
712 !isXRP(uReceiverID) && uReceiverID != noAccount(),
713 "xrpl::directSendNoFeeIOU : receiver is not XRP");
714
715 // If the line exists, modify it accordingly.
716 if (auto const sleRippleState = view.peek(index))
717 {
718 STAmount saBalance = sleRippleState->getFieldAmount(sfBalance);
719
720 if (bSenderHigh)
721 saBalance.negate(); // Put balance in sender terms.
722
723 view.creditHookIOU(uSenderID, uReceiverID, saAmount, saBalance);
724
725 STAmount const saBefore = saBalance;
726
727 saBalance -= saAmount;
728
729 JLOG(j.trace()) << "directSendNoFeeIOU: " << to_string(uSenderID) << " -> "
730 << to_string(uReceiverID) << " : before=" << saBefore.getFullText()
731 << " amount=" << saAmount.getFullText()
732 << " after=" << saBalance.getFullText();
733
734 bool bDelete = false;
735
736 auto const senderReserveFlag = bSenderHigh ? lsfHighReserve : lsfLowReserve;
737 auto const senderNoRippleFlag = bSenderHigh ? lsfHighNoRipple : lsfLowNoRipple;
738 auto const senderFreezeFlag = bSenderHigh ? lsfHighFreeze : lsfLowFreeze;
739 auto const receiverReserveFlag = bSenderHigh ? lsfLowReserve : lsfHighReserve;
740
741 // FIXME This NEEDS to be cleaned up and simplified. It's impossible
742 // for anyone to understand.
743 if (saBefore > beast::kZero
744 // Sender balance was positive.
745 && saBalance <= beast::kZero
746 // Sender is zero or negative.
747 && sleRippleState->isFlag(senderReserveFlag)
748 // Sender reserve is set.
749 && sleRippleState->isFlag(senderNoRippleFlag) !=
750 view.read(keylet::account(uSenderID))->isFlag(lsfDefaultRipple) &&
751 !sleRippleState->isFlag(senderFreezeFlag) &&
752 !sleRippleState->getFieldAmount(bSenderHigh ? sfHighLimit : sfLowLimit)
753 // Sender trust limit is 0.
754 && (sleRippleState->getFieldU32(bSenderHigh ? sfHighQualityIn : sfLowQualityIn) == 0u)
755 // Sender quality in is 0.
756 &&
757 (sleRippleState->getFieldU32(bSenderHigh ? sfHighQualityOut : sfLowQualityOut) == 0u))
758 // Sender quality out is 0.
759 {
760 // Clear the reserve of the sender, possibly delete the line!
761 auto const currentSponsor = getLedgerEntryReserveSponsor(
762 view, sleRippleState, !bSenderHigh ? sfLowSponsor : sfHighSponsor);
763 decreaseOwnerCount(view, view.peek(keylet::account(uSenderID)), currentSponsor, 1, j);
764
766 sleRippleState, !bSenderHigh ? sfLowSponsor : sfHighSponsor);
767
768 // Clear reserve flag.
769 sleRippleState->clearFlag(senderReserveFlag);
770
771 // Balance is zero, receiver reserve is clear.
772 bDelete = !saBalance // Balance is zero.
773 && !sleRippleState->isFlag(receiverReserveFlag);
774 // Receiver reserve is clear.
775 }
776
777 if (bSenderHigh)
778 saBalance.negate();
779
780 // Want to reflect balance to zero even if we are deleting line.
781 sleRippleState->setFieldAmount(sfBalance, saBalance);
782 // ONLY: Adjust balance.
783
784 if (bDelete)
785 {
786 return trustDelete(
787 view,
788 sleRippleState,
789 bSenderHigh ? uReceiverID : uSenderID,
790 bSenderHigh ? uSenderID : uReceiverID,
791 j);
792 }
793
794 view.update(sleRippleState);
795 return tesSUCCESS;
796 }
797
798 STAmount const saReceiverLimit(Issue{currency, uReceiverID});
799 STAmount saBalance{saAmount};
800
801 saBalance.get<Issue>().account = noAccount();
802
803 JLOG(j.debug()) << "directSendNoFeeIOU: "
804 "create line: "
805 << to_string(uSenderID) << " -> " << to_string(uReceiverID) << " : "
806 << saAmount.getFullText();
807
808 auto const sleAccount = view.peek(keylet::account(uReceiverID));
809 if (!sleAccount)
810 return tefINTERNAL; // LCOV_EXCL_LINE
811
812 bool const noRipple = !sleAccount->isFlag(lsfDefaultRipple);
813
814 return trustCreate(
815 view,
816 bSenderHigh,
817 uSenderID,
818 uReceiverID,
819 index.key,
820 sleAccount,
821 false,
822 noRipple,
823 false,
824 false,
825 saBalance,
826 saReceiverLimit,
827 0,
828 0,
829 sponsorSle,
830 j);
831}
832
833// Send regardless of limits.
834// --> saAmount: Amount/currency/issuer to deliver to receiver.
835// <-- saActual: Amount actually cost. Sender pays fees.
836static TER
838 ApplyView& view,
839 AccountID const& uSenderID,
840 AccountID const& uReceiverID,
841 STAmount const& saAmount,
842 STAmount& saActual,
844 SLE::Ref sponsorSle,
845 WaiveTransferFee waiveFee)
846{
847 auto const& issuer = saAmount.getIssuer();
848
849 XRPL_ASSERT(
850 !isXRP(uSenderID) && !isXRP(uReceiverID),
851 "xrpl::directSendNoLimitIOU : neither sender nor receiver is XRP");
852 XRPL_ASSERT(uSenderID != uReceiverID, "xrpl::directSendNoLimitIOU : sender is not receiver");
853
854 if (uSenderID == issuer || uReceiverID == issuer || issuer == noAccount())
855 {
856 // Direct send: redeeming IOUs and/or sending own IOUs.
857 auto const ter =
858 directSendNoFeeIOU(view, uSenderID, uReceiverID, saAmount, false, sponsorSle, j);
859 if (!isTesSuccess(ter))
860 return ter;
861 saActual = saAmount;
862 return tesSUCCESS;
863 }
864
865 // Sending 3rd party IOUs: transit.
866
867 // Calculate the amount to transfer accounting
868 // for any transfer fees if the fee is not waived:
869 saActual = (waiveFee == WaiveTransferFee::Yes) ? saAmount
870 : multiply(saAmount, transferRate(view, issuer));
871
872 JLOG(j.debug()) << "directSendNoLimitIOU> " << to_string(uSenderID) << " - > "
873 << to_string(uReceiverID) << " : deliver=" << saAmount.getFullText()
874 << " cost=" << saActual.getFullText();
875
876 TER terResult = directSendNoFeeIOU(view, issuer, uReceiverID, saAmount, true, sponsorSle, j);
877
878 if (tesSUCCESS == terResult)
879 {
880 terResult = directSendNoFeeIOU(view, uSenderID, issuer, saActual, true, sponsorSle, j);
881 }
882
883 return terResult;
884}
885
886// Send regardless of limits.
887// --> receivers: Amount/currency/issuer to deliver to receivers.
888// <-- saActual: Amount actually cost to sender. Sender pays fees.
889static TER
891 ApplyView& view,
892 AccountID const& senderID,
893 Issue const& issue,
894 MultiplePaymentDestinations const& receivers,
895 STAmount& actual,
897 WaiveTransferFee waiveFee)
898{
899 auto const& issuer = issue.getIssuer();
900
901 XRPL_ASSERT(!isXRP(senderID), "xrpl::directSendNoLimitMultiIOU : sender is not XRP");
902
903 // These may diverge
904 STAmount takeFromSender{issue};
905 actual = takeFromSender;
906
907 // Failures return immediately.
908 for (auto const& r : receivers)
909 {
910 auto const& receiverID = r.first;
911 STAmount const amount{issue, r.second};
912
913 /* If we aren't sending anything or if the sender is the same as the
914 * receiver then we don't need to do anything.
915 */
916 if (!amount || (senderID == receiverID))
917 continue;
918
919 XRPL_ASSERT(!isXRP(receiverID), "xrpl::directSendNoLimitMultiIOU : receiver is not XRP");
920
921 if (senderID == issuer || receiverID == issuer || issuer == noAccount())
922 {
923 // Direct send: redeeming IOUs and/or sending own IOUs.
924 if (auto const ter =
925 directSendNoFeeIOU(view, senderID, receiverID, amount, false, {}, j);
926 !isTesSuccess(ter))
927 return ter;
928 actual += amount;
929 // Do not add amount to takeFromSender, because directSendNoFeeIOU took
930 // it.
931
932 continue;
933 }
934
935 // Sending 3rd party IOUs: transit.
936
937 // Calculate the amount to transfer accounting
938 // for any transfer fees if the fee is not waived:
939 STAmount const actualSend = (waiveFee == WaiveTransferFee::Yes)
940 ? amount
941 : multiply(amount, transferRate(view, issuer));
942 actual += actualSend;
943 takeFromSender += actualSend;
944
945 JLOG(j.debug()) << "directSendNoLimitMultiIOU> " << to_string(senderID) << " - > "
946 << to_string(receiverID) << " : deliver=" << amount.getFullText()
947 << " cost=" << actual.getFullText();
948
949 if (TER const terResult = directSendNoFeeIOU(view, issuer, receiverID, amount, true, {}, j))
950 return terResult;
951 }
952
953 if (senderID != issuer && takeFromSender)
954 {
955 if (TER const terResult =
956 directSendNoFeeIOU(view, senderID, issuer, takeFromSender, true, {}, j))
957 return terResult;
958 }
959
960 return tesSUCCESS;
961}
962
963static TER
965 ApplyView& view,
966 AccountID const& uSenderID,
967 AccountID const& uReceiverID,
968 STAmount const& saAmount,
970 SLE::Ref sponsorSle,
971 WaiveTransferFee waiveFee)
972{
973 if (view.rules().enabled(fixAMMv1_1))
974 {
975 if (saAmount < beast::kZero || saAmount.holds<MPTIssue>())
976 {
977 return tecINTERNAL; // LCOV_EXCL_LINE
978 }
979 }
980 else
981 {
982 // LCOV_EXCL_START
983 XRPL_ASSERT(
984 saAmount >= beast::kZero && !saAmount.holds<MPTIssue>(),
985 "xrpl::accountSendIOU : minimum amount and not MPT");
986 // LCOV_EXCL_STOP
987 }
988
989 /* If we aren't sending anything or if the sender is the same as the
990 * receiver then we don't need to do anything.
991 */
992 if (!saAmount || (uSenderID == uReceiverID))
993 return tesSUCCESS;
994
995 if (!saAmount.native())
996 {
997 STAmount saActual;
998
999 JLOG(j.trace()) << "accountSendIOU: " << to_string(uSenderID) << " -> "
1000 << to_string(uReceiverID) << " : " << saAmount.getFullText();
1001
1002 return directSendNoLimitIOU(
1003 view, uSenderID, uReceiverID, saAmount, saActual, j, sponsorSle, waiveFee);
1004 }
1005
1006 /* XRP send which does not check reserve and can do pure adjustment.
1007 * Note that sender or receiver may be null and this not a mistake; this
1008 * setup is used during pathfinding and it is carefully controlled to
1009 * ensure that transfers are balanced.
1010 */
1011 TER terResult(tesSUCCESS);
1012
1013 SLE::pointer const sender =
1014 uSenderID != beast::kZero ? view.peek(keylet::account(uSenderID)) : SLE::pointer();
1015 SLE::pointer const receiver =
1016 uReceiverID != beast::kZero ? view.peek(keylet::account(uReceiverID)) : SLE::pointer();
1017
1018 if (auto stream = j.trace())
1019 {
1020 std::string senderBal("-");
1021 std::string receiverBal("-");
1022
1023 if (sender)
1024 senderBal = sender->getFieldAmount(sfBalance).getFullText();
1025
1026 if (receiver)
1027 receiverBal = receiver->getFieldAmount(sfBalance).getFullText();
1028
1029 stream << "accountSendIOU> " << to_string(uSenderID) << " (" << senderBal << ") -> "
1030 << to_string(uReceiverID) << " (" << receiverBal << ") : " << saAmount.getFullText();
1031 }
1032
1033 if (sender)
1034 {
1035 if (sender->getFieldAmount(sfBalance) < saAmount)
1036 {
1037 // VFALCO Its laborious to have to mutate the
1038 // TER based on params everywhere
1039 // LCOV_EXCL_START
1040 terResult = view.open() ? TER{telFAILED_PROCESSING} : TER{tecFAILED_PROCESSING};
1041 // LCOV_EXCL_STOP
1042 }
1043 else
1044 {
1045 auto const sndBal = sender->getFieldAmount(sfBalance);
1046 view.creditHookIOU(uSenderID, xrpAccount(), saAmount, sndBal);
1047
1048 // Decrement XRP balance.
1049 sender->setFieldAmount(sfBalance, sndBal - saAmount);
1050 view.update(sender);
1051 }
1052 }
1053
1054 if (tesSUCCESS == terResult && receiver)
1055 {
1056 // Increment XRP balance.
1057 auto const rcvBal = receiver->getFieldAmount(sfBalance);
1058 receiver->setFieldAmount(sfBalance, rcvBal + saAmount);
1059 view.creditHookIOU(xrpAccount(), uReceiverID, saAmount, -rcvBal);
1060
1061 view.update(receiver);
1062 }
1063
1064 if (auto stream = j.trace())
1065 {
1066 std::string senderBal("-");
1067 std::string receiverBal("-");
1068
1069 if (sender)
1070 senderBal = sender->getFieldAmount(sfBalance).getFullText();
1071
1072 if (receiver)
1073 receiverBal = receiver->getFieldAmount(sfBalance).getFullText();
1074
1075 stream << "accountSendIOU< " << to_string(uSenderID) << " (" << senderBal << ") -> "
1076 << to_string(uReceiverID) << " (" << receiverBal << ") : " << saAmount.getFullText();
1077 }
1078
1079 return terResult;
1080}
1081
1082static TER
1084 ApplyView& view,
1085 AccountID const& senderID,
1086 Issue const& issue,
1087 MultiplePaymentDestinations const& receivers,
1089 WaiveTransferFee waiveFee)
1090{
1091 XRPL_ASSERT_PARTS(
1092 receivers.size() > 1, "xrpl::accountSendMultiIOU", "multiple recipients provided");
1093
1094 if (!issue.native())
1095 {
1096 STAmount actual;
1097 JLOG(j.trace()) << "accountSendMultiIOU: " << to_string(senderID) << " sending "
1098 << receivers.size() << " IOUs";
1099
1100 return directSendNoLimitMultiIOU(view, senderID, issue, receivers, actual, j, waiveFee);
1101 }
1102
1103 /* XRP send which does not check reserve and can do pure adjustment.
1104 * Note that sender or receiver may be null and this not a mistake; this
1105 * setup could be used during pathfinding and it is carefully controlled to
1106 * ensure that transfers are balanced.
1107 */
1108
1109 SLE::pointer const sender =
1110 senderID != beast::kZero ? view.peek(keylet::account(senderID)) : SLE::pointer();
1111
1112 if (auto stream = j.trace())
1113 {
1114 std::string senderBal("-");
1115
1116 if (sender)
1117 senderBal = sender->getFieldAmount(sfBalance).getFullText();
1118
1119 stream << "accountSendMultiIOU> " << to_string(senderID) << " (" << senderBal << ") -> "
1120 << receivers.size() << " receivers.";
1121 }
1122
1123 // Failures return immediately.
1124 STAmount takeFromSender{issue};
1125 for (auto const& r : receivers)
1126 {
1127 auto const& receiverID = r.first;
1128 STAmount const amount{issue, r.second};
1129
1130 if (amount < beast::kZero)
1131 {
1132 return tecINTERNAL; // LCOV_EXCL_LINE
1133 }
1134
1135 /* If we aren't sending anything or if the sender is the same as the
1136 * receiver then we don't need to do anything.
1137 */
1138 if (!amount || (senderID == receiverID))
1139 continue;
1140
1141 SLE::pointer const receiver =
1142 receiverID != beast::kZero ? view.peek(keylet::account(receiverID)) : SLE::pointer();
1143
1144 if (auto stream = j.trace())
1145 {
1146 std::string receiverBal("-");
1147
1148 if (receiver)
1149 receiverBal = receiver->getFieldAmount(sfBalance).getFullText();
1150
1151 stream << "accountSendMultiIOU> " << to_string(senderID) << " -> "
1152 << to_string(receiverID) << " (" << receiverBal
1153 << ") : " << amount.getFullText();
1154 }
1155
1156 if (receiver)
1157 {
1158 // Increment XRP balance.
1159 auto const rcvBal = receiver->getFieldAmount(sfBalance);
1160 receiver->setFieldAmount(sfBalance, rcvBal + amount);
1161 view.creditHookIOU(xrpAccount(), receiverID, amount, -rcvBal);
1162
1163 view.update(receiver);
1164
1165 // Take what is actually sent
1166 takeFromSender += amount;
1167 }
1168
1169 if (auto stream = j.trace())
1170 {
1171 std::string receiverBal("-");
1172
1173 if (receiver)
1174 receiverBal = receiver->getFieldAmount(sfBalance).getFullText();
1175
1176 stream << "accountSendMultiIOU< " << to_string(senderID) << " -> "
1177 << to_string(receiverID) << " (" << receiverBal
1178 << ") : " << amount.getFullText();
1179 }
1180 }
1181
1182 if (sender)
1183 {
1184 if (sender->getFieldAmount(sfBalance) < takeFromSender)
1185 {
1186 return TER{tecFAILED_PROCESSING};
1187 }
1188 auto const sndBal = sender->getFieldAmount(sfBalance);
1189 view.creditHookIOU(senderID, xrpAccount(), takeFromSender, sndBal);
1190
1191 // Decrement XRP balance.
1192 sender->setFieldAmount(sfBalance, sndBal - takeFromSender);
1193 view.update(sender);
1194 }
1195
1196 if (auto stream = j.trace())
1197 {
1198 std::string senderBal("-");
1199
1200 if (sender)
1201 senderBal = sender->getFieldAmount(sfBalance).getFullText();
1202
1203 stream << "accountSendMultiIOU< " << to_string(senderID) << " (" << senderBal << ") -> "
1204 << receivers.size() << " receivers.";
1205 }
1206 return tesSUCCESS;
1207}
1208
1209static TER
1211 ApplyView& view,
1212 AccountID const& uSenderID,
1213 AccountID const& uReceiverID,
1214 STAmount const& saAmount,
1216{
1217 // Do not check MPT authorization here - it must have been checked earlier
1218 auto const mptID = keylet::mptokenIssuance(saAmount.get<MPTIssue>().getMptID());
1219 auto const& issuer = saAmount.getIssuer();
1220 auto sleIssuance = view.peek(mptID);
1221 if (!sleIssuance)
1222 return tecOBJECT_NOT_FOUND;
1223
1224 auto const maxAmount = maxMPTAmount(*sleIssuance);
1225 auto const outstanding = sleIssuance->getFieldU64(sfOutstandingAmount);
1226 auto const available = availableMPTAmount(*sleIssuance);
1227 auto const amt = saAmount.mpt().value();
1228
1229 if (uSenderID == issuer)
1230 {
1231 if (view.rules().enabled(featureMPTokensV2))
1232 {
1233 if (isMPTOverflow(amt, outstanding, maxAmount, AllowMPTOverflow::Yes))
1234 return tecPATH_DRY;
1235 }
1236 (*sleIssuance)[sfOutstandingAmount] += amt;
1237 view.update(sleIssuance);
1238 }
1239 else
1240 {
1241 auto const mptokenID = keylet::mptoken(mptID.key, uSenderID);
1242 if (auto sle = view.peek(mptokenID))
1243 {
1244 auto const senderBalance = sle->getFieldU64(sfMPTAmount);
1245 if (senderBalance < amt)
1246 return tecINSUFFICIENT_FUNDS;
1247 view.creditHookMPT(uSenderID, uReceiverID, saAmount, (*sle)[sfMPTAmount], available);
1248 (*sle)[sfMPTAmount] = senderBalance - amt;
1249 view.update(sle);
1250 }
1251 else
1252 {
1253 return tecNO_AUTH;
1254 }
1255 }
1256
1257 if (uReceiverID == issuer)
1258 {
1259 if (outstanding >= amt)
1260 {
1261 sleIssuance->setFieldU64(sfOutstandingAmount, outstanding - amt);
1262 view.update(sleIssuance);
1263 }
1264 else
1265 {
1266 return tecINTERNAL; // LCOV_EXCL_LINE
1267 }
1268 }
1269 else
1270 {
1271 auto const mptokenID = keylet::mptoken(mptID.key, uReceiverID);
1272 if (auto sle = view.peek(mptokenID))
1273 {
1274 if (view.rules().enabled(featureMPTokensV2))
1275 {
1276 if ((*sle)[sfMPTAmount] > (std::numeric_limits<std::uint64_t>::max() - amt))
1277 {
1278 return tecINTERNAL; // LCOV_EXCL_LINE
1279 }
1280 }
1281 view.creditHookMPT(uSenderID, uReceiverID, saAmount, (*sle)[sfMPTAmount], available);
1282 (*sle)[sfMPTAmount] += amt;
1283 view.update(sle);
1284 }
1285 else
1286 {
1287 return tecNO_AUTH;
1288 }
1289 }
1290
1291 return tesSUCCESS;
1292}
1293
1294static TER
1296 ApplyView& view,
1297 AccountID const& uSenderID,
1298 AccountID const& uReceiverID,
1299 STAmount const& saAmount,
1300 STAmount& saActual,
1302 WaiveTransferFee waiveFee,
1303 AllowMPTOverflow allowOverflow)
1304{
1305 XRPL_ASSERT(uSenderID != uReceiverID, "xrpl::directSendNoLimitMPT : sender is not receiver");
1306
1307 // Safe to get MPT since directSendNoLimitMPT is only called by accountSendMPT
1308 auto const& issuer = saAmount.getIssuer();
1309
1310 auto const sle = view.read(keylet::mptokenIssuance(saAmount.get<MPTIssue>().getMptID()));
1311 if (!sle)
1312 return tecOBJECT_NOT_FOUND;
1313
1314 if (uSenderID == issuer || uReceiverID == issuer)
1315 {
1316 // if sender is issuer, check that the new OutstandingAmount will not
1317 // exceed MaximumAmount
1318 if (uSenderID == issuer)
1319 {
1320 auto const sendAmount = saAmount.mpt().value();
1321 auto const maxAmount = maxMPTAmount(*sle);
1322 auto const outstanding = sle->getFieldU64(sfOutstandingAmount);
1323 auto const mptokensV2 = view.rules().enabled(featureMPTokensV2);
1324 allowOverflow = (allowOverflow == AllowMPTOverflow::Yes && mptokensV2)
1327 if (isMPTOverflow(sendAmount, outstanding, maxAmount, allowOverflow))
1328 return tecPATH_DRY;
1329 }
1330
1331 // Direct send: redeeming MPTs and/or sending own MPTs.
1332 auto const ter = directSendNoFeeMPT(view, uSenderID, uReceiverID, saAmount, j);
1333 if (!isTesSuccess(ter))
1334 return ter;
1335 saActual = saAmount;
1336 return tesSUCCESS;
1337 }
1338
1339 // Sending 3rd party MPTs: transit.
1340 saActual = (waiveFee == WaiveTransferFee::Yes)
1341 ? saAmount
1342 : multiply(saAmount, transferRate(view, saAmount.get<MPTIssue>().getMptID()));
1343
1344 JLOG(j.debug()) << "directSendNoLimitMPT> " << to_string(uSenderID) << " - > "
1345 << to_string(uReceiverID) << " : deliver=" << saAmount.getFullText()
1346 << " cost=" << saActual.getFullText();
1347
1348 if (auto const terResult = directSendNoFeeMPT(view, issuer, uReceiverID, saAmount, j);
1349 !isTesSuccess(terResult))
1350 return terResult;
1351
1352 return directSendNoFeeMPT(view, uSenderID, issuer, saActual, j);
1353}
1354
1355static TER
1357 ApplyView& view,
1358 AccountID const& senderID,
1359 MPTIssue const& mptIssue,
1360 MultiplePaymentDestinations const& receivers,
1361 STAmount& actual,
1363 WaiveTransferFee waiveFee)
1364{
1365 auto const& issuer = mptIssue.getIssuer();
1366
1367 auto const sle = view.read(keylet::mptokenIssuance(mptIssue.getMptID()));
1368 if (!sle)
1369 return tecOBJECT_NOT_FOUND;
1370
1371 // For the issuer-as-sender case, track the running total to validate
1372 // against MaximumAmount. The read-only SLE (view.read) is not updated
1373 // by directSendNoFeeMPT, so a per-iteration SLE read would be stale.
1374 // Use uint64_t, not STAmount, to keep MaximumAmount comparisons in exact
1375 // integer arithmetic. STAmount implicitly converts to Number, whose
1376 // small-scale mantissa (~16 digits) can lose precision for values near
1377 // kMaxMpTokenAmount (19 digits).
1378 std::uint64_t totalSendAmount{0};
1379 std::uint64_t const maximumAmount = sle->at(~sfMaximumAmount).value_or(kMaxMpTokenAmount);
1380 std::uint64_t const outstandingAmount = sle->getFieldU64(sfOutstandingAmount);
1381
1382 // actual accumulates the total cost to the sender (includes transfer
1383 // fees for third-party transit sends). takeFromSender accumulates only
1384 // the transit portion that is debited to the issuer in bulk after the
1385 // loop. They diverge when there are transfer fees.
1386 STAmount takeFromSender{mptIssue};
1387 actual = takeFromSender;
1388
1389 for (auto const& [receiverID, amt] : receivers)
1390 {
1391 STAmount const amount{mptIssue, amt};
1392
1393 if (amount < beast::kZero)
1394 return tecINTERNAL; // LCOV_EXCL_LINE
1395
1396 if (!amount || senderID == receiverID)
1397 continue;
1398
1399 if (senderID == issuer || receiverID == issuer)
1400 {
1401 if (senderID == issuer)
1402 {
1403 XRPL_ASSERT_PARTS(
1404 takeFromSender == beast::kZero,
1405 "xrpl::directSendNoLimitMultiMPT",
1406 "sender == issuer, takeFromSender == zero");
1407
1408 std::uint64_t const sendAmount = amount.mpt().value();
1409
1410 if (view.rules().enabled(fixCleanup3_1_3))
1411 {
1412 // Post-fixCleanup3_1_3: aggregate MaximumAmount
1413 // check. WARNING: the order of conditions is
1414 // critical — each guards the subtraction in the
1415 // next against unsigned underflow. Do not reorder.
1416 bool const exceedsMaximumAmount =
1417 // This send alone exceeds the max cap
1418 sendAmount > maximumAmount ||
1419 // The aggregate of all sends exceeds the max cap
1420 totalSendAmount > maximumAmount - sendAmount ||
1421 // Outstanding + aggregate exceeds the max cap
1422 outstandingAmount > maximumAmount - sendAmount - totalSendAmount;
1423
1424 if (exceedsMaximumAmount)
1425 return tecPATH_DRY;
1426 totalSendAmount += sendAmount;
1427 }
1428 else
1429 {
1430 // Pre-fixCleanup3_1_3: per-iteration MaximumAmount
1431 // check. Reads sfOutstandingAmount from a stale
1432 // view.read() snapshot — incorrect for multi-destination
1433 // sends but retained for ledger replay compatibility.
1434 if (sendAmount > maximumAmount ||
1435 outstandingAmount > maximumAmount - sendAmount)
1436 return tecPATH_DRY;
1437 }
1438 }
1439
1440 // Direct send: redeeming MPTs and/or sending own MPTs.
1441 if (auto const ter = directSendNoFeeMPT(view, senderID, receiverID, amount, j);
1442 !isTesSuccess(ter))
1443 return ter;
1444 actual += amount;
1445 // Do not add amount to takeFromSender, because directSendNoFeeMPT took it.
1446
1447 continue;
1448 }
1449
1450 // Sending 3rd party MPTs: transit.
1451 STAmount const actualSend = (waiveFee == WaiveTransferFee::Yes)
1452 ? amount
1453 : multiply(amount, transferRate(view, amount.get<MPTIssue>().getMptID()));
1454 actual += actualSend;
1455 takeFromSender += actualSend;
1456
1457 JLOG(j.debug()) << "directSendNoLimitMultiMPT> " << to_string(senderID) << " - > "
1458 << to_string(receiverID) << " : deliver=" << amount.getFullText()
1459 << " cost=" << actualSend.getFullText();
1460
1461 if (auto const ter = directSendNoFeeMPT(view, issuer, receiverID, amount, j);
1462 !isTesSuccess(ter))
1463 return ter;
1464 }
1465 if (senderID != issuer && takeFromSender)
1466 {
1467 if (auto const ter = directSendNoFeeMPT(view, senderID, issuer, takeFromSender, j);
1468 !isTesSuccess(ter))
1469 return ter;
1470 }
1471
1472 return tesSUCCESS;
1473}
1474
1475static TER
1477 ApplyView& view,
1478 AccountID const& uSenderID,
1479 AccountID const& uReceiverID,
1480 STAmount const& saAmount,
1482 WaiveTransferFee waiveFee,
1483 AllowMPTOverflow allowOverflow)
1484{
1485 XRPL_ASSERT(
1486 saAmount >= beast::kZero && saAmount.holds<MPTIssue>(),
1487 "xrpl::accountSendMPT : minimum amount and MPT");
1488
1489 /* If we aren't sending anything or if the sender is the same as the
1490 * receiver then we don't need to do anything.
1491 */
1492 if (!saAmount || (uSenderID == uReceiverID))
1493 return tesSUCCESS;
1494
1495 STAmount saActual{saAmount.asset()};
1496
1497 return directSendNoLimitMPT(
1498 view, uSenderID, uReceiverID, saAmount, saActual, j, waiveFee, allowOverflow);
1499}
1500
1501static TER
1503 ApplyView& view,
1504 AccountID const& senderID,
1505 MPTIssue const& mptIssue,
1506 MultiplePaymentDestinations const& receivers,
1508 WaiveTransferFee waiveFee)
1509{
1510 STAmount actual;
1511
1512 return directSendNoLimitMultiMPT(view, senderID, mptIssue, receivers, actual, j, waiveFee);
1513}
1514
1515//------------------------------------------------------------------------------
1516//
1517// Public Dispatcher Functions
1518//
1519//------------------------------------------------------------------------------
1520
1521TER
1523 ApplyView& view,
1524 AccountID const& uSenderID,
1525 AccountID const& uReceiverID,
1526 STAmount const& saAmount,
1527 bool bCheckIssuer,
1529{
1530 return saAmount.asset().visit(
1531 [&](Issue const&) {
1532 return directSendNoFeeIOU(view, uSenderID, uReceiverID, saAmount, bCheckIssuer, {}, j);
1533 },
1534 [&](MPTIssue const&) {
1535 XRPL_ASSERT(!bCheckIssuer, "xrpl::directSendNoFee : not checking issuer");
1536 return directSendNoFeeMPT(view, uSenderID, uReceiverID, saAmount, j);
1537 });
1538}
1539
1540TER
1542 ApplyView& view,
1543 AccountID const& uSenderID,
1544 AccountID const& uReceiverID,
1545 STAmount const& saAmount,
1547 SLE::Ref sponsorSle,
1548 WaiveTransferFee waiveFee,
1549 AllowMPTOverflow allowOverflow)
1550{
1551 return saAmount.asset().visit(
1552 [&](Issue const&) {
1553 return accountSendIOU(view, uSenderID, uReceiverID, saAmount, j, sponsorSle, waiveFee);
1554 },
1555 [&](MPTIssue const&) {
1556 return accountSendMPT(
1557 view, uSenderID, uReceiverID, saAmount, j, waiveFee, allowOverflow);
1558 });
1559}
1560
1561TER
1563 ApplyView& view,
1564 AccountID const& senderID,
1565 Asset const& asset,
1566 MultiplePaymentDestinations const& receivers,
1568 WaiveTransferFee waiveFee)
1569{
1570 XRPL_ASSERT_PARTS(
1571 receivers.size() > 1, "xrpl::accountSendMulti", "multiple recipients provided");
1572 return asset.visit(
1573 [&](Issue const& issue) {
1574 return accountSendMultiIOU(view, senderID, issue, receivers, j, waiveFee);
1575 },
1576 [&](MPTIssue const& issue) {
1577 return accountSendMultiMPT(view, senderID, issue, receivers, j, waiveFee);
1578 });
1579}
1580
1581TER
1583 ApplyView& view,
1584 AccountID const& from,
1585 AccountID const& to,
1586 STAmount const& amount,
1588{
1589 XRPL_ASSERT(from != beast::kZero, "xrpl::transferXRP : nonzero from account");
1590 XRPL_ASSERT(to != beast::kZero, "xrpl::transferXRP : nonzero to account");
1591 XRPL_ASSERT(from != to, "xrpl::transferXRP : sender is not receiver");
1592 XRPL_ASSERT(amount.native(), "xrpl::transferXRP : amount is XRP");
1593
1594 SLE::pointer const sender = view.peek(keylet::account(from));
1595 SLE::pointer const receiver = view.peek(keylet::account(to));
1596 if (!sender || !receiver)
1597 return tefINTERNAL; // LCOV_EXCL_LINE
1598
1599 JLOG(j.trace()) << "transferXRP: " << to_string(from) << " -> " << to_string(to)
1600 << ") : " << amount.getFullText();
1601
1602 if (sender->getFieldAmount(sfBalance) < amount)
1603 {
1604 // VFALCO Its unfortunate we have to keep
1605 // mutating these TER everywhere
1606 // FIXME: this logic should be moved to callers maybe?
1607 // LCOV_EXCL_START
1609 // LCOV_EXCL_STOP
1610 }
1611
1612 // Decrement XRP balance.
1613 sender->setFieldAmount(sfBalance, sender->getFieldAmount(sfBalance) - amount);
1614 view.update(sender);
1615
1616 receiver->setFieldAmount(sfBalance, receiver->getFieldAmount(sfBalance) + amount);
1617 view.update(receiver);
1618
1619 return tesSUCCESS;
1620}
1621
1622} // 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:141
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:248
virtual void creditHookMPT(AccountID const &from, AccountID const &to, STAmount const &amount, std::uint64_t preCreditBalanceHolder, std::int64_t preCreditBalanceIssuer)
Definition ApplyView.h:258
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:135
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 bool exists(Keylet const &k) const =0
Determine if a state item exists.
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:182
constexpr bool holds() const noexcept
Definition STAmount.h:478
constexpr TIss const & get() const
std::string getFullText() const override
Definition STAmount.cpp:637
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:302
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 > pointer
std::shared_ptr< STLedgerEntry const > const & ConstRef
std::shared_ptr< STLedgerEntry > 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:471
Keylet mptoken(MPTID const &issuanceID, AccountID const &holder) noexcept
Definition Indexes.cpp:573
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:220
Keylet mptokenIssuance(MPTID const &issuanceID) noexcept
Definition Indexes.cpp:567
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:275
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
void removeSponsorFromLedgerEntry(SLE::Ref sle, SF_ACCOUNT const &field=sfSponsor)
Remove the reserve sponsor field from a ledger entry.
TER checkDeepFrozen(ReadView const &view, AccountID const &account, Issue const &issue)
@ telFAILED_PROCESSING
Definition TER.h:42
@ terNO_RIPPLE
Definition TER.h:225
@ terNO_ACCOUNT
Definition TER.h:218
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)
static STAmount getTrustLineBalance(ReadView const &view, SLE::ConstRef sle, AccountID const &account, Currency const &currency, AccountID const &issuer, bool includeOppositeLimit, beast::Journal j)
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: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 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:168
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 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.
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)
static TER directSendNoFeeIOU(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, bool bCheckIssuer, SLE::Ref sponsorSle, beast::Journal j)
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:657
bool isVaultPseudoAccountFrozen(ReadView const &view, AccountID const &account, MPTIssue const &mptShare, std::uint8_t depth)
Definition View.cpp:159
STAmount accountFunds(ReadView const &view, AccountID const &id, STAmount const &saDefault, FreezeHandling freezeHandling, beast::Journal j)
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 isGlobalFrozen(ReadView const &view, AccountID const &issuer)
Check if the issuer has the global freeze flag set.
TER canTransferLPToken(ReadView const &view, AccountID const &from, AccountID const &to, AccountID const &lpTokenIssuer)
Check whether an AMM LPToken may be transferred between from and to.
Definition View.cpp:186
TER canAddHolding(ReadView const &view, MPTIssue const &mptIssue)
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.
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.
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 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)
Returns true if account cannot send or receive tokens of mptIssue because a freeze applies.
TER checkGlobalFrozen(ReadView const &view, Asset const &asset)
bool isPseudoAccount(SLE::const_pointer sleAcct)
Returns true if and only if sleAcct is a pseudo-account of any kind (i.e.
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:683
bool isAnyFrozen(ReadView const &view, std::initializer_list< AccountID > const &accounts, MPTIssue const &mptIssue, std::uint8_t depth=0)
SLE::pointer getLedgerEntryReserveSponsor(ApplyView &view, SLE::ConstRef sle, SF_ACCOUNT const &field=sfSponsor)
Return a mutable SLE for the reserve sponsor recorded on a ledger entry.
TERSubset< CanCvtToTER > TER
Definition TER.h:654
bool isLPTokenFrozen(ReadView const &view, AccountID const &account, Asset const &asset, Asset const &asset2)
Definition View.cpp:176
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:366
@ tecPATH_DRY
Definition TER.h:302
@ tecOBJECT_NOT_FOUND
Definition TER.h:334
@ tecNO_AUTH
Definition TER.h:308
@ tecINTERNAL
Definition TER.h:318
@ tecFAILED_PROCESSING
Definition TER.h:294
@ tecFROZEN
Definition TER.h:311
@ tecINSUFFICIENT_FUNDS
Definition TER.h:333
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
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:297
TER trustDelete(ApplyView &view, SLE::Ref sleRippleState, AccountID const &uLowAccountID, AccountID const &uHighAccountID, beast::Journal j)
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 accountSendIOU(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, beast::Journal j, SLE::Ref sponsorSle, WaiveTransferFee waiveFee)
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:250
bool holdingExists(ReadView const &view, AccountID const &account, Asset const &asset)
True if the account already holds this asset (or is the issuer / XRP).
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:451
T visit(T... args)