rippled
Loading...
Searching...
No Matches
View.cpp
1#include <xrpl/basics/Expected.h>
2#include <xrpl/basics/Log.h>
3#include <xrpl/basics/chrono.h>
4#include <xrpl/beast/utility/instrumentation.h>
5#include <xrpl/ledger/CredentialHelpers.h>
6#include <xrpl/ledger/Credit.h>
7#include <xrpl/ledger/ReadView.h>
8#include <xrpl/ledger/View.h>
9#include <xrpl/protocol/Feature.h>
10#include <xrpl/protocol/Indexes.h>
11#include <xrpl/protocol/LedgerFormats.h>
12#include <xrpl/protocol/MPTIssue.h>
13#include <xrpl/protocol/Protocol.h>
14#include <xrpl/protocol/Quality.h>
15#include <xrpl/protocol/TER.h>
16#include <xrpl/protocol/TxFlags.h>
17#include <xrpl/protocol/digest.h>
18#include <xrpl/protocol/st.h>
19
20#include <type_traits>
21#include <variant>
22
23namespace xrpl {
24
25namespace detail {
26
27template <
28 class V,
29 class N,
30 class = std::enable_if_t<
33bool
35 V& view,
36 uint256 const& root,
38 unsigned int& index,
39 uint256& entry)
40{
41 auto const& svIndexes = page->getFieldV256(sfIndexes);
42 XRPL_ASSERT(
43 index <= svIndexes.size(),
44 "xrpl::detail::internalDirNext : index inside range");
45
46 if (index >= svIndexes.size())
47 {
48 auto const next = page->getFieldU64(sfIndexNext);
49
50 if (!next)
51 {
52 entry.zero();
53 return false;
54 }
55
56 if constexpr (std::is_const_v<N>)
57 page = view.read(keylet::page(root, next));
58 else
59 page = view.peek(keylet::page(root, next));
60
61 XRPL_ASSERT(page, "xrpl::detail::internalDirNext : non-null root");
62
63 if (!page)
64 return false;
65
66 index = 0;
67
68 return internalDirNext(view, root, page, index, entry);
69 }
70
71 entry = svIndexes[index++];
72 return true;
73}
74
75template <
76 class V,
77 class N,
78 class = std::enable_if_t<
81bool
83 V& view,
84 uint256 const& root,
86 unsigned int& index,
87 uint256& entry)
88{
89 if constexpr (std::is_const_v<N>)
90 page = view.read(keylet::page(root));
91 else
92 page = view.peek(keylet::page(root));
93
94 if (!page)
95 return false;
96
97 index = 0;
98
99 return internalDirNext(view, root, page, index, entry);
100}
101
102} // namespace detail
103
104bool
106 ApplyView& view,
107 uint256 const& root,
109 unsigned int& index,
110 uint256& entry)
111{
112 return detail::internalDirFirst(view, root, page, index, entry);
113}
114
115bool
117 ApplyView& view,
118 uint256 const& root,
120 unsigned int& index,
121 uint256& entry)
122{
123 return detail::internalDirNext(view, root, page, index, entry);
124}
125
126bool
128 ReadView const& view,
129 uint256 const& root,
131 unsigned int& index,
132 uint256& entry)
133{
134 return detail::internalDirFirst(view, root, page, index, entry);
135}
136
137bool
139 ReadView const& view,
140 uint256 const& root,
142 unsigned int& index,
143 uint256& entry)
144{
145 return detail::internalDirNext(view, root, page, index, entry);
146}
147
148//------------------------------------------------------------------------------
149//
150// Observers
151//
152//------------------------------------------------------------------------------
153
154bool
156{
157 using d = NetClock::duration;
158 using tp = NetClock::time_point;
159
160 return exp && (view.parentCloseTime() >= tp{d{*exp}});
161}
162
163bool
164isGlobalFrozen(ReadView const& view, AccountID const& issuer)
165{
166 if (isXRP(issuer))
167 return false;
168 if (auto const sle = view.read(keylet::account(issuer)))
169 return sle->isFlag(lsfGlobalFreeze);
170 return false;
171}
172
173bool
174isGlobalFrozen(ReadView const& view, MPTIssue const& mptIssue)
175{
176 if (auto const sle = view.read(keylet::mptIssuance(mptIssue.getMptID())))
177 return sle->isFlag(lsfMPTLocked);
178 return false;
179}
180
181bool
182isGlobalFrozen(ReadView const& view, Asset const& asset)
183{
184 return std::visit(
185 [&]<ValidIssueType TIss>(TIss const& issue) {
186 if constexpr (std::is_same_v<TIss, Issue>)
187 return isGlobalFrozen(view, issue.getIssuer());
188 else
189 return isGlobalFrozen(view, issue);
190 },
191 asset.value());
192}
193
194bool
196 ReadView const& view,
197 AccountID const& account,
198 Currency const& currency,
199 AccountID const& issuer)
200{
201 if (isXRP(currency))
202 return false;
203 if (issuer != account)
204 {
205 // Check if the issuer froze the line
206 auto const sle = view.read(keylet::line(account, issuer, currency));
207 if (sle &&
208 sle->isFlag((issuer > account) ? lsfHighFreeze : lsfLowFreeze))
209 return true;
210 }
211 return false;
212}
213
214bool
216 ReadView const& view,
217 AccountID const& account,
218 MPTIssue const& mptIssue)
219{
220 if (auto const sle =
221 view.read(keylet::mptoken(mptIssue.getMptID(), account)))
222 return sle->isFlag(lsfMPTLocked);
223 return false;
224}
225
226// Can the specified account spend the specified currency issued by
227// the specified issuer or does the freeze flag prohibit it?
228bool
230 ReadView const& view,
231 AccountID const& account,
232 Currency const& currency,
233 AccountID const& issuer)
234{
235 if (isXRP(currency))
236 return false;
237 auto sle = view.read(keylet::account(issuer));
238 if (sle && sle->isFlag(lsfGlobalFreeze))
239 return true;
240 if (issuer != account)
241 {
242 // Check if the issuer froze the line
243 sle = view.read(keylet::line(account, issuer, currency));
244 if (sle &&
245 sle->isFlag((issuer > account) ? lsfHighFreeze : lsfLowFreeze))
246 return true;
247 }
248 return false;
249}
250
251bool
253 ReadView const& view,
254 AccountID const& account,
255 MPTIssue const& mptIssue,
256 int depth)
257{
258 return isGlobalFrozen(view, mptIssue) ||
259 isIndividualFrozen(view, account, mptIssue) ||
260 isVaultPseudoAccountFrozen(view, account, mptIssue, depth);
261}
262
263[[nodiscard]] bool
265 ReadView const& view,
266 std::initializer_list<AccountID> const& accounts,
267 MPTIssue const& mptIssue,
268 int depth)
269{
270 if (isGlobalFrozen(view, mptIssue))
271 return true;
272
273 for (auto const& account : accounts)
274 {
275 if (isIndividualFrozen(view, account, mptIssue))
276 return true;
277 }
278
279 for (auto const& account : accounts)
280 {
281 if (isVaultPseudoAccountFrozen(view, account, mptIssue, depth))
282 return true;
283 }
284
285 return false;
286}
287
288bool
290 ReadView const& view,
291 AccountID const& account,
292 MPTIssue const& mptShare,
293 int depth)
294{
295 if (!view.rules().enabled(featureSingleAssetVault))
296 return false;
297
298 if (depth >= maxAssetCheckDepth)
299 return true; // LCOV_EXCL_LINE
300
301 auto const mptIssuance =
302 view.read(keylet::mptIssuance(mptShare.getMptID()));
303 if (mptIssuance == nullptr)
304 return false; // zero MPToken won't block deletion of MPTokenIssuance
305
306 auto const issuer = mptIssuance->getAccountID(sfIssuer);
307 auto const mptIssuer = view.read(keylet::account(issuer));
308 if (mptIssuer == nullptr)
309 {
310 // LCOV_EXCL_START
311 UNREACHABLE("xrpl::isVaultPseudoAccountFrozen : null MPToken issuer");
312 return false;
313 // LCOV_EXCL_STOP
314 }
315
316 if (!mptIssuer->isFieldPresent(sfVaultID))
317 return false; // not a Vault pseudo-account, common case
318
319 auto const vault =
320 view.read(keylet::vault(mptIssuer->getFieldH256(sfVaultID)));
321 if (vault == nullptr)
322 { // LCOV_EXCL_START
323 UNREACHABLE("xrpl::isVaultPseudoAccountFrozen : null vault");
324 return false;
325 // LCOV_EXCL_STOP
326 }
327
328 return isAnyFrozen(view, {issuer, account}, vault->at(sfAsset), depth + 1);
329}
330
331bool
333 ReadView const& view,
334 AccountID const& account,
335 Currency const& currency,
336 AccountID const& issuer)
337{
338 if (isXRP(currency))
339 {
340 return false;
341 }
342
343 if (issuer == account)
344 {
345 return false;
346 }
347
348 auto const sle = view.read(keylet::line(account, issuer, currency));
349 if (!sle)
350 {
351 return false;
352 }
353
354 return sle->isFlag(lsfHighDeepFreeze) || sle->isFlag(lsfLowDeepFreeze);
355}
356
357bool
359 ReadView const& view,
360 AccountID const& account,
361 Issue const& asset,
362 Issue const& asset2)
363{
364 return isFrozen(view, account, asset.currency, asset.account) ||
365 isFrozen(view, account, asset2.currency, asset2.account);
366}
367
370 ReadView const& view,
371 AccountID const& account,
372 Currency const& currency,
373 AccountID const& issuer,
374 FreezeHandling zeroIfFrozen,
376{
377 auto const sle = view.read(keylet::line(account, issuer, currency));
378
379 if (!sle)
380 {
381 return nullptr;
382 }
383
384 if (zeroIfFrozen == fhZERO_IF_FROZEN)
385 {
386 if (isFrozen(view, account, currency, issuer) ||
387 isDeepFrozen(view, account, currency, issuer))
388 {
389 return nullptr;
390 }
391
392 // when fixFrozenLPTokenTransfer is enabled, if currency is lptoken,
393 // we need to check if the associated assets have been frozen
394 if (view.rules().enabled(fixFrozenLPTokenTransfer))
395 {
396 auto const sleIssuer = view.read(keylet::account(issuer));
397 if (!sleIssuer)
398 {
399 return nullptr; // LCOV_EXCL_LINE
400 }
401 else if (sleIssuer->isFieldPresent(sfAMMID))
402 {
403 auto const sleAmm =
404 view.read(keylet::amm((*sleIssuer)[sfAMMID]));
405
406 if (!sleAmm ||
408 view,
409 account,
410 (*sleAmm)[sfAsset].get<Issue>(),
411 (*sleAmm)[sfAsset2].get<Issue>()))
412 {
413 return nullptr;
414 }
415 }
416 }
417 }
418
419 return sle;
420}
421
422static STAmount
424 ReadView const& view,
425 SLE::const_ref sle,
426 AccountID const& account,
427 Currency const& currency,
428 AccountID const& issuer,
429 bool includeOppositeLimit,
431{
432 STAmount amount;
433 if (sle)
434 {
435 amount = sle->getFieldAmount(sfBalance);
436 bool const accountHigh = account > issuer;
437 auto const& oppositeField = accountHigh ? sfLowLimit : sfHighLimit;
438 if (accountHigh)
439 {
440 // Put balance in account terms.
441 amount.negate();
442 }
443 if (includeOppositeLimit)
444 {
445 amount += sle->getFieldAmount(oppositeField);
446 }
447 amount.setIssuer(issuer);
448 }
449 else
450 {
451 amount.clear(Issue{currency, issuer});
452 }
453
454 JLOG(j.trace()) << "getTrustLineBalance:"
455 << " account=" << to_string(account)
456 << " amount=" << amount.getFullText();
457
458 return view.balanceHook(account, issuer, amount);
459}
460
461STAmount
463 ReadView const& view,
464 AccountID const& account,
465 Currency const& currency,
466 AccountID const& issuer,
467 FreezeHandling zeroIfFrozen,
469 SpendableHandling includeFullBalance)
470{
471 STAmount amount;
472 if (isXRP(currency))
473 {
474 return {xrpLiquid(view, account, 0, j)};
475 }
476
477 bool const returnSpendable = (includeFullBalance == shFULL_BALANCE);
478 if (returnSpendable && account == issuer)
479 // If the account is the issuer, then their limit is effectively
480 // infinite
481 return STAmount{
483
484 // IOU: Return balance on trust line modulo freeze
485 SLE::const_pointer const sle =
486 getLineIfUsable(view, account, currency, issuer, zeroIfFrozen, j);
487
488 return getTrustLineBalance(
489 view, sle, account, currency, issuer, returnSpendable, j);
490}
491
492STAmount
494 ReadView const& view,
495 AccountID const& account,
496 Issue const& issue,
497 FreezeHandling zeroIfFrozen,
499 SpendableHandling includeFullBalance)
500{
501 return accountHolds(
502 view,
503 account,
504 issue.currency,
505 issue.account,
506 zeroIfFrozen,
507 j,
508 includeFullBalance);
509}
510
511STAmount
513 ReadView const& view,
514 AccountID const& account,
515 MPTIssue const& mptIssue,
516 FreezeHandling zeroIfFrozen,
517 AuthHandling zeroIfUnauthorized,
519 SpendableHandling includeFullBalance)
520{
521 bool const returnSpendable = (includeFullBalance == shFULL_BALANCE);
522
523 if (returnSpendable && account == mptIssue.getIssuer())
524 {
525 // if the account is the issuer, and the issuance exists, their limit is
526 // the issuance limit minus the outstanding value
527 auto const issuance =
528 view.read(keylet::mptIssuance(mptIssue.getMptID()));
529
530 if (!issuance)
531 {
532 return STAmount{mptIssue};
533 }
534 return STAmount{
535 mptIssue,
536 issuance->at(~sfMaximumAmount).value_or(maxMPTokenAmount) -
537 issuance->at(sfOutstandingAmount)};
538 }
539
540 STAmount amount;
541
542 auto const sleMpt =
543 view.read(keylet::mptoken(mptIssue.getMptID(), account));
544
545 if (!sleMpt)
546 amount.clear(mptIssue);
547 else if (
548 zeroIfFrozen == fhZERO_IF_FROZEN && isFrozen(view, account, mptIssue))
549 amount.clear(mptIssue);
550 else
551 {
552 amount = STAmount{mptIssue, sleMpt->getFieldU64(sfMPTAmount)};
553
554 // Only if auth check is needed, as it needs to do an additional read
555 // operation. Note featureSingleAssetVault will affect error codes.
556 if (zeroIfUnauthorized == ahZERO_IF_UNAUTHORIZED &&
557 view.rules().enabled(featureSingleAssetVault))
558 {
559 if (auto const err =
560 requireAuth(view, mptIssue, account, AuthType::StrongAuth);
561 !isTesSuccess(err))
562 amount.clear(mptIssue);
563 }
564 else if (zeroIfUnauthorized == ahZERO_IF_UNAUTHORIZED)
565 {
566 auto const sleIssuance =
567 view.read(keylet::mptIssuance(mptIssue.getMptID()));
568
569 // if auth is enabled on the issuance and mpt is not authorized,
570 // clear amount
571 if (sleIssuance && sleIssuance->isFlag(lsfMPTRequireAuth) &&
572 !sleMpt->isFlag(lsfMPTAuthorized))
573 amount.clear(mptIssue);
574 }
575 }
576
577 return amount;
578}
579
580[[nodiscard]] STAmount
582 ReadView const& view,
583 AccountID const& account,
584 Asset const& asset,
585 FreezeHandling zeroIfFrozen,
586 AuthHandling zeroIfUnauthorized,
588 SpendableHandling includeFullBalance)
589{
590 return std::visit(
591 [&]<ValidIssueType TIss>(TIss const& value) {
592 if constexpr (std::is_same_v<TIss, Issue>)
593 {
594 return accountHolds(
595 view, account, value, zeroIfFrozen, j, includeFullBalance);
596 }
597 else if constexpr (std::is_same_v<TIss, MPTIssue>)
598 {
599 return accountHolds(
600 view,
601 account,
602 value,
603 zeroIfFrozen,
604 zeroIfUnauthorized,
605 j,
606 includeFullBalance);
607 }
608 },
609 asset.value());
610}
611
612STAmount
614 ReadView const& view,
615 AccountID const& id,
616 STAmount const& saDefault,
617 FreezeHandling freezeHandling,
619{
620 if (!saDefault.native() && saDefault.getIssuer() == id)
621 return saDefault;
622
623 return accountHolds(
624 view,
625 id,
626 saDefault.getCurrency(),
627 saDefault.getIssuer(),
628 freezeHandling,
629 j);
630}
631
632// Prevent ownerCount from wrapping under error conditions.
633//
634// adjustment allows the ownerCount to be adjusted up or down in multiple steps.
635// If id != std::nullopt, then do error reporting.
636//
637// Returns adjusted owner count.
638static std::uint32_t
641 std::int32_t adjustment,
644{
645 std::uint32_t adjusted{current + adjustment};
646 if (adjustment > 0)
647 {
648 // Overflow is well defined on unsigned
649 if (adjusted < current)
650 {
651 if (id)
652 {
653 JLOG(j.fatal())
654 << "Account " << *id << " owner count exceeds max!";
655 }
657 }
658 }
659 else
660 {
661 // Underflow is well defined on unsigned
662 if (adjusted > current)
663 {
664 if (id)
665 {
666 JLOG(j.fatal())
667 << "Account " << *id << " owner count set below 0!";
668 }
669 adjusted = 0;
670 XRPL_ASSERT(!id, "xrpl::confineOwnerCount : id is not set");
671 }
672 }
673 return adjusted;
674}
675
676XRPAmount
678 ReadView const& view,
679 AccountID const& id,
680 std::int32_t ownerCountAdj,
682{
683 auto const sle = view.read(keylet::account(id));
684 if (sle == nullptr)
685 return beast::zero;
686
687 // Return balance minus reserve
688 std::uint32_t const ownerCount = confineOwnerCount(
689 view.ownerCountHook(id, sle->getFieldU32(sfOwnerCount)), ownerCountAdj);
690
691 // Pseudo-accounts have no reserve requirement
692 auto const reserve = isPseudoAccount(sle)
693 ? XRPAmount{0}
694 : view.fees().accountReserve(ownerCount);
695
696 auto const fullBalance = sle->getFieldAmount(sfBalance);
697
698 auto const balance = view.balanceHook(id, xrpAccount(), fullBalance);
699
700 STAmount const amount =
701 (balance < reserve) ? STAmount{0} : balance - reserve;
702
703 JLOG(j.trace()) << "accountHolds:"
704 << " account=" << to_string(id)
705 << " amount=" << amount.getFullText()
706 << " fullBalance=" << fullBalance.getFullText()
707 << " balance=" << balance.getFullText()
708 << " reserve=" << reserve << " ownerCount=" << ownerCount
709 << " ownerCountAdj=" << ownerCountAdj;
710
711 return amount.xrp();
712}
713
714void
716 ReadView const& view,
717 Keylet const& root,
718 std::function<void(std::shared_ptr<SLE const> const&)> const& f)
719{
720 XRPL_ASSERT(root.type == ltDIR_NODE, "xrpl::forEachItem : valid root type");
721
722 if (root.type != ltDIR_NODE)
723 return;
724
725 auto pos = root;
726
727 while (true)
728 {
729 auto sle = view.read(pos);
730 if (!sle)
731 return;
732 for (auto const& key : sle->getFieldV256(sfIndexes))
733 f(view.read(keylet::child(key)));
734 auto const next = sle->getFieldU64(sfIndexNext);
735 if (!next)
736 return;
737 pos = keylet::page(root, next);
738 }
739}
740
741bool
743 ReadView const& view,
744 Keylet const& root,
745 uint256 const& after,
746 std::uint64_t const hint,
747 unsigned int limit,
748 std::function<bool(std::shared_ptr<SLE const> const&)> const& f)
749{
750 XRPL_ASSERT(
751 root.type == ltDIR_NODE, "xrpl::forEachItemAfter : valid root type");
752
753 if (root.type != ltDIR_NODE)
754 return false;
755
756 auto currentIndex = root;
757
758 // If startAfter is not zero try jumping to that page using the hint
759 if (after.isNonZero())
760 {
761 auto const hintIndex = keylet::page(root, hint);
762
763 if (auto hintDir = view.read(hintIndex))
764 {
765 for (auto const& key : hintDir->getFieldV256(sfIndexes))
766 {
767 if (key == after)
768 {
769 // We found the hint, we can start here
770 currentIndex = hintIndex;
771 break;
772 }
773 }
774 }
775
776 bool found = false;
777 for (;;)
778 {
779 auto const ownerDir = view.read(currentIndex);
780 if (!ownerDir)
781 return found;
782 for (auto const& key : ownerDir->getFieldV256(sfIndexes))
783 {
784 if (!found)
785 {
786 if (key == after)
787 found = true;
788 }
789 else if (f(view.read(keylet::child(key))) && limit-- <= 1)
790 {
791 return found;
792 }
793 }
794
795 auto const uNodeNext = ownerDir->getFieldU64(sfIndexNext);
796 if (uNodeNext == 0)
797 return found;
798 currentIndex = keylet::page(root, uNodeNext);
799 }
800 }
801 else
802 {
803 for (;;)
804 {
805 auto const ownerDir = view.read(currentIndex);
806 if (!ownerDir)
807 return true;
808 for (auto const& key : ownerDir->getFieldV256(sfIndexes))
809 if (f(view.read(keylet::child(key))) && limit-- <= 1)
810 return true;
811 auto const uNodeNext = ownerDir->getFieldU64(sfIndexNext);
812 if (uNodeNext == 0)
813 return true;
814 currentIndex = keylet::page(root, uNodeNext);
815 }
816 }
817}
818
819Rate
820transferRate(ReadView const& view, AccountID const& issuer)
821{
822 auto const sle = view.read(keylet::account(issuer));
823
824 if (sle && sle->isFieldPresent(sfTransferRate))
825 return Rate{sle->getFieldU32(sfTransferRate)};
826
827 return parityRate;
828}
829
830Rate
831transferRate(ReadView const& view, MPTID const& issuanceID)
832{
833 // fee is 0-50,000 (0-50%), rate is 1,000,000,000-2,000,000,000
834 // For example, if transfer fee is 50% then 10,000 * 50,000 = 500,000
835 // which represents 50% of 1,000,000,000
836 if (auto const sle = view.read(keylet::mptIssuance(issuanceID));
837 sle && sle->isFieldPresent(sfTransferFee))
838 return Rate{1'000'000'000u + 10'000 * sle->getFieldU16(sfTransferFee)};
839
840 return parityRate;
841}
842
843Rate
844transferRate(ReadView const& view, STAmount const& amount)
845{
846 return std::visit(
847 [&]<ValidIssueType TIss>(TIss const& issue) {
848 if constexpr (std::is_same_v<TIss, Issue>)
849 return transferRate(view, issue.getIssuer());
850 else
851 return transferRate(view, issue.getMptID());
852 },
853 amount.asset().value());
854}
855
856bool
858 ReadView const& validLedger,
859 ReadView const& testLedger,
861 char const* reason)
862{
863 bool ret = true;
864
865 if (validLedger.header().seq < testLedger.header().seq)
866 {
867 // valid -> ... -> test
868 auto hash = hashOfSeq(
869 testLedger,
870 validLedger.header().seq,
871 beast::Journal{beast::Journal::getNullSink()});
872 if (hash && (*hash != validLedger.header().hash))
873 {
874 JLOG(s) << reason << " incompatible with valid ledger";
875
876 JLOG(s) << "Hash(VSeq): " << to_string(*hash);
877
878 ret = false;
879 }
880 }
881 else if (validLedger.header().seq > testLedger.header().seq)
882 {
883 // test -> ... -> valid
884 auto hash = hashOfSeq(
885 validLedger,
886 testLedger.header().seq,
887 beast::Journal{beast::Journal::getNullSink()});
888 if (hash && (*hash != testLedger.header().hash))
889 {
890 JLOG(s) << reason << " incompatible preceding ledger";
891
892 JLOG(s) << "Hash(NSeq): " << to_string(*hash);
893
894 ret = false;
895 }
896 }
897 else if (
898 (validLedger.header().seq == testLedger.header().seq) &&
899 (validLedger.header().hash != testLedger.header().hash))
900 {
901 // Same sequence number, different hash
902 JLOG(s) << reason << " incompatible ledger";
903
904 ret = false;
905 }
906
907 if (!ret)
908 {
909 JLOG(s) << "Val: " << validLedger.header().seq << " "
910 << to_string(validLedger.header().hash);
911
912 JLOG(s) << "New: " << testLedger.header().seq << " "
913 << to_string(testLedger.header().hash);
914 }
915
916 return ret;
917}
918
919bool
921 uint256 const& validHash,
922 LedgerIndex validIndex,
923 ReadView const& testLedger,
925 char const* reason)
926{
927 bool ret = true;
928
929 if (testLedger.header().seq > validIndex)
930 {
931 // Ledger we are testing follows last valid ledger
932 auto hash = hashOfSeq(
933 testLedger,
934 validIndex,
936 if (hash && (*hash != validHash))
937 {
938 JLOG(s) << reason << " incompatible following ledger";
939 JLOG(s) << "Hash(VSeq): " << to_string(*hash);
940
941 ret = false;
942 }
943 }
944 else if (
945 (validIndex == testLedger.header().seq) &&
946 (testLedger.header().hash != validHash))
947 {
948 JLOG(s) << reason << " incompatible ledger";
949
950 ret = false;
951 }
952
953 if (!ret)
954 {
955 JLOG(s) << "Val: " << validIndex << " " << to_string(validHash);
956
957 JLOG(s) << "New: " << testLedger.header().seq << " "
958 << to_string(testLedger.header().hash);
959 }
960
961 return ret;
962}
963
964bool
965dirIsEmpty(ReadView const& view, Keylet const& k)
966{
967 auto const sleNode = view.read(k);
968 if (!sleNode)
969 return true;
970 if (!sleNode->getFieldV256(sfIndexes).empty())
971 return false;
972 // The first page of a directory may legitimately be empty even if there
973 // are other pages (the first page is the anchor page) so check to see if
974 // there is another page. If there is, the directory isn't empty.
975 return sleNode->getFieldU64(sfIndexNext) == 0;
976}
977
980{
981 std::set<uint256> amendments;
982
983 if (auto const sle = view.read(keylet::amendments()))
984 {
985 if (sle->isFieldPresent(sfAmendments))
986 {
987 auto const& v = sle->getFieldV256(sfAmendments);
988 amendments.insert(v.begin(), v.end());
989 }
990 }
991
992 return amendments;
993}
994
997{
999
1000 if (auto const sle = view.read(keylet::amendments()))
1001 {
1002 if (sle->isFieldPresent(sfMajorities))
1003 {
1004 using tp = NetClock::time_point;
1005 using d = tp::duration;
1006
1007 auto const majorities = sle->getFieldArray(sfMajorities);
1008
1009 for (auto const& m : majorities)
1010 ret[m.getFieldH256(sfAmendment)] =
1011 tp(d(m.getFieldU32(sfCloseTime)));
1012 }
1013 }
1014
1015 return ret;
1016}
1017
1019hashOfSeq(ReadView const& ledger, LedgerIndex seq, beast::Journal journal)
1020{
1021 // Easy cases...
1022 if (seq > ledger.seq())
1023 {
1024 JLOG(journal.warn())
1025 << "Can't get seq " << seq << " from " << ledger.seq() << " future";
1026 return std::nullopt;
1027 }
1028 if (seq == ledger.seq())
1029 return ledger.header().hash;
1030 if (seq == (ledger.seq() - 1))
1031 return ledger.header().parentHash;
1032
1033 if (int diff = ledger.seq() - seq; diff <= 256)
1034 {
1035 // Within 256...
1036 auto const hashIndex = ledger.read(keylet::skip());
1037 if (hashIndex)
1038 {
1039 XRPL_ASSERT(
1040 hashIndex->getFieldU32(sfLastLedgerSequence) ==
1041 (ledger.seq() - 1),
1042 "xrpl::hashOfSeq : matching ledger sequence");
1043 STVector256 vec = hashIndex->getFieldV256(sfHashes);
1044 if (vec.size() >= diff)
1045 return vec[vec.size() - diff];
1046 JLOG(journal.warn())
1047 << "Ledger " << ledger.seq() << " missing hash for " << seq
1048 << " (" << vec.size() << "," << diff << ")";
1049 }
1050 else
1051 {
1052 JLOG(journal.warn())
1053 << "Ledger " << ledger.seq() << ":" << ledger.header().hash
1054 << " missing normal list";
1055 }
1056 }
1057
1058 if ((seq & 0xff) != 0)
1059 {
1060 JLOG(journal.debug())
1061 << "Can't get seq " << seq << " from " << ledger.seq() << " past";
1062 return std::nullopt;
1063 }
1064
1065 // in skiplist
1066 auto const hashIndex = ledger.read(keylet::skip(seq));
1067 if (hashIndex)
1068 {
1069 auto const lastSeq = hashIndex->getFieldU32(sfLastLedgerSequence);
1070 XRPL_ASSERT(lastSeq >= seq, "xrpl::hashOfSeq : minimum last ledger");
1071 XRPL_ASSERT(
1072 (lastSeq & 0xff) == 0, "xrpl::hashOfSeq : valid last ledger");
1073 auto const diff = (lastSeq - seq) >> 8;
1074 STVector256 vec = hashIndex->getFieldV256(sfHashes);
1075 if (vec.size() > diff)
1076 return vec[vec.size() - diff - 1];
1077 }
1078 JLOG(journal.warn()) << "Can't get seq " << seq << " from " << ledger.seq()
1079 << " error";
1080 return std::nullopt;
1081}
1082
1083//------------------------------------------------------------------------------
1084//
1085// Modifiers
1086//
1087//------------------------------------------------------------------------------
1088
1089void
1091 ApplyView& view,
1092 std::shared_ptr<SLE> const& sle,
1093 std::int32_t amount,
1095{
1096 if (!sle)
1097 return;
1098 XRPL_ASSERT(amount, "xrpl::adjustOwnerCount : nonzero amount input");
1099 std::uint32_t const current{sle->getFieldU32(sfOwnerCount)};
1100 AccountID const id = (*sle)[sfAccount];
1101 std::uint32_t const adjusted = confineOwnerCount(current, amount, id, j);
1102 view.adjustOwnerCountHook(id, current, adjusted);
1103 sle->at(sfOwnerCount) = adjusted;
1104 view.update(sle);
1105}
1106
1109{
1110 return [&account](std::shared_ptr<SLE> const& sle) {
1111 (*sle)[sfOwner] = account;
1112 };
1113}
1114
1115TER
1117 ApplyView& view,
1118 AccountID const& owner,
1119 std::shared_ptr<SLE>& object,
1120 SF_UINT64 const& node)
1121{
1122 auto const page = view.dirInsert(
1123 keylet::ownerDir(owner), object->key(), describeOwnerDir(owner));
1124 if (!page)
1125 return tecDIR_FULL; // LCOV_EXCL_LINE
1126 object->setFieldU64(node, *page);
1127 return tesSUCCESS;
1128}
1129
1131pseudoAccountAddress(ReadView const& view, uint256 const& pseudoOwnerKey)
1132{
1133 // This number must not be changed without an amendment
1134 constexpr std::uint16_t maxAccountAttempts = 256;
1135 for (std::uint16_t i = 0; i < maxAccountAttempts; ++i)
1136 {
1137 ripesha_hasher rsh;
1138 auto const hash =
1139 sha512Half(i, view.header().parentHash, pseudoOwnerKey);
1140 rsh(hash.data(), hash.size());
1141 AccountID const ret{static_cast<ripesha_hasher::result_type>(rsh)};
1142 if (!view.read(keylet::account(ret)))
1143 return ret;
1144 }
1145 return beast::zero;
1146}
1147
1148// Pseudo-account designator fields MUST be maintained by including the
1149// SField::sMD_PseudoAccount flag in the SField definition. (Don't forget to
1150// "| SField::sMD_Default"!) The fields do NOT need to be amendment-gated,
1151// since a non-active amendment will not set any field, by definition.
1152// Specific properties of a pseudo-account are NOT checked here, that's what
1153// InvariantCheck is for.
1154[[nodiscard]] std::vector<SField const*> const&
1156{
1157 static std::vector<SField const*> const pseudoFields = []() {
1158 auto const ar = LedgerFormats::getInstance().findByType(ltACCOUNT_ROOT);
1159 if (!ar)
1160 {
1161 // LCOV_EXCL_START
1162 LogicError(
1163 "xrpl::getPseudoAccountFields : unable to find account root "
1164 "ledger format");
1165 // LCOV_EXCL_STOP
1166 }
1167 auto const& soTemplate = ar->getSOTemplate();
1168
1169 std::vector<SField const*> pseudoFields;
1170 for (auto const& field : soTemplate)
1171 {
1172 if (field.sField().shouldMeta(SField::sMD_PseudoAccount))
1173 pseudoFields.emplace_back(&field.sField());
1174 }
1175 return pseudoFields;
1176 }();
1177 return pseudoFields;
1178}
1179
1180[[nodiscard]] bool
1183 std::set<SField const*> const& pseudoFieldFilter)
1184{
1185 auto const& fields = getPseudoAccountFields();
1186
1187 // Intentionally use defensive coding here because it's cheap and makes the
1188 // semantics of true return value clean.
1189 return sleAcct && sleAcct->getType() == ltACCOUNT_ROOT &&
1191 fields.begin(),
1192 fields.end(),
1193 [&sleAcct, &pseudoFieldFilter](SField const* sf) -> bool {
1194 return sleAcct->isFieldPresent(*sf) &&
1195 (pseudoFieldFilter.empty() ||
1196 pseudoFieldFilter.contains(sf));
1197 }) > 0;
1198}
1199
1200Expected<std::shared_ptr<SLE>, TER>
1202 ApplyView& view,
1203 uint256 const& pseudoOwnerKey,
1204 SField const& ownerField)
1205{
1206 [[maybe_unused]]
1207 auto const& fields = getPseudoAccountFields();
1208 XRPL_ASSERT(
1210 fields.begin(),
1211 fields.end(),
1212 [&ownerField](SField const* sf) -> bool {
1213 return *sf == ownerField;
1214 }) == 1,
1215 "xrpl::createPseudoAccount : valid owner field");
1216
1217 auto const accountId = pseudoAccountAddress(view, pseudoOwnerKey);
1218 if (accountId == beast::zero)
1219 return Unexpected(tecDUPLICATE);
1220
1221 // Create pseudo-account.
1222 auto account = std::make_shared<SLE>(keylet::account(accountId));
1223 account->setAccountID(sfAccount, accountId);
1224 account->setFieldAmount(sfBalance, STAmount{});
1225
1226 // Pseudo-accounts can't submit transactions, so set the sequence number
1227 // to 0 to make them easier to spot and verify, and add an extra level
1228 // of protection.
1229 std::uint32_t const seqno = //
1230 view.rules().enabled(featureSingleAssetVault) || //
1231 view.rules().enabled(featureLendingProtocol) //
1232 ? 0 //
1233 : view.seq();
1234 account->setFieldU32(sfSequence, seqno);
1235 // Ignore reserves requirement, disable the master key, allow default
1236 // rippling, and enable deposit authorization to prevent payments into
1237 // pseudo-account.
1238 account->setFieldU32(
1240 // Link the pseudo-account with its owner object.
1241 account->setFieldH256(ownerField, pseudoOwnerKey);
1242
1243 view.insert(account);
1244
1245 return account;
1246}
1247
1248[[nodiscard]] TER
1249canAddHolding(ReadView const& view, Issue const& issue)
1250{
1251 if (issue.native())
1252 return tesSUCCESS; // No special checks for XRP
1253
1254 auto const issuer = view.read(keylet::account(issue.getIssuer()));
1255 if (!issuer)
1256 return terNO_ACCOUNT;
1257 else if (!issuer->isFlag(lsfDefaultRipple))
1258 return terNO_RIPPLE;
1259
1260 return tesSUCCESS;
1261}
1262
1263[[nodiscard]] TER
1264canAddHolding(ReadView const& view, MPTIssue const& mptIssue)
1265{
1266 auto mptID = mptIssue.getMptID();
1267 auto issuance = view.read(keylet::mptIssuance(mptID));
1268 if (!issuance)
1269 return tecOBJECT_NOT_FOUND;
1270 if (!issuance->isFlag(lsfMPTCanTransfer))
1271 return tecNO_AUTH;
1272
1273 return tesSUCCESS;
1274}
1275
1276[[nodiscard]] TER
1277canAddHolding(ReadView const& view, Asset const& asset)
1278{
1279 return std::visit(
1280 [&]<ValidIssueType TIss>(TIss const& issue) -> TER {
1281 return canAddHolding(view, issue);
1282 },
1283 asset.value());
1284}
1285
1286[[nodiscard]] TER
1287checkDestinationAndTag(SLE::const_ref toSle, bool hasDestinationTag)
1288{
1289 if (toSle == nullptr)
1290 return tecNO_DST;
1291
1292 // The tag is basically account-specific information we don't
1293 // understand, but we can require someone to fill it in.
1294 if (toSle->isFlag(lsfRequireDestTag) && !hasDestinationTag)
1295 return tecDST_TAG_NEEDED; // Cannot send without a tag
1296
1297 return tesSUCCESS;
1298}
1299
1300/*
1301 * Checks if a withdrawal amount into the destination account exceeds
1302 * any applicable receiving limit.
1303 * Called by VaultWithdraw and LoanBrokerCoverWithdraw.
1304 *
1305 * IOU : Performs the trustline check against the destination account's
1306 * credit limit to ensure the account's trust maximum is not exceeded.
1307 *
1308 * MPT: The limit check is effectively skipped (returns true). This is
1309 * because MPT MaximumAmount relates to token supply, and withdrawal does not
1310 * involve minting new tokens that could exceed the global cap.
1311 * On withdrawal, tokens are simply transferred from the vault's pseudo-account
1312 * to the destination account. Since no new MPT tokens are minted during this
1313 * transfer, the withdrawal cannot violate the MPT MaximumAmount/supply cap
1314 * even if `from` is the issuer.
1315 */
1316static TER
1318 ReadView const& view,
1319 AccountID const& from,
1320 AccountID const& to,
1321 STAmount const& amount)
1322{
1323 auto const& issuer = amount.getIssuer();
1324 if (from == to || to == issuer || isXRP(issuer))
1325 return tesSUCCESS;
1326
1327 return std::visit(
1328 [&]<ValidIssueType TIss>(TIss const& issue) -> TER {
1329 if constexpr (std::is_same_v<TIss, Issue>)
1330 {
1331 auto const& currency = issue.currency;
1332 auto const owed = creditBalance(view, to, issuer, currency);
1333 if (owed <= beast::zero)
1334 {
1335 auto const limit = creditLimit(view, to, issuer, currency);
1336 if (-owed >= limit || amount > (limit + owed))
1337 return tecNO_LINE;
1338 }
1339 }
1340 return tesSUCCESS;
1341 },
1342 amount.asset().value());
1343}
1344
1345[[nodiscard]] TER
1347 ReadView const& view,
1348 AccountID const& from,
1349 AccountID const& to,
1350 SLE::const_ref toSle,
1351 STAmount const& amount,
1352 bool hasDestinationTag)
1353{
1354 if (auto const ret = checkDestinationAndTag(toSle, hasDestinationTag))
1355 return ret;
1356
1357 if (from == to)
1358 return tesSUCCESS;
1359
1360 if (toSle->isFlag(lsfDepositAuth))
1361 {
1362 if (!view.exists(keylet::depositPreauth(to, from)))
1363 return tecNO_PERMISSION;
1364 }
1365
1366 return withdrawToDestExceedsLimit(view, from, to, amount);
1367}
1368
1369[[nodiscard]] TER
1371 ReadView const& view,
1372 AccountID const& from,
1373 AccountID const& to,
1374 STAmount const& amount,
1375 bool hasDestinationTag)
1376{
1377 auto const toSle = view.read(keylet::account(to));
1378
1379 return canWithdraw(view, from, to, toSle, amount, hasDestinationTag);
1380}
1381
1382[[nodiscard]] TER
1383canWithdraw(ReadView const& view, STTx const& tx)
1384{
1385 auto const from = tx[sfAccount];
1386 auto const to = tx[~sfDestination].value_or(from);
1387
1388 return canWithdraw(
1389 view, from, to, tx[sfAmount], tx.isFieldPresent(sfDestinationTag));
1390}
1391
1392TER
1394 ApplyView& view,
1395 STTx const& tx,
1396 AccountID const& senderAcct,
1397 AccountID const& dstAcct,
1398 AccountID const& sourceAcct,
1399 XRPAmount priorBalance,
1400 STAmount const& amount,
1402{
1403 // Create trust line or MPToken for the receiving account
1404 if (dstAcct == senderAcct)
1405 {
1406 if (auto const ter = addEmptyHolding(
1407 view, senderAcct, priorBalance, amount.asset(), j);
1408 !isTesSuccess(ter) && ter != tecDUPLICATE)
1409 return ter;
1410 }
1411 else
1412 {
1413 auto dstSle = view.peek(keylet::account(dstAcct));
1414 if (auto err =
1415 verifyDepositPreauth(tx, view, senderAcct, dstAcct, dstSle, j))
1416 return err;
1417 }
1418
1419 // Sanity check
1420 if (accountHolds(
1421 view,
1422 sourceAcct,
1423 amount.asset(),
1426 j) < amount)
1427 {
1428 // LCOV_EXCL_START
1429 JLOG(j.error()) << "LoanBrokerCoverWithdraw: negative balance of "
1430 "broker cover assets.";
1431 return tefINTERNAL;
1432 // LCOV_EXCL_STOP
1433 }
1434
1435 // Move the funds directly from the broker's pseudo-account to the
1436 // dstAcct
1437 return accountSend(
1438 view, sourceAcct, dstAcct, amount, j, WaiveTransferFee::Yes);
1439}
1440
1441[[nodiscard]] TER
1443 ApplyView& view,
1444 AccountID const& accountID,
1445 XRPAmount priorBalance,
1446 Issue const& issue,
1447 beast::Journal journal)
1448{
1449 // Every account can hold XRP. An issuer can issue directly.
1450 if (issue.native() || accountID == issue.getIssuer())
1451 return tesSUCCESS;
1452
1453 auto const& issuerId = issue.getIssuer();
1454 auto const& currency = issue.currency;
1455 if (isGlobalFrozen(view, issuerId))
1456 return tecFROZEN; // LCOV_EXCL_LINE
1457
1458 auto const& srcId = issuerId;
1459 auto const& dstId = accountID;
1460 auto const high = srcId > dstId;
1461 auto const index = keylet::line(srcId, dstId, currency);
1462 auto const sleSrc = view.peek(keylet::account(srcId));
1463 auto const sleDst = view.peek(keylet::account(dstId));
1464 if (!sleDst || !sleSrc)
1465 return tefINTERNAL; // LCOV_EXCL_LINE
1466 if (!sleSrc->isFlag(lsfDefaultRipple))
1467 return tecINTERNAL; // LCOV_EXCL_LINE
1468 // If the line already exists, don't create it again.
1469 if (view.read(index))
1470 return tecDUPLICATE;
1471
1472 // Can the account cover the trust line reserve ?
1473 std::uint32_t const ownerCount = sleDst->at(sfOwnerCount);
1474 if (priorBalance < view.fees().accountReserve(ownerCount + 1))
1476
1477 return trustCreate(
1478 view,
1479 high,
1480 srcId,
1481 dstId,
1482 index.key,
1483 sleDst,
1484 /*auth=*/false,
1485 /*noRipple=*/true,
1486 /*freeze=*/false,
1487 /*deepFreeze*/ false,
1488 /*balance=*/STAmount{Issue{currency, noAccount()}},
1489 /*limit=*/STAmount{Issue{currency, dstId}},
1490 /*qualityIn=*/0,
1491 /*qualityOut=*/0,
1492 journal);
1493}
1494
1495[[nodiscard]] TER
1497 ApplyView& view,
1498 AccountID const& accountID,
1499 XRPAmount priorBalance,
1500 MPTIssue const& mptIssue,
1501 beast::Journal journal)
1502{
1503 auto const& mptID = mptIssue.getMptID();
1504 auto const mpt = view.peek(keylet::mptIssuance(mptID));
1505 if (!mpt)
1506 return tefINTERNAL; // LCOV_EXCL_LINE
1507 if (mpt->isFlag(lsfMPTLocked))
1508 return tefINTERNAL; // LCOV_EXCL_LINE
1509 if (view.peek(keylet::mptoken(mptID, accountID)))
1510 return tecDUPLICATE;
1511 if (accountID == mptIssue.getIssuer())
1512 return tesSUCCESS;
1513
1514 return authorizeMPToken(view, priorBalance, mptID, accountID, journal);
1515}
1516
1517[[nodiscard]] TER
1519 ApplyView& view,
1520 XRPAmount const& priorBalance,
1521 MPTID const& mptIssuanceID,
1522 AccountID const& account,
1523 beast::Journal journal,
1524 std::uint32_t flags,
1525 std::optional<AccountID> holderID)
1526{
1527 auto const sleAcct = view.peek(keylet::account(account));
1528 if (!sleAcct)
1529 return tecINTERNAL; // LCOV_EXCL_LINE
1530
1531 // If the account that submitted the tx is a holder
1532 // Note: `account_` is holder's account
1533 // `holderID` is NOT used
1534 if (!holderID)
1535 {
1536 // When a holder wants to unauthorize/delete a MPT, the ledger must
1537 // - delete mptokenKey from owner directory
1538 // - delete the MPToken
1539 if (flags & tfMPTUnauthorize)
1540 {
1541 auto const mptokenKey = keylet::mptoken(mptIssuanceID, account);
1542 auto const sleMpt = view.peek(mptokenKey);
1543 if (!sleMpt || (*sleMpt)[sfMPTAmount] != 0)
1544 return tecINTERNAL; // LCOV_EXCL_LINE
1545
1546 if (!view.dirRemove(
1547 keylet::ownerDir(account),
1548 (*sleMpt)[sfOwnerNode],
1549 sleMpt->key(),
1550 false))
1551 return tecINTERNAL; // LCOV_EXCL_LINE
1552
1553 adjustOwnerCount(view, sleAcct, -1, journal);
1554
1555 view.erase(sleMpt);
1556 return tesSUCCESS;
1557 }
1558
1559 // A potential holder wants to authorize/hold a mpt, the ledger must:
1560 // - add the new mptokenKey to the owner directory
1561 // - create the MPToken object for the holder
1562
1563 // The reserve that is required to create the MPToken. Note
1564 // that although the reserve increases with every item
1565 // an account owns, in the case of MPTokens we only
1566 // *enforce* a reserve if the user owns more than two
1567 // items. This is similar to the reserve requirements of trust lines.
1568 std::uint32_t const uOwnerCount = sleAcct->getFieldU32(sfOwnerCount);
1569 XRPAmount const reserveCreate(
1570 (uOwnerCount < 2) ? XRPAmount(beast::zero)
1571 : view.fees().accountReserve(uOwnerCount + 1));
1572
1573 if (priorBalance < reserveCreate)
1575
1576 // Defensive check before we attempt to create MPToken for the issuer
1577 auto const mpt = view.read(keylet::mptIssuance(mptIssuanceID));
1578 if (!mpt || mpt->getAccountID(sfIssuer) == account)
1579 {
1580 // LCOV_EXCL_START
1581 UNREACHABLE(
1582 "xrpl::authorizeMPToken : invalid issuance or issuers token");
1583 if (view.rules().enabled(featureLendingProtocol))
1584 return tecINTERNAL;
1585 // LCOV_EXCL_STOP
1586 }
1587
1588 auto const mptokenKey = keylet::mptoken(mptIssuanceID, account);
1589 auto mptoken = std::make_shared<SLE>(mptokenKey);
1590 if (auto ter = dirLink(view, account, mptoken))
1591 return ter; // LCOV_EXCL_LINE
1592
1593 (*mptoken)[sfAccount] = account;
1594 (*mptoken)[sfMPTokenIssuanceID] = mptIssuanceID;
1595 (*mptoken)[sfFlags] = 0;
1596 view.insert(mptoken);
1597
1598 // Update owner count.
1599 adjustOwnerCount(view, sleAcct, 1, journal);
1600
1601 return tesSUCCESS;
1602 }
1603
1604 auto const sleMptIssuance = view.read(keylet::mptIssuance(mptIssuanceID));
1605 if (!sleMptIssuance)
1606 return tecINTERNAL; // LCOV_EXCL_LINE
1607
1608 // If the account that submitted this tx is the issuer of the MPT
1609 // Note: `account_` is issuer's account
1610 // `holderID` is holder's account
1611 if (account != (*sleMptIssuance)[sfIssuer])
1612 return tecINTERNAL; // LCOV_EXCL_LINE
1613
1614 auto const sleMpt = view.peek(keylet::mptoken(mptIssuanceID, *holderID));
1615 if (!sleMpt)
1616 return tecINTERNAL; // LCOV_EXCL_LINE
1617
1618 std::uint32_t const flagsIn = sleMpt->getFieldU32(sfFlags);
1619 std::uint32_t flagsOut = flagsIn;
1620
1621 // Issuer wants to unauthorize the holder, unset lsfMPTAuthorized on
1622 // their MPToken
1623 if (flags & tfMPTUnauthorize)
1624 flagsOut &= ~lsfMPTAuthorized;
1625 // Issuer wants to authorize a holder, set lsfMPTAuthorized on their
1626 // MPToken
1627 else
1628 flagsOut |= lsfMPTAuthorized;
1629
1630 if (flagsIn != flagsOut)
1631 sleMpt->setFieldU32(sfFlags, flagsOut);
1632
1633 view.update(sleMpt);
1634 return tesSUCCESS;
1635}
1636
1637TER
1639 ApplyView& view,
1640 bool const bSrcHigh,
1641 AccountID const& uSrcAccountID,
1642 AccountID const& uDstAccountID,
1643 uint256 const& uIndex, // --> ripple state entry
1644 SLE::ref sleAccount, // --> the account being set.
1645 bool const bAuth, // --> authorize account.
1646 bool const bNoRipple, // --> others cannot ripple through
1647 bool const bFreeze, // --> funds cannot leave
1648 bool bDeepFreeze, // --> can neither receive nor send funds
1649 STAmount const& saBalance, // --> balance of account being set.
1650 // Issuer should be noAccount()
1651 STAmount const& saLimit, // --> limit for account being set.
1652 // Issuer should be the account being set.
1653 std::uint32_t uQualityIn,
1654 std::uint32_t uQualityOut,
1656{
1657 JLOG(j.trace()) << "trustCreate: " << to_string(uSrcAccountID) << ", "
1658 << to_string(uDstAccountID) << ", "
1659 << saBalance.getFullText();
1660
1661 auto const& uLowAccountID = !bSrcHigh ? uSrcAccountID : uDstAccountID;
1662 auto const& uHighAccountID = bSrcHigh ? uSrcAccountID : uDstAccountID;
1663 if (uLowAccountID == uHighAccountID)
1664 {
1665 // LCOV_EXCL_START
1666 UNREACHABLE("xrpl::trustCreate : trust line to self");
1667 if (view.rules().enabled(featureLendingProtocol))
1668 return tecINTERNAL;
1669 // LCOV_EXCL_STOP
1670 }
1671
1672 auto const sleRippleState = std::make_shared<SLE>(ltRIPPLE_STATE, uIndex);
1673 view.insert(sleRippleState);
1674
1675 auto lowNode = view.dirInsert(
1676 keylet::ownerDir(uLowAccountID),
1677 sleRippleState->key(),
1678 describeOwnerDir(uLowAccountID));
1679
1680 if (!lowNode)
1681 return tecDIR_FULL; // LCOV_EXCL_LINE
1682
1683 auto highNode = view.dirInsert(
1684 keylet::ownerDir(uHighAccountID),
1685 sleRippleState->key(),
1686 describeOwnerDir(uHighAccountID));
1687
1688 if (!highNode)
1689 return tecDIR_FULL; // LCOV_EXCL_LINE
1690
1691 bool const bSetDst = saLimit.getIssuer() == uDstAccountID;
1692 bool const bSetHigh = bSrcHigh ^ bSetDst;
1693
1694 XRPL_ASSERT(sleAccount, "xrpl::trustCreate : non-null SLE");
1695 if (!sleAccount)
1696 return tefINTERNAL; // LCOV_EXCL_LINE
1697
1698 XRPL_ASSERT(
1699 sleAccount->getAccountID(sfAccount) ==
1700 (bSetHigh ? uHighAccountID : uLowAccountID),
1701 "xrpl::trustCreate : matching account ID");
1702 auto const slePeer =
1703 view.peek(keylet::account(bSetHigh ? uLowAccountID : uHighAccountID));
1704 if (!slePeer)
1705 return tecNO_TARGET;
1706
1707 // Remember deletion hints.
1708 sleRippleState->setFieldU64(sfLowNode, *lowNode);
1709 sleRippleState->setFieldU64(sfHighNode, *highNode);
1710
1711 sleRippleState->setFieldAmount(
1712 bSetHigh ? sfHighLimit : sfLowLimit, saLimit);
1713 sleRippleState->setFieldAmount(
1714 bSetHigh ? sfLowLimit : sfHighLimit,
1716 saBalance.getCurrency(), bSetDst ? uSrcAccountID : uDstAccountID}));
1717
1718 if (uQualityIn)
1719 sleRippleState->setFieldU32(
1720 bSetHigh ? sfHighQualityIn : sfLowQualityIn, uQualityIn);
1721
1722 if (uQualityOut)
1723 sleRippleState->setFieldU32(
1724 bSetHigh ? sfHighQualityOut : sfLowQualityOut, uQualityOut);
1725
1726 std::uint32_t uFlags = bSetHigh ? lsfHighReserve : lsfLowReserve;
1727
1728 if (bAuth)
1729 {
1730 uFlags |= (bSetHigh ? lsfHighAuth : lsfLowAuth);
1731 }
1732 if (bNoRipple)
1733 {
1734 uFlags |= (bSetHigh ? lsfHighNoRipple : lsfLowNoRipple);
1735 }
1736 if (bFreeze)
1737 {
1738 uFlags |= (bSetHigh ? lsfHighFreeze : lsfLowFreeze);
1739 }
1740 if (bDeepFreeze)
1741 {
1742 uFlags |= (bSetHigh ? lsfHighDeepFreeze : lsfLowDeepFreeze);
1743 }
1744
1745 if ((slePeer->getFlags() & lsfDefaultRipple) == 0)
1746 {
1747 // The other side's default is no rippling
1748 uFlags |= (bSetHigh ? lsfLowNoRipple : lsfHighNoRipple);
1749 }
1750
1751 sleRippleState->setFieldU32(sfFlags, uFlags);
1752 adjustOwnerCount(view, sleAccount, 1, j);
1753
1754 // ONLY: Create ripple balance.
1755 sleRippleState->setFieldAmount(
1756 sfBalance, bSetHigh ? -saBalance : saBalance);
1757
1758 view.creditHook(
1759 uSrcAccountID, uDstAccountID, saBalance, saBalance.zeroed());
1760
1761 return tesSUCCESS;
1762}
1763
1764[[nodiscard]] TER
1766 ApplyView& view,
1767 AccountID const& accountID,
1768 Issue const& issue,
1769 beast::Journal journal)
1770{
1771 if (issue.native())
1772 {
1773 auto const sle = view.read(keylet::account(accountID));
1774 if (!sle)
1775 return tecINTERNAL; // LCOV_EXCL_LINE
1776
1777 auto const balance = sle->getFieldAmount(sfBalance);
1778 if (balance.xrp() != 0)
1779 return tecHAS_OBLIGATIONS;
1780
1781 return tesSUCCESS;
1782 }
1783
1784 // `asset` is an IOU.
1785 // If the account is the issuer, then no line should exist. Check anyway. If
1786 // a line does exist, it will get deleted. If not, return success.
1787 bool const accountIsIssuer = accountID == issue.account;
1788 auto const line = view.peek(keylet::line(accountID, issue));
1789 if (!line)
1790 return accountIsIssuer ? (TER)tesSUCCESS : (TER)tecOBJECT_NOT_FOUND;
1791 if (!accountIsIssuer && line->at(sfBalance)->iou() != beast::zero)
1792 return tecHAS_OBLIGATIONS;
1793
1794 // Adjust the owner count(s)
1795 if (line->isFlag(lsfLowReserve))
1796 {
1797 // Clear reserve for low account.
1798 auto sleLowAccount =
1799 view.peek(keylet::account(line->at(sfLowLimit)->getIssuer()));
1800 if (!sleLowAccount)
1801 return tecINTERNAL; // LCOV_EXCL_LINE
1802
1803 adjustOwnerCount(view, sleLowAccount, -1, journal);
1804 // It's not really necessary to clear the reserve flag, since the line
1805 // is about to be deleted, but this will make the metadata reflect an
1806 // accurate state at the time of deletion.
1807 line->clearFlag(lsfLowReserve);
1808 }
1809
1810 if (line->isFlag(lsfHighReserve))
1811 {
1812 // Clear reserve for high account.
1813 auto sleHighAccount =
1814 view.peek(keylet::account(line->at(sfHighLimit)->getIssuer()));
1815 if (!sleHighAccount)
1816 return tecINTERNAL; // LCOV_EXCL_LINE
1817
1818 adjustOwnerCount(view, sleHighAccount, -1, journal);
1819 // It's not really necessary to clear the reserve flag, since the line
1820 // is about to be deleted, but this will make the metadata reflect an
1821 // accurate state at the time of deletion.
1822 line->clearFlag(lsfHighReserve);
1823 }
1824
1825 return trustDelete(
1826 view,
1827 line,
1828 line->at(sfLowLimit)->getIssuer(),
1829 line->at(sfHighLimit)->getIssuer(),
1830 journal);
1831}
1832
1833[[nodiscard]] TER
1835 ApplyView& view,
1836 AccountID const& accountID,
1837 MPTIssue const& mptIssue,
1838 beast::Journal journal)
1839{
1840 // If the account is the issuer, then no token should exist. MPTs do not
1841 // have the legacy ability to create such a situation, but check anyway. If
1842 // a token does exist, it will get deleted. If not, return success.
1843 bool const accountIsIssuer = accountID == mptIssue.getIssuer();
1844 auto const& mptID = mptIssue.getMptID();
1845 auto const mptoken = view.peek(keylet::mptoken(mptID, accountID));
1846 if (!mptoken)
1847 return accountIsIssuer ? (TER)tesSUCCESS : (TER)tecOBJECT_NOT_FOUND;
1848 // Unlike a trust line, if the account is the issuer, and the token has a
1849 // balance, it can not just be deleted, because that will throw the issuance
1850 // accounting out of balance, so fail. Since this should be impossible
1851 // anyway, I'm not going to put any effort into it.
1852 if (mptoken->at(sfMPTAmount) != 0)
1853 return tecHAS_OBLIGATIONS;
1854
1855 return authorizeMPToken(
1856 view,
1857 {}, // priorBalance
1858 mptID,
1859 accountID,
1860 journal,
1861 tfMPTUnauthorize // flags
1862 );
1863}
1864
1865TER
1867 ApplyView& view,
1868 std::shared_ptr<SLE> const& sleRippleState,
1869 AccountID const& uLowAccountID,
1870 AccountID const& uHighAccountID,
1872{
1873 // Detect legacy dirs.
1874 std::uint64_t uLowNode = sleRippleState->getFieldU64(sfLowNode);
1875 std::uint64_t uHighNode = sleRippleState->getFieldU64(sfHighNode);
1876
1877 JLOG(j.trace()) << "trustDelete: Deleting ripple line: low";
1878
1879 if (!view.dirRemove(
1880 keylet::ownerDir(uLowAccountID),
1881 uLowNode,
1882 sleRippleState->key(),
1883 false))
1884 {
1885 return tefBAD_LEDGER; // LCOV_EXCL_LINE
1886 }
1887
1888 JLOG(j.trace()) << "trustDelete: Deleting ripple line: high";
1889
1890 if (!view.dirRemove(
1891 keylet::ownerDir(uHighAccountID),
1892 uHighNode,
1893 sleRippleState->key(),
1894 false))
1895 {
1896 return tefBAD_LEDGER; // LCOV_EXCL_LINE
1897 }
1898
1899 JLOG(j.trace()) << "trustDelete: Deleting ripple line: state";
1900 view.erase(sleRippleState);
1901
1902 return tesSUCCESS;
1903}
1904
1905TER
1907{
1908 if (!sle)
1909 return tesSUCCESS;
1910 auto offerIndex = sle->key();
1911 auto owner = sle->getAccountID(sfAccount);
1912
1913 // Detect legacy directories.
1914 uint256 uDirectory = sle->getFieldH256(sfBookDirectory);
1915
1916 if (!view.dirRemove(
1917 keylet::ownerDir(owner),
1918 sle->getFieldU64(sfOwnerNode),
1919 offerIndex,
1920 false))
1921 {
1922 return tefBAD_LEDGER; // LCOV_EXCL_LINE
1923 }
1924
1925 if (!view.dirRemove(
1926 keylet::page(uDirectory),
1927 sle->getFieldU64(sfBookNode),
1928 offerIndex,
1929 false))
1930 {
1931 return tefBAD_LEDGER; // LCOV_EXCL_LINE
1932 }
1933
1934 if (sle->isFieldPresent(sfAdditionalBooks))
1935 {
1936 XRPL_ASSERT(
1937 sle->isFlag(lsfHybrid) && sle->isFieldPresent(sfDomainID),
1938 "xrpl::offerDelete : should be a hybrid domain offer");
1939
1940 auto const& additionalBookDirs = sle->getFieldArray(sfAdditionalBooks);
1941
1942 for (auto const& bookDir : additionalBookDirs)
1943 {
1944 auto const& dirIndex = bookDir.getFieldH256(sfBookDirectory);
1945 auto const& dirNode = bookDir.getFieldU64(sfBookNode);
1946
1947 if (!view.dirRemove(
1948 keylet::page(dirIndex), dirNode, offerIndex, false))
1949 {
1950 return tefBAD_LEDGER; // LCOV_EXCL_LINE
1951 }
1952 }
1953 }
1954
1955 adjustOwnerCount(view, view.peek(keylet::account(owner)), -1, j);
1956
1957 view.erase(sle);
1958
1959 return tesSUCCESS;
1960}
1961
1962// Direct send w/o fees:
1963// - Redeeming IOUs and/or sending sender's own IOUs.
1964// - Create trust line if needed.
1965// --> bCheckIssuer : normally require issuer to be involved.
1966static TER
1968 ApplyView& view,
1969 AccountID const& uSenderID,
1970 AccountID const& uReceiverID,
1971 STAmount const& saAmount,
1972 bool bCheckIssuer,
1974{
1975 AccountID const& issuer = saAmount.getIssuer();
1976 Currency const& currency = saAmount.getCurrency();
1977
1978 // Make sure issuer is involved.
1979 XRPL_ASSERT(
1980 !bCheckIssuer || uSenderID == issuer || uReceiverID == issuer,
1981 "xrpl::rippleCreditIOU : matching issuer or don't care");
1982 (void)issuer;
1983
1984 // Disallow sending to self.
1985 XRPL_ASSERT(
1986 uSenderID != uReceiverID,
1987 "xrpl::rippleCreditIOU : sender is not receiver");
1988
1989 bool const bSenderHigh = uSenderID > uReceiverID;
1990 auto const index = keylet::line(uSenderID, uReceiverID, currency);
1991
1992 XRPL_ASSERT(
1993 !isXRP(uSenderID) && uSenderID != noAccount(),
1994 "xrpl::rippleCreditIOU : sender is not XRP");
1995 XRPL_ASSERT(
1996 !isXRP(uReceiverID) && uReceiverID != noAccount(),
1997 "xrpl::rippleCreditIOU : receiver is not XRP");
1998
1999 // If the line exists, modify it accordingly.
2000 if (auto const sleRippleState = view.peek(index))
2001 {
2002 STAmount saBalance = sleRippleState->getFieldAmount(sfBalance);
2003
2004 if (bSenderHigh)
2005 saBalance.negate(); // Put balance in sender terms.
2006
2007 view.creditHook(uSenderID, uReceiverID, saAmount, saBalance);
2008
2009 STAmount const saBefore = saBalance;
2010
2011 saBalance -= saAmount;
2012
2013 JLOG(j.trace()) << "rippleCreditIOU: " << to_string(uSenderID) << " -> "
2014 << to_string(uReceiverID)
2015 << " : before=" << saBefore.getFullText()
2016 << " amount=" << saAmount.getFullText()
2017 << " after=" << saBalance.getFullText();
2018
2019 std::uint32_t const uFlags(sleRippleState->getFieldU32(sfFlags));
2020 bool bDelete = false;
2021
2022 // FIXME This NEEDS to be cleaned up and simplified. It's impossible
2023 // for anyone to understand.
2024 if (saBefore > beast::zero
2025 // Sender balance was positive.
2026 && saBalance <= beast::zero
2027 // Sender is zero or negative.
2028 && (uFlags & (!bSenderHigh ? lsfLowReserve : lsfHighReserve))
2029 // Sender reserve is set.
2030 &&
2031 static_cast<bool>(
2032 uFlags & (!bSenderHigh ? lsfLowNoRipple : lsfHighNoRipple)) !=
2033 static_cast<bool>(
2034 view.read(keylet::account(uSenderID))->getFlags() &
2036 !(uFlags & (!bSenderHigh ? lsfLowFreeze : lsfHighFreeze)) &&
2037 !sleRippleState->getFieldAmount(
2038 !bSenderHigh ? sfLowLimit : sfHighLimit)
2039 // Sender trust limit is 0.
2040 && !sleRippleState->getFieldU32(
2041 !bSenderHigh ? sfLowQualityIn : sfHighQualityIn)
2042 // Sender quality in is 0.
2043 && !sleRippleState->getFieldU32(
2044 !bSenderHigh ? sfLowQualityOut : sfHighQualityOut))
2045 // Sender quality out is 0.
2046 {
2047 // Clear the reserve of the sender, possibly delete the line!
2049 view, view.peek(keylet::account(uSenderID)), -1, j);
2050
2051 // Clear reserve flag.
2052 sleRippleState->setFieldU32(
2053 sfFlags,
2054 uFlags & (!bSenderHigh ? ~lsfLowReserve : ~lsfHighReserve));
2055
2056 // Balance is zero, receiver reserve is clear.
2057 bDelete = !saBalance // Balance is zero.
2058 && !(uFlags & (bSenderHigh ? lsfLowReserve : lsfHighReserve));
2059 // Receiver reserve is clear.
2060 }
2061
2062 if (bSenderHigh)
2063 saBalance.negate();
2064
2065 // Want to reflect balance to zero even if we are deleting line.
2066 sleRippleState->setFieldAmount(sfBalance, saBalance);
2067 // ONLY: Adjust ripple balance.
2068
2069 if (bDelete)
2070 {
2071 return trustDelete(
2072 view,
2073 sleRippleState,
2074 bSenderHigh ? uReceiverID : uSenderID,
2075 !bSenderHigh ? uReceiverID : uSenderID,
2076 j);
2077 }
2078
2079 view.update(sleRippleState);
2080 return tesSUCCESS;
2081 }
2082
2083 STAmount const saReceiverLimit(Issue{currency, uReceiverID});
2084 STAmount saBalance{saAmount};
2085
2086 saBalance.setIssuer(noAccount());
2087
2088 JLOG(j.debug()) << "rippleCreditIOU: "
2089 "create line: "
2090 << to_string(uSenderID) << " -> " << to_string(uReceiverID)
2091 << " : " << saAmount.getFullText();
2092
2093 auto const sleAccount = view.peek(keylet::account(uReceiverID));
2094 if (!sleAccount)
2095 return tefINTERNAL; // LCOV_EXCL_LINE
2096
2097 bool const noRipple = (sleAccount->getFlags() & lsfDefaultRipple) == 0;
2098
2099 return trustCreate(
2100 view,
2101 bSenderHigh,
2102 uSenderID,
2103 uReceiverID,
2104 index.key,
2105 sleAccount,
2106 false,
2107 noRipple,
2108 false,
2109 false,
2110 saBalance,
2111 saReceiverLimit,
2112 0,
2113 0,
2114 j);
2115}
2116
2117// Send regardless of limits.
2118// --> saAmount: Amount/currency/issuer to deliver to receiver.
2119// <-- saActual: Amount actually cost. Sender pays fees.
2120static TER
2122 ApplyView& view,
2123 AccountID const& uSenderID,
2124 AccountID const& uReceiverID,
2125 STAmount const& saAmount,
2126 STAmount& saActual,
2128 WaiveTransferFee waiveFee)
2129{
2130 auto const& issuer = saAmount.getIssuer();
2131
2132 XRPL_ASSERT(
2133 !isXRP(uSenderID) && !isXRP(uReceiverID),
2134 "xrpl::rippleSendIOU : neither sender nor receiver is XRP");
2135 XRPL_ASSERT(
2136 uSenderID != uReceiverID,
2137 "xrpl::rippleSendIOU : sender is not receiver");
2138
2139 if (uSenderID == issuer || uReceiverID == issuer || issuer == noAccount())
2140 {
2141 // Direct send: redeeming IOUs and/or sending own IOUs.
2142 auto const ter =
2143 rippleCreditIOU(view, uSenderID, uReceiverID, saAmount, false, j);
2144 if (ter != tesSUCCESS)
2145 return ter;
2146 saActual = saAmount;
2147 return tesSUCCESS;
2148 }
2149
2150 // Sending 3rd party IOUs: transit.
2151
2152 // Calculate the amount to transfer accounting
2153 // for any transfer fees if the fee is not waived:
2154 saActual = (waiveFee == WaiveTransferFee::Yes)
2155 ? saAmount
2156 : multiply(saAmount, transferRate(view, issuer));
2157
2158 JLOG(j.debug()) << "rippleSendIOU> " << to_string(uSenderID) << " - > "
2159 << to_string(uReceiverID)
2160 << " : deliver=" << saAmount.getFullText()
2161 << " cost=" << saActual.getFullText();
2162
2163 TER terResult =
2164 rippleCreditIOU(view, issuer, uReceiverID, saAmount, true, j);
2165
2166 if (tesSUCCESS == terResult)
2167 terResult = rippleCreditIOU(view, uSenderID, issuer, saActual, true, j);
2168
2169 return terResult;
2170}
2171
2172// Send regardless of limits.
2173// --> receivers: Amount/currency/issuer to deliver to receivers.
2174// <-- saActual: Amount actually cost to sender. Sender pays fees.
2175static TER
2177 ApplyView& view,
2178 AccountID const& senderID,
2179 Issue const& issue,
2180 MultiplePaymentDestinations const& receivers,
2181 STAmount& actual,
2183 WaiveTransferFee waiveFee)
2184{
2185 auto const& issuer = issue.getIssuer();
2186
2187 XRPL_ASSERT(
2188 !isXRP(senderID), "xrpl::rippleSendMultiIOU : sender is not XRP");
2189
2190 // These may diverge
2191 STAmount takeFromSender{issue};
2192 actual = takeFromSender;
2193
2194 // Failures return immediately.
2195 for (auto const& r : receivers)
2196 {
2197 auto const& receiverID = r.first;
2198 STAmount amount{issue, r.second};
2199
2200 /* If we aren't sending anything or if the sender is the same as the
2201 * receiver then we don't need to do anything.
2202 */
2203 if (!amount || (senderID == receiverID))
2204 continue;
2205
2206 XRPL_ASSERT(
2207 !isXRP(receiverID),
2208 "xrpl::rippleSendMultiIOU : receiver is not XRP");
2209
2210 if (senderID == issuer || receiverID == issuer || issuer == noAccount())
2211 {
2212 // Direct send: redeeming IOUs and/or sending own IOUs.
2213 if (auto const ter = rippleCreditIOU(
2214 view, senderID, receiverID, amount, false, j))
2215 return ter;
2216 actual += amount;
2217 // Do not add amount to takeFromSender, because rippleCreditIOU took
2218 // it.
2219
2220 continue;
2221 }
2222
2223 // Sending 3rd party IOUs: transit.
2224
2225 // Calculate the amount to transfer accounting
2226 // for any transfer fees if the fee is not waived:
2227 STAmount actualSend = (waiveFee == WaiveTransferFee::Yes)
2228 ? amount
2229 : multiply(amount, transferRate(view, issuer));
2230 actual += actualSend;
2231 takeFromSender += actualSend;
2232
2233 JLOG(j.debug()) << "rippleSendMultiIOU> " << to_string(senderID)
2234 << " - > " << to_string(receiverID)
2235 << " : deliver=" << amount.getFullText()
2236 << " cost=" << actual.getFullText();
2237
2238 if (TER const terResult =
2239 rippleCreditIOU(view, issuer, receiverID, amount, true, j))
2240 return terResult;
2241 }
2242
2243 if (senderID != issuer && takeFromSender)
2244 {
2245 if (TER const terResult = rippleCreditIOU(
2246 view, senderID, issuer, takeFromSender, true, j))
2247 return terResult;
2248 }
2249
2250 return tesSUCCESS;
2251}
2252
2253static TER
2255 ApplyView& view,
2256 AccountID const& uSenderID,
2257 AccountID const& uReceiverID,
2258 STAmount const& saAmount,
2260 WaiveTransferFee waiveFee)
2261{
2262 if (view.rules().enabled(fixAMMv1_1))
2263 {
2264 if (saAmount < beast::zero || saAmount.holds<MPTIssue>())
2265 {
2266 return tecINTERNAL; // LCOV_EXCL_LINE
2267 }
2268 }
2269 else
2270 {
2271 // LCOV_EXCL_START
2272 XRPL_ASSERT(
2273 saAmount >= beast::zero && !saAmount.holds<MPTIssue>(),
2274 "xrpl::accountSendIOU : minimum amount and not MPT");
2275 // LCOV_EXCL_STOP
2276 }
2277
2278 /* If we aren't sending anything or if the sender is the same as the
2279 * receiver then we don't need to do anything.
2280 */
2281 if (!saAmount || (uSenderID == uReceiverID))
2282 return tesSUCCESS;
2283
2284 if (!saAmount.native())
2285 {
2286 STAmount saActual;
2287
2288 JLOG(j.trace()) << "accountSendIOU: " << to_string(uSenderID) << " -> "
2289 << to_string(uReceiverID) << " : "
2290 << saAmount.getFullText();
2291
2292 return rippleSendIOU(
2293 view, uSenderID, uReceiverID, saAmount, saActual, j, waiveFee);
2294 }
2295
2296 /* XRP send which does not check reserve and can do pure adjustment.
2297 * Note that sender or receiver may be null and this not a mistake; this
2298 * setup is used during pathfinding and it is carefully controlled to
2299 * ensure that transfers are balanced.
2300 */
2301 TER terResult(tesSUCCESS);
2302
2303 SLE::pointer sender = uSenderID != beast::zero
2304 ? view.peek(keylet::account(uSenderID))
2305 : SLE::pointer();
2306 SLE::pointer receiver = uReceiverID != beast::zero
2307 ? view.peek(keylet::account(uReceiverID))
2308 : SLE::pointer();
2309
2310 if (auto stream = j.trace())
2311 {
2312 std::string sender_bal("-");
2313 std::string receiver_bal("-");
2314
2315 if (sender)
2316 sender_bal = sender->getFieldAmount(sfBalance).getFullText();
2317
2318 if (receiver)
2319 receiver_bal = receiver->getFieldAmount(sfBalance).getFullText();
2320
2321 stream << "accountSendIOU> " << to_string(uSenderID) << " ("
2322 << sender_bal << ") -> " << to_string(uReceiverID) << " ("
2323 << receiver_bal << ") : " << saAmount.getFullText();
2324 }
2325
2326 if (sender)
2327 {
2328 if (sender->getFieldAmount(sfBalance) < saAmount)
2329 {
2330 // VFALCO Its laborious to have to mutate the
2331 // TER based on params everywhere
2332 // LCOV_EXCL_START
2333 terResult = view.open() ? TER{telFAILED_PROCESSING}
2335 // LCOV_EXCL_STOP
2336 }
2337 else
2338 {
2339 auto const sndBal = sender->getFieldAmount(sfBalance);
2340 view.creditHook(uSenderID, xrpAccount(), saAmount, sndBal);
2341
2342 // Decrement XRP balance.
2343 sender->setFieldAmount(sfBalance, sndBal - saAmount);
2344 view.update(sender);
2345 }
2346 }
2347
2348 if (tesSUCCESS == terResult && receiver)
2349 {
2350 // Increment XRP balance.
2351 auto const rcvBal = receiver->getFieldAmount(sfBalance);
2352 receiver->setFieldAmount(sfBalance, rcvBal + saAmount);
2353 view.creditHook(xrpAccount(), uReceiverID, saAmount, -rcvBal);
2354
2355 view.update(receiver);
2356 }
2357
2358 if (auto stream = j.trace())
2359 {
2360 std::string sender_bal("-");
2361 std::string receiver_bal("-");
2362
2363 if (sender)
2364 sender_bal = sender->getFieldAmount(sfBalance).getFullText();
2365
2366 if (receiver)
2367 receiver_bal = receiver->getFieldAmount(sfBalance).getFullText();
2368
2369 stream << "accountSendIOU< " << to_string(uSenderID) << " ("
2370 << sender_bal << ") -> " << to_string(uReceiverID) << " ("
2371 << receiver_bal << ") : " << saAmount.getFullText();
2372 }
2373
2374 return terResult;
2375}
2376
2377static TER
2379 ApplyView& view,
2380 AccountID const& senderID,
2381 Issue const& issue,
2382 MultiplePaymentDestinations const& receivers,
2384 WaiveTransferFee waiveFee)
2385{
2386 XRPL_ASSERT_PARTS(
2387 receivers.size() > 1,
2388 "xrpl::accountSendMultiIOU",
2389 "multiple recipients provided");
2390
2391 if (!issue.native())
2392 {
2393 STAmount actual;
2394 JLOG(j.trace()) << "accountSendMultiIOU: " << to_string(senderID)
2395 << " sending " << receivers.size() << " IOUs";
2396
2397 return rippleSendMultiIOU(
2398 view, senderID, issue, receivers, actual, j, waiveFee);
2399 }
2400
2401 /* XRP send which does not check reserve and can do pure adjustment.
2402 * Note that sender or receiver may be null and this not a mistake; this
2403 * setup could be used during pathfinding and it is carefully controlled to
2404 * ensure that transfers are balanced.
2405 */
2406
2407 SLE::pointer sender = senderID != beast::zero
2408 ? view.peek(keylet::account(senderID))
2409 : SLE::pointer();
2410
2411 if (auto stream = j.trace())
2412 {
2413 std::string sender_bal("-");
2414
2415 if (sender)
2416 sender_bal = sender->getFieldAmount(sfBalance).getFullText();
2417
2418 stream << "accountSendMultiIOU> " << to_string(senderID) << " ("
2419 << sender_bal << ") -> " << receivers.size() << " receivers.";
2420 }
2421
2422 // Failures return immediately.
2423 STAmount takeFromSender{issue};
2424 for (auto const& r : receivers)
2425 {
2426 auto const& receiverID = r.first;
2427 STAmount amount{issue, r.second};
2428
2429 if (amount < beast::zero)
2430 {
2431 return tecINTERNAL; // LCOV_EXCL_LINE
2432 }
2433
2434 /* If we aren't sending anything or if the sender is the same as the
2435 * receiver then we don't need to do anything.
2436 */
2437 if (!amount || (senderID == receiverID))
2438 continue;
2439
2440 SLE::pointer receiver = receiverID != beast::zero
2441 ? view.peek(keylet::account(receiverID))
2442 : SLE::pointer();
2443
2444 if (auto stream = j.trace())
2445 {
2446 std::string receiver_bal("-");
2447
2448 if (receiver)
2449 receiver_bal =
2450 receiver->getFieldAmount(sfBalance).getFullText();
2451
2452 stream << "accountSendMultiIOU> " << to_string(senderID) << " -> "
2453 << to_string(receiverID) << " (" << receiver_bal
2454 << ") : " << amount.getFullText();
2455 }
2456
2457 if (receiver)
2458 {
2459 // Increment XRP balance.
2460 auto const rcvBal = receiver->getFieldAmount(sfBalance);
2461 receiver->setFieldAmount(sfBalance, rcvBal + amount);
2462 view.creditHook(xrpAccount(), receiverID, amount, -rcvBal);
2463
2464 view.update(receiver);
2465
2466 // Take what is actually sent
2467 takeFromSender += amount;
2468 }
2469
2470 if (auto stream = j.trace())
2471 {
2472 std::string receiver_bal("-");
2473
2474 if (receiver)
2475 receiver_bal =
2476 receiver->getFieldAmount(sfBalance).getFullText();
2477
2478 stream << "accountSendMultiIOU< " << to_string(senderID) << " -> "
2479 << to_string(receiverID) << " (" << receiver_bal
2480 << ") : " << amount.getFullText();
2481 }
2482 }
2483
2484 if (sender)
2485 {
2486 if (sender->getFieldAmount(sfBalance) < takeFromSender)
2487 {
2488 return TER{tecFAILED_PROCESSING};
2489 }
2490 else
2491 {
2492 auto const sndBal = sender->getFieldAmount(sfBalance);
2493 view.creditHook(senderID, xrpAccount(), takeFromSender, sndBal);
2494
2495 // Decrement XRP balance.
2496 sender->setFieldAmount(sfBalance, sndBal - takeFromSender);
2497 view.update(sender);
2498 }
2499 }
2500
2501 if (auto stream = j.trace())
2502 {
2503 std::string sender_bal("-");
2504 std::string receiver_bal("-");
2505
2506 if (sender)
2507 sender_bal = sender->getFieldAmount(sfBalance).getFullText();
2508
2509 stream << "accountSendMultiIOU< " << to_string(senderID) << " ("
2510 << sender_bal << ") -> " << receivers.size() << " receivers.";
2511 }
2512 return tesSUCCESS;
2513}
2514
2515static TER
2517 ApplyView& view,
2518 AccountID const& uSenderID,
2519 AccountID const& uReceiverID,
2520 STAmount const& saAmount,
2522{
2523 // Do not check MPT authorization here - it must have been checked earlier
2524 auto const mptID = keylet::mptIssuance(saAmount.get<MPTIssue>().getMptID());
2525 auto const& issuer = saAmount.getIssuer();
2526 auto sleIssuance = view.peek(mptID);
2527 if (!sleIssuance)
2528 return tecOBJECT_NOT_FOUND;
2529 if (uSenderID == issuer)
2530 {
2531 (*sleIssuance)[sfOutstandingAmount] += saAmount.mpt().value();
2532 view.update(sleIssuance);
2533 }
2534 else
2535 {
2536 auto const mptokenID = keylet::mptoken(mptID.key, uSenderID);
2537 if (auto sle = view.peek(mptokenID))
2538 {
2539 auto const amt = sle->getFieldU64(sfMPTAmount);
2540 auto const pay = saAmount.mpt().value();
2541 if (amt < pay)
2542 return tecINSUFFICIENT_FUNDS;
2543 (*sle)[sfMPTAmount] = amt - pay;
2544 view.update(sle);
2545 }
2546 else
2547 return tecNO_AUTH;
2548 }
2549
2550 if (uReceiverID == issuer)
2551 {
2552 auto const outstanding = sleIssuance->getFieldU64(sfOutstandingAmount);
2553 auto const redeem = saAmount.mpt().value();
2554 if (outstanding >= redeem)
2555 {
2556 sleIssuance->setFieldU64(sfOutstandingAmount, outstanding - redeem);
2557 view.update(sleIssuance);
2558 }
2559 else
2560 return tecINTERNAL; // LCOV_EXCL_LINE
2561 }
2562 else
2563 {
2564 auto const mptokenID = keylet::mptoken(mptID.key, uReceiverID);
2565 if (auto sle = view.peek(mptokenID))
2566 {
2567 (*sle)[sfMPTAmount] += saAmount.mpt().value();
2568 view.update(sle);
2569 }
2570 else
2571 return tecNO_AUTH;
2572 }
2573
2574 return tesSUCCESS;
2575}
2576
2577static TER
2579 ApplyView& view,
2580 AccountID const& uSenderID,
2581 AccountID const& uReceiverID,
2582 STAmount const& saAmount,
2583 STAmount& saActual,
2585 WaiveTransferFee waiveFee)
2586{
2587 XRPL_ASSERT(
2588 uSenderID != uReceiverID,
2589 "xrpl::rippleSendMPT : sender is not receiver");
2590
2591 // Safe to get MPT since rippleSendMPT is only called by accountSendMPT
2592 auto const& issuer = saAmount.getIssuer();
2593
2594 auto const sle =
2595 view.read(keylet::mptIssuance(saAmount.get<MPTIssue>().getMptID()));
2596 if (!sle)
2597 return tecOBJECT_NOT_FOUND;
2598
2599 if (uSenderID == issuer || uReceiverID == issuer)
2600 {
2601 // if sender is issuer, check that the new OutstandingAmount will not
2602 // exceed MaximumAmount
2603 if (uSenderID == issuer)
2604 {
2605 auto const sendAmount = saAmount.mpt().value();
2606 auto const maximumAmount =
2607 sle->at(~sfMaximumAmount).value_or(maxMPTokenAmount);
2608 if (sendAmount > maximumAmount ||
2609 sle->getFieldU64(sfOutstandingAmount) >
2610 maximumAmount - sendAmount)
2611 return tecPATH_DRY;
2612 }
2613
2614 // Direct send: redeeming MPTs and/or sending own MPTs.
2615 auto const ter =
2616 rippleCreditMPT(view, uSenderID, uReceiverID, saAmount, j);
2617 if (ter != tesSUCCESS)
2618 return ter;
2619 saActual = saAmount;
2620 return tesSUCCESS;
2621 }
2622
2623 // Sending 3rd party MPTs: transit.
2624 saActual = (waiveFee == WaiveTransferFee::Yes)
2625 ? saAmount
2626 : multiply(
2627 saAmount,
2628 transferRate(view, saAmount.get<MPTIssue>().getMptID()));
2629
2630 JLOG(j.debug()) << "rippleSendMPT> " << to_string(uSenderID) << " - > "
2631 << to_string(uReceiverID)
2632 << " : deliver=" << saAmount.getFullText()
2633 << " cost=" << saActual.getFullText();
2634
2635 if (auto const terResult =
2636 rippleCreditMPT(view, issuer, uReceiverID, saAmount, j);
2637 terResult != tesSUCCESS)
2638 return terResult;
2639
2640 return rippleCreditMPT(view, uSenderID, issuer, saActual, j);
2641}
2642
2643static TER
2645 ApplyView& view,
2646 AccountID const& senderID,
2647 MPTIssue const& mptIssue,
2648 MultiplePaymentDestinations const& receivers,
2649 STAmount& actual,
2651 WaiveTransferFee waiveFee)
2652{
2653 // Safe to get MPT since rippleSendMultiMPT is only called by
2654 // accountSendMultiMPT
2655 auto const& issuer = mptIssue.getIssuer();
2656
2657 auto const sle = view.read(keylet::mptIssuance(mptIssue.getMptID()));
2658 if (!sle)
2659 return tecOBJECT_NOT_FOUND;
2660
2661 // These may diverge
2662 STAmount takeFromSender{mptIssue};
2663 actual = takeFromSender;
2664
2665 for (auto const& r : receivers)
2666 {
2667 auto const& receiverID = r.first;
2668 STAmount amount{mptIssue, r.second};
2669
2670 if (amount < beast::zero)
2671 {
2672 return tecINTERNAL; // LCOV_EXCL_LINE
2673 }
2674
2675 /* If we aren't sending anything or if the sender is the same as the
2676 * receiver then we don't need to do anything.
2677 */
2678 if (!amount || (senderID == receiverID))
2679 continue;
2680
2681 if (senderID == issuer || receiverID == issuer)
2682 {
2683 // if sender is issuer, check that the new OutstandingAmount will
2684 // not exceed MaximumAmount
2685 if (senderID == issuer)
2686 {
2687 XRPL_ASSERT_PARTS(
2688 takeFromSender == beast::zero,
2689 "rippler::rippleSendMultiMPT",
2690 "sender == issuer, takeFromSender == zero");
2691 auto const sendAmount = amount.mpt().value();
2692 auto const maximumAmount =
2693 sle->at(~sfMaximumAmount).value_or(maxMPTokenAmount);
2694 if (sendAmount > maximumAmount ||
2695 sle->getFieldU64(sfOutstandingAmount) >
2696 maximumAmount - sendAmount)
2697 return tecPATH_DRY;
2698 }
2699
2700 // Direct send: redeeming MPTs and/or sending own MPTs.
2701 if (auto const ter =
2702 rippleCreditMPT(view, senderID, receiverID, amount, j))
2703 return ter;
2704 actual += amount;
2705 // Do not add amount to takeFromSender, because rippleCreditMPT took
2706 // it
2707
2708 continue;
2709 }
2710
2711 // Sending 3rd party MPTs: transit.
2712 STAmount actualSend = (waiveFee == WaiveTransferFee::Yes)
2713 ? amount
2714 : multiply(
2715 amount,
2716 transferRate(view, amount.get<MPTIssue>().getMptID()));
2717 actual += actualSend;
2718 takeFromSender += actualSend;
2719
2720 JLOG(j.debug()) << "rippleSendMultiMPT> " << to_string(senderID)
2721 << " - > " << to_string(receiverID)
2722 << " : deliver=" << amount.getFullText()
2723 << " cost=" << actualSend.getFullText();
2724
2725 if (auto const terResult =
2726 rippleCreditMPT(view, issuer, receiverID, amount, j))
2727 return terResult;
2728 }
2729 if (senderID != issuer && takeFromSender)
2730 {
2731 if (TER const terResult =
2732 rippleCreditMPT(view, senderID, issuer, takeFromSender, j))
2733 return terResult;
2734 }
2735
2736 return tesSUCCESS;
2737}
2738
2739static TER
2741 ApplyView& view,
2742 AccountID const& uSenderID,
2743 AccountID const& uReceiverID,
2744 STAmount const& saAmount,
2746 WaiveTransferFee waiveFee)
2747{
2748 XRPL_ASSERT(
2749 saAmount >= beast::zero && saAmount.holds<MPTIssue>(),
2750 "xrpl::accountSendMPT : minimum amount and MPT");
2751
2752 /* If we aren't sending anything or if the sender is the same as the
2753 * receiver then we don't need to do anything.
2754 */
2755 if (!saAmount || (uSenderID == uReceiverID))
2756 return tesSUCCESS;
2757
2758 STAmount saActual{saAmount.asset()};
2759
2760 return rippleSendMPT(
2761 view, uSenderID, uReceiverID, saAmount, saActual, j, waiveFee);
2762}
2763
2764static TER
2766 ApplyView& view,
2767 AccountID const& senderID,
2768 MPTIssue const& mptIssue,
2769 MultiplePaymentDestinations const& receivers,
2771 WaiveTransferFee waiveFee)
2772{
2773 STAmount actual;
2774
2775 return rippleSendMultiMPT(
2776 view, senderID, mptIssue, receivers, actual, j, waiveFee);
2777}
2778
2779TER
2781 ApplyView& view,
2782 AccountID const& uSenderID,
2783 AccountID const& uReceiverID,
2784 STAmount const& saAmount,
2786 WaiveTransferFee waiveFee)
2787{
2788 return std::visit(
2789 [&]<ValidIssueType TIss>(TIss const& issue) {
2790 if constexpr (std::is_same_v<TIss, Issue>)
2791 return accountSendIOU(
2792 view, uSenderID, uReceiverID, saAmount, j, waiveFee);
2793 else
2794 return accountSendMPT(
2795 view, uSenderID, uReceiverID, saAmount, j, waiveFee);
2796 },
2797 saAmount.asset().value());
2798}
2799
2800TER
2802 ApplyView& view,
2803 AccountID const& senderID,
2804 Asset const& asset,
2805 MultiplePaymentDestinations const& receivers,
2807 WaiveTransferFee waiveFee)
2808{
2809 XRPL_ASSERT_PARTS(
2810 receivers.size() > 1,
2811 "xrpl::accountSendMulti",
2812 "multiple recipients provided");
2813 return std::visit(
2814 [&]<ValidIssueType TIss>(TIss const& issue) {
2815 if constexpr (std::is_same_v<TIss, Issue>)
2816 return accountSendMultiIOU(
2817 view, senderID, issue, receivers, j, waiveFee);
2818 else
2819 return accountSendMultiMPT(
2820 view, senderID, issue, receivers, j, waiveFee);
2821 },
2822 asset.value());
2823}
2824
2825static bool
2827 ApplyView& view,
2828 SLE::pointer state,
2829 bool bSenderHigh,
2830 AccountID const& sender,
2831 STAmount const& before,
2832 STAmount const& after,
2834{
2835 if (!state)
2836 return false;
2837 std::uint32_t const flags(state->getFieldU32(sfFlags));
2838
2839 auto sle = view.peek(keylet::account(sender));
2840 if (!sle)
2841 return false;
2842
2843 // YYY Could skip this if rippling in reverse.
2844 if (before > beast::zero
2845 // Sender balance was positive.
2846 && after <= beast::zero
2847 // Sender is zero or negative.
2848 && (flags & (!bSenderHigh ? lsfLowReserve : lsfHighReserve))
2849 // Sender reserve is set.
2850 && static_cast<bool>(
2851 flags & (!bSenderHigh ? lsfLowNoRipple : lsfHighNoRipple)) !=
2852 static_cast<bool>(sle->getFlags() & lsfDefaultRipple) &&
2853 !(flags & (!bSenderHigh ? lsfLowFreeze : lsfHighFreeze)) &&
2854 !state->getFieldAmount(!bSenderHigh ? sfLowLimit : sfHighLimit)
2855 // Sender trust limit is 0.
2856 && !state->getFieldU32(!bSenderHigh ? sfLowQualityIn : sfHighQualityIn)
2857 // Sender quality in is 0.
2858 &&
2859 !state->getFieldU32(!bSenderHigh ? sfLowQualityOut : sfHighQualityOut))
2860 // Sender quality out is 0.
2861 {
2862 // VFALCO Where is the line being deleted?
2863 // Clear the reserve of the sender, possibly delete the line!
2864 adjustOwnerCount(view, sle, -1, j);
2865
2866 // Clear reserve flag.
2867 state->setFieldU32(
2868 sfFlags, flags & (!bSenderHigh ? ~lsfLowReserve : ~lsfHighReserve));
2869
2870 // Balance is zero, receiver reserve is clear.
2871 if (!after // Balance is zero.
2872 && !(flags & (bSenderHigh ? lsfLowReserve : lsfHighReserve)))
2873 return true;
2874 }
2875 return false;
2876}
2877
2878TER
2880 ApplyView& view,
2881 AccountID const& account,
2882 STAmount const& amount,
2883 Issue const& issue,
2885{
2886 XRPL_ASSERT(
2887 !isXRP(account) && !isXRP(issue.account),
2888 "xrpl::issueIOU : neither account nor issuer is XRP");
2889
2890 // Consistency check
2891 XRPL_ASSERT(issue == amount.issue(), "xrpl::issueIOU : matching issue");
2892
2893 // Can't send to self!
2894 XRPL_ASSERT(
2895 issue.account != account, "xrpl::issueIOU : not issuer account");
2896
2897 JLOG(j.trace()) << "issueIOU: " << to_string(account) << ": "
2898 << amount.getFullText();
2899
2900 bool bSenderHigh = issue.account > account;
2901
2902 auto const index = keylet::line(issue.account, account, issue.currency);
2903
2904 if (auto state = view.peek(index))
2905 {
2906 STAmount final_balance = state->getFieldAmount(sfBalance);
2907
2908 if (bSenderHigh)
2909 final_balance.negate(); // Put balance in sender terms.
2910
2911 STAmount const start_balance = final_balance;
2912
2913 final_balance -= amount;
2914
2915 auto const must_delete = updateTrustLine(
2916 view,
2917 state,
2918 bSenderHigh,
2919 issue.account,
2920 start_balance,
2921 final_balance,
2922 j);
2923
2924 view.creditHook(issue.account, account, amount, start_balance);
2925
2926 if (bSenderHigh)
2927 final_balance.negate();
2928
2929 // Adjust the balance on the trust line if necessary. We do this even if
2930 // we are going to delete the line to reflect the correct balance at the
2931 // time of deletion.
2932 state->setFieldAmount(sfBalance, final_balance);
2933 if (must_delete)
2934 return trustDelete(
2935 view,
2936 state,
2937 bSenderHigh ? account : issue.account,
2938 bSenderHigh ? issue.account : account,
2939 j);
2940
2941 view.update(state);
2942
2943 return tesSUCCESS;
2944 }
2945
2946 // NIKB TODO: The limit uses the receiver's account as the issuer and
2947 // this is unnecessarily inefficient as copying which could be avoided
2948 // is now required. Consider available options.
2949 STAmount const limit(Issue{issue.currency, account});
2950 STAmount final_balance = amount;
2951
2952 final_balance.setIssuer(noAccount());
2953
2954 auto const receiverAccount = view.peek(keylet::account(account));
2955 if (!receiverAccount)
2956 return tefINTERNAL; // LCOV_EXCL_LINE
2957
2958 bool noRipple = (receiverAccount->getFlags() & lsfDefaultRipple) == 0;
2959
2960 return trustCreate(
2961 view,
2962 bSenderHigh,
2963 issue.account,
2964 account,
2965 index.key,
2966 receiverAccount,
2967 false,
2968 noRipple,
2969 false,
2970 false,
2971 final_balance,
2972 limit,
2973 0,
2974 0,
2975 j);
2976}
2977
2978TER
2980 ApplyView& view,
2981 AccountID const& account,
2982 STAmount const& amount,
2983 Issue const& issue,
2985{
2986 XRPL_ASSERT(
2987 !isXRP(account) && !isXRP(issue.account),
2988 "xrpl::redeemIOU : neither account nor issuer is XRP");
2989
2990 // Consistency check
2991 XRPL_ASSERT(issue == amount.issue(), "xrpl::redeemIOU : matching issue");
2992
2993 // Can't send to self!
2994 XRPL_ASSERT(
2995 issue.account != account, "xrpl::redeemIOU : not issuer account");
2996
2997 JLOG(j.trace()) << "redeemIOU: " << to_string(account) << ": "
2998 << amount.getFullText();
2999
3000 bool bSenderHigh = account > issue.account;
3001
3002 if (auto state =
3003 view.peek(keylet::line(account, issue.account, issue.currency)))
3004 {
3005 STAmount final_balance = state->getFieldAmount(sfBalance);
3006
3007 if (bSenderHigh)
3008 final_balance.negate(); // Put balance in sender terms.
3009
3010 STAmount const start_balance = final_balance;
3011
3012 final_balance -= amount;
3013
3014 auto const must_delete = updateTrustLine(
3015 view, state, bSenderHigh, account, start_balance, final_balance, j);
3016
3017 view.creditHook(account, issue.account, amount, start_balance);
3018
3019 if (bSenderHigh)
3020 final_balance.negate();
3021
3022 // Adjust the balance on the trust line if necessary. We do this even if
3023 // we are going to delete the line to reflect the correct balance at the
3024 // time of deletion.
3025 state->setFieldAmount(sfBalance, final_balance);
3026
3027 if (must_delete)
3028 {
3029 return trustDelete(
3030 view,
3031 state,
3032 bSenderHigh ? issue.account : account,
3033 bSenderHigh ? account : issue.account,
3034 j);
3035 }
3036
3037 view.update(state);
3038 return tesSUCCESS;
3039 }
3040
3041 // In order to hold an IOU, a trust line *MUST* exist to track the
3042 // balance. If it doesn't, then something is very wrong. Don't try
3043 // to continue.
3044 // LCOV_EXCL_START
3045 JLOG(j.fatal()) << "redeemIOU: " << to_string(account)
3046 << " attempts to redeem " << amount.getFullText()
3047 << " but no trust line exists!";
3048
3049 return tefINTERNAL;
3050 // LCOV_EXCL_STOP
3051}
3052
3053TER
3055 ApplyView& view,
3056 AccountID const& from,
3057 AccountID const& to,
3058 STAmount const& amount,
3060{
3061 XRPL_ASSERT(
3062 from != beast::zero, "xrpl::transferXRP : nonzero from account");
3063 XRPL_ASSERT(to != beast::zero, "xrpl::transferXRP : nonzero to account");
3064 XRPL_ASSERT(from != to, "xrpl::transferXRP : sender is not receiver");
3065 XRPL_ASSERT(amount.native(), "xrpl::transferXRP : amount is XRP");
3066
3067 SLE::pointer const sender = view.peek(keylet::account(from));
3068 SLE::pointer const receiver = view.peek(keylet::account(to));
3069 if (!sender || !receiver)
3070 return tefINTERNAL; // LCOV_EXCL_LINE
3071
3072 JLOG(j.trace()) << "transferXRP: " << to_string(from) << " -> "
3073 << to_string(to) << ") : " << amount.getFullText();
3074
3075 if (sender->getFieldAmount(sfBalance) < amount)
3076 {
3077 // VFALCO Its unfortunate we have to keep
3078 // mutating these TER everywhere
3079 // FIXME: this logic should be moved to callers maybe?
3080 // LCOV_EXCL_START
3081 return view.open() ? TER{telFAILED_PROCESSING}
3083 // LCOV_EXCL_STOP
3084 }
3085
3086 // Decrement XRP balance.
3087 sender->setFieldAmount(
3088 sfBalance, sender->getFieldAmount(sfBalance) - amount);
3089 view.update(sender);
3090
3091 receiver->setFieldAmount(
3092 sfBalance, receiver->getFieldAmount(sfBalance) + amount);
3093 view.update(receiver);
3094
3095 return tesSUCCESS;
3096}
3097
3098TER
3100 ReadView const& view,
3101 Issue const& issue,
3102 AccountID const& account,
3103 AuthType authType)
3104{
3105 if (isXRP(issue) || issue.account == account)
3106 return tesSUCCESS;
3107
3108 auto const trustLine =
3109 view.read(keylet::line(account, issue.account, issue.currency));
3110 // If account has no line, and this is a strong check, fail
3111 if (!trustLine && authType == AuthType::StrongAuth)
3112 return tecNO_LINE;
3113
3114 // If this is a weak or legacy check, or if the account has a line, fail if
3115 // auth is required and not set on the line
3116 if (auto const issuerAccount = view.read(keylet::account(issue.account));
3117 issuerAccount && (*issuerAccount)[sfFlags] & lsfRequireAuth)
3118 {
3119 if (trustLine)
3120 return ((*trustLine)[sfFlags] &
3121 ((account > issue.account) ? lsfLowAuth : lsfHighAuth))
3122 ? tesSUCCESS
3123 : TER{tecNO_AUTH};
3124 return TER{tecNO_LINE};
3125 }
3126
3127 return tesSUCCESS;
3128}
3129
3130TER
3132 ReadView const& view,
3133 MPTIssue const& mptIssue,
3134 AccountID const& account,
3135 AuthType authType,
3136 int depth)
3137{
3138 auto const mptID = keylet::mptIssuance(mptIssue.getMptID());
3139 auto const sleIssuance = view.read(mptID);
3140 if (!sleIssuance)
3141 return tecOBJECT_NOT_FOUND;
3142
3143 auto const mptIssuer = sleIssuance->getAccountID(sfIssuer);
3144
3145 // issuer is always "authorized"
3146 if (mptIssuer == account) // Issuer won't have MPToken
3147 return tesSUCCESS;
3148
3149 bool const featureSAVEnabled =
3150 view.rules().enabled(featureSingleAssetVault);
3151
3152 if (featureSAVEnabled)
3153 {
3154 if (depth >= maxAssetCheckDepth)
3155 return tecINTERNAL; // LCOV_EXCL_LINE
3156
3157 // requireAuth is recursive if the issuer is a vault pseudo-account
3158 auto const sleIssuer = view.read(keylet::account(mptIssuer));
3159 if (!sleIssuer)
3160 return tefINTERNAL; // LCOV_EXCL_LINE
3161
3162 if (sleIssuer->isFieldPresent(sfVaultID))
3163 {
3164 auto const sleVault =
3165 view.read(keylet::vault(sleIssuer->getFieldH256(sfVaultID)));
3166 if (!sleVault)
3167 return tefINTERNAL; // LCOV_EXCL_LINE
3168
3169 auto const asset = sleVault->at(sfAsset);
3170 if (auto const err = std::visit(
3171 [&]<ValidIssueType TIss>(TIss const& issue) {
3172 if constexpr (std::is_same_v<TIss, Issue>)
3173 return requireAuth(view, issue, account, authType);
3174 else
3175 return requireAuth(
3176 view, issue, account, authType, depth + 1);
3177 },
3178 asset.value());
3179 !isTesSuccess(err))
3180 return err;
3181 }
3182 }
3183
3184 auto const mptokenID = keylet::mptoken(mptID.key, account);
3185 auto const sleToken = view.read(mptokenID);
3186
3187 // if account has no MPToken, fail
3188 if (!sleToken &&
3189 (authType == AuthType::StrongAuth || authType == AuthType::Legacy))
3190 return tecNO_AUTH;
3191
3192 // Note, this check is not amendment-gated because DomainID will be always
3193 // empty **unless** writing to it has been enabled by an amendment
3194 auto const maybeDomainID = sleIssuance->at(~sfDomainID);
3195 if (maybeDomainID)
3196 {
3197 XRPL_ASSERT(
3198 sleIssuance->getFieldU32(sfFlags) & lsfMPTRequireAuth,
3199 "xrpl::requireAuth : issuance requires authorization");
3200 // ter = tefINTERNAL | tecOBJECT_NOT_FOUND | tecNO_AUTH | tecEXPIRED
3201 if (auto const ter =
3202 credentials::validDomain(view, *maybeDomainID, account);
3203 isTesSuccess(ter))
3204 return ter; // Note: sleToken might be null
3205 else if (!sleToken)
3206 return ter;
3207 // We ignore error from validDomain if we found sleToken, as it could
3208 // belong to someone who is explicitly authorized e.g. a vault owner.
3209 }
3210
3211 if (featureSAVEnabled)
3212 {
3213 // Implicitly authorize Vault and LoanBroker pseudo-accounts
3214 if (isPseudoAccount(view, account, {&sfVaultID, &sfLoanBrokerID}))
3215 return tesSUCCESS;
3216 }
3217
3218 // mptoken must be authorized if issuance enabled requireAuth
3219 if (sleIssuance->isFlag(lsfMPTRequireAuth) &&
3220 (!sleToken || !sleToken->isFlag(lsfMPTAuthorized)))
3221 return tecNO_AUTH;
3222
3223 return tesSUCCESS; // Note: sleToken might be null
3224}
3225
3226[[nodiscard]] TER
3228 ApplyView& view,
3229 MPTID const& mptIssuanceID,
3230 AccountID const& account,
3231 XRPAmount const& priorBalance, // for MPToken authorization
3233{
3234 auto const sleIssuance = view.read(keylet::mptIssuance(mptIssuanceID));
3235 if (!sleIssuance)
3236 return tefINTERNAL; // LCOV_EXCL_LINE
3237
3238 XRPL_ASSERT(
3239 sleIssuance->isFlag(lsfMPTRequireAuth),
3240 "xrpl::enforceMPTokenAuthorization : authorization required");
3241
3242 if (account == sleIssuance->at(sfIssuer))
3243 return tefINTERNAL; // LCOV_EXCL_LINE
3244
3245 auto const keylet = keylet::mptoken(mptIssuanceID, account);
3246 auto const sleToken = view.read(keylet); // NOTE: might be null
3247 auto const maybeDomainID = sleIssuance->at(~sfDomainID);
3248 bool expired = false;
3249 bool const authorizedByDomain = [&]() -> bool {
3250 // NOTE: defensive here, should be checked in preclaim
3251 if (!maybeDomainID.has_value())
3252 return false; // LCOV_EXCL_LINE
3253
3254 auto const ter = verifyValidDomain(view, account, *maybeDomainID, j);
3255 if (isTesSuccess(ter))
3256 return true;
3257 if (ter == tecEXPIRED)
3258 expired = true;
3259 return false;
3260 }();
3261
3262 if (!authorizedByDomain && sleToken == nullptr)
3263 {
3264 // Could not find MPToken and won't create one, could be either of:
3265 //
3266 // 1. Field sfDomainID not set in MPTokenIssuance or
3267 // 2. Account has no matching and accepted credentials or
3268 // 3. Account has all expired credentials (deleted in verifyValidDomain)
3269 //
3270 // Either way, return tecNO_AUTH and there is nothing else to do
3271 return expired ? tecEXPIRED : tecNO_AUTH;
3272 }
3273 else if (!authorizedByDomain && maybeDomainID.has_value())
3274 {
3275 // Found an MPToken but the account is not authorized and we expect
3276 // it to have been authorized by the domain. This could be because the
3277 // credentials used to create the MPToken have expired or been deleted.
3278 return expired ? tecEXPIRED : tecNO_AUTH;
3279 }
3280 else if (!authorizedByDomain)
3281 {
3282 // We found an MPToken, but sfDomainID is not set, so this is a classic
3283 // MPToken which requires authorization by the token issuer.
3284 XRPL_ASSERT(
3285 sleToken != nullptr && !maybeDomainID.has_value(),
3286 "xrpl::enforceMPTokenAuthorization : found MPToken");
3287 if (sleToken->isFlag(lsfMPTAuthorized))
3288 return tesSUCCESS;
3289
3290 return tecNO_AUTH;
3291 }
3292 else if (authorizedByDomain && sleToken != nullptr)
3293 {
3294 // Found an MPToken, authorized by the domain. Ignore authorization flag
3295 // lsfMPTAuthorized because it is meaningless. Return tesSUCCESS
3296 XRPL_ASSERT(
3297 maybeDomainID.has_value(),
3298 "xrpl::enforceMPTokenAuthorization : found MPToken for domain");
3299 return tesSUCCESS;
3300 }
3301 else if (authorizedByDomain)
3302 {
3303 // Could not find MPToken but there should be one because we are
3304 // authorized by domain. Proceed to create it, then return tesSUCCESS
3305 XRPL_ASSERT(
3306 maybeDomainID.has_value() && sleToken == nullptr,
3307 "xrpl::enforceMPTokenAuthorization : new MPToken for domain");
3308 if (auto const err = authorizeMPToken(
3309 view,
3310 priorBalance, // priorBalance
3311 mptIssuanceID, // mptIssuanceID
3312 account, // account
3313 j);
3314 !isTesSuccess(err))
3315 return err;
3316
3317 return tesSUCCESS;
3318 }
3319
3320 // LCOV_EXCL_START
3321 UNREACHABLE(
3322 "xrpl::enforceMPTokenAuthorization : condition list is incomplete");
3323 return tefINTERNAL;
3324 // LCOV_EXCL_STOP
3325}
3326
3327TER
3329 ReadView const& view,
3330 MPTIssue const& mptIssue,
3331 AccountID const& from,
3332 AccountID const& to)
3333{
3334 auto const mptID = keylet::mptIssuance(mptIssue.getMptID());
3335 auto const sleIssuance = view.read(mptID);
3336 if (!sleIssuance)
3337 return tecOBJECT_NOT_FOUND;
3338
3339 if (!(sleIssuance->getFieldU32(sfFlags) & lsfMPTCanTransfer))
3340 {
3341 if (from != (*sleIssuance)[sfIssuer] && to != (*sleIssuance)[sfIssuer])
3342 return TER{tecNO_AUTH};
3343 }
3344 return tesSUCCESS;
3345}
3346
3347[[nodiscard]] TER
3349 ReadView const& view,
3350 Issue const& issue,
3351 AccountID const& from,
3352 AccountID const& to)
3353{
3354 if (issue.native())
3355 return tesSUCCESS;
3356
3357 auto const& issuerId = issue.getIssuer();
3358 if (issuerId == from || issuerId == to)
3359 return tesSUCCESS;
3360 auto const sleIssuer = view.read(keylet::account(issuerId));
3361 if (sleIssuer == nullptr)
3362 return tefINTERNAL; // LCOV_EXCL_LINE
3363
3364 auto const isRippleDisabled = [&](AccountID account) -> bool {
3365 // Line might not exist, but some transfers can create it. If this
3366 // is the case, just check the default ripple on the issuer account.
3367 auto const line = view.read(keylet::line(account, issue));
3368 if (line)
3369 {
3370 bool const issuerHigh = issuerId > account;
3371 return line->isFlag(issuerHigh ? lsfHighNoRipple : lsfLowNoRipple);
3372 }
3373 return sleIssuer->isFlag(lsfDefaultRipple) == false;
3374 };
3375
3376 // Fail if rippling disabled on both trust lines
3377 if (isRippleDisabled(from) && isRippleDisabled(to))
3378 return terNO_RIPPLE;
3379
3380 return tesSUCCESS;
3381}
3382
3383TER
3385 ApplyView& view,
3386 Keylet const& ownerDirKeylet,
3387 EntryDeleter const& deleter,
3389 std::optional<uint16_t> maxNodesToDelete)
3390{
3391 // Delete all the entries in the account directory.
3392 std::shared_ptr<SLE> sleDirNode{};
3393 unsigned int uDirEntry{0};
3394 uint256 dirEntry{beast::zero};
3395 std::uint32_t deleted = 0;
3396
3397 if (view.exists(ownerDirKeylet) &&
3398 dirFirst(view, ownerDirKeylet.key, sleDirNode, uDirEntry, dirEntry))
3399 {
3400 do
3401 {
3402 if (maxNodesToDelete && ++deleted > *maxNodesToDelete)
3403 return tecINCOMPLETE;
3404
3405 // Choose the right way to delete each directory node.
3406 auto sleItem = view.peek(keylet::child(dirEntry));
3407 if (!sleItem)
3408 {
3409 // Directory node has an invalid index. Bail out.
3410 // LCOV_EXCL_START
3411 JLOG(j.fatal())
3412 << "DeleteAccount: Directory node in ledger " << view.seq()
3413 << " has index to object that is missing: "
3414 << to_string(dirEntry);
3415 return tefBAD_LEDGER;
3416 // LCOV_EXCL_STOP
3417 }
3418
3419 LedgerEntryType const nodeType{safe_cast<LedgerEntryType>(
3420 sleItem->getFieldU16(sfLedgerEntryType))};
3421
3422 // Deleter handles the details of specific account-owned object
3423 // deletion
3424 auto const [ter, skipEntry] = deleter(nodeType, dirEntry, sleItem);
3425 if (ter != tesSUCCESS)
3426 return ter;
3427
3428 // dirFirst() and dirNext() are like iterators with exposed
3429 // internal state. We'll take advantage of that exposed state
3430 // to solve a common C++ problem: iterator invalidation while
3431 // deleting elements from a container.
3432 //
3433 // We have just deleted one directory entry, which means our
3434 // "iterator state" is invalid.
3435 //
3436 // 1. During the process of getting an entry from the
3437 // directory uDirEntry was incremented from 'it' to 'it'+1.
3438 //
3439 // 2. We then deleted the entry at index 'it', which means the
3440 // entry that was at 'it'+1 has now moved to 'it'.
3441 //
3442 // 3. So we verify that uDirEntry is indeed 'it'+1. Then we jam it
3443 // back to 'it' to "un-invalidate" the iterator.
3444 XRPL_ASSERT(
3445 uDirEntry >= 1,
3446 "xrpl::cleanupOnAccountDelete : minimum dir entries");
3447 if (uDirEntry == 0)
3448 {
3449 // LCOV_EXCL_START
3450 JLOG(j.error())
3451 << "DeleteAccount iterator re-validation failed.";
3452 return tefBAD_LEDGER;
3453 // LCOV_EXCL_STOP
3454 }
3455 if (skipEntry == SkipEntry::No)
3456 uDirEntry--;
3457
3458 } while (
3459 dirNext(view, ownerDirKeylet.key, sleDirNode, uDirEntry, dirEntry));
3460 }
3461
3462 return tesSUCCESS;
3463}
3464
3465TER
3467 ApplyView& view,
3468 std::shared_ptr<SLE> sleState,
3469 std::optional<AccountID> const& ammAccountID,
3471{
3472 if (!sleState || sleState->getType() != ltRIPPLE_STATE)
3473 return tecINTERNAL; // LCOV_EXCL_LINE
3474
3475 auto const& [low, high] = std::minmax(
3476 sleState->getFieldAmount(sfLowLimit).getIssuer(),
3477 sleState->getFieldAmount(sfHighLimit).getIssuer());
3478 auto sleLow = view.peek(keylet::account(low));
3479 auto sleHigh = view.peek(keylet::account(high));
3480 if (!sleLow || !sleHigh)
3481 return tecINTERNAL; // LCOV_EXCL_LINE
3482
3483 bool const ammLow = sleLow->isFieldPresent(sfAMMID);
3484 bool const ammHigh = sleHigh->isFieldPresent(sfAMMID);
3485
3486 // can't both be AMM
3487 if (ammLow && ammHigh)
3488 return tecINTERNAL; // LCOV_EXCL_LINE
3489
3490 // at least one must be
3491 if (!ammLow && !ammHigh)
3492 return terNO_AMM;
3493
3494 // one must be the target amm
3495 if (ammAccountID && (low != *ammAccountID && high != *ammAccountID))
3496 return terNO_AMM;
3497
3498 if (auto const ter = trustDelete(view, sleState, low, high, j);
3499 ter != tesSUCCESS)
3500 {
3501 JLOG(j.error())
3502 << "deleteAMMTrustLine: failed to delete the trustline.";
3503 return ter;
3504 }
3505
3506 auto const uFlags = !ammLow ? lsfLowReserve : lsfHighReserve;
3507 if (!(sleState->getFlags() & uFlags))
3508 return tecINTERNAL; // LCOV_EXCL_LINE
3509
3510 adjustOwnerCount(view, !ammLow ? sleLow : sleHigh, -1, j);
3511
3512 return tesSUCCESS;
3513}
3514
3515TER
3517 ApplyView& view,
3518 AccountID const& uSenderID,
3519 AccountID const& uReceiverID,
3520 STAmount const& saAmount,
3521 bool bCheckIssuer,
3523{
3524 return std::visit(
3525 [&]<ValidIssueType TIss>(TIss const& issue) {
3526 if constexpr (std::is_same_v<TIss, Issue>)
3527 {
3528 return rippleCreditIOU(
3529 view, uSenderID, uReceiverID, saAmount, bCheckIssuer, j);
3530 }
3531 else
3532 {
3533 XRPL_ASSERT(
3534 !bCheckIssuer, "xrpl::rippleCredit : not checking issuer");
3535 return rippleCreditMPT(
3536 view, uSenderID, uReceiverID, saAmount, j);
3537 }
3538 },
3539 saAmount.asset().value());
3540}
3541
3542[[nodiscard]] std::optional<STAmount>
3544 std::shared_ptr<SLE const> const& vault,
3545 std::shared_ptr<SLE const> const& issuance,
3546 STAmount const& assets)
3547{
3548 XRPL_ASSERT(
3549 !assets.negative(),
3550 "xrpl::assetsToSharesDeposit : non-negative assets");
3551 XRPL_ASSERT(
3552 assets.asset() == vault->at(sfAsset),
3553 "xrpl::assetsToSharesDeposit : assets and vault match");
3554 if (assets.negative() || assets.asset() != vault->at(sfAsset))
3555 return std::nullopt; // LCOV_EXCL_LINE
3556
3557 Number const assetTotal = vault->at(sfAssetsTotal);
3558 STAmount shares{vault->at(sfShareMPTID)};
3559 if (assetTotal == 0)
3560 return STAmount{
3561 shares.asset(),
3562 Number(assets.mantissa(), assets.exponent() + vault->at(sfScale))
3563 .truncate()};
3564
3565 Number const shareTotal = issuance->at(sfOutstandingAmount);
3566 shares = ((shareTotal * assets) / assetTotal).truncate();
3567 return shares;
3568}
3569
3570[[nodiscard]] std::optional<STAmount>
3572 std::shared_ptr<SLE const> const& vault,
3573 std::shared_ptr<SLE const> const& issuance,
3574 STAmount const& shares)
3575{
3576 XRPL_ASSERT(
3577 !shares.negative(),
3578 "xrpl::sharesToAssetsDeposit : non-negative shares");
3579 XRPL_ASSERT(
3580 shares.asset() == vault->at(sfShareMPTID),
3581 "xrpl::sharesToAssetsDeposit : shares and vault match");
3582 if (shares.negative() || shares.asset() != vault->at(sfShareMPTID))
3583 return std::nullopt; // LCOV_EXCL_LINE
3584
3585 Number const assetTotal = vault->at(sfAssetsTotal);
3586 STAmount assets{vault->at(sfAsset)};
3587 if (assetTotal == 0)
3588 return STAmount{
3589 assets.asset(),
3590 shares.mantissa(),
3591 shares.exponent() - vault->at(sfScale),
3592 false};
3593
3594 Number const shareTotal = issuance->at(sfOutstandingAmount);
3595 assets = (assetTotal * shares) / shareTotal;
3596 return assets;
3597}
3598
3599[[nodiscard]] std::optional<STAmount>
3601 std::shared_ptr<SLE const> const& vault,
3602 std::shared_ptr<SLE const> const& issuance,
3603 STAmount const& assets,
3604 TruncateShares truncate)
3605{
3606 XRPL_ASSERT(
3607 !assets.negative(),
3608 "xrpl::assetsToSharesDeposit : non-negative assets");
3609 XRPL_ASSERT(
3610 assets.asset() == vault->at(sfAsset),
3611 "xrpl::assetsToSharesWithdraw : assets and vault match");
3612 if (assets.negative() || assets.asset() != vault->at(sfAsset))
3613 return std::nullopt; // LCOV_EXCL_LINE
3614
3615 Number assetTotal = vault->at(sfAssetsTotal);
3616 assetTotal -= vault->at(sfLossUnrealized);
3617 STAmount shares{vault->at(sfShareMPTID)};
3618 if (assetTotal == 0)
3619 return shares;
3620 Number const shareTotal = issuance->at(sfOutstandingAmount);
3621 Number result = (shareTotal * assets) / assetTotal;
3622 if (truncate == TruncateShares::yes)
3623 result = result.truncate();
3624 shares = result;
3625 return shares;
3626}
3627
3628[[nodiscard]] std::optional<STAmount>
3630 std::shared_ptr<SLE const> const& vault,
3631 std::shared_ptr<SLE const> const& issuance,
3632 STAmount const& shares)
3633{
3634 XRPL_ASSERT(
3635 !shares.negative(),
3636 "xrpl::sharesToAssetsDeposit : non-negative shares");
3637 XRPL_ASSERT(
3638 shares.asset() == vault->at(sfShareMPTID),
3639 "xrpl::sharesToAssetsWithdraw : shares and vault match");
3640 if (shares.negative() || shares.asset() != vault->at(sfShareMPTID))
3641 return std::nullopt; // LCOV_EXCL_LINE
3642
3643 Number assetTotal = vault->at(sfAssetsTotal);
3644 assetTotal -= vault->at(sfLossUnrealized);
3645 STAmount assets{vault->at(sfAsset)};
3646 if (assetTotal == 0)
3647 return assets;
3648 Number const shareTotal = issuance->at(sfOutstandingAmount);
3649 assets = (assetTotal * shares) / shareTotal;
3650 return assets;
3651}
3652
3653TER
3655 ApplyView& view,
3656 AccountID const& sender,
3657 STAmount const& amount,
3659{
3660 auto const mptIssue = amount.get<MPTIssue>();
3661 auto const mptID = keylet::mptIssuance(mptIssue.getMptID());
3662 auto sleIssuance = view.peek(mptID);
3663 if (!sleIssuance)
3664 { // LCOV_EXCL_START
3665 JLOG(j.error()) << "rippleLockEscrowMPT: MPT issuance not found for "
3666 << mptIssue.getMptID();
3667 return tecOBJECT_NOT_FOUND;
3668 } // LCOV_EXCL_STOP
3669
3670 if (amount.getIssuer() == sender)
3671 { // LCOV_EXCL_START
3672 JLOG(j.error())
3673 << "rippleLockEscrowMPT: sender is the issuer, cannot lock MPTs.";
3674 return tecINTERNAL;
3675 } // LCOV_EXCL_STOP
3676
3677 // 1. Decrease the MPT Holder MPTAmount
3678 // 2. Increase the MPT Holder EscrowedAmount
3679 {
3680 auto const mptokenID = keylet::mptoken(mptID.key, sender);
3681 auto sle = view.peek(mptokenID);
3682 if (!sle)
3683 { // LCOV_EXCL_START
3684 JLOG(j.error())
3685 << "rippleLockEscrowMPT: MPToken not found for " << sender;
3686 return tecOBJECT_NOT_FOUND;
3687 } // LCOV_EXCL_STOP
3688
3689 auto const amt = sle->getFieldU64(sfMPTAmount);
3690 auto const pay = amount.mpt().value();
3691
3692 // Underflow check for subtraction
3693 if (!canSubtract(STAmount(mptIssue, amt), STAmount(mptIssue, pay)))
3694 { // LCOV_EXCL_START
3695 JLOG(j.error())
3696 << "rippleLockEscrowMPT: insufficient MPTAmount for "
3697 << to_string(sender) << ": " << amt << " < " << pay;
3698 return tecINTERNAL;
3699 } // LCOV_EXCL_STOP
3700
3701 (*sle)[sfMPTAmount] = amt - pay;
3702
3703 // Overflow check for addition
3704 uint64_t const locked = (*sle)[~sfLockedAmount].value_or(0);
3705
3706 if (!canAdd(STAmount(mptIssue, locked), STAmount(mptIssue, pay)))
3707 { // LCOV_EXCL_START
3708 JLOG(j.error())
3709 << "rippleLockEscrowMPT: overflow on locked amount for "
3710 << to_string(sender) << ": " << locked << " + " << pay;
3711 return tecINTERNAL;
3712 } // LCOV_EXCL_STOP
3713
3714 if (sle->isFieldPresent(sfLockedAmount))
3715 (*sle)[sfLockedAmount] += pay;
3716 else
3717 sle->setFieldU64(sfLockedAmount, pay);
3718
3719 view.update(sle);
3720 }
3721
3722 // 1. Increase the Issuance EscrowedAmount
3723 // 2. DO NOT change the Issuance OutstandingAmount
3724 {
3725 uint64_t const issuanceEscrowed =
3726 (*sleIssuance)[~sfLockedAmount].value_or(0);
3727 auto const pay = amount.mpt().value();
3728
3729 // Overflow check for addition
3730 if (!canAdd(
3731 STAmount(mptIssue, issuanceEscrowed), STAmount(mptIssue, pay)))
3732 { // LCOV_EXCL_START
3733 JLOG(j.error()) << "rippleLockEscrowMPT: overflow on issuance "
3734 "locked amount for "
3735 << mptIssue.getMptID() << ": " << issuanceEscrowed
3736 << " + " << pay;
3737 return tecINTERNAL;
3738 } // LCOV_EXCL_STOP
3739
3740 if (sleIssuance->isFieldPresent(sfLockedAmount))
3741 (*sleIssuance)[sfLockedAmount] += pay;
3742 else
3743 sleIssuance->setFieldU64(sfLockedAmount, pay);
3744
3745 view.update(sleIssuance);
3746 }
3747 return tesSUCCESS;
3748}
3749
3750TER
3752 ApplyView& view,
3753 AccountID const& sender,
3754 AccountID const& receiver,
3755 STAmount const& netAmount,
3756 STAmount const& grossAmount,
3758{
3759 if (!view.rules().enabled(fixTokenEscrowV1))
3760 XRPL_ASSERT(
3761 netAmount == grossAmount,
3762 "xrpl::rippleUnlockEscrowMPT : netAmount == grossAmount");
3763
3764 auto const& issuer = netAmount.getIssuer();
3765 auto const& mptIssue = netAmount.get<MPTIssue>();
3766 auto const mptID = keylet::mptIssuance(mptIssue.getMptID());
3767 auto sleIssuance = view.peek(mptID);
3768 if (!sleIssuance)
3769 { // LCOV_EXCL_START
3770 JLOG(j.error()) << "rippleUnlockEscrowMPT: MPT issuance not found for "
3771 << mptIssue.getMptID();
3772 return tecOBJECT_NOT_FOUND;
3773 } // LCOV_EXCL_STOP
3774
3775 // Decrease the Issuance EscrowedAmount
3776 {
3777 if (!sleIssuance->isFieldPresent(sfLockedAmount))
3778 { // LCOV_EXCL_START
3779 JLOG(j.error())
3780 << "rippleUnlockEscrowMPT: no locked amount in issuance for "
3781 << mptIssue.getMptID();
3782 return tecINTERNAL;
3783 } // LCOV_EXCL_STOP
3784
3785 auto const locked = sleIssuance->getFieldU64(sfLockedAmount);
3786 auto const redeem = grossAmount.mpt().value();
3787
3788 // Underflow check for subtraction
3789 if (!canSubtract(
3790 STAmount(mptIssue, locked), STAmount(mptIssue, redeem)))
3791 { // LCOV_EXCL_START
3792 JLOG(j.error())
3793 << "rippleUnlockEscrowMPT: insufficient locked amount for "
3794 << mptIssue.getMptID() << ": " << locked << " < " << redeem;
3795 return tecINTERNAL;
3796 } // LCOV_EXCL_STOP
3797
3798 auto const newLocked = locked - redeem;
3799 if (newLocked == 0)
3800 sleIssuance->makeFieldAbsent(sfLockedAmount);
3801 else
3802 sleIssuance->setFieldU64(sfLockedAmount, newLocked);
3803 view.update(sleIssuance);
3804 }
3805
3806 if (issuer != receiver)
3807 {
3808 // Increase the MPT Holder MPTAmount
3809 auto const mptokenID = keylet::mptoken(mptID.key, receiver);
3810 auto sle = view.peek(mptokenID);
3811 if (!sle)
3812 { // LCOV_EXCL_START
3813 JLOG(j.error())
3814 << "rippleUnlockEscrowMPT: MPToken not found for " << receiver;
3815 return tecOBJECT_NOT_FOUND;
3816 } // LCOV_EXCL_STOP
3817
3818 auto current = sle->getFieldU64(sfMPTAmount);
3819 auto delta = netAmount.mpt().value();
3820
3821 // Overflow check for addition
3822 if (!canAdd(STAmount(mptIssue, current), STAmount(mptIssue, delta)))
3823 { // LCOV_EXCL_START
3824 JLOG(j.error())
3825 << "rippleUnlockEscrowMPT: overflow on MPTAmount for "
3826 << to_string(receiver) << ": " << current << " + " << delta;
3827 return tecINTERNAL;
3828 } // LCOV_EXCL_STOP
3829
3830 (*sle)[sfMPTAmount] += delta;
3831 view.update(sle);
3832 }
3833 else
3834 {
3835 // Decrease the Issuance OutstandingAmount
3836 auto const outstanding = sleIssuance->getFieldU64(sfOutstandingAmount);
3837 auto const redeem = netAmount.mpt().value();
3838
3839 // Underflow check for subtraction
3840 if (!canSubtract(
3841 STAmount(mptIssue, outstanding), STAmount(mptIssue, redeem)))
3842 { // LCOV_EXCL_START
3843 JLOG(j.error())
3844 << "rippleUnlockEscrowMPT: insufficient outstanding amount for "
3845 << mptIssue.getMptID() << ": " << outstanding << " < "
3846 << redeem;
3847 return tecINTERNAL;
3848 } // LCOV_EXCL_STOP
3849
3850 sleIssuance->setFieldU64(sfOutstandingAmount, outstanding - redeem);
3851 view.update(sleIssuance);
3852 }
3853
3854 if (issuer == sender)
3855 { // LCOV_EXCL_START
3856 JLOG(j.error()) << "rippleUnlockEscrowMPT: sender is the issuer, "
3857 "cannot unlock MPTs.";
3858 return tecINTERNAL;
3859 } // LCOV_EXCL_STOP
3860 else
3861 {
3862 // Decrease the MPT Holder EscrowedAmount
3863 auto const mptokenID = keylet::mptoken(mptID.key, sender);
3864 auto sle = view.peek(mptokenID);
3865 if (!sle)
3866 { // LCOV_EXCL_START
3867 JLOG(j.error())
3868 << "rippleUnlockEscrowMPT: MPToken not found for " << sender;
3869 return tecOBJECT_NOT_FOUND;
3870 } // LCOV_EXCL_STOP
3871
3872 if (!sle->isFieldPresent(sfLockedAmount))
3873 { // LCOV_EXCL_START
3874 JLOG(j.error())
3875 << "rippleUnlockEscrowMPT: no locked amount in MPToken for "
3876 << to_string(sender);
3877 return tecINTERNAL;
3878 } // LCOV_EXCL_STOP
3879
3880 auto const locked = sle->getFieldU64(sfLockedAmount);
3881 auto const delta = grossAmount.mpt().value();
3882
3883 // Underflow check for subtraction
3884 if (!canSubtract(STAmount(mptIssue, locked), STAmount(mptIssue, delta)))
3885 { // LCOV_EXCL_START
3886 JLOG(j.error())
3887 << "rippleUnlockEscrowMPT: insufficient locked amount for "
3888 << to_string(sender) << ": " << locked << " < " << delta;
3889 return tecINTERNAL;
3890 } // LCOV_EXCL_STOP
3891
3892 auto const newLocked = locked - delta;
3893 if (newLocked == 0)
3894 sle->makeFieldAbsent(sfLockedAmount);
3895 else
3896 sle->setFieldU64(sfLockedAmount, newLocked);
3897 view.update(sle);
3898 }
3899
3900 // Note: The gross amount is the amount that was locked, the net
3901 // amount is the amount that is being unlocked. The difference is the fee
3902 // that was charged for the transfer. If this difference is greater than
3903 // zero, we need to update the outstanding amount.
3904 auto const diff = grossAmount.mpt().value() - netAmount.mpt().value();
3905 if (diff != 0)
3906 {
3907 auto const outstanding = sleIssuance->getFieldU64(sfOutstandingAmount);
3908 // Underflow check for subtraction
3909 if (!canSubtract(
3910 STAmount(mptIssue, outstanding), STAmount(mptIssue, diff)))
3911 { // LCOV_EXCL_START
3912 JLOG(j.error())
3913 << "rippleUnlockEscrowMPT: insufficient outstanding amount for "
3914 << mptIssue.getMptID() << ": " << outstanding << " < " << diff;
3915 return tecINTERNAL;
3916 } // LCOV_EXCL_STOP
3917
3918 sleIssuance->setFieldU64(sfOutstandingAmount, outstanding - diff);
3919 view.update(sleIssuance);
3920 }
3921 return tesSUCCESS;
3922}
3923
3924bool
3926{
3927 return now.time_since_epoch().count() > mark;
3928}
3929
3930} // namespace xrpl
Provide a light-weight way to check active() before string formatting.
Definition Journal.h:186
A generic endpoint for log messages.
Definition Journal.h:41
Stream fatal() const
Definition Journal.h:333
Stream error() const
Definition Journal.h:327
Stream debug() const
Definition Journal.h:309
static Sink & getNullSink()
Returns a Sink which does nothing.
Stream trace() const
Severity stream access functions.
Definition Journal.h:303
Stream warn() const
Definition Journal.h:321
Writeable view to a ledger, for applying a transaction.
Definition ApplyView.h:124
virtual void update(std::shared_ptr< SLE > const &sle)=0
Indicate changes to a peeked SLE.
bool dirRemove(Keylet const &directory, std::uint64_t page, uint256 const &key, bool keepRoot)
Remove an entry from a directory.
virtual void erase(std::shared_ptr< SLE > const &sle)=0
Remove a peeked SLE.
virtual void insert(std::shared_ptr< SLE > const &sle)=0
Insert a new state SLE.
virtual void creditHook(AccountID const &from, AccountID const &to, STAmount const &amount, STAmount const &preCreditBalance)
Definition ApplyView.h:224
virtual void adjustOwnerCountHook(AccountID const &account, std::uint32_t cur, std::uint32_t next)
Definition ApplyView.h:235
std::optional< std::uint64_t > dirInsert(Keylet const &directory, uint256 const &key, std::function< void(std::shared_ptr< SLE > const &)> const &describe)
Insert an entry to a directory.
Definition ApplyView.h:300
virtual std::shared_ptr< SLE > peek(Keylet const &k)=0
Prepare to modify the SLE associated with key.
constexpr value_type const & value() const
Definition Asset.h:157
A currency issued by an account.
Definition Issue.h:14
Currency currency
Definition Issue.h:16
AccountID account
Definition Issue.h:17
bool native() const
Definition Issue.cpp:47
AccountID const & getIssuer() const
Definition Issue.h:26
Item const * findByType(KeyType type) const
Retrieve a format based on its type.
static LedgerFormats const & getInstance()
constexpr value_type value() const
Returns the underlying value.
Definition MPTAmount.h:114
constexpr MPTID const & getMptID() const
Definition MPTIssue.h:27
AccountID const & getIssuer() const
Definition MPTIssue.cpp:21
std::chrono::time_point< NetClock > time_point
Definition chrono.h:50
std::chrono::duration< rep, period > duration
Definition chrono.h:49
Number truncate() const noexcept
Definition Number.cpp:506
A view into a ledger.
Definition ReadView.h:32
virtual Rules const & rules() const =0
Returns the tx processing rules.
NetClock::time_point parentCloseTime() const
Returns the close time of the previous ledger.
Definition ReadView.h:92
virtual Fees const & fees() const =0
Returns the fees for the base ledger.
virtual bool exists(Keylet const &k) const =0
Determine if a state item exists.
virtual LedgerHeader const & header() const =0
Returns information about the ledger.
virtual STAmount balanceHook(AccountID const &account, AccountID const &issuer, STAmount const &amount) const
Definition ReadView.h:159
virtual bool open() const =0
Returns true if this reflects an open ledger.
LedgerIndex seq() const
Returns the sequence number of the base ledger.
Definition ReadView.h:99
virtual std::uint32_t ownerCountHook(AccountID const &account, std::uint32_t count) const
Definition ReadView.h:173
virtual std::shared_ptr< SLE const > read(Keylet const &k) const =0
Return the state item associated with a key.
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition Rules.cpp:111
Identifies fields.
Definition SField.h:127
@ sMD_PseudoAccount
Definition SField.h:137
constexpr bool holds() const noexcept
Definition STAmount.h:457
constexpr TIss const & get() const
std::string getFullText() const override
Definition STAmount.cpp:653
static constexpr std::uint64_t cMaxValue
Definition STAmount.h:52
void setIssuer(AccountID const &uIssuer)
Definition STAmount.h:580
void negate()
Definition STAmount.h:556
std::uint64_t mantissa() const noexcept
Definition STAmount.h:469
bool negative() const noexcept
Definition STAmount.h:463
STAmount zeroed() const
Returns a zero value with the same issuer and currency.
Definition STAmount.h:512
static int const cMaxOffset
Definition STAmount.h:47
Currency const & getCurrency() const
Definition STAmount.h:494
bool native() const noexcept
Definition STAmount.h:450
Asset const & asset() const
Definition STAmount.h:475
MPTAmount mpt() const
Definition STAmount.cpp:295
int exponent() const noexcept
Definition STAmount.h:438
AccountID const & getIssuer() const
Definition STAmount.h:500
std::shared_ptr< STLedgerEntry > const & ref
std::shared_ptr< STLedgerEntry const > const_pointer
bool isFieldPresent(SField const &field) const
Definition STObject.cpp:465
std::size_t size() const
T count_if(T... args)
T emplace_back(T... args)
T is_same_v
T max(T... args)
T minmax(T... args)
bool internalDirFirst(V &view, uint256 const &root, std::shared_ptr< N > &page, unsigned int &index, uint256 &entry)
Definition View.cpp:82
bool internalDirNext(V &view, uint256 const &root, std::shared_ptr< N > &page, unsigned int &index, uint256 &entry)
Definition View.cpp:34
Keylet const & skip() noexcept
The index of the "short" skip list.
Definition Indexes.cpp:178
Keylet depositPreauth(AccountID const &owner, AccountID const &preauthorized) noexcept
A DepositPreauth.
Definition Indexes.cpp:324
Keylet const & amendments() noexcept
The index of the amendment table.
Definition Indexes.cpp:196
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition Indexes.cpp:356
Keylet mptIssuance(std::uint32_t seq, AccountID const &issuer) noexcept
Definition Indexes.cpp:508
Keylet amm(Asset const &issue1, Asset const &issue2) noexcept
AMM entry.
Definition Indexes.cpp:428
Keylet child(uint256 const &key) noexcept
Any item that can be in an owner dir.
Definition Indexes.cpp:172
Keylet vault(AccountID const &owner, std::uint32_t seq) noexcept
Definition Indexes.cpp:546
Keylet line(AccountID const &id0, AccountID const &id1, Currency const &currency) noexcept
The index of a trust line for a given currency.
Definition Indexes.cpp:226
Keylet mptoken(MPTID const &issuanceID, AccountID const &holder) noexcept
Definition Indexes.cpp:522
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:166
Keylet page(uint256 const &root, std::uint64_t index=0) noexcept
A page in a directory.
Definition Indexes.cpp:362
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
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 uSrcQualityIn, std::uint32_t uSrcQualityOut, beast::Journal j)
Create a trust line.
Definition View.cpp:1638
std::optional< STAmount > sharesToAssetsDeposit(std::shared_ptr< SLE const > const &vault, std::shared_ptr< SLE const > const &issuance, STAmount const &shares)
Definition View.cpp:3571
@ telFAILED_PROCESSING
Definition TER.h:37
@ terNO_AMM
Definition TER.h:208
@ terNO_RIPPLE
Definition TER.h:205
@ terNO_ACCOUNT
Definition TER.h:198
std::vector< SField const * > const & getPseudoAccountFields()
Definition View.cpp:1155
XRPAmount xrpLiquid(ReadView const &view, AccountID const &id, std::int32_t ownerCountAdj, beast::Journal j)
Definition View.cpp:677
TER addEmptyHolding(ApplyView &view, AccountID const &accountID, XRPAmount priorBalance, Issue const &issue, beast::Journal journal)
Any transactors that call addEmptyHolding() in doApply must call canAddHolding() in preflight with th...
Definition View.cpp:1442
void LogicError(std::string const &how) noexcept
Called when faulty logic causes a broken invariant.
FreezeHandling
Controls the treatment of frozen account balances.
Definition View.h:59
@ fhZERO_IF_FROZEN
Definition View.h:59
@ fhIGNORE_FREEZE
Definition View.h:59
bool dirFirst(ApplyView &view, uint256 const &root, std::shared_ptr< SLE > &page, unsigned int &index, uint256 &entry)
Definition View.cpp:105
AccountID pseudoAccountAddress(ReadView const &view, uint256 const &pseudoOwnerKey)
Definition View.cpp:1131
bool dirIsEmpty(ReadView const &view, Keylet const &k)
Returns true if the directory is empty.
Definition View.cpp:965
SpendableHandling
Controls whether to include the account's full spendable balance.
Definition View.h:65
@ shFULL_BALANCE
Definition View.h:65
bool isXRP(AccountID const &c)
Definition AccountID.h:71
TER canAddHolding(ReadView const &view, Asset const &asset)
Definition View.cpp:1277
std::uint8_t constexpr maxAssetCheckDepth
Maximum recursion depth for vault shares being put as an asset inside another vault; counted from 0.
Definition Protocol.h:252
std::map< uint256, NetClock::time_point > majorityAmendments_t
Definition View.h:473
std::set< uint256 > getEnabledAmendments(ReadView const &view)
Definition View.cpp:979
TER issueIOU(ApplyView &view, AccountID const &account, STAmount const &amount, Issue const &issue, beast::Journal j)
Definition View.cpp:2879
sha512_half_hasher::result_type sha512Half(Args const &... args)
Returns the SHA512-Half of a series of objects.
Definition digest.h:205
TER verifyValidDomain(ApplyView &view, AccountID const &account, uint256 domainID, beast::Journal j)
TER removeEmptyHolding(ApplyView &view, AccountID const &accountID, Issue const &issue, beast::Journal journal)
Definition View.cpp:1765
bool isVaultPseudoAccountFrozen(ReadView const &view, AccountID const &account, MPTIssue const &mptShare, int depth)
Definition View.cpp:289
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:611
TER rippleUnlockEscrowMPT(ApplyView &view, AccountID const &uGrantorID, AccountID const &uGranteeID, STAmount const &netAmount, STAmount const &grossAmount, beast::Journal j)
Definition View.cpp:3751
TER doWithdraw(ApplyView &view, STTx const &tx, AccountID const &senderAcct, AccountID const &dstAcct, AccountID const &sourceAcct, XRPAmount priorBalance, STAmount const &amount, beast::Journal j)
Definition View.cpp:1393
static TER accountSendMultiIOU(ApplyView &view, AccountID const &senderID, Issue const &issue, MultiplePaymentDestinations const &receivers, beast::Journal j, WaiveTransferFee waiveFee)
Definition View.cpp:2378
bool hasExpired(ReadView const &view, std::optional< std::uint32_t > const &exp)
Determines whether the given expiration time has passed.
Definition View.cpp:155
static TER rippleSendMPT(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, STAmount &saActual, beast::Journal j, WaiveTransferFee waiveFee)
Definition View.cpp:2578
@ expired
List is expired, but has the largest non-pending sequence seen so far.
std::uint64_t constexpr maxMPTokenAmount
The maximum amount of MPTokenIssuance.
Definition Protocol.h:235
void forEachItem(ReadView const &view, Keylet const &root, std::function< void(std::shared_ptr< SLE const > const &)> const &f)
Iterate all items in the given directory.
Definition View.cpp:715
@ tefBAD_LEDGER
Definition TER.h:151
@ tefINTERNAL
Definition TER.h:154
bool isAnyFrozen(ReadView const &view, std::initializer_list< AccountID > const &accounts, MPTIssue const &mptIssue, int depth=0)
Definition View.cpp:264
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j, SpendableHandling includeFullBalance=shSIMPLE_BALANCE)
Definition View.cpp:462
WaiveTransferFee
Definition View.h:25
Number root(Number f, unsigned d)
Definition Number.cpp:644
static TER accountSendMPT(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee)
Definition View.cpp:2740
static TER rippleCreditMPT(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, beast::Journal j)
Definition View.cpp:2516
bool cdirNext(ReadView const &view, uint256 const &root, std::shared_ptr< SLE const > &page, unsigned int &index, uint256 &entry)
Returns the next entry in the directory, advancing the index.
Definition View.cpp:138
STAmount multiply(STAmount const &amount, Rate const &rate)
Definition Rate2.cpp:34
base_uint< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition AccountID.h:29
TER authorizeMPToken(ApplyView &view, XRPAmount const &priorBalance, MPTID const &mptIssuanceID, AccountID const &account, beast::Journal journal, std::uint32_t flags=0, std::optional< AccountID > holderID=std::nullopt)
Definition View.cpp:1518
TER deleteAMMTrustLine(ApplyView &view, std::shared_ptr< SLE > sleState, std::optional< AccountID > const &ammAccountID, beast::Journal j)
Delete trustline to AMM.
Definition View.cpp:3466
STAmount creditLimit(ReadView const &view, AccountID const &account, AccountID const &issuer, Currency const &currency)
Calculate the maximum amount of IOUs that an account can hold.
Definition Credit.cpp:9
static TER rippleCreditIOU(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, bool bCheckIssuer, beast::Journal j)
Definition View.cpp:1967
bool isDeepFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:332
bool cdirFirst(ReadView const &view, uint256 const &root, std::shared_ptr< SLE const > &page, unsigned int &index, uint256 &entry)
Returns the first entry in the directory, advancing the index.
Definition View.cpp:127
static TER rippleSendIOU(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, STAmount &saActual, beast::Journal j, WaiveTransferFee waiveFee)
Definition View.cpp:2121
static TER rippleSendMultiMPT(ApplyView &view, AccountID const &senderID, MPTIssue const &mptIssue, MultiplePaymentDestinations const &receivers, STAmount &actual, beast::Journal j, WaiveTransferFee waiveFee)
Definition View.cpp:2644
TER verifyDepositPreauth(STTx const &tx, ApplyView &view, AccountID const &src, AccountID const &dst, std::shared_ptr< SLE > const &sleDst, beast::Journal j)
@ current
This was a new validation and was added.
bool areCompatible(ReadView const &validLedger, ReadView const &testLedger, beast::Journal::Stream &s, char const *reason)
Return false if the test ledger is provably incompatible with the valid ledger, that is,...
Definition View.cpp:857
bool isPseudoAccount(std::shared_ptr< SLE const > sleAcct, std::set< SField const * > const &pseudoFieldFilter={})
Definition View.cpp:1181
TruncateShares
Definition View.h:1151
static std::uint32_t confineOwnerCount(std::uint32_t current, std::int32_t adjustment, std::optional< AccountID > const &id=std::nullopt, beast::Journal j=beast::Journal{beast::Journal::getNullSink()})
Definition View.cpp:639
TER accountSend(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No)
Calls static accountSendIOU if saAmount represents Issue.
Definition View.cpp:2780
STLedgerEntry SLE
std::optional< STAmount > assetsToSharesDeposit(std::shared_ptr< SLE const > const &vault, std::shared_ptr< SLE const > const &issuance, STAmount const &assets)
Definition View.cpp:3543
STAmount accountFunds(ReadView const &view, AccountID const &id, STAmount const &saDefault, FreezeHandling freezeHandling, beast::Journal j)
Definition View.cpp:613
bool isGlobalFrozen(ReadView const &view, AccountID const &issuer)
Definition View.cpp:164
std::optional< uint256 > hashOfSeq(ReadView const &ledger, LedgerIndex seq, beast::Journal journal)
Return the hash of a ledger by sequence.
Definition View.cpp:1019
std::optional< STAmount > assetsToSharesWithdraw(std::shared_ptr< SLE const > const &vault, std::shared_ptr< SLE const > const &issuance, STAmount const &assets, TruncateShares truncate=TruncateShares::no)
Definition View.cpp:3600
static TER rippleSendMultiIOU(ApplyView &view, AccountID const &senderID, Issue const &issue, MultiplePaymentDestinations const &receivers, STAmount &actual, beast::Journal j, WaiveTransferFee waiveFee)
Definition View.cpp:2176
bool canAdd(STAmount const &amt1, STAmount const &amt2)
Safely checks if two STAmount values can be added without overflow, underflow, or precision loss.
Definition STAmount.cpp:485
TERSubset< CanCvtToTER > TER
Definition TER.h:630
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1090
static STAmount getTrustLineBalance(ReadView const &view, SLE::const_ref sle, AccountID const &account, Currency const &currency, AccountID const &issuer, bool includeOppositeLimit, beast::Journal j)
Definition View.cpp:423
static TER withdrawToDestExceedsLimit(ReadView const &view, AccountID const &from, AccountID const &to, STAmount const &amount)
Definition View.cpp:1317
AuthHandling
Controls the treatment of unauthorized MPT balances.
Definition View.h:62
@ ahIGNORE_AUTH
Definition View.h:62
@ ahZERO_IF_UNAUTHORIZED
Definition View.h:62
Rate transferRate(ReadView const &view, AccountID const &issuer)
Returns IOU issuer transfer fee as Rate.
Definition View.cpp:820
static SLE::const_pointer getLineIfUsable(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j)
Definition View.cpp:369
majorityAmendments_t getMajorityAmendments(ReadView const &view)
Definition View.cpp:996
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition View.cpp:3925
AuthType
Definition View.h:967
STAmount creditBalance(ReadView const &view, AccountID const &account, AccountID const &issuer, Currency const &currency)
Returns the amount of IOUs issued by issuer that are held by an account.
Definition Credit.cpp:46
TER requireAuth(ReadView const &view, Issue const &issue, AccountID const &account, AuthType authType=AuthType::Legacy)
Check if the account lacks required authorization.
Definition View.cpp:3099
TER canTransfer(ReadView const &view, MPTIssue const &mptIssue, AccountID const &from, AccountID const &to)
Check if the destination account is allowed to receive MPT.
Definition View.cpp:3328
bool isFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:229
TER transferXRP(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &amount, beast::Journal j)
Definition View.cpp:3054
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
Definition View.cpp:1108
constexpr std::uint32_t const tfMPTUnauthorize
Definition TxFlags.h:153
TER dirLink(ApplyView &view, AccountID const &owner, std::shared_ptr< SLE > &object, SF_UINT64 const &node=sfOwnerNode)
Definition View.cpp:1116
bool isIndividualFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:195
TER offerDelete(ApplyView &view, std::shared_ptr< SLE > const &sle, beast::Journal j)
Delete an offer.
Definition View.cpp:1906
AccountID const & noAccount()
A placeholder for empty accounts.
static bool updateTrustLine(ApplyView &view, SLE::pointer state, bool bSenderHigh, AccountID const &sender, STAmount const &before, STAmount const &after, beast::Journal j)
Definition View.cpp:2826
TER redeemIOU(ApplyView &view, AccountID const &account, STAmount const &amount, Issue const &issue, beast::Journal j)
Definition View.cpp:2979
static TER accountSendIOU(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee)
Definition View.cpp:2254
bool canSubtract(STAmount const &amt1, STAmount const &amt2)
Determines if it is safe to subtract one STAmount from another.
Definition STAmount.cpp:565
bool isTesSuccess(TER x) noexcept
Definition TER.h:659
std::optional< STAmount > sharesToAssetsWithdraw(std::shared_ptr< SLE const > const &vault, std::shared_ptr< SLE const > const &issuance, STAmount const &shares)
Definition View.cpp:3629
TER rippleLockEscrowMPT(ApplyView &view, AccountID const &uGrantorID, STAmount const &saAmount, beast::Journal j)
Definition View.cpp:3654
LedgerEntryType
Identifiers for on-ledger objects.
AccountID const & xrpAccount()
Compute AccountID from public key.
@ tecDIR_FULL
Definition TER.h:269
@ tecNO_LINE_INSUF_RESERVE
Definition TER.h:274
@ tecNO_TARGET
Definition TER.h:286
@ tecPATH_DRY
Definition TER.h:276
@ tecINCOMPLETE
Definition TER.h:317
@ tecOBJECT_NOT_FOUND
Definition TER.h:308
@ tecNO_AUTH
Definition TER.h:282
@ tecINTERNAL
Definition TER.h:292
@ tecFAILED_PROCESSING
Definition TER.h:268
@ tecFROZEN
Definition TER.h:285
@ tecINSUFFICIENT_FUNDS
Definition TER.h:307
@ tecEXPIRED
Definition TER.h:296
@ tecNO_LINE
Definition TER.h:283
@ tecINSUFFICIENT_RESERVE
Definition TER.h:289
@ tecNO_PERMISSION
Definition TER.h:287
@ tecDST_TAG_NEEDED
Definition TER.h:291
@ tecDUPLICATE
Definition TER.h:297
@ tecHAS_OBLIGATIONS
Definition TER.h:299
@ tecNO_DST
Definition TER.h:272
bool forEachItemAfter(ReadView const &view, Keylet const &root, uint256 const &after, std::uint64_t const hint, unsigned int limit, std::function< bool(std::shared_ptr< SLE const > const &)> const &f)
Iterate all items after an item in the given directory.
Definition View.cpp:742
static TER accountSendMultiMPT(ApplyView &view, AccountID const &senderID, MPTIssue const &mptIssue, MultiplePaymentDestinations const &receivers, beast::Journal j, WaiveTransferFee waiveFee)
Definition View.cpp:2765
@ lsfHighReserve
@ lsfDepositAuth
@ lsfLowReserve
@ lsfRequireAuth
@ lsfDefaultRipple
@ lsfLowDeepFreeze
@ lsfMPTRequireAuth
@ lsfRequireDestTag
@ lsfMPTLocked
@ lsfMPTAuthorized
@ lsfMPTCanTransfer
@ lsfLowNoRipple
@ lsfGlobalFreeze
@ lsfLowFreeze
@ lsfDisableMaster
@ lsfHighFreeze
@ lsfHighNoRipple
@ lsfHighDeepFreeze
@ lsfHighAuth
TER enforceMPTokenAuthorization(ApplyView &view, MPTID const &mptIssuanceID, AccountID const &account, XRPAmount const &priorBalance, beast::Journal j)
Enforce account has MPToken to match its authorization.
Definition View.cpp:3227
TER canWithdraw(ReadView const &view, AccountID const &from, AccountID const &to, SLE::const_ref toSle, STAmount const &amount, bool hasDestinationTag)
Checks that can withdraw funds from an object to itself or a destination.
Definition View.cpp:1346
Expected< std::shared_ptr< SLE >, TER > createPseudoAccount(ApplyView &view, uint256 const &pseudoOwnerKey, SField const &ownerField)
Create pseudo-account, storing pseudoOwnerKey into ownerField.
Definition View.cpp:1201
TER checkDestinationAndTag(SLE::const_ref toSle, bool hasDestinationTag)
Validates that the destination SLE and tag are valid.
Definition View.cpp:1287
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!...
Definition View.cpp:2801
TER trustDelete(ApplyView &view, std::shared_ptr< SLE > const &sleRippleState, AccountID const &uLowAccountID, AccountID const &uHighAccountID, beast::Journal j)
Definition View.cpp:1866
bool isLPTokenFrozen(ReadView const &view, AccountID const &account, Issue const &asset, Issue const &asset2)
Definition View.cpp:358
@ tesSUCCESS
Definition TER.h:226
Rate const parityRate
A transfer rate signifying a 1:1 exchange.
bool dirNext(ApplyView &view, uint256 const &root, std::shared_ptr< SLE > &page, unsigned int &index, uint256 &entry)
Definition View.cpp:116
TER cleanupOnAccountDelete(ApplyView &view, Keylet const &ownerDirKeylet, EntryDeleter const &deleter, beast::Journal j, std::optional< std::uint16_t > maxNodesToDelete=std::nullopt)
Cleanup owner directory entries on account delete.
TER rippleCredit(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, bool bCheckIssuer, beast::Journal j)
Calls static rippleCreditIOU if saAmount represents Issue.
Definition View.cpp:3516
T size(T... args)
XRPAmount accountReserve(std::size_t ownerCount) const
Returns the account reserve given the owner count, in drops.
A pair of SHAMap key and LedgerEntryType.
Definition Keylet.h:20
uint256 key
Definition Keylet.h:21
Represents a transfer rate.
Definition Rate.h:21
A field with a type known at compile time.
Definition SField.h:301
Returns the RIPEMD-160 digest of the SHA256 hash of the message.
Definition digest.h:117
T time_since_epoch(T... args)
T visit(T... args)