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