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