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/AMMHelpers.h>
21#include <xrpld/app/misc/AMMUtils.h>
22#include <xrpld/app/misc/CredentialHelpers.h>
23#include <xrpld/app/tx/detail/InvariantCheck.h>
24#include <xrpld/app/tx/detail/NFTokenUtils.h>
25#include <xrpld/app/tx/detail/PermissionedDomainSet.h>
26#include <xrpld/ledger/ReadView.h>
27#include <xrpld/ledger/View.h>
28
29#include <xrpl/basics/Log.h>
30#include <xrpl/protocol/Feature.h>
31#include <xrpl/protocol/FeeUnits.h>
32#include <xrpl/protocol/STArray.h>
33#include <xrpl/protocol/SystemParameters.h>
34#include <xrpl/protocol/TxFormats.h>
35#include <xrpl/protocol/nftPageMask.h>
36
37namespace ripple {
38
39void
41 bool,
44{
45 // nothing to do
46}
47
48bool
50 STTx const& tx,
51 TER const,
52 XRPAmount const fee,
53 ReadView const&,
54 beast::Journal const& j)
55{
56 // We should never charge a negative fee
57 if (fee.drops() < 0)
58 {
59 JLOG(j.fatal()) << "Invariant failed: fee paid was negative: "
60 << fee.drops();
61 return false;
62 }
63
64 // We should never charge a fee that's greater than or equal to the
65 // entire XRP supply.
66 if (fee >= INITIAL_XRP)
67 {
68 JLOG(j.fatal()) << "Invariant failed: fee paid exceeds system limit: "
69 << fee.drops();
70 return false;
71 }
72
73 // We should never charge more for a transaction than the transaction
74 // authorizes. It's possible to charge less in some circumstances.
75 if (fee > tx.getFieldAmount(sfFee).xrp())
76 {
77 JLOG(j.fatal()) << "Invariant failed: fee paid is " << fee.drops()
78 << " exceeds fee specified in transaction.";
79 return false;
80 }
81
82 return true;
83}
84
85//------------------------------------------------------------------------------
86
87void
89 bool isDelete,
90 std::shared_ptr<SLE const> const& before,
92{
93 /* We go through all modified ledger entries, looking only at account roots,
94 * escrow payments, and payment channels. We remove from the total any
95 * previous XRP values and add to the total any new XRP values. The net
96 * balance of a payment channel is computed from two fields (amount and
97 * balance) and deletions are ignored for paychan and escrow because the
98 * amount fields have not been adjusted for those in the case of deletion.
99 */
100 if (before)
101 {
102 switch (before->getType())
103 {
104 case ltACCOUNT_ROOT:
105 drops_ -= (*before)[sfBalance].xrp().drops();
106 break;
107 case ltPAYCHAN:
108 drops_ -=
109 ((*before)[sfAmount] - (*before)[sfBalance]).xrp().drops();
110 break;
111 case ltESCROW:
112 if (isXRP((*before)[sfAmount]))
113 drops_ -= (*before)[sfAmount].xrp().drops();
114 break;
115 default:
116 break;
117 }
118 }
119
120 if (after)
121 {
122 switch (after->getType())
123 {
124 case ltACCOUNT_ROOT:
125 drops_ += (*after)[sfBalance].xrp().drops();
126 break;
127 case ltPAYCHAN:
128 if (!isDelete)
129 drops_ += ((*after)[sfAmount] - (*after)[sfBalance])
130 .xrp()
131 .drops();
132 break;
133 case ltESCROW:
134 if (!isDelete && isXRP((*after)[sfAmount]))
135 drops_ += (*after)[sfAmount].xrp().drops();
136 break;
137 default:
138 break;
139 }
140 }
141}
142
143bool
145 STTx const& tx,
146 TER const,
147 XRPAmount const fee,
148 ReadView const&,
149 beast::Journal const& j)
150{
151 // The net change should never be positive, as this would mean that the
152 // transaction created XRP out of thin air. That's not possible.
153 if (drops_ > 0)
154 {
155 JLOG(j.fatal()) << "Invariant failed: XRP net change was positive: "
156 << drops_;
157 return false;
158 }
159
160 // The negative of the net change should be equal to actual fee charged.
161 if (-drops_ != fee.drops())
162 {
163 JLOG(j.fatal()) << "Invariant failed: XRP net change of " << drops_
164 << " doesn't match fee " << fee.drops();
165 return false;
166 }
167
168 return true;
169}
170
171//------------------------------------------------------------------------------
172
173void
175 bool,
176 std::shared_ptr<SLE const> const& before,
178{
179 auto isBad = [](STAmount const& balance) {
180 if (!balance.native())
181 return true;
182
183 auto const drops = balance.xrp();
184
185 // Can't have more than the number of drops instantiated
186 // in the genesis ledger.
187 if (drops > INITIAL_XRP)
188 return true;
189
190 // Can't have a negative balance (0 is OK)
191 if (drops < XRPAmount{0})
192 return true;
193
194 return false;
195 };
196
197 if (before && before->getType() == ltACCOUNT_ROOT)
198 bad_ |= isBad((*before)[sfBalance]);
199
200 if (after && after->getType() == ltACCOUNT_ROOT)
201 bad_ |= isBad((*after)[sfBalance]);
202}
203
204bool
206 STTx const&,
207 TER const,
208 XRPAmount const,
209 ReadView const&,
210 beast::Journal const& j)
211{
212 if (bad_)
213 {
214 JLOG(j.fatal()) << "Invariant failed: incorrect account XRP balance";
215 return false;
216 }
217
218 return true;
219}
220
221//------------------------------------------------------------------------------
222
223void
225 bool isDelete,
226 std::shared_ptr<SLE const> const& before,
228{
229 auto isBad = [](STAmount const& pays, STAmount const& gets) {
230 // An offer should never be negative
231 if (pays < beast::zero)
232 return true;
233
234 if (gets < beast::zero)
235 return true;
236
237 // Can't have an XRP to XRP offer:
238 return pays.native() && gets.native();
239 };
240
241 if (before && before->getType() == ltOFFER)
242 bad_ |= isBad((*before)[sfTakerPays], (*before)[sfTakerGets]);
243
244 if (after && after->getType() == ltOFFER)
245 bad_ |= isBad((*after)[sfTakerPays], (*after)[sfTakerGets]);
246}
247
248bool
250 STTx const&,
251 TER const,
252 XRPAmount const,
253 ReadView const&,
254 beast::Journal const& j)
255{
256 if (bad_)
257 {
258 JLOG(j.fatal()) << "Invariant failed: offer with a bad amount";
259 return false;
260 }
261
262 return true;
263}
264
265//------------------------------------------------------------------------------
266
267void
269 bool isDelete,
270 std::shared_ptr<SLE const> const& before,
272{
273 auto isBad = [](STAmount const& amount) {
274 // XRP case
275 if (amount.native())
276 {
277 if (amount.xrp() <= XRPAmount{0})
278 return true;
279
280 if (amount.xrp() >= INITIAL_XRP)
281 return true;
282 }
283 else
284 {
285 // IOU case
286 if (amount.holds<Issue>())
287 {
288 if (amount <= beast::zero)
289 return true;
290
291 if (badCurrency() == amount.getCurrency())
292 return true;
293 }
294
295 // MPT case
296 if (amount.holds<MPTIssue>())
297 {
298 if (amount <= beast::zero)
299 return true;
300
301 if (amount.mpt() > MPTAmount{maxMPTokenAmount})
302 return true; // LCOV_EXCL_LINE
303 }
304 }
305 return false;
306 };
307
308 if (before && before->getType() == ltESCROW)
309 bad_ |= isBad((*before)[sfAmount]);
310
311 if (after && after->getType() == ltESCROW)
312 bad_ |= isBad((*after)[sfAmount]);
313
314 auto checkAmount = [this](std::int64_t amount) {
315 if (amount > maxMPTokenAmount || amount < 0)
316 bad_ = true;
317 };
318
319 if (after && after->getType() == ltMPTOKEN_ISSUANCE)
320 {
321 auto const outstanding = (*after)[sfOutstandingAmount];
322 checkAmount(outstanding);
323 if (auto const locked = (*after)[~sfLockedAmount])
324 {
325 checkAmount(*locked);
326 bad_ = outstanding < *locked;
327 }
328 }
329
330 if (after && after->getType() == ltMPTOKEN)
331 {
332 auto const mptAmount = (*after)[sfMPTAmount];
333 checkAmount(mptAmount);
334 if (auto const locked = (*after)[~sfLockedAmount])
335 {
336 checkAmount(*locked);
337 }
338 }
339}
340
341bool
343 STTx const& txn,
344 TER const,
345 XRPAmount const,
346 ReadView const& rv,
347 beast::Journal const& j)
348{
349 if (bad_)
350 {
351 JLOG(j.fatal()) << "Invariant failed: escrow specifies invalid amount";
352 return false;
353 }
354
355 return true;
356}
357
358//------------------------------------------------------------------------------
359
360void
362 bool isDelete,
363 std::shared_ptr<SLE const> const& before,
365{
366 if (isDelete && before && before->getType() == ltACCOUNT_ROOT)
368}
369
370bool
372 STTx const& tx,
373 TER const result,
374 XRPAmount const,
375 ReadView const&,
376 beast::Journal const& j)
377{
378 // AMM account root can be deleted as the result of AMM withdraw/delete
379 // transaction when the total AMM LP Tokens balance goes to 0.
380 // A successful AccountDelete or AMMDelete MUST delete exactly
381 // one account root.
382 if ((tx.getTxnType() == ttACCOUNT_DELETE ||
383 tx.getTxnType() == ttAMM_DELETE ||
384 tx.getTxnType() == ttVAULT_DELETE) &&
385 result == tesSUCCESS)
386 {
387 if (accountsDeleted_ == 1)
388 return true;
389
390 if (accountsDeleted_ == 0)
391 JLOG(j.fatal()) << "Invariant failed: account deletion "
392 "succeeded without deleting an account";
393 else
394 JLOG(j.fatal()) << "Invariant failed: account deletion "
395 "succeeded but deleted multiple accounts!";
396 return false;
397 }
398
399 // A successful AMMWithdraw/AMMClawback MAY delete one account root
400 // when the total AMM LP Tokens balance goes to 0. Not every AMM withdraw
401 // deletes the AMM account, accountsDeleted_ is set if it is deleted.
402 if ((tx.getTxnType() == ttAMM_WITHDRAW ||
403 tx.getTxnType() == ttAMM_CLAWBACK) &&
404 result == tesSUCCESS && accountsDeleted_ == 1)
405 return true;
406
407 if (accountsDeleted_ == 0)
408 return true;
409
410 JLOG(j.fatal()) << "Invariant failed: an account root was deleted";
411 return false;
412}
413
414//------------------------------------------------------------------------------
415
416void
418 bool isDelete,
419 std::shared_ptr<SLE const> const& before,
421{
422 if (isDelete && before && before->getType() == ltACCOUNT_ROOT)
423 accountsDeleted_.emplace_back(before);
424}
425
426bool
428 STTx const& tx,
429 TER const result,
430 XRPAmount const,
431 ReadView const& view,
432 beast::Journal const& j)
433{
434 // Always check for objects in the ledger, but to prevent differing
435 // transaction processing results, however unlikely, only fail if the
436 // feature is enabled. Enabled, or not, though, a fatal-level message will
437 // be logged
438 [[maybe_unused]] bool const enforce =
439 view.rules().enabled(featureInvariantsV1_1);
440
441 auto const objectExists = [&view, enforce, &j](auto const& keylet) {
442 (void)enforce;
443 if (auto const sle = view.read(keylet))
444 {
445 // Finding the object is bad
446 auto const typeName = [&sle]() {
447 auto item =
448 LedgerFormats::getInstance().findByType(sle->getType());
449
450 if (item != nullptr)
451 return item->getName();
452 return std::to_string(sle->getType());
453 }();
454
455 JLOG(j.fatal())
456 << "Invariant failed: account deletion left behind a "
457 << typeName << " object";
458 XRPL_ASSERT(
459 enforce,
460 "ripple::AccountRootsDeletedClean::finalize::objectExists : "
461 "account deletion left no objects behind");
462 return true;
463 }
464 return false;
465 };
466
467 for (auto const& accountSLE : accountsDeleted_)
468 {
469 auto const accountID = accountSLE->getAccountID(sfAccount);
470 // Simple types
471 for (auto const& [keyletfunc, _, __] : directAccountKeylets)
472 {
473 if (objectExists(std::invoke(keyletfunc, accountID)) && enforce)
474 return false;
475 }
476
477 {
478 // NFT pages. ntfpage_min and nftpage_max were already explicitly
479 // checked above as entries in directAccountKeylets. This uses
480 // view.succ() to check for any NFT pages in between the two
481 // endpoints.
482 Keylet const first = keylet::nftpage_min(accountID);
483 Keylet const last = keylet::nftpage_max(accountID);
484
485 std::optional<uint256> key = view.succ(first.key, last.key.next());
486
487 // current page
488 if (key && objectExists(Keylet{ltNFTOKEN_PAGE, *key}) && enforce)
489 return false;
490 }
491
492 // Keys directly stored in the AccountRoot object
493 if (auto const ammKey = accountSLE->at(~sfAMMID))
494 {
495 if (objectExists(keylet::amm(*ammKey)) && enforce)
496 return false;
497 }
498 }
499
500 return true;
501}
502
503//------------------------------------------------------------------------------
504
505void
507 bool,
508 std::shared_ptr<SLE const> const& before,
510{
511 if (before && after && before->getType() != after->getType())
512 typeMismatch_ = true;
513
514 if (after)
515 {
516 switch (after->getType())
517 {
518 case ltACCOUNT_ROOT:
519 case ltDELEGATE:
520 case ltDIR_NODE:
521 case ltRIPPLE_STATE:
522 case ltTICKET:
523 case ltSIGNER_LIST:
524 case ltOFFER:
525 case ltLEDGER_HASHES:
526 case ltAMENDMENTS:
527 case ltFEE_SETTINGS:
528 case ltESCROW:
529 case ltPAYCHAN:
530 case ltCHECK:
531 case ltDEPOSIT_PREAUTH:
532 case ltNEGATIVE_UNL:
533 case ltNFTOKEN_PAGE:
534 case ltNFTOKEN_OFFER:
535 case ltAMM:
536 case ltBRIDGE:
537 case ltXCHAIN_OWNED_CLAIM_ID:
538 case ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID:
539 case ltDID:
540 case ltORACLE:
541 case ltMPTOKEN_ISSUANCE:
542 case ltMPTOKEN:
543 case ltCREDENTIAL:
544 case ltPERMISSIONED_DOMAIN:
545 case ltVAULT:
546 break;
547 default:
548 invalidTypeAdded_ = true;
549 break;
550 }
551 }
552}
553
554bool
556 STTx const&,
557 TER const,
558 XRPAmount const,
559 ReadView const&,
560 beast::Journal const& j)
561{
562 if ((!typeMismatch_) && (!invalidTypeAdded_))
563 return true;
564
565 if (typeMismatch_)
566 {
567 JLOG(j.fatal()) << "Invariant failed: ledger entry type mismatch";
568 }
569
571 {
572 JLOG(j.fatal()) << "Invariant failed: invalid ledger entry type added";
573 }
574
575 return false;
576}
577
578//------------------------------------------------------------------------------
579
580void
582 bool,
585{
586 if (after && after->getType() == ltRIPPLE_STATE)
587 {
588 // checking the issue directly here instead of
589 // relying on .native() just in case native somehow
590 // were systematically incorrect
592 after->getFieldAmount(sfLowLimit).issue() == xrpIssue() ||
593 after->getFieldAmount(sfHighLimit).issue() == xrpIssue();
594 }
595}
596
597bool
599 STTx const&,
600 TER const,
601 XRPAmount const,
602 ReadView const&,
603 beast::Journal const& j)
604{
605 if (!xrpTrustLine_)
606 return true;
607
608 JLOG(j.fatal()) << "Invariant failed: an XRP trust line was created";
609 return false;
610}
611
612//------------------------------------------------------------------------------
613
614void
616 bool,
619{
620 if (after && after->getType() == ltRIPPLE_STATE)
621 {
622 std::uint32_t const uFlags = after->getFieldU32(sfFlags);
623 bool const lowFreeze = uFlags & lsfLowFreeze;
624 bool const lowDeepFreeze = uFlags & lsfLowDeepFreeze;
625
626 bool const highFreeze = uFlags & lsfHighFreeze;
627 bool const highDeepFreeze = uFlags & lsfHighDeepFreeze;
628
630 (lowDeepFreeze && !lowFreeze) || (highDeepFreeze && !highFreeze);
631 }
632}
633
634bool
636 STTx const&,
637 TER const,
638 XRPAmount const,
639 ReadView const&,
640 beast::Journal const& j)
641{
643 return true;
644
645 JLOG(j.fatal()) << "Invariant failed: a trust line with deep freeze flag "
646 "without normal freeze was created";
647 return false;
648}
649
650//------------------------------------------------------------------------------
651
652void
654 bool isDelete,
655 std::shared_ptr<SLE const> const& before,
657{
658 /*
659 * A trust line freeze state alone doesn't determine if a transfer is
660 * frozen. The transfer must be examined "end-to-end" because both sides of
661 * the transfer may have different freeze states and freeze impact depends
662 * on the transfer direction. This is why first we need to track the
663 * transfers using IssuerChanges senders/receivers.
664 *
665 * Only in validateIssuerChanges, after we collected all changes can we
666 * determine if the transfer is valid.
667 */
668 if (!isValidEntry(before, after))
669 {
670 return;
671 }
672
673 auto const balanceChange = calculateBalanceChange(before, after, isDelete);
674 if (balanceChange.signum() == 0)
675 {
676 return;
677 }
678
679 recordBalanceChanges(after, balanceChange);
680}
681
682bool
684 STTx const& tx,
685 TER const ter,
686 XRPAmount const fee,
687 ReadView const& view,
688 beast::Journal const& j)
689{
690 /*
691 * We check this invariant regardless of deep freeze amendment status,
692 * allowing for detection and logging of potential issues even when the
693 * amendment is disabled.
694 *
695 * If an exploit that allows moving frozen assets is discovered,
696 * we can alert operators who monitor fatal messages and trigger assert in
697 * debug builds for an early warning.
698 *
699 * In an unlikely event that an exploit is found, this early detection
700 * enables encouraging the UNL to expedite deep freeze amendment activation
701 * or deploy hotfixes via new amendments. In case of a new amendment, we'd
702 * only have to change this line setting 'enforce' variable.
703 * enforce = view.rules().enabled(featureDeepFreeze) ||
704 * view.rules().enabled(fixFreezeExploit);
705 */
706 [[maybe_unused]] bool const enforce =
707 view.rules().enabled(featureDeepFreeze);
708
709 for (auto const& [issue, changes] : balanceChanges_)
710 {
711 auto const issuerSle = findIssuer(issue.account, view);
712 // It should be impossible for the issuer to not be found, but check
713 // just in case so rippled doesn't crash in release.
714 if (!issuerSle)
715 {
716 XRPL_ASSERT(
717 enforce,
718 "ripple::TransfersNotFrozen::finalize : enforce "
719 "invariant.");
720 if (enforce)
721 {
722 return false;
723 }
724 continue;
725 }
726
727 if (!validateIssuerChanges(issuerSle, changes, tx, j, enforce))
728 {
729 return false;
730 }
731 }
732
733 return true;
734}
735
736bool
738 std::shared_ptr<SLE const> const& before,
740{
741 // `after` can never be null, even if the trust line is deleted.
742 XRPL_ASSERT(
743 after, "ripple::TransfersNotFrozen::isValidEntry : valid after.");
744 if (!after)
745 {
746 return false;
747 }
748
749 if (after->getType() == ltACCOUNT_ROOT)
750 {
751 possibleIssuers_.emplace(after->at(sfAccount), after);
752 return false;
753 }
754
755 /* While LedgerEntryTypesMatch invariant also checks types, all invariants
756 * are processed regardless of previous failures.
757 *
758 * This type check is still necessary here because it prevents potential
759 * issues in subsequent processing.
760 */
761 return after->getType() == ltRIPPLE_STATE &&
762 (!before || before->getType() == ltRIPPLE_STATE);
763}
764
767 std::shared_ptr<SLE const> const& before,
769 bool isDelete)
770{
771 auto const getBalance = [](auto const& line, auto const& other, bool zero) {
772 STAmount amt =
773 line ? line->at(sfBalance) : other->at(sfBalance).zeroed();
774 return zero ? amt.zeroed() : amt;
775 };
776
777 /* Trust lines can be created dynamically by other transactions such as
778 * Payment and OfferCreate that cross offers. Such trust line won't be
779 * created frozen, but the sender might be, so the starting balance must be
780 * treated as zero.
781 */
782 auto const balanceBefore = getBalance(before, after, false);
783
784 /* Same as above, trust lines can be dynamically deleted, and for frozen
785 * trust lines, payments not involving the issuer must be blocked. This is
786 * achieved by treating the final balance as zero when isDelete=true to
787 * ensure frozen line restrictions are enforced even during deletion.
788 */
789 auto const balanceAfter = getBalance(after, before, isDelete);
790
791 return balanceAfter - balanceBefore;
792}
793
794void
796{
797 XRPL_ASSERT(
798 change.balanceChangeSign,
799 "ripple::TransfersNotFrozen::recordBalance : valid trustline "
800 "balance sign.");
801 auto& changes = balanceChanges_[issue];
802 if (change.balanceChangeSign < 0)
803 changes.senders.emplace_back(std::move(change));
804 else
805 changes.receivers.emplace_back(std::move(change));
806}
807
808void
811 STAmount const& balanceChange)
812{
813 auto const balanceChangeSign = balanceChange.signum();
814 auto const currency = after->at(sfBalance).getCurrency();
815
816 // Change from low account's perspective, which is trust line default
818 {currency, after->at(sfHighLimit).getIssuer()},
819 {after, balanceChangeSign});
820
821 // Change from high account's perspective, which reverses the sign.
823 {currency, after->at(sfLowLimit).getIssuer()},
824 {after, -balanceChangeSign});
825}
826
829{
830 if (auto it = possibleIssuers_.find(issuerID); it != possibleIssuers_.end())
831 {
832 return it->second;
833 }
834
835 return view.read(keylet::account(issuerID));
836}
837
838bool
840 std::shared_ptr<SLE const> const& issuer,
841 IssuerChanges const& changes,
842 STTx const& tx,
843 beast::Journal const& j,
844 bool enforce)
845{
846 if (!issuer)
847 {
848 return false;
849 }
850
851 bool const globalFreeze = issuer->isFlag(lsfGlobalFreeze);
852 if (changes.receivers.empty() || changes.senders.empty())
853 {
854 /* If there are no receivers, then the holder(s) are returning
855 * their tokens to the issuer. Likewise, if there are no
856 * senders, then the issuer is issuing tokens to the holder(s).
857 * This is allowed regardless of the issuer's freeze flags. (The
858 * holder may have contradicting freeze flags, but that will be
859 * checked when the holder is treated as issuer.)
860 */
861 return true;
862 }
863
864 for (auto const& actors : {changes.senders, changes.receivers})
865 {
866 for (auto const& change : actors)
867 {
868 bool const high = change.line->at(sfLowLimit).getIssuer() ==
869 issuer->at(sfAccount);
870
872 change, high, tx, j, enforce, globalFreeze))
873 {
874 return false;
875 }
876 }
877 }
878 return true;
879}
880
881bool
883 BalanceChange const& change,
884 bool high,
885 STTx const& tx,
886 beast::Journal const& j,
887 bool enforce,
888 bool globalFreeze)
889{
890 bool const freeze = change.balanceChangeSign < 0 &&
891 change.line->isFlag(high ? lsfLowFreeze : lsfHighFreeze);
892 bool const deepFreeze =
893 change.line->isFlag(high ? lsfLowDeepFreeze : lsfHighDeepFreeze);
894 bool const frozen = globalFreeze || deepFreeze || freeze;
895
896 bool const isAMMLine = change.line->isFlag(lsfAMMNode);
897
898 if (!frozen)
899 {
900 return true;
901 }
902
903 // AMMClawbacks are allowed to override some freeze rules
904 if ((!isAMMLine || globalFreeze) && tx.getTxnType() == ttAMM_CLAWBACK)
905 {
906 JLOG(j.debug()) << "Invariant check allowing funds to be moved "
907 << (change.balanceChangeSign > 0 ? "to" : "from")
908 << " a frozen trustline for AMMClawback "
909 << tx.getTransactionID();
910 return true;
911 }
912
913 JLOG(j.fatal()) << "Invariant failed: Attempting to move frozen funds for "
914 << tx.getTransactionID();
915 XRPL_ASSERT(
916 enforce,
917 "ripple::TransfersNotFrozen::validateFrozenState : enforce "
918 "invariant.");
919
920 if (enforce)
921 {
922 return false;
923 }
924
925 return true;
926}
927
928//------------------------------------------------------------------------------
929
930void
932 bool,
933 std::shared_ptr<SLE const> const& before,
935{
936 if (!before && after->getType() == ltACCOUNT_ROOT)
937 {
939 accountSeq_ = (*after)[sfSequence];
941 flags_ = after->getFlags();
942 }
943}
944
945bool
947 STTx const& tx,
948 TER const result,
949 XRPAmount const,
950 ReadView const& view,
951 beast::Journal const& j)
952{
953 if (accountsCreated_ == 0)
954 return true;
955
956 if (accountsCreated_ > 1)
957 {
958 JLOG(j.fatal()) << "Invariant failed: multiple accounts "
959 "created in a single transaction";
960 return false;
961 }
962
963 // From this point on we know exactly one account was created.
964 if ((tx.getTxnType() == ttPAYMENT || tx.getTxnType() == ttAMM_CREATE ||
965 tx.getTxnType() == ttVAULT_CREATE ||
966 tx.getTxnType() == ttXCHAIN_ADD_CLAIM_ATTESTATION ||
967 tx.getTxnType() == ttXCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION) &&
968 result == tesSUCCESS)
969 {
970 bool const pseudoAccount =
971 (pseudoAccount_ && view.rules().enabled(featureSingleAssetVault));
972
973 if (pseudoAccount && tx.getTxnType() != ttAMM_CREATE &&
974 tx.getTxnType() != ttVAULT_CREATE)
975 {
976 JLOG(j.fatal()) << "Invariant failed: pseudo-account created by a "
977 "wrong transaction type";
978 return false;
979 }
980
981 std::uint32_t const startingSeq = //
982 pseudoAccount //
983 ? 0 //
984 : view.rules().enabled(featureDeletableAccounts) //
985 ? view.seq() //
986 : 1;
987
988 if (accountSeq_ != startingSeq)
989 {
990 JLOG(j.fatal()) << "Invariant failed: account created with "
991 "wrong starting sequence number";
992 return false;
993 }
994
995 if (pseudoAccount)
996 {
997 std::uint32_t const expected =
999 if (flags_ != expected)
1000 {
1001 JLOG(j.fatal())
1002 << "Invariant failed: pseudo-account created with "
1003 "wrong flags";
1004 return false;
1005 }
1006 }
1007
1008 return true;
1009 }
1010
1011 JLOG(j.fatal()) << "Invariant failed: account root created illegally";
1012 return false;
1013}
1014
1015//------------------------------------------------------------------------------
1016
1017void
1019 bool isDelete,
1020 std::shared_ptr<SLE const> const& before,
1022{
1023 static constexpr uint256 const& pageBits = nft::pageMask;
1024 static constexpr uint256 const accountBits = ~pageBits;
1025
1026 if ((before && before->getType() != ltNFTOKEN_PAGE) ||
1027 (after && after->getType() != ltNFTOKEN_PAGE))
1028 return;
1029
1030 auto check = [this, isDelete](std::shared_ptr<SLE const> const& sle) {
1031 uint256 const account = sle->key() & accountBits;
1032 uint256 const hiLimit = sle->key() & pageBits;
1033 std::optional<uint256> const prev = (*sle)[~sfPreviousPageMin];
1034
1035 // Make sure that any page links...
1036 // 1. Are properly associated with the owning account and
1037 // 2. The page is correctly ordered between links.
1038 if (prev)
1039 {
1040 if (account != (*prev & accountBits))
1041 badLink_ = true;
1042
1043 if (hiLimit <= (*prev & pageBits))
1044 badLink_ = true;
1045 }
1046
1047 if (auto const next = (*sle)[~sfNextPageMin])
1048 {
1049 if (account != (*next & accountBits))
1050 badLink_ = true;
1051
1052 if (hiLimit >= (*next & pageBits))
1053 badLink_ = true;
1054 }
1055
1056 {
1057 auto const& nftokens = sle->getFieldArray(sfNFTokens);
1058
1059 // An NFTokenPage should never contain too many tokens or be empty.
1060 if (std::size_t const nftokenCount = nftokens.size();
1061 (!isDelete && nftokenCount == 0) ||
1062 nftokenCount > dirMaxTokensPerPage)
1063 invalidSize_ = true;
1064
1065 // If prev is valid, use it to establish a lower bound for
1066 // page entries. If prev is not valid the lower bound is zero.
1067 uint256 const loLimit =
1068 prev ? *prev & pageBits : uint256(beast::zero);
1069
1070 // Also verify that all NFTokenIDs in the page are sorted.
1071 uint256 loCmp = loLimit;
1072 for (auto const& obj : nftokens)
1073 {
1074 uint256 const tokenID = obj[sfNFTokenID];
1075 if (!nft::compareTokens(loCmp, tokenID))
1076 badSort_ = true;
1077 loCmp = tokenID;
1078
1079 // None of the NFTs on this page should belong on lower or
1080 // higher pages.
1081 if (uint256 const tokenPageBits = tokenID & pageBits;
1082 tokenPageBits < loLimit || tokenPageBits >= hiLimit)
1083 badEntry_ = true;
1084
1085 if (auto uri = obj[~sfURI]; uri && uri->empty())
1086 badURI_ = true;
1087 }
1088 }
1089 };
1090
1091 if (before)
1092 {
1093 check(before);
1094
1095 // While an account's NFToken directory contains any NFTokens, the last
1096 // NFTokenPage (with 96 bits of 1 in the low part of the index) should
1097 // never be deleted.
1098 if (isDelete && (before->key() & nft::pageMask) == nft::pageMask &&
1099 before->isFieldPresent(sfPreviousPageMin))
1100 {
1101 deletedFinalPage_ = true;
1102 }
1103 }
1104
1105 if (after)
1106 check(after);
1107
1108 if (!isDelete && before && after)
1109 {
1110 // If the NFTokenPage
1111 // 1. Has a NextMinPage field in before, but loses it in after, and
1112 // 2. This is not the last page in the directory
1113 // Then we have identified a corruption in the links between the
1114 // NFToken pages in the NFToken directory.
1115 if ((before->key() & nft::pageMask) != nft::pageMask &&
1116 before->isFieldPresent(sfNextPageMin) &&
1117 !after->isFieldPresent(sfNextPageMin))
1118 {
1119 deletedLink_ = true;
1120 }
1121 }
1122}
1123
1124bool
1126 STTx const& tx,
1127 TER const result,
1128 XRPAmount const,
1129 ReadView const& view,
1130 beast::Journal const& j)
1131{
1132 if (badLink_)
1133 {
1134 JLOG(j.fatal()) << "Invariant failed: NFT page is improperly linked.";
1135 return false;
1136 }
1137
1138 if (badEntry_)
1139 {
1140 JLOG(j.fatal()) << "Invariant failed: NFT found in incorrect page.";
1141 return false;
1142 }
1143
1144 if (badSort_)
1145 {
1146 JLOG(j.fatal()) << "Invariant failed: NFTs on page are not sorted.";
1147 return false;
1148 }
1149
1150 if (badURI_)
1151 {
1152 JLOG(j.fatal()) << "Invariant failed: NFT contains empty URI.";
1153 return false;
1154 }
1155
1156 if (invalidSize_)
1157 {
1158 JLOG(j.fatal()) << "Invariant failed: NFT page has invalid size.";
1159 return false;
1160 }
1161
1162 if (view.rules().enabled(fixNFTokenPageLinks))
1163 {
1165 {
1166 JLOG(j.fatal()) << "Invariant failed: Last NFT page deleted with "
1167 "non-empty directory.";
1168 return false;
1169 }
1170 if (deletedLink_)
1171 {
1172 JLOG(j.fatal()) << "Invariant failed: Lost NextMinPage link.";
1173 return false;
1174 }
1175 }
1176
1177 return true;
1178}
1179
1180//------------------------------------------------------------------------------
1181void
1183 bool,
1184 std::shared_ptr<SLE const> const& before,
1186{
1187 if (before && before->getType() == ltACCOUNT_ROOT)
1188 {
1189 beforeMintedTotal += (*before)[~sfMintedNFTokens].value_or(0);
1190 beforeBurnedTotal += (*before)[~sfBurnedNFTokens].value_or(0);
1191 }
1192
1193 if (after && after->getType() == ltACCOUNT_ROOT)
1194 {
1195 afterMintedTotal += (*after)[~sfMintedNFTokens].value_or(0);
1196 afterBurnedTotal += (*after)[~sfBurnedNFTokens].value_or(0);
1197 }
1198}
1199
1200bool
1202 STTx const& tx,
1203 TER const result,
1204 XRPAmount const,
1205 ReadView const& view,
1206 beast::Journal const& j)
1207{
1208 if (TxType const txType = tx.getTxnType();
1209 txType != ttNFTOKEN_MINT && txType != ttNFTOKEN_BURN)
1210 {
1212 {
1213 JLOG(j.fatal()) << "Invariant failed: the number of minted tokens "
1214 "changed without a mint transaction!";
1215 return false;
1216 }
1217
1219 {
1220 JLOG(j.fatal()) << "Invariant failed: the number of burned tokens "
1221 "changed without a burn transaction!";
1222 return false;
1223 }
1224
1225 return true;
1226 }
1227
1228 if (tx.getTxnType() == ttNFTOKEN_MINT)
1229 {
1230 if (result == tesSUCCESS && beforeMintedTotal >= afterMintedTotal)
1231 {
1232 JLOG(j.fatal())
1233 << "Invariant failed: successful minting didn't increase "
1234 "the number of minted tokens.";
1235 return false;
1236 }
1237
1238 if (result != tesSUCCESS && beforeMintedTotal != afterMintedTotal)
1239 {
1240 JLOG(j.fatal()) << "Invariant failed: failed minting changed the "
1241 "number of minted tokens.";
1242 return false;
1243 }
1244
1246 {
1247 JLOG(j.fatal())
1248 << "Invariant failed: minting changed the number of "
1249 "burned tokens.";
1250 return false;
1251 }
1252 }
1253
1254 if (tx.getTxnType() == ttNFTOKEN_BURN)
1255 {
1256 if (result == tesSUCCESS)
1257 {
1259 {
1260 JLOG(j.fatal())
1261 << "Invariant failed: successful burning didn't increase "
1262 "the number of burned tokens.";
1263 return false;
1264 }
1265 }
1266
1267 if (result != tesSUCCESS && beforeBurnedTotal != afterBurnedTotal)
1268 {
1269 JLOG(j.fatal()) << "Invariant failed: failed burning changed the "
1270 "number of burned tokens.";
1271 return false;
1272 }
1273
1275 {
1276 JLOG(j.fatal())
1277 << "Invariant failed: burning changed the number of "
1278 "minted tokens.";
1279 return false;
1280 }
1281 }
1282
1283 return true;
1284}
1285
1286//------------------------------------------------------------------------------
1287
1288void
1290 bool,
1291 std::shared_ptr<SLE const> const& before,
1293{
1294 if (before && before->getType() == ltRIPPLE_STATE)
1296
1297 if (before && before->getType() == ltMPTOKEN)
1299}
1300
1301bool
1303 STTx const& tx,
1304 TER const result,
1305 XRPAmount const,
1306 ReadView const& view,
1307 beast::Journal const& j)
1308{
1309 if (tx.getTxnType() != ttCLAWBACK)
1310 return true;
1311
1312 if (result == tesSUCCESS)
1313 {
1314 if (trustlinesChanged > 1)
1315 {
1316 JLOG(j.fatal())
1317 << "Invariant failed: more than one trustline changed.";
1318 return false;
1319 }
1320
1321 if (mptokensChanged > 1)
1322 {
1323 JLOG(j.fatal())
1324 << "Invariant failed: more than one mptokens changed.";
1325 return false;
1326 }
1327
1328 if (trustlinesChanged == 1)
1329 {
1330 AccountID const issuer = tx.getAccountID(sfAccount);
1331 STAmount const& amount = tx.getFieldAmount(sfAmount);
1332 AccountID const& holder = amount.getIssuer();
1333 STAmount const holderBalance = accountHolds(
1334 view, holder, amount.getCurrency(), issuer, fhIGNORE_FREEZE, j);
1335
1336 if (holderBalance.signum() < 0)
1337 {
1338 JLOG(j.fatal())
1339 << "Invariant failed: trustline balance is negative";
1340 return false;
1341 }
1342 }
1343 }
1344 else
1345 {
1346 if (trustlinesChanged != 0)
1347 {
1348 JLOG(j.fatal()) << "Invariant failed: some trustlines were changed "
1349 "despite failure of the transaction.";
1350 return false;
1351 }
1352
1353 if (mptokensChanged != 0)
1354 {
1355 JLOG(j.fatal()) << "Invariant failed: some mptokens were changed "
1356 "despite failure of the transaction.";
1357 return false;
1358 }
1359 }
1360
1361 return true;
1362}
1363
1364//------------------------------------------------------------------------------
1365
1366void
1368 bool isDelete,
1369 std::shared_ptr<SLE const> const& before,
1371{
1372 if (after && after->getType() == ltMPTOKEN_ISSUANCE)
1373 {
1374 if (isDelete)
1376 else if (!before)
1378 }
1379
1380 if (after && after->getType() == ltMPTOKEN)
1381 {
1382 if (isDelete)
1384 else if (!before)
1386 }
1387}
1388
1389bool
1391 STTx const& tx,
1392 TER const result,
1393 XRPAmount const _fee,
1394 ReadView const& _view,
1395 beast::Journal const& j)
1396{
1397 if (result == tesSUCCESS)
1398 {
1399 if (tx.getTxnType() == ttMPTOKEN_ISSUANCE_CREATE ||
1400 tx.getTxnType() == ttVAULT_CREATE)
1401 {
1402 if (mptIssuancesCreated_ == 0)
1403 {
1404 JLOG(j.fatal()) << "Invariant failed: transaction "
1405 "succeeded without creating a MPT issuance";
1406 }
1407 else if (mptIssuancesDeleted_ != 0)
1408 {
1409 JLOG(j.fatal()) << "Invariant failed: transaction "
1410 "succeeded while removing MPT issuances";
1411 }
1412 else if (mptIssuancesCreated_ > 1)
1413 {
1414 JLOG(j.fatal()) << "Invariant failed: transaction "
1415 "succeeded but created multiple issuances";
1416 }
1417
1418 return mptIssuancesCreated_ == 1 && mptIssuancesDeleted_ == 0;
1419 }
1420
1421 if (tx.getTxnType() == ttMPTOKEN_ISSUANCE_DESTROY ||
1422 tx.getTxnType() == ttVAULT_DELETE)
1423 {
1424 if (mptIssuancesDeleted_ == 0)
1425 {
1426 JLOG(j.fatal()) << "Invariant failed: MPT issuance deletion "
1427 "succeeded without removing a MPT issuance";
1428 }
1429 else if (mptIssuancesCreated_ > 0)
1430 {
1431 JLOG(j.fatal()) << "Invariant failed: MPT issuance deletion "
1432 "succeeded while creating MPT issuances";
1433 }
1434 else if (mptIssuancesDeleted_ > 1)
1435 {
1436 JLOG(j.fatal()) << "Invariant failed: MPT issuance deletion "
1437 "succeeded but deleted multiple issuances";
1438 }
1439
1440 return mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 1;
1441 }
1442
1443 if (tx.getTxnType() == ttMPTOKEN_AUTHORIZE ||
1444 tx.getTxnType() == ttVAULT_DEPOSIT)
1445 {
1446 bool const submittedByIssuer = tx.isFieldPresent(sfHolder);
1447
1448 if (mptIssuancesCreated_ > 0)
1449 {
1450 JLOG(j.fatal()) << "Invariant failed: MPT authorize "
1451 "succeeded but created MPT issuances";
1452 return false;
1453 }
1454 else if (mptIssuancesDeleted_ > 0)
1455 {
1456 JLOG(j.fatal()) << "Invariant failed: MPT authorize "
1457 "succeeded but deleted issuances";
1458 return false;
1459 }
1460 else if (
1461 submittedByIssuer &&
1463 {
1464 JLOG(j.fatal())
1465 << "Invariant failed: MPT authorize submitted by issuer "
1466 "succeeded but created/deleted mptokens";
1467 return false;
1468 }
1469 else if (
1470 !submittedByIssuer && (tx.getTxnType() != ttVAULT_DEPOSIT) &&
1472 {
1473 // if the holder submitted this tx, then a mptoken must be
1474 // either created or deleted.
1475 JLOG(j.fatal())
1476 << "Invariant failed: MPT authorize submitted by holder "
1477 "succeeded but created/deleted bad number of mptokens";
1478 return false;
1479 }
1480
1481 return true;
1482 }
1483
1484 if (tx.getTxnType() == ttMPTOKEN_ISSUANCE_SET)
1485 {
1486 if (mptIssuancesDeleted_ > 0)
1487 {
1488 JLOG(j.fatal()) << "Invariant failed: MPT issuance set "
1489 "succeeded while removing MPT issuances";
1490 }
1491 else if (mptIssuancesCreated_ > 0)
1492 {
1493 JLOG(j.fatal()) << "Invariant failed: MPT issuance set "
1494 "succeeded while creating MPT issuances";
1495 }
1496 else if (mptokensDeleted_ > 0)
1497 {
1498 JLOG(j.fatal()) << "Invariant failed: MPT issuance set "
1499 "succeeded while removing MPTokens";
1500 }
1501 else if (mptokensCreated_ > 0)
1502 {
1503 JLOG(j.fatal()) << "Invariant failed: MPT issuance set "
1504 "succeeded while creating MPTokens";
1505 }
1506
1507 return mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 0 &&
1509 }
1510
1511 if (tx.getTxnType() == ttESCROW_FINISH)
1512 return true;
1513 }
1514
1515 if (mptIssuancesCreated_ != 0)
1516 {
1517 JLOG(j.fatal()) << "Invariant failed: a MPT issuance was created";
1518 }
1519 else if (mptIssuancesDeleted_ != 0)
1520 {
1521 JLOG(j.fatal()) << "Invariant failed: a MPT issuance was deleted";
1522 }
1523 else if (mptokensCreated_ != 0)
1524 {
1525 JLOG(j.fatal()) << "Invariant failed: a MPToken was created";
1526 }
1527 else if (mptokensDeleted_ != 0)
1528 {
1529 JLOG(j.fatal()) << "Invariant failed: a MPToken was deleted";
1530 }
1531
1532 return mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 0 &&
1534}
1535
1536//------------------------------------------------------------------------------
1537
1538void
1540 bool,
1541 std::shared_ptr<SLE const> const& before,
1543{
1544 if (before && before->getType() != ltPERMISSIONED_DOMAIN)
1545 return;
1546 if (after && after->getType() != ltPERMISSIONED_DOMAIN)
1547 return;
1548
1549 auto check = [](SleStatus& sleStatus,
1550 std::shared_ptr<SLE const> const& sle) {
1551 auto const& credentials = sle->getFieldArray(sfAcceptedCredentials);
1552 sleStatus.credentialsSize_ = credentials.size();
1553 auto const sorted = credentials::makeSorted(credentials);
1554 sleStatus.isUnique_ = !sorted.empty();
1555
1556 // If array have duplicates then all the other checks are invalid
1557 sleStatus.isSorted_ = false;
1558
1559 if (sleStatus.isUnique_)
1560 {
1561 unsigned i = 0;
1562 for (auto const& cred : sorted)
1563 {
1564 auto const& credTx = credentials[i++];
1565 sleStatus.isSorted_ = (cred.first == credTx[sfIssuer]) &&
1566 (cred.second == credTx[sfCredentialType]);
1567 if (!sleStatus.isSorted_)
1568 break;
1569 }
1570 }
1571 };
1572
1573 if (before)
1574 {
1575 sleStatus_[0] = SleStatus();
1576 check(*sleStatus_[0], after);
1577 }
1578
1579 if (after)
1580 {
1581 sleStatus_[1] = SleStatus();
1582 check(*sleStatus_[1], after);
1583 }
1584}
1585
1586bool
1588 STTx const& tx,
1589 TER const result,
1590 XRPAmount const,
1591 ReadView const& view,
1592 beast::Journal const& j)
1593{
1594 if (tx.getTxnType() != ttPERMISSIONED_DOMAIN_SET || result != tesSUCCESS)
1595 return true;
1596
1597 auto check = [](SleStatus const& sleStatus, beast::Journal const& j) {
1598 if (!sleStatus.credentialsSize_)
1599 {
1600 JLOG(j.fatal()) << "Invariant failed: permissioned domain with "
1601 "no rules.";
1602 return false;
1603 }
1604
1605 if (sleStatus.credentialsSize_ >
1607 {
1608 JLOG(j.fatal()) << "Invariant failed: permissioned domain bad "
1609 "credentials size "
1610 << sleStatus.credentialsSize_;
1611 return false;
1612 }
1613
1614 if (!sleStatus.isUnique_)
1615 {
1616 JLOG(j.fatal())
1617 << "Invariant failed: permissioned domain credentials "
1618 "aren't unique";
1619 return false;
1620 }
1621
1622 if (!sleStatus.isSorted_)
1623 {
1624 JLOG(j.fatal())
1625 << "Invariant failed: permissioned domain credentials "
1626 "aren't sorted";
1627 return false;
1628 }
1629
1630 return true;
1631 };
1632
1633 return (sleStatus_[0] ? check(*sleStatus_[0], j) : true) &&
1634 (sleStatus_[1] ? check(*sleStatus_[1], j) : true);
1635}
1636
1637void
1639 bool,
1640 std::shared_ptr<SLE const> const& before,
1642{
1643 if (after && after->getType() == ltDIR_NODE)
1644 {
1645 if (after->isFieldPresent(sfDomainID))
1646 domains_.insert(after->getFieldH256(sfDomainID));
1647 }
1648
1649 if (after && after->getType() == ltOFFER)
1650 {
1651 if (after->isFieldPresent(sfDomainID))
1652 domains_.insert(after->getFieldH256(sfDomainID));
1653 else
1654 regularOffers_ = true;
1655
1656 // if a hybrid offer is missing domain or additional book, there's
1657 // something wrong
1658 if (after->isFlag(lsfHybrid) &&
1659 (!after->isFieldPresent(sfDomainID) ||
1660 !after->isFieldPresent(sfAdditionalBooks) ||
1661 after->getFieldArray(sfAdditionalBooks).size() > 1))
1662 badHybrids_ = true;
1663 }
1664}
1665
1666bool
1668 STTx const& tx,
1669 TER const result,
1670 XRPAmount const,
1671 ReadView const& view,
1672 beast::Journal const& j)
1673{
1674 auto const txType = tx.getTxnType();
1675 if ((txType != ttPAYMENT && txType != ttOFFER_CREATE) ||
1676 result != tesSUCCESS)
1677 return true;
1678
1679 // For each offercreate transaction, check if
1680 // permissioned offers are valid
1681 if (txType == ttOFFER_CREATE && badHybrids_)
1682 {
1683 JLOG(j.fatal()) << "Invariant failed: hybrid offer is malformed";
1684 return false;
1685 }
1686
1687 if (!tx.isFieldPresent(sfDomainID))
1688 return true;
1689
1690 auto const domain = tx.getFieldH256(sfDomainID);
1691
1692 if (!view.exists(keylet::permissionedDomain(domain)))
1693 {
1694 JLOG(j.fatal()) << "Invariant failed: domain doesn't exist";
1695 return false;
1696 }
1697
1698 // for both payment and offercreate, there shouldn't be another domain
1699 // that's different from the domain specified
1700 for (auto const& d : domains_)
1701 {
1702 if (d != domain)
1703 {
1704 JLOG(j.fatal()) << "Invariant failed: transaction"
1705 " consumed wrong domains";
1706 return false;
1707 }
1708 }
1709
1710 if (regularOffers_)
1711 {
1712 JLOG(j.fatal()) << "Invariant failed: domain transaction"
1713 " affected regular offers";
1714 return false;
1715 }
1716
1717 return true;
1718}
1719
1720void
1722 bool isDelete,
1723 std::shared_ptr<SLE const> const& before,
1725{
1726 if (isDelete)
1727 return;
1728
1729 if (after)
1730 {
1731 auto const type = after->getType();
1732 // AMM object changed
1733 if (type == ltAMM)
1734 {
1735 ammAccount_ = after->getAccountID(sfAccount);
1736 lptAMMBalanceAfter_ = after->getFieldAmount(sfLPTokenBalance);
1737 }
1738 // AMM pool changed
1739 else if (
1740 (type == ltRIPPLE_STATE && after->getFlags() & lsfAMMNode) ||
1741 (type == ltACCOUNT_ROOT && after->isFieldPresent(sfAMMID)))
1742 {
1743 ammPoolChanged_ = true;
1744 }
1745 }
1746
1747 if (before)
1748 {
1749 // AMM object changed
1750 if (before->getType() == ltAMM)
1751 {
1752 lptAMMBalanceBefore_ = before->getFieldAmount(sfLPTokenBalance);
1753 }
1754 }
1755}
1756
1757static bool
1759 STAmount const& amount,
1760 STAmount const& amount2,
1761 STAmount const& lptAMMBalance,
1762 ValidAMM::ZeroAllowed zeroAllowed)
1763{
1764 bool const positive = amount > beast::zero && amount2 > beast::zero &&
1765 lptAMMBalance > beast::zero;
1766 if (zeroAllowed == ValidAMM::ZeroAllowed::Yes)
1767 return positive ||
1768 (amount == beast::zero && amount2 == beast::zero &&
1769 lptAMMBalance == beast::zero);
1770 return positive;
1771}
1772
1773bool
1774ValidAMM::finalizeVote(bool enforce, beast::Journal const& j) const
1775{
1777 {
1778 // LPTokens and the pool can not change on vote
1779 // LCOV_EXCL_START
1780 JLOG(j.error()) << "AMMVote invariant failed: "
1783 << ammPoolChanged_;
1784 if (enforce)
1785 return false;
1786 // LCOV_EXCL_STOP
1787 }
1788
1789 return true;
1790}
1791
1792bool
1793ValidAMM::finalizeBid(bool enforce, beast::Journal const& j) const
1794{
1795 if (ammPoolChanged_)
1796 {
1797 // The pool can not change on bid
1798 // LCOV_EXCL_START
1799 JLOG(j.error()) << "AMMBid invariant failed: pool changed";
1800 if (enforce)
1801 return false;
1802 // LCOV_EXCL_STOP
1803 }
1804 // LPTokens are burnt, therefore there should be fewer LPTokens
1805 else if (
1808 *lptAMMBalanceAfter_ <= beast::zero))
1809 {
1810 // LCOV_EXCL_START
1811 JLOG(j.error()) << "AMMBid invariant failed: " << *lptAMMBalanceBefore_
1812 << " " << *lptAMMBalanceAfter_;
1813 if (enforce)
1814 return false;
1815 // LCOV_EXCL_STOP
1816 }
1817
1818 return true;
1819}
1820
1821bool
1823 STTx const& tx,
1824 ReadView const& view,
1825 bool enforce,
1826 beast::Journal const& j) const
1827{
1828 if (!ammAccount_)
1829 {
1830 // LCOV_EXCL_START
1831 JLOG(j.error())
1832 << "AMMCreate invariant failed: AMM object is not created";
1833 if (enforce)
1834 return false;
1835 // LCOV_EXCL_STOP
1836 }
1837 else
1838 {
1839 auto const [amount, amount2] = ammPoolHolds(
1840 view,
1841 *ammAccount_,
1842 tx[sfAmount].get<Issue>(),
1843 tx[sfAmount2].get<Issue>(),
1845 j);
1846 // Create invariant:
1847 // sqrt(amount * amount2) == LPTokens
1848 // all balances are greater than zero
1849 if (!validBalances(
1850 amount, amount2, *lptAMMBalanceAfter_, ZeroAllowed::No) ||
1851 ammLPTokens(amount, amount2, lptAMMBalanceAfter_->issue()) !=
1853 {
1854 JLOG(j.error()) << "AMMCreate invariant failed: " << amount << " "
1855 << amount2 << " " << *lptAMMBalanceAfter_;
1856 if (enforce)
1857 return false;
1858 }
1859 }
1860
1861 return true;
1862}
1863
1864bool
1865ValidAMM::finalizeDelete(bool enforce, TER res, beast::Journal const& j) const
1866{
1867 if (ammAccount_)
1868 {
1869 // LCOV_EXCL_START
1870 std::string const msg = (res == tesSUCCESS)
1871 ? "AMM object is not deleted on tesSUCCESS"
1872 : "AMM object is changed on tecINCOMPLETE";
1873 JLOG(j.error()) << "AMMDelete invariant failed: " << msg;
1874 if (enforce)
1875 return false;
1876 // LCOV_EXCL_STOP
1877 }
1878
1879 return true;
1880}
1881
1882bool
1883ValidAMM::finalizeDEX(bool enforce, beast::Journal const& j) const
1884{
1885 if (ammAccount_)
1886 {
1887 // LCOV_EXCL_START
1888 JLOG(j.error()) << "AMM swap invariant failed: AMM object changed";
1889 if (enforce)
1890 return false;
1891 // LCOV_EXCL_STOP
1892 }
1893
1894 return true;
1895}
1896
1897bool
1899 ripple::STTx const& tx,
1900 ripple::ReadView const& view,
1901 ZeroAllowed zeroAllowed,
1902 beast::Journal const& j) const
1903{
1904 auto const [amount, amount2] = ammPoolHolds(
1905 view,
1906 *ammAccount_,
1907 tx[sfAsset].get<Issue>(),
1908 tx[sfAsset2].get<Issue>(),
1910 j);
1911 // Deposit and Withdrawal invariant:
1912 // sqrt(amount * amount2) >= LPTokens
1913 // all balances are greater than zero
1914 // unless on last withdrawal
1915 auto const poolProductMean = root2(amount * amount2);
1916 bool const nonNegativeBalances =
1917 validBalances(amount, amount2, *lptAMMBalanceAfter_, zeroAllowed);
1918 bool const strongInvariantCheck = poolProductMean >= *lptAMMBalanceAfter_;
1919 // Allow for a small relative error if strongInvariantCheck fails
1920 auto weakInvariantCheck = [&]() {
1921 return *lptAMMBalanceAfter_ != beast::zero &&
1923 poolProductMean, Number{*lptAMMBalanceAfter_}, Number{1, -11});
1924 };
1925 if (!nonNegativeBalances ||
1926 (!strongInvariantCheck && !weakInvariantCheck()))
1927 {
1928 JLOG(j.error()) << "AMM " << tx.getTxnType() << " invariant failed: "
1930 << ammPoolChanged_ << " " << amount << " " << amount2
1931 << " " << poolProductMean << " "
1932 << lptAMMBalanceAfter_->getText() << " "
1933 << ((*lptAMMBalanceAfter_ == beast::zero)
1934 ? Number{1}
1935 : ((*lptAMMBalanceAfter_ - poolProductMean) /
1936 poolProductMean));
1937 return false;
1938 }
1939
1940 return true;
1941}
1942
1943bool
1945 ripple::STTx const& tx,
1946 ripple::ReadView const& view,
1947 bool enforce,
1948 beast::Journal const& j) const
1949{
1950 if (!ammAccount_)
1951 {
1952 // LCOV_EXCL_START
1953 JLOG(j.error()) << "AMMDeposit invariant failed: AMM object is deleted";
1954 if (enforce)
1955 return false;
1956 // LCOV_EXCL_STOP
1957 }
1958 else if (!generalInvariant(tx, view, ZeroAllowed::No, j) && enforce)
1959 return false;
1960
1961 return true;
1962}
1963
1964bool
1966 ripple::STTx const& tx,
1967 ripple::ReadView const& view,
1968 bool enforce,
1969 beast::Journal const& j) const
1970{
1971 if (!ammAccount_)
1972 {
1973 // Last Withdraw or Clawback deleted AMM
1974 }
1975 else if (!generalInvariant(tx, view, ZeroAllowed::Yes, j))
1976 {
1977 if (enforce)
1978 return false;
1979 }
1980
1981 return true;
1982}
1983
1984bool
1986 STTx const& tx,
1987 TER const result,
1988 XRPAmount const,
1989 ReadView const& view,
1990 beast::Journal const& j)
1991{
1992 // Delete may return tecINCOMPLETE if there are too many
1993 // trustlines to delete.
1994 if (result != tesSUCCESS && result != tecINCOMPLETE)
1995 return true;
1996
1997 bool const enforce = view.rules().enabled(fixAMMv1_3);
1998
1999 switch (tx.getTxnType())
2000 {
2001 case ttAMM_CREATE:
2002 return finalizeCreate(tx, view, enforce, j);
2003 case ttAMM_DEPOSIT:
2004 return finalizeDeposit(tx, view, enforce, j);
2005 case ttAMM_CLAWBACK:
2006 case ttAMM_WITHDRAW:
2007 return finalizeWithdraw(tx, view, enforce, j);
2008 case ttAMM_BID:
2009 return finalizeBid(enforce, j);
2010 case ttAMM_VOTE:
2011 return finalizeVote(enforce, j);
2012 case ttAMM_DELETE:
2013 return finalizeDelete(enforce, result, j);
2014 case ttCHECK_CASH:
2015 case ttOFFER_CREATE:
2016 case ttPAYMENT:
2017 return finalizeDEX(enforce, j);
2018 default:
2019 break;
2020 }
2021
2022 return true;
2023}
2024
2025} // namespace ripple
A generic endpoint for log messages.
Definition Journal.h:60
Stream fatal() const
Definition Journal.h:352
Stream error() const
Definition Journal.h:346
Stream debug() const
Definition Journal.h:328
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:33
Item const * findByType(KeyType type) const
Retrieve a format based on its type.
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.
virtual bool exists(Keylet const &k) const =0
Determine if a state item exists.
LedgerIndex seq() const
Returns the sequence number of the base ledger.
Definition ReadView.h:119
virtual Rules const & rules() const =0
Returns the tx processing rules.
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition Rules.cpp:130
Currency const & getCurrency() const
Definition STAmount.h:502
XRPAmount xrp() const
Definition STAmount.cpp:306
int signum() const noexcept
Definition STAmount.h:514
AccountID const & getIssuer() const
Definition STAmount.h:508
bool native() const noexcept
Definition STAmount.h:458
STAmount zeroed() const
Returns a zero value with the same issuer and currency.
Definition STAmount.h:520
AccountID getAccountID(SField const &field) const
Definition STObject.cpp:651
STAmount const & getFieldAmount(SField const &field) const
Definition STObject.cpp:665
uint256 getHash(HashPrefix prefix) const
Definition STObject.cpp:395
bool isFieldPresent(SField const &field) const
Definition STObject.cpp:484
uint256 getFieldH256(SField const &field) const
Definition STObject.cpp:645
TxType getTxnType() const
Definition STTx.h:207
uint256 getTransactionID() const
Definition STTx.h:219
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
std::map< AccountID, std::shared_ptr< SLE const > const > possibleIssuers_
bool isValidEntry(std::shared_ptr< SLE const > const &before, std::shared_ptr< SLE const > const &after)
void recordBalance(Issue const &issue, BalanceChange change)
std::shared_ptr< SLE const > findIssuer(AccountID const &issuerID, ReadView const &view)
bool validateIssuerChanges(std::shared_ptr< SLE const > const &issuer, IssuerChanges const &changes, STTx const &tx, beast::Journal const &j, bool enforce)
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
STAmount calculateBalanceChange(std::shared_ptr< SLE const > const &before, std::shared_ptr< SLE const > const &after, bool isDelete)
void recordBalanceChanges(std::shared_ptr< SLE const > const &after, STAmount const &balanceChange)
bool validateFrozenState(BalanceChange const &change, bool high, STTx const &tx, beast::Journal const &j, bool enforce, bool globalFreeze)
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
bool finalizeWithdraw(STTx const &, ReadView const &, bool enforce, beast::Journal const &) const
bool finalizeDEX(bool enforce, beast::Journal const &) const
std::optional< STAmount > lptAMMBalanceAfter_
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
bool finalizeBid(bool enforce, beast::Journal const &) const
std::optional< AccountID > ammAccount_
bool finalizeDelete(bool enforce, TER res, beast::Journal const &) const
bool finalizeCreate(STTx const &, ReadView const &, bool enforce, beast::Journal const &) const
bool finalizeVote(bool enforce, beast::Journal const &) const
bool finalizeDeposit(STTx const &, ReadView const &, bool enforce, beast::Journal const &) const
bool generalInvariant(STTx const &, ReadView const &, ZeroAllowed zeroAllowed, beast::Journal const &) const
std::optional< STAmount > lptAMMBalanceBefore_
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 mptIssuancesDeleted_
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 &)
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 &)
hash_set< uint256 > domains_
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 permissionedDomain(AccountID const &account, std::uint32_t seq) noexcept
Definition Indexes.cpp:570
Keylet amm(Asset const &issue1, Asset const &issue2) noexcept
AMM entry.
Definition Indexes.cpp:446
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:184
Keylet nftpage_min(AccountID const &owner)
NFT page keylets.
Definition Indexes.cpp:403
Keylet nftpage_max(AccountID const &owner)
A keylet for the owner's last possible NFT page.
Definition Indexes.cpp:411
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:25
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
Definition Issue.h:115
Currency const & badCurrency()
We deliberately disallow the currency that looks like "XRP" because too many people were using it ins...
@ fhIGNORE_FREEZE
Definition View.h:78
bool isXRP(AccountID const &c)
Definition AccountID.h:90
TxType
Transaction type identifiers.
Definition TxFormats.h:57
static bool validBalances(STAmount const &amount, STAmount const &amount2, STAmount const &lptAMMBalance, ValidAMM::ZeroAllowed zeroAllowed)
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
std::uint64_t constexpr maxMPTokenAmount
The maximum amount of MPTokenIssuance.
Definition Protocol.h:117
@ lsfHighDeepFreeze
@ lsfDefaultRipple
@ lsfDisableMaster
@ lsfGlobalFreeze
@ 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:382
std::pair< STAmount, STAmount > ammPoolHolds(ReadView const &view, AccountID const &ammAccountID, Issue const &issue1, Issue const &issue2, FreezeHandling freezeHandling, beast::Journal const j)
Get AMM pool balances.
Definition AMMUtils.cpp:31
@ tecINCOMPLETE
Definition TER.h:335
@ tesSUCCESS
Definition TER.h:244
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j)
Definition View.cpp:385
STAmount ammLPTokens(STAmount const &asset1, STAmount const &asset2, Issue const &lptIssue)
Calculate LP Tokens given AMM pool reserves.
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition View.cpp:3096
@ transactionID
transaction plus signature to give transaction ID
bool withinRelativeDistance(Quality const &calcQuality, Quality const &reqQuality, Number const &dist)
Check if the relative distance between the qualities is within the requested distance.
Definition AMMHelpers.h:129
Number root2(Number f)
Definition Number.cpp:701
bool isPseudoAccount(std::shared_ptr< SLE const > sleAcct)
Definition View.cpp:1139
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)
T value_or(T... args)