rippled
Loading...
Searching...
No Matches
InvariantCheck.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2012-2016 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 <xrpld/app/misc/CredentialHelpers.h>
21#include <xrpld/app/tx/detail/InvariantCheck.h>
22#include <xrpld/app/tx/detail/NFTokenUtils.h>
23#include <xrpld/app/tx/detail/PermissionedDomainSet.h>
24#include <xrpld/ledger/ReadView.h>
25#include <xrpld/ledger/View.h>
26
27#include <xrpl/basics/Log.h>
28#include <xrpl/protocol/Feature.h>
29#include <xrpl/protocol/FeeUnits.h>
30#include <xrpl/protocol/STArray.h>
31#include <xrpl/protocol/SystemParameters.h>
32#include <xrpl/protocol/TxFormats.h>
33#include <xrpl/protocol/nftPageMask.h>
34
35namespace ripple {
36
37void
39 bool,
42{
43 // nothing to do
44}
45
46bool
48 STTx const& tx,
49 TER const,
50 XRPAmount const fee,
51 ReadView const&,
52 beast::Journal const& j)
53{
54 // We should never charge a negative fee
55 if (fee.drops() < 0)
56 {
57 JLOG(j.fatal()) << "Invariant failed: fee paid was negative: "
58 << fee.drops();
59 return false;
60 }
61
62 // We should never charge a fee that's greater than or equal to the
63 // entire XRP supply.
64 if (fee >= INITIAL_XRP)
65 {
66 JLOG(j.fatal()) << "Invariant failed: fee paid exceeds system limit: "
67 << fee.drops();
68 return false;
69 }
70
71 // We should never charge more for a transaction than the transaction
72 // authorizes. It's possible to charge less in some circumstances.
73 if (fee > tx.getFieldAmount(sfFee).xrp())
74 {
75 JLOG(j.fatal()) << "Invariant failed: fee paid is " << fee.drops()
76 << " exceeds fee specified in transaction.";
77 return false;
78 }
79
80 return true;
81}
82
83//------------------------------------------------------------------------------
84
85void
87 bool isDelete,
88 std::shared_ptr<SLE const> const& before,
90{
91 /* We go through all modified ledger entries, looking only at account roots,
92 * escrow payments, and payment channels. We remove from the total any
93 * previous XRP values and add to the total any new XRP values. The net
94 * balance of a payment channel is computed from two fields (amount and
95 * balance) and deletions are ignored for paychan and escrow because the
96 * amount fields have not been adjusted for those in the case of deletion.
97 */
98 if (before)
99 {
100 switch (before->getType())
101 {
102 case ltACCOUNT_ROOT:
103 drops_ -= (*before)[sfBalance].xrp().drops();
104 break;
105 case ltPAYCHAN:
106 drops_ -=
107 ((*before)[sfAmount] - (*before)[sfBalance]).xrp().drops();
108 break;
109 case ltESCROW:
110 drops_ -= (*before)[sfAmount].xrp().drops();
111 break;
112 default:
113 break;
114 }
115 }
116
117 if (after)
118 {
119 switch (after->getType())
120 {
121 case ltACCOUNT_ROOT:
122 drops_ += (*after)[sfBalance].xrp().drops();
123 break;
124 case ltPAYCHAN:
125 if (!isDelete)
126 drops_ += ((*after)[sfAmount] - (*after)[sfBalance])
127 .xrp()
128 .drops();
129 break;
130 case ltESCROW:
131 if (!isDelete)
132 drops_ += (*after)[sfAmount].xrp().drops();
133 break;
134 default:
135 break;
136 }
137 }
138}
139
140bool
142 STTx const& tx,
143 TER const,
144 XRPAmount const fee,
145 ReadView const&,
146 beast::Journal const& j)
147{
148 // The net change should never be positive, as this would mean that the
149 // transaction created XRP out of thin air. That's not possible.
150 if (drops_ > 0)
151 {
152 JLOG(j.fatal()) << "Invariant failed: XRP net change was positive: "
153 << drops_;
154 return false;
155 }
156
157 // The negative of the net change should be equal to actual fee charged.
158 if (-drops_ != fee.drops())
159 {
160 JLOG(j.fatal()) << "Invariant failed: XRP net change of " << drops_
161 << " doesn't match fee " << fee.drops();
162 return false;
163 }
164
165 return true;
166}
167
168//------------------------------------------------------------------------------
169
170void
172 bool,
173 std::shared_ptr<SLE const> const& before,
175{
176 auto isBad = [](STAmount const& balance) {
177 if (!balance.native())
178 return true;
179
180 auto const drops = balance.xrp();
181
182 // Can't have more than the number of drops instantiated
183 // in the genesis ledger.
184 if (drops > INITIAL_XRP)
185 return true;
186
187 // Can't have a negative balance (0 is OK)
188 if (drops < XRPAmount{0})
189 return true;
190
191 return false;
192 };
193
194 if (before && before->getType() == ltACCOUNT_ROOT)
195 bad_ |= isBad((*before)[sfBalance]);
196
197 if (after && after->getType() == ltACCOUNT_ROOT)
198 bad_ |= isBad((*after)[sfBalance]);
199}
200
201bool
203 STTx const&,
204 TER const,
205 XRPAmount const,
206 ReadView const&,
207 beast::Journal const& j)
208{
209 if (bad_)
210 {
211 JLOG(j.fatal()) << "Invariant failed: incorrect account XRP balance";
212 return false;
213 }
214
215 return true;
216}
217
218//------------------------------------------------------------------------------
219
220void
222 bool isDelete,
223 std::shared_ptr<SLE const> const& before,
225{
226 auto isBad = [](STAmount const& pays, STAmount const& gets) {
227 // An offer should never be negative
228 if (pays < beast::zero)
229 return true;
230
231 if (gets < beast::zero)
232 return true;
233
234 // Can't have an XRP to XRP offer:
235 return pays.native() && gets.native();
236 };
237
238 if (before && before->getType() == ltOFFER)
239 bad_ |= isBad((*before)[sfTakerPays], (*before)[sfTakerGets]);
240
241 if (after && after->getType() == ltOFFER)
242 bad_ |= isBad((*after)[sfTakerPays], (*after)[sfTakerGets]);
243}
244
245bool
247 STTx const&,
248 TER const,
249 XRPAmount const,
250 ReadView const&,
251 beast::Journal const& j)
252{
253 if (bad_)
254 {
255 JLOG(j.fatal()) << "Invariant failed: offer with a bad amount";
256 return false;
257 }
258
259 return true;
260}
261
262//------------------------------------------------------------------------------
263
264void
266 bool isDelete,
267 std::shared_ptr<SLE const> const& before,
269{
270 auto isBad = [](STAmount const& amount) {
271 if (!amount.native())
272 return true;
273
274 if (amount.xrp() <= XRPAmount{0})
275 return true;
276
277 if (amount.xrp() >= INITIAL_XRP)
278 return true;
279
280 return false;
281 };
282
283 if (before && before->getType() == ltESCROW)
284 bad_ |= isBad((*before)[sfAmount]);
285
286 if (after && after->getType() == ltESCROW)
287 bad_ |= isBad((*after)[sfAmount]);
288}
289
290bool
292 STTx const&,
293 TER const,
294 XRPAmount const,
295 ReadView const&,
296 beast::Journal const& j)
297{
298 if (bad_)
299 {
300 JLOG(j.fatal()) << "Invariant failed: escrow specifies invalid amount";
301 return false;
302 }
303
304 return true;
305}
306
307//------------------------------------------------------------------------------
308
309void
311 bool isDelete,
312 std::shared_ptr<SLE const> const& before,
314{
315 if (isDelete && before && before->getType() == ltACCOUNT_ROOT)
317}
318
319bool
321 STTx const& tx,
322 TER const result,
323 XRPAmount const,
324 ReadView const&,
325 beast::Journal const& j)
326{
327 // AMM account root can be deleted as the result of AMM withdraw/delete
328 // transaction when the total AMM LP Tokens balance goes to 0.
329 // A successful AccountDelete or AMMDelete MUST delete exactly
330 // one account root.
331 if ((tx.getTxnType() == ttACCOUNT_DELETE ||
332 tx.getTxnType() == ttAMM_DELETE ||
333 tx.getTxnType() == ttVAULT_DELETE) &&
334 result == tesSUCCESS)
335 {
336 if (accountsDeleted_ == 1)
337 return true;
338
339 if (accountsDeleted_ == 0)
340 JLOG(j.fatal()) << "Invariant failed: account deletion "
341 "succeeded without deleting an account";
342 else
343 JLOG(j.fatal()) << "Invariant failed: account deletion "
344 "succeeded but deleted multiple accounts!";
345 return false;
346 }
347
348 // A successful AMMWithdraw/AMMClawback MAY delete one account root
349 // when the total AMM LP Tokens balance goes to 0. Not every AMM withdraw
350 // deletes the AMM account, accountsDeleted_ is set if it is deleted.
351 if ((tx.getTxnType() == ttAMM_WITHDRAW ||
352 tx.getTxnType() == ttAMM_CLAWBACK) &&
353 result == tesSUCCESS && accountsDeleted_ == 1)
354 return true;
355
356 if (accountsDeleted_ == 0)
357 return true;
358
359 JLOG(j.fatal()) << "Invariant failed: an account root was deleted";
360 return false;
361}
362
363//------------------------------------------------------------------------------
364
365void
367 bool isDelete,
368 std::shared_ptr<SLE const> const& before,
370{
371 if (isDelete && before && before->getType() == ltACCOUNT_ROOT)
372 accountsDeleted_.emplace_back(before);
373}
374
375bool
377 STTx const& tx,
378 TER const result,
379 XRPAmount const,
380 ReadView const& view,
381 beast::Journal const& j)
382{
383 // Always check for objects in the ledger, but to prevent differing
384 // transaction processing results, however unlikely, only fail if the
385 // feature is enabled. Enabled, or not, though, a fatal-level message will
386 // be logged
387 [[maybe_unused]] bool const enforce =
388 view.rules().enabled(featureInvariantsV1_1);
389
390 auto const objectExists = [&view, enforce, &j](auto const& keylet) {
391 (void)enforce;
392 if (auto const sle = view.read(keylet))
393 {
394 // Finding the object is bad
395 auto const typeName = [&sle]() {
396 auto item =
397 LedgerFormats::getInstance().findByType(sle->getType());
398
399 if (item != nullptr)
400 return item->getName();
401 return std::to_string(sle->getType());
402 }();
403
404 JLOG(j.fatal())
405 << "Invariant failed: account deletion left behind a "
406 << typeName << " object";
407 XRPL_ASSERT(
408 enforce,
409 "ripple::AccountRootsDeletedClean::finalize::objectExists : "
410 "account deletion left no objects behind");
411 return true;
412 }
413 return false;
414 };
415
416 for (auto const& accountSLE : accountsDeleted_)
417 {
418 auto const accountID = accountSLE->getAccountID(sfAccount);
419 // Simple types
420 for (auto const& [keyletfunc, _, __] : directAccountKeylets)
421 {
422 if (objectExists(std::invoke(keyletfunc, accountID)) && enforce)
423 return false;
424 }
425
426 {
427 // NFT pages. ntfpage_min and nftpage_max were already explicitly
428 // checked above as entries in directAccountKeylets. This uses
429 // view.succ() to check for any NFT pages in between the two
430 // endpoints.
431 Keylet const first = keylet::nftpage_min(accountID);
432 Keylet const last = keylet::nftpage_max(accountID);
433
434 std::optional<uint256> key = view.succ(first.key, last.key.next());
435
436 // current page
437 if (key && objectExists(Keylet{ltNFTOKEN_PAGE, *key}) && enforce)
438 return false;
439 }
440
441 // Keys directly stored in the AccountRoot object
442 if (auto const ammKey = accountSLE->at(~sfAMMID))
443 {
444 if (objectExists(keylet::amm(*ammKey)) && enforce)
445 return false;
446 }
447 }
448
449 return true;
450}
451
452//------------------------------------------------------------------------------
453
454void
456 bool,
457 std::shared_ptr<SLE const> const& before,
459{
460 if (before && after && before->getType() != after->getType())
461 typeMismatch_ = true;
462
463 if (after)
464 {
465 switch (after->getType())
466 {
467 case ltACCOUNT_ROOT:
468 case ltDELEGATE:
469 case ltDIR_NODE:
470 case ltRIPPLE_STATE:
471 case ltTICKET:
472 case ltSIGNER_LIST:
473 case ltOFFER:
474 case ltLEDGER_HASHES:
475 case ltAMENDMENTS:
476 case ltFEE_SETTINGS:
477 case ltESCROW:
478 case ltPAYCHAN:
479 case ltCHECK:
480 case ltDEPOSIT_PREAUTH:
481 case ltNEGATIVE_UNL:
482 case ltNFTOKEN_PAGE:
483 case ltNFTOKEN_OFFER:
484 case ltAMM:
485 case ltBRIDGE:
486 case ltXCHAIN_OWNED_CLAIM_ID:
487 case ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID:
488 case ltDID:
489 case ltORACLE:
490 case ltMPTOKEN_ISSUANCE:
491 case ltMPTOKEN:
492 case ltCREDENTIAL:
493 case ltPERMISSIONED_DOMAIN:
494 case ltVAULT:
495 break;
496 default:
497 invalidTypeAdded_ = true;
498 break;
499 }
500 }
501}
502
503bool
505 STTx const&,
506 TER const,
507 XRPAmount const,
508 ReadView const&,
509 beast::Journal const& j)
510{
511 if ((!typeMismatch_) && (!invalidTypeAdded_))
512 return true;
513
514 if (typeMismatch_)
515 {
516 JLOG(j.fatal()) << "Invariant failed: ledger entry type mismatch";
517 }
518
520 {
521 JLOG(j.fatal()) << "Invariant failed: invalid ledger entry type added";
522 }
523
524 return false;
525}
526
527//------------------------------------------------------------------------------
528
529void
531 bool,
534{
535 if (after && after->getType() == ltRIPPLE_STATE)
536 {
537 // checking the issue directly here instead of
538 // relying on .native() just in case native somehow
539 // were systematically incorrect
541 after->getFieldAmount(sfLowLimit).issue() == xrpIssue() ||
542 after->getFieldAmount(sfHighLimit).issue() == xrpIssue();
543 }
544}
545
546bool
548 STTx const&,
549 TER const,
550 XRPAmount const,
551 ReadView const&,
552 beast::Journal const& j)
553{
554 if (!xrpTrustLine_)
555 return true;
556
557 JLOG(j.fatal()) << "Invariant failed: an XRP trust line was created";
558 return false;
559}
560
561//------------------------------------------------------------------------------
562
563void
565 bool,
568{
569 if (after && after->getType() == ltRIPPLE_STATE)
570 {
571 std::uint32_t const uFlags = after->getFieldU32(sfFlags);
572 bool const lowFreeze = uFlags & lsfLowFreeze;
573 bool const lowDeepFreeze = uFlags & lsfLowDeepFreeze;
574
575 bool const highFreeze = uFlags & lsfHighFreeze;
576 bool const highDeepFreeze = uFlags & lsfHighDeepFreeze;
577
579 (lowDeepFreeze && !lowFreeze) || (highDeepFreeze && !highFreeze);
580 }
581}
582
583bool
585 STTx const&,
586 TER const,
587 XRPAmount const,
588 ReadView const&,
589 beast::Journal const& j)
590{
592 return true;
593
594 JLOG(j.fatal()) << "Invariant failed: a trust line with deep freeze flag "
595 "without normal freeze was created";
596 return false;
597}
598
599//------------------------------------------------------------------------------
600
601void
603 bool isDelete,
604 std::shared_ptr<SLE const> const& before,
606{
607 /*
608 * A trust line freeze state alone doesn't determine if a transfer is
609 * frozen. The transfer must be examined "end-to-end" because both sides of
610 * the transfer may have different freeze states and freeze impact depends
611 * on the transfer direction. This is why first we need to track the
612 * transfers using IssuerChanges senders/receivers.
613 *
614 * Only in validateIssuerChanges, after we collected all changes can we
615 * determine if the transfer is valid.
616 */
617 if (!isValidEntry(before, after))
618 {
619 return;
620 }
621
622 auto const balanceChange = calculateBalanceChange(before, after, isDelete);
623 if (balanceChange.signum() == 0)
624 {
625 return;
626 }
627
628 recordBalanceChanges(after, balanceChange);
629}
630
631bool
633 STTx const& tx,
634 TER const ter,
635 XRPAmount const fee,
636 ReadView const& view,
637 beast::Journal const& j)
638{
639 /*
640 * We check this invariant regardless of deep freeze amendment status,
641 * allowing for detection and logging of potential issues even when the
642 * amendment is disabled.
643 *
644 * If an exploit that allows moving frozen assets is discovered,
645 * we can alert operators who monitor fatal messages and trigger assert in
646 * debug builds for an early warning.
647 *
648 * In an unlikely event that an exploit is found, this early detection
649 * enables encouraging the UNL to expedite deep freeze amendment activation
650 * or deploy hotfixes via new amendments. In case of a new amendment, we'd
651 * only have to change this line setting 'enforce' variable.
652 * enforce = view.rules().enabled(featureDeepFreeze) ||
653 * view.rules().enabled(fixFreezeExploit);
654 */
655 [[maybe_unused]] bool const enforce =
656 view.rules().enabled(featureDeepFreeze);
657
658 for (auto const& [issue, changes] : balanceChanges_)
659 {
660 auto const issuerSle = findIssuer(issue.account, view);
661 // It should be impossible for the issuer to not be found, but check
662 // just in case so rippled doesn't crash in release.
663 if (!issuerSle)
664 {
665 XRPL_ASSERT(
666 enforce,
667 "ripple::TransfersNotFrozen::finalize : enforce "
668 "invariant.");
669 if (enforce)
670 {
671 return false;
672 }
673 continue;
674 }
675
676 if (!validateIssuerChanges(issuerSle, changes, tx, j, enforce))
677 {
678 return false;
679 }
680 }
681
682 return true;
683}
684
685bool
687 std::shared_ptr<SLE const> const& before,
689{
690 // `after` can never be null, even if the trust line is deleted.
691 XRPL_ASSERT(
692 after, "ripple::TransfersNotFrozen::isValidEntry : valid after.");
693 if (!after)
694 {
695 return false;
696 }
697
698 if (after->getType() == ltACCOUNT_ROOT)
699 {
700 possibleIssuers_.emplace(after->at(sfAccount), after);
701 return false;
702 }
703
704 /* While LedgerEntryTypesMatch invariant also checks types, all invariants
705 * are processed regardless of previous failures.
706 *
707 * This type check is still necessary here because it prevents potential
708 * issues in subsequent processing.
709 */
710 return after->getType() == ltRIPPLE_STATE &&
711 (!before || before->getType() == ltRIPPLE_STATE);
712}
713
716 std::shared_ptr<SLE const> const& before,
718 bool isDelete)
719{
720 auto const getBalance = [](auto const& line, auto const& other, bool zero) {
721 STAmount amt =
722 line ? line->at(sfBalance) : other->at(sfBalance).zeroed();
723 return zero ? amt.zeroed() : amt;
724 };
725
726 /* Trust lines can be created dynamically by other transactions such as
727 * Payment and OfferCreate that cross offers. Such trust line won't be
728 * created frozen, but the sender might be, so the starting balance must be
729 * treated as zero.
730 */
731 auto const balanceBefore = getBalance(before, after, false);
732
733 /* Same as above, trust lines can be dynamically deleted, and for frozen
734 * trust lines, payments not involving the issuer must be blocked. This is
735 * achieved by treating the final balance as zero when isDelete=true to
736 * ensure frozen line restrictions are enforced even during deletion.
737 */
738 auto const balanceAfter = getBalance(after, before, isDelete);
739
740 return balanceAfter - balanceBefore;
741}
742
743void
745{
746 XRPL_ASSERT(
747 change.balanceChangeSign,
748 "ripple::TransfersNotFrozen::recordBalance : valid trustline "
749 "balance sign.");
750 auto& changes = balanceChanges_[issue];
751 if (change.balanceChangeSign < 0)
752 changes.senders.emplace_back(std::move(change));
753 else
754 changes.receivers.emplace_back(std::move(change));
755}
756
757void
760 STAmount const& balanceChange)
761{
762 auto const balanceChangeSign = balanceChange.signum();
763 auto const currency = after->at(sfBalance).getCurrency();
764
765 // Change from low account's perspective, which is trust line default
767 {currency, after->at(sfHighLimit).getIssuer()},
768 {after, balanceChangeSign});
769
770 // Change from high account's perspective, which reverses the sign.
772 {currency, after->at(sfLowLimit).getIssuer()},
773 {after, -balanceChangeSign});
774}
775
778{
779 if (auto it = possibleIssuers_.find(issuerID); it != possibleIssuers_.end())
780 {
781 return it->second;
782 }
783
784 return view.read(keylet::account(issuerID));
785}
786
787bool
789 std::shared_ptr<SLE const> const& issuer,
790 IssuerChanges const& changes,
791 STTx const& tx,
792 beast::Journal const& j,
793 bool enforce)
794{
795 if (!issuer)
796 {
797 return false;
798 }
799
800 bool const globalFreeze = issuer->isFlag(lsfGlobalFreeze);
801 if (changes.receivers.empty() || changes.senders.empty())
802 {
803 /* If there are no receivers, then the holder(s) are returning
804 * their tokens to the issuer. Likewise, if there are no
805 * senders, then the issuer is issuing tokens to the holder(s).
806 * This is allowed regardless of the issuer's freeze flags. (The
807 * holder may have contradicting freeze flags, but that will be
808 * checked when the holder is treated as issuer.)
809 */
810 return true;
811 }
812
813 for (auto const& actors : {changes.senders, changes.receivers})
814 {
815 for (auto const& change : actors)
816 {
817 bool const high = change.line->at(sfLowLimit).getIssuer() ==
818 issuer->at(sfAccount);
819
821 change, high, tx, j, enforce, globalFreeze))
822 {
823 return false;
824 }
825 }
826 }
827 return true;
828}
829
830bool
832 BalanceChange const& change,
833 bool high,
834 STTx const& tx,
835 beast::Journal const& j,
836 bool enforce,
837 bool globalFreeze)
838{
839 bool const freeze = change.balanceChangeSign < 0 &&
840 change.line->isFlag(high ? lsfLowFreeze : lsfHighFreeze);
841 bool const deepFreeze =
842 change.line->isFlag(high ? lsfLowDeepFreeze : lsfHighDeepFreeze);
843 bool const frozen = globalFreeze || deepFreeze || freeze;
844
845 bool const isAMMLine = change.line->isFlag(lsfAMMNode);
846
847 if (!frozen)
848 {
849 return true;
850 }
851
852 // AMMClawbacks are allowed to override some freeze rules
853 if ((!isAMMLine || globalFreeze) && tx.getTxnType() == ttAMM_CLAWBACK)
854 {
855 JLOG(j.debug()) << "Invariant check allowing funds to be moved "
856 << (change.balanceChangeSign > 0 ? "to" : "from")
857 << " a frozen trustline for AMMClawback "
858 << tx.getTransactionID();
859 return true;
860 }
861
862 JLOG(j.fatal()) << "Invariant failed: Attempting to move frozen funds for "
863 << tx.getTransactionID();
864 XRPL_ASSERT(
865 enforce,
866 "ripple::TransfersNotFrozen::validateFrozenState : enforce "
867 "invariant.");
868
869 if (enforce)
870 {
871 return false;
872 }
873
874 return true;
875}
876
877//------------------------------------------------------------------------------
878
879void
881 bool,
882 std::shared_ptr<SLE const> const& before,
884{
885 if (!before && after->getType() == ltACCOUNT_ROOT)
886 {
888 accountSeq_ = (*after)[sfSequence];
890 flags_ = after->getFlags();
891 }
892}
893
894bool
896 STTx const& tx,
897 TER const result,
898 XRPAmount const,
899 ReadView const& view,
900 beast::Journal const& j)
901{
902 if (accountsCreated_ == 0)
903 return true;
904
905 if (accountsCreated_ > 1)
906 {
907 JLOG(j.fatal()) << "Invariant failed: multiple accounts "
908 "created in a single transaction";
909 return false;
910 }
911
912 // From this point on we know exactly one account was created.
913 if ((tx.getTxnType() == ttPAYMENT || tx.getTxnType() == ttAMM_CREATE ||
914 tx.getTxnType() == ttVAULT_CREATE ||
915 tx.getTxnType() == ttXCHAIN_ADD_CLAIM_ATTESTATION ||
916 tx.getTxnType() == ttXCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION) &&
917 result == tesSUCCESS)
918 {
919 bool const pseudoAccount =
920 (pseudoAccount_ && view.rules().enabled(featureSingleAssetVault));
921
922 if (pseudoAccount && tx.getTxnType() != ttAMM_CREATE &&
923 tx.getTxnType() != ttVAULT_CREATE)
924 {
925 JLOG(j.fatal()) << "Invariant failed: pseudo-account created by a "
926 "wrong transaction type";
927 return false;
928 }
929
930 std::uint32_t const startingSeq = //
931 pseudoAccount //
932 ? 0 //
933 : view.rules().enabled(featureDeletableAccounts) //
934 ? view.seq() //
935 : 1;
936
937 if (accountSeq_ != startingSeq)
938 {
939 JLOG(j.fatal()) << "Invariant failed: account created with "
940 "wrong starting sequence number";
941 return false;
942 }
943
944 if (pseudoAccount)
945 {
946 std::uint32_t const expected =
948 if (flags_ != expected)
949 {
950 JLOG(j.fatal())
951 << "Invariant failed: pseudo-account created with "
952 "wrong flags";
953 return false;
954 }
955 }
956
957 return true;
958 }
959
960 JLOG(j.fatal()) << "Invariant failed: account root created illegally";
961 return false;
962}
963
964//------------------------------------------------------------------------------
965
966void
968 bool isDelete,
969 std::shared_ptr<SLE const> const& before,
971{
972 static constexpr uint256 const& pageBits = nft::pageMask;
973 static constexpr uint256 const accountBits = ~pageBits;
974
975 if ((before && before->getType() != ltNFTOKEN_PAGE) ||
976 (after && after->getType() != ltNFTOKEN_PAGE))
977 return;
978
979 auto check = [this, isDelete](std::shared_ptr<SLE const> const& sle) {
980 uint256 const account = sle->key() & accountBits;
981 uint256 const hiLimit = sle->key() & pageBits;
982 std::optional<uint256> const prev = (*sle)[~sfPreviousPageMin];
983
984 // Make sure that any page links...
985 // 1. Are properly associated with the owning account and
986 // 2. The page is correctly ordered between links.
987 if (prev)
988 {
989 if (account != (*prev & accountBits))
990 badLink_ = true;
991
992 if (hiLimit <= (*prev & pageBits))
993 badLink_ = true;
994 }
995
996 if (auto const next = (*sle)[~sfNextPageMin])
997 {
998 if (account != (*next & accountBits))
999 badLink_ = true;
1000
1001 if (hiLimit >= (*next & pageBits))
1002 badLink_ = true;
1003 }
1004
1005 {
1006 auto const& nftokens = sle->getFieldArray(sfNFTokens);
1007
1008 // An NFTokenPage should never contain too many tokens or be empty.
1009 if (std::size_t const nftokenCount = nftokens.size();
1010 (!isDelete && nftokenCount == 0) ||
1011 nftokenCount > dirMaxTokensPerPage)
1012 invalidSize_ = true;
1013
1014 // If prev is valid, use it to establish a lower bound for
1015 // page entries. If prev is not valid the lower bound is zero.
1016 uint256 const loLimit =
1017 prev ? *prev & pageBits : uint256(beast::zero);
1018
1019 // Also verify that all NFTokenIDs in the page are sorted.
1020 uint256 loCmp = loLimit;
1021 for (auto const& obj : nftokens)
1022 {
1023 uint256 const tokenID = obj[sfNFTokenID];
1024 if (!nft::compareTokens(loCmp, tokenID))
1025 badSort_ = true;
1026 loCmp = tokenID;
1027
1028 // None of the NFTs on this page should belong on lower or
1029 // higher pages.
1030 if (uint256 const tokenPageBits = tokenID & pageBits;
1031 tokenPageBits < loLimit || tokenPageBits >= hiLimit)
1032 badEntry_ = true;
1033
1034 if (auto uri = obj[~sfURI]; uri && uri->empty())
1035 badURI_ = true;
1036 }
1037 }
1038 };
1039
1040 if (before)
1041 {
1042 check(before);
1043
1044 // While an account's NFToken directory contains any NFTokens, the last
1045 // NFTokenPage (with 96 bits of 1 in the low part of the index) should
1046 // never be deleted.
1047 if (isDelete && (before->key() & nft::pageMask) == nft::pageMask &&
1048 before->isFieldPresent(sfPreviousPageMin))
1049 {
1050 deletedFinalPage_ = true;
1051 }
1052 }
1053
1054 if (after)
1055 check(after);
1056
1057 if (!isDelete && before && after)
1058 {
1059 // If the NFTokenPage
1060 // 1. Has a NextMinPage field in before, but loses it in after, and
1061 // 2. This is not the last page in the directory
1062 // Then we have identified a corruption in the links between the
1063 // NFToken pages in the NFToken directory.
1064 if ((before->key() & nft::pageMask) != nft::pageMask &&
1065 before->isFieldPresent(sfNextPageMin) &&
1066 !after->isFieldPresent(sfNextPageMin))
1067 {
1068 deletedLink_ = true;
1069 }
1070 }
1071}
1072
1073bool
1075 STTx const& tx,
1076 TER const result,
1077 XRPAmount const,
1078 ReadView const& view,
1079 beast::Journal const& j)
1080{
1081 if (badLink_)
1082 {
1083 JLOG(j.fatal()) << "Invariant failed: NFT page is improperly linked.";
1084 return false;
1085 }
1086
1087 if (badEntry_)
1088 {
1089 JLOG(j.fatal()) << "Invariant failed: NFT found in incorrect page.";
1090 return false;
1091 }
1092
1093 if (badSort_)
1094 {
1095 JLOG(j.fatal()) << "Invariant failed: NFTs on page are not sorted.";
1096 return false;
1097 }
1098
1099 if (badURI_)
1100 {
1101 JLOG(j.fatal()) << "Invariant failed: NFT contains empty URI.";
1102 return false;
1103 }
1104
1105 if (invalidSize_)
1106 {
1107 JLOG(j.fatal()) << "Invariant failed: NFT page has invalid size.";
1108 return false;
1109 }
1110
1111 if (view.rules().enabled(fixNFTokenPageLinks))
1112 {
1114 {
1115 JLOG(j.fatal()) << "Invariant failed: Last NFT page deleted with "
1116 "non-empty directory.";
1117 return false;
1118 }
1119 if (deletedLink_)
1120 {
1121 JLOG(j.fatal()) << "Invariant failed: Lost NextMinPage link.";
1122 return false;
1123 }
1124 }
1125
1126 return true;
1127}
1128
1129//------------------------------------------------------------------------------
1130void
1132 bool,
1133 std::shared_ptr<SLE const> const& before,
1135{
1136 if (before && before->getType() == ltACCOUNT_ROOT)
1137 {
1138 beforeMintedTotal += (*before)[~sfMintedNFTokens].value_or(0);
1139 beforeBurnedTotal += (*before)[~sfBurnedNFTokens].value_or(0);
1140 }
1141
1142 if (after && after->getType() == ltACCOUNT_ROOT)
1143 {
1144 afterMintedTotal += (*after)[~sfMintedNFTokens].value_or(0);
1145 afterBurnedTotal += (*after)[~sfBurnedNFTokens].value_or(0);
1146 }
1147}
1148
1149bool
1151 STTx const& tx,
1152 TER const result,
1153 XRPAmount const,
1154 ReadView const& view,
1155 beast::Journal const& j)
1156{
1157 if (TxType const txType = tx.getTxnType();
1158 txType != ttNFTOKEN_MINT && txType != ttNFTOKEN_BURN)
1159 {
1161 {
1162 JLOG(j.fatal()) << "Invariant failed: the number of minted tokens "
1163 "changed without a mint transaction!";
1164 return false;
1165 }
1166
1168 {
1169 JLOG(j.fatal()) << "Invariant failed: the number of burned tokens "
1170 "changed without a burn transaction!";
1171 return false;
1172 }
1173
1174 return true;
1175 }
1176
1177 if (tx.getTxnType() == ttNFTOKEN_MINT)
1178 {
1179 if (result == tesSUCCESS && beforeMintedTotal >= afterMintedTotal)
1180 {
1181 JLOG(j.fatal())
1182 << "Invariant failed: successful minting didn't increase "
1183 "the number of minted tokens.";
1184 return false;
1185 }
1186
1187 if (result != tesSUCCESS && beforeMintedTotal != afterMintedTotal)
1188 {
1189 JLOG(j.fatal()) << "Invariant failed: failed minting changed the "
1190 "number of minted tokens.";
1191 return false;
1192 }
1193
1195 {
1196 JLOG(j.fatal())
1197 << "Invariant failed: minting changed the number of "
1198 "burned tokens.";
1199 return false;
1200 }
1201 }
1202
1203 if (tx.getTxnType() == ttNFTOKEN_BURN)
1204 {
1205 if (result == tesSUCCESS)
1206 {
1208 {
1209 JLOG(j.fatal())
1210 << "Invariant failed: successful burning didn't increase "
1211 "the number of burned tokens.";
1212 return false;
1213 }
1214 }
1215
1216 if (result != tesSUCCESS && beforeBurnedTotal != afterBurnedTotal)
1217 {
1218 JLOG(j.fatal()) << "Invariant failed: failed burning changed the "
1219 "number of burned tokens.";
1220 return false;
1221 }
1222
1224 {
1225 JLOG(j.fatal())
1226 << "Invariant failed: burning changed the number of "
1227 "minted tokens.";
1228 return false;
1229 }
1230 }
1231
1232 return true;
1233}
1234
1235//------------------------------------------------------------------------------
1236
1237void
1239 bool,
1240 std::shared_ptr<SLE const> const& before,
1242{
1243 if (before && before->getType() == ltRIPPLE_STATE)
1245
1246 if (before && before->getType() == ltMPTOKEN)
1248}
1249
1250bool
1252 STTx const& tx,
1253 TER const result,
1254 XRPAmount const,
1255 ReadView const& view,
1256 beast::Journal const& j)
1257{
1258 if (tx.getTxnType() != ttCLAWBACK)
1259 return true;
1260
1261 if (result == tesSUCCESS)
1262 {
1263 if (trustlinesChanged > 1)
1264 {
1265 JLOG(j.fatal())
1266 << "Invariant failed: more than one trustline changed.";
1267 return false;
1268 }
1269
1270 if (mptokensChanged > 1)
1271 {
1272 JLOG(j.fatal())
1273 << "Invariant failed: more than one mptokens changed.";
1274 return false;
1275 }
1276
1277 if (trustlinesChanged == 1)
1278 {
1279 AccountID const issuer = tx.getAccountID(sfAccount);
1280 STAmount const& amount = tx.getFieldAmount(sfAmount);
1281 AccountID const& holder = amount.getIssuer();
1282 STAmount const holderBalance = accountHolds(
1283 view, holder, amount.getCurrency(), issuer, fhIGNORE_FREEZE, j);
1284
1285 if (holderBalance.signum() < 0)
1286 {
1287 JLOG(j.fatal())
1288 << "Invariant failed: trustline balance is negative";
1289 return false;
1290 }
1291 }
1292 }
1293 else
1294 {
1295 if (trustlinesChanged != 0)
1296 {
1297 JLOG(j.fatal()) << "Invariant failed: some trustlines were changed "
1298 "despite failure of the transaction.";
1299 return false;
1300 }
1301
1302 if (mptokensChanged != 0)
1303 {
1304 JLOG(j.fatal()) << "Invariant failed: some mptokens were changed "
1305 "despite failure of the transaction.";
1306 return false;
1307 }
1308 }
1309
1310 return true;
1311}
1312
1313//------------------------------------------------------------------------------
1314
1315void
1317 bool isDelete,
1318 std::shared_ptr<SLE const> const& before,
1320{
1321 if (after && after->getType() == ltMPTOKEN_ISSUANCE)
1322 {
1323 if (isDelete)
1325 else if (!before)
1327 }
1328
1329 if (after && after->getType() == ltMPTOKEN)
1330 {
1331 if (isDelete)
1333 else if (!before)
1335 }
1336}
1337
1338bool
1340 STTx const& tx,
1341 TER const result,
1342 XRPAmount const _fee,
1343 ReadView const& _view,
1344 beast::Journal const& j)
1345{
1346 if (result == tesSUCCESS)
1347 {
1348 if (tx.getTxnType() == ttMPTOKEN_ISSUANCE_CREATE ||
1349 tx.getTxnType() == ttVAULT_CREATE)
1350 {
1351 if (mptIssuancesCreated_ == 0)
1352 {
1353 JLOG(j.fatal()) << "Invariant failed: transaction "
1354 "succeeded without creating a MPT issuance";
1355 }
1356 else if (mptIssuancesDeleted_ != 0)
1357 {
1358 JLOG(j.fatal()) << "Invariant failed: transaction "
1359 "succeeded while removing MPT issuances";
1360 }
1361 else if (mptIssuancesCreated_ > 1)
1362 {
1363 JLOG(j.fatal()) << "Invariant failed: transaction "
1364 "succeeded but created multiple issuances";
1365 }
1366
1367 return mptIssuancesCreated_ == 1 && mptIssuancesDeleted_ == 0;
1368 }
1369
1370 if (tx.getTxnType() == ttMPTOKEN_ISSUANCE_DESTROY ||
1371 tx.getTxnType() == ttVAULT_DELETE)
1372 {
1373 if (mptIssuancesDeleted_ == 0)
1374 {
1375 JLOG(j.fatal()) << "Invariant failed: MPT issuance deletion "
1376 "succeeded without removing a MPT issuance";
1377 }
1378 else if (mptIssuancesCreated_ > 0)
1379 {
1380 JLOG(j.fatal()) << "Invariant failed: MPT issuance deletion "
1381 "succeeded while creating MPT issuances";
1382 }
1383 else if (mptIssuancesDeleted_ > 1)
1384 {
1385 JLOG(j.fatal()) << "Invariant failed: MPT issuance deletion "
1386 "succeeded but deleted multiple issuances";
1387 }
1388
1389 return mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 1;
1390 }
1391
1392 if (tx.getTxnType() == ttMPTOKEN_AUTHORIZE ||
1393 tx.getTxnType() == ttVAULT_DEPOSIT)
1394 {
1395 bool const submittedByIssuer = tx.isFieldPresent(sfHolder);
1396
1397 if (mptIssuancesCreated_ > 0)
1398 {
1399 JLOG(j.fatal()) << "Invariant failed: MPT authorize "
1400 "succeeded but created MPT issuances";
1401 return false;
1402 }
1403 else if (mptIssuancesDeleted_ > 0)
1404 {
1405 JLOG(j.fatal()) << "Invariant failed: MPT authorize "
1406 "succeeded but deleted issuances";
1407 return false;
1408 }
1409 else if (
1410 submittedByIssuer &&
1412 {
1413 JLOG(j.fatal())
1414 << "Invariant failed: MPT authorize submitted by issuer "
1415 "succeeded but created/deleted mptokens";
1416 return false;
1417 }
1418 else if (
1419 !submittedByIssuer && (tx.getTxnType() != ttVAULT_DEPOSIT) &&
1421 {
1422 // if the holder submitted this tx, then a mptoken must be
1423 // either created or deleted.
1424 JLOG(j.fatal())
1425 << "Invariant failed: MPT authorize submitted by holder "
1426 "succeeded but created/deleted bad number of mptokens";
1427 return false;
1428 }
1429
1430 return true;
1431 }
1432
1433 if (tx.getTxnType() == ttMPTOKEN_ISSUANCE_SET)
1434 {
1435 if (mptIssuancesDeleted_ > 0)
1436 {
1437 JLOG(j.fatal()) << "Invariant failed: MPT issuance set "
1438 "succeeded while removing MPT issuances";
1439 }
1440 else if (mptIssuancesCreated_ > 0)
1441 {
1442 JLOG(j.fatal()) << "Invariant failed: MPT issuance set "
1443 "succeeded while creating MPT issuances";
1444 }
1445 else if (mptokensDeleted_ > 0)
1446 {
1447 JLOG(j.fatal()) << "Invariant failed: MPT issuance set "
1448 "succeeded while removing MPTokens";
1449 }
1450 else if (mptokensCreated_ > 0)
1451 {
1452 JLOG(j.fatal()) << "Invariant failed: MPT issuance set "
1453 "succeeded while creating MPTokens";
1454 }
1455
1456 return mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 0 &&
1458 }
1459 }
1460
1461 if (mptIssuancesCreated_ != 0)
1462 {
1463 JLOG(j.fatal()) << "Invariant failed: a MPT issuance was created";
1464 }
1465 else if (mptIssuancesDeleted_ != 0)
1466 {
1467 JLOG(j.fatal()) << "Invariant failed: a MPT issuance was deleted";
1468 }
1469 else if (mptokensCreated_ != 0)
1470 {
1471 JLOG(j.fatal()) << "Invariant failed: a MPToken was created";
1472 }
1473 else if (mptokensDeleted_ != 0)
1474 {
1475 JLOG(j.fatal()) << "Invariant failed: a MPToken was deleted";
1476 }
1477
1478 return mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 0 &&
1480}
1481
1482//------------------------------------------------------------------------------
1483
1484void
1486 bool,
1487 std::shared_ptr<SLE const> const& before,
1489{
1490 if (before && before->getType() != ltPERMISSIONED_DOMAIN)
1491 return;
1492 if (after && after->getType() != ltPERMISSIONED_DOMAIN)
1493 return;
1494
1495 auto check = [](SleStatus& sleStatus,
1496 std::shared_ptr<SLE const> const& sle) {
1497 auto const& credentials = sle->getFieldArray(sfAcceptedCredentials);
1498 sleStatus.credentialsSize_ = credentials.size();
1499 auto const sorted = credentials::makeSorted(credentials);
1500 sleStatus.isUnique_ = !sorted.empty();
1501
1502 // If array have duplicates then all the other checks are invalid
1503 sleStatus.isSorted_ = false;
1504
1505 if (sleStatus.isUnique_)
1506 {
1507 unsigned i = 0;
1508 for (auto const& cred : sorted)
1509 {
1510 auto const& credTx = credentials[i++];
1511 sleStatus.isSorted_ = (cred.first == credTx[sfIssuer]) &&
1512 (cred.second == credTx[sfCredentialType]);
1513 if (!sleStatus.isSorted_)
1514 break;
1515 }
1516 }
1517 };
1518
1519 if (before)
1520 {
1521 sleStatus_[0] = SleStatus();
1522 check(*sleStatus_[0], after);
1523 }
1524
1525 if (after)
1526 {
1527 sleStatus_[1] = SleStatus();
1528 check(*sleStatus_[1], after);
1529 }
1530}
1531
1532bool
1534 STTx const& tx,
1535 TER const result,
1536 XRPAmount const,
1537 ReadView const& view,
1538 beast::Journal const& j)
1539{
1540 if (tx.getTxnType() != ttPERMISSIONED_DOMAIN_SET || result != tesSUCCESS)
1541 return true;
1542
1543 auto check = [](SleStatus const& sleStatus, beast::Journal const& j) {
1544 if (!sleStatus.credentialsSize_)
1545 {
1546 JLOG(j.fatal()) << "Invariant failed: permissioned domain with "
1547 "no rules.";
1548 return false;
1549 }
1550
1551 if (sleStatus.credentialsSize_ >
1553 {
1554 JLOG(j.fatal()) << "Invariant failed: permissioned domain bad "
1555 "credentials size "
1556 << sleStatus.credentialsSize_;
1557 return false;
1558 }
1559
1560 if (!sleStatus.isUnique_)
1561 {
1562 JLOG(j.fatal())
1563 << "Invariant failed: permissioned domain credentials "
1564 "aren't unique";
1565 return false;
1566 }
1567
1568 if (!sleStatus.isSorted_)
1569 {
1570 JLOG(j.fatal())
1571 << "Invariant failed: permissioned domain credentials "
1572 "aren't sorted";
1573 return false;
1574 }
1575
1576 return true;
1577 };
1578
1579 return (sleStatus_[0] ? check(*sleStatus_[0], j) : true) &&
1580 (sleStatus_[1] ? check(*sleStatus_[1], j) : true);
1581}
1582
1583} // namespace ripple
A generic endpoint for log messages.
Definition: Journal.h:60
Stream fatal() const
Definition: Journal.h:352
Stream debug() const
Definition: Journal.h:328
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
std::vector< std::shared_ptr< SLE const > > accountsDeleted_
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
A currency issued by an account.
Definition: Issue.h:36
Item const * findByType(KeyType type) const
Retrieve a format based on its type.
Definition: KnownFormats.h:129
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
static LedgerFormats const & getInstance()
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
A view into a ledger.
Definition: ReadView.h:52
virtual std::shared_ptr< SLE const > read(Keylet const &k) const =0
Return the state item associated with a key.
virtual std::optional< key_type > succ(key_type const &key, std::optional< key_type > const &last=std::nullopt) const =0
Return the key of the next state item.
LedgerIndex seq() const
Returns the sequence number of the base ledger.
Definition: ReadView.h:119
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
Currency const & getCurrency() const
Definition: STAmount.h:502
XRPAmount xrp() const
Definition: STAmount.cpp:306
int signum() const noexcept
Definition: STAmount.h:514
AccountID const & getIssuer() const
Definition: STAmount.h:508
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
AccountID getAccountID(SField const &field) const
Definition: STObject.cpp:651
STAmount const & getFieldAmount(SField const &field) const
Definition: STObject.cpp:665
bool isFieldPresent(SField const &field) const
Definition: STObject.cpp:484
TxType getTxnType() const
Definition: STTx.h:207
uint256 getTransactionID() const
Definition: STTx.h:219
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
std::map< AccountID, std::shared_ptr< SLE const > const > possibleIssuers_
bool isValidEntry(std::shared_ptr< SLE const > const &before, std::shared_ptr< SLE const > const &after)
void recordBalance(Issue const &issue, BalanceChange change)
std::shared_ptr< SLE const > findIssuer(AccountID const &issuerID, ReadView const &view)
bool validateIssuerChanges(std::shared_ptr< SLE const > const &issuer, IssuerChanges const &changes, STTx const &tx, beast::Journal const &j, bool enforce)
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
STAmount calculateBalanceChange(std::shared_ptr< SLE const > const &before, std::shared_ptr< SLE const > const &after, bool isDelete)
void recordBalanceChanges(std::shared_ptr< SLE const > const &after, STAmount const &balanceChange)
bool validateFrozenState(BalanceChange const &change, bool high, STTx const &tx, beast::Journal const &j, bool enforce, bool globalFreeze)
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
std::uint32_t trustlinesChanged
std::uint32_t mptokensChanged
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
std::uint32_t mptIssuancesCreated_
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
std::uint32_t mptokensCreated_
std::uint32_t mptIssuancesDeleted_
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
std::uint32_t mptokensDeleted_
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
std::optional< SleStatus > sleStatus_[2]
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
constexpr value_type drops() const
Returns the number of drops.
Definition: XRPAmount.h:177
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
base_uint next() const
Definition: base_uint.h:455
T invoke(T... args)
std::set< std::pair< AccountID, Slice > > makeSorted(STArray const &credentials)
Keylet amm(Asset const &issue1, Asset const &issue2) noexcept
AMM entry.
Definition: Indexes.cpp:439
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition: Indexes.cpp:177
Keylet nftpage_min(AccountID const &owner)
NFT page keylets.
Definition: Indexes.cpp:396
Keylet nftpage_max(AccountID const &owner)
A keylet for the owner's last possible NFT page.
Definition: Indexes.cpp:404
bool compareTokens(uint256 const &a, uint256 const &b)
uint256 constexpr pageMask(std::string_view("0000000000000000000000000000000000000000ffffffffffffffffffffffff"))
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
Definition: Issue.h:118
@ fhIGNORE_FREEZE
Definition: View.h:78
TxType
Transaction type identifiers.
Definition: TxFormats.h:57
base_uint< 256 > uint256
Definition: base_uint.h:558
std::size_t constexpr maxPermissionedDomainCredentialsArraySize
The maximum number of credentials can be passed in array for permissioned domain.
Definition: Protocol.h:111
@ lsfHighDeepFreeze
@ lsfDefaultRipple
@ lsfHighFreeze
@ lsfDisableMaster
@ lsfDepositAuth
@ lsfGlobalFreeze
@ lsfLowFreeze
@ lsfLowDeepFreeze
constexpr XRPAmount INITIAL_XRP
Configure the native currency.
std::size_t constexpr dirMaxTokensPerPage
The maximum number of items in an NFT page.
Definition: Protocol.h:63
std::array< keyletDesc< AccountID const & >, 6 > const directAccountKeylets
Definition: Indexes.h:381
@ tesSUCCESS
Definition: TER.h:244
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j)
Definition: View.cpp:387
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition: View.cpp:2707
bool isPseudoAccount(std::shared_ptr< SLE const > sleAcct)
Definition: View.cpp:1128
A pair of SHAMap key and LedgerEntryType.
Definition: Keylet.h:39
uint256 key
Definition: Keylet.h:40
std::shared_ptr< SLE const > const line
std::vector< BalanceChange > receivers
std::vector< BalanceChange > senders
T to_string(T... args)