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 result == tesSUCCESS)
334 {
335 if (accountsDeleted_ == 1)
336 return true;
337
338 if (accountsDeleted_ == 0)
339 JLOG(j.fatal()) << "Invariant failed: account deletion "
340 "succeeded without deleting an account";
341 else
342 JLOG(j.fatal()) << "Invariant failed: account deletion "
343 "succeeded but deleted multiple accounts!";
344 return false;
345 }
346
347 // A successful AMMWithdraw/AMMClawback MAY delete one account root
348 // when the total AMM LP Tokens balance goes to 0. Not every AMM withdraw
349 // deletes the AMM account, accountsDeleted_ is set if it is deleted.
350 if ((tx.getTxnType() == ttAMM_WITHDRAW ||
351 tx.getTxnType() == ttAMM_CLAWBACK) &&
352 result == tesSUCCESS && accountsDeleted_ == 1)
353 return true;
354
355 if (accountsDeleted_ == 0)
356 return true;
357
358 JLOG(j.fatal()) << "Invariant failed: an account root was deleted";
359 return false;
360}
361
362//------------------------------------------------------------------------------
363
364void
366 bool isDelete,
367 std::shared_ptr<SLE const> const& before,
369{
370 if (isDelete && before && before->getType() == ltACCOUNT_ROOT)
371 accountsDeleted_.emplace_back(before);
372}
373
374bool
376 STTx const& tx,
377 TER const result,
378 XRPAmount const,
379 ReadView const& view,
380 beast::Journal const& j)
381{
382 // Always check for objects in the ledger, but to prevent differing
383 // transaction processing results, however unlikely, only fail if the
384 // feature is enabled. Enabled, or not, though, a fatal-level message will
385 // be logged
386 [[maybe_unused]] bool const enforce =
387 view.rules().enabled(featureInvariantsV1_1);
388
389 auto const objectExists = [&view, enforce, &j](auto const& keylet) {
390 (void)enforce;
391 if (auto const sle = view.read(keylet))
392 {
393 // Finding the object is bad
394 auto const typeName = [&sle]() {
395 auto item =
396 LedgerFormats::getInstance().findByType(sle->getType());
397
398 if (item != nullptr)
399 return item->getName();
400 return std::to_string(sle->getType());
401 }();
402
403 JLOG(j.fatal())
404 << "Invariant failed: account deletion left behind a "
405 << typeName << " object";
406 XRPL_ASSERT(
407 enforce,
408 "ripple::AccountRootsDeletedClean::finalize::objectExists : "
409 "account deletion left no objects behind");
410 return true;
411 }
412 return false;
413 };
414
415 for (auto const& accountSLE : accountsDeleted_)
416 {
417 auto const accountID = accountSLE->getAccountID(sfAccount);
418 // Simple types
419 for (auto const& [keyletfunc, _, __] : directAccountKeylets)
420 {
421 if (objectExists(std::invoke(keyletfunc, accountID)) && enforce)
422 return false;
423 }
424
425 {
426 // NFT pages. ntfpage_min and nftpage_max were already explicitly
427 // checked above as entries in directAccountKeylets. This uses
428 // view.succ() to check for any NFT pages in between the two
429 // endpoints.
430 Keylet const first = keylet::nftpage_min(accountID);
431 Keylet const last = keylet::nftpage_max(accountID);
432
433 std::optional<uint256> key = view.succ(first.key, last.key.next());
434
435 // current page
436 if (key && objectExists(Keylet{ltNFTOKEN_PAGE, *key}) && enforce)
437 return false;
438 }
439
440 // Keys directly stored in the AccountRoot object
441 if (auto const ammKey = accountSLE->at(~sfAMMID))
442 {
443 if (objectExists(keylet::amm(*ammKey)) && enforce)
444 return false;
445 }
446 }
447
448 return true;
449}
450
451//------------------------------------------------------------------------------
452
453void
455 bool,
456 std::shared_ptr<SLE const> const& before,
458{
459 if (before && after && before->getType() != after->getType())
460 typeMismatch_ = true;
461
462 if (after)
463 {
464 switch (after->getType())
465 {
466 case ltACCOUNT_ROOT:
467 case ltDELEGATE:
468 case ltDIR_NODE:
469 case ltRIPPLE_STATE:
470 case ltTICKET:
471 case ltSIGNER_LIST:
472 case ltOFFER:
473 case ltLEDGER_HASHES:
474 case ltAMENDMENTS:
475 case ltFEE_SETTINGS:
476 case ltESCROW:
477 case ltPAYCHAN:
478 case ltCHECK:
479 case ltDEPOSIT_PREAUTH:
480 case ltNEGATIVE_UNL:
481 case ltNFTOKEN_PAGE:
482 case ltNFTOKEN_OFFER:
483 case ltAMM:
484 case ltBRIDGE:
485 case ltXCHAIN_OWNED_CLAIM_ID:
486 case ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID:
487 case ltDID:
488 case ltORACLE:
489 case ltMPTOKEN_ISSUANCE:
490 case ltMPTOKEN:
491 case ltCREDENTIAL:
492 case ltPERMISSIONED_DOMAIN:
493 break;
494 default:
495 invalidTypeAdded_ = true;
496 break;
497 }
498 }
499}
500
501bool
503 STTx const&,
504 TER const,
505 XRPAmount const,
506 ReadView const&,
507 beast::Journal const& j)
508{
509 if ((!typeMismatch_) && (!invalidTypeAdded_))
510 return true;
511
512 if (typeMismatch_)
513 {
514 JLOG(j.fatal()) << "Invariant failed: ledger entry type mismatch";
515 }
516
518 {
519 JLOG(j.fatal()) << "Invariant failed: invalid ledger entry type added";
520 }
521
522 return false;
523}
524
525//------------------------------------------------------------------------------
526
527void
529 bool,
532{
533 if (after && after->getType() == ltRIPPLE_STATE)
534 {
535 // checking the issue directly here instead of
536 // relying on .native() just in case native somehow
537 // were systematically incorrect
539 after->getFieldAmount(sfLowLimit).issue() == xrpIssue() ||
540 after->getFieldAmount(sfHighLimit).issue() == xrpIssue();
541 }
542}
543
544bool
546 STTx const&,
547 TER const,
548 XRPAmount const,
549 ReadView const&,
550 beast::Journal const& j)
551{
552 if (!xrpTrustLine_)
553 return true;
554
555 JLOG(j.fatal()) << "Invariant failed: an XRP trust line was created";
556 return false;
557}
558
559//------------------------------------------------------------------------------
560
561void
563 bool,
566{
567 if (after && after->getType() == ltRIPPLE_STATE)
568 {
569 std::uint32_t const uFlags = after->getFieldU32(sfFlags);
570 bool const lowFreeze = uFlags & lsfLowFreeze;
571 bool const lowDeepFreeze = uFlags & lsfLowDeepFreeze;
572
573 bool const highFreeze = uFlags & lsfHighFreeze;
574 bool const highDeepFreeze = uFlags & lsfHighDeepFreeze;
575
577 (lowDeepFreeze && !lowFreeze) || (highDeepFreeze && !highFreeze);
578 }
579}
580
581bool
583 STTx const&,
584 TER const,
585 XRPAmount const,
586 ReadView const&,
587 beast::Journal const& j)
588{
590 return true;
591
592 JLOG(j.fatal()) << "Invariant failed: a trust line with deep freeze flag "
593 "without normal freeze was created";
594 return false;
595}
596
597//------------------------------------------------------------------------------
598
599void
601 bool isDelete,
602 std::shared_ptr<SLE const> const& before,
604{
605 /*
606 * A trust line freeze state alone doesn't determine if a transfer is
607 * frozen. The transfer must be examined "end-to-end" because both sides of
608 * the transfer may have different freeze states and freeze impact depends
609 * on the transfer direction. This is why first we need to track the
610 * transfers using IssuerChanges senders/receivers.
611 *
612 * Only in validateIssuerChanges, after we collected all changes can we
613 * determine if the transfer is valid.
614 */
615 if (!isValidEntry(before, after))
616 {
617 return;
618 }
619
620 auto const balanceChange = calculateBalanceChange(before, after, isDelete);
621 if (balanceChange.signum() == 0)
622 {
623 return;
624 }
625
626 recordBalanceChanges(after, balanceChange);
627}
628
629bool
631 STTx const& tx,
632 TER const ter,
633 XRPAmount const fee,
634 ReadView const& view,
635 beast::Journal const& j)
636{
637 /*
638 * We check this invariant regardless of deep freeze amendment status,
639 * allowing for detection and logging of potential issues even when the
640 * amendment is disabled.
641 *
642 * If an exploit that allows moving frozen assets is discovered,
643 * we can alert operators who monitor fatal messages and trigger assert in
644 * debug builds for an early warning.
645 *
646 * In an unlikely event that an exploit is found, this early detection
647 * enables encouraging the UNL to expedite deep freeze amendment activation
648 * or deploy hotfixes via new amendments. In case of a new amendment, we'd
649 * only have to change this line setting 'enforce' variable.
650 * enforce = view.rules().enabled(featureDeepFreeze) ||
651 * view.rules().enabled(fixFreezeExploit);
652 */
653 [[maybe_unused]] bool const enforce =
654 view.rules().enabled(featureDeepFreeze);
655
656 for (auto const& [issue, changes] : balanceChanges_)
657 {
658 auto const issuerSle = findIssuer(issue.account, view);
659 // It should be impossible for the issuer to not be found, but check
660 // just in case so rippled doesn't crash in release.
661 if (!issuerSle)
662 {
663 XRPL_ASSERT(
664 enforce,
665 "ripple::TransfersNotFrozen::finalize : enforce "
666 "invariant.");
667 if (enforce)
668 {
669 return false;
670 }
671 continue;
672 }
673
674 if (!validateIssuerChanges(issuerSle, changes, tx, j, enforce))
675 {
676 return false;
677 }
678 }
679
680 return true;
681}
682
683bool
685 std::shared_ptr<SLE const> const& before,
687{
688 // `after` can never be null, even if the trust line is deleted.
689 XRPL_ASSERT(
690 after, "ripple::TransfersNotFrozen::isValidEntry : valid after.");
691 if (!after)
692 {
693 return false;
694 }
695
696 if (after->getType() == ltACCOUNT_ROOT)
697 {
698 possibleIssuers_.emplace(after->at(sfAccount), after);
699 return false;
700 }
701
702 /* While LedgerEntryTypesMatch invariant also checks types, all invariants
703 * are processed regardless of previous failures.
704 *
705 * This type check is still necessary here because it prevents potential
706 * issues in subsequent processing.
707 */
708 return after->getType() == ltRIPPLE_STATE &&
709 (!before || before->getType() == ltRIPPLE_STATE);
710}
711
714 std::shared_ptr<SLE const> const& before,
716 bool isDelete)
717{
718 auto const getBalance = [](auto const& line, auto const& other, bool zero) {
719 STAmount amt =
720 line ? line->at(sfBalance) : other->at(sfBalance).zeroed();
721 return zero ? amt.zeroed() : amt;
722 };
723
724 /* Trust lines can be created dynamically by other transactions such as
725 * Payment and OfferCreate that cross offers. Such trust line won't be
726 * created frozen, but the sender might be, so the starting balance must be
727 * treated as zero.
728 */
729 auto const balanceBefore = getBalance(before, after, false);
730
731 /* Same as above, trust lines can be dynamically deleted, and for frozen
732 * trust lines, payments not involving the issuer must be blocked. This is
733 * achieved by treating the final balance as zero when isDelete=true to
734 * ensure frozen line restrictions are enforced even during deletion.
735 */
736 auto const balanceAfter = getBalance(after, before, isDelete);
737
738 return balanceAfter - balanceBefore;
739}
740
741void
743{
744 XRPL_ASSERT(
745 change.balanceChangeSign,
746 "ripple::TransfersNotFrozen::recordBalance : valid trustline "
747 "balance sign.");
748 auto& changes = balanceChanges_[issue];
749 if (change.balanceChangeSign < 0)
750 changes.senders.emplace_back(std::move(change));
751 else
752 changes.receivers.emplace_back(std::move(change));
753}
754
755void
758 STAmount const& balanceChange)
759{
760 auto const balanceChangeSign = balanceChange.signum();
761 auto const currency = after->at(sfBalance).getCurrency();
762
763 // Change from low account's perspective, which is trust line default
765 {currency, after->at(sfHighLimit).getIssuer()},
766 {after, balanceChangeSign});
767
768 // Change from high account's perspective, which reverses the sign.
770 {currency, after->at(sfLowLimit).getIssuer()},
771 {after, -balanceChangeSign});
772}
773
776{
777 if (auto it = possibleIssuers_.find(issuerID); it != possibleIssuers_.end())
778 {
779 return it->second;
780 }
781
782 return view.read(keylet::account(issuerID));
783}
784
785bool
787 std::shared_ptr<SLE const> const& issuer,
788 IssuerChanges const& changes,
789 STTx const& tx,
790 beast::Journal const& j,
791 bool enforce)
792{
793 if (!issuer)
794 {
795 return false;
796 }
797
798 bool const globalFreeze = issuer->isFlag(lsfGlobalFreeze);
799 if (changes.receivers.empty() || changes.senders.empty())
800 {
801 /* If there are no receivers, then the holder(s) are returning
802 * their tokens to the issuer. Likewise, if there are no
803 * senders, then the issuer is issuing tokens to the holder(s).
804 * This is allowed regardless of the issuer's freeze flags. (The
805 * holder may have contradicting freeze flags, but that will be
806 * checked when the holder is treated as issuer.)
807 */
808 return true;
809 }
810
811 for (auto const& actors : {changes.senders, changes.receivers})
812 {
813 for (auto const& change : actors)
814 {
815 bool const high = change.line->at(sfLowLimit).getIssuer() ==
816 issuer->at(sfAccount);
817
819 change, high, tx, j, enforce, globalFreeze))
820 {
821 return false;
822 }
823 }
824 }
825 return true;
826}
827
828bool
830 BalanceChange const& change,
831 bool high,
832 STTx const& tx,
833 beast::Journal const& j,
834 bool enforce,
835 bool globalFreeze)
836{
837 bool const freeze = change.balanceChangeSign < 0 &&
838 change.line->isFlag(high ? lsfLowFreeze : lsfHighFreeze);
839 bool const deepFreeze =
840 change.line->isFlag(high ? lsfLowDeepFreeze : lsfHighDeepFreeze);
841 bool const frozen = globalFreeze || deepFreeze || freeze;
842
843 bool const isAMMLine = change.line->isFlag(lsfAMMNode);
844
845 if (!frozen)
846 {
847 return true;
848 }
849
850 // AMMClawbacks are allowed to override some freeze rules
851 if ((!isAMMLine || globalFreeze) && tx.getTxnType() == ttAMM_CLAWBACK)
852 {
853 JLOG(j.debug()) << "Invariant check allowing funds to be moved "
854 << (change.balanceChangeSign > 0 ? "to" : "from")
855 << " a frozen trustline for AMMClawback "
856 << tx.getTransactionID();
857 return true;
858 }
859
860 JLOG(j.fatal()) << "Invariant failed: Attempting to move frozen funds for "
861 << tx.getTransactionID();
862 XRPL_ASSERT(
863 enforce,
864 "ripple::TransfersNotFrozen::validateFrozenState : enforce "
865 "invariant.");
866
867 if (enforce)
868 {
869 return false;
870 }
871
872 return true;
873}
874
875//------------------------------------------------------------------------------
876
877void
879 bool,
880 std::shared_ptr<SLE const> const& before,
882{
883 if (!before && after->getType() == ltACCOUNT_ROOT)
884 {
886 accountSeq_ = (*after)[sfSequence];
887 }
888}
889
890bool
892 STTx const& tx,
893 TER const result,
894 XRPAmount const,
895 ReadView const& view,
896 beast::Journal const& j)
897{
898 if (accountsCreated_ == 0)
899 return true;
900
901 if (accountsCreated_ > 1)
902 {
903 JLOG(j.fatal()) << "Invariant failed: multiple accounts "
904 "created in a single transaction";
905 return false;
906 }
907
908 // From this point on we know exactly one account was created.
909 if ((tx.getTxnType() == ttPAYMENT || tx.getTxnType() == ttAMM_CREATE ||
910 tx.getTxnType() == ttXCHAIN_ADD_CLAIM_ATTESTATION ||
911 tx.getTxnType() == ttXCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION) &&
912 result == tesSUCCESS)
913 {
914 std::uint32_t const startingSeq{
915 view.rules().enabled(featureDeletableAccounts) ? view.seq() : 1};
916
917 if (accountSeq_ != startingSeq)
918 {
919 JLOG(j.fatal()) << "Invariant failed: account created with "
920 "wrong starting sequence number";
921 return false;
922 }
923 return true;
924 }
925
926 JLOG(j.fatal()) << "Invariant failed: account root created "
927 "by a non-Payment, by an unsuccessful transaction, "
928 "or by AMM";
929 return false;
930}
931
932//------------------------------------------------------------------------------
933
934void
936 bool isDelete,
937 std::shared_ptr<SLE const> const& before,
939{
940 static constexpr uint256 const& pageBits = nft::pageMask;
941 static constexpr uint256 const accountBits = ~pageBits;
942
943 if ((before && before->getType() != ltNFTOKEN_PAGE) ||
944 (after && after->getType() != ltNFTOKEN_PAGE))
945 return;
946
947 auto check = [this, isDelete](std::shared_ptr<SLE const> const& sle) {
948 uint256 const account = sle->key() & accountBits;
949 uint256 const hiLimit = sle->key() & pageBits;
950 std::optional<uint256> const prev = (*sle)[~sfPreviousPageMin];
951
952 // Make sure that any page links...
953 // 1. Are properly associated with the owning account and
954 // 2. The page is correctly ordered between links.
955 if (prev)
956 {
957 if (account != (*prev & accountBits))
958 badLink_ = true;
959
960 if (hiLimit <= (*prev & pageBits))
961 badLink_ = true;
962 }
963
964 if (auto const next = (*sle)[~sfNextPageMin])
965 {
966 if (account != (*next & accountBits))
967 badLink_ = true;
968
969 if (hiLimit >= (*next & pageBits))
970 badLink_ = true;
971 }
972
973 {
974 auto const& nftokens = sle->getFieldArray(sfNFTokens);
975
976 // An NFTokenPage should never contain too many tokens or be empty.
977 if (std::size_t const nftokenCount = nftokens.size();
978 (!isDelete && nftokenCount == 0) ||
979 nftokenCount > dirMaxTokensPerPage)
980 invalidSize_ = true;
981
982 // If prev is valid, use it to establish a lower bound for
983 // page entries. If prev is not valid the lower bound is zero.
984 uint256 const loLimit =
985 prev ? *prev & pageBits : uint256(beast::zero);
986
987 // Also verify that all NFTokenIDs in the page are sorted.
988 uint256 loCmp = loLimit;
989 for (auto const& obj : nftokens)
990 {
991 uint256 const tokenID = obj[sfNFTokenID];
992 if (!nft::compareTokens(loCmp, tokenID))
993 badSort_ = true;
994 loCmp = tokenID;
995
996 // None of the NFTs on this page should belong on lower or
997 // higher pages.
998 if (uint256 const tokenPageBits = tokenID & pageBits;
999 tokenPageBits < loLimit || tokenPageBits >= hiLimit)
1000 badEntry_ = true;
1001
1002 if (auto uri = obj[~sfURI]; uri && uri->empty())
1003 badURI_ = true;
1004 }
1005 }
1006 };
1007
1008 if (before)
1009 {
1010 check(before);
1011
1012 // While an account's NFToken directory contains any NFTokens, the last
1013 // NFTokenPage (with 96 bits of 1 in the low part of the index) should
1014 // never be deleted.
1015 if (isDelete && (before->key() & nft::pageMask) == nft::pageMask &&
1016 before->isFieldPresent(sfPreviousPageMin))
1017 {
1018 deletedFinalPage_ = true;
1019 }
1020 }
1021
1022 if (after)
1023 check(after);
1024
1025 if (!isDelete && before && after)
1026 {
1027 // If the NFTokenPage
1028 // 1. Has a NextMinPage field in before, but loses it in after, and
1029 // 2. This is not the last page in the directory
1030 // Then we have identified a corruption in the links between the
1031 // NFToken pages in the NFToken directory.
1032 if ((before->key() & nft::pageMask) != nft::pageMask &&
1033 before->isFieldPresent(sfNextPageMin) &&
1034 !after->isFieldPresent(sfNextPageMin))
1035 {
1036 deletedLink_ = true;
1037 }
1038 }
1039}
1040
1041bool
1043 STTx const& tx,
1044 TER const result,
1045 XRPAmount const,
1046 ReadView const& view,
1047 beast::Journal const& j)
1048{
1049 if (badLink_)
1050 {
1051 JLOG(j.fatal()) << "Invariant failed: NFT page is improperly linked.";
1052 return false;
1053 }
1054
1055 if (badEntry_)
1056 {
1057 JLOG(j.fatal()) << "Invariant failed: NFT found in incorrect page.";
1058 return false;
1059 }
1060
1061 if (badSort_)
1062 {
1063 JLOG(j.fatal()) << "Invariant failed: NFTs on page are not sorted.";
1064 return false;
1065 }
1066
1067 if (badURI_)
1068 {
1069 JLOG(j.fatal()) << "Invariant failed: NFT contains empty URI.";
1070 return false;
1071 }
1072
1073 if (invalidSize_)
1074 {
1075 JLOG(j.fatal()) << "Invariant failed: NFT page has invalid size.";
1076 return false;
1077 }
1078
1079 if (view.rules().enabled(fixNFTokenPageLinks))
1080 {
1082 {
1083 JLOG(j.fatal()) << "Invariant failed: Last NFT page deleted with "
1084 "non-empty directory.";
1085 return false;
1086 }
1087 if (deletedLink_)
1088 {
1089 JLOG(j.fatal()) << "Invariant failed: Lost NextMinPage link.";
1090 return false;
1091 }
1092 }
1093
1094 return true;
1095}
1096
1097//------------------------------------------------------------------------------
1098void
1100 bool,
1101 std::shared_ptr<SLE const> const& before,
1103{
1104 if (before && before->getType() == ltACCOUNT_ROOT)
1105 {
1106 beforeMintedTotal += (*before)[~sfMintedNFTokens].value_or(0);
1107 beforeBurnedTotal += (*before)[~sfBurnedNFTokens].value_or(0);
1108 }
1109
1110 if (after && after->getType() == ltACCOUNT_ROOT)
1111 {
1112 afterMintedTotal += (*after)[~sfMintedNFTokens].value_or(0);
1113 afterBurnedTotal += (*after)[~sfBurnedNFTokens].value_or(0);
1114 }
1115}
1116
1117bool
1119 STTx const& tx,
1120 TER const result,
1121 XRPAmount const,
1122 ReadView const& view,
1123 beast::Journal const& j)
1124{
1125 if (TxType const txType = tx.getTxnType();
1126 txType != ttNFTOKEN_MINT && txType != ttNFTOKEN_BURN)
1127 {
1129 {
1130 JLOG(j.fatal()) << "Invariant failed: the number of minted tokens "
1131 "changed without a mint transaction!";
1132 return false;
1133 }
1134
1136 {
1137 JLOG(j.fatal()) << "Invariant failed: the number of burned tokens "
1138 "changed without a burn transaction!";
1139 return false;
1140 }
1141
1142 return true;
1143 }
1144
1145 if (tx.getTxnType() == ttNFTOKEN_MINT)
1146 {
1147 if (result == tesSUCCESS && beforeMintedTotal >= afterMintedTotal)
1148 {
1149 JLOG(j.fatal())
1150 << "Invariant failed: successful minting didn't increase "
1151 "the number of minted tokens.";
1152 return false;
1153 }
1154
1155 if (result != tesSUCCESS && beforeMintedTotal != afterMintedTotal)
1156 {
1157 JLOG(j.fatal()) << "Invariant failed: failed minting changed the "
1158 "number of minted tokens.";
1159 return false;
1160 }
1161
1163 {
1164 JLOG(j.fatal())
1165 << "Invariant failed: minting changed the number of "
1166 "burned tokens.";
1167 return false;
1168 }
1169 }
1170
1171 if (tx.getTxnType() == ttNFTOKEN_BURN)
1172 {
1173 if (result == tesSUCCESS)
1174 {
1176 {
1177 JLOG(j.fatal())
1178 << "Invariant failed: successful burning didn't increase "
1179 "the number of burned tokens.";
1180 return false;
1181 }
1182 }
1183
1184 if (result != tesSUCCESS && beforeBurnedTotal != afterBurnedTotal)
1185 {
1186 JLOG(j.fatal()) << "Invariant failed: failed burning changed the "
1187 "number of burned tokens.";
1188 return false;
1189 }
1190
1192 {
1193 JLOG(j.fatal())
1194 << "Invariant failed: burning changed the number of "
1195 "minted tokens.";
1196 return false;
1197 }
1198 }
1199
1200 return true;
1201}
1202
1203//------------------------------------------------------------------------------
1204
1205void
1207 bool,
1208 std::shared_ptr<SLE const> const& before,
1210{
1211 if (before && before->getType() == ltRIPPLE_STATE)
1213
1214 if (before && before->getType() == ltMPTOKEN)
1216}
1217
1218bool
1220 STTx const& tx,
1221 TER const result,
1222 XRPAmount const,
1223 ReadView const& view,
1224 beast::Journal const& j)
1225{
1226 if (tx.getTxnType() != ttCLAWBACK)
1227 return true;
1228
1229 if (result == tesSUCCESS)
1230 {
1231 if (trustlinesChanged > 1)
1232 {
1233 JLOG(j.fatal())
1234 << "Invariant failed: more than one trustline changed.";
1235 return false;
1236 }
1237
1238 if (mptokensChanged > 1)
1239 {
1240 JLOG(j.fatal())
1241 << "Invariant failed: more than one mptokens changed.";
1242 return false;
1243 }
1244
1245 if (trustlinesChanged == 1)
1246 {
1247 AccountID const issuer = tx.getAccountID(sfAccount);
1248 STAmount const& amount = tx.getFieldAmount(sfAmount);
1249 AccountID const& holder = amount.getIssuer();
1250 STAmount const holderBalance = accountHolds(
1251 view, holder, amount.getCurrency(), issuer, fhIGNORE_FREEZE, j);
1252
1253 if (holderBalance.signum() < 0)
1254 {
1255 JLOG(j.fatal())
1256 << "Invariant failed: trustline balance is negative";
1257 return false;
1258 }
1259 }
1260 }
1261 else
1262 {
1263 if (trustlinesChanged != 0)
1264 {
1265 JLOG(j.fatal()) << "Invariant failed: some trustlines were changed "
1266 "despite failure of the transaction.";
1267 return false;
1268 }
1269
1270 if (mptokensChanged != 0)
1271 {
1272 JLOG(j.fatal()) << "Invariant failed: some mptokens were changed "
1273 "despite failure of the transaction.";
1274 return false;
1275 }
1276 }
1277
1278 return true;
1279}
1280
1281//------------------------------------------------------------------------------
1282
1283void
1285 bool isDelete,
1286 std::shared_ptr<SLE const> const& before,
1288{
1289 if (after && after->getType() == ltMPTOKEN_ISSUANCE)
1290 {
1291 if (isDelete)
1293 else if (!before)
1295 }
1296
1297 if (after && after->getType() == ltMPTOKEN)
1298 {
1299 if (isDelete)
1301 else if (!before)
1303 }
1304}
1305
1306bool
1308 STTx const& tx,
1309 TER const result,
1310 XRPAmount const _fee,
1311 ReadView const& _view,
1312 beast::Journal const& j)
1313{
1314 if (result == tesSUCCESS)
1315 {
1316 if (tx.getTxnType() == ttMPTOKEN_ISSUANCE_CREATE)
1317 {
1318 if (mptIssuancesCreated_ == 0)
1319 {
1320 JLOG(j.fatal()) << "Invariant failed: MPT issuance creation "
1321 "succeeded without creating a MPT issuance";
1322 }
1323 else if (mptIssuancesDeleted_ != 0)
1324 {
1325 JLOG(j.fatal()) << "Invariant failed: MPT issuance creation "
1326 "succeeded while removing MPT issuances";
1327 }
1328 else if (mptIssuancesCreated_ > 1)
1329 {
1330 JLOG(j.fatal()) << "Invariant failed: MPT issuance creation "
1331 "succeeded but created multiple issuances";
1332 }
1333
1334 return mptIssuancesCreated_ == 1 && mptIssuancesDeleted_ == 0;
1335 }
1336
1337 if (tx.getTxnType() == ttMPTOKEN_ISSUANCE_DESTROY)
1338 {
1339 if (mptIssuancesDeleted_ == 0)
1340 {
1341 JLOG(j.fatal()) << "Invariant failed: MPT issuance deletion "
1342 "succeeded without removing a MPT issuance";
1343 }
1344 else if (mptIssuancesCreated_ > 0)
1345 {
1346 JLOG(j.fatal()) << "Invariant failed: MPT issuance deletion "
1347 "succeeded while creating MPT issuances";
1348 }
1349 else if (mptIssuancesDeleted_ > 1)
1350 {
1351 JLOG(j.fatal()) << "Invariant failed: MPT issuance deletion "
1352 "succeeded but deleted multiple issuances";
1353 }
1354
1355 return mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 1;
1356 }
1357
1358 if (tx.getTxnType() == ttMPTOKEN_AUTHORIZE)
1359 {
1360 bool const submittedByIssuer = tx.isFieldPresent(sfHolder);
1361
1362 if (mptIssuancesCreated_ > 0)
1363 {
1364 JLOG(j.fatal()) << "Invariant failed: MPT authorize "
1365 "succeeded but created MPT issuances";
1366 return false;
1367 }
1368 else if (mptIssuancesDeleted_ > 0)
1369 {
1370 JLOG(j.fatal()) << "Invariant failed: MPT authorize "
1371 "succeeded but deleted issuances";
1372 return false;
1373 }
1374 else if (
1375 submittedByIssuer &&
1377 {
1378 JLOG(j.fatal())
1379 << "Invariant failed: MPT authorize submitted by issuer "
1380 "succeeded but created/deleted mptokens";
1381 return false;
1382 }
1383 else if (
1384 !submittedByIssuer &&
1386 {
1387 // if the holder submitted this tx, then a mptoken must be
1388 // either created or deleted.
1389 JLOG(j.fatal())
1390 << "Invariant failed: MPT authorize submitted by holder "
1391 "succeeded but created/deleted bad number of mptokens";
1392 return false;
1393 }
1394
1395 return true;
1396 }
1397
1398 if (tx.getTxnType() == ttMPTOKEN_ISSUANCE_SET)
1399 {
1400 if (mptIssuancesDeleted_ > 0)
1401 {
1402 JLOG(j.fatal()) << "Invariant failed: MPT issuance set "
1403 "succeeded while removing MPT issuances";
1404 }
1405 else if (mptIssuancesCreated_ > 0)
1406 {
1407 JLOG(j.fatal()) << "Invariant failed: MPT issuance set "
1408 "succeeded while creating MPT issuances";
1409 }
1410 else if (mptokensDeleted_ > 0)
1411 {
1412 JLOG(j.fatal()) << "Invariant failed: MPT issuance set "
1413 "succeeded while removing MPTokens";
1414 }
1415 else if (mptokensCreated_ > 0)
1416 {
1417 JLOG(j.fatal()) << "Invariant failed: MPT issuance set "
1418 "succeeded while creating MPTokens";
1419 }
1420
1421 return mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 0 &&
1423 }
1424 }
1425
1426 if (mptIssuancesCreated_ != 0)
1427 {
1428 JLOG(j.fatal()) << "Invariant failed: a MPT issuance was created";
1429 }
1430 else if (mptIssuancesDeleted_ != 0)
1431 {
1432 JLOG(j.fatal()) << "Invariant failed: a MPT issuance was deleted";
1433 }
1434 else if (mptokensCreated_ != 0)
1435 {
1436 JLOG(j.fatal()) << "Invariant failed: a MPToken was created";
1437 }
1438 else if (mptokensDeleted_ != 0)
1439 {
1440 JLOG(j.fatal()) << "Invariant failed: a MPToken was deleted";
1441 }
1442
1443 return mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 0 &&
1445}
1446
1447//------------------------------------------------------------------------------
1448
1449void
1451 bool,
1452 std::shared_ptr<SLE const> const& before,
1454{
1455 if (before && before->getType() != ltPERMISSIONED_DOMAIN)
1456 return;
1457 if (after && after->getType() != ltPERMISSIONED_DOMAIN)
1458 return;
1459
1460 auto check = [](SleStatus& sleStatus,
1461 std::shared_ptr<SLE const> const& sle) {
1462 auto const& credentials = sle->getFieldArray(sfAcceptedCredentials);
1463 sleStatus.credentialsSize_ = credentials.size();
1464 auto const sorted = credentials::makeSorted(credentials);
1465 sleStatus.isUnique_ = !sorted.empty();
1466
1467 // If array have duplicates then all the other checks are invalid
1468 sleStatus.isSorted_ = false;
1469
1470 if (sleStatus.isUnique_)
1471 {
1472 unsigned i = 0;
1473 for (auto const& cred : sorted)
1474 {
1475 auto const& credTx = credentials[i++];
1476 sleStatus.isSorted_ = (cred.first == credTx[sfIssuer]) &&
1477 (cred.second == credTx[sfCredentialType]);
1478 if (!sleStatus.isSorted_)
1479 break;
1480 }
1481 }
1482 };
1483
1484 if (before)
1485 {
1486 sleStatus_[0] = SleStatus();
1487 check(*sleStatus_[0], after);
1488 }
1489
1490 if (after)
1491 {
1492 sleStatus_[1] = SleStatus();
1493 check(*sleStatus_[1], after);
1494 }
1495}
1496
1497bool
1499 STTx const& tx,
1500 TER const result,
1501 XRPAmount const,
1502 ReadView const& view,
1503 beast::Journal const& j)
1504{
1505 if (tx.getTxnType() != ttPERMISSIONED_DOMAIN_SET || result != tesSUCCESS)
1506 return true;
1507
1508 auto check = [](SleStatus const& sleStatus, beast::Journal const& j) {
1509 if (!sleStatus.credentialsSize_)
1510 {
1511 JLOG(j.fatal()) << "Invariant failed: permissioned domain with "
1512 "no rules.";
1513 return false;
1514 }
1515
1516 if (sleStatus.credentialsSize_ >
1518 {
1519 JLOG(j.fatal()) << "Invariant failed: permissioned domain bad "
1520 "credentials size "
1521 << sleStatus.credentialsSize_;
1522 return false;
1523 }
1524
1525 if (!sleStatus.isUnique_)
1526 {
1527 JLOG(j.fatal())
1528 << "Invariant failed: permissioned domain credentials "
1529 "aren't unique";
1530 return false;
1531 }
1532
1533 if (!sleStatus.isSorted_)
1534 {
1535 JLOG(j.fatal())
1536 << "Invariant failed: permissioned domain credentials "
1537 "aren't sorted";
1538 return false;
1539 }
1540
1541 return true;
1542 };
1543
1544 return (sleStatus_[0] ? check(*sleStatus_[0], j) : true) &&
1545 (sleStatus_[1] ? check(*sleStatus_[1], j) : true);
1546}
1547
1548} // 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:493
XRPAmount xrp() const
Definition: STAmount.cpp:305
int signum() const noexcept
Definition: STAmount.h:505
AccountID const & getIssuer() const
Definition: STAmount.h:499
bool native() const noexcept
Definition: STAmount.h:449
STAmount zeroed() const
Returns a zero value with the same issuer and currency.
Definition: STAmount.h:511
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:182
uint256 getTransactionID() const
Definition: STTx.h:194
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:438
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition: Indexes.cpp:176
Keylet nftpage_min(AccountID const &owner)
NFT page keylets.
Definition: Indexes.cpp:395
Keylet nftpage_max(AccountID const &owner)
A keylet for the owner's last possible NFT page.
Definition: Indexes.cpp:403
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:76
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
@ lsfHighFreeze
@ 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:372
@ tesSUCCESS
Definition: TER.h:242
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j)
Definition: View.cpp:309
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition: View.cpp:2129
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)