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