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 = pseudoAccount ? 0 : view.seq();
1032
1033 if (accountSeq_ != startingSeq)
1034 {
1035 JLOG(j.fatal()) << "Invariant failed: account created with "
1036 "wrong starting sequence number";
1037 return false;
1038 }
1039
1040 if (pseudoAccount)
1041 {
1042 std::uint32_t const expected =
1044 if (flags_ != expected)
1045 {
1046 JLOG(j.fatal())
1047 << "Invariant failed: pseudo-account created with "
1048 "wrong flags";
1049 return false;
1050 }
1051 }
1052
1053 return true;
1054 }
1055
1056 JLOG(j.fatal()) << "Invariant failed: account root created illegally";
1057 return false;
1058} // namespace ripple
1059
1060//------------------------------------------------------------------------------
1061
1062void
1064 bool isDelete,
1065 std::shared_ptr<SLE const> const& before,
1067{
1068 static constexpr uint256 const& pageBits = nft::pageMask;
1069 static constexpr uint256 const accountBits = ~pageBits;
1070
1071 if ((before && before->getType() != ltNFTOKEN_PAGE) ||
1072 (after && after->getType() != ltNFTOKEN_PAGE))
1073 return;
1074
1075 auto check = [this, isDelete](std::shared_ptr<SLE const> const& sle) {
1076 uint256 const account = sle->key() & accountBits;
1077 uint256 const hiLimit = sle->key() & pageBits;
1078 std::optional<uint256> const prev = (*sle)[~sfPreviousPageMin];
1079
1080 // Make sure that any page links...
1081 // 1. Are properly associated with the owning account and
1082 // 2. The page is correctly ordered between links.
1083 if (prev)
1084 {
1085 if (account != (*prev & accountBits))
1086 badLink_ = true;
1087
1088 if (hiLimit <= (*prev & pageBits))
1089 badLink_ = true;
1090 }
1091
1092 if (auto const next = (*sle)[~sfNextPageMin])
1093 {
1094 if (account != (*next & accountBits))
1095 badLink_ = true;
1096
1097 if (hiLimit >= (*next & pageBits))
1098 badLink_ = true;
1099 }
1100
1101 {
1102 auto const& nftokens = sle->getFieldArray(sfNFTokens);
1103
1104 // An NFTokenPage should never contain too many tokens or be empty.
1105 if (std::size_t const nftokenCount = nftokens.size();
1106 (!isDelete && nftokenCount == 0) ||
1107 nftokenCount > dirMaxTokensPerPage)
1108 invalidSize_ = true;
1109
1110 // If prev is valid, use it to establish a lower bound for
1111 // page entries. If prev is not valid the lower bound is zero.
1112 uint256 const loLimit =
1113 prev ? *prev & pageBits : uint256(beast::zero);
1114
1115 // Also verify that all NFTokenIDs in the page are sorted.
1116 uint256 loCmp = loLimit;
1117 for (auto const& obj : nftokens)
1118 {
1119 uint256 const tokenID = obj[sfNFTokenID];
1120 if (!nft::compareTokens(loCmp, tokenID))
1121 badSort_ = true;
1122 loCmp = tokenID;
1123
1124 // None of the NFTs on this page should belong on lower or
1125 // higher pages.
1126 if (uint256 const tokenPageBits = tokenID & pageBits;
1127 tokenPageBits < loLimit || tokenPageBits >= hiLimit)
1128 badEntry_ = true;
1129
1130 if (auto uri = obj[~sfURI]; uri && uri->empty())
1131 badURI_ = true;
1132 }
1133 }
1134 };
1135
1136 if (before)
1137 {
1138 check(before);
1139
1140 // While an account's NFToken directory contains any NFTokens, the last
1141 // NFTokenPage (with 96 bits of 1 in the low part of the index) should
1142 // never be deleted.
1143 if (isDelete && (before->key() & nft::pageMask) == nft::pageMask &&
1144 before->isFieldPresent(sfPreviousPageMin))
1145 {
1146 deletedFinalPage_ = true;
1147 }
1148 }
1149
1150 if (after)
1151 check(after);
1152
1153 if (!isDelete && before && after)
1154 {
1155 // If the NFTokenPage
1156 // 1. Has a NextMinPage field in before, but loses it in after, and
1157 // 2. This is not the last page in the directory
1158 // Then we have identified a corruption in the links between the
1159 // NFToken pages in the NFToken directory.
1160 if ((before->key() & nft::pageMask) != nft::pageMask &&
1161 before->isFieldPresent(sfNextPageMin) &&
1162 !after->isFieldPresent(sfNextPageMin))
1163 {
1164 deletedLink_ = true;
1165 }
1166 }
1167}
1168
1169bool
1171 STTx const& tx,
1172 TER const result,
1173 XRPAmount const,
1174 ReadView const& view,
1175 beast::Journal const& j)
1176{
1177 if (badLink_)
1178 {
1179 JLOG(j.fatal()) << "Invariant failed: NFT page is improperly linked.";
1180 return false;
1181 }
1182
1183 if (badEntry_)
1184 {
1185 JLOG(j.fatal()) << "Invariant failed: NFT found in incorrect page.";
1186 return false;
1187 }
1188
1189 if (badSort_)
1190 {
1191 JLOG(j.fatal()) << "Invariant failed: NFTs on page are not sorted.";
1192 return false;
1193 }
1194
1195 if (badURI_)
1196 {
1197 JLOG(j.fatal()) << "Invariant failed: NFT contains empty URI.";
1198 return false;
1199 }
1200
1201 if (invalidSize_)
1202 {
1203 JLOG(j.fatal()) << "Invariant failed: NFT page has invalid size.";
1204 return false;
1205 }
1206
1207 if (view.rules().enabled(fixNFTokenPageLinks))
1208 {
1210 {
1211 JLOG(j.fatal()) << "Invariant failed: Last NFT page deleted with "
1212 "non-empty directory.";
1213 return false;
1214 }
1215 if (deletedLink_)
1216 {
1217 JLOG(j.fatal()) << "Invariant failed: Lost NextMinPage link.";
1218 return false;
1219 }
1220 }
1221
1222 return true;
1223}
1224
1225//------------------------------------------------------------------------------
1226void
1228 bool,
1229 std::shared_ptr<SLE const> const& before,
1231{
1232 if (before && before->getType() == ltACCOUNT_ROOT)
1233 {
1234 beforeMintedTotal += (*before)[~sfMintedNFTokens].value_or(0);
1235 beforeBurnedTotal += (*before)[~sfBurnedNFTokens].value_or(0);
1236 }
1237
1238 if (after && after->getType() == ltACCOUNT_ROOT)
1239 {
1240 afterMintedTotal += (*after)[~sfMintedNFTokens].value_or(0);
1241 afterBurnedTotal += (*after)[~sfBurnedNFTokens].value_or(0);
1242 }
1243}
1244
1245bool
1247 STTx const& tx,
1248 TER const result,
1249 XRPAmount const,
1250 ReadView const& view,
1251 beast::Journal const& j)
1252{
1253 if (!hasPrivilege(tx, changeNFTCounts))
1254 {
1256 {
1257 JLOG(j.fatal()) << "Invariant failed: the number of minted tokens "
1258 "changed without a mint transaction!";
1259 return false;
1260 }
1261
1263 {
1264 JLOG(j.fatal()) << "Invariant failed: the number of burned tokens "
1265 "changed without a burn transaction!";
1266 return false;
1267 }
1268
1269 return true;
1270 }
1271
1272 if (tx.getTxnType() == ttNFTOKEN_MINT)
1273 {
1274 if (result == tesSUCCESS && beforeMintedTotal >= afterMintedTotal)
1275 {
1276 JLOG(j.fatal())
1277 << "Invariant failed: successful minting didn't increase "
1278 "the number of minted tokens.";
1279 return false;
1280 }
1281
1282 if (result != tesSUCCESS && beforeMintedTotal != afterMintedTotal)
1283 {
1284 JLOG(j.fatal()) << "Invariant failed: failed minting changed the "
1285 "number of minted tokens.";
1286 return false;
1287 }
1288
1290 {
1291 JLOG(j.fatal())
1292 << "Invariant failed: minting changed the number of "
1293 "burned tokens.";
1294 return false;
1295 }
1296 }
1297
1298 if (tx.getTxnType() == ttNFTOKEN_BURN)
1299 {
1300 if (result == tesSUCCESS)
1301 {
1303 {
1304 JLOG(j.fatal())
1305 << "Invariant failed: successful burning didn't increase "
1306 "the number of burned tokens.";
1307 return false;
1308 }
1309 }
1310
1311 if (result != tesSUCCESS && beforeBurnedTotal != afterBurnedTotal)
1312 {
1313 JLOG(j.fatal()) << "Invariant failed: failed burning changed the "
1314 "number of burned tokens.";
1315 return false;
1316 }
1317
1319 {
1320 JLOG(j.fatal())
1321 << "Invariant failed: burning changed the number of "
1322 "minted tokens.";
1323 return false;
1324 }
1325 }
1326
1327 return true;
1328}
1329
1330//------------------------------------------------------------------------------
1331
1332void
1334 bool,
1335 std::shared_ptr<SLE const> const& before,
1337{
1338 if (before && before->getType() == ltRIPPLE_STATE)
1340
1341 if (before && before->getType() == ltMPTOKEN)
1343}
1344
1345bool
1347 STTx const& tx,
1348 TER const result,
1349 XRPAmount const,
1350 ReadView const& view,
1351 beast::Journal const& j)
1352{
1353 if (tx.getTxnType() != ttCLAWBACK)
1354 return true;
1355
1356 if (result == tesSUCCESS)
1357 {
1358 if (trustlinesChanged > 1)
1359 {
1360 JLOG(j.fatal())
1361 << "Invariant failed: more than one trustline changed.";
1362 return false;
1363 }
1364
1365 if (mptokensChanged > 1)
1366 {
1367 JLOG(j.fatal())
1368 << "Invariant failed: more than one mptokens changed.";
1369 return false;
1370 }
1371
1372 if (trustlinesChanged == 1)
1373 {
1374 AccountID const issuer = tx.getAccountID(sfAccount);
1375 STAmount const& amount = tx.getFieldAmount(sfAmount);
1376 AccountID const& holder = amount.getIssuer();
1377 STAmount const holderBalance = accountHolds(
1378 view, holder, amount.getCurrency(), issuer, fhIGNORE_FREEZE, j);
1379
1380 if (holderBalance.signum() < 0)
1381 {
1382 JLOG(j.fatal())
1383 << "Invariant failed: trustline balance is negative";
1384 return false;
1385 }
1386 }
1387 }
1388 else
1389 {
1390 if (trustlinesChanged != 0)
1391 {
1392 JLOG(j.fatal()) << "Invariant failed: some trustlines were changed "
1393 "despite failure of the transaction.";
1394 return false;
1395 }
1396
1397 if (mptokensChanged != 0)
1398 {
1399 JLOG(j.fatal()) << "Invariant failed: some mptokens were changed "
1400 "despite failure of the transaction.";
1401 return false;
1402 }
1403 }
1404
1405 return true;
1406}
1407
1408//------------------------------------------------------------------------------
1409
1410void
1412 bool isDelete,
1413 std::shared_ptr<SLE const> const& before,
1415{
1416 if (after && after->getType() == ltMPTOKEN_ISSUANCE)
1417 {
1418 if (isDelete)
1420 else if (!before)
1422 }
1423
1424 if (after && after->getType() == ltMPTOKEN)
1425 {
1426 if (isDelete)
1428 else if (!before)
1430 }
1431}
1432
1433bool
1435 STTx const& tx,
1436 TER const result,
1437 XRPAmount const _fee,
1438 ReadView const& view,
1439 beast::Journal const& j)
1440{
1441 if (result == tesSUCCESS)
1442 {
1444 {
1445 if (mptIssuancesCreated_ == 0)
1446 {
1447 JLOG(j.fatal()) << "Invariant failed: transaction "
1448 "succeeded without creating a MPT issuance";
1449 }
1450 else if (mptIssuancesDeleted_ != 0)
1451 {
1452 JLOG(j.fatal()) << "Invariant failed: transaction "
1453 "succeeded while removing MPT issuances";
1454 }
1455 else if (mptIssuancesCreated_ > 1)
1456 {
1457 JLOG(j.fatal()) << "Invariant failed: transaction "
1458 "succeeded but created multiple issuances";
1459 }
1460
1461 return mptIssuancesCreated_ == 1 && mptIssuancesDeleted_ == 0;
1462 }
1463
1465 {
1466 if (mptIssuancesDeleted_ == 0)
1467 {
1468 JLOG(j.fatal()) << "Invariant failed: MPT issuance deletion "
1469 "succeeded without removing a MPT issuance";
1470 }
1471 else if (mptIssuancesCreated_ > 0)
1472 {
1473 JLOG(j.fatal()) << "Invariant failed: MPT issuance deletion "
1474 "succeeded while creating MPT issuances";
1475 }
1476 else if (mptIssuancesDeleted_ > 1)
1477 {
1478 JLOG(j.fatal()) << "Invariant failed: MPT issuance deletion "
1479 "succeeded but deleted multiple issuances";
1480 }
1481
1482 return mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 1;
1483 }
1484
1485 // ttESCROW_FINISH may authorize an MPT, but it can't have the
1486 // mayAuthorizeMPT privilege, because that may cause
1487 // non-amendment-gated side effects.
1488 bool const enforceEscrowFinish = (tx.getTxnType() == ttESCROW_FINISH) &&
1489 (view.rules().enabled(featureSingleAssetVault)
1490 /*
1491 TODO: Uncomment when LendingProtocol is defined
1492 || view.rules().enabled(featureLendingProtocol)*/
1493 );
1495 enforceEscrowFinish)
1496 {
1497 bool const submittedByIssuer = tx.isFieldPresent(sfHolder);
1498
1499 if (mptIssuancesCreated_ > 0)
1500 {
1501 JLOG(j.fatal()) << "Invariant failed: MPT authorize "
1502 "succeeded but created MPT issuances";
1503 return false;
1504 }
1505 else if (mptIssuancesDeleted_ > 0)
1506 {
1507 JLOG(j.fatal()) << "Invariant failed: MPT authorize "
1508 "succeeded but deleted issuances";
1509 return false;
1510 }
1511 else if (
1512 submittedByIssuer &&
1514 {
1515 JLOG(j.fatal())
1516 << "Invariant failed: MPT authorize submitted by issuer "
1517 "succeeded but created/deleted mptokens";
1518 return false;
1519 }
1520 else if (
1521 !submittedByIssuer && hasPrivilege(tx, mustAuthorizeMPT) &&
1523 {
1524 // if the holder submitted this tx, then a mptoken must be
1525 // either created or deleted.
1526 JLOG(j.fatal())
1527 << "Invariant failed: MPT authorize submitted by holder "
1528 "succeeded but created/deleted bad number of mptokens";
1529 return false;
1530 }
1531
1532 return true;
1533 }
1534 if (tx.getTxnType() == ttESCROW_FINISH)
1535 {
1536 // ttESCROW_FINISH may authorize an MPT, but it can't have the
1537 // mayAuthorizeMPT privilege, because that may cause
1538 // non-amendment-gated side effects.
1539 XRPL_ASSERT_PARTS(
1540 !enforceEscrowFinish,
1541 "ripple::ValidMPTIssuance::finalize",
1542 "not escrow finish tx");
1543 return true;
1544 }
1545
1546 if (hasPrivilege(tx, mayDeleteMPT) && mptokensDeleted_ == 1 &&
1549 return true;
1550 }
1551
1552 if (mptIssuancesCreated_ != 0)
1553 {
1554 JLOG(j.fatal()) << "Invariant failed: a MPT issuance was created";
1555 }
1556 else if (mptIssuancesDeleted_ != 0)
1557 {
1558 JLOG(j.fatal()) << "Invariant failed: a MPT issuance was deleted";
1559 }
1560 else if (mptokensCreated_ != 0)
1561 {
1562 JLOG(j.fatal()) << "Invariant failed: a MPToken was created";
1563 }
1564 else if (mptokensDeleted_ != 0)
1565 {
1566 JLOG(j.fatal()) << "Invariant failed: a MPToken was deleted";
1567 }
1568
1569 return mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 0 &&
1571}
1572
1573//------------------------------------------------------------------------------
1574
1575void
1577 bool,
1578 std::shared_ptr<SLE const> const& before,
1580{
1581 if (before && before->getType() != ltPERMISSIONED_DOMAIN)
1582 return;
1583 if (after && after->getType() != ltPERMISSIONED_DOMAIN)
1584 return;
1585
1586 auto check = [](SleStatus& sleStatus,
1587 std::shared_ptr<SLE const> const& sle) {
1588 auto const& credentials = sle->getFieldArray(sfAcceptedCredentials);
1589 sleStatus.credentialsSize_ = credentials.size();
1590 auto const sorted = credentials::makeSorted(credentials);
1591 sleStatus.isUnique_ = !sorted.empty();
1592
1593 // If array have duplicates then all the other checks are invalid
1594 sleStatus.isSorted_ = false;
1595
1596 if (sleStatus.isUnique_)
1597 {
1598 unsigned i = 0;
1599 for (auto const& cred : sorted)
1600 {
1601 auto const& credTx = credentials[i++];
1602 sleStatus.isSorted_ = (cred.first == credTx[sfIssuer]) &&
1603 (cred.second == credTx[sfCredentialType]);
1604 if (!sleStatus.isSorted_)
1605 break;
1606 }
1607 }
1608 };
1609
1610 if (before)
1611 {
1612 sleStatus_[0] = SleStatus();
1613 check(*sleStatus_[0], after);
1614 }
1615
1616 if (after)
1617 {
1618 sleStatus_[1] = SleStatus();
1619 check(*sleStatus_[1], after);
1620 }
1621}
1622
1623bool
1625 STTx const& tx,
1626 TER const result,
1627 XRPAmount const,
1628 ReadView const& view,
1629 beast::Journal const& j)
1630{
1631 if (tx.getTxnType() != ttPERMISSIONED_DOMAIN_SET || result != tesSUCCESS)
1632 return true;
1633
1634 auto check = [](SleStatus const& sleStatus, beast::Journal const& j) {
1635 if (!sleStatus.credentialsSize_)
1636 {
1637 JLOG(j.fatal()) << "Invariant failed: permissioned domain with "
1638 "no rules.";
1639 return false;
1640 }
1641
1642 if (sleStatus.credentialsSize_ >
1644 {
1645 JLOG(j.fatal()) << "Invariant failed: permissioned domain bad "
1646 "credentials size "
1647 << sleStatus.credentialsSize_;
1648 return false;
1649 }
1650
1651 if (!sleStatus.isUnique_)
1652 {
1653 JLOG(j.fatal())
1654 << "Invariant failed: permissioned domain credentials "
1655 "aren't unique";
1656 return false;
1657 }
1658
1659 if (!sleStatus.isSorted_)
1660 {
1661 JLOG(j.fatal())
1662 << "Invariant failed: permissioned domain credentials "
1663 "aren't sorted";
1664 return false;
1665 }
1666
1667 return true;
1668 };
1669
1670 return (sleStatus_[0] ? check(*sleStatus_[0], j) : true) &&
1671 (sleStatus_[1] ? check(*sleStatus_[1], j) : true);
1672}
1673
1674//------------------------------------------------------------------------------
1675
1676void
1678 bool isDelete,
1679 std::shared_ptr<SLE const> const& before,
1681{
1682 if (isDelete)
1683 // Deletion is ignored
1684 return;
1685
1686 if (after && after->getType() == ltACCOUNT_ROOT)
1687 {
1688 bool const isPseudo = [&]() {
1689 // isPseudoAccount checks that any of the pseudo-account fields are
1690 // set.
1692 return true;
1693 // Not all pseudo-accounts have a zero sequence, but all accounts
1694 // with a zero sequence had better be pseudo-accounts.
1695 if (after->at(sfSequence) == 0)
1696 return true;
1697
1698 return false;
1699 }();
1700 if (isPseudo)
1701 {
1702 // Pseudo accounts must have the following properties:
1703 // 1. Exactly one of the pseudo-account fields is set.
1704 // 2. The sequence number is not changed.
1705 // 3. The lsfDisableMaster, lsfDefaultRipple, and lsfDepositAuth
1706 // flags are set.
1707 // 4. The RegularKey is not set.
1708 {
1709 std::vector<SField const*> const& fields =
1711
1712 auto const numFields = std::count_if(
1713 fields.begin(),
1714 fields.end(),
1715 [&after](SField const* sf) -> bool {
1716 return after->isFieldPresent(*sf);
1717 });
1718 if (numFields != 1)
1719 {
1720 std::stringstream error;
1721 error << "pseudo-account has " << numFields
1722 << " pseudo-account fields set";
1723 errors_.emplace_back(error.str());
1724 }
1725 }
1726 if (before && before->at(sfSequence) != after->at(sfSequence))
1727 {
1728 errors_.emplace_back("pseudo-account sequence changed");
1729 }
1730 if (!after->isFlag(
1732 {
1733 errors_.emplace_back("pseudo-account flags are not set");
1734 }
1735 if (after->isFieldPresent(sfRegularKey))
1736 {
1737 errors_.emplace_back("pseudo-account has a regular key");
1738 }
1739 }
1740 }
1741}
1742
1743bool
1745 STTx const& tx,
1746 TER const,
1747 XRPAmount const,
1748 ReadView const& view,
1749 beast::Journal const& j)
1750{
1751 bool const enforce = view.rules().enabled(featureSingleAssetVault);
1752
1753 // The comment above starting with "assert(enforce)" explains this assert.
1754 XRPL_ASSERT(
1755 errors_.empty() || enforce,
1756 "ripple::ValidPseudoAccounts::finalize : no bad "
1757 "changes or enforce invariant");
1758 if (!errors_.empty())
1759 {
1760 for (auto const& error : errors_)
1761 {
1762 JLOG(j.fatal()) << "Invariant failed: " << error;
1763 }
1764 if (enforce)
1765 return false;
1766 }
1767 return true;
1768}
1769
1770//------------------------------------------------------------------------------
1771
1772void
1774 bool,
1775 std::shared_ptr<SLE const> const& before,
1777{
1778 if (after && after->getType() == ltDIR_NODE)
1779 {
1780 if (after->isFieldPresent(sfDomainID))
1781 domains_.insert(after->getFieldH256(sfDomainID));
1782 }
1783
1784 if (after && after->getType() == ltOFFER)
1785 {
1786 if (after->isFieldPresent(sfDomainID))
1787 domains_.insert(after->getFieldH256(sfDomainID));
1788 else
1789 regularOffers_ = true;
1790
1791 // if a hybrid offer is missing domain or additional book, there's
1792 // something wrong
1793 if (after->isFlag(lsfHybrid) &&
1794 (!after->isFieldPresent(sfDomainID) ||
1795 !after->isFieldPresent(sfAdditionalBooks) ||
1796 after->getFieldArray(sfAdditionalBooks).size() > 1))
1797 badHybrids_ = true;
1798 }
1799}
1800
1801bool
1803 STTx const& tx,
1804 TER const result,
1805 XRPAmount const,
1806 ReadView const& view,
1807 beast::Journal const& j)
1808{
1809 auto const txType = tx.getTxnType();
1810 if ((txType != ttPAYMENT && txType != ttOFFER_CREATE) ||
1811 result != tesSUCCESS)
1812 return true;
1813
1814 // For each offercreate transaction, check if
1815 // permissioned offers are valid
1816 if (txType == ttOFFER_CREATE && badHybrids_)
1817 {
1818 JLOG(j.fatal()) << "Invariant failed: hybrid offer is malformed";
1819 return false;
1820 }
1821
1822 if (!tx.isFieldPresent(sfDomainID))
1823 return true;
1824
1825 auto const domain = tx.getFieldH256(sfDomainID);
1826
1827 if (!view.exists(keylet::permissionedDomain(domain)))
1828 {
1829 JLOG(j.fatal()) << "Invariant failed: domain doesn't exist";
1830 return false;
1831 }
1832
1833 // for both payment and offercreate, there shouldn't be another domain
1834 // that's different from the domain specified
1835 for (auto const& d : domains_)
1836 {
1837 if (d != domain)
1838 {
1839 JLOG(j.fatal()) << "Invariant failed: transaction"
1840 " consumed wrong domains";
1841 return false;
1842 }
1843 }
1844
1845 if (regularOffers_)
1846 {
1847 JLOG(j.fatal()) << "Invariant failed: domain transaction"
1848 " affected regular offers";
1849 return false;
1850 }
1851
1852 return true;
1853}
1854
1855void
1857 bool isDelete,
1858 std::shared_ptr<SLE const> const& before,
1860{
1861 if (isDelete)
1862 return;
1863
1864 if (after)
1865 {
1866 auto const type = after->getType();
1867 // AMM object changed
1868 if (type == ltAMM)
1869 {
1870 ammAccount_ = after->getAccountID(sfAccount);
1871 lptAMMBalanceAfter_ = after->getFieldAmount(sfLPTokenBalance);
1872 }
1873 // AMM pool changed
1874 else if (
1875 (type == ltRIPPLE_STATE && after->getFlags() & lsfAMMNode) ||
1876 (type == ltACCOUNT_ROOT && after->isFieldPresent(sfAMMID)))
1877 {
1878 ammPoolChanged_ = true;
1879 }
1880 }
1881
1882 if (before)
1883 {
1884 // AMM object changed
1885 if (before->getType() == ltAMM)
1886 {
1887 lptAMMBalanceBefore_ = before->getFieldAmount(sfLPTokenBalance);
1888 }
1889 }
1890}
1891
1892static bool
1894 STAmount const& amount,
1895 STAmount const& amount2,
1896 STAmount const& lptAMMBalance,
1897 ValidAMM::ZeroAllowed zeroAllowed)
1898{
1899 bool const positive = amount > beast::zero && amount2 > beast::zero &&
1900 lptAMMBalance > beast::zero;
1901 if (zeroAllowed == ValidAMM::ZeroAllowed::Yes)
1902 return positive ||
1903 (amount == beast::zero && amount2 == beast::zero &&
1904 lptAMMBalance == beast::zero);
1905 return positive;
1906}
1907
1908bool
1909ValidAMM::finalizeVote(bool enforce, beast::Journal const& j) const
1910{
1912 {
1913 // LPTokens and the pool can not change on vote
1914 // LCOV_EXCL_START
1915 JLOG(j.error()) << "AMMVote invariant failed: "
1918 << ammPoolChanged_;
1919 if (enforce)
1920 return false;
1921 // LCOV_EXCL_STOP
1922 }
1923
1924 return true;
1925}
1926
1927bool
1928ValidAMM::finalizeBid(bool enforce, beast::Journal const& j) const
1929{
1930 if (ammPoolChanged_)
1931 {
1932 // The pool can not change on bid
1933 // LCOV_EXCL_START
1934 JLOG(j.error()) << "AMMBid invariant failed: pool changed";
1935 if (enforce)
1936 return false;
1937 // LCOV_EXCL_STOP
1938 }
1939 // LPTokens are burnt, therefore there should be fewer LPTokens
1940 else if (
1943 *lptAMMBalanceAfter_ <= beast::zero))
1944 {
1945 // LCOV_EXCL_START
1946 JLOG(j.error()) << "AMMBid invariant failed: " << *lptAMMBalanceBefore_
1947 << " " << *lptAMMBalanceAfter_;
1948 if (enforce)
1949 return false;
1950 // LCOV_EXCL_STOP
1951 }
1952
1953 return true;
1954}
1955
1956bool
1958 STTx const& tx,
1959 ReadView const& view,
1960 bool enforce,
1961 beast::Journal const& j) const
1962{
1963 if (!ammAccount_)
1964 {
1965 // LCOV_EXCL_START
1966 JLOG(j.error())
1967 << "AMMCreate invariant failed: AMM object is not created";
1968 if (enforce)
1969 return false;
1970 // LCOV_EXCL_STOP
1971 }
1972 else
1973 {
1974 auto const [amount, amount2] = ammPoolHolds(
1975 view,
1976 *ammAccount_,
1977 tx[sfAmount].get<Issue>(),
1978 tx[sfAmount2].get<Issue>(),
1980 j);
1981 // Create invariant:
1982 // sqrt(amount * amount2) == LPTokens
1983 // all balances are greater than zero
1984 if (!validBalances(
1985 amount, amount2, *lptAMMBalanceAfter_, ZeroAllowed::No) ||
1986 ammLPTokens(amount, amount2, lptAMMBalanceAfter_->issue()) !=
1988 {
1989 JLOG(j.error()) << "AMMCreate invariant failed: " << amount << " "
1990 << amount2 << " " << *lptAMMBalanceAfter_;
1991 if (enforce)
1992 return false;
1993 }
1994 }
1995
1996 return true;
1997}
1998
1999bool
2000ValidAMM::finalizeDelete(bool enforce, TER res, beast::Journal const& j) const
2001{
2002 if (ammAccount_)
2003 {
2004 // LCOV_EXCL_START
2005 std::string const msg = (res == tesSUCCESS)
2006 ? "AMM object is not deleted on tesSUCCESS"
2007 : "AMM object is changed on tecINCOMPLETE";
2008 JLOG(j.error()) << "AMMDelete invariant failed: " << msg;
2009 if (enforce)
2010 return false;
2011 // LCOV_EXCL_STOP
2012 }
2013
2014 return true;
2015}
2016
2017bool
2018ValidAMM::finalizeDEX(bool enforce, beast::Journal const& j) const
2019{
2020 if (ammAccount_)
2021 {
2022 // LCOV_EXCL_START
2023 JLOG(j.error()) << "AMM swap invariant failed: AMM object changed";
2024 if (enforce)
2025 return false;
2026 // LCOV_EXCL_STOP
2027 }
2028
2029 return true;
2030}
2031
2032bool
2034 ripple::STTx const& tx,
2035 ripple::ReadView const& view,
2036 ZeroAllowed zeroAllowed,
2037 beast::Journal const& j) const
2038{
2039 auto const [amount, amount2] = ammPoolHolds(
2040 view,
2041 *ammAccount_,
2042 tx[sfAsset].get<Issue>(),
2043 tx[sfAsset2].get<Issue>(),
2045 j);
2046 // Deposit and Withdrawal invariant:
2047 // sqrt(amount * amount2) >= LPTokens
2048 // all balances are greater than zero
2049 // unless on last withdrawal
2050 auto const poolProductMean = root2(amount * amount2);
2051 bool const nonNegativeBalances =
2052 validBalances(amount, amount2, *lptAMMBalanceAfter_, zeroAllowed);
2053 bool const strongInvariantCheck = poolProductMean >= *lptAMMBalanceAfter_;
2054 // Allow for a small relative error if strongInvariantCheck fails
2055 auto weakInvariantCheck = [&]() {
2056 return *lptAMMBalanceAfter_ != beast::zero &&
2058 poolProductMean, Number{*lptAMMBalanceAfter_}, Number{1, -11});
2059 };
2060 if (!nonNegativeBalances ||
2061 (!strongInvariantCheck && !weakInvariantCheck()))
2062 {
2063 JLOG(j.error()) << "AMM " << tx.getTxnType() << " invariant failed: "
2065 << ammPoolChanged_ << " " << amount << " " << amount2
2066 << " " << poolProductMean << " "
2067 << lptAMMBalanceAfter_->getText() << " "
2068 << ((*lptAMMBalanceAfter_ == beast::zero)
2069 ? Number{1}
2070 : ((*lptAMMBalanceAfter_ - poolProductMean) /
2071 poolProductMean));
2072 return false;
2073 }
2074
2075 return true;
2076}
2077
2078bool
2080 ripple::STTx const& tx,
2081 ripple::ReadView const& view,
2082 bool enforce,
2083 beast::Journal const& j) const
2084{
2085 if (!ammAccount_)
2086 {
2087 // LCOV_EXCL_START
2088 JLOG(j.error()) << "AMMDeposit invariant failed: AMM object is deleted";
2089 if (enforce)
2090 return false;
2091 // LCOV_EXCL_STOP
2092 }
2093 else if (!generalInvariant(tx, view, ZeroAllowed::No, j) && enforce)
2094 return false;
2095
2096 return true;
2097}
2098
2099bool
2101 ripple::STTx const& tx,
2102 ripple::ReadView const& view,
2103 bool enforce,
2104 beast::Journal const& j) const
2105{
2106 if (!ammAccount_)
2107 {
2108 // Last Withdraw or Clawback deleted AMM
2109 }
2110 else if (!generalInvariant(tx, view, ZeroAllowed::Yes, j))
2111 {
2112 if (enforce)
2113 return false;
2114 }
2115
2116 return true;
2117}
2118
2119bool
2121 STTx const& tx,
2122 TER const result,
2123 XRPAmount const,
2124 ReadView const& view,
2125 beast::Journal const& j)
2126{
2127 // Delete may return tecINCOMPLETE if there are too many
2128 // trustlines to delete.
2129 if (result != tesSUCCESS && result != tecINCOMPLETE)
2130 return true;
2131
2132 bool const enforce = view.rules().enabled(fixAMMv1_3);
2133
2134 switch (tx.getTxnType())
2135 {
2136 case ttAMM_CREATE:
2137 return finalizeCreate(tx, view, enforce, j);
2138 case ttAMM_DEPOSIT:
2139 return finalizeDeposit(tx, view, enforce, j);
2140 case ttAMM_CLAWBACK:
2141 case ttAMM_WITHDRAW:
2142 return finalizeWithdraw(tx, view, enforce, j);
2143 case ttAMM_BID:
2144 return finalizeBid(enforce, j);
2145 case ttAMM_VOTE:
2146 return finalizeVote(enforce, j);
2147 case ttAMM_DELETE:
2148 return finalizeDelete(enforce, result, j);
2149 case ttCHECK_CASH:
2150 case ttOFFER_CREATE:
2151 case ttPAYMENT:
2152 return finalizeDEX(enforce, j);
2153 default:
2154 break;
2155 }
2156
2157 return true;
2158}
2159
2160//------------------------------------------------------------------------------
2161
2164{
2165 XRPL_ASSERT(
2166 from.getType() == ltVAULT,
2167 "ValidVault::Vault::make : from Vault object");
2168
2169 ValidVault::Vault self;
2170 self.key = from.key();
2171 self.asset = from.at(sfAsset);
2172 self.pseudoId = from.getAccountID(sfAccount);
2173 self.shareMPTID = from.getFieldH192(sfShareMPTID);
2174 self.assetsTotal = from.at(sfAssetsTotal);
2175 self.assetsAvailable = from.at(sfAssetsAvailable);
2176 self.assetsMaximum = from.at(sfAssetsMaximum);
2177 self.lossUnrealized = from.at(sfLossUnrealized);
2178 return self;
2179}
2180
2183{
2184 XRPL_ASSERT(
2185 from.getType() == ltMPTOKEN_ISSUANCE,
2186 "ValidVault::Shares::make : from MPTokenIssuance object");
2187
2188 ValidVault::Shares self;
2189 self.share = MPTIssue(
2190 makeMptID(from.getFieldU32(sfSequence), from.getAccountID(sfIssuer)));
2191 self.sharesTotal = from.at(sfOutstandingAmount);
2192 self.sharesMaximum = from[~sfMaximumAmount].value_or(maxMPTokenAmount);
2193 return self;
2194}
2195
2196void
2198 bool isDelete,
2199 std::shared_ptr<SLE const> const& before,
2201{
2202 // If `before` is empty, this means an object is being created, in which
2203 // case `isDelete` must be false. Otherwise `before` and `after` are set and
2204 // `isDelete` indicates whether an object is being deleted or modified.
2205 XRPL_ASSERT(
2206 after != nullptr && (before != nullptr || !isDelete),
2207 "ripple::ValidVault::visitEntry : some object is available");
2208
2209 // Number balanceDelta will capture the difference (delta) between "before"
2210 // state (zero if created) and "after" state (zero if destroyed), so the
2211 // invariants can validate that the change in account balances matches the
2212 // change in vault balances, stored to deltas_ at the end of this function.
2213 Number balanceDelta{};
2214
2215 std::int8_t sign = 0;
2216 if (before)
2217 {
2218 switch (before->getType())
2219 {
2220 case ltVAULT:
2221 beforeVault_.push_back(Vault::make(*before));
2222 break;
2223 case ltMPTOKEN_ISSUANCE:
2224 // At this moment we have no way of telling if this object holds
2225 // vault shares or something else. Save it for finalize.
2226 beforeMPTs_.push_back(Shares::make(*before));
2227 balanceDelta = static_cast<std::int64_t>(
2228 before->getFieldU64(sfOutstandingAmount));
2229 sign = 1;
2230 break;
2231 case ltMPTOKEN:
2232 balanceDelta =
2233 static_cast<std::int64_t>(before->getFieldU64(sfMPTAmount));
2234 sign = -1;
2235 break;
2236 case ltACCOUNT_ROOT:
2237 case ltRIPPLE_STATE:
2238 balanceDelta = before->getFieldAmount(sfBalance);
2239 sign = -1;
2240 break;
2241 default:;
2242 }
2243 }
2244
2245 if (!isDelete && after)
2246 {
2247 switch (after->getType())
2248 {
2249 case ltVAULT:
2250 afterVault_.push_back(Vault::make(*after));
2251 break;
2252 case ltMPTOKEN_ISSUANCE:
2253 // At this moment we have no way of telling if this object holds
2254 // vault shares or something else. Save it for finalize.
2255 afterMPTs_.push_back(Shares::make(*after));
2256 balanceDelta -= Number(static_cast<std::int64_t>(
2257 after->getFieldU64(sfOutstandingAmount)));
2258 sign = 1;
2259 break;
2260 case ltMPTOKEN:
2261 balanceDelta -= Number(
2262 static_cast<std::int64_t>(after->getFieldU64(sfMPTAmount)));
2263 sign = -1;
2264 break;
2265 case ltACCOUNT_ROOT:
2266 case ltRIPPLE_STATE:
2267 balanceDelta -= Number(after->getFieldAmount(sfBalance));
2268 sign = -1;
2269 break;
2270 default:;
2271 }
2272 }
2273
2274 uint256 const key = (before ? before->key() : after->key());
2275 // Append to deltas if sign is non-zero, i.e. an object of an interesting
2276 // type has been updated. A transaction may update an object even when
2277 // its balance has not changed, e.g. transaction fee equals the amount
2278 // transferred to the account. We intentionally do not compare balanceDelta
2279 // against zero, to avoid missing such updates.
2280 if (sign != 0)
2281 deltas_[key] = balanceDelta * sign;
2282}
2283
2284bool
2286 STTx const& tx,
2287 TER const ret,
2288 XRPAmount const fee,
2289 ReadView const& view,
2290 beast::Journal const& j)
2291{
2292 bool const enforce = view.rules().enabled(featureSingleAssetVault);
2293
2294 if (!isTesSuccess(ret))
2295 return true; // Do not perform checks
2296
2297 if (afterVault_.empty() && beforeVault_.empty())
2298 {
2300 {
2301 JLOG(j.fatal()) << //
2302 "Invariant failed: vault operation succeeded without modifying "
2303 "a vault";
2304 XRPL_ASSERT(
2305 enforce, "ripple::ValidVault::finalize : vault noop invariant");
2306 return !enforce;
2307 }
2308
2309 return true; // Not a vault operation
2310 }
2311 else if (!hasPrivilege(tx, mustModifyVault)) // TODO: mayModifyVault
2312 {
2313 JLOG(j.fatal()) << //
2314 "Invariant failed: vault updated by a wrong transaction type";
2315 XRPL_ASSERT(
2316 enforce,
2317 "ripple::ValidVault::finalize : illegal vault transaction "
2318 "invariant");
2319 return !enforce; // Also not a vault operation
2320 }
2321
2322 if (beforeVault_.size() > 1 || afterVault_.size() > 1)
2323 {
2324 JLOG(j.fatal()) << //
2325 "Invariant failed: vault operation updated more than single vault";
2326 XRPL_ASSERT(
2327 enforce, "ripple::ValidVault::finalize : single vault invariant");
2328 return !enforce; // That's all we can do here
2329 }
2330
2331 auto const txnType = tx.getTxnType();
2332
2333 // We do special handling for ttVAULT_DELETE first, because it's the only
2334 // vault-modifying transaction without an "after" state of the vault
2335 if (afterVault_.empty())
2336 {
2337 if (txnType != ttVAULT_DELETE)
2338 {
2339 JLOG(j.fatal()) << //
2340 "Invariant failed: vault deleted by a wrong transaction type";
2341 XRPL_ASSERT(
2342 enforce,
2343 "ripple::ValidVault::finalize : illegal vault deletion "
2344 "invariant");
2345 return !enforce; // That's all we can do here
2346 }
2347
2348 // Note, if afterVault_ is empty then we know that beforeVault_ is not
2349 // empty, as enforced at the top of this function
2350 auto const& beforeVault = beforeVault_[0];
2351
2352 // At this moment we only know a vault is being deleted and there
2353 // might be some MPTokenIssuance objects which are deleted in the
2354 // same transaction. Find the one matching this vault.
2355 auto const deletedShares = [&]() -> std::optional<Shares> {
2356 for (auto const& e : beforeMPTs_)
2357 {
2358 if (e.share.getMptID() == beforeVault.shareMPTID)
2359 return std::move(e);
2360 }
2361 return std::nullopt;
2362 }();
2363
2364 if (!deletedShares)
2365 {
2366 JLOG(j.fatal()) << "Invariant failed: deleted vault must also "
2367 "delete shares";
2368 XRPL_ASSERT(
2369 enforce,
2370 "ripple::ValidVault::finalize : shares deletion invariant");
2371 return !enforce; // That's all we can do here
2372 }
2373
2374 bool result = true;
2375 if (deletedShares->sharesTotal != 0)
2376 {
2377 JLOG(j.fatal()) << "Invariant failed: deleted vault must have no "
2378 "shares outstanding";
2379 result = false;
2380 }
2381 if (beforeVault.assetsTotal != zero)
2382 {
2383 JLOG(j.fatal()) << "Invariant failed: deleted vault must have no "
2384 "assets outstanding";
2385 result = false;
2386 }
2387 if (beforeVault.assetsAvailable != zero)
2388 {
2389 JLOG(j.fatal()) << "Invariant failed: deleted vault must have no "
2390 "assets available";
2391 result = false;
2392 }
2393
2394 return result;
2395 }
2396 else if (txnType == ttVAULT_DELETE)
2397 {
2398 JLOG(j.fatal()) << "Invariant failed: vault deletion succeeded without "
2399 "deleting a vault";
2400 XRPL_ASSERT(
2401 enforce, "ripple::ValidVault::finalize : vault deletion invariant");
2402 return !enforce; // That's all we can do here
2403 }
2404
2405 // Note, `afterVault_.empty()` is handled above
2406 auto const& afterVault = afterVault_[0];
2407 XRPL_ASSERT(
2408 beforeVault_.empty() || beforeVault_[0].key == afterVault.key,
2409 "ripple::ValidVault::finalize : single vault operation");
2410
2411 auto const updatedShares = [&]() -> std::optional<Shares> {
2412 // At this moment we only know that a vault is being updated and there
2413 // might be some MPTokenIssuance objects which are also updated in the
2414 // same transaction. Find the one matching the shares to this vault.
2415 // Note, we expect updatedMPTs collection to be extremely small. For
2416 // such collections linear search is faster than lookup.
2417 for (auto const& e : afterMPTs_)
2418 {
2419 if (e.share.getMptID() == afterVault.shareMPTID)
2420 return e;
2421 }
2422
2423 auto const sleShares =
2424 view.read(keylet::mptIssuance(afterVault.shareMPTID));
2425
2426 return sleShares ? std::optional<Shares>(Shares::make(*sleShares))
2427 : std::nullopt;
2428 }();
2429
2430 bool result = true;
2431
2432 // Universal transaction checks
2433 if (!beforeVault_.empty())
2434 {
2435 auto const& beforeVault = beforeVault_[0];
2436 if (afterVault.asset != beforeVault.asset ||
2437 afterVault.pseudoId != beforeVault.pseudoId ||
2438 afterVault.shareMPTID != beforeVault.shareMPTID)
2439 {
2440 JLOG(j.fatal())
2441 << "Invariant failed: violation of vault immutable data";
2442 result = false;
2443 }
2444 }
2445
2446 if (!updatedShares)
2447 {
2448 JLOG(j.fatal()) << "Invariant failed: updated vault must have shares";
2449 XRPL_ASSERT(
2450 enforce,
2451 "ripple::ValidVault::finalize : vault has shares invariant");
2452 return !enforce; // That's all we can do here
2453 }
2454
2455 if (updatedShares->sharesTotal == 0)
2456 {
2457 if (afterVault.assetsTotal != zero)
2458 {
2459 JLOG(j.fatal()) << "Invariant failed: updated zero sized "
2460 "vault must have no assets outstanding";
2461 result = false;
2462 }
2463 if (afterVault.assetsAvailable != zero)
2464 {
2465 JLOG(j.fatal()) << "Invariant failed: updated zero sized "
2466 "vault must have no assets available";
2467 result = false;
2468 }
2469 }
2470 else if (updatedShares->sharesTotal > updatedShares->sharesMaximum)
2471 {
2472 JLOG(j.fatal()) //
2473 << "Invariant failed: updated shares must not exceed maximum "
2474 << updatedShares->sharesMaximum;
2475 result = false;
2476 }
2477
2478 if (afterVault.assetsAvailable < zero)
2479 {
2480 JLOG(j.fatal())
2481 << "Invariant failed: assets available must be positive";
2482 result = false;
2483 }
2484
2485 if (afterVault.assetsAvailable > afterVault.assetsTotal)
2486 {
2487 JLOG(j.fatal()) << "Invariant failed: assets available must "
2488 "not be greater than assets outstanding";
2489 result = false;
2490 }
2491 else if (
2492 afterVault.lossUnrealized >
2493 afterVault.assetsTotal - afterVault.assetsAvailable)
2494 {
2495 JLOG(j.fatal()) //
2496 << "Invariant failed: loss unrealized must not exceed "
2497 "the difference between assets outstanding and available";
2498 result = false;
2499 }
2500
2501 if (afterVault.assetsTotal < zero)
2502 {
2503 JLOG(j.fatal())
2504 << "Invariant failed: assets outstanding must be positive";
2505 result = false;
2506 }
2507
2508 if (afterVault.assetsMaximum < zero)
2509 {
2510 JLOG(j.fatal()) << "Invariant failed: assets maximum must be positive";
2511 result = false;
2512 }
2513
2514 // Thanks to this check we can simply do `assert(!beforeVault_.empty()` when
2515 // enforcing invariants on transaction types other than ttVAULT_CREATE
2516 if (beforeVault_.empty() && txnType != ttVAULT_CREATE)
2517 {
2518 JLOG(j.fatal()) << //
2519 "Invariant failed: vault created by a wrong transaction type";
2520 XRPL_ASSERT(
2521 enforce, "ripple::ValidVault::finalize : vault creation invariant");
2522 return !enforce; // That's all we can do here
2523 }
2524
2525 if (!beforeVault_.empty() &&
2526 afterVault.lossUnrealized != beforeVault_[0].lossUnrealized)
2527 {
2528 JLOG(j.fatal()) << //
2529 "Invariant failed: vault transaction must not change loss "
2530 "unrealized";
2531 result = false;
2532 }
2533
2534 auto const beforeShares = [&]() -> std::optional<Shares> {
2535 if (beforeVault_.empty())
2536 return std::nullopt;
2537 auto const& beforeVault = beforeVault_[0];
2538
2539 for (auto const& e : beforeMPTs_)
2540 {
2541 if (e.share.getMptID() == beforeVault.shareMPTID)
2542 return std::move(e);
2543 }
2544 return std::nullopt;
2545 }();
2546
2547 if (!beforeShares &&
2548 (tx.getTxnType() == ttVAULT_DEPOSIT || //
2549 tx.getTxnType() == ttVAULT_WITHDRAW || //
2550 tx.getTxnType() == ttVAULT_CLAWBACK))
2551 {
2552 JLOG(j.fatal()) << "Invariant failed: vault operation succeeded "
2553 "without updating shares";
2554 XRPL_ASSERT(
2555 enforce, "ripple::ValidVault::finalize : shares noop invariant");
2556 return !enforce; // That's all we can do here
2557 }
2558
2559 auto const& vaultAsset = afterVault.asset;
2560 auto const deltaAssets = [&](AccountID const& id) -> std::optional<Number> {
2561 auto const get = //
2562 [&](auto const& it, std::int8_t sign = 1) -> std::optional<Number> {
2563 if (it == deltas_.end())
2564 return std::nullopt;
2565
2566 return it->second * sign;
2567 };
2568
2569 return std::visit(
2570 [&]<typename TIss>(TIss const& issue) {
2571 if constexpr (std::is_same_v<TIss, Issue>)
2572 {
2573 if (isXRP(issue))
2574 return get(deltas_.find(keylet::account(id).key));
2575 return get(
2576 deltas_.find(keylet::line(id, issue).key),
2577 id > issue.getIssuer() ? -1 : 1);
2578 }
2579 else if constexpr (std::is_same_v<TIss, MPTIssue>)
2580 {
2581 return get(deltas_.find(
2582 keylet::mptoken(issue.getMptID(), id).key));
2583 }
2584 },
2585 vaultAsset.value());
2586 };
2587 auto const deltaAssetsTxAccount = [&]() -> std::optional<Number> {
2588 auto ret = deltaAssets(tx[sfAccount]);
2589 // Nothing returned or not XRP transaction
2590 if (!ret.has_value() || !vaultAsset.native())
2591 return ret;
2592
2593 // Delegated transaction; no need to compensate for fees
2594 if (auto const delegate = tx[~sfDelegate];
2595 delegate.has_value() && *delegate != tx[sfAccount])
2596 return ret;
2597
2598 *ret += fee.drops();
2599 if (*ret == zero)
2600 return std::nullopt;
2601
2602 return ret;
2603 };
2604 auto const deltaShares = [&](AccountID const& id) -> std::optional<Number> {
2605 auto const it = [&]() {
2606 if (id == afterVault.pseudoId)
2607 return deltas_.find(
2608 keylet::mptIssuance(afterVault.shareMPTID).key);
2609 return deltas_.find(keylet::mptoken(afterVault.shareMPTID, id).key);
2610 }();
2611
2612 return it != deltas_.end() ? std::optional<Number>(it->second)
2613 : std::nullopt;
2614 };
2615
2616 // Technically this does not need to be a lambda, but it's more
2617 // convenient thanks to early "return false"; the not-so-nice
2618 // alternatives are several layers of nested if/else or more complex
2619 // (i.e. brittle) if statements.
2620 result &= [&]() {
2621 switch (txnType)
2622 {
2623 case ttVAULT_CREATE: {
2624 bool result = true;
2625
2626 if (!beforeVault_.empty())
2627 {
2628 JLOG(j.fatal()) //
2629 << "Invariant failed: create operation must not have "
2630 "updated a vault";
2631 result = false;
2632 }
2633
2634 if (afterVault.assetsAvailable != zero ||
2635 afterVault.assetsTotal != zero ||
2636 afterVault.lossUnrealized != zero ||
2637 updatedShares->sharesTotal != 0)
2638 {
2639 JLOG(j.fatal()) //
2640 << "Invariant failed: created vault must be empty";
2641 result = false;
2642 }
2643
2644 if (afterVault.pseudoId != updatedShares->share.getIssuer())
2645 {
2646 JLOG(j.fatal()) //
2647 << "Invariant failed: shares issuer and vault "
2648 "pseudo-account must be the same";
2649 result = false;
2650 }
2651
2652 auto const sleSharesIssuer = view.read(
2653 keylet::account(updatedShares->share.getIssuer()));
2654 if (!sleSharesIssuer)
2655 {
2656 JLOG(j.fatal()) //
2657 << "Invariant failed: shares issuer must exist";
2658 return false;
2659 }
2660
2661 if (!isPseudoAccount(sleSharesIssuer))
2662 {
2663 JLOG(j.fatal()) //
2664 << "Invariant failed: shares issuer must be a "
2665 "pseudo-account";
2666 result = false;
2667 }
2668
2669 if (auto const vaultId = (*sleSharesIssuer)[~sfVaultID];
2670 !vaultId || *vaultId != afterVault.key)
2671 {
2672 JLOG(j.fatal()) //
2673 << "Invariant failed: shares issuer pseudo-account "
2674 "must point back to the vault";
2675 result = false;
2676 }
2677
2678 return result;
2679 }
2680 case ttVAULT_SET: {
2681 bool result = true;
2682
2683 XRPL_ASSERT(
2684 !beforeVault_.empty(),
2685 "ripple::ValidVault::finalize : set updated a vault");
2686 auto const& beforeVault = beforeVault_[0];
2687
2688 auto const vaultDeltaAssets = deltaAssets(afterVault.pseudoId);
2689 if (vaultDeltaAssets)
2690 {
2691 JLOG(j.fatal()) << //
2692 "Invariant failed: set must not change vault balance";
2693 result = false;
2694 }
2695
2696 if (beforeVault.assetsTotal != afterVault.assetsTotal)
2697 {
2698 JLOG(j.fatal()) << //
2699 "Invariant failed: set must not change assets "
2700 "outstanding";
2701 result = false;
2702 }
2703
2704 if (afterVault.assetsMaximum > zero &&
2705 afterVault.assetsTotal > afterVault.assetsMaximum)
2706 {
2707 JLOG(j.fatal()) << //
2708 "Invariant failed: set assets outstanding must not "
2709 "exceed assets maximum";
2710 result = false;
2711 }
2712
2713 if (beforeVault.assetsAvailable != afterVault.assetsAvailable)
2714 {
2715 JLOG(j.fatal()) << //
2716 "Invariant failed: set must not change assets "
2717 "available";
2718 result = false;
2719 }
2720
2721 if (beforeShares && updatedShares &&
2722 beforeShares->sharesTotal != updatedShares->sharesTotal)
2723 {
2724 JLOG(j.fatal()) << //
2725 "Invariant failed: set must not change shares "
2726 "outstanding";
2727 result = false;
2728 }
2729
2730 return result;
2731 }
2732 case ttVAULT_DEPOSIT: {
2733 bool result = true;
2734
2735 XRPL_ASSERT(
2736 !beforeVault_.empty(),
2737 "ripple::ValidVault::finalize : deposit updated a vault");
2738 auto const& beforeVault = beforeVault_[0];
2739
2740 auto const vaultDeltaAssets = deltaAssets(afterVault.pseudoId);
2741
2742 if (!vaultDeltaAssets)
2743 {
2744 JLOG(j.fatal()) << //
2745 "Invariant failed: deposit must change vault balance";
2746 return false; // That's all we can do
2747 }
2748
2749 if (*vaultDeltaAssets > tx[sfAmount])
2750 {
2751 JLOG(j.fatal()) << //
2752 "Invariant failed: deposit must not change vault "
2753 "balance by more than deposited amount";
2754 result = false;
2755 }
2756
2757 if (*vaultDeltaAssets <= zero)
2758 {
2759 JLOG(j.fatal()) << //
2760 "Invariant failed: deposit must increase vault balance";
2761 result = false;
2762 }
2763
2764 // Any payments (including deposits) made by the issuer
2765 // do not change their balance, but create funds instead.
2766 bool const issuerDeposit = [&]() -> bool {
2767 if (vaultAsset.native())
2768 return false;
2769 return tx[sfAccount] == vaultAsset.getIssuer();
2770 }();
2771
2772 if (!issuerDeposit)
2773 {
2774 auto const accountDeltaAssets = deltaAssetsTxAccount();
2775 if (!accountDeltaAssets)
2776 {
2777 JLOG(j.fatal()) << //
2778 "Invariant failed: deposit must change depositor "
2779 "balance";
2780 return false;
2781 }
2782
2783 if (*accountDeltaAssets >= zero)
2784 {
2785 JLOG(j.fatal()) << //
2786 "Invariant failed: deposit must decrease depositor "
2787 "balance";
2788 result = false;
2789 }
2790
2791 if (*accountDeltaAssets * -1 != *vaultDeltaAssets)
2792 {
2793 JLOG(j.fatal()) << //
2794 "Invariant failed: deposit must change vault and "
2795 "depositor balance by equal amount";
2796 result = false;
2797 }
2798 }
2799
2800 if (afterVault.assetsMaximum > zero &&
2801 afterVault.assetsTotal > afterVault.assetsMaximum)
2802 {
2803 JLOG(j.fatal()) << //
2804 "Invariant failed: deposit assets outstanding must not "
2805 "exceed assets maximum";
2806 result = false;
2807 }
2808
2809 auto const accountDeltaShares = deltaShares(tx[sfAccount]);
2810 if (!accountDeltaShares)
2811 {
2812 JLOG(j.fatal()) << //
2813 "Invariant failed: deposit must change depositor "
2814 "shares";
2815 return false; // That's all we can do
2816 }
2817
2818 if (*accountDeltaShares <= zero)
2819 {
2820 JLOG(j.fatal()) << //
2821 "Invariant failed: deposit must increase depositor "
2822 "shares";
2823 result = false;
2824 }
2825
2826 auto const vaultDeltaShares = deltaShares(afterVault.pseudoId);
2827 if (!vaultDeltaShares || *vaultDeltaShares == zero)
2828 {
2829 JLOG(j.fatal()) << //
2830 "Invariant failed: deposit must change vault shares";
2831 return false; // That's all we can do
2832 }
2833
2834 if (*vaultDeltaShares * -1 != *accountDeltaShares)
2835 {
2836 JLOG(j.fatal()) << //
2837 "Invariant failed: deposit must change depositor and "
2838 "vault shares by equal amount";
2839 result = false;
2840 }
2841
2842 if (beforeVault.assetsTotal + *vaultDeltaAssets !=
2843 afterVault.assetsTotal)
2844 {
2845 JLOG(j.fatal()) << "Invariant failed: deposit and assets "
2846 "outstanding must add up";
2847 result = false;
2848 }
2849 if (beforeVault.assetsAvailable + *vaultDeltaAssets !=
2850 afterVault.assetsAvailable)
2851 {
2852 JLOG(j.fatal()) << "Invariant failed: deposit and assets "
2853 "available must add up";
2854 result = false;
2855 }
2856
2857 return result;
2858 }
2859 case ttVAULT_WITHDRAW: {
2860 bool result = true;
2861
2862 XRPL_ASSERT(
2863 !beforeVault_.empty(),
2864 "ripple::ValidVault::finalize : withdrawal updated a "
2865 "vault");
2866 auto const& beforeVault = beforeVault_[0];
2867
2868 auto const vaultDeltaAssets = deltaAssets(afterVault.pseudoId);
2869
2870 if (!vaultDeltaAssets)
2871 {
2872 JLOG(j.fatal()) << "Invariant failed: withdrawal must "
2873 "change vault balance";
2874 return false; // That's all we can do
2875 }
2876
2877 if (*vaultDeltaAssets >= zero)
2878 {
2879 JLOG(j.fatal()) << "Invariant failed: withdrawal must "
2880 "decrease vault balance";
2881 result = false;
2882 }
2883
2884 // Any payments (including withdrawal) going to the issuer
2885 // do not change their balance, but destroy funds instead.
2886 bool const issuerWithdrawal = [&]() -> bool {
2887 if (vaultAsset.native())
2888 return false;
2889 auto const destination =
2890 tx[~sfDestination].value_or(tx[sfAccount]);
2891 return destination == vaultAsset.getIssuer();
2892 }();
2893
2894 if (!issuerWithdrawal)
2895 {
2896 auto const accountDeltaAssets = deltaAssetsTxAccount();
2897 auto const otherAccountDelta =
2898 [&]() -> std::optional<Number> {
2899 if (auto const destination = tx[~sfDestination];
2900 destination && *destination != tx[sfAccount])
2901 return deltaAssets(*destination);
2902 return std::nullopt;
2903 }();
2904
2905 if (accountDeltaAssets.has_value() ==
2906 otherAccountDelta.has_value())
2907 {
2908 JLOG(j.fatal()) << //
2909 "Invariant failed: withdrawal must change one "
2910 "destination balance";
2911 return false;
2912 }
2913
2914 auto const destinationDelta = //
2915 accountDeltaAssets ? *accountDeltaAssets
2916 : *otherAccountDelta;
2917
2918 if (destinationDelta <= zero)
2919 {
2920 JLOG(j.fatal()) << //
2921 "Invariant failed: withdrawal must increase "
2922 "destination balance";
2923 result = false;
2924 }
2925
2926 if (*vaultDeltaAssets * -1 != destinationDelta)
2927 {
2928 JLOG(j.fatal()) << //
2929 "Invariant failed: withdrawal must change vault "
2930 "and destination balance by equal amount";
2931 result = false;
2932 }
2933 }
2934
2935 auto const accountDeltaShares = deltaShares(tx[sfAccount]);
2936 if (!accountDeltaShares)
2937 {
2938 JLOG(j.fatal()) << //
2939 "Invariant failed: withdrawal must change depositor "
2940 "shares";
2941 return false;
2942 }
2943
2944 if (*accountDeltaShares >= zero)
2945 {
2946 JLOG(j.fatal()) << //
2947 "Invariant failed: withdrawal must decrease depositor "
2948 "shares";
2949 result = false;
2950 }
2951
2952 auto const vaultDeltaShares = deltaShares(afterVault.pseudoId);
2953 if (!vaultDeltaShares || *vaultDeltaShares == zero)
2954 {
2955 JLOG(j.fatal()) << //
2956 "Invariant failed: withdrawal must change vault shares";
2957 return false; // That's all we can do
2958 }
2959
2960 if (*vaultDeltaShares * -1 != *accountDeltaShares)
2961 {
2962 JLOG(j.fatal()) << //
2963 "Invariant failed: withdrawal must change depositor "
2964 "and vault shares by equal amount";
2965 result = false;
2966 }
2967
2968 // Note, vaultBalance is negative (see check above)
2969 if (beforeVault.assetsTotal + *vaultDeltaAssets !=
2970 afterVault.assetsTotal)
2971 {
2972 JLOG(j.fatal()) << "Invariant failed: withdrawal and "
2973 "assets outstanding must add up";
2974 result = false;
2975 }
2976
2977 if (beforeVault.assetsAvailable + *vaultDeltaAssets !=
2978 afterVault.assetsAvailable)
2979 {
2980 JLOG(j.fatal()) << "Invariant failed: withdrawal and "
2981 "assets available must add up";
2982 result = false;
2983 }
2984
2985 return result;
2986 }
2987 case ttVAULT_CLAWBACK: {
2988 bool result = true;
2989
2990 XRPL_ASSERT(
2991 !beforeVault_.empty(),
2992 "ripple::ValidVault::finalize : clawback updated a vault");
2993 auto const& beforeVault = beforeVault_[0];
2994
2995 if (vaultAsset.native() ||
2996 vaultAsset.getIssuer() != tx[sfAccount])
2997 {
2998 JLOG(j.fatal()) << //
2999 "Invariant failed: clawback may only be performed by "
3000 "the asset issuer";
3001 return false; // That's all we can do
3002 }
3003
3004 auto const vaultDeltaAssets = deltaAssets(afterVault.pseudoId);
3005
3006 if (!vaultDeltaAssets)
3007 {
3008 JLOG(j.fatal()) << //
3009 "Invariant failed: clawback must change vault balance";
3010 return false; // That's all we can do
3011 }
3012
3013 if (*vaultDeltaAssets >= zero)
3014 {
3015 JLOG(j.fatal()) << //
3016 "Invariant failed: clawback must decrease vault "
3017 "balance";
3018 result = false;
3019 }
3020
3021 auto const accountDeltaShares = deltaShares(tx[sfHolder]);
3022 if (!accountDeltaShares)
3023 {
3024 JLOG(j.fatal()) << //
3025 "Invariant failed: clawback must change holder shares";
3026 return false; // That's all we can do
3027 }
3028
3029 if (*accountDeltaShares >= zero)
3030 {
3031 JLOG(j.fatal()) << //
3032 "Invariant failed: clawback must decrease holder "
3033 "shares";
3034 result = false;
3035 }
3036
3037 auto const vaultDeltaShares = deltaShares(afterVault.pseudoId);
3038 if (!vaultDeltaShares || *vaultDeltaShares == zero)
3039 {
3040 JLOG(j.fatal()) << //
3041 "Invariant failed: clawback must change vault shares";
3042 return false; // That's all we can do
3043 }
3044
3045 if (*vaultDeltaShares * -1 != *accountDeltaShares)
3046 {
3047 JLOG(j.fatal()) << //
3048 "Invariant failed: clawback must change holder and "
3049 "vault shares by equal amount";
3050 result = false;
3051 }
3052
3053 if (beforeVault.assetsTotal + *vaultDeltaAssets !=
3054 afterVault.assetsTotal)
3055 {
3056 JLOG(j.fatal()) << //
3057 "Invariant failed: clawback and assets outstanding "
3058 "must add up";
3059 result = false;
3060 }
3061
3062 if (beforeVault.assetsAvailable + *vaultDeltaAssets !=
3063 afterVault.assetsAvailable)
3064 {
3065 JLOG(j.fatal()) << //
3066 "Invariant failed: clawback and assets available must "
3067 "add up";
3068 result = false;
3069 }
3070
3071 return result;
3072 }
3073
3074 default:
3075 // LCOV_EXCL_START
3076 UNREACHABLE(
3077 "ripple::ValidVault::finalize : unknown transaction type");
3078 return false;
3079 // LCOV_EXCL_STOP
3080 }
3081 }();
3082
3083 if (!result)
3084 {
3085 // The comment at the top of this file starting with "assert(enforce)"
3086 // explains this assert.
3087 XRPL_ASSERT(enforce, "ripple::ValidVault::finalize : vault invariants");
3088 return !enforce;
3089 }
3090
3091 return true;
3092}
3093
3094} // 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:187
uint256 getTransactionID() const
Definition STTx.h:199
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)