rippled
Loading...
Searching...
No Matches
InvariantCheck.cpp
1#include <xrpld/app/misc/AMMHelpers.h>
2#include <xrpld/app/misc/AMMUtils.h>
3#include <xrpld/app/tx/detail/InvariantCheck.h>
4#include <xrpld/app/tx/detail/NFTokenUtils.h>
5#include <xrpld/app/tx/detail/PermissionedDomainSet.h>
6
7#include <xrpl/basics/Log.h>
8#include <xrpl/beast/utility/instrumentation.h>
9#include <xrpl/ledger/CredentialHelpers.h>
10#include <xrpl/ledger/ReadView.h>
11#include <xrpl/ledger/View.h>
12#include <xrpl/protocol/Feature.h>
13#include <xrpl/protocol/Indexes.h>
14#include <xrpl/protocol/LedgerFormats.h>
15#include <xrpl/protocol/MPTIssue.h>
16#include <xrpl/protocol/SField.h>
17#include <xrpl/protocol/STArray.h>
18#include <xrpl/protocol/STNumber.h>
19#include <xrpl/protocol/SystemParameters.h>
20#include <xrpl/protocol/TER.h>
21#include <xrpl/protocol/TxFormats.h>
22#include <xrpl/protocol/Units.h>
23#include <xrpl/protocol/nftPageMask.h>
24
25#include <cstdint>
26#include <optional>
27
28namespace ripple {
29
30/*
31assert(enforce)
32
33There are several asserts (or XRPL_ASSERTs) in this file that check a variable
34named `enforce` when an invariant fails. At first glance, those asserts may look
35incorrect, but they are not.
36
37Those asserts take advantage of two facts:
381. `asserts` are not (normally) executed in release builds.
392. Invariants should *never* fail, except in tests that specifically modify
40 the open ledger to break them.
41
42This makes `assert(enforce)` sort of a second-layer of invariant enforcement
43aimed at _developers_. It's designed to fire if a developer writes code that
44violates an invariant, and runs it in unit tests or a develop build that _does
45not have the relevant amendments enabled_. It's intentionally a pain in the neck
46so that bad code gets caught and fixed as early as possible.
47*/
48
50 noPriv =
51 0x0000, // The transaction can not do any of the enumerated operations
53 0x0001, // The transaction can create a new ACCOUNT_ROOT object.
54 createPseudoAcct = 0x0002, // The transaction can create a pseudo account,
55 // which implies createAcct
57 0x0004, // The transaction must delete an ACCOUNT_ROOT object
58 mayDeleteAcct = 0x0008, // The transaction may delete an ACCOUNT_ROOT
59 // object, but does not have to
60 overrideFreeze = 0x0010, // The transaction can override some freeze rules
61 changeNFTCounts = 0x0020, // The transaction can mint or burn an NFT
63 0x0040, // The transaction can create a new MPT issuance
64 destroyMPTIssuance = 0x0080, // The transaction can destroy an MPT issuance
65 mustAuthorizeMPT = 0x0100, // The transaction MUST create or delete an MPT
66 // object (except by issuer)
67 mayAuthorizeMPT = 0x0200, // The transaction MAY create or delete an MPT
68 // object (except by issuer)
70 0x0400, // The transaction MAY delete an MPT object. May not create.
72 0x0800, // The transaction must modify, delete or create, a vault
73};
74constexpr Privilege
76{
77 return safe_cast<Privilege>(
80}
81
82#pragma push_macro("TRANSACTION")
83#undef TRANSACTION
84
85#define TRANSACTION(tag, value, name, delegatable, amendment, privileges, ...) \
86 case tag: { \
87 return (privileges) & priv; \
88 }
89
90bool
91hasPrivilege(STTx const& tx, Privilege priv)
92{
93 switch (tx.getTxnType())
94 {
95#include <xrpl/protocol/detail/transactions.macro>
96 // Deprecated types
97 default:
98 return false;
99 }
100};
101
102#undef TRANSACTION
103#pragma pop_macro("TRANSACTION")
104
105void
107 bool,
110{
111 // nothing to do
112}
113
114bool
116 STTx const& tx,
117 TER const,
118 XRPAmount const fee,
119 ReadView const&,
120 beast::Journal const& j)
121{
122 // We should never charge a negative fee
123 if (fee.drops() < 0)
124 {
125 JLOG(j.fatal()) << "Invariant failed: fee paid was negative: "
126 << fee.drops();
127 return false;
128 }
129
130 // We should never charge a fee that's greater than or equal to the
131 // entire XRP supply.
132 if (fee >= INITIAL_XRP)
133 {
134 JLOG(j.fatal()) << "Invariant failed: fee paid exceeds system limit: "
135 << fee.drops();
136 return false;
137 }
138
139 // We should never charge more for a transaction than the transaction
140 // authorizes. It's possible to charge less in some circumstances.
141 if (fee > tx.getFieldAmount(sfFee).xrp())
142 {
143 JLOG(j.fatal()) << "Invariant failed: fee paid is " << fee.drops()
144 << " exceeds fee specified in transaction.";
145 return false;
146 }
147
148 return true;
149}
150
151//------------------------------------------------------------------------------
152
153void
155 bool isDelete,
156 std::shared_ptr<SLE const> const& before,
158{
159 /* We go through all modified ledger entries, looking only at account roots,
160 * escrow payments, and payment channels. We remove from the total any
161 * previous XRP values and add to the total any new XRP values. The net
162 * balance of a payment channel is computed from two fields (amount and
163 * balance) and deletions are ignored for paychan and escrow because the
164 * amount fields have not been adjusted for those in the case of deletion.
165 */
166 if (before)
167 {
168 switch (before->getType())
169 {
170 case ltACCOUNT_ROOT:
171 drops_ -= (*before)[sfBalance].xrp().drops();
172 break;
173 case ltPAYCHAN:
174 drops_ -=
175 ((*before)[sfAmount] - (*before)[sfBalance]).xrp().drops();
176 break;
177 case ltESCROW:
178 if (isXRP((*before)[sfAmount]))
179 drops_ -= (*before)[sfAmount].xrp().drops();
180 break;
181 default:
182 break;
183 }
184 }
185
186 if (after)
187 {
188 switch (after->getType())
189 {
190 case ltACCOUNT_ROOT:
191 drops_ += (*after)[sfBalance].xrp().drops();
192 break;
193 case ltPAYCHAN:
194 if (!isDelete)
195 drops_ += ((*after)[sfAmount] - (*after)[sfBalance])
196 .xrp()
197 .drops();
198 break;
199 case ltESCROW:
200 if (!isDelete && isXRP((*after)[sfAmount]))
201 drops_ += (*after)[sfAmount].xrp().drops();
202 break;
203 default:
204 break;
205 }
206 }
207}
208
209bool
211 STTx const& tx,
212 TER const,
213 XRPAmount const fee,
214 ReadView const&,
215 beast::Journal const& j)
216{
217 // The net change should never be positive, as this would mean that the
218 // transaction created XRP out of thin air. That's not possible.
219 if (drops_ > 0)
220 {
221 JLOG(j.fatal()) << "Invariant failed: XRP net change was positive: "
222 << drops_;
223 return false;
224 }
225
226 // The negative of the net change should be equal to actual fee charged.
227 if (-drops_ != fee.drops())
228 {
229 JLOG(j.fatal()) << "Invariant failed: XRP net change of " << drops_
230 << " doesn't match fee " << fee.drops();
231 return false;
232 }
233
234 return true;
235}
236
237//------------------------------------------------------------------------------
238
239void
241 bool,
242 std::shared_ptr<SLE const> const& before,
244{
245 auto isBad = [](STAmount const& balance) {
246 if (!balance.native())
247 return true;
248
249 auto const drops = balance.xrp();
250
251 // Can't have more than the number of drops instantiated
252 // in the genesis ledger.
253 if (drops > INITIAL_XRP)
254 return true;
255
256 // Can't have a negative balance (0 is OK)
257 if (drops < XRPAmount{0})
258 return true;
259
260 return false;
261 };
262
263 if (before && before->getType() == ltACCOUNT_ROOT)
264 bad_ |= isBad((*before)[sfBalance]);
265
266 if (after && after->getType() == ltACCOUNT_ROOT)
267 bad_ |= isBad((*after)[sfBalance]);
268}
269
270bool
272 STTx const&,
273 TER const,
274 XRPAmount const,
275 ReadView const&,
276 beast::Journal const& j)
277{
278 if (bad_)
279 {
280 JLOG(j.fatal()) << "Invariant failed: incorrect account XRP balance";
281 return false;
282 }
283
284 return true;
285}
286
287//------------------------------------------------------------------------------
288
289void
291 bool isDelete,
292 std::shared_ptr<SLE const> const& before,
294{
295 auto isBad = [](STAmount const& pays, STAmount const& gets) {
296 // An offer should never be negative
297 if (pays < beast::zero)
298 return true;
299
300 if (gets < beast::zero)
301 return true;
302
303 // Can't have an XRP to XRP offer:
304 return pays.native() && gets.native();
305 };
306
307 if (before && before->getType() == ltOFFER)
308 bad_ |= isBad((*before)[sfTakerPays], (*before)[sfTakerGets]);
309
310 if (after && after->getType() == ltOFFER)
311 bad_ |= isBad((*after)[sfTakerPays], (*after)[sfTakerGets]);
312}
313
314bool
316 STTx const&,
317 TER const,
318 XRPAmount const,
319 ReadView const&,
320 beast::Journal const& j)
321{
322 if (bad_)
323 {
324 JLOG(j.fatal()) << "Invariant failed: offer with a bad amount";
325 return false;
326 }
327
328 return true;
329}
330
331//------------------------------------------------------------------------------
332
333void
335 bool isDelete,
336 std::shared_ptr<SLE const> const& before,
338{
339 auto isBad = [](STAmount const& amount) {
340 // XRP case
341 if (amount.native())
342 {
343 if (amount.xrp() <= XRPAmount{0})
344 return true;
345
346 if (amount.xrp() >= INITIAL_XRP)
347 return true;
348 }
349 else
350 {
351 // IOU case
352 if (amount.holds<Issue>())
353 {
354 if (amount <= beast::zero)
355 return true;
356
357 if (badCurrency() == amount.getCurrency())
358 return true;
359 }
360
361 // MPT case
362 if (amount.holds<MPTIssue>())
363 {
364 if (amount <= beast::zero)
365 return true;
366
367 if (amount.mpt() > MPTAmount{maxMPTokenAmount})
368 return true; // LCOV_EXCL_LINE
369 }
370 }
371 return false;
372 };
373
374 if (before && before->getType() == ltESCROW)
375 bad_ |= isBad((*before)[sfAmount]);
376
377 if (after && after->getType() == ltESCROW)
378 bad_ |= isBad((*after)[sfAmount]);
379
380 auto checkAmount = [this](std::int64_t amount) {
381 if (amount > maxMPTokenAmount || amount < 0)
382 bad_ = true;
383 };
384
385 if (after && after->getType() == ltMPTOKEN_ISSUANCE)
386 {
387 auto const outstanding = (*after)[sfOutstandingAmount];
388 checkAmount(outstanding);
389 if (auto const locked = (*after)[~sfLockedAmount])
390 {
391 checkAmount(*locked);
392 bad_ = outstanding < *locked;
393 }
394 }
395
396 if (after && after->getType() == ltMPTOKEN)
397 {
398 auto const mptAmount = (*after)[sfMPTAmount];
399 checkAmount(mptAmount);
400 if (auto const locked = (*after)[~sfLockedAmount])
401 {
402 checkAmount(*locked);
403 }
404 }
405}
406
407bool
409 STTx const& txn,
410 TER const,
411 XRPAmount const,
412 ReadView const& rv,
413 beast::Journal const& j)
414{
415 if (bad_)
416 {
417 JLOG(j.fatal()) << "Invariant failed: escrow specifies invalid amount";
418 return false;
419 }
420
421 return true;
422}
423
424//------------------------------------------------------------------------------
425
426void
428 bool isDelete,
429 std::shared_ptr<SLE const> const& before,
431{
432 if (isDelete && before && before->getType() == ltACCOUNT_ROOT)
434}
435
436bool
438 STTx const& tx,
439 TER const result,
440 XRPAmount const,
441 ReadView const&,
442 beast::Journal const& j)
443{
444 // AMM account root can be deleted as the result of AMM withdraw/delete
445 // transaction when the total AMM LP Tokens balance goes to 0.
446 // A successful AccountDelete or AMMDelete MUST delete exactly
447 // one account root.
448 if (hasPrivilege(tx, mustDeleteAcct) && result == tesSUCCESS)
449 {
450 if (accountsDeleted_ == 1)
451 return true;
452
453 if (accountsDeleted_ == 0)
454 JLOG(j.fatal()) << "Invariant failed: account deletion "
455 "succeeded without deleting an account";
456 else
457 JLOG(j.fatal()) << "Invariant failed: account deletion "
458 "succeeded but deleted multiple accounts!";
459 return false;
460 }
461
462 // A successful AMMWithdraw/AMMClawback MAY delete one account root
463 // when the total AMM LP Tokens balance goes to 0. Not every AMM withdraw
464 // deletes the AMM account, accountsDeleted_ is set if it is deleted.
465 if (hasPrivilege(tx, mayDeleteAcct) && result == tesSUCCESS &&
466 accountsDeleted_ == 1)
467 return true;
468
469 if (accountsDeleted_ == 0)
470 return true;
471
472 JLOG(j.fatal()) << "Invariant failed: an account root was deleted";
473 return false;
474}
475
476//------------------------------------------------------------------------------
477
478void
480 bool isDelete,
481 std::shared_ptr<SLE const> const& before,
483{
484 if (isDelete && before && before->getType() == ltACCOUNT_ROOT)
485 accountsDeleted_.emplace_back(before);
486}
487
488bool
490 STTx const& tx,
491 TER const result,
492 XRPAmount const,
493 ReadView const& view,
494 beast::Journal const& j)
495{
496 // Always check for objects in the ledger, but to prevent differing
497 // transaction processing results, however unlikely, only fail if the
498 // feature is enabled. Enabled, or not, though, a fatal-level message will
499 // be logged
500 [[maybe_unused]] bool const enforce =
501 view.rules().enabled(featureInvariantsV1_1) ||
502 view.rules().enabled(featureSingleAssetVault);
503
504 auto const objectExists = [&view, enforce, &j](auto const& keylet) {
505 (void)enforce;
506 if (auto const sle = view.read(keylet))
507 {
508 // Finding the object is bad
509 auto const typeName = [&sle]() {
510 auto item =
511 LedgerFormats::getInstance().findByType(sle->getType());
512
513 if (item != nullptr)
514 return item->getName();
515 return std::to_string(sle->getType());
516 }();
517
518 JLOG(j.fatal())
519 << "Invariant failed: account deletion left behind a "
520 << typeName << " object";
521 // The comment above starting with "assert(enforce)" explains this
522 // assert.
523 XRPL_ASSERT(
524 enforce,
525 "ripple::AccountRootsDeletedClean::finalize::objectExists : "
526 "account deletion left no objects behind");
527 return true;
528 }
529 return false;
530 };
531
532 for (auto const& accountSLE : accountsDeleted_)
533 {
534 auto const accountID = accountSLE->getAccountID(sfAccount);
535 // Simple types
536 for (auto const& [keyletfunc, _, __] : directAccountKeylets)
537 {
538 if (objectExists(std::invoke(keyletfunc, accountID)) && enforce)
539 return false;
540 }
541
542 {
543 // NFT pages. ntfpage_min and nftpage_max were already explicitly
544 // checked above as entries in directAccountKeylets. This uses
545 // view.succ() to check for any NFT pages in between the two
546 // endpoints.
547 Keylet const first = keylet::nftpage_min(accountID);
548 Keylet const last = keylet::nftpage_max(accountID);
549
550 std::optional<uint256> key = view.succ(first.key, last.key.next());
551
552 // current page
553 if (key && objectExists(Keylet{ltNFTOKEN_PAGE, *key}) && enforce)
554 return false;
555 }
556
557 // If the account is a pseudo account, then the linked object must
558 // also be deleted. e.g. AMM, Vault, etc.
559 for (auto const& field : getPseudoAccountFields())
560 {
561 if (accountSLE->isFieldPresent(*field))
562 {
563 auto const key = accountSLE->getFieldH256(*field);
564 if (objectExists(keylet::unchecked(key)) && enforce)
565 return false;
566 }
567 }
568 }
569
570 return true;
571}
572
573//------------------------------------------------------------------------------
574
575void
577 bool,
578 std::shared_ptr<SLE const> const& before,
580{
581 if (before && after && before->getType() != after->getType())
582 typeMismatch_ = true;
583
584 if (after)
585 {
586#pragma push_macro("LEDGER_ENTRY")
587#undef LEDGER_ENTRY
588
589#define LEDGER_ENTRY(tag, ...) case tag:
590
591 switch (after->getType())
592 {
593#include <xrpl/protocol/detail/ledger_entries.macro>
594
595 break;
596 default:
597 invalidTypeAdded_ = true;
598 break;
599 }
600
601#undef LEDGER_ENTRY
602#pragma pop_macro("LEDGER_ENTRY")
603 }
604}
605
606bool
608 STTx const&,
609 TER const,
610 XRPAmount const,
611 ReadView const&,
612 beast::Journal const& j)
613{
614 if ((!typeMismatch_) && (!invalidTypeAdded_))
615 return true;
616
617 if (typeMismatch_)
618 {
619 JLOG(j.fatal()) << "Invariant failed: ledger entry type mismatch";
620 }
621
623 {
624 JLOG(j.fatal()) << "Invariant failed: invalid ledger entry type added";
625 }
626
627 return false;
628}
629
630//------------------------------------------------------------------------------
631
632void
634 bool,
637{
638 if (after && after->getType() == ltRIPPLE_STATE)
639 {
640 // checking the issue directly here instead of
641 // relying on .native() just in case native somehow
642 // were systematically incorrect
644 after->getFieldAmount(sfLowLimit).issue() == xrpIssue() ||
645 after->getFieldAmount(sfHighLimit).issue() == xrpIssue();
646 }
647}
648
649bool
651 STTx const&,
652 TER const,
653 XRPAmount const,
654 ReadView const&,
655 beast::Journal const& j)
656{
657 if (!xrpTrustLine_)
658 return true;
659
660 JLOG(j.fatal()) << "Invariant failed: an XRP trust line was created";
661 return false;
662}
663
664//------------------------------------------------------------------------------
665
666void
668 bool,
671{
672 if (after && after->getType() == ltRIPPLE_STATE)
673 {
674 std::uint32_t const uFlags = after->getFieldU32(sfFlags);
675 bool const lowFreeze = uFlags & lsfLowFreeze;
676 bool const lowDeepFreeze = uFlags & lsfLowDeepFreeze;
677
678 bool const highFreeze = uFlags & lsfHighFreeze;
679 bool const highDeepFreeze = uFlags & lsfHighDeepFreeze;
680
682 (lowDeepFreeze && !lowFreeze) || (highDeepFreeze && !highFreeze);
683 }
684}
685
686bool
688 STTx const&,
689 TER const,
690 XRPAmount const,
691 ReadView const&,
692 beast::Journal const& j)
693{
695 return true;
696
697 JLOG(j.fatal()) << "Invariant failed: a trust line with deep freeze flag "
698 "without normal freeze was created";
699 return false;
700}
701
702//------------------------------------------------------------------------------
703
704void
706 bool isDelete,
707 std::shared_ptr<SLE const> const& before,
709{
710 /*
711 * A trust line freeze state alone doesn't determine if a transfer is
712 * frozen. The transfer must be examined "end-to-end" because both sides of
713 * the transfer may have different freeze states and freeze impact depends
714 * on the transfer direction. This is why first we need to track the
715 * transfers using IssuerChanges senders/receivers.
716 *
717 * Only in validateIssuerChanges, after we collected all changes can we
718 * determine if the transfer is valid.
719 */
720 if (!isValidEntry(before, after))
721 {
722 return;
723 }
724
725 auto const balanceChange = calculateBalanceChange(before, after, isDelete);
726 if (balanceChange.signum() == 0)
727 {
728 return;
729 }
730
731 recordBalanceChanges(after, balanceChange);
732}
733
734bool
736 STTx const& tx,
737 TER const ter,
738 XRPAmount const fee,
739 ReadView const& view,
740 beast::Journal const& j)
741{
742 /*
743 * We check this invariant regardless of deep freeze amendment status,
744 * allowing for detection and logging of potential issues even when the
745 * amendment is disabled.
746 *
747 * If an exploit that allows moving frozen assets is discovered,
748 * we can alert operators who monitor fatal messages and trigger assert in
749 * debug builds for an early warning.
750 *
751 * In an unlikely event that an exploit is found, this early detection
752 * enables encouraging the UNL to expedite deep freeze amendment activation
753 * or deploy hotfixes via new amendments. In case of a new amendment, we'd
754 * only have to change this line setting 'enforce' variable.
755 * enforce = view.rules().enabled(featureDeepFreeze) ||
756 * view.rules().enabled(fixFreezeExploit);
757 */
758 [[maybe_unused]] bool const enforce =
759 view.rules().enabled(featureDeepFreeze);
760
761 for (auto const& [issue, changes] : balanceChanges_)
762 {
763 auto const issuerSle = findIssuer(issue.account, view);
764 // It should be impossible for the issuer to not be found, but check
765 // just in case so rippled doesn't crash in release.
766 if (!issuerSle)
767 {
768 // The comment above starting with "assert(enforce)" explains this
769 // assert.
770 XRPL_ASSERT(
771 enforce,
772 "ripple::TransfersNotFrozen::finalize : enforce "
773 "invariant.");
774 if (enforce)
775 {
776 return false;
777 }
778 continue;
779 }
780
781 if (!validateIssuerChanges(issuerSle, changes, tx, j, enforce))
782 {
783 return false;
784 }
785 }
786
787 return true;
788}
789
790bool
792 std::shared_ptr<SLE const> const& before,
794{
795 // `after` can never be null, even if the trust line is deleted.
796 XRPL_ASSERT(
797 after, "ripple::TransfersNotFrozen::isValidEntry : valid after.");
798 if (!after)
799 {
800 return false;
801 }
802
803 if (after->getType() == ltACCOUNT_ROOT)
804 {
805 possibleIssuers_.emplace(after->at(sfAccount), after);
806 return false;
807 }
808
809 /* While LedgerEntryTypesMatch invariant also checks types, all invariants
810 * are processed regardless of previous failures.
811 *
812 * This type check is still necessary here because it prevents potential
813 * issues in subsequent processing.
814 */
815 return after->getType() == ltRIPPLE_STATE &&
816 (!before || before->getType() == ltRIPPLE_STATE);
817}
818
821 std::shared_ptr<SLE const> const& before,
823 bool isDelete)
824{
825 auto const getBalance = [](auto const& line, auto const& other, bool zero) {
826 STAmount amt =
827 line ? line->at(sfBalance) : other->at(sfBalance).zeroed();
828 return zero ? amt.zeroed() : amt;
829 };
830
831 /* Trust lines can be created dynamically by other transactions such as
832 * Payment and OfferCreate that cross offers. Such trust line won't be
833 * created frozen, but the sender might be, so the starting balance must be
834 * treated as zero.
835 */
836 auto const balanceBefore = getBalance(before, after, false);
837
838 /* Same as above, trust lines can be dynamically deleted, and for frozen
839 * trust lines, payments not involving the issuer must be blocked. This is
840 * achieved by treating the final balance as zero when isDelete=true to
841 * ensure frozen line restrictions are enforced even during deletion.
842 */
843 auto const balanceAfter = getBalance(after, before, isDelete);
844
845 return balanceAfter - balanceBefore;
846}
847
848void
850{
851 XRPL_ASSERT(
852 change.balanceChangeSign,
853 "ripple::TransfersNotFrozen::recordBalance : valid trustline "
854 "balance sign.");
855 auto& changes = balanceChanges_[issue];
856 if (change.balanceChangeSign < 0)
857 changes.senders.emplace_back(std::move(change));
858 else
859 changes.receivers.emplace_back(std::move(change));
860}
861
862void
865 STAmount const& balanceChange)
866{
867 auto const balanceChangeSign = balanceChange.signum();
868 auto const currency = after->at(sfBalance).getCurrency();
869
870 // Change from low account's perspective, which is trust line default
872 {currency, after->at(sfHighLimit).getIssuer()},
873 {after, balanceChangeSign});
874
875 // Change from high account's perspective, which reverses the sign.
877 {currency, after->at(sfLowLimit).getIssuer()},
878 {after, -balanceChangeSign});
879}
880
883{
884 if (auto it = possibleIssuers_.find(issuerID); it != possibleIssuers_.end())
885 {
886 return it->second;
887 }
888
889 return view.read(keylet::account(issuerID));
890}
891
892bool
894 std::shared_ptr<SLE const> const& issuer,
895 IssuerChanges const& changes,
896 STTx const& tx,
897 beast::Journal const& j,
898 bool enforce)
899{
900 if (!issuer)
901 {
902 return false;
903 }
904
905 bool const globalFreeze = issuer->isFlag(lsfGlobalFreeze);
906 if (changes.receivers.empty() || changes.senders.empty())
907 {
908 /* If there are no receivers, then the holder(s) are returning
909 * their tokens to the issuer. Likewise, if there are no
910 * senders, then the issuer is issuing tokens to the holder(s).
911 * This is allowed regardless of the issuer's freeze flags. (The
912 * holder may have contradicting freeze flags, but that will be
913 * checked when the holder is treated as issuer.)
914 */
915 return true;
916 }
917
918 for (auto const& actors : {changes.senders, changes.receivers})
919 {
920 for (auto const& change : actors)
921 {
922 bool const high = change.line->at(sfLowLimit).getIssuer() ==
923 issuer->at(sfAccount);
924
926 change, high, tx, j, enforce, globalFreeze))
927 {
928 return false;
929 }
930 }
931 }
932 return true;
933}
934
935bool
937 BalanceChange const& change,
938 bool high,
939 STTx const& tx,
940 beast::Journal const& j,
941 bool enforce,
942 bool globalFreeze)
943{
944 bool const freeze = change.balanceChangeSign < 0 &&
945 change.line->isFlag(high ? lsfLowFreeze : lsfHighFreeze);
946 bool const deepFreeze =
947 change.line->isFlag(high ? lsfLowDeepFreeze : lsfHighDeepFreeze);
948 bool const frozen = globalFreeze || deepFreeze || freeze;
949
950 bool const isAMMLine = change.line->isFlag(lsfAMMNode);
951
952 if (!frozen)
953 {
954 return true;
955 }
956
957 // AMMClawbacks are allowed to override some freeze rules
958 if ((!isAMMLine || globalFreeze) && hasPrivilege(tx, overrideFreeze))
959 {
960 JLOG(j.debug()) << "Invariant check allowing funds to be moved "
961 << (change.balanceChangeSign > 0 ? "to" : "from")
962 << " a frozen trustline for AMMClawback "
963 << tx.getTransactionID();
964 return true;
965 }
966
967 JLOG(j.fatal()) << "Invariant failed: Attempting to move frozen funds for "
968 << tx.getTransactionID();
969 // The comment above starting with "assert(enforce)" explains this assert.
970 XRPL_ASSERT(
971 enforce,
972 "ripple::TransfersNotFrozen::validateFrozenState : enforce "
973 "invariant.");
974
975 if (enforce)
976 {
977 return false;
978 }
979
980 return true;
981}
982
983//------------------------------------------------------------------------------
984
985void
987 bool,
988 std::shared_ptr<SLE const> const& before,
990{
991 if (!before && after->getType() == ltACCOUNT_ROOT)
992 {
994 accountSeq_ = (*after)[sfSequence];
996 flags_ = after->getFlags();
997 }
998}
999
1000bool
1002 STTx const& tx,
1003 TER const result,
1004 XRPAmount const,
1005 ReadView const& view,
1006 beast::Journal const& j)
1007{
1008 if (accountsCreated_ == 0)
1009 return true;
1010
1011 if (accountsCreated_ > 1)
1012 {
1013 JLOG(j.fatal()) << "Invariant failed: multiple accounts "
1014 "created in a single transaction";
1015 return false;
1016 }
1017
1018 // From this point on we know exactly one account was created.
1019 if (hasPrivilege(tx, createAcct | createPseudoAcct) && result == tesSUCCESS)
1020 {
1021 bool const pseudoAccount =
1022 (pseudoAccount_ && view.rules().enabled(featureSingleAssetVault));
1023
1024 if (pseudoAccount && !hasPrivilege(tx, createPseudoAcct))
1025 {
1026 JLOG(j.fatal()) << "Invariant failed: pseudo-account created by a "
1027 "wrong transaction type";
1028 return false;
1029 }
1030
1031 std::uint32_t const startingSeq = //
1032 pseudoAccount //
1033 ? 0 //
1034 : view.rules().enabled(featureDeletableAccounts) //
1035 ? view.seq() //
1036 : 1;
1037
1038 if (accountSeq_ != startingSeq)
1039 {
1040 JLOG(j.fatal()) << "Invariant failed: account created with "
1041 "wrong starting sequence number";
1042 return false;
1043 }
1044
1045 if (pseudoAccount)
1046 {
1047 std::uint32_t const expected =
1049 if (flags_ != expected)
1050 {
1051 JLOG(j.fatal())
1052 << "Invariant failed: pseudo-account created with "
1053 "wrong flags";
1054 return false;
1055 }
1056 }
1057
1058 return true;
1059 }
1060
1061 JLOG(j.fatal()) << "Invariant failed: account root created illegally";
1062 return false;
1063} // namespace ripple
1064
1065//------------------------------------------------------------------------------
1066
1067void
1069 bool isDelete,
1070 std::shared_ptr<SLE const> const& before,
1072{
1073 static constexpr uint256 const& pageBits = nft::pageMask;
1074 static constexpr uint256 const accountBits = ~pageBits;
1075
1076 if ((before && before->getType() != ltNFTOKEN_PAGE) ||
1077 (after && after->getType() != ltNFTOKEN_PAGE))
1078 return;
1079
1080 auto check = [this, isDelete](std::shared_ptr<SLE const> const& sle) {
1081 uint256 const account = sle->key() & accountBits;
1082 uint256 const hiLimit = sle->key() & pageBits;
1083 std::optional<uint256> const prev = (*sle)[~sfPreviousPageMin];
1084
1085 // Make sure that any page links...
1086 // 1. Are properly associated with the owning account and
1087 // 2. The page is correctly ordered between links.
1088 if (prev)
1089 {
1090 if (account != (*prev & accountBits))
1091 badLink_ = true;
1092
1093 if (hiLimit <= (*prev & pageBits))
1094 badLink_ = true;
1095 }
1096
1097 if (auto const next = (*sle)[~sfNextPageMin])
1098 {
1099 if (account != (*next & accountBits))
1100 badLink_ = true;
1101
1102 if (hiLimit >= (*next & pageBits))
1103 badLink_ = true;
1104 }
1105
1106 {
1107 auto const& nftokens = sle->getFieldArray(sfNFTokens);
1108
1109 // An NFTokenPage should never contain too many tokens or be empty.
1110 if (std::size_t const nftokenCount = nftokens.size();
1111 (!isDelete && nftokenCount == 0) ||
1112 nftokenCount > dirMaxTokensPerPage)
1113 invalidSize_ = true;
1114
1115 // If prev is valid, use it to establish a lower bound for
1116 // page entries. If prev is not valid the lower bound is zero.
1117 uint256 const loLimit =
1118 prev ? *prev & pageBits : uint256(beast::zero);
1119
1120 // Also verify that all NFTokenIDs in the page are sorted.
1121 uint256 loCmp = loLimit;
1122 for (auto const& obj : nftokens)
1123 {
1124 uint256 const tokenID = obj[sfNFTokenID];
1125 if (!nft::compareTokens(loCmp, tokenID))
1126 badSort_ = true;
1127 loCmp = tokenID;
1128
1129 // None of the NFTs on this page should belong on lower or
1130 // higher pages.
1131 if (uint256 const tokenPageBits = tokenID & pageBits;
1132 tokenPageBits < loLimit || tokenPageBits >= hiLimit)
1133 badEntry_ = true;
1134
1135 if (auto uri = obj[~sfURI]; uri && uri->empty())
1136 badURI_ = true;
1137 }
1138 }
1139 };
1140
1141 if (before)
1142 {
1143 check(before);
1144
1145 // While an account's NFToken directory contains any NFTokens, the last
1146 // NFTokenPage (with 96 bits of 1 in the low part of the index) should
1147 // never be deleted.
1148 if (isDelete && (before->key() & nft::pageMask) == nft::pageMask &&
1149 before->isFieldPresent(sfPreviousPageMin))
1150 {
1151 deletedFinalPage_ = true;
1152 }
1153 }
1154
1155 if (after)
1156 check(after);
1157
1158 if (!isDelete && before && after)
1159 {
1160 // If the NFTokenPage
1161 // 1. Has a NextMinPage field in before, but loses it in after, and
1162 // 2. This is not the last page in the directory
1163 // Then we have identified a corruption in the links between the
1164 // NFToken pages in the NFToken directory.
1165 if ((before->key() & nft::pageMask) != nft::pageMask &&
1166 before->isFieldPresent(sfNextPageMin) &&
1167 !after->isFieldPresent(sfNextPageMin))
1168 {
1169 deletedLink_ = true;
1170 }
1171 }
1172}
1173
1174bool
1176 STTx const& tx,
1177 TER const result,
1178 XRPAmount const,
1179 ReadView const& view,
1180 beast::Journal const& j)
1181{
1182 if (badLink_)
1183 {
1184 JLOG(j.fatal()) << "Invariant failed: NFT page is improperly linked.";
1185 return false;
1186 }
1187
1188 if (badEntry_)
1189 {
1190 JLOG(j.fatal()) << "Invariant failed: NFT found in incorrect page.";
1191 return false;
1192 }
1193
1194 if (badSort_)
1195 {
1196 JLOG(j.fatal()) << "Invariant failed: NFTs on page are not sorted.";
1197 return false;
1198 }
1199
1200 if (badURI_)
1201 {
1202 JLOG(j.fatal()) << "Invariant failed: NFT contains empty URI.";
1203 return false;
1204 }
1205
1206 if (invalidSize_)
1207 {
1208 JLOG(j.fatal()) << "Invariant failed: NFT page has invalid size.";
1209 return false;
1210 }
1211
1212 if (view.rules().enabled(fixNFTokenPageLinks))
1213 {
1215 {
1216 JLOG(j.fatal()) << "Invariant failed: Last NFT page deleted with "
1217 "non-empty directory.";
1218 return false;
1219 }
1220 if (deletedLink_)
1221 {
1222 JLOG(j.fatal()) << "Invariant failed: Lost NextMinPage link.";
1223 return false;
1224 }
1225 }
1226
1227 return true;
1228}
1229
1230//------------------------------------------------------------------------------
1231void
1233 bool,
1234 std::shared_ptr<SLE const> const& before,
1236{
1237 if (before && before->getType() == ltACCOUNT_ROOT)
1238 {
1239 beforeMintedTotal += (*before)[~sfMintedNFTokens].value_or(0);
1240 beforeBurnedTotal += (*before)[~sfBurnedNFTokens].value_or(0);
1241 }
1242
1243 if (after && after->getType() == ltACCOUNT_ROOT)
1244 {
1245 afterMintedTotal += (*after)[~sfMintedNFTokens].value_or(0);
1246 afterBurnedTotal += (*after)[~sfBurnedNFTokens].value_or(0);
1247 }
1248}
1249
1250bool
1252 STTx const& tx,
1253 TER const result,
1254 XRPAmount const,
1255 ReadView const& view,
1256 beast::Journal const& j)
1257{
1258 if (!hasPrivilege(tx, changeNFTCounts))
1259 {
1261 {
1262 JLOG(j.fatal()) << "Invariant failed: the number of minted tokens "
1263 "changed without a mint transaction!";
1264 return false;
1265 }
1266
1268 {
1269 JLOG(j.fatal()) << "Invariant failed: the number of burned tokens "
1270 "changed without a burn transaction!";
1271 return false;
1272 }
1273
1274 return true;
1275 }
1276
1277 if (tx.getTxnType() == ttNFTOKEN_MINT)
1278 {
1279 if (result == tesSUCCESS && beforeMintedTotal >= afterMintedTotal)
1280 {
1281 JLOG(j.fatal())
1282 << "Invariant failed: successful minting didn't increase "
1283 "the number of minted tokens.";
1284 return false;
1285 }
1286
1287 if (result != tesSUCCESS && beforeMintedTotal != afterMintedTotal)
1288 {
1289 JLOG(j.fatal()) << "Invariant failed: failed minting changed the "
1290 "number of minted tokens.";
1291 return false;
1292 }
1293
1295 {
1296 JLOG(j.fatal())
1297 << "Invariant failed: minting changed the number of "
1298 "burned tokens.";
1299 return false;
1300 }
1301 }
1302
1303 if (tx.getTxnType() == ttNFTOKEN_BURN)
1304 {
1305 if (result == tesSUCCESS)
1306 {
1308 {
1309 JLOG(j.fatal())
1310 << "Invariant failed: successful burning didn't increase "
1311 "the number of burned tokens.";
1312 return false;
1313 }
1314 }
1315
1316 if (result != tesSUCCESS && beforeBurnedTotal != afterBurnedTotal)
1317 {
1318 JLOG(j.fatal()) << "Invariant failed: failed burning changed the "
1319 "number of burned tokens.";
1320 return false;
1321 }
1322
1324 {
1325 JLOG(j.fatal())
1326 << "Invariant failed: burning changed the number of "
1327 "minted tokens.";
1328 return false;
1329 }
1330 }
1331
1332 return true;
1333}
1334
1335//------------------------------------------------------------------------------
1336
1337void
1339 bool,
1340 std::shared_ptr<SLE const> const& before,
1342{
1343 if (before && before->getType() == ltRIPPLE_STATE)
1345
1346 if (before && before->getType() == ltMPTOKEN)
1348}
1349
1350bool
1352 STTx const& tx,
1353 TER const result,
1354 XRPAmount const,
1355 ReadView const& view,
1356 beast::Journal const& j)
1357{
1358 if (tx.getTxnType() != ttCLAWBACK)
1359 return true;
1360
1361 if (result == tesSUCCESS)
1362 {
1363 if (trustlinesChanged > 1)
1364 {
1365 JLOG(j.fatal())
1366 << "Invariant failed: more than one trustline changed.";
1367 return false;
1368 }
1369
1370 if (mptokensChanged > 1)
1371 {
1372 JLOG(j.fatal())
1373 << "Invariant failed: more than one mptokens changed.";
1374 return false;
1375 }
1376
1377 if (trustlinesChanged == 1)
1378 {
1379 AccountID const issuer = tx.getAccountID(sfAccount);
1380 STAmount const& amount = tx.getFieldAmount(sfAmount);
1381 AccountID const& holder = amount.getIssuer();
1382 STAmount const holderBalance = accountHolds(
1383 view, holder, amount.getCurrency(), issuer, fhIGNORE_FREEZE, j);
1384
1385 if (holderBalance.signum() < 0)
1386 {
1387 JLOG(j.fatal())
1388 << "Invariant failed: trustline balance is negative";
1389 return false;
1390 }
1391 }
1392 }
1393 else
1394 {
1395 if (trustlinesChanged != 0)
1396 {
1397 JLOG(j.fatal()) << "Invariant failed: some trustlines were changed "
1398 "despite failure of the transaction.";
1399 return false;
1400 }
1401
1402 if (mptokensChanged != 0)
1403 {
1404 JLOG(j.fatal()) << "Invariant failed: some mptokens were changed "
1405 "despite failure of the transaction.";
1406 return false;
1407 }
1408 }
1409
1410 return true;
1411}
1412
1413//------------------------------------------------------------------------------
1414
1415void
1417 bool isDelete,
1418 std::shared_ptr<SLE const> const& before,
1420{
1421 if (after && after->getType() == ltMPTOKEN_ISSUANCE)
1422 {
1423 if (isDelete)
1425 else if (!before)
1427 }
1428
1429 if (after && after->getType() == ltMPTOKEN)
1430 {
1431 if (isDelete)
1433 else if (!before)
1435 }
1436}
1437
1438bool
1440 STTx const& tx,
1441 TER const result,
1442 XRPAmount const _fee,
1443 ReadView const& view,
1444 beast::Journal const& j)
1445{
1446 if (result == tesSUCCESS)
1447 {
1449 {
1450 if (mptIssuancesCreated_ == 0)
1451 {
1452 JLOG(j.fatal()) << "Invariant failed: transaction "
1453 "succeeded without creating a MPT issuance";
1454 }
1455 else if (mptIssuancesDeleted_ != 0)
1456 {
1457 JLOG(j.fatal()) << "Invariant failed: transaction "
1458 "succeeded while removing MPT issuances";
1459 }
1460 else if (mptIssuancesCreated_ > 1)
1461 {
1462 JLOG(j.fatal()) << "Invariant failed: transaction "
1463 "succeeded but created multiple issuances";
1464 }
1465
1466 return mptIssuancesCreated_ == 1 && mptIssuancesDeleted_ == 0;
1467 }
1468
1470 {
1471 if (mptIssuancesDeleted_ == 0)
1472 {
1473 JLOG(j.fatal()) << "Invariant failed: MPT issuance deletion "
1474 "succeeded without removing a MPT issuance";
1475 }
1476 else if (mptIssuancesCreated_ > 0)
1477 {
1478 JLOG(j.fatal()) << "Invariant failed: MPT issuance deletion "
1479 "succeeded while creating MPT issuances";
1480 }
1481 else if (mptIssuancesDeleted_ > 1)
1482 {
1483 JLOG(j.fatal()) << "Invariant failed: MPT issuance deletion "
1484 "succeeded but deleted multiple issuances";
1485 }
1486
1487 return mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 1;
1488 }
1489
1490 // ttESCROW_FINISH may authorize an MPT, but it can't have the
1491 // mayAuthorizeMPT privilege, because that may cause
1492 // non-amendment-gated side effects.
1493 bool const enforceEscrowFinish = (tx.getTxnType() == ttESCROW_FINISH) &&
1494 (view.rules().enabled(featureSingleAssetVault)
1495 /*
1496 TODO: Uncomment when LendingProtocol is defined
1497 || view.rules().enabled(featureLendingProtocol)*/
1498 );
1500 enforceEscrowFinish)
1501 {
1502 bool const submittedByIssuer = tx.isFieldPresent(sfHolder);
1503
1504 if (mptIssuancesCreated_ > 0)
1505 {
1506 JLOG(j.fatal()) << "Invariant failed: MPT authorize "
1507 "succeeded but created MPT issuances";
1508 return false;
1509 }
1510 else if (mptIssuancesDeleted_ > 0)
1511 {
1512 JLOG(j.fatal()) << "Invariant failed: MPT authorize "
1513 "succeeded but deleted issuances";
1514 return false;
1515 }
1516 else if (
1517 submittedByIssuer &&
1519 {
1520 JLOG(j.fatal())
1521 << "Invariant failed: MPT authorize submitted by issuer "
1522 "succeeded but created/deleted mptokens";
1523 return false;
1524 }
1525 else if (
1526 !submittedByIssuer && hasPrivilege(tx, mustAuthorizeMPT) &&
1528 {
1529 // if the holder submitted this tx, then a mptoken must be
1530 // either created or deleted.
1531 JLOG(j.fatal())
1532 << "Invariant failed: MPT authorize submitted by holder "
1533 "succeeded but created/deleted bad number of mptokens";
1534 return false;
1535 }
1536
1537 return true;
1538 }
1539 if (tx.getTxnType() == ttESCROW_FINISH)
1540 {
1541 // ttESCROW_FINISH may authorize an MPT, but it can't have the
1542 // mayAuthorizeMPT privilege, because that may cause
1543 // non-amendment-gated side effects.
1544 XRPL_ASSERT_PARTS(
1545 !enforceEscrowFinish,
1546 "ripple::ValidMPTIssuance::finalize",
1547 "not escrow finish tx");
1548 return true;
1549 }
1550
1551 if (hasPrivilege(tx, mayDeleteMPT) && mptokensDeleted_ == 1 &&
1554 return true;
1555 }
1556
1557 if (mptIssuancesCreated_ != 0)
1558 {
1559 JLOG(j.fatal()) << "Invariant failed: a MPT issuance was created";
1560 }
1561 else if (mptIssuancesDeleted_ != 0)
1562 {
1563 JLOG(j.fatal()) << "Invariant failed: a MPT issuance was deleted";
1564 }
1565 else if (mptokensCreated_ != 0)
1566 {
1567 JLOG(j.fatal()) << "Invariant failed: a MPToken was created";
1568 }
1569 else if (mptokensDeleted_ != 0)
1570 {
1571 JLOG(j.fatal()) << "Invariant failed: a MPToken was deleted";
1572 }
1573
1574 return mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 0 &&
1576}
1577
1578//------------------------------------------------------------------------------
1579
1580void
1582 bool,
1583 std::shared_ptr<SLE const> const& before,
1585{
1586 if (before && before->getType() != ltPERMISSIONED_DOMAIN)
1587 return;
1588 if (after && after->getType() != ltPERMISSIONED_DOMAIN)
1589 return;
1590
1591 auto check = [](SleStatus& sleStatus,
1592 std::shared_ptr<SLE const> const& sle) {
1593 auto const& credentials = sle->getFieldArray(sfAcceptedCredentials);
1594 sleStatus.credentialsSize_ = credentials.size();
1595 auto const sorted = credentials::makeSorted(credentials);
1596 sleStatus.isUnique_ = !sorted.empty();
1597
1598 // If array have duplicates then all the other checks are invalid
1599 sleStatus.isSorted_ = false;
1600
1601 if (sleStatus.isUnique_)
1602 {
1603 unsigned i = 0;
1604 for (auto const& cred : sorted)
1605 {
1606 auto const& credTx = credentials[i++];
1607 sleStatus.isSorted_ = (cred.first == credTx[sfIssuer]) &&
1608 (cred.second == credTx[sfCredentialType]);
1609 if (!sleStatus.isSorted_)
1610 break;
1611 }
1612 }
1613 };
1614
1615 if (before)
1616 {
1617 sleStatus_[0] = SleStatus();
1618 check(*sleStatus_[0], after);
1619 }
1620
1621 if (after)
1622 {
1623 sleStatus_[1] = SleStatus();
1624 check(*sleStatus_[1], after);
1625 }
1626}
1627
1628bool
1630 STTx const& tx,
1631 TER const result,
1632 XRPAmount const,
1633 ReadView const& view,
1634 beast::Journal const& j)
1635{
1636 if (tx.getTxnType() != ttPERMISSIONED_DOMAIN_SET || result != tesSUCCESS)
1637 return true;
1638
1639 auto check = [](SleStatus const& sleStatus, beast::Journal const& j) {
1640 if (!sleStatus.credentialsSize_)
1641 {
1642 JLOG(j.fatal()) << "Invariant failed: permissioned domain with "
1643 "no rules.";
1644 return false;
1645 }
1646
1647 if (sleStatus.credentialsSize_ >
1649 {
1650 JLOG(j.fatal()) << "Invariant failed: permissioned domain bad "
1651 "credentials size "
1652 << sleStatus.credentialsSize_;
1653 return false;
1654 }
1655
1656 if (!sleStatus.isUnique_)
1657 {
1658 JLOG(j.fatal())
1659 << "Invariant failed: permissioned domain credentials "
1660 "aren't unique";
1661 return false;
1662 }
1663
1664 if (!sleStatus.isSorted_)
1665 {
1666 JLOG(j.fatal())
1667 << "Invariant failed: permissioned domain credentials "
1668 "aren't sorted";
1669 return false;
1670 }
1671
1672 return true;
1673 };
1674
1675 return (sleStatus_[0] ? check(*sleStatus_[0], j) : true) &&
1676 (sleStatus_[1] ? check(*sleStatus_[1], j) : true);
1677}
1678
1679//------------------------------------------------------------------------------
1680
1681void
1683 bool isDelete,
1684 std::shared_ptr<SLE const> const& before,
1686{
1687 if (isDelete)
1688 // Deletion is ignored
1689 return;
1690
1691 if (after && after->getType() == ltACCOUNT_ROOT)
1692 {
1693 bool const isPseudo = [&]() {
1694 // isPseudoAccount checks that any of the pseudo-account fields are
1695 // set.
1697 return true;
1698 // Not all pseudo-accounts have a zero sequence, but all accounts
1699 // with a zero sequence had better be pseudo-accounts.
1700 if (after->at(sfSequence) == 0)
1701 return true;
1702
1703 return false;
1704 }();
1705 if (isPseudo)
1706 {
1707 // Pseudo accounts must have the following properties:
1708 // 1. Exactly one of the pseudo-account fields is set.
1709 // 2. The sequence number is not changed.
1710 // 3. The lsfDisableMaster, lsfDefaultRipple, and lsfDepositAuth
1711 // flags are set.
1712 // 4. The RegularKey is not set.
1713 {
1714 std::vector<SField const*> const& fields =
1716
1717 auto const numFields = std::count_if(
1718 fields.begin(),
1719 fields.end(),
1720 [&after](SField const* sf) -> bool {
1721 return after->isFieldPresent(*sf);
1722 });
1723 if (numFields != 1)
1724 {
1725 std::stringstream error;
1726 error << "pseudo-account has " << numFields
1727 << " pseudo-account fields set";
1728 errors_.emplace_back(error.str());
1729 }
1730 }
1731 if (before && before->at(sfSequence) != after->at(sfSequence))
1732 {
1733 errors_.emplace_back("pseudo-account sequence changed");
1734 }
1735 if (!after->isFlag(
1737 {
1738 errors_.emplace_back("pseudo-account flags are not set");
1739 }
1740 if (after->isFieldPresent(sfRegularKey))
1741 {
1742 errors_.emplace_back("pseudo-account has a regular key");
1743 }
1744 }
1745 }
1746}
1747
1748bool
1750 STTx const& tx,
1751 TER const,
1752 XRPAmount const,
1753 ReadView const& view,
1754 beast::Journal const& j)
1755{
1756 bool const enforce = view.rules().enabled(featureSingleAssetVault);
1757
1758 // The comment above starting with "assert(enforce)" explains this assert.
1759 XRPL_ASSERT(
1760 errors_.empty() || enforce,
1761 "ripple::ValidPseudoAccounts::finalize : no bad "
1762 "changes or enforce invariant");
1763 if (!errors_.empty())
1764 {
1765 for (auto const& error : errors_)
1766 {
1767 JLOG(j.fatal()) << "Invariant failed: " << error;
1768 }
1769 if (enforce)
1770 return false;
1771 }
1772 return true;
1773}
1774
1775//------------------------------------------------------------------------------
1776
1777void
1779 bool,
1780 std::shared_ptr<SLE const> const& before,
1782{
1783 if (after && after->getType() == ltDIR_NODE)
1784 {
1785 if (after->isFieldPresent(sfDomainID))
1786 domains_.insert(after->getFieldH256(sfDomainID));
1787 }
1788
1789 if (after && after->getType() == ltOFFER)
1790 {
1791 if (after->isFieldPresent(sfDomainID))
1792 domains_.insert(after->getFieldH256(sfDomainID));
1793 else
1794 regularOffers_ = true;
1795
1796 // if a hybrid offer is missing domain or additional book, there's
1797 // something wrong
1798 if (after->isFlag(lsfHybrid) &&
1799 (!after->isFieldPresent(sfDomainID) ||
1800 !after->isFieldPresent(sfAdditionalBooks) ||
1801 after->getFieldArray(sfAdditionalBooks).size() > 1))
1802 badHybrids_ = true;
1803 }
1804}
1805
1806bool
1808 STTx const& tx,
1809 TER const result,
1810 XRPAmount const,
1811 ReadView const& view,
1812 beast::Journal const& j)
1813{
1814 auto const txType = tx.getTxnType();
1815 if ((txType != ttPAYMENT && txType != ttOFFER_CREATE) ||
1816 result != tesSUCCESS)
1817 return true;
1818
1819 // For each offercreate transaction, check if
1820 // permissioned offers are valid
1821 if (txType == ttOFFER_CREATE && badHybrids_)
1822 {
1823 JLOG(j.fatal()) << "Invariant failed: hybrid offer is malformed";
1824 return false;
1825 }
1826
1827 if (!tx.isFieldPresent(sfDomainID))
1828 return true;
1829
1830 auto const domain = tx.getFieldH256(sfDomainID);
1831
1832 if (!view.exists(keylet::permissionedDomain(domain)))
1833 {
1834 JLOG(j.fatal()) << "Invariant failed: domain doesn't exist";
1835 return false;
1836 }
1837
1838 // for both payment and offercreate, there shouldn't be another domain
1839 // that's different from the domain specified
1840 for (auto const& d : domains_)
1841 {
1842 if (d != domain)
1843 {
1844 JLOG(j.fatal()) << "Invariant failed: transaction"
1845 " consumed wrong domains";
1846 return false;
1847 }
1848 }
1849
1850 if (regularOffers_)
1851 {
1852 JLOG(j.fatal()) << "Invariant failed: domain transaction"
1853 " affected regular offers";
1854 return false;
1855 }
1856
1857 return true;
1858}
1859
1860void
1862 bool isDelete,
1863 std::shared_ptr<SLE const> const& before,
1865{
1866 if (isDelete)
1867 return;
1868
1869 if (after)
1870 {
1871 auto const type = after->getType();
1872 // AMM object changed
1873 if (type == ltAMM)
1874 {
1875 ammAccount_ = after->getAccountID(sfAccount);
1876 lptAMMBalanceAfter_ = after->getFieldAmount(sfLPTokenBalance);
1877 }
1878 // AMM pool changed
1879 else if (
1880 (type == ltRIPPLE_STATE && after->getFlags() & lsfAMMNode) ||
1881 (type == ltACCOUNT_ROOT && after->isFieldPresent(sfAMMID)))
1882 {
1883 ammPoolChanged_ = true;
1884 }
1885 }
1886
1887 if (before)
1888 {
1889 // AMM object changed
1890 if (before->getType() == ltAMM)
1891 {
1892 lptAMMBalanceBefore_ = before->getFieldAmount(sfLPTokenBalance);
1893 }
1894 }
1895}
1896
1897static bool
1899 STAmount const& amount,
1900 STAmount const& amount2,
1901 STAmount const& lptAMMBalance,
1902 ValidAMM::ZeroAllowed zeroAllowed)
1903{
1904 bool const positive = amount > beast::zero && amount2 > beast::zero &&
1905 lptAMMBalance > beast::zero;
1906 if (zeroAllowed == ValidAMM::ZeroAllowed::Yes)
1907 return positive ||
1908 (amount == beast::zero && amount2 == beast::zero &&
1909 lptAMMBalance == beast::zero);
1910 return positive;
1911}
1912
1913bool
1914ValidAMM::finalizeVote(bool enforce, beast::Journal const& j) const
1915{
1917 {
1918 // LPTokens and the pool can not change on vote
1919 // LCOV_EXCL_START
1920 JLOG(j.error()) << "AMMVote invariant failed: "
1923 << ammPoolChanged_;
1924 if (enforce)
1925 return false;
1926 // LCOV_EXCL_STOP
1927 }
1928
1929 return true;
1930}
1931
1932bool
1933ValidAMM::finalizeBid(bool enforce, beast::Journal const& j) const
1934{
1935 if (ammPoolChanged_)
1936 {
1937 // The pool can not change on bid
1938 // LCOV_EXCL_START
1939 JLOG(j.error()) << "AMMBid invariant failed: pool changed";
1940 if (enforce)
1941 return false;
1942 // LCOV_EXCL_STOP
1943 }
1944 // LPTokens are burnt, therefore there should be fewer LPTokens
1945 else if (
1948 *lptAMMBalanceAfter_ <= beast::zero))
1949 {
1950 // LCOV_EXCL_START
1951 JLOG(j.error()) << "AMMBid invariant failed: " << *lptAMMBalanceBefore_
1952 << " " << *lptAMMBalanceAfter_;
1953 if (enforce)
1954 return false;
1955 // LCOV_EXCL_STOP
1956 }
1957
1958 return true;
1959}
1960
1961bool
1963 STTx const& tx,
1964 ReadView const& view,
1965 bool enforce,
1966 beast::Journal const& j) const
1967{
1968 if (!ammAccount_)
1969 {
1970 // LCOV_EXCL_START
1971 JLOG(j.error())
1972 << "AMMCreate invariant failed: AMM object is not created";
1973 if (enforce)
1974 return false;
1975 // LCOV_EXCL_STOP
1976 }
1977 else
1978 {
1979 auto const [amount, amount2] = ammPoolHolds(
1980 view,
1981 *ammAccount_,
1982 tx[sfAmount].get<Issue>(),
1983 tx[sfAmount2].get<Issue>(),
1985 j);
1986 // Create invariant:
1987 // sqrt(amount * amount2) == LPTokens
1988 // all balances are greater than zero
1989 if (!validBalances(
1990 amount, amount2, *lptAMMBalanceAfter_, ZeroAllowed::No) ||
1991 ammLPTokens(amount, amount2, lptAMMBalanceAfter_->issue()) !=
1993 {
1994 JLOG(j.error()) << "AMMCreate invariant failed: " << amount << " "
1995 << amount2 << " " << *lptAMMBalanceAfter_;
1996 if (enforce)
1997 return false;
1998 }
1999 }
2000
2001 return true;
2002}
2003
2004bool
2005ValidAMM::finalizeDelete(bool enforce, TER res, beast::Journal const& j) const
2006{
2007 if (ammAccount_)
2008 {
2009 // LCOV_EXCL_START
2010 std::string const msg = (res == tesSUCCESS)
2011 ? "AMM object is not deleted on tesSUCCESS"
2012 : "AMM object is changed on tecINCOMPLETE";
2013 JLOG(j.error()) << "AMMDelete invariant failed: " << msg;
2014 if (enforce)
2015 return false;
2016 // LCOV_EXCL_STOP
2017 }
2018
2019 return true;
2020}
2021
2022bool
2023ValidAMM::finalizeDEX(bool enforce, beast::Journal const& j) const
2024{
2025 if (ammAccount_)
2026 {
2027 // LCOV_EXCL_START
2028 JLOG(j.error()) << "AMM swap invariant failed: AMM object changed";
2029 if (enforce)
2030 return false;
2031 // LCOV_EXCL_STOP
2032 }
2033
2034 return true;
2035}
2036
2037bool
2039 ripple::STTx const& tx,
2040 ripple::ReadView const& view,
2041 ZeroAllowed zeroAllowed,
2042 beast::Journal const& j) const
2043{
2044 auto const [amount, amount2] = ammPoolHolds(
2045 view,
2046 *ammAccount_,
2047 tx[sfAsset].get<Issue>(),
2048 tx[sfAsset2].get<Issue>(),
2050 j);
2051 // Deposit and Withdrawal invariant:
2052 // sqrt(amount * amount2) >= LPTokens
2053 // all balances are greater than zero
2054 // unless on last withdrawal
2055 auto const poolProductMean = root2(amount * amount2);
2056 bool const nonNegativeBalances =
2057 validBalances(amount, amount2, *lptAMMBalanceAfter_, zeroAllowed);
2058 bool const strongInvariantCheck = poolProductMean >= *lptAMMBalanceAfter_;
2059 // Allow for a small relative error if strongInvariantCheck fails
2060 auto weakInvariantCheck = [&]() {
2061 return *lptAMMBalanceAfter_ != beast::zero &&
2063 poolProductMean, Number{*lptAMMBalanceAfter_}, Number{1, -11});
2064 };
2065 if (!nonNegativeBalances ||
2066 (!strongInvariantCheck && !weakInvariantCheck()))
2067 {
2068 JLOG(j.error()) << "AMM " << tx.getTxnType() << " invariant failed: "
2070 << ammPoolChanged_ << " " << amount << " " << amount2
2071 << " " << poolProductMean << " "
2072 << lptAMMBalanceAfter_->getText() << " "
2073 << ((*lptAMMBalanceAfter_ == beast::zero)
2074 ? Number{1}
2075 : ((*lptAMMBalanceAfter_ - poolProductMean) /
2076 poolProductMean));
2077 return false;
2078 }
2079
2080 return true;
2081}
2082
2083bool
2085 ripple::STTx const& tx,
2086 ripple::ReadView const& view,
2087 bool enforce,
2088 beast::Journal const& j) const
2089{
2090 if (!ammAccount_)
2091 {
2092 // LCOV_EXCL_START
2093 JLOG(j.error()) << "AMMDeposit invariant failed: AMM object is deleted";
2094 if (enforce)
2095 return false;
2096 // LCOV_EXCL_STOP
2097 }
2098 else if (!generalInvariant(tx, view, ZeroAllowed::No, j) && enforce)
2099 return false;
2100
2101 return true;
2102}
2103
2104bool
2106 ripple::STTx const& tx,
2107 ripple::ReadView const& view,
2108 bool enforce,
2109 beast::Journal const& j) const
2110{
2111 if (!ammAccount_)
2112 {
2113 // Last Withdraw or Clawback deleted AMM
2114 }
2115 else if (!generalInvariant(tx, view, ZeroAllowed::Yes, j))
2116 {
2117 if (enforce)
2118 return false;
2119 }
2120
2121 return true;
2122}
2123
2124bool
2126 STTx const& tx,
2127 TER const result,
2128 XRPAmount const,
2129 ReadView const& view,
2130 beast::Journal const& j)
2131{
2132 // Delete may return tecINCOMPLETE if there are too many
2133 // trustlines to delete.
2134 if (result != tesSUCCESS && result != tecINCOMPLETE)
2135 return true;
2136
2137 bool const enforce = view.rules().enabled(fixAMMv1_3);
2138
2139 switch (tx.getTxnType())
2140 {
2141 case ttAMM_CREATE:
2142 return finalizeCreate(tx, view, enforce, j);
2143 case ttAMM_DEPOSIT:
2144 return finalizeDeposit(tx, view, enforce, j);
2145 case ttAMM_CLAWBACK:
2146 case ttAMM_WITHDRAW:
2147 return finalizeWithdraw(tx, view, enforce, j);
2148 case ttAMM_BID:
2149 return finalizeBid(enforce, j);
2150 case ttAMM_VOTE:
2151 return finalizeVote(enforce, j);
2152 case ttAMM_DELETE:
2153 return finalizeDelete(enforce, result, j);
2154 case ttCHECK_CASH:
2155 case ttOFFER_CREATE:
2156 case ttPAYMENT:
2157 return finalizeDEX(enforce, j);
2158 default:
2159 break;
2160 }
2161
2162 return true;
2163}
2164
2165//------------------------------------------------------------------------------
2166
2169{
2170 XRPL_ASSERT(
2171 from.getType() == ltVAULT,
2172 "ValidVault::Vault::make : from Vault object");
2173
2174 ValidVault::Vault self;
2175 self.key = from.key();
2176 self.asset = from.at(sfAsset);
2177 self.pseudoId = from.getAccountID(sfAccount);
2178 self.shareMPTID = from.getFieldH192(sfShareMPTID);
2179 self.assetsTotal = from.at(sfAssetsTotal);
2180 self.assetsAvailable = from.at(sfAssetsAvailable);
2181 self.assetsMaximum = from.at(sfAssetsMaximum);
2182 self.lossUnrealized = from.at(sfLossUnrealized);
2183 return self;
2184}
2185
2188{
2189 XRPL_ASSERT(
2190 from.getType() == ltMPTOKEN_ISSUANCE,
2191 "ValidVault::Shares::make : from MPTokenIssuance object");
2192
2193 ValidVault::Shares self;
2194 self.share = MPTIssue(
2195 makeMptID(from.getFieldU32(sfSequence), from.getAccountID(sfIssuer)));
2196 self.sharesTotal = from.at(sfOutstandingAmount);
2197 self.sharesMaximum = from[~sfMaximumAmount].value_or(maxMPTokenAmount);
2198 return self;
2199}
2200
2201void
2203 bool isDelete,
2204 std::shared_ptr<SLE const> const& before,
2206{
2207 // If `before` is empty, this means an object is being created, in which
2208 // case `isDelete` must be false. Otherwise `before` and `after` are set and
2209 // `isDelete` indicates whether an object is being deleted or modified.
2210 XRPL_ASSERT(
2211 after != nullptr && (before != nullptr || !isDelete),
2212 "ripple::ValidVault::visitEntry : some object is available");
2213
2214 // Number balanceDelta will capture the difference (delta) between "before"
2215 // state (zero if created) and "after" state (zero if destroyed), so the
2216 // invariants can validate that the change in account balances matches the
2217 // change in vault balances, stored to deltas_ at the end of this function.
2218 Number balanceDelta{};
2219
2220 std::int8_t sign = 0;
2221 if (before)
2222 {
2223 switch (before->getType())
2224 {
2225 case ltVAULT:
2226 beforeVault_.push_back(Vault::make(*before));
2227 break;
2228 case ltMPTOKEN_ISSUANCE:
2229 // At this moment we have no way of telling if this object holds
2230 // vault shares or something else. Save it for finalize.
2231 beforeMPTs_.push_back(Shares::make(*before));
2232 balanceDelta = static_cast<std::int64_t>(
2233 before->getFieldU64(sfOutstandingAmount));
2234 sign = 1;
2235 break;
2236 case ltMPTOKEN:
2237 balanceDelta =
2238 static_cast<std::int64_t>(before->getFieldU64(sfMPTAmount));
2239 sign = -1;
2240 break;
2241 case ltACCOUNT_ROOT:
2242 case ltRIPPLE_STATE:
2243 balanceDelta = before->getFieldAmount(sfBalance);
2244 sign = -1;
2245 break;
2246 default:;
2247 }
2248 }
2249
2250 if (!isDelete && after)
2251 {
2252 switch (after->getType())
2253 {
2254 case ltVAULT:
2255 afterVault_.push_back(Vault::make(*after));
2256 break;
2257 case ltMPTOKEN_ISSUANCE:
2258 // At this moment we have no way of telling if this object holds
2259 // vault shares or something else. Save it for finalize.
2260 afterMPTs_.push_back(Shares::make(*after));
2261 balanceDelta -= Number(static_cast<std::int64_t>(
2262 after->getFieldU64(sfOutstandingAmount)));
2263 sign = 1;
2264 break;
2265 case ltMPTOKEN:
2266 balanceDelta -= Number(
2267 static_cast<std::int64_t>(after->getFieldU64(sfMPTAmount)));
2268 sign = -1;
2269 break;
2270 case ltACCOUNT_ROOT:
2271 case ltRIPPLE_STATE:
2272 balanceDelta -= Number(after->getFieldAmount(sfBalance));
2273 sign = -1;
2274 break;
2275 default:;
2276 }
2277 }
2278
2279 uint256 const key = (before ? before->key() : after->key());
2280 // Append to deltas if sign is non-zero, i.e. an object of an interesting
2281 // type has been updated. A transaction may update an object even when
2282 // its balance has not changed, e.g. transaction fee equals the amount
2283 // transferred to the account. We intentionally do not compare balanceDelta
2284 // against zero, to avoid missing such updates.
2285 if (sign != 0)
2286 deltas_[key] = balanceDelta * sign;
2287}
2288
2289bool
2291 STTx const& tx,
2292 TER const ret,
2293 XRPAmount const fee,
2294 ReadView const& view,
2295 beast::Journal const& j)
2296{
2297 bool const enforce = view.rules().enabled(featureSingleAssetVault);
2298
2299 if (!isTesSuccess(ret))
2300 return true; // Do not perform checks
2301
2302 if (afterVault_.empty() && beforeVault_.empty())
2303 {
2305 {
2306 JLOG(j.fatal()) << //
2307 "Invariant failed: vault operation succeeded without modifying "
2308 "a vault";
2309 XRPL_ASSERT(
2310 enforce, "ripple::ValidVault::finalize : vault noop invariant");
2311 return !enforce;
2312 }
2313
2314 return true; // Not a vault operation
2315 }
2316 else if (!hasPrivilege(tx, mustModifyVault)) // TODO: mayModifyVault
2317 {
2318 JLOG(j.fatal()) << //
2319 "Invariant failed: vault updated by a wrong transaction type";
2320 XRPL_ASSERT(
2321 enforce,
2322 "ripple::ValidVault::finalize : illegal vault transaction "
2323 "invariant");
2324 return !enforce; // Also not a vault operation
2325 }
2326
2327 if (beforeVault_.size() > 1 || afterVault_.size() > 1)
2328 {
2329 JLOG(j.fatal()) << //
2330 "Invariant failed: vault operation updated more than single vault";
2331 XRPL_ASSERT(
2332 enforce, "ripple::ValidVault::finalize : single vault invariant");
2333 return !enforce; // That's all we can do here
2334 }
2335
2336 auto const txnType = tx.getTxnType();
2337
2338 // We do special handling for ttVAULT_DELETE first, because it's the only
2339 // vault-modifying transaction without an "after" state of the vault
2340 if (afterVault_.empty())
2341 {
2342 if (txnType != ttVAULT_DELETE)
2343 {
2344 JLOG(j.fatal()) << //
2345 "Invariant failed: vault deleted by a wrong transaction type";
2346 XRPL_ASSERT(
2347 enforce,
2348 "ripple::ValidVault::finalize : illegal vault deletion "
2349 "invariant");
2350 return !enforce; // That's all we can do here
2351 }
2352
2353 // Note, if afterVault_ is empty then we know that beforeVault_ is not
2354 // empty, as enforced at the top of this function
2355 auto const& beforeVault = beforeVault_[0];
2356
2357 // At this moment we only know a vault is being deleted and there
2358 // might be some MPTokenIssuance objects which are deleted in the
2359 // same transaction. Find the one matching this vault.
2360 auto const deletedShares = [&]() -> std::optional<Shares> {
2361 for (auto const& e : beforeMPTs_)
2362 {
2363 if (e.share.getMptID() == beforeVault.shareMPTID)
2364 return std::move(e);
2365 }
2366 return std::nullopt;
2367 }();
2368
2369 if (!deletedShares)
2370 {
2371 JLOG(j.fatal()) << "Invariant failed: deleted vault must also "
2372 "delete shares";
2373 XRPL_ASSERT(
2374 enforce,
2375 "ripple::ValidVault::finalize : shares deletion invariant");
2376 return !enforce; // That's all we can do here
2377 }
2378
2379 bool result = true;
2380 if (deletedShares->sharesTotal != 0)
2381 {
2382 JLOG(j.fatal()) << "Invariant failed: deleted vault must have no "
2383 "shares outstanding";
2384 result = false;
2385 }
2386 if (beforeVault.assetsTotal != zero)
2387 {
2388 JLOG(j.fatal()) << "Invariant failed: deleted vault must have no "
2389 "assets outstanding";
2390 result = false;
2391 }
2392 if (beforeVault.assetsAvailable != zero)
2393 {
2394 JLOG(j.fatal()) << "Invariant failed: deleted vault must have no "
2395 "assets available";
2396 result = false;
2397 }
2398
2399 return result;
2400 }
2401 else if (txnType == ttVAULT_DELETE)
2402 {
2403 JLOG(j.fatal()) << "Invariant failed: vault deletion succeeded without "
2404 "deleting a vault";
2405 XRPL_ASSERT(
2406 enforce, "ripple::ValidVault::finalize : vault deletion invariant");
2407 return !enforce; // That's all we can do here
2408 }
2409
2410 // Note, `afterVault_.empty()` is handled above
2411 auto const& afterVault = afterVault_[0];
2412 XRPL_ASSERT(
2413 beforeVault_.empty() || beforeVault_[0].key == afterVault.key,
2414 "ripple::ValidVault::finalize : single vault operation");
2415
2416 auto const updatedShares = [&]() -> std::optional<Shares> {
2417 // At this moment we only know that a vault is being updated and there
2418 // might be some MPTokenIssuance objects which are also updated in the
2419 // same transaction. Find the one matching the shares to this vault.
2420 // Note, we expect updatedMPTs collection to be extremely small. For
2421 // such collections linear search is faster than lookup.
2422 for (auto const& e : afterMPTs_)
2423 {
2424 if (e.share.getMptID() == afterVault.shareMPTID)
2425 return e;
2426 }
2427
2428 auto const sleShares =
2429 view.read(keylet::mptIssuance(afterVault.shareMPTID));
2430
2431 return sleShares ? std::optional<Shares>(Shares::make(*sleShares))
2432 : std::nullopt;
2433 }();
2434
2435 bool result = true;
2436
2437 // Universal transaction checks
2438 if (!beforeVault_.empty())
2439 {
2440 auto const& beforeVault = beforeVault_[0];
2441 if (afterVault.asset != beforeVault.asset ||
2442 afterVault.pseudoId != beforeVault.pseudoId ||
2443 afterVault.shareMPTID != beforeVault.shareMPTID)
2444 {
2445 JLOG(j.fatal())
2446 << "Invariant failed: violation of vault immutable data";
2447 result = false;
2448 }
2449 }
2450
2451 if (!updatedShares)
2452 {
2453 JLOG(j.fatal()) << "Invariant failed: updated vault must have shares";
2454 XRPL_ASSERT(
2455 enforce,
2456 "ripple::ValidVault::finalize : vault has shares invariant");
2457 return !enforce; // That's all we can do here
2458 }
2459
2460 if (updatedShares->sharesTotal == 0)
2461 {
2462 if (afterVault.assetsTotal != zero)
2463 {
2464 JLOG(j.fatal()) << "Invariant failed: updated zero sized "
2465 "vault must have no assets outstanding";
2466 result = false;
2467 }
2468 if (afterVault.assetsAvailable != zero)
2469 {
2470 JLOG(j.fatal()) << "Invariant failed: updated zero sized "
2471 "vault must have no assets available";
2472 result = false;
2473 }
2474 }
2475 else if (updatedShares->sharesTotal > updatedShares->sharesMaximum)
2476 {
2477 JLOG(j.fatal()) //
2478 << "Invariant failed: updated shares must not exceed maximum "
2479 << updatedShares->sharesMaximum;
2480 result = false;
2481 }
2482
2483 if (afterVault.assetsAvailable < zero)
2484 {
2485 JLOG(j.fatal())
2486 << "Invariant failed: assets available must be positive";
2487 result = false;
2488 }
2489
2490 if (afterVault.assetsAvailable > afterVault.assetsTotal)
2491 {
2492 JLOG(j.fatal()) << "Invariant failed: assets available must "
2493 "not be greater than assets outstanding";
2494 result = false;
2495 }
2496 else if (
2497 afterVault.lossUnrealized >
2498 afterVault.assetsTotal - afterVault.assetsAvailable)
2499 {
2500 JLOG(j.fatal()) //
2501 << "Invariant failed: loss unrealized must not exceed "
2502 "the difference between assets outstanding and available";
2503 result = false;
2504 }
2505
2506 if (afterVault.assetsTotal < zero)
2507 {
2508 JLOG(j.fatal())
2509 << "Invariant failed: assets outstanding must be positive";
2510 result = false;
2511 }
2512
2513 if (afterVault.assetsMaximum < zero)
2514 {
2515 JLOG(j.fatal()) << "Invariant failed: assets maximum must be positive";
2516 result = false;
2517 }
2518
2519 // Thanks to this check we can simply do `assert(!beforeVault_.empty()` when
2520 // enforcing invariants on transaction types other than ttVAULT_CREATE
2521 if (beforeVault_.empty() && txnType != ttVAULT_CREATE)
2522 {
2523 JLOG(j.fatal()) << //
2524 "Invariant failed: vault created by a wrong transaction type";
2525 XRPL_ASSERT(
2526 enforce, "ripple::ValidVault::finalize : vault creation invariant");
2527 return !enforce; // That's all we can do here
2528 }
2529
2530 if (!beforeVault_.empty() &&
2531 afterVault.lossUnrealized != beforeVault_[0].lossUnrealized)
2532 {
2533 JLOG(j.fatal()) << //
2534 "Invariant failed: vault transaction must not change loss "
2535 "unrealized";
2536 result = false;
2537 }
2538
2539 auto const beforeShares = [&]() -> std::optional<Shares> {
2540 if (beforeVault_.empty())
2541 return std::nullopt;
2542 auto const& beforeVault = beforeVault_[0];
2543
2544 for (auto const& e : beforeMPTs_)
2545 {
2546 if (e.share.getMptID() == beforeVault.shareMPTID)
2547 return std::move(e);
2548 }
2549 return std::nullopt;
2550 }();
2551
2552 if (!beforeShares &&
2553 (tx.getTxnType() == ttVAULT_DEPOSIT || //
2554 tx.getTxnType() == ttVAULT_WITHDRAW || //
2555 tx.getTxnType() == ttVAULT_CLAWBACK))
2556 {
2557 JLOG(j.fatal()) << "Invariant failed: vault operation succeeded "
2558 "without updating shares";
2559 XRPL_ASSERT(
2560 enforce, "ripple::ValidVault::finalize : shares noop invariant");
2561 return !enforce; // That's all we can do here
2562 }
2563
2564 auto const& vaultAsset = afterVault.asset;
2565 auto const deltaAssets = [&](AccountID const& id) -> std::optional<Number> {
2566 auto const get = //
2567 [&](auto const& it, std::int8_t sign = 1) -> std::optional<Number> {
2568 if (it == deltas_.end())
2569 return std::nullopt;
2570
2571 return it->second * sign;
2572 };
2573
2574 return std::visit(
2575 [&]<typename TIss>(TIss const& issue) {
2576 if constexpr (std::is_same_v<TIss, Issue>)
2577 {
2578 if (isXRP(issue))
2579 return get(deltas_.find(keylet::account(id).key));
2580 return get(
2581 deltas_.find(keylet::line(id, issue).key),
2582 id > issue.getIssuer() ? -1 : 1);
2583 }
2584 else if constexpr (std::is_same_v<TIss, MPTIssue>)
2585 {
2586 return get(deltas_.find(
2587 keylet::mptoken(issue.getMptID(), id).key));
2588 }
2589 },
2590 vaultAsset.value());
2591 };
2592 auto const deltaAssetsTxAccount = [&]() -> std::optional<Number> {
2593 auto ret = deltaAssets(tx[sfAccount]);
2594 // Nothing returned or not XRP transaction
2595 if (!ret.has_value() || !vaultAsset.native())
2596 return ret;
2597
2598 // Delegated transaction; no need to compensate for fees
2599 if (auto const delegate = tx[~sfDelegate];
2600 delegate.has_value() && *delegate != tx[sfAccount])
2601 return ret;
2602
2603 *ret += fee.drops();
2604 if (*ret == zero)
2605 return std::nullopt;
2606
2607 return ret;
2608 };
2609 auto const deltaShares = [&](AccountID const& id) -> std::optional<Number> {
2610 auto const it = [&]() {
2611 if (id == afterVault.pseudoId)
2612 return deltas_.find(
2613 keylet::mptIssuance(afterVault.shareMPTID).key);
2614 return deltas_.find(keylet::mptoken(afterVault.shareMPTID, id).key);
2615 }();
2616
2617 return it != deltas_.end() ? std::optional<Number>(it->second)
2618 : std::nullopt;
2619 };
2620
2621 // Technically this does not need to be a lambda, but it's more
2622 // convenient thanks to early "return false"; the not-so-nice
2623 // alternatives are several layers of nested if/else or more complex
2624 // (i.e. brittle) if statements.
2625 result &= [&]() {
2626 switch (txnType)
2627 {
2628 case ttVAULT_CREATE: {
2629 bool result = true;
2630
2631 if (!beforeVault_.empty())
2632 {
2633 JLOG(j.fatal()) //
2634 << "Invariant failed: create operation must not have "
2635 "updated a vault";
2636 result = false;
2637 }
2638
2639 if (afterVault.assetsAvailable != zero ||
2640 afterVault.assetsTotal != zero ||
2641 afterVault.lossUnrealized != zero ||
2642 updatedShares->sharesTotal != 0)
2643 {
2644 JLOG(j.fatal()) //
2645 << "Invariant failed: created vault must be empty";
2646 result = false;
2647 }
2648
2649 if (afterVault.pseudoId != updatedShares->share.getIssuer())
2650 {
2651 JLOG(j.fatal()) //
2652 << "Invariant failed: shares issuer and vault "
2653 "pseudo-account must be the same";
2654 result = false;
2655 }
2656
2657 auto const sleSharesIssuer = view.read(
2658 keylet::account(updatedShares->share.getIssuer()));
2659 if (!sleSharesIssuer)
2660 {
2661 JLOG(j.fatal()) //
2662 << "Invariant failed: shares issuer must exist";
2663 return false;
2664 }
2665
2666 if (!isPseudoAccount(sleSharesIssuer))
2667 {
2668 JLOG(j.fatal()) //
2669 << "Invariant failed: shares issuer must be a "
2670 "pseudo-account";
2671 result = false;
2672 }
2673
2674 if (auto const vaultId = (*sleSharesIssuer)[~sfVaultID];
2675 !vaultId || *vaultId != afterVault.key)
2676 {
2677 JLOG(j.fatal()) //
2678 << "Invariant failed: shares issuer pseudo-account "
2679 "must point back to the vault";
2680 result = false;
2681 }
2682
2683 return result;
2684 }
2685 case ttVAULT_SET: {
2686 bool result = true;
2687
2688 XRPL_ASSERT(
2689 !beforeVault_.empty(),
2690 "ripple::ValidVault::finalize : set updated a vault");
2691 auto const& beforeVault = beforeVault_[0];
2692
2693 auto const vaultDeltaAssets = deltaAssets(afterVault.pseudoId);
2694 if (vaultDeltaAssets)
2695 {
2696 JLOG(j.fatal()) << //
2697 "Invariant failed: set must not change vault balance";
2698 result = false;
2699 }
2700
2701 if (beforeVault.assetsTotal != afterVault.assetsTotal)
2702 {
2703 JLOG(j.fatal()) << //
2704 "Invariant failed: set must not change assets "
2705 "outstanding";
2706 result = false;
2707 }
2708
2709 if (afterVault.assetsMaximum > zero &&
2710 afterVault.assetsTotal > afterVault.assetsMaximum)
2711 {
2712 JLOG(j.fatal()) << //
2713 "Invariant failed: set assets outstanding must not "
2714 "exceed assets maximum";
2715 result = false;
2716 }
2717
2718 if (beforeVault.assetsAvailable != afterVault.assetsAvailable)
2719 {
2720 JLOG(j.fatal()) << //
2721 "Invariant failed: set must not change assets "
2722 "available";
2723 result = false;
2724 }
2725
2726 if (beforeShares && updatedShares &&
2727 beforeShares->sharesTotal != updatedShares->sharesTotal)
2728 {
2729 JLOG(j.fatal()) << //
2730 "Invariant failed: set must not change shares "
2731 "outstanding";
2732 result = false;
2733 }
2734
2735 return result;
2736 }
2737 case ttVAULT_DEPOSIT: {
2738 bool result = true;
2739
2740 XRPL_ASSERT(
2741 !beforeVault_.empty(),
2742 "ripple::ValidVault::finalize : deposit updated a vault");
2743 auto const& beforeVault = beforeVault_[0];
2744
2745 auto const vaultDeltaAssets = deltaAssets(afterVault.pseudoId);
2746
2747 if (!vaultDeltaAssets)
2748 {
2749 JLOG(j.fatal()) << //
2750 "Invariant failed: deposit must change vault balance";
2751 return false; // That's all we can do
2752 }
2753
2754 if (*vaultDeltaAssets > tx[sfAmount])
2755 {
2756 JLOG(j.fatal()) << //
2757 "Invariant failed: deposit must not change vault "
2758 "balance by more than deposited amount";
2759 result = false;
2760 }
2761
2762 if (*vaultDeltaAssets <= zero)
2763 {
2764 JLOG(j.fatal()) << //
2765 "Invariant failed: deposit must increase vault balance";
2766 result = false;
2767 }
2768
2769 // Any payments (including deposits) made by the issuer
2770 // do not change their balance, but create funds instead.
2771 bool const issuerDeposit = [&]() -> bool {
2772 if (vaultAsset.native())
2773 return false;
2774 return tx[sfAccount] == vaultAsset.getIssuer();
2775 }();
2776
2777 if (!issuerDeposit)
2778 {
2779 auto const accountDeltaAssets = deltaAssetsTxAccount();
2780 if (!accountDeltaAssets)
2781 {
2782 JLOG(j.fatal()) << //
2783 "Invariant failed: deposit must change depositor "
2784 "balance";
2785 return false;
2786 }
2787
2788 if (*accountDeltaAssets >= zero)
2789 {
2790 JLOG(j.fatal()) << //
2791 "Invariant failed: deposit must decrease depositor "
2792 "balance";
2793 result = false;
2794 }
2795
2796 if (*accountDeltaAssets * -1 != *vaultDeltaAssets)
2797 {
2798 JLOG(j.fatal()) << //
2799 "Invariant failed: deposit must change vault and "
2800 "depositor balance by equal amount";
2801 result = false;
2802 }
2803 }
2804
2805 if (afterVault.assetsMaximum > zero &&
2806 afterVault.assetsTotal > afterVault.assetsMaximum)
2807 {
2808 JLOG(j.fatal()) << //
2809 "Invariant failed: deposit assets outstanding must not "
2810 "exceed assets maximum";
2811 result = false;
2812 }
2813
2814 auto const accountDeltaShares = deltaShares(tx[sfAccount]);
2815 if (!accountDeltaShares)
2816 {
2817 JLOG(j.fatal()) << //
2818 "Invariant failed: deposit must change depositor "
2819 "shares";
2820 return false; // That's all we can do
2821 }
2822
2823 if (*accountDeltaShares <= zero)
2824 {
2825 JLOG(j.fatal()) << //
2826 "Invariant failed: deposit must increase depositor "
2827 "shares";
2828 result = false;
2829 }
2830
2831 auto const vaultDeltaShares = deltaShares(afterVault.pseudoId);
2832 if (!vaultDeltaShares || *vaultDeltaShares == zero)
2833 {
2834 JLOG(j.fatal()) << //
2835 "Invariant failed: deposit must change vault shares";
2836 return false; // That's all we can do
2837 }
2838
2839 if (*vaultDeltaShares * -1 != *accountDeltaShares)
2840 {
2841 JLOG(j.fatal()) << //
2842 "Invariant failed: deposit must change depositor and "
2843 "vault shares by equal amount";
2844 result = false;
2845 }
2846
2847 if (beforeVault.assetsTotal + *vaultDeltaAssets !=
2848 afterVault.assetsTotal)
2849 {
2850 JLOG(j.fatal()) << "Invariant failed: deposit and assets "
2851 "outstanding must add up";
2852 result = false;
2853 }
2854 if (beforeVault.assetsAvailable + *vaultDeltaAssets !=
2855 afterVault.assetsAvailable)
2856 {
2857 JLOG(j.fatal()) << "Invariant failed: deposit and assets "
2858 "available must add up";
2859 result = false;
2860 }
2861
2862 return result;
2863 }
2864 case ttVAULT_WITHDRAW: {
2865 bool result = true;
2866
2867 XRPL_ASSERT(
2868 !beforeVault_.empty(),
2869 "ripple::ValidVault::finalize : withdrawal updated a "
2870 "vault");
2871 auto const& beforeVault = beforeVault_[0];
2872
2873 auto const vaultDeltaAssets = deltaAssets(afterVault.pseudoId);
2874
2875 if (!vaultDeltaAssets)
2876 {
2877 JLOG(j.fatal()) << "Invariant failed: withdrawal must "
2878 "change vault balance";
2879 return false; // That's all we can do
2880 }
2881
2882 if (*vaultDeltaAssets >= zero)
2883 {
2884 JLOG(j.fatal()) << "Invariant failed: withdrawal must "
2885 "decrease vault balance";
2886 result = false;
2887 }
2888
2889 // Any payments (including withdrawal) going to the issuer
2890 // do not change their balance, but destroy funds instead.
2891 bool const issuerWithdrawal = [&]() -> bool {
2892 if (vaultAsset.native())
2893 return false;
2894 auto const destination =
2895 tx[~sfDestination].value_or(tx[sfAccount]);
2896 return destination == vaultAsset.getIssuer();
2897 }();
2898
2899 if (!issuerWithdrawal)
2900 {
2901 auto const accountDeltaAssets = deltaAssetsTxAccount();
2902 auto const otherAccountDelta =
2903 [&]() -> std::optional<Number> {
2904 if (auto const destination = tx[~sfDestination];
2905 destination && *destination != tx[sfAccount])
2906 return deltaAssets(*destination);
2907 return std::nullopt;
2908 }();
2909
2910 if (accountDeltaAssets.has_value() ==
2911 otherAccountDelta.has_value())
2912 {
2913 JLOG(j.fatal()) << //
2914 "Invariant failed: withdrawal must change one "
2915 "destination balance";
2916 return false;
2917 }
2918
2919 auto const destinationDelta = //
2920 accountDeltaAssets ? *accountDeltaAssets
2921 : *otherAccountDelta;
2922
2923 if (destinationDelta <= zero)
2924 {
2925 JLOG(j.fatal()) << //
2926 "Invariant failed: withdrawal must increase "
2927 "destination balance";
2928 result = false;
2929 }
2930
2931 if (*vaultDeltaAssets * -1 != destinationDelta)
2932 {
2933 JLOG(j.fatal()) << //
2934 "Invariant failed: withdrawal must change vault "
2935 "and destination balance by equal amount";
2936 result = false;
2937 }
2938 }
2939
2940 auto const accountDeltaShares = deltaShares(tx[sfAccount]);
2941 if (!accountDeltaShares)
2942 {
2943 JLOG(j.fatal()) << //
2944 "Invariant failed: withdrawal must change depositor "
2945 "shares";
2946 return false;
2947 }
2948
2949 if (*accountDeltaShares >= zero)
2950 {
2951 JLOG(j.fatal()) << //
2952 "Invariant failed: withdrawal must decrease depositor "
2953 "shares";
2954 result = false;
2955 }
2956
2957 auto const vaultDeltaShares = deltaShares(afterVault.pseudoId);
2958 if (!vaultDeltaShares || *vaultDeltaShares == zero)
2959 {
2960 JLOG(j.fatal()) << //
2961 "Invariant failed: withdrawal must change vault shares";
2962 return false; // That's all we can do
2963 }
2964
2965 if (*vaultDeltaShares * -1 != *accountDeltaShares)
2966 {
2967 JLOG(j.fatal()) << //
2968 "Invariant failed: withdrawal must change depositor "
2969 "and vault shares by equal amount";
2970 result = false;
2971 }
2972
2973 // Note, vaultBalance is negative (see check above)
2974 if (beforeVault.assetsTotal + *vaultDeltaAssets !=
2975 afterVault.assetsTotal)
2976 {
2977 JLOG(j.fatal()) << "Invariant failed: withdrawal and "
2978 "assets outstanding must add up";
2979 result = false;
2980 }
2981
2982 if (beforeVault.assetsAvailable + *vaultDeltaAssets !=
2983 afterVault.assetsAvailable)
2984 {
2985 JLOG(j.fatal()) << "Invariant failed: withdrawal and "
2986 "assets available must add up";
2987 result = false;
2988 }
2989
2990 return result;
2991 }
2992 case ttVAULT_CLAWBACK: {
2993 bool result = true;
2994
2995 XRPL_ASSERT(
2996 !beforeVault_.empty(),
2997 "ripple::ValidVault::finalize : clawback updated a vault");
2998 auto const& beforeVault = beforeVault_[0];
2999
3000 if (vaultAsset.native() ||
3001 vaultAsset.getIssuer() != tx[sfAccount])
3002 {
3003 JLOG(j.fatal()) << //
3004 "Invariant failed: clawback may only be performed by "
3005 "the asset issuer";
3006 return false; // That's all we can do
3007 }
3008
3009 auto const vaultDeltaAssets = deltaAssets(afterVault.pseudoId);
3010
3011 if (!vaultDeltaAssets)
3012 {
3013 JLOG(j.fatal()) << //
3014 "Invariant failed: clawback must change vault balance";
3015 return false; // That's all we can do
3016 }
3017
3018 if (*vaultDeltaAssets >= zero)
3019 {
3020 JLOG(j.fatal()) << //
3021 "Invariant failed: clawback must decrease vault "
3022 "balance";
3023 result = false;
3024 }
3025
3026 auto const accountDeltaShares = deltaShares(tx[sfHolder]);
3027 if (!accountDeltaShares)
3028 {
3029 JLOG(j.fatal()) << //
3030 "Invariant failed: clawback must change holder shares";
3031 return false; // That's all we can do
3032 }
3033
3034 if (*accountDeltaShares >= zero)
3035 {
3036 JLOG(j.fatal()) << //
3037 "Invariant failed: clawback must decrease holder "
3038 "shares";
3039 result = false;
3040 }
3041
3042 auto const vaultDeltaShares = deltaShares(afterVault.pseudoId);
3043 if (!vaultDeltaShares || *vaultDeltaShares == zero)
3044 {
3045 JLOG(j.fatal()) << //
3046 "Invariant failed: clawback must change vault shares";
3047 return false; // That's all we can do
3048 }
3049
3050 if (*vaultDeltaShares * -1 != *accountDeltaShares)
3051 {
3052 JLOG(j.fatal()) << //
3053 "Invariant failed: clawback must change holder and "
3054 "vault shares by equal amount";
3055 result = false;
3056 }
3057
3058 if (beforeVault.assetsTotal + *vaultDeltaAssets !=
3059 afterVault.assetsTotal)
3060 {
3061 JLOG(j.fatal()) << //
3062 "Invariant failed: clawback and assets outstanding "
3063 "must add up";
3064 result = false;
3065 }
3066
3067 if (beforeVault.assetsAvailable + *vaultDeltaAssets !=
3068 afterVault.assetsAvailable)
3069 {
3070 JLOG(j.fatal()) << //
3071 "Invariant failed: clawback and assets available must "
3072 "add up";
3073 result = false;
3074 }
3075
3076 return result;
3077 }
3078
3079 default:
3080 // LCOV_EXCL_START
3081 UNREACHABLE(
3082 "ripple::ValidVault::finalize : unknown transaction type");
3083 return false;
3084 // LCOV_EXCL_STOP
3085 }
3086 }();
3087
3088 if (!result)
3089 {
3090 // The comment at the top of this file starting with "assert(enforce)"
3091 // explains this assert.
3092 XRPL_ASSERT(enforce, "ripple::ValidVault::finalize : vault invariants");
3093 return !enforce;
3094 }
3095
3096 return true;
3097}
3098
3099} // namespace ripple
T begin(T... args)
A generic endpoint for log messages.
Definition Journal.h:41
Stream fatal() const
Definition Journal.h:333
Stream error() const
Definition Journal.h:327
Stream debug() const
Definition Journal.h:309
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:14
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:32
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:99
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:111
Identifies fields.
Definition SField.h:127
Currency const & getCurrency() const
Definition STAmount.h:483
XRPAmount xrp() const
Definition STAmount.cpp:264
int signum() const noexcept
Definition STAmount.h:495
AccountID const & getIssuer() const
Definition STAmount.h:489
bool native() const noexcept
Definition STAmount.h:439
STAmount zeroed() const
Returns a zero value with the same issuer and currency.
Definition STAmount.h:501
LedgerEntryType getType() const
uint256 const & key() const
Returns the 'key' (or 'index') of this item.
uint192 getFieldH192(SField const &field) const
Definition STObject.cpp:620
AccountID getAccountID(SField const &field) const
Definition STObject.cpp:638
T::value_type at(TypedField< T > const &f) const
Get the value of a field.
Definition STObject.h:1028
std::uint32_t getFieldU32(SField const &field) const
Definition STObject.cpp:596
STAmount const & getFieldAmount(SField const &field) const
Definition STObject.cpp:652
uint256 getHash(HashPrefix prefix) const
Definition STObject.cpp:376
bool isFieldPresent(SField const &field) const
Definition STObject.cpp:465
uint256 getFieldH256(SField const &field) const
Definition STObject.cpp:626
TxType getTxnType() const
Definition STTx.h:209
uint256 getTransactionID() const
Definition STTx.h:221
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 &)
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
std::vector< std::string > errors_
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< Shares > beforeMPTs_
std::vector< Vault > beforeVault_
std::unordered_map< uint256, Number > deltas_
static Number constexpr zero
std::vector< Shares > afterMPTs_
std::vector< Vault > afterVault_
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
constexpr value_type drops() const
Returns the number of drops.
Definition XRPAmount.h:158
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:436
T count_if(T... args)
T emplace_back(T... args)
T empty(T... args)
T end(T... args)
T invoke(T... args)
T is_same_v
std::set< std::pair< AccountID, Slice > > makeSorted(STArray const &credentials)
Keylet mptoken(MPTID const &issuanceID, AccountID const &holder) noexcept
Definition Indexes.cpp:521
Keylet permissionedDomain(AccountID const &account, std::uint32_t seq) noexcept
Definition Indexes.cpp:551
Keylet line(AccountID const &id0, AccountID const &id1, Currency const &currency) noexcept
The index of a trust line for a given currency.
Definition Indexes.cpp:225
Keylet mptIssuance(std::uint32_t seq, AccountID const &issuer) noexcept
Definition Indexes.cpp:507
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:165
Keylet unchecked(uint256 const &key) noexcept
Any ledger entry.
Definition Indexes.cpp:349
Keylet nftpage_min(AccountID const &owner)
NFT page keylets.
Definition Indexes.cpp:384
Keylet nftpage_max(AccountID const &owner)
A keylet for the owner's last possible NFT page.
Definition Indexes.cpp:392
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:6
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
Definition Issue.h:96
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:58
bool isXRP(AccountID const &c)
Definition AccountID.h:71
constexpr base_uint< Bits, Tag > operator|(base_uint< Bits, Tag > const &a, base_uint< Bits, Tag > const &b)
Definition base_uint.h:596
static bool validBalances(STAmount const &amount, STAmount const &amount2, STAmount const &lptAMMBalance, ValidAMM::ZeroAllowed zeroAllowed)
base_uint< 256 > uint256
Definition base_uint.h:539
std::size_t constexpr maxPermissionedDomainCredentialsArraySize
The maximum number of credentials can be passed in array for permissioned domain.
Definition Protocol.h:94
bool hasPrivilege(STTx const &tx, Privilege priv)
constexpr std::enable_if_t< std::is_integral_v< Dest > &&std::is_integral_v< Src >, Dest > safe_cast(Src s) noexcept
Definition safe_cast.h:22
std::uint64_t constexpr maxMPTokenAmount
The maximum amount of MPTokenIssuance.
Definition Protocol.h:100
@ 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:46
Buffer sign(PublicKey const &pk, SecretKey const &sk, Slice const &message)
Generate a signature for a message.
std::array< keyletDesc< AccountID const & >, 6 > const directAccountKeylets
Definition Indexes.h:365
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:12
@ tecINCOMPLETE
Definition TER.h:317
@ tesSUCCESS
Definition TER.h:226
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j)
Definition View.cpp:368
bool isTesSuccess(TER x) noexcept
Definition TER.h:659
STAmount ammLPTokens(STAmount const &asset1, STAmount const &asset2, Issue const &lptIssue)
Calculate LP Tokens given AMM pool reserves.
Definition AMMHelpers.cpp:6
T get(Section const &section, std::string const &name, T const &defaultValue=T{})
Retrieve a key/value pair from a section.
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition View.cpp:3247
@ transactionID
transaction plus signature to give transaction ID
MPTID makeMptID(std::uint32_t sequence, AccountID const &account)
Definition Indexes.cpp:151
std::vector< SField const * > const & getPseudoAccountFields()
Definition View.cpp:1073
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:110
Number root2(Number f)
Definition Number.cpp:682
bool isPseudoAccount(std::shared_ptr< SLE const > sleAcct)
Definition View.cpp:1099
A pair of SHAMap key and LedgerEntryType.
Definition Keylet.h:20
uint256 key
Definition Keylet.h:21
std::shared_ptr< SLE const > const line
std::vector< BalanceChange > receivers
std::vector< BalanceChange > senders
static Shares make(SLE const &)
static Vault make(SLE const &)
T to_string(T... args)
T value_or(T... args)
T visit(T... args)