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
1246 // Can the account cover the trust line reserve ?
1247 std::uint32_t const ownerCount = sleDst->at(sfOwnerCount);
1248 if (priorBalance < view.fees().accountReserve(ownerCount + 1))
1250
1251 return trustCreate(
1252 view,
1253 high,
1254 srcId,
1255 dstId,
1256 index.key,
1257 sleDst,
1258 /*auth=*/false,
1259 /*noRipple=*/true,
1260 /*freeze=*/false,
1261 /*deepFreeze*/ false,
1262 /*balance=*/STAmount{Issue{currency, noAccount()}},
1263 /*limit=*/STAmount{Issue{currency, dstId}},
1264 /*qualityIn=*/0,
1265 /*qualityOut=*/0,
1266 journal);
1267}
1268
1269[[nodiscard]] TER
1271 ApplyView& view,
1272 AccountID const& accountID,
1273 XRPAmount priorBalance,
1274 MPTIssue const& mptIssue,
1275 beast::Journal journal)
1276{
1277 auto const& mptID = mptIssue.getMptID();
1278 auto const mpt = view.peek(keylet::mptIssuance(mptID));
1279 if (!mpt)
1280 return tefINTERNAL; // LCOV_EXCL_LINE
1281 if (mpt->isFlag(lsfMPTLocked))
1282 return tefINTERNAL; // LCOV_EXCL_LINE
1283 if (view.peek(keylet::mptoken(mptID, accountID)))
1284 return tecDUPLICATE;
1285
1286 return authorizeMPToken(view, priorBalance, mptID, accountID, journal);
1287}
1288
1289[[nodiscard]] TER
1291 ApplyView& view,
1292 XRPAmount const& priorBalance,
1293 MPTID const& mptIssuanceID,
1294 AccountID const& account,
1295 beast::Journal journal,
1296 std::uint32_t flags,
1297 std::optional<AccountID> holderID)
1298{
1299 auto const sleAcct = view.peek(keylet::account(account));
1300 if (!sleAcct)
1301 return tecINTERNAL; // LCOV_EXCL_LINE
1302
1303 // If the account that submitted the tx is a holder
1304 // Note: `account_` is holder's account
1305 // `holderID` is NOT used
1306 if (!holderID)
1307 {
1308 // When a holder wants to unauthorize/delete a MPT, the ledger must
1309 // - delete mptokenKey from owner directory
1310 // - delete the MPToken
1311 if (flags & tfMPTUnauthorize)
1312 {
1313 auto const mptokenKey = keylet::mptoken(mptIssuanceID, account);
1314 auto const sleMpt = view.peek(mptokenKey);
1315 if (!sleMpt || (*sleMpt)[sfMPTAmount] != 0)
1316 return tecINTERNAL; // LCOV_EXCL_LINE
1317
1318 if (!view.dirRemove(
1319 keylet::ownerDir(account),
1320 (*sleMpt)[sfOwnerNode],
1321 sleMpt->key(),
1322 false))
1323 return tecINTERNAL; // LCOV_EXCL_LINE
1324
1325 adjustOwnerCount(view, sleAcct, -1, journal);
1326
1327 view.erase(sleMpt);
1328 return tesSUCCESS;
1329 }
1330
1331 // A potential holder wants to authorize/hold a mpt, the ledger must:
1332 // - add the new mptokenKey to the owner directory
1333 // - create the MPToken object for the holder
1334
1335 // The reserve that is required to create the MPToken. Note
1336 // that although the reserve increases with every item
1337 // an account owns, in the case of MPTokens we only
1338 // *enforce* a reserve if the user owns more than two
1339 // items. This is similar to the reserve requirements of trust lines.
1340 std::uint32_t const uOwnerCount = sleAcct->getFieldU32(sfOwnerCount);
1341 XRPAmount const reserveCreate(
1342 (uOwnerCount < 2) ? XRPAmount(beast::zero)
1343 : view.fees().accountReserve(uOwnerCount + 1));
1344
1345 if (priorBalance < reserveCreate)
1347
1348 auto const mptokenKey = keylet::mptoken(mptIssuanceID, account);
1349 auto mptoken = std::make_shared<SLE>(mptokenKey);
1350 if (auto ter = dirLink(view, account, mptoken))
1351 return ter; // LCOV_EXCL_LINE
1352
1353 (*mptoken)[sfAccount] = account;
1354 (*mptoken)[sfMPTokenIssuanceID] = mptIssuanceID;
1355 (*mptoken)[sfFlags] = 0;
1356 view.insert(mptoken);
1357
1358 // Update owner count.
1359 adjustOwnerCount(view, sleAcct, 1, journal);
1360
1361 return tesSUCCESS;
1362 }
1363
1364 auto const sleMptIssuance = view.read(keylet::mptIssuance(mptIssuanceID));
1365 if (!sleMptIssuance)
1366 return tecINTERNAL; // LCOV_EXCL_LINE
1367
1368 // If the account that submitted this tx is the issuer of the MPT
1369 // Note: `account_` is issuer's account
1370 // `holderID` is holder's account
1371 if (account != (*sleMptIssuance)[sfIssuer])
1372 return tecINTERNAL; // LCOV_EXCL_LINE
1373
1374 auto const sleMpt = view.peek(keylet::mptoken(mptIssuanceID, *holderID));
1375 if (!sleMpt)
1376 return tecINTERNAL; // LCOV_EXCL_LINE
1377
1378 std::uint32_t const flagsIn = sleMpt->getFieldU32(sfFlags);
1379 std::uint32_t flagsOut = flagsIn;
1380
1381 // Issuer wants to unauthorize the holder, unset lsfMPTAuthorized on
1382 // their MPToken
1383 if (flags & tfMPTUnauthorize)
1384 flagsOut &= ~lsfMPTAuthorized;
1385 // Issuer wants to authorize a holder, set lsfMPTAuthorized on their
1386 // MPToken
1387 else
1388 flagsOut |= lsfMPTAuthorized;
1389
1390 if (flagsIn != flagsOut)
1391 sleMpt->setFieldU32(sfFlags, flagsOut);
1392
1393 view.update(sleMpt);
1394 return tesSUCCESS;
1395}
1396
1397TER
1399 ApplyView& view,
1400 bool const bSrcHigh,
1401 AccountID const& uSrcAccountID,
1402 AccountID const& uDstAccountID,
1403 uint256 const& uIndex, // --> ripple state entry
1404 SLE::ref sleAccount, // --> the account being set.
1405 bool const bAuth, // --> authorize account.
1406 bool const bNoRipple, // --> others cannot ripple through
1407 bool const bFreeze, // --> funds cannot leave
1408 bool bDeepFreeze, // --> can neither receive nor send funds
1409 STAmount const& saBalance, // --> balance of account being set.
1410 // Issuer should be noAccount()
1411 STAmount const& saLimit, // --> limit for account being set.
1412 // Issuer should be the account being set.
1413 std::uint32_t uQualityIn,
1414 std::uint32_t uQualityOut,
1416{
1417 JLOG(j.trace()) << "trustCreate: " << to_string(uSrcAccountID) << ", "
1418 << to_string(uDstAccountID) << ", "
1419 << saBalance.getFullText();
1420
1421 auto const& uLowAccountID = !bSrcHigh ? uSrcAccountID : uDstAccountID;
1422 auto const& uHighAccountID = bSrcHigh ? uSrcAccountID : uDstAccountID;
1423
1424 auto const sleRippleState = std::make_shared<SLE>(ltRIPPLE_STATE, uIndex);
1425 view.insert(sleRippleState);
1426
1427 auto lowNode = view.dirInsert(
1428 keylet::ownerDir(uLowAccountID),
1429 sleRippleState->key(),
1430 describeOwnerDir(uLowAccountID));
1431
1432 if (!lowNode)
1433 return tecDIR_FULL; // LCOV_EXCL_LINE
1434
1435 auto highNode = view.dirInsert(
1436 keylet::ownerDir(uHighAccountID),
1437 sleRippleState->key(),
1438 describeOwnerDir(uHighAccountID));
1439
1440 if (!highNode)
1441 return tecDIR_FULL; // LCOV_EXCL_LINE
1442
1443 bool const bSetDst = saLimit.getIssuer() == uDstAccountID;
1444 bool const bSetHigh = bSrcHigh ^ bSetDst;
1445
1446 XRPL_ASSERT(sleAccount, "ripple::trustCreate : non-null SLE");
1447 if (!sleAccount)
1448 return tefINTERNAL; // LCOV_EXCL_LINE
1449
1450 XRPL_ASSERT(
1451 sleAccount->getAccountID(sfAccount) ==
1452 (bSetHigh ? uHighAccountID : uLowAccountID),
1453 "ripple::trustCreate : matching account ID");
1454 auto const slePeer =
1455 view.peek(keylet::account(bSetHigh ? uLowAccountID : uHighAccountID));
1456 if (!slePeer)
1457 return tecNO_TARGET;
1458
1459 // Remember deletion hints.
1460 sleRippleState->setFieldU64(sfLowNode, *lowNode);
1461 sleRippleState->setFieldU64(sfHighNode, *highNode);
1462
1463 sleRippleState->setFieldAmount(
1464 bSetHigh ? sfHighLimit : sfLowLimit, saLimit);
1465 sleRippleState->setFieldAmount(
1466 bSetHigh ? sfLowLimit : sfHighLimit,
1468 saBalance.getCurrency(), bSetDst ? uSrcAccountID : uDstAccountID}));
1469
1470 if (uQualityIn)
1471 sleRippleState->setFieldU32(
1472 bSetHigh ? sfHighQualityIn : sfLowQualityIn, uQualityIn);
1473
1474 if (uQualityOut)
1475 sleRippleState->setFieldU32(
1476 bSetHigh ? sfHighQualityOut : sfLowQualityOut, uQualityOut);
1477
1478 std::uint32_t uFlags = bSetHigh ? lsfHighReserve : lsfLowReserve;
1479
1480 if (bAuth)
1481 {
1482 uFlags |= (bSetHigh ? lsfHighAuth : lsfLowAuth);
1483 }
1484 if (bNoRipple)
1485 {
1486 uFlags |= (bSetHigh ? lsfHighNoRipple : lsfLowNoRipple);
1487 }
1488 if (bFreeze)
1489 {
1490 uFlags |= (bSetHigh ? lsfHighFreeze : lsfLowFreeze);
1491 }
1492 if (bDeepFreeze)
1493 {
1494 uFlags |= (bSetHigh ? lsfHighDeepFreeze : lsfLowDeepFreeze);
1495 }
1496
1497 if ((slePeer->getFlags() & lsfDefaultRipple) == 0)
1498 {
1499 // The other side's default is no rippling
1500 uFlags |= (bSetHigh ? lsfLowNoRipple : lsfHighNoRipple);
1501 }
1502
1503 sleRippleState->setFieldU32(sfFlags, uFlags);
1504 adjustOwnerCount(view, sleAccount, 1, j);
1505
1506 // ONLY: Create ripple balance.
1507 sleRippleState->setFieldAmount(
1508 sfBalance, bSetHigh ? -saBalance : saBalance);
1509
1510 view.creditHook(
1511 uSrcAccountID, uDstAccountID, saBalance, saBalance.zeroed());
1512
1513 return tesSUCCESS;
1514}
1515
1516[[nodiscard]] TER
1518 ApplyView& view,
1519 AccountID const& accountID,
1520 Issue const& issue,
1521 beast::Journal journal)
1522{
1523 if (issue.native())
1524 {
1525 auto const sle = view.read(keylet::account(accountID));
1526 if (!sle)
1527 return tecINTERNAL; // LCOV_EXCL_LINE
1528
1529 auto const balance = sle->getFieldAmount(sfBalance);
1530 if (balance.xrp() != 0)
1531 return tecHAS_OBLIGATIONS;
1532
1533 return tesSUCCESS;
1534 }
1535
1536 // `asset` is an IOU.
1537 auto const line = view.peek(keylet::line(accountID, issue));
1538 if (!line)
1539 return tecOBJECT_NOT_FOUND;
1540 if (line->at(sfBalance)->iou() != beast::zero)
1541 return tecHAS_OBLIGATIONS;
1542
1543 // Adjust the owner count(s)
1544 if (line->isFlag(lsfLowReserve))
1545 {
1546 // Clear reserve for low account.
1547 auto sleLowAccount =
1548 view.peek(keylet::account(line->at(sfLowLimit)->getIssuer()));
1549 if (!sleLowAccount)
1550 return tecINTERNAL; // LCOV_EXCL_LINE
1551
1552 adjustOwnerCount(view, sleLowAccount, -1, journal);
1553 // It's not really necessary to clear the reserve flag, since the line
1554 // is about to be deleted, but this will make the metadata reflect an
1555 // accurate state at the time of deletion.
1556 line->clearFlag(lsfLowReserve);
1557 }
1558
1559 if (line->isFlag(lsfHighReserve))
1560 {
1561 // Clear reserve for high account.
1562 auto sleHighAccount =
1563 view.peek(keylet::account(line->at(sfHighLimit)->getIssuer()));
1564 if (!sleHighAccount)
1565 return tecINTERNAL; // LCOV_EXCL_LINE
1566
1567 adjustOwnerCount(view, sleHighAccount, -1, journal);
1568 // It's not really necessary to clear the reserve flag, since the line
1569 // is about to be deleted, but this will make the metadata reflect an
1570 // accurate state at the time of deletion.
1571 line->clearFlag(lsfHighReserve);
1572 }
1573
1574 return trustDelete(
1575 view,
1576 line,
1577 line->at(sfLowLimit)->getIssuer(),
1578 line->at(sfHighLimit)->getIssuer(),
1579 journal);
1580}
1581
1582[[nodiscard]] TER
1584 ApplyView& view,
1585 AccountID const& accountID,
1586 MPTIssue const& mptIssue,
1587 beast::Journal journal)
1588{
1589 auto const& mptID = mptIssue.getMptID();
1590 auto const mptoken = view.peek(keylet::mptoken(mptID, accountID));
1591 if (!mptoken)
1592 return tecOBJECT_NOT_FOUND;
1593 if (mptoken->at(sfMPTAmount) != 0)
1594 return tecHAS_OBLIGATIONS;
1595
1596 return authorizeMPToken(
1597 view,
1598 {}, // priorBalance
1599 mptID,
1600 accountID,
1601 journal,
1602 tfMPTUnauthorize // flags
1603 );
1604}
1605
1606TER
1608 ApplyView& view,
1609 std::shared_ptr<SLE> const& sleRippleState,
1610 AccountID const& uLowAccountID,
1611 AccountID const& uHighAccountID,
1613{
1614 // Detect legacy dirs.
1615 std::uint64_t uLowNode = sleRippleState->getFieldU64(sfLowNode);
1616 std::uint64_t uHighNode = sleRippleState->getFieldU64(sfHighNode);
1617
1618 JLOG(j.trace()) << "trustDelete: Deleting ripple line: low";
1619
1620 if (!view.dirRemove(
1621 keylet::ownerDir(uLowAccountID),
1622 uLowNode,
1623 sleRippleState->key(),
1624 false))
1625 {
1626 return tefBAD_LEDGER; // LCOV_EXCL_LINE
1627 }
1628
1629 JLOG(j.trace()) << "trustDelete: Deleting ripple line: high";
1630
1631 if (!view.dirRemove(
1632 keylet::ownerDir(uHighAccountID),
1633 uHighNode,
1634 sleRippleState->key(),
1635 false))
1636 {
1637 return tefBAD_LEDGER; // LCOV_EXCL_LINE
1638 }
1639
1640 JLOG(j.trace()) << "trustDelete: Deleting ripple line: state";
1641 view.erase(sleRippleState);
1642
1643 return tesSUCCESS;
1644}
1645
1646TER
1648{
1649 if (!sle)
1650 return tesSUCCESS;
1651 auto offerIndex = sle->key();
1652 auto owner = sle->getAccountID(sfAccount);
1653
1654 // Detect legacy directories.
1655 uint256 uDirectory = sle->getFieldH256(sfBookDirectory);
1656
1657 if (!view.dirRemove(
1658 keylet::ownerDir(owner),
1659 sle->getFieldU64(sfOwnerNode),
1660 offerIndex,
1661 false))
1662 {
1663 return tefBAD_LEDGER; // LCOV_EXCL_LINE
1664 }
1665
1666 if (!view.dirRemove(
1667 keylet::page(uDirectory),
1668 sle->getFieldU64(sfBookNode),
1669 offerIndex,
1670 false))
1671 {
1672 return tefBAD_LEDGER; // LCOV_EXCL_LINE
1673 }
1674
1675 if (sle->isFieldPresent(sfAdditionalBooks))
1676 {
1677 XRPL_ASSERT(
1678 sle->isFlag(lsfHybrid) && sle->isFieldPresent(sfDomainID),
1679 "ripple::offerDelete : should be a hybrid domain offer");
1680
1681 auto const& additionalBookDirs = sle->getFieldArray(sfAdditionalBooks);
1682
1683 for (auto const& bookDir : additionalBookDirs)
1684 {
1685 auto const& dirIndex = bookDir.getFieldH256(sfBookDirectory);
1686 auto const& dirNode = bookDir.getFieldU64(sfBookNode);
1687
1688 if (!view.dirRemove(
1689 keylet::page(dirIndex), dirNode, offerIndex, false))
1690 {
1691 return tefBAD_LEDGER; // LCOV_EXCL_LINE
1692 }
1693 }
1694 }
1695
1696 adjustOwnerCount(view, view.peek(keylet::account(owner)), -1, j);
1697
1698 view.erase(sle);
1699
1700 return tesSUCCESS;
1701}
1702
1703// Direct send w/o fees:
1704// - Redeeming IOUs and/or sending sender's own IOUs.
1705// - Create trust line if needed.
1706// --> bCheckIssuer : normally require issuer to be involved.
1707static TER
1709 ApplyView& view,
1710 AccountID const& uSenderID,
1711 AccountID const& uReceiverID,
1712 STAmount const& saAmount,
1713 bool bCheckIssuer,
1715{
1716 AccountID const& issuer = saAmount.getIssuer();
1717 Currency const& currency = saAmount.getCurrency();
1718
1719 // Make sure issuer is involved.
1720 XRPL_ASSERT(
1721 !bCheckIssuer || uSenderID == issuer || uReceiverID == issuer,
1722 "ripple::rippleCreditIOU : matching issuer or don't care");
1723 (void)issuer;
1724
1725 // Disallow sending to self.
1726 XRPL_ASSERT(
1727 uSenderID != uReceiverID,
1728 "ripple::rippleCreditIOU : sender is not receiver");
1729
1730 bool const bSenderHigh = uSenderID > uReceiverID;
1731 auto const index = keylet::line(uSenderID, uReceiverID, currency);
1732
1733 XRPL_ASSERT(
1734 !isXRP(uSenderID) && uSenderID != noAccount(),
1735 "ripple::rippleCreditIOU : sender is not XRP");
1736 XRPL_ASSERT(
1737 !isXRP(uReceiverID) && uReceiverID != noAccount(),
1738 "ripple::rippleCreditIOU : receiver is not XRP");
1739
1740 // If the line exists, modify it accordingly.
1741 if (auto const sleRippleState = view.peek(index))
1742 {
1743 STAmount saBalance = sleRippleState->getFieldAmount(sfBalance);
1744
1745 if (bSenderHigh)
1746 saBalance.negate(); // Put balance in sender terms.
1747
1748 view.creditHook(uSenderID, uReceiverID, saAmount, saBalance);
1749
1750 STAmount const saBefore = saBalance;
1751
1752 saBalance -= saAmount;
1753
1754 JLOG(j.trace()) << "rippleCreditIOU: " << to_string(uSenderID) << " -> "
1755 << to_string(uReceiverID)
1756 << " : before=" << saBefore.getFullText()
1757 << " amount=" << saAmount.getFullText()
1758 << " after=" << saBalance.getFullText();
1759
1760 std::uint32_t const uFlags(sleRippleState->getFieldU32(sfFlags));
1761 bool bDelete = false;
1762
1763 // FIXME This NEEDS to be cleaned up and simplified. It's impossible
1764 // for anyone to understand.
1765 if (saBefore > beast::zero
1766 // Sender balance was positive.
1767 && saBalance <= beast::zero
1768 // Sender is zero or negative.
1769 && (uFlags & (!bSenderHigh ? lsfLowReserve : lsfHighReserve))
1770 // Sender reserve is set.
1771 &&
1772 static_cast<bool>(
1773 uFlags & (!bSenderHigh ? lsfLowNoRipple : lsfHighNoRipple)) !=
1774 static_cast<bool>(
1775 view.read(keylet::account(uSenderID))->getFlags() &
1777 !(uFlags & (!bSenderHigh ? lsfLowFreeze : lsfHighFreeze)) &&
1778 !sleRippleState->getFieldAmount(
1779 !bSenderHigh ? sfLowLimit : sfHighLimit)
1780 // Sender trust limit is 0.
1781 && !sleRippleState->getFieldU32(
1782 !bSenderHigh ? sfLowQualityIn : sfHighQualityIn)
1783 // Sender quality in is 0.
1784 && !sleRippleState->getFieldU32(
1785 !bSenderHigh ? sfLowQualityOut : sfHighQualityOut))
1786 // Sender quality out is 0.
1787 {
1788 // Clear the reserve of the sender, possibly delete the line!
1790 view, view.peek(keylet::account(uSenderID)), -1, j);
1791
1792 // Clear reserve flag.
1793 sleRippleState->setFieldU32(
1794 sfFlags,
1795 uFlags & (!bSenderHigh ? ~lsfLowReserve : ~lsfHighReserve));
1796
1797 // Balance is zero, receiver reserve is clear.
1798 bDelete = !saBalance // Balance is zero.
1799 && !(uFlags & (bSenderHigh ? lsfLowReserve : lsfHighReserve));
1800 // Receiver reserve is clear.
1801 }
1802
1803 if (bSenderHigh)
1804 saBalance.negate();
1805
1806 // Want to reflect balance to zero even if we are deleting line.
1807 sleRippleState->setFieldAmount(sfBalance, saBalance);
1808 // ONLY: Adjust ripple balance.
1809
1810 if (bDelete)
1811 {
1812 return trustDelete(
1813 view,
1814 sleRippleState,
1815 bSenderHigh ? uReceiverID : uSenderID,
1816 !bSenderHigh ? uReceiverID : uSenderID,
1817 j);
1818 }
1819
1820 view.update(sleRippleState);
1821 return tesSUCCESS;
1822 }
1823
1824 STAmount const saReceiverLimit(Issue{currency, uReceiverID});
1825 STAmount saBalance{saAmount};
1826
1827 saBalance.setIssuer(noAccount());
1828
1829 JLOG(j.debug()) << "rippleCreditIOU: "
1830 "create line: "
1831 << to_string(uSenderID) << " -> " << to_string(uReceiverID)
1832 << " : " << saAmount.getFullText();
1833
1834 auto const sleAccount = view.peek(keylet::account(uReceiverID));
1835 if (!sleAccount)
1836 return tefINTERNAL; // LCOV_EXCL_LINE
1837
1838 bool const noRipple = (sleAccount->getFlags() & lsfDefaultRipple) == 0;
1839
1840 return trustCreate(
1841 view,
1842 bSenderHigh,
1843 uSenderID,
1844 uReceiverID,
1845 index.key,
1846 sleAccount,
1847 false,
1848 noRipple,
1849 false,
1850 false,
1851 saBalance,
1852 saReceiverLimit,
1853 0,
1854 0,
1855 j);
1856}
1857
1858// Send regardless of limits.
1859// --> saAmount: Amount/currency/issuer to deliver to receiver.
1860// <-- saActual: Amount actually cost. Sender pays fees.
1861static TER
1863 ApplyView& view,
1864 AccountID const& uSenderID,
1865 AccountID const& uReceiverID,
1866 STAmount const& saAmount,
1867 STAmount& saActual,
1869 WaiveTransferFee waiveFee)
1870{
1871 auto const issuer = saAmount.getIssuer();
1872
1873 XRPL_ASSERT(
1874 !isXRP(uSenderID) && !isXRP(uReceiverID),
1875 "ripple::rippleSendIOU : neither sender nor receiver is XRP");
1876 XRPL_ASSERT(
1877 uSenderID != uReceiverID,
1878 "ripple::rippleSendIOU : sender is not receiver");
1879
1880 if (uSenderID == issuer || uReceiverID == issuer || issuer == noAccount())
1881 {
1882 // Direct send: redeeming IOUs and/or sending own IOUs.
1883 auto const ter =
1884 rippleCreditIOU(view, uSenderID, uReceiverID, saAmount, false, j);
1885 if (view.rules().enabled(featureDeletableAccounts) && ter != tesSUCCESS)
1886 return ter;
1887 saActual = saAmount;
1888 return tesSUCCESS;
1889 }
1890
1891 // Sending 3rd party IOUs: transit.
1892
1893 // Calculate the amount to transfer accounting
1894 // for any transfer fees if the fee is not waived:
1895 saActual = (waiveFee == WaiveTransferFee::Yes)
1896 ? saAmount
1897 : multiply(saAmount, transferRate(view, issuer));
1898
1899 JLOG(j.debug()) << "rippleSendIOU> " << to_string(uSenderID) << " - > "
1900 << to_string(uReceiverID)
1901 << " : deliver=" << saAmount.getFullText()
1902 << " cost=" << saActual.getFullText();
1903
1904 TER terResult =
1905 rippleCreditIOU(view, issuer, uReceiverID, saAmount, true, j);
1906
1907 if (tesSUCCESS == terResult)
1908 terResult = rippleCreditIOU(view, uSenderID, issuer, saActual, true, j);
1909
1910 return terResult;
1911}
1912
1913static TER
1915 ApplyView& view,
1916 AccountID const& uSenderID,
1917 AccountID const& uReceiverID,
1918 STAmount const& saAmount,
1920 WaiveTransferFee waiveFee)
1921{
1922 if (view.rules().enabled(fixAMMv1_1))
1923 {
1924 if (saAmount < beast::zero || saAmount.holds<MPTIssue>())
1925 {
1926 return tecINTERNAL; // LCOV_EXCL_LINE
1927 }
1928 }
1929 else
1930 {
1931 // LCOV_EXCL_START
1932 XRPL_ASSERT(
1933 saAmount >= beast::zero && !saAmount.holds<MPTIssue>(),
1934 "ripple::accountSendIOU : minimum amount and not MPT");
1935 // LCOV_EXCL_STOP
1936 }
1937
1938 /* If we aren't sending anything or if the sender is the same as the
1939 * receiver then we don't need to do anything.
1940 */
1941 if (!saAmount || (uSenderID == uReceiverID))
1942 return tesSUCCESS;
1943
1944 if (!saAmount.native())
1945 {
1946 STAmount saActual;
1947
1948 JLOG(j.trace()) << "accountSendIOU: " << to_string(uSenderID) << " -> "
1949 << to_string(uReceiverID) << " : "
1950 << saAmount.getFullText();
1951
1952 return rippleSendIOU(
1953 view, uSenderID, uReceiverID, saAmount, saActual, j, waiveFee);
1954 }
1955
1956 /* XRP send which does not check reserve and can do pure adjustment.
1957 * Note that sender or receiver may be null and this not a mistake; this
1958 * setup is used during pathfinding and it is carefully controlled to
1959 * ensure that transfers are balanced.
1960 */
1961 TER terResult(tesSUCCESS);
1962
1963 SLE::pointer sender = uSenderID != beast::zero
1964 ? view.peek(keylet::account(uSenderID))
1965 : SLE::pointer();
1966 SLE::pointer receiver = uReceiverID != beast::zero
1967 ? view.peek(keylet::account(uReceiverID))
1968 : SLE::pointer();
1969
1970 if (auto stream = j.trace())
1971 {
1972 std::string sender_bal("-");
1973 std::string receiver_bal("-");
1974
1975 if (sender)
1976 sender_bal = sender->getFieldAmount(sfBalance).getFullText();
1977
1978 if (receiver)
1979 receiver_bal = receiver->getFieldAmount(sfBalance).getFullText();
1980
1981 stream << "accountSendIOU> " << to_string(uSenderID) << " ("
1982 << sender_bal << ") -> " << to_string(uReceiverID) << " ("
1983 << receiver_bal << ") : " << saAmount.getFullText();
1984 }
1985
1986 if (sender)
1987 {
1988 if (sender->getFieldAmount(sfBalance) < saAmount)
1989 {
1990 // VFALCO Its laborious to have to mutate the
1991 // TER based on params everywhere
1992 // LCOV_EXCL_START
1993 terResult = view.open() ? TER{telFAILED_PROCESSING}
1995 // LCOV_EXCL_STOP
1996 }
1997 else
1998 {
1999 auto const sndBal = sender->getFieldAmount(sfBalance);
2000 view.creditHook(uSenderID, xrpAccount(), saAmount, sndBal);
2001
2002 // Decrement XRP balance.
2003 sender->setFieldAmount(sfBalance, sndBal - saAmount);
2004 view.update(sender);
2005 }
2006 }
2007
2008 if (tesSUCCESS == terResult && receiver)
2009 {
2010 // Increment XRP balance.
2011 auto const rcvBal = receiver->getFieldAmount(sfBalance);
2012 receiver->setFieldAmount(sfBalance, rcvBal + saAmount);
2013 view.creditHook(xrpAccount(), uReceiverID, saAmount, -rcvBal);
2014
2015 view.update(receiver);
2016 }
2017
2018 if (auto stream = j.trace())
2019 {
2020 std::string sender_bal("-");
2021 std::string receiver_bal("-");
2022
2023 if (sender)
2024 sender_bal = sender->getFieldAmount(sfBalance).getFullText();
2025
2026 if (receiver)
2027 receiver_bal = receiver->getFieldAmount(sfBalance).getFullText();
2028
2029 stream << "accountSendIOU< " << to_string(uSenderID) << " ("
2030 << sender_bal << ") -> " << to_string(uReceiverID) << " ("
2031 << receiver_bal << ") : " << saAmount.getFullText();
2032 }
2033
2034 return terResult;
2035}
2036
2037static TER
2039 ApplyView& view,
2040 AccountID const& uSenderID,
2041 AccountID const& uReceiverID,
2042 STAmount const& saAmount,
2044{
2045 // Do not check MPT authorization here - it must have been checked earlier
2046 auto const mptID = keylet::mptIssuance(saAmount.get<MPTIssue>().getMptID());
2047 auto const issuer = saAmount.getIssuer();
2048 auto sleIssuance = view.peek(mptID);
2049 if (!sleIssuance)
2050 return tecOBJECT_NOT_FOUND;
2051 if (uSenderID == issuer)
2052 {
2053 (*sleIssuance)[sfOutstandingAmount] += saAmount.mpt().value();
2054 view.update(sleIssuance);
2055 }
2056 else
2057 {
2058 auto const mptokenID = keylet::mptoken(mptID.key, uSenderID);
2059 if (auto sle = view.peek(mptokenID))
2060 {
2061 auto const amt = sle->getFieldU64(sfMPTAmount);
2062 auto const pay = saAmount.mpt().value();
2063 if (amt < pay)
2064 return tecINSUFFICIENT_FUNDS;
2065 (*sle)[sfMPTAmount] = amt - pay;
2066 view.update(sle);
2067 }
2068 else
2069 return tecNO_AUTH;
2070 }
2071
2072 if (uReceiverID == issuer)
2073 {
2074 auto const outstanding = sleIssuance->getFieldU64(sfOutstandingAmount);
2075 auto const redeem = saAmount.mpt().value();
2076 if (outstanding >= redeem)
2077 {
2078 sleIssuance->setFieldU64(sfOutstandingAmount, outstanding - redeem);
2079 view.update(sleIssuance);
2080 }
2081 else
2082 return tecINTERNAL; // LCOV_EXCL_LINE
2083 }
2084 else
2085 {
2086 auto const mptokenID = keylet::mptoken(mptID.key, uReceiverID);
2087 if (auto sle = view.peek(mptokenID))
2088 {
2089 (*sle)[sfMPTAmount] += saAmount.mpt().value();
2090 view.update(sle);
2091 }
2092 else
2093 return tecNO_AUTH;
2094 }
2095
2096 return tesSUCCESS;
2097}
2098
2099static TER
2101 ApplyView& view,
2102 AccountID const& uSenderID,
2103 AccountID const& uReceiverID,
2104 STAmount const& saAmount,
2105 STAmount& saActual,
2107 WaiveTransferFee waiveFee)
2108{
2109 XRPL_ASSERT(
2110 uSenderID != uReceiverID,
2111 "ripple::rippleSendMPT : sender is not receiver");
2112
2113 // Safe to get MPT since rippleSendMPT is only called by accountSendMPT
2114 auto const issuer = saAmount.getIssuer();
2115
2116 auto const sle =
2117 view.read(keylet::mptIssuance(saAmount.get<MPTIssue>().getMptID()));
2118 if (!sle)
2119 return tecOBJECT_NOT_FOUND;
2120
2121 if (uSenderID == issuer || uReceiverID == issuer)
2122 {
2123 // if sender is issuer, check that the new OutstandingAmount will not
2124 // exceed MaximumAmount
2125 if (uSenderID == issuer)
2126 {
2127 auto const sendAmount = saAmount.mpt().value();
2128 auto const maximumAmount =
2129 sle->at(~sfMaximumAmount).value_or(maxMPTokenAmount);
2130 if (sendAmount > maximumAmount ||
2131 sle->getFieldU64(sfOutstandingAmount) >
2132 maximumAmount - sendAmount)
2133 return tecPATH_DRY;
2134 }
2135
2136 // Direct send: redeeming MPTs and/or sending own MPTs.
2137 auto const ter =
2138 rippleCreditMPT(view, uSenderID, uReceiverID, saAmount, j);
2139 if (ter != tesSUCCESS)
2140 return ter;
2141 saActual = saAmount;
2142 return tesSUCCESS;
2143 }
2144
2145 // Sending 3rd party MPTs: transit.
2146 saActual = (waiveFee == WaiveTransferFee::Yes)
2147 ? saAmount
2148 : multiply(
2149 saAmount,
2150 transferRate(view, saAmount.get<MPTIssue>().getMptID()));
2151
2152 JLOG(j.debug()) << "rippleSendMPT> " << to_string(uSenderID) << " - > "
2153 << to_string(uReceiverID)
2154 << " : deliver=" << saAmount.getFullText()
2155 << " cost=" << saActual.getFullText();
2156
2157 if (auto const terResult =
2158 rippleCreditMPT(view, issuer, uReceiverID, saAmount, j);
2159 terResult != tesSUCCESS)
2160 return terResult;
2161
2162 return rippleCreditMPT(view, uSenderID, issuer, saActual, j);
2163}
2164
2165static TER
2167 ApplyView& view,
2168 AccountID const& uSenderID,
2169 AccountID const& uReceiverID,
2170 STAmount const& saAmount,
2172 WaiveTransferFee waiveFee)
2173{
2174 XRPL_ASSERT(
2175 saAmount >= beast::zero && saAmount.holds<MPTIssue>(),
2176 "ripple::accountSendMPT : minimum amount and MPT");
2177
2178 /* If we aren't sending anything or if the sender is the same as the
2179 * receiver then we don't need to do anything.
2180 */
2181 if (!saAmount || (uSenderID == uReceiverID))
2182 return tesSUCCESS;
2183
2184 STAmount saActual{saAmount.asset()};
2185
2186 return rippleSendMPT(
2187 view, uSenderID, uReceiverID, saAmount, saActual, j, waiveFee);
2188}
2189
2190TER
2192 ApplyView& view,
2193 AccountID const& uSenderID,
2194 AccountID const& uReceiverID,
2195 STAmount const& saAmount,
2197 WaiveTransferFee waiveFee)
2198{
2199 return std::visit(
2200 [&]<ValidIssueType TIss>(TIss const& issue) {
2201 if constexpr (std::is_same_v<TIss, Issue>)
2202 return accountSendIOU(
2203 view, uSenderID, uReceiverID, saAmount, j, waiveFee);
2204 else
2205 return accountSendMPT(
2206 view, uSenderID, uReceiverID, saAmount, j, waiveFee);
2207 },
2208 saAmount.asset().value());
2209}
2210
2211static bool
2213 ApplyView& view,
2214 SLE::pointer state,
2215 bool bSenderHigh,
2216 AccountID const& sender,
2217 STAmount const& before,
2218 STAmount const& after,
2220{
2221 if (!state)
2222 return false;
2223 std::uint32_t const flags(state->getFieldU32(sfFlags));
2224
2225 auto sle = view.peek(keylet::account(sender));
2226 if (!sle)
2227 return false;
2228
2229 // YYY Could skip this if rippling in reverse.
2230 if (before > beast::zero
2231 // Sender balance was positive.
2232 && after <= beast::zero
2233 // Sender is zero or negative.
2234 && (flags & (!bSenderHigh ? lsfLowReserve : lsfHighReserve))
2235 // Sender reserve is set.
2236 && static_cast<bool>(
2237 flags & (!bSenderHigh ? lsfLowNoRipple : lsfHighNoRipple)) !=
2238 static_cast<bool>(sle->getFlags() & lsfDefaultRipple) &&
2239 !(flags & (!bSenderHigh ? lsfLowFreeze : lsfHighFreeze)) &&
2240 !state->getFieldAmount(!bSenderHigh ? sfLowLimit : sfHighLimit)
2241 // Sender trust limit is 0.
2242 && !state->getFieldU32(!bSenderHigh ? sfLowQualityIn : sfHighQualityIn)
2243 // Sender quality in is 0.
2244 &&
2245 !state->getFieldU32(!bSenderHigh ? sfLowQualityOut : sfHighQualityOut))
2246 // Sender quality out is 0.
2247 {
2248 // VFALCO Where is the line being deleted?
2249 // Clear the reserve of the sender, possibly delete the line!
2250 adjustOwnerCount(view, sle, -1, j);
2251
2252 // Clear reserve flag.
2253 state->setFieldU32(
2254 sfFlags, flags & (!bSenderHigh ? ~lsfLowReserve : ~lsfHighReserve));
2255
2256 // Balance is zero, receiver reserve is clear.
2257 if (!after // Balance is zero.
2258 && !(flags & (bSenderHigh ? lsfLowReserve : lsfHighReserve)))
2259 return true;
2260 }
2261 return false;
2262}
2263
2264TER
2266 ApplyView& view,
2267 AccountID const& account,
2268 STAmount const& amount,
2269 Issue const& issue,
2271{
2272 XRPL_ASSERT(
2273 !isXRP(account) && !isXRP(issue.account),
2274 "ripple::issueIOU : neither account nor issuer is XRP");
2275
2276 // Consistency check
2277 XRPL_ASSERT(issue == amount.issue(), "ripple::issueIOU : matching issue");
2278
2279 // Can't send to self!
2280 XRPL_ASSERT(
2281 issue.account != account, "ripple::issueIOU : not issuer account");
2282
2283 JLOG(j.trace()) << "issueIOU: " << to_string(account) << ": "
2284 << amount.getFullText();
2285
2286 bool bSenderHigh = issue.account > account;
2287
2288 auto const index = keylet::line(issue.account, account, issue.currency);
2289
2290 if (auto state = view.peek(index))
2291 {
2292 STAmount final_balance = state->getFieldAmount(sfBalance);
2293
2294 if (bSenderHigh)
2295 final_balance.negate(); // Put balance in sender terms.
2296
2297 STAmount const start_balance = final_balance;
2298
2299 final_balance -= amount;
2300
2301 auto const must_delete = updateTrustLine(
2302 view,
2303 state,
2304 bSenderHigh,
2305 issue.account,
2306 start_balance,
2307 final_balance,
2308 j);
2309
2310 view.creditHook(issue.account, account, amount, start_balance);
2311
2312 if (bSenderHigh)
2313 final_balance.negate();
2314
2315 // Adjust the balance on the trust line if necessary. We do this even if
2316 // we are going to delete the line to reflect the correct balance at the
2317 // time of deletion.
2318 state->setFieldAmount(sfBalance, final_balance);
2319 if (must_delete)
2320 return trustDelete(
2321 view,
2322 state,
2323 bSenderHigh ? account : issue.account,
2324 bSenderHigh ? issue.account : account,
2325 j);
2326
2327 view.update(state);
2328
2329 return tesSUCCESS;
2330 }
2331
2332 // NIKB TODO: The limit uses the receiver's account as the issuer and
2333 // this is unnecessarily inefficient as copying which could be avoided
2334 // is now required. Consider available options.
2335 STAmount const limit(Issue{issue.currency, account});
2336 STAmount final_balance = amount;
2337
2338 final_balance.setIssuer(noAccount());
2339
2340 auto const receiverAccount = view.peek(keylet::account(account));
2341 if (!receiverAccount)
2342 return tefINTERNAL; // LCOV_EXCL_LINE
2343
2344 bool noRipple = (receiverAccount->getFlags() & lsfDefaultRipple) == 0;
2345
2346 return trustCreate(
2347 view,
2348 bSenderHigh,
2349 issue.account,
2350 account,
2351 index.key,
2352 receiverAccount,
2353 false,
2354 noRipple,
2355 false,
2356 false,
2357 final_balance,
2358 limit,
2359 0,
2360 0,
2361 j);
2362}
2363
2364TER
2366 ApplyView& view,
2367 AccountID const& account,
2368 STAmount const& amount,
2369 Issue const& issue,
2371{
2372 XRPL_ASSERT(
2373 !isXRP(account) && !isXRP(issue.account),
2374 "ripple::redeemIOU : neither account nor issuer is XRP");
2375
2376 // Consistency check
2377 XRPL_ASSERT(issue == amount.issue(), "ripple::redeemIOU : matching issue");
2378
2379 // Can't send to self!
2380 XRPL_ASSERT(
2381 issue.account != account, "ripple::redeemIOU : not issuer account");
2382
2383 JLOG(j.trace()) << "redeemIOU: " << to_string(account) << ": "
2384 << amount.getFullText();
2385
2386 bool bSenderHigh = account > issue.account;
2387
2388 if (auto state =
2389 view.peek(keylet::line(account, issue.account, issue.currency)))
2390 {
2391 STAmount final_balance = state->getFieldAmount(sfBalance);
2392
2393 if (bSenderHigh)
2394 final_balance.negate(); // Put balance in sender terms.
2395
2396 STAmount const start_balance = final_balance;
2397
2398 final_balance -= amount;
2399
2400 auto const must_delete = updateTrustLine(
2401 view, state, bSenderHigh, account, start_balance, final_balance, j);
2402
2403 view.creditHook(account, issue.account, amount, start_balance);
2404
2405 if (bSenderHigh)
2406 final_balance.negate();
2407
2408 // Adjust the balance on the trust line if necessary. We do this even if
2409 // we are going to delete the line to reflect the correct balance at the
2410 // time of deletion.
2411 state->setFieldAmount(sfBalance, final_balance);
2412
2413 if (must_delete)
2414 {
2415 return trustDelete(
2416 view,
2417 state,
2418 bSenderHigh ? issue.account : account,
2419 bSenderHigh ? account : issue.account,
2420 j);
2421 }
2422
2423 view.update(state);
2424 return tesSUCCESS;
2425 }
2426
2427 // In order to hold an IOU, a trust line *MUST* exist to track the
2428 // balance. If it doesn't, then something is very wrong. Don't try
2429 // to continue.
2430 // LCOV_EXCL_START
2431 JLOG(j.fatal()) << "redeemIOU: " << to_string(account)
2432 << " attempts to redeem " << amount.getFullText()
2433 << " but no trust line exists!";
2434
2435 return tefINTERNAL;
2436 // LCOV_EXCL_STOP
2437}
2438
2439TER
2441 ApplyView& view,
2442 AccountID const& from,
2443 AccountID const& to,
2444 STAmount const& amount,
2446{
2447 XRPL_ASSERT(
2448 from != beast::zero, "ripple::transferXRP : nonzero from account");
2449 XRPL_ASSERT(to != beast::zero, "ripple::transferXRP : nonzero to account");
2450 XRPL_ASSERT(from != to, "ripple::transferXRP : sender is not receiver");
2451 XRPL_ASSERT(amount.native(), "ripple::transferXRP : amount is XRP");
2452
2453 SLE::pointer const sender = view.peek(keylet::account(from));
2454 SLE::pointer const receiver = view.peek(keylet::account(to));
2455 if (!sender || !receiver)
2456 return tefINTERNAL; // LCOV_EXCL_LINE
2457
2458 JLOG(j.trace()) << "transferXRP: " << to_string(from) << " -> "
2459 << to_string(to) << ") : " << amount.getFullText();
2460
2461 if (sender->getFieldAmount(sfBalance) < amount)
2462 {
2463 // VFALCO Its unfortunate we have to keep
2464 // mutating these TER everywhere
2465 // FIXME: this logic should be moved to callers maybe?
2466 // LCOV_EXCL_START
2467 return view.open() ? TER{telFAILED_PROCESSING}
2469 // LCOV_EXCL_STOP
2470 }
2471
2472 // Decrement XRP balance.
2473 sender->setFieldAmount(
2474 sfBalance, sender->getFieldAmount(sfBalance) - amount);
2475 view.update(sender);
2476
2477 receiver->setFieldAmount(
2478 sfBalance, receiver->getFieldAmount(sfBalance) + amount);
2479 view.update(receiver);
2480
2481 return tesSUCCESS;
2482}
2483
2484TER
2486 ReadView const& view,
2487 Issue const& issue,
2488 AccountID const& account,
2489 AuthType authType)
2490{
2491 if (isXRP(issue) || issue.account == account)
2492 return tesSUCCESS;
2493
2494 auto const trustLine =
2495 view.read(keylet::line(account, issue.account, issue.currency));
2496 // If account has no line, and this is a strong check, fail
2497 if (!trustLine && authType == AuthType::StrongAuth)
2498 return tecNO_LINE;
2499
2500 // If this is a weak or legacy check, or if the account has a line, fail if
2501 // auth is required and not set on the line
2502 if (auto const issuerAccount = view.read(keylet::account(issue.account));
2503 issuerAccount && (*issuerAccount)[sfFlags] & lsfRequireAuth)
2504 {
2505 if (trustLine)
2506 return ((*trustLine)[sfFlags] &
2507 ((account > issue.account) ? lsfLowAuth : lsfHighAuth))
2508 ? tesSUCCESS
2509 : TER{tecNO_AUTH};
2510 return TER{tecNO_LINE};
2511 }
2512
2513 return tesSUCCESS;
2514}
2515
2516TER
2518 ReadView const& view,
2519 MPTIssue const& mptIssue,
2520 AccountID const& account,
2521 AuthType authType,
2522 int depth)
2523{
2524 auto const mptID = keylet::mptIssuance(mptIssue.getMptID());
2525 auto const sleIssuance = view.read(mptID);
2526 if (!sleIssuance)
2527 return tecOBJECT_NOT_FOUND;
2528
2529 auto const mptIssuer = sleIssuance->getAccountID(sfIssuer);
2530
2531 // issuer is always "authorized"
2532 if (mptIssuer == account) // Issuer won't have MPToken
2533 return tesSUCCESS;
2534
2535 if (view.rules().enabled(featureSingleAssetVault))
2536 {
2537 if (depth >= maxAssetCheckDepth)
2538 return tecINTERNAL; // LCOV_EXCL_LINE
2539
2540 // requireAuth is recursive if the issuer is a vault pseudo-account
2541 auto const sleIssuer = view.read(keylet::account(mptIssuer));
2542 if (!sleIssuer)
2543 return tefINTERNAL; // LCOV_EXCL_LINE
2544
2545 if (sleIssuer->isFieldPresent(sfVaultID))
2546 {
2547 auto const sleVault =
2548 view.read(keylet::vault(sleIssuer->getFieldH256(sfVaultID)));
2549 if (!sleVault)
2550 return tefINTERNAL; // LCOV_EXCL_LINE
2551
2552 auto const asset = sleVault->at(sfAsset);
2553 if (auto const err = std::visit(
2554 [&]<ValidIssueType TIss>(TIss const& issue) {
2555 if constexpr (std::is_same_v<TIss, Issue>)
2556 return requireAuth(view, issue, account, authType);
2557 else
2558 return requireAuth(
2559 view, issue, account, authType, depth + 1);
2560 },
2561 asset.value());
2562 !isTesSuccess(err))
2563 return err;
2564 }
2565 }
2566
2567 auto const mptokenID = keylet::mptoken(mptID.key, account);
2568 auto const sleToken = view.read(mptokenID);
2569
2570 // if account has no MPToken, fail
2571 if (!sleToken &&
2572 (authType == AuthType::StrongAuth || authType == AuthType::Legacy))
2573 return tecNO_AUTH;
2574
2575 // Note, this check is not amendment-gated because DomainID will be always
2576 // empty **unless** writing to it has been enabled by an amendment
2577 auto const maybeDomainID = sleIssuance->at(~sfDomainID);
2578 if (maybeDomainID)
2579 {
2580 XRPL_ASSERT(
2581 sleIssuance->getFieldU32(sfFlags) & lsfMPTRequireAuth,
2582 "ripple::requireAuth : issuance requires authorization");
2583 // ter = tefINTERNAL | tecOBJECT_NOT_FOUND | tecNO_AUTH | tecEXPIRED
2584 if (auto const ter =
2585 credentials::validDomain(view, *maybeDomainID, account);
2586 isTesSuccess(ter))
2587 return ter; // Note: sleToken might be null
2588 else if (!sleToken)
2589 return ter;
2590 // We ignore error from validDomain if we found sleToken, as it could
2591 // belong to someone who is explicitly authorized e.g. a vault owner.
2592 }
2593
2594 // mptoken must be authorized if issuance enabled requireAuth
2595 if (sleIssuance->isFlag(lsfMPTRequireAuth) &&
2596 (!sleToken || !sleToken->isFlag(lsfMPTAuthorized)))
2597 return tecNO_AUTH;
2598
2599 return tesSUCCESS; // Note: sleToken might be null
2600}
2601
2602[[nodiscard]] TER
2604 ApplyView& view,
2605 MPTID const& mptIssuanceID,
2606 AccountID const& account,
2607 XRPAmount const& priorBalance, // for MPToken authorization
2609{
2610 auto const sleIssuance = view.read(keylet::mptIssuance(mptIssuanceID));
2611 if (!sleIssuance)
2612 return tefINTERNAL; // LCOV_EXCL_LINE
2613
2614 XRPL_ASSERT(
2615 sleIssuance->isFlag(lsfMPTRequireAuth),
2616 "ripple::enforceMPTokenAuthorization : authorization required");
2617
2618 if (account == sleIssuance->at(sfIssuer))
2619 return tefINTERNAL; // LCOV_EXCL_LINE
2620
2621 auto const keylet = keylet::mptoken(mptIssuanceID, account);
2622 auto const sleToken = view.read(keylet); // NOTE: might be null
2623 auto const maybeDomainID = sleIssuance->at(~sfDomainID);
2624 bool expired = false;
2625 bool const authorizedByDomain = [&]() -> bool {
2626 // NOTE: defensive here, shuld be checked in preclaim
2627 if (!maybeDomainID.has_value())
2628 return false; // LCOV_EXCL_LINE
2629
2630 auto const ter = verifyValidDomain(view, account, *maybeDomainID, j);
2631 if (isTesSuccess(ter))
2632 return true;
2633 if (ter == tecEXPIRED)
2634 expired = true;
2635 return false;
2636 }();
2637
2638 if (!authorizedByDomain && sleToken == nullptr)
2639 {
2640 // Could not find MPToken and won't create one, could be either of:
2641 //
2642 // 1. Field sfDomainID not set in MPTokenIssuance or
2643 // 2. Account has no matching and accepted credentials or
2644 // 3. Account has all expired credentials (deleted in verifyValidDomain)
2645 //
2646 // Either way, return tecNO_AUTH and there is nothing else to do
2647 return expired ? tecEXPIRED : tecNO_AUTH;
2648 }
2649 else if (!authorizedByDomain && maybeDomainID.has_value())
2650 {
2651 // Found an MPToken but the account is not authorized and we expect
2652 // it to have been authorized by the domain. This could be because the
2653 // credentials used to create the MPToken have expired or been deleted.
2654 return expired ? tecEXPIRED : tecNO_AUTH;
2655 }
2656 else if (!authorizedByDomain)
2657 {
2658 // We found an MPToken, but sfDomainID is not set, so this is a classic
2659 // MPToken which requires authorization by the token issuer.
2660 XRPL_ASSERT(
2661 sleToken != nullptr && !maybeDomainID.has_value(),
2662 "ripple::enforceMPTokenAuthorization : found MPToken");
2663 if (sleToken->isFlag(lsfMPTAuthorized))
2664 return tesSUCCESS;
2665
2666 return tecNO_AUTH;
2667 }
2668 else if (authorizedByDomain && sleToken != nullptr)
2669 {
2670 // Found an MPToken, authorized by the domain. Ignore authorization flag
2671 // lsfMPTAuthorized because it is meaningless. Return tesSUCCESS
2672 XRPL_ASSERT(
2673 maybeDomainID.has_value(),
2674 "ripple::enforceMPTokenAuthorization : found MPToken for domain");
2675 return tesSUCCESS;
2676 }
2677 else if (authorizedByDomain)
2678 {
2679 // Could not find MPToken but there should be one because we are
2680 // authorized by domain. Proceed to create it, then return tesSUCCESS
2681 XRPL_ASSERT(
2682 maybeDomainID.has_value() && sleToken == nullptr,
2683 "ripple::enforceMPTokenAuthorization : new MPToken for domain");
2684 if (auto const err = authorizeMPToken(
2685 view,
2686 priorBalance, // priorBalance
2687 mptIssuanceID, // mptIssuanceID
2688 account, // account
2689 j);
2690 !isTesSuccess(err))
2691 return err;
2692
2693 return tesSUCCESS;
2694 }
2695
2696 // LCOV_EXCL_START
2697 UNREACHABLE(
2698 "ripple::enforceMPTokenAuthorization : condition list is incomplete");
2699 return tefINTERNAL;
2700 // LCOV_EXCL_STOP
2701}
2702
2703TER
2705 ReadView const& view,
2706 MPTIssue const& mptIssue,
2707 AccountID const& from,
2708 AccountID const& to)
2709{
2710 auto const mptID = keylet::mptIssuance(mptIssue.getMptID());
2711 auto const sleIssuance = view.read(mptID);
2712 if (!sleIssuance)
2713 return tecOBJECT_NOT_FOUND;
2714
2715 if (!(sleIssuance->getFieldU32(sfFlags) & lsfMPTCanTransfer))
2716 {
2717 if (from != (*sleIssuance)[sfIssuer] && to != (*sleIssuance)[sfIssuer])
2718 return TER{tecNO_AUTH};
2719 }
2720 return tesSUCCESS;
2721}
2722
2723TER
2725 ApplyView& view,
2726 Keylet const& ownerDirKeylet,
2727 EntryDeleter const& deleter,
2729 std::optional<uint16_t> maxNodesToDelete)
2730{
2731 // Delete all the entries in the account directory.
2732 std::shared_ptr<SLE> sleDirNode{};
2733 unsigned int uDirEntry{0};
2734 uint256 dirEntry{beast::zero};
2735 std::uint32_t deleted = 0;
2736
2737 if (view.exists(ownerDirKeylet) &&
2738 dirFirst(view, ownerDirKeylet.key, sleDirNode, uDirEntry, dirEntry))
2739 {
2740 do
2741 {
2742 if (maxNodesToDelete && ++deleted > *maxNodesToDelete)
2743 return tecINCOMPLETE;
2744
2745 // Choose the right way to delete each directory node.
2746 auto sleItem = view.peek(keylet::child(dirEntry));
2747 if (!sleItem)
2748 {
2749 // Directory node has an invalid index. Bail out.
2750 // LCOV_EXCL_START
2751 JLOG(j.fatal())
2752 << "DeleteAccount: Directory node in ledger " << view.seq()
2753 << " has index to object that is missing: "
2754 << to_string(dirEntry);
2755 return tefBAD_LEDGER;
2756 // LCOV_EXCL_STOP
2757 }
2758
2759 LedgerEntryType const nodeType{safe_cast<LedgerEntryType>(
2760 sleItem->getFieldU16(sfLedgerEntryType))};
2761
2762 // Deleter handles the details of specific account-owned object
2763 // deletion
2764 auto const [ter, skipEntry] = deleter(nodeType, dirEntry, sleItem);
2765 if (ter != tesSUCCESS)
2766 return ter;
2767
2768 // dirFirst() and dirNext() are like iterators with exposed
2769 // internal state. We'll take advantage of that exposed state
2770 // to solve a common C++ problem: iterator invalidation while
2771 // deleting elements from a container.
2772 //
2773 // We have just deleted one directory entry, which means our
2774 // "iterator state" is invalid.
2775 //
2776 // 1. During the process of getting an entry from the
2777 // directory uDirEntry was incremented from 'it' to 'it'+1.
2778 //
2779 // 2. We then deleted the entry at index 'it', which means the
2780 // entry that was at 'it'+1 has now moved to 'it'.
2781 //
2782 // 3. So we verify that uDirEntry is indeed 'it'+1. Then we jam it
2783 // back to 'it' to "un-invalidate" the iterator.
2784 XRPL_ASSERT(
2785 uDirEntry >= 1,
2786 "ripple::cleanupOnAccountDelete : minimum dir entries");
2787 if (uDirEntry == 0)
2788 {
2789 // LCOV_EXCL_START
2790 JLOG(j.error())
2791 << "DeleteAccount iterator re-validation failed.";
2792 return tefBAD_LEDGER;
2793 // LCOV_EXCL_STOP
2794 }
2795 if (skipEntry == SkipEntry::No)
2796 uDirEntry--;
2797
2798 } while (
2799 dirNext(view, ownerDirKeylet.key, sleDirNode, uDirEntry, dirEntry));
2800 }
2801
2802 return tesSUCCESS;
2803}
2804
2805TER
2807 ApplyView& view,
2808 std::shared_ptr<SLE> sleState,
2809 std::optional<AccountID> const& ammAccountID,
2811{
2812 if (!sleState || sleState->getType() != ltRIPPLE_STATE)
2813 return tecINTERNAL; // LCOV_EXCL_LINE
2814
2815 auto const& [low, high] = std::minmax(
2816 sleState->getFieldAmount(sfLowLimit).getIssuer(),
2817 sleState->getFieldAmount(sfHighLimit).getIssuer());
2818 auto sleLow = view.peek(keylet::account(low));
2819 auto sleHigh = view.peek(keylet::account(high));
2820 if (!sleLow || !sleHigh)
2821 return tecINTERNAL; // LCOV_EXCL_LINE
2822
2823 bool const ammLow = sleLow->isFieldPresent(sfAMMID);
2824 bool const ammHigh = sleHigh->isFieldPresent(sfAMMID);
2825
2826 // can't both be AMM
2827 if (ammLow && ammHigh)
2828 return tecINTERNAL; // LCOV_EXCL_LINE
2829
2830 // at least one must be
2831 if (!ammLow && !ammHigh)
2832 return terNO_AMM;
2833
2834 // one must be the target amm
2835 if (ammAccountID && (low != *ammAccountID && high != *ammAccountID))
2836 return terNO_AMM;
2837
2838 if (auto const ter = trustDelete(view, sleState, low, high, j);
2839 ter != tesSUCCESS)
2840 {
2841 JLOG(j.error())
2842 << "deleteAMMTrustLine: failed to delete the trustline.";
2843 return ter;
2844 }
2845
2846 auto const uFlags = !ammLow ? lsfLowReserve : lsfHighReserve;
2847 if (!(sleState->getFlags() & uFlags))
2848 return tecINTERNAL; // LCOV_EXCL_LINE
2849
2850 adjustOwnerCount(view, !ammLow ? sleLow : sleHigh, -1, j);
2851
2852 return tesSUCCESS;
2853}
2854
2855TER
2857 ApplyView& view,
2858 AccountID const& uSenderID,
2859 AccountID const& uReceiverID,
2860 STAmount const& saAmount,
2861 bool bCheckIssuer,
2863{
2864 return std::visit(
2865 [&]<ValidIssueType TIss>(TIss const& issue) {
2866 if constexpr (std::is_same_v<TIss, Issue>)
2867 {
2868 return rippleCreditIOU(
2869 view, uSenderID, uReceiverID, saAmount, bCheckIssuer, j);
2870 }
2871 else
2872 {
2873 XRPL_ASSERT(
2874 !bCheckIssuer,
2875 "ripple::rippleCredit : not checking issuer");
2876 return rippleCreditMPT(
2877 view, uSenderID, uReceiverID, saAmount, j);
2878 }
2879 },
2880 saAmount.asset().value());
2881}
2882
2883[[nodiscard]] std::optional<STAmount>
2885 std::shared_ptr<SLE const> const& vault,
2886 std::shared_ptr<SLE const> const& issuance,
2887 STAmount const& assets)
2888{
2889 XRPL_ASSERT(
2890 !assets.negative(),
2891 "ripple::assetsToSharesDeposit : non-negative assets");
2892 XRPL_ASSERT(
2893 assets.asset() == vault->at(sfAsset),
2894 "ripple::assetsToSharesDeposit : assets and vault match");
2895 if (assets.negative() || assets.asset() != vault->at(sfAsset))
2896 return std::nullopt; // LCOV_EXCL_LINE
2897
2898 Number const assetTotal = vault->at(sfAssetsTotal);
2899 STAmount shares{vault->at(sfShareMPTID)};
2900 if (assetTotal == 0)
2901 return STAmount{
2902 shares.asset(),
2903 Number(assets.mantissa(), assets.exponent() + vault->at(sfScale))
2904 .truncate()};
2905
2906 Number const shareTotal = issuance->at(sfOutstandingAmount);
2907 shares = (shareTotal * (assets / assetTotal)).truncate();
2908 return shares;
2909}
2910
2911[[nodiscard]] std::optional<STAmount>
2913 std::shared_ptr<SLE const> const& vault,
2914 std::shared_ptr<SLE const> const& issuance,
2915 STAmount const& shares)
2916{
2917 XRPL_ASSERT(
2918 !shares.negative(),
2919 "ripple::sharesToAssetsDeposit : non-negative shares");
2920 XRPL_ASSERT(
2921 shares.asset() == vault->at(sfShareMPTID),
2922 "ripple::sharesToAssetsDeposit : shares and vault match");
2923 if (shares.negative() || shares.asset() != vault->at(sfShareMPTID))
2924 return std::nullopt; // LCOV_EXCL_LINE
2925
2926 Number const assetTotal = vault->at(sfAssetsTotal);
2927 STAmount assets{vault->at(sfAsset)};
2928 if (assetTotal == 0)
2929 return STAmount{
2930 assets.asset(),
2931 shares.mantissa(),
2932 shares.exponent() - vault->at(sfScale),
2933 false};
2934
2935 Number const shareTotal = issuance->at(sfOutstandingAmount);
2936 assets = assetTotal * (shares / shareTotal);
2937 return assets;
2938}
2939
2940[[nodiscard]] std::optional<STAmount>
2942 std::shared_ptr<SLE const> const& vault,
2943 std::shared_ptr<SLE const> const& issuance,
2944 STAmount const& assets,
2945 TruncateShares truncate)
2946{
2947 XRPL_ASSERT(
2948 !assets.negative(),
2949 "ripple::assetsToSharesDeposit : non-negative assets");
2950 XRPL_ASSERT(
2951 assets.asset() == vault->at(sfAsset),
2952 "ripple::assetsToSharesWithdraw : assets and vault match");
2953 if (assets.negative() || assets.asset() != vault->at(sfAsset))
2954 return std::nullopt; // LCOV_EXCL_LINE
2955
2956 Number assetTotal = vault->at(sfAssetsTotal);
2957 assetTotal -= vault->at(sfLossUnrealized);
2958 STAmount shares{vault->at(sfShareMPTID)};
2959 if (assetTotal == 0)
2960 return shares;
2961 Number const shareTotal = issuance->at(sfOutstandingAmount);
2962 Number result = shareTotal * (assets / assetTotal);
2963 if (truncate == TruncateShares::yes)
2964 result = result.truncate();
2965 shares = result;
2966 return shares;
2967}
2968
2969[[nodiscard]] std::optional<STAmount>
2971 std::shared_ptr<SLE const> const& vault,
2972 std::shared_ptr<SLE const> const& issuance,
2973 STAmount const& shares)
2974{
2975 XRPL_ASSERT(
2976 !shares.negative(),
2977 "ripple::sharesToAssetsDeposit : non-negative shares");
2978 XRPL_ASSERT(
2979 shares.asset() == vault->at(sfShareMPTID),
2980 "ripple::sharesToAssetsWithdraw : shares and vault match");
2981 if (shares.negative() || shares.asset() != vault->at(sfShareMPTID))
2982 return std::nullopt; // LCOV_EXCL_LINE
2983
2984 Number assetTotal = vault->at(sfAssetsTotal);
2985 assetTotal -= vault->at(sfLossUnrealized);
2986 STAmount assets{vault->at(sfAsset)};
2987 if (assetTotal == 0)
2988 return assets;
2989 Number const shareTotal = issuance->at(sfOutstandingAmount);
2990 assets = assetTotal * (shares / shareTotal);
2991 return assets;
2992}
2993
2994TER
2996 ApplyView& view,
2997 AccountID const& sender,
2998 STAmount const& amount,
3000{
3001 auto const mptIssue = amount.get<MPTIssue>();
3002 auto const mptID = keylet::mptIssuance(mptIssue.getMptID());
3003 auto sleIssuance = view.peek(mptID);
3004 if (!sleIssuance)
3005 { // LCOV_EXCL_START
3006 JLOG(j.error()) << "rippleLockEscrowMPT: MPT issuance not found for "
3007 << mptIssue.getMptID();
3008 return tecOBJECT_NOT_FOUND;
3009 } // LCOV_EXCL_STOP
3010
3011 if (amount.getIssuer() == sender)
3012 { // LCOV_EXCL_START
3013 JLOG(j.error())
3014 << "rippleLockEscrowMPT: sender is the issuer, cannot lock MPTs.";
3015 return tecINTERNAL;
3016 } // LCOV_EXCL_STOP
3017
3018 // 1. Decrease the MPT Holder MPTAmount
3019 // 2. Increase the MPT Holder EscrowedAmount
3020 {
3021 auto const mptokenID = keylet::mptoken(mptID.key, sender);
3022 auto sle = view.peek(mptokenID);
3023 if (!sle)
3024 { // LCOV_EXCL_START
3025 JLOG(j.error())
3026 << "rippleLockEscrowMPT: MPToken not found for " << sender;
3027 return tecOBJECT_NOT_FOUND;
3028 } // LCOV_EXCL_STOP
3029
3030 auto const amt = sle->getFieldU64(sfMPTAmount);
3031 auto const pay = amount.mpt().value();
3032
3033 // Underflow check for subtraction
3034 if (!canSubtract(STAmount(mptIssue, amt), STAmount(mptIssue, pay)))
3035 { // LCOV_EXCL_START
3036 JLOG(j.error())
3037 << "rippleLockEscrowMPT: insufficient MPTAmount for "
3038 << to_string(sender) << ": " << amt << " < " << pay;
3039 return tecINTERNAL;
3040 } // LCOV_EXCL_STOP
3041
3042 (*sle)[sfMPTAmount] = amt - pay;
3043
3044 // Overflow check for addition
3045 uint64_t const locked = (*sle)[~sfLockedAmount].value_or(0);
3046
3047 if (!canAdd(STAmount(mptIssue, locked), STAmount(mptIssue, pay)))
3048 { // LCOV_EXCL_START
3049 JLOG(j.error())
3050 << "rippleLockEscrowMPT: overflow on locked amount for "
3051 << to_string(sender) << ": " << locked << " + " << pay;
3052 return tecINTERNAL;
3053 } // LCOV_EXCL_STOP
3054
3055 if (sle->isFieldPresent(sfLockedAmount))
3056 (*sle)[sfLockedAmount] += pay;
3057 else
3058 sle->setFieldU64(sfLockedAmount, pay);
3059
3060 view.update(sle);
3061 }
3062
3063 // 1. Increase the Issuance EscrowedAmount
3064 // 2. DO NOT change the Issuance OutstandingAmount
3065 {
3066 uint64_t const issuanceEscrowed =
3067 (*sleIssuance)[~sfLockedAmount].value_or(0);
3068 auto const pay = amount.mpt().value();
3069
3070 // Overflow check for addition
3071 if (!canAdd(
3072 STAmount(mptIssue, issuanceEscrowed), STAmount(mptIssue, pay)))
3073 { // LCOV_EXCL_START
3074 JLOG(j.error()) << "rippleLockEscrowMPT: overflow on issuance "
3075 "locked amount for "
3076 << mptIssue.getMptID() << ": " << issuanceEscrowed
3077 << " + " << pay;
3078 return tecINTERNAL;
3079 } // LCOV_EXCL_STOP
3080
3081 if (sleIssuance->isFieldPresent(sfLockedAmount))
3082 (*sleIssuance)[sfLockedAmount] += pay;
3083 else
3084 sleIssuance->setFieldU64(sfLockedAmount, pay);
3085
3086 view.update(sleIssuance);
3087 }
3088 return tesSUCCESS;
3089}
3090
3091TER
3093 ApplyView& view,
3094 AccountID const& sender,
3095 AccountID const& receiver,
3096 STAmount const& netAmount,
3097 STAmount const& grossAmount,
3099{
3100 if (!view.rules().enabled(fixTokenEscrowV1))
3101 XRPL_ASSERT(
3102 netAmount == grossAmount,
3103 "ripple::rippleUnlockEscrowMPT : netAmount == grossAmount");
3104
3105 auto const& issuer = netAmount.getIssuer();
3106 auto const& mptIssue = netAmount.get<MPTIssue>();
3107 auto const mptID = keylet::mptIssuance(mptIssue.getMptID());
3108 auto sleIssuance = view.peek(mptID);
3109 if (!sleIssuance)
3110 { // LCOV_EXCL_START
3111 JLOG(j.error()) << "rippleUnlockEscrowMPT: MPT issuance not found for "
3112 << mptIssue.getMptID();
3113 return tecOBJECT_NOT_FOUND;
3114 } // LCOV_EXCL_STOP
3115
3116 // Decrease the Issuance EscrowedAmount
3117 {
3118 if (!sleIssuance->isFieldPresent(sfLockedAmount))
3119 { // LCOV_EXCL_START
3120 JLOG(j.error())
3121 << "rippleUnlockEscrowMPT: no locked amount in issuance for "
3122 << mptIssue.getMptID();
3123 return tecINTERNAL;
3124 } // LCOV_EXCL_STOP
3125
3126 auto const locked = sleIssuance->getFieldU64(sfLockedAmount);
3127 auto const redeem = grossAmount.mpt().value();
3128
3129 // Underflow check for subtraction
3130 if (!canSubtract(
3131 STAmount(mptIssue, locked), STAmount(mptIssue, redeem)))
3132 { // LCOV_EXCL_START
3133 JLOG(j.error())
3134 << "rippleUnlockEscrowMPT: insufficient locked amount for "
3135 << mptIssue.getMptID() << ": " << locked << " < " << redeem;
3136 return tecINTERNAL;
3137 } // LCOV_EXCL_STOP
3138
3139 auto const newLocked = locked - redeem;
3140 if (newLocked == 0)
3141 sleIssuance->makeFieldAbsent(sfLockedAmount);
3142 else
3143 sleIssuance->setFieldU64(sfLockedAmount, newLocked);
3144 view.update(sleIssuance);
3145 }
3146
3147 if (issuer != receiver)
3148 {
3149 // Increase the MPT Holder MPTAmount
3150 auto const mptokenID = keylet::mptoken(mptID.key, receiver);
3151 auto sle = view.peek(mptokenID);
3152 if (!sle)
3153 { // LCOV_EXCL_START
3154 JLOG(j.error())
3155 << "rippleUnlockEscrowMPT: MPToken not found for " << receiver;
3156 return tecOBJECT_NOT_FOUND;
3157 } // LCOV_EXCL_STOP
3158
3159 auto current = sle->getFieldU64(sfMPTAmount);
3160 auto delta = netAmount.mpt().value();
3161
3162 // Overflow check for addition
3163 if (!canAdd(STAmount(mptIssue, current), STAmount(mptIssue, delta)))
3164 { // LCOV_EXCL_START
3165 JLOG(j.error())
3166 << "rippleUnlockEscrowMPT: overflow on MPTAmount for "
3167 << to_string(receiver) << ": " << current << " + " << delta;
3168 return tecINTERNAL;
3169 } // LCOV_EXCL_STOP
3170
3171 (*sle)[sfMPTAmount] += delta;
3172 view.update(sle);
3173 }
3174 else
3175 {
3176 // Decrease the Issuance OutstandingAmount
3177 auto const outstanding = sleIssuance->getFieldU64(sfOutstandingAmount);
3178 auto const redeem = netAmount.mpt().value();
3179
3180 // Underflow check for subtraction
3181 if (!canSubtract(
3182 STAmount(mptIssue, outstanding), STAmount(mptIssue, redeem)))
3183 { // LCOV_EXCL_START
3184 JLOG(j.error())
3185 << "rippleUnlockEscrowMPT: insufficient outstanding amount for "
3186 << mptIssue.getMptID() << ": " << outstanding << " < "
3187 << redeem;
3188 return tecINTERNAL;
3189 } // LCOV_EXCL_STOP
3190
3191 sleIssuance->setFieldU64(sfOutstandingAmount, outstanding - redeem);
3192 view.update(sleIssuance);
3193 }
3194
3195 if (issuer == sender)
3196 { // LCOV_EXCL_START
3197 JLOG(j.error()) << "rippleUnlockEscrowMPT: sender is the issuer, "
3198 "cannot unlock MPTs.";
3199 return tecINTERNAL;
3200 } // LCOV_EXCL_STOP
3201 else
3202 {
3203 // Decrease the MPT Holder EscrowedAmount
3204 auto const mptokenID = keylet::mptoken(mptID.key, sender);
3205 auto sle = view.peek(mptokenID);
3206 if (!sle)
3207 { // LCOV_EXCL_START
3208 JLOG(j.error())
3209 << "rippleUnlockEscrowMPT: MPToken not found for " << sender;
3210 return tecOBJECT_NOT_FOUND;
3211 } // LCOV_EXCL_STOP
3212
3213 if (!sle->isFieldPresent(sfLockedAmount))
3214 { // LCOV_EXCL_START
3215 JLOG(j.error())
3216 << "rippleUnlockEscrowMPT: no locked amount in MPToken for "
3217 << to_string(sender);
3218 return tecINTERNAL;
3219 } // LCOV_EXCL_STOP
3220
3221 auto const locked = sle->getFieldU64(sfLockedAmount);
3222 auto const delta = grossAmount.mpt().value();
3223
3224 // Underflow check for subtraction
3225 if (!canSubtract(STAmount(mptIssue, locked), STAmount(mptIssue, delta)))
3226 { // LCOV_EXCL_START
3227 JLOG(j.error())
3228 << "rippleUnlockEscrowMPT: insufficient locked amount for "
3229 << to_string(sender) << ": " << locked << " < " << delta;
3230 return tecINTERNAL;
3231 } // LCOV_EXCL_STOP
3232
3233 auto const newLocked = locked - delta;
3234 if (newLocked == 0)
3235 sle->makeFieldAbsent(sfLockedAmount);
3236 else
3237 sle->setFieldU64(sfLockedAmount, newLocked);
3238 view.update(sle);
3239 }
3240
3241 // Note: The gross amount is the amount that was locked, the net
3242 // amount is the amount that is being unlocked. The difference is the fee
3243 // that was charged for the transfer. If this difference is greater than
3244 // zero, we need to update the outstanding amount.
3245 auto const diff = grossAmount.mpt().value() - netAmount.mpt().value();
3246 if (diff != 0)
3247 {
3248 auto const outstanding = sleIssuance->getFieldU64(sfOutstandingAmount);
3249 // Underflow check for subtraction
3250 if (!canSubtract(
3251 STAmount(mptIssue, outstanding), STAmount(mptIssue, diff)))
3252 { // LCOV_EXCL_START
3253 JLOG(j.error())
3254 << "rippleUnlockEscrowMPT: insufficient outstanding amount for "
3255 << mptIssue.getMptID() << ": " << outstanding << " < " << diff;
3256 return tecINTERNAL;
3257 } // LCOV_EXCL_STOP
3258
3259 sleIssuance->setFieldU64(sfOutstandingAmount, outstanding - diff);
3260 view.update(sleIssuance);
3261 }
3262 return tesSUCCESS;
3263}
3264
3265bool
3267{
3268 return now.time_since_epoch().count() > mark;
3269}
3270
3271} // 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:283
bool negative() const noexcept
Definition STAmount.h:471
AccountID const & getIssuer() const
Definition STAmount.h:508
MPTAmount mpt() const
Definition STAmount.cpp:314
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:673
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:2212
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:136
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:2995
@ 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:2912
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:585
static TER rippleSendMPT(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, STAmount &saActual, beast::Journal j, WaiveTransferFee waiveFee)
Definition View.cpp:2100
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:505
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:119
TER redeemIOU(ApplyView &view, AccountID const &account, STAmount const &amount, Issue const &issue, beast::Journal j)
Definition View.cpp:2365
@ 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:2884
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:2941
TER transferXRP(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &amount, beast::Journal j)
Definition View.cpp:2440
@ 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:2191
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:3092
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:2485
@ 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:2970
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:1708
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:2704
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:2265
static TER accountSendIOU(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee)
Definition View.cpp:1914
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:1607
@ tecNO_LINE_INSUF_RESERVE
Definition TER.h:293
@ tecOBJECT_NOT_FOUND
Definition TER.h:327
@ tecNO_TARGET
Definition TER.h:305
@ tecDIR_FULL
Definition TER.h:288
@ tecINCOMPLETE
Definition TER.h:336
@ tecFROZEN
Definition TER.h:304
@ tecDUPLICATE
Definition TER.h:316
@ tecINSUFFICIENT_FUNDS
Definition TER.h:326
@ tecINTERNAL
Definition TER.h:311
@ tecHAS_OBLIGATIONS
Definition TER.h:318
@ tecNO_LINE
Definition TER.h:302
@ tecPATH_DRY
Definition TER.h:295
@ tecINSUFFICIENT_RESERVE
Definition TER.h:308
@ tecFAILED_PROCESSING
Definition TER.h:287
@ tecEXPIRED
Definition TER.h:315
@ tecNO_AUTH
Definition TER.h:301
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:2856
@ tesSUCCESS
Definition TER.h:245
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:678
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:3266
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:1398
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:2806
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:2603
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:1862
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:1290
TERSubset< CanCvtToTER > TER
Definition TER.h:649
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:1517
static TER rippleCreditMPT(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, beast::Journal j)
Definition View.cpp:2038
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:1647
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:2166
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)