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 xrpl {
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
74 0x1000, // The transaction MAY modify, delete or create, a vault
75};
76constexpr Privilege
78{
79 return safe_cast<Privilege>(
82}
83
84#pragma push_macro("TRANSACTION")
85#undef TRANSACTION
86
87#define TRANSACTION(tag, value, name, delegable, amendment, privileges, ...) \
88 case tag: { \
89 return (privileges) & priv; \
90 }
91
92bool
93hasPrivilege(STTx const& tx, Privilege priv)
94{
95 switch (tx.getTxnType())
96 {
97#include <xrpl/protocol/detail/transactions.macro>
98
99 // Deprecated types
100 default:
101 return false;
102 }
103};
104
105#undef TRANSACTION
106#pragma pop_macro("TRANSACTION")
107
108void
110 bool,
113{
114 // nothing to do
115}
116
117bool
119 STTx const& tx,
120 TER const,
121 XRPAmount const fee,
122 ReadView const&,
123 beast::Journal const& j)
124{
125 // We should never charge a negative fee
126 if (fee.drops() < 0)
127 {
128 JLOG(j.fatal()) << "Invariant failed: fee paid was negative: "
129 << fee.drops();
130 return false;
131 }
132
133 // We should never charge a fee that's greater than or equal to the
134 // entire XRP supply.
135 if (fee >= INITIAL_XRP)
136 {
137 JLOG(j.fatal()) << "Invariant failed: fee paid exceeds system limit: "
138 << fee.drops();
139 return false;
140 }
141
142 // We should never charge more for a transaction than the transaction
143 // authorizes. It's possible to charge less in some circumstances.
144 if (fee > tx.getFieldAmount(sfFee).xrp())
145 {
146 JLOG(j.fatal()) << "Invariant failed: fee paid is " << fee.drops()
147 << " exceeds fee specified in transaction.";
148 return false;
149 }
150
151 return true;
152}
153
154//------------------------------------------------------------------------------
155
156void
158 bool isDelete,
159 std::shared_ptr<SLE const> const& before,
161{
162 /* We go through all modified ledger entries, looking only at account roots,
163 * escrow payments, and payment channels. We remove from the total any
164 * previous XRP values and add to the total any new XRP values. The net
165 * balance of a payment channel is computed from two fields (amount and
166 * balance) and deletions are ignored for paychan and escrow because the
167 * amount fields have not been adjusted for those in the case of deletion.
168 */
169 if (before)
170 {
171 switch (before->getType())
172 {
173 case ltACCOUNT_ROOT:
174 drops_ -= (*before)[sfBalance].xrp().drops();
175 break;
176 case ltPAYCHAN:
177 drops_ -=
178 ((*before)[sfAmount] - (*before)[sfBalance]).xrp().drops();
179 break;
180 case ltESCROW:
181 if (isXRP((*before)[sfAmount]))
182 drops_ -= (*before)[sfAmount].xrp().drops();
183 break;
184 default:
185 break;
186 }
187 }
188
189 if (after)
190 {
191 switch (after->getType())
192 {
193 case ltACCOUNT_ROOT:
194 drops_ += (*after)[sfBalance].xrp().drops();
195 break;
196 case ltPAYCHAN:
197 if (!isDelete)
198 drops_ += ((*after)[sfAmount] - (*after)[sfBalance])
199 .xrp()
200 .drops();
201 break;
202 case ltESCROW:
203 if (!isDelete && isXRP((*after)[sfAmount]))
204 drops_ += (*after)[sfAmount].xrp().drops();
205 break;
206 default:
207 break;
208 }
209 }
210}
211
212bool
214 STTx const& tx,
215 TER const,
216 XRPAmount const fee,
217 ReadView const&,
218 beast::Journal const& j)
219{
220 // The net change should never be positive, as this would mean that the
221 // transaction created XRP out of thin air. That's not possible.
222 if (drops_ > 0)
223 {
224 JLOG(j.fatal()) << "Invariant failed: XRP net change was positive: "
225 << drops_;
226 return false;
227 }
228
229 // The negative of the net change should be equal to actual fee charged.
230 if (-drops_ != fee.drops())
231 {
232 JLOG(j.fatal()) << "Invariant failed: XRP net change of " << drops_
233 << " doesn't match fee " << fee.drops();
234 return false;
235 }
236
237 return true;
238}
239
240//------------------------------------------------------------------------------
241
242void
244 bool,
245 std::shared_ptr<SLE const> const& before,
247{
248 auto isBad = [](STAmount const& balance) {
249 if (!balance.native())
250 return true;
251
252 auto const drops = balance.xrp();
253
254 // Can't have more than the number of drops instantiated
255 // in the genesis ledger.
256 if (drops > INITIAL_XRP)
257 return true;
258
259 // Can't have a negative balance (0 is OK)
260 if (drops < XRPAmount{0})
261 return true;
262
263 return false;
264 };
265
266 if (before && before->getType() == ltACCOUNT_ROOT)
267 bad_ |= isBad((*before)[sfBalance]);
268
269 if (after && after->getType() == ltACCOUNT_ROOT)
270 bad_ |= isBad((*after)[sfBalance]);
271}
272
273bool
275 STTx const&,
276 TER const,
277 XRPAmount const,
278 ReadView const&,
279 beast::Journal const& j)
280{
281 if (bad_)
282 {
283 JLOG(j.fatal()) << "Invariant failed: incorrect account XRP balance";
284 return false;
285 }
286
287 return true;
288}
289
290//------------------------------------------------------------------------------
291
292void
294 bool isDelete,
295 std::shared_ptr<SLE const> const& before,
297{
298 auto isBad = [](STAmount const& pays, STAmount const& gets) {
299 // An offer should never be negative
300 if (pays < beast::zero)
301 return true;
302
303 if (gets < beast::zero)
304 return true;
305
306 // Can't have an XRP to XRP offer:
307 return pays.native() && gets.native();
308 };
309
310 if (before && before->getType() == ltOFFER)
311 bad_ |= isBad((*before)[sfTakerPays], (*before)[sfTakerGets]);
312
313 if (after && after->getType() == ltOFFER)
314 bad_ |= isBad((*after)[sfTakerPays], (*after)[sfTakerGets]);
315}
316
317bool
319 STTx const&,
320 TER const,
321 XRPAmount const,
322 ReadView const&,
323 beast::Journal const& j)
324{
325 if (bad_)
326 {
327 JLOG(j.fatal()) << "Invariant failed: offer with a bad amount";
328 return false;
329 }
330
331 return true;
332}
333
334//------------------------------------------------------------------------------
335
336void
338 bool isDelete,
339 std::shared_ptr<SLE const> const& before,
341{
342 auto isBad = [](STAmount const& amount) {
343 // XRP case
344 if (amount.native())
345 {
346 if (amount.xrp() <= XRPAmount{0})
347 return true;
348
349 if (amount.xrp() >= INITIAL_XRP)
350 return true;
351 }
352 else
353 {
354 // IOU case
355 if (amount.holds<Issue>())
356 {
357 if (amount <= beast::zero)
358 return true;
359
360 if (badCurrency() == amount.getCurrency())
361 return true;
362 }
363
364 // MPT case
365 if (amount.holds<MPTIssue>())
366 {
367 if (amount <= beast::zero)
368 return true;
369
370 if (amount.mpt() > MPTAmount{maxMPTokenAmount})
371 return true; // LCOV_EXCL_LINE
372 }
373 }
374 return false;
375 };
376
377 if (before && before->getType() == ltESCROW)
378 bad_ |= isBad((*before)[sfAmount]);
379
380 if (after && after->getType() == ltESCROW)
381 bad_ |= isBad((*after)[sfAmount]);
382
383 auto checkAmount = [this](std::int64_t amount) {
384 if (amount > maxMPTokenAmount || amount < 0)
385 bad_ = true;
386 };
387
388 if (after && after->getType() == ltMPTOKEN_ISSUANCE)
389 {
390 auto const outstanding = (*after)[sfOutstandingAmount];
391 checkAmount(outstanding);
392 if (auto const locked = (*after)[~sfLockedAmount])
393 {
394 checkAmount(*locked);
395 bad_ = outstanding < *locked;
396 }
397 }
398
399 if (after && after->getType() == ltMPTOKEN)
400 {
401 auto const mptAmount = (*after)[sfMPTAmount];
402 checkAmount(mptAmount);
403 if (auto const locked = (*after)[~sfLockedAmount])
404 {
405 checkAmount(*locked);
406 }
407 }
408}
409
410bool
412 STTx const& txn,
413 TER const,
414 XRPAmount const,
415 ReadView const& rv,
416 beast::Journal const& j)
417{
418 if (bad_)
419 {
420 JLOG(j.fatal()) << "Invariant failed: escrow specifies invalid amount";
421 return false;
422 }
423
424 return true;
425}
426
427//------------------------------------------------------------------------------
428
429void
431 bool isDelete,
432 std::shared_ptr<SLE const> const& before,
434{
435 if (isDelete && before && before->getType() == ltACCOUNT_ROOT)
437}
438
439bool
441 STTx const& tx,
442 TER const result,
443 XRPAmount const,
444 ReadView const&,
445 beast::Journal const& j)
446{
447 // AMM account root can be deleted as the result of AMM withdraw/delete
448 // transaction when the total AMM LP Tokens balance goes to 0.
449 // A successful AccountDelete or AMMDelete MUST delete exactly
450 // one account root.
451 if (hasPrivilege(tx, mustDeleteAcct) && result == tesSUCCESS)
452 {
453 if (accountsDeleted_ == 1)
454 return true;
455
456 if (accountsDeleted_ == 0)
457 JLOG(j.fatal()) << "Invariant failed: account deletion "
458 "succeeded without deleting an account";
459 else
460 JLOG(j.fatal()) << "Invariant failed: account deletion "
461 "succeeded but deleted multiple accounts!";
462 return false;
463 }
464
465 // A successful AMMWithdraw/AMMClawback MAY delete one account root
466 // when the total AMM LP Tokens balance goes to 0. Not every AMM withdraw
467 // deletes the AMM account, accountsDeleted_ is set if it is deleted.
468 if (hasPrivilege(tx, mayDeleteAcct) && result == tesSUCCESS &&
469 accountsDeleted_ == 1)
470 return true;
471
472 if (accountsDeleted_ == 0)
473 return true;
474
475 JLOG(j.fatal()) << "Invariant failed: an account root was deleted";
476 return false;
477}
478
479//------------------------------------------------------------------------------
480
481void
483 bool isDelete,
484 std::shared_ptr<SLE const> const& before,
486{
487 if (isDelete && before && before->getType() == ltACCOUNT_ROOT)
488 accountsDeleted_.emplace_back(before, after);
489}
490
491bool
493 STTx const& tx,
494 TER const result,
495 XRPAmount const,
496 ReadView const& view,
497 beast::Journal const& j)
498{
499 // Always check for objects in the ledger, but to prevent differing
500 // transaction processing results, however unlikely, only fail if the
501 // feature is enabled. Enabled, or not, though, a fatal-level message will
502 // be logged
503 [[maybe_unused]] bool const enforce =
504 view.rules().enabled(featureInvariantsV1_1) ||
505 view.rules().enabled(featureSingleAssetVault) ||
506 view.rules().enabled(featureLendingProtocol);
507
508 auto const objectExists = [&view, enforce, &j](auto const& keylet) {
509 (void)enforce;
510 if (auto const sle = view.read(keylet))
511 {
512 // Finding the object is bad
513 auto const typeName = [&sle]() {
514 auto item =
515 LedgerFormats::getInstance().findByType(sle->getType());
516
517 if (item != nullptr)
518 return item->getName();
519 return std::to_string(sle->getType());
520 }();
521
522 JLOG(j.fatal())
523 << "Invariant failed: account deletion left behind a "
524 << typeName << " object";
525 // The comment above starting with "assert(enforce)" explains this
526 // assert.
527 XRPL_ASSERT(
528 enforce,
529 "xrpl::AccountRootsDeletedClean::finalize::objectExists : "
530 "account deletion left no objects behind");
531 return true;
532 }
533 return false;
534 };
535
536 for (auto const& [before, after] : accountsDeleted_)
537 {
538 auto const accountID = before->getAccountID(sfAccount);
539 // An account should not be deleted with a balance
540 if (after->at(sfBalance) != beast::zero)
541 {
542 JLOG(j.fatal()) << "Invariant failed: account deletion left "
543 "behind a non-zero balance";
544 XRPL_ASSERT(
545 enforce,
546 "xrpl::AccountRootsDeletedClean::finalize : "
547 "deleted account has zero balance");
548 if (enforce)
549 return false;
550 }
551 // An account should not be deleted with a non-zero owner count
552 if (after->at(sfOwnerCount) != 0)
553 {
554 JLOG(j.fatal()) << "Invariant failed: account deletion left "
555 "behind a non-zero owner count";
556 XRPL_ASSERT(
557 enforce,
558 "xrpl::AccountRootsDeletedClean::finalize : "
559 "deleted account has zero owner count");
560 if (enforce)
561 return false;
562 }
563 // Simple types
564 for (auto const& [keyletfunc, _, __] : directAccountKeylets)
565 {
566 if (objectExists(std::invoke(keyletfunc, accountID)) && enforce)
567 return false;
568 }
569
570 {
571 // NFT pages. nftpage_min and nftpage_max were already explicitly
572 // checked above as entries in directAccountKeylets. This uses
573 // view.succ() to check for any NFT pages in between the two
574 // endpoints.
575 Keylet const first = keylet::nftpage_min(accountID);
576 Keylet const last = keylet::nftpage_max(accountID);
577
578 std::optional<uint256> key = view.succ(first.key, last.key.next());
579
580 // current page
581 if (key && objectExists(Keylet{ltNFTOKEN_PAGE, *key}) && enforce)
582 return false;
583 }
584
585 // If the account is a pseudo account, then the linked object must
586 // also be deleted. e.g. AMM, Vault, etc.
587 for (auto const& field : getPseudoAccountFields())
588 {
589 if (before->isFieldPresent(*field))
590 {
591 auto const key = before->getFieldH256(*field);
592 if (objectExists(keylet::unchecked(key)) && enforce)
593 return false;
594 }
595 }
596 }
597
598 return true;
599}
600
601//------------------------------------------------------------------------------
602
603void
605 bool,
606 std::shared_ptr<SLE const> const& before,
608{
609 if (before && after && before->getType() != after->getType())
610 typeMismatch_ = true;
611
612 if (after)
613 {
614#pragma push_macro("LEDGER_ENTRY")
615#undef LEDGER_ENTRY
616
617#define LEDGER_ENTRY(tag, ...) case tag:
618
619 switch (after->getType())
620 {
621#include <xrpl/protocol/detail/ledger_entries.macro>
622
623 break;
624 default:
625 invalidTypeAdded_ = true;
626 break;
627 }
628
629#undef LEDGER_ENTRY
630#pragma pop_macro("LEDGER_ENTRY")
631 }
632}
633
634bool
636 STTx const&,
637 TER const,
638 XRPAmount const,
639 ReadView const&,
640 beast::Journal const& j)
641{
642 if ((!typeMismatch_) && (!invalidTypeAdded_))
643 return true;
644
645 if (typeMismatch_)
646 {
647 JLOG(j.fatal()) << "Invariant failed: ledger entry type mismatch";
648 }
649
651 {
652 JLOG(j.fatal()) << "Invariant failed: invalid ledger entry type added";
653 }
654
655 return false;
656}
657
658//------------------------------------------------------------------------------
659
660void
662 bool,
665{
666 if (after && after->getType() == ltRIPPLE_STATE)
667 {
668 // checking the issue directly here instead of
669 // relying on .native() just in case native somehow
670 // were systematically incorrect
672 after->getFieldAmount(sfLowLimit).issue() == xrpIssue() ||
673 after->getFieldAmount(sfHighLimit).issue() == xrpIssue();
674 }
675}
676
677bool
679 STTx const&,
680 TER const,
681 XRPAmount const,
682 ReadView const&,
683 beast::Journal const& j)
684{
685 if (!xrpTrustLine_)
686 return true;
687
688 JLOG(j.fatal()) << "Invariant failed: an XRP trust line was created";
689 return false;
690}
691
692//------------------------------------------------------------------------------
693
694void
696 bool,
699{
700 if (after && after->getType() == ltRIPPLE_STATE)
701 {
702 std::uint32_t const uFlags = after->getFieldU32(sfFlags);
703 bool const lowFreeze = uFlags & lsfLowFreeze;
704 bool const lowDeepFreeze = uFlags & lsfLowDeepFreeze;
705
706 bool const highFreeze = uFlags & lsfHighFreeze;
707 bool const highDeepFreeze = uFlags & lsfHighDeepFreeze;
708
710 (lowDeepFreeze && !lowFreeze) || (highDeepFreeze && !highFreeze);
711 }
712}
713
714bool
716 STTx const&,
717 TER const,
718 XRPAmount const,
719 ReadView const&,
720 beast::Journal const& j)
721{
723 return true;
724
725 JLOG(j.fatal()) << "Invariant failed: a trust line with deep freeze flag "
726 "without normal freeze was created";
727 return false;
728}
729
730//------------------------------------------------------------------------------
731
732void
734 bool isDelete,
735 std::shared_ptr<SLE const> const& before,
737{
738 /*
739 * A trust line freeze state alone doesn't determine if a transfer is
740 * frozen. The transfer must be examined "end-to-end" because both sides of
741 * the transfer may have different freeze states and freeze impact depends
742 * on the transfer direction. This is why first we need to track the
743 * transfers using IssuerChanges senders/receivers.
744 *
745 * Only in validateIssuerChanges, after we collected all changes can we
746 * determine if the transfer is valid.
747 */
748 if (!isValidEntry(before, after))
749 {
750 return;
751 }
752
753 auto const balanceChange = calculateBalanceChange(before, after, isDelete);
754 if (balanceChange.signum() == 0)
755 {
756 return;
757 }
758
759 recordBalanceChanges(after, balanceChange);
760}
761
762bool
764 STTx const& tx,
765 TER const ter,
766 XRPAmount const fee,
767 ReadView const& view,
768 beast::Journal const& j)
769{
770 /*
771 * We check this invariant regardless of deep freeze amendment status,
772 * allowing for detection and logging of potential issues even when the
773 * amendment is disabled.
774 *
775 * If an exploit that allows moving frozen assets is discovered,
776 * we can alert operators who monitor fatal messages and trigger assert in
777 * debug builds for an early warning.
778 *
779 * In an unlikely event that an exploit is found, this early detection
780 * enables encouraging the UNL to expedite deep freeze amendment activation
781 * or deploy hotfixes via new amendments. In case of a new amendment, we'd
782 * only have to change this line setting 'enforce' variable.
783 * enforce = view.rules().enabled(featureDeepFreeze) ||
784 * view.rules().enabled(fixFreezeExploit);
785 */
786 [[maybe_unused]] bool const enforce =
787 view.rules().enabled(featureDeepFreeze);
788
789 for (auto const& [issue, changes] : balanceChanges_)
790 {
791 auto const issuerSle = findIssuer(issue.account, view);
792 // It should be impossible for the issuer to not be found, but check
793 // just in case so rippled doesn't crash in release.
794 if (!issuerSle)
795 {
796 // The comment above starting with "assert(enforce)" explains this
797 // assert.
798 XRPL_ASSERT(
799 enforce,
800 "xrpl::TransfersNotFrozen::finalize : enforce "
801 "invariant.");
802 if (enforce)
803 {
804 return false;
805 }
806 continue;
807 }
808
809 if (!validateIssuerChanges(issuerSle, changes, tx, j, enforce))
810 {
811 return false;
812 }
813 }
814
815 return true;
816}
817
818bool
820 std::shared_ptr<SLE const> const& before,
822{
823 // `after` can never be null, even if the trust line is deleted.
824 XRPL_ASSERT(after, "xrpl::TransfersNotFrozen::isValidEntry : valid after.");
825 if (!after)
826 {
827 return false;
828 }
829
830 if (after->getType() == ltACCOUNT_ROOT)
831 {
832 possibleIssuers_.emplace(after->at(sfAccount), after);
833 return false;
834 }
835
836 /* While LedgerEntryTypesMatch invariant also checks types, all invariants
837 * are processed regardless of previous failures.
838 *
839 * This type check is still necessary here because it prevents potential
840 * issues in subsequent processing.
841 */
842 return after->getType() == ltRIPPLE_STATE &&
843 (!before || before->getType() == ltRIPPLE_STATE);
844}
845
848 std::shared_ptr<SLE const> const& before,
850 bool isDelete)
851{
852 auto const getBalance = [](auto const& line, auto const& other, bool zero) {
853 STAmount amt =
854 line ? line->at(sfBalance) : other->at(sfBalance).zeroed();
855 return zero ? amt.zeroed() : amt;
856 };
857
858 /* Trust lines can be created dynamically by other transactions such as
859 * Payment and OfferCreate that cross offers. Such trust line won't be
860 * created frozen, but the sender might be, so the starting balance must be
861 * treated as zero.
862 */
863 auto const balanceBefore = getBalance(before, after, false);
864
865 /* Same as above, trust lines can be dynamically deleted, and for frozen
866 * trust lines, payments not involving the issuer must be blocked. This is
867 * achieved by treating the final balance as zero when isDelete=true to
868 * ensure frozen line restrictions are enforced even during deletion.
869 */
870 auto const balanceAfter = getBalance(after, before, isDelete);
871
872 return balanceAfter - balanceBefore;
873}
874
875void
877{
878 XRPL_ASSERT(
879 change.balanceChangeSign,
880 "xrpl::TransfersNotFrozen::recordBalance : valid trustline "
881 "balance sign.");
882 auto& changes = balanceChanges_[issue];
883 if (change.balanceChangeSign < 0)
884 changes.senders.emplace_back(std::move(change));
885 else
886 changes.receivers.emplace_back(std::move(change));
887}
888
889void
892 STAmount const& balanceChange)
893{
894 auto const balanceChangeSign = balanceChange.signum();
895 auto const currency = after->at(sfBalance).getCurrency();
896
897 // Change from low account's perspective, which is trust line default
899 {currency, after->at(sfHighLimit).getIssuer()},
900 {after, balanceChangeSign});
901
902 // Change from high account's perspective, which reverses the sign.
904 {currency, after->at(sfLowLimit).getIssuer()},
905 {after, -balanceChangeSign});
906}
907
910{
911 if (auto it = possibleIssuers_.find(issuerID); it != possibleIssuers_.end())
912 {
913 return it->second;
914 }
915
916 return view.read(keylet::account(issuerID));
917}
918
919bool
921 std::shared_ptr<SLE const> const& issuer,
922 IssuerChanges const& changes,
923 STTx const& tx,
924 beast::Journal const& j,
925 bool enforce)
926{
927 if (!issuer)
928 {
929 return false;
930 }
931
932 bool const globalFreeze = issuer->isFlag(lsfGlobalFreeze);
933 if (changes.receivers.empty() || changes.senders.empty())
934 {
935 /* If there are no receivers, then the holder(s) are returning
936 * their tokens to the issuer. Likewise, if there are no
937 * senders, then the issuer is issuing tokens to the holder(s).
938 * This is allowed regardless of the issuer's freeze flags. (The
939 * holder may have contradicting freeze flags, but that will be
940 * checked when the holder is treated as issuer.)
941 */
942 return true;
943 }
944
945 for (auto const& actors : {changes.senders, changes.receivers})
946 {
947 for (auto const& change : actors)
948 {
949 bool const high = change.line->at(sfLowLimit).getIssuer() ==
950 issuer->at(sfAccount);
951
953 change, high, tx, j, enforce, globalFreeze))
954 {
955 return false;
956 }
957 }
958 }
959 return true;
960}
961
962bool
964 BalanceChange const& change,
965 bool high,
966 STTx const& tx,
967 beast::Journal const& j,
968 bool enforce,
969 bool globalFreeze)
970{
971 bool const freeze = change.balanceChangeSign < 0 &&
972 change.line->isFlag(high ? lsfLowFreeze : lsfHighFreeze);
973 bool const deepFreeze =
974 change.line->isFlag(high ? lsfLowDeepFreeze : lsfHighDeepFreeze);
975 bool const frozen = globalFreeze || deepFreeze || freeze;
976
977 bool const isAMMLine = change.line->isFlag(lsfAMMNode);
978
979 if (!frozen)
980 {
981 return true;
982 }
983
984 // AMMClawbacks are allowed to override some freeze rules
985 if ((!isAMMLine || globalFreeze) && hasPrivilege(tx, overrideFreeze))
986 {
987 JLOG(j.debug()) << "Invariant check allowing funds to be moved "
988 << (change.balanceChangeSign > 0 ? "to" : "from")
989 << " a frozen trustline for AMMClawback "
990 << tx.getTransactionID();
991 return true;
992 }
993
994 JLOG(j.fatal()) << "Invariant failed: Attempting to move frozen funds for "
995 << tx.getTransactionID();
996 // The comment above starting with "assert(enforce)" explains this assert.
997 XRPL_ASSERT(
998 enforce,
999 "xrpl::TransfersNotFrozen::validateFrozenState : enforce "
1000 "invariant.");
1001
1002 if (enforce)
1003 {
1004 return false;
1005 }
1006
1007 return true;
1008}
1009
1010//------------------------------------------------------------------------------
1011
1012void
1014 bool,
1015 std::shared_ptr<SLE const> const& before,
1017{
1018 if (!before && after->getType() == ltACCOUNT_ROOT)
1019 {
1021 accountSeq_ = (*after)[sfSequence];
1023 flags_ = after->getFlags();
1024 }
1025}
1026
1027bool
1029 STTx const& tx,
1030 TER const result,
1031 XRPAmount const,
1032 ReadView const& view,
1033 beast::Journal const& j)
1034{
1035 if (accountsCreated_ == 0)
1036 return true;
1037
1038 if (accountsCreated_ > 1)
1039 {
1040 JLOG(j.fatal()) << "Invariant failed: multiple accounts "
1041 "created in a single transaction";
1042 return false;
1043 }
1044
1045 // From this point on we know exactly one account was created.
1046 if (hasPrivilege(tx, createAcct | createPseudoAcct) && result == tesSUCCESS)
1047 {
1048 bool const pseudoAccount =
1049 (pseudoAccount_ &&
1050 (view.rules().enabled(featureSingleAssetVault) ||
1051 view.rules().enabled(featureLendingProtocol)));
1052
1053 if (pseudoAccount && !hasPrivilege(tx, createPseudoAcct))
1054 {
1055 JLOG(j.fatal()) << "Invariant failed: pseudo-account created by a "
1056 "wrong transaction type";
1057 return false;
1058 }
1059
1060 std::uint32_t const startingSeq = pseudoAccount ? 0 : view.seq();
1061
1062 if (accountSeq_ != startingSeq)
1063 {
1064 JLOG(j.fatal()) << "Invariant failed: account created with "
1065 "wrong starting sequence number";
1066 return false;
1067 }
1068
1069 if (pseudoAccount)
1070 {
1071 std::uint32_t const expected =
1073 if (flags_ != expected)
1074 {
1075 JLOG(j.fatal())
1076 << "Invariant failed: pseudo-account created with "
1077 "wrong flags";
1078 return false;
1079 }
1080 }
1081
1082 return true;
1083 }
1084
1085 JLOG(j.fatal()) << "Invariant failed: account root created illegally";
1086 return false;
1087} // namespace xrpl
1088
1089//------------------------------------------------------------------------------
1090
1091void
1093 bool isDelete,
1094 std::shared_ptr<SLE const> const& before,
1096{
1097 static constexpr uint256 const& pageBits = nft::pageMask;
1098 static constexpr uint256 const accountBits = ~pageBits;
1099
1100 if ((before && before->getType() != ltNFTOKEN_PAGE) ||
1101 (after && after->getType() != ltNFTOKEN_PAGE))
1102 return;
1103
1104 auto check = [this, isDelete](std::shared_ptr<SLE const> const& sle) {
1105 uint256 const account = sle->key() & accountBits;
1106 uint256 const hiLimit = sle->key() & pageBits;
1107 std::optional<uint256> const prev = (*sle)[~sfPreviousPageMin];
1108
1109 // Make sure that any page links...
1110 // 1. Are properly associated with the owning account and
1111 // 2. The page is correctly ordered between links.
1112 if (prev)
1113 {
1114 if (account != (*prev & accountBits))
1115 badLink_ = true;
1116
1117 if (hiLimit <= (*prev & pageBits))
1118 badLink_ = true;
1119 }
1120
1121 if (auto const next = (*sle)[~sfNextPageMin])
1122 {
1123 if (account != (*next & accountBits))
1124 badLink_ = true;
1125
1126 if (hiLimit >= (*next & pageBits))
1127 badLink_ = true;
1128 }
1129
1130 {
1131 auto const& nftokens = sle->getFieldArray(sfNFTokens);
1132
1133 // An NFTokenPage should never contain too many tokens or be empty.
1134 if (std::size_t const nftokenCount = nftokens.size();
1135 (!isDelete && nftokenCount == 0) ||
1136 nftokenCount > dirMaxTokensPerPage)
1137 invalidSize_ = true;
1138
1139 // If prev is valid, use it to establish a lower bound for
1140 // page entries. If prev is not valid the lower bound is zero.
1141 uint256 const loLimit =
1142 prev ? *prev & pageBits : uint256(beast::zero);
1143
1144 // Also verify that all NFTokenIDs in the page are sorted.
1145 uint256 loCmp = loLimit;
1146 for (auto const& obj : nftokens)
1147 {
1148 uint256 const tokenID = obj[sfNFTokenID];
1149 if (!nft::compareTokens(loCmp, tokenID))
1150 badSort_ = true;
1151 loCmp = tokenID;
1152
1153 // None of the NFTs on this page should belong on lower or
1154 // higher pages.
1155 if (uint256 const tokenPageBits = tokenID & pageBits;
1156 tokenPageBits < loLimit || tokenPageBits >= hiLimit)
1157 badEntry_ = true;
1158
1159 if (auto uri = obj[~sfURI]; uri && uri->empty())
1160 badURI_ = true;
1161 }
1162 }
1163 };
1164
1165 if (before)
1166 {
1167 check(before);
1168
1169 // While an account's NFToken directory contains any NFTokens, the last
1170 // NFTokenPage (with 96 bits of 1 in the low part of the index) should
1171 // never be deleted.
1172 if (isDelete && (before->key() & nft::pageMask) == nft::pageMask &&
1173 before->isFieldPresent(sfPreviousPageMin))
1174 {
1175 deletedFinalPage_ = true;
1176 }
1177 }
1178
1179 if (after)
1180 check(after);
1181
1182 if (!isDelete && before && after)
1183 {
1184 // If the NFTokenPage
1185 // 1. Has a NextMinPage field in before, but loses it in after, and
1186 // 2. This is not the last page in the directory
1187 // Then we have identified a corruption in the links between the
1188 // NFToken pages in the NFToken directory.
1189 if ((before->key() & nft::pageMask) != nft::pageMask &&
1190 before->isFieldPresent(sfNextPageMin) &&
1191 !after->isFieldPresent(sfNextPageMin))
1192 {
1193 deletedLink_ = true;
1194 }
1195 }
1196}
1197
1198bool
1200 STTx const& tx,
1201 TER const result,
1202 XRPAmount const,
1203 ReadView const& view,
1204 beast::Journal const& j)
1205{
1206 if (badLink_)
1207 {
1208 JLOG(j.fatal()) << "Invariant failed: NFT page is improperly linked.";
1209 return false;
1210 }
1211
1212 if (badEntry_)
1213 {
1214 JLOG(j.fatal()) << "Invariant failed: NFT found in incorrect page.";
1215 return false;
1216 }
1217
1218 if (badSort_)
1219 {
1220 JLOG(j.fatal()) << "Invariant failed: NFTs on page are not sorted.";
1221 return false;
1222 }
1223
1224 if (badURI_)
1225 {
1226 JLOG(j.fatal()) << "Invariant failed: NFT contains empty URI.";
1227 return false;
1228 }
1229
1230 if (invalidSize_)
1231 {
1232 JLOG(j.fatal()) << "Invariant failed: NFT page has invalid size.";
1233 return false;
1234 }
1235
1236 if (view.rules().enabled(fixNFTokenPageLinks))
1237 {
1239 {
1240 JLOG(j.fatal()) << "Invariant failed: Last NFT page deleted with "
1241 "non-empty directory.";
1242 return false;
1243 }
1244 if (deletedLink_)
1245 {
1246 JLOG(j.fatal()) << "Invariant failed: Lost NextMinPage link.";
1247 return false;
1248 }
1249 }
1250
1251 return true;
1252}
1253
1254//------------------------------------------------------------------------------
1255void
1257 bool,
1258 std::shared_ptr<SLE const> const& before,
1260{
1261 if (before && before->getType() == ltACCOUNT_ROOT)
1262 {
1263 beforeMintedTotal += (*before)[~sfMintedNFTokens].value_or(0);
1264 beforeBurnedTotal += (*before)[~sfBurnedNFTokens].value_or(0);
1265 }
1266
1267 if (after && after->getType() == ltACCOUNT_ROOT)
1268 {
1269 afterMintedTotal += (*after)[~sfMintedNFTokens].value_or(0);
1270 afterBurnedTotal += (*after)[~sfBurnedNFTokens].value_or(0);
1271 }
1272}
1273
1274bool
1276 STTx const& tx,
1277 TER const result,
1278 XRPAmount const,
1279 ReadView const& view,
1280 beast::Journal const& j)
1281{
1282 if (!hasPrivilege(tx, changeNFTCounts))
1283 {
1285 {
1286 JLOG(j.fatal()) << "Invariant failed: the number of minted tokens "
1287 "changed without a mint transaction!";
1288 return false;
1289 }
1290
1292 {
1293 JLOG(j.fatal()) << "Invariant failed: the number of burned tokens "
1294 "changed without a burn transaction!";
1295 return false;
1296 }
1297
1298 return true;
1299 }
1300
1301 if (tx.getTxnType() == ttNFTOKEN_MINT)
1302 {
1303 if (result == tesSUCCESS && beforeMintedTotal >= afterMintedTotal)
1304 {
1305 JLOG(j.fatal())
1306 << "Invariant failed: successful minting didn't increase "
1307 "the number of minted tokens.";
1308 return false;
1309 }
1310
1311 if (result != tesSUCCESS && beforeMintedTotal != afterMintedTotal)
1312 {
1313 JLOG(j.fatal()) << "Invariant failed: failed minting changed the "
1314 "number of minted tokens.";
1315 return false;
1316 }
1317
1319 {
1320 JLOG(j.fatal())
1321 << "Invariant failed: minting changed the number of "
1322 "burned tokens.";
1323 return false;
1324 }
1325 }
1326
1327 if (tx.getTxnType() == ttNFTOKEN_BURN)
1328 {
1329 if (result == tesSUCCESS)
1330 {
1332 {
1333 JLOG(j.fatal())
1334 << "Invariant failed: successful burning didn't increase "
1335 "the number of burned tokens.";
1336 return false;
1337 }
1338 }
1339
1340 if (result != tesSUCCESS && beforeBurnedTotal != afterBurnedTotal)
1341 {
1342 JLOG(j.fatal()) << "Invariant failed: failed burning changed the "
1343 "number of burned tokens.";
1344 return false;
1345 }
1346
1348 {
1349 JLOG(j.fatal())
1350 << "Invariant failed: burning changed the number of "
1351 "minted tokens.";
1352 return false;
1353 }
1354 }
1355
1356 return true;
1357}
1358
1359//------------------------------------------------------------------------------
1360
1361void
1363 bool,
1364 std::shared_ptr<SLE const> const& before,
1366{
1367 if (before && before->getType() == ltRIPPLE_STATE)
1369
1370 if (before && before->getType() == ltMPTOKEN)
1372}
1373
1374bool
1376 STTx const& tx,
1377 TER const result,
1378 XRPAmount const,
1379 ReadView const& view,
1380 beast::Journal const& j)
1381{
1382 if (tx.getTxnType() != ttCLAWBACK)
1383 return true;
1384
1385 if (result == tesSUCCESS)
1386 {
1387 if (trustlinesChanged > 1)
1388 {
1389 JLOG(j.fatal())
1390 << "Invariant failed: more than one trustline changed.";
1391 return false;
1392 }
1393
1394 if (mptokensChanged > 1)
1395 {
1396 JLOG(j.fatal())
1397 << "Invariant failed: more than one mptokens changed.";
1398 return false;
1399 }
1400
1401 if (trustlinesChanged == 1)
1402 {
1403 AccountID const issuer = tx.getAccountID(sfAccount);
1404 STAmount const& amount = tx.getFieldAmount(sfAmount);
1405 AccountID const& holder = amount.getIssuer();
1406 STAmount const holderBalance = accountHolds(
1407 view, holder, amount.getCurrency(), issuer, fhIGNORE_FREEZE, j);
1408
1409 if (holderBalance.signum() < 0)
1410 {
1411 JLOG(j.fatal())
1412 << "Invariant failed: trustline balance is negative";
1413 return false;
1414 }
1415 }
1416 }
1417 else
1418 {
1419 if (trustlinesChanged != 0)
1420 {
1421 JLOG(j.fatal()) << "Invariant failed: some trustlines were changed "
1422 "despite failure of the transaction.";
1423 return false;
1424 }
1425
1426 if (mptokensChanged != 0)
1427 {
1428 JLOG(j.fatal()) << "Invariant failed: some mptokens were changed "
1429 "despite failure of the transaction.";
1430 return false;
1431 }
1432 }
1433
1434 return true;
1435}
1436
1437//------------------------------------------------------------------------------
1438
1439void
1441 bool isDelete,
1442 std::shared_ptr<SLE const> const& before,
1444{
1445 if (after && after->getType() == ltMPTOKEN_ISSUANCE)
1446 {
1447 if (isDelete)
1449 else if (!before)
1451 }
1452
1453 if (after && after->getType() == ltMPTOKEN)
1454 {
1455 if (isDelete)
1457 else if (!before)
1458 {
1460 MPTIssue const mptIssue{after->at(sfMPTokenIssuanceID)};
1461 if (mptIssue.getIssuer() == after->at(sfAccount))
1462 mptCreatedByIssuer_ = true;
1463 }
1464 }
1465}
1466
1467bool
1469 STTx const& tx,
1470 TER const result,
1471 XRPAmount const _fee,
1472 ReadView const& view,
1473 beast::Journal const& j)
1474{
1475 if (result == tesSUCCESS)
1476 {
1477 auto const& rules = view.rules();
1478 [[maybe_unused]]
1479 bool enforceCreatedByIssuer = rules.enabled(featureSingleAssetVault) ||
1480 rules.enabled(featureLendingProtocol);
1482 {
1483 JLOG(j.fatal())
1484 << "Invariant failed: MPToken created for the MPT issuer";
1485 // The comment above starting with "assert(enforce)" explains this
1486 // assert.
1487 XRPL_ASSERT_PARTS(
1488 enforceCreatedByIssuer,
1489 "xrpl::ValidMPTIssuance::finalize",
1490 "no issuer MPToken");
1491 if (enforceCreatedByIssuer)
1492 return false;
1493 }
1494
1495 auto const txnType = tx.getTxnType();
1497 {
1498 if (mptIssuancesCreated_ == 0)
1499 {
1500 JLOG(j.fatal()) << "Invariant failed: transaction "
1501 "succeeded without creating a MPT issuance";
1502 }
1503 else if (mptIssuancesDeleted_ != 0)
1504 {
1505 JLOG(j.fatal()) << "Invariant failed: transaction "
1506 "succeeded while removing MPT issuances";
1507 }
1508 else if (mptIssuancesCreated_ > 1)
1509 {
1510 JLOG(j.fatal()) << "Invariant failed: transaction "
1511 "succeeded but created multiple issuances";
1512 }
1513
1514 return mptIssuancesCreated_ == 1 && mptIssuancesDeleted_ == 0;
1515 }
1516
1518 {
1519 if (mptIssuancesDeleted_ == 0)
1520 {
1521 JLOG(j.fatal()) << "Invariant failed: MPT issuance deletion "
1522 "succeeded without removing a MPT issuance";
1523 }
1524 else if (mptIssuancesCreated_ > 0)
1525 {
1526 JLOG(j.fatal()) << "Invariant failed: MPT issuance deletion "
1527 "succeeded while creating MPT issuances";
1528 }
1529 else if (mptIssuancesDeleted_ > 1)
1530 {
1531 JLOG(j.fatal()) << "Invariant failed: MPT issuance deletion "
1532 "succeeded but deleted multiple issuances";
1533 }
1534
1535 return mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 1;
1536 }
1537
1538 bool const lendingProtocolEnabled =
1539 view.rules().enabled(featureLendingProtocol);
1540 // ttESCROW_FINISH may authorize an MPT, but it can't have the
1541 // mayAuthorizeMPT privilege, because that may cause
1542 // non-amendment-gated side effects.
1543 bool const enforceEscrowFinish = (txnType == ttESCROW_FINISH) &&
1544 (view.rules().enabled(featureSingleAssetVault) ||
1545 lendingProtocolEnabled);
1547 enforceEscrowFinish)
1548 {
1549 bool const submittedByIssuer = tx.isFieldPresent(sfHolder);
1550
1551 if (mptIssuancesCreated_ > 0)
1552 {
1553 JLOG(j.fatal()) << "Invariant failed: MPT authorize "
1554 "succeeded but created MPT issuances";
1555 return false;
1556 }
1557 else if (mptIssuancesDeleted_ > 0)
1558 {
1559 JLOG(j.fatal()) << "Invariant failed: MPT authorize "
1560 "succeeded but deleted issuances";
1561 return false;
1562 }
1563 else if (
1564 lendingProtocolEnabled &&
1566 {
1567 JLOG(j.fatal()) << "Invariant failed: MPT authorize succeeded "
1568 "but created/deleted bad number mptokens";
1569 return false;
1570 }
1571 else if (
1572 submittedByIssuer &&
1574 {
1575 JLOG(j.fatal())
1576 << "Invariant failed: MPT authorize submitted by issuer "
1577 "succeeded but created/deleted mptokens";
1578 return false;
1579 }
1580 else if (
1581 !submittedByIssuer && hasPrivilege(tx, mustAuthorizeMPT) &&
1583 {
1584 // if the holder submitted this tx, then a mptoken must be
1585 // either created or deleted.
1586 JLOG(j.fatal())
1587 << "Invariant failed: MPT authorize submitted by holder "
1588 "succeeded but created/deleted bad number of mptokens";
1589 return false;
1590 }
1591
1592 return true;
1593 }
1594 if (txnType == ttESCROW_FINISH)
1595 {
1596 // ttESCROW_FINISH may authorize an MPT, but it can't have the
1597 // mayAuthorizeMPT privilege, because that may cause
1598 // non-amendment-gated side effects.
1599 XRPL_ASSERT_PARTS(
1600 !enforceEscrowFinish,
1601 "xrpl::ValidMPTIssuance::finalize",
1602 "not escrow finish tx");
1603 return true;
1604 }
1605
1606 if (hasPrivilege(tx, mayDeleteMPT) && mptokensDeleted_ == 1 &&
1609 return true;
1610 }
1611
1612 if (mptIssuancesCreated_ != 0)
1613 {
1614 JLOG(j.fatal()) << "Invariant failed: a MPT issuance was created";
1615 }
1616 else if (mptIssuancesDeleted_ != 0)
1617 {
1618 JLOG(j.fatal()) << "Invariant failed: a MPT issuance was deleted";
1619 }
1620 else if (mptokensCreated_ != 0)
1621 {
1622 JLOG(j.fatal()) << "Invariant failed: a MPToken was created";
1623 }
1624 else if (mptokensDeleted_ != 0)
1625 {
1626 JLOG(j.fatal()) << "Invariant failed: a MPToken was deleted";
1627 }
1628
1629 return mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 0 &&
1631}
1632
1633//------------------------------------------------------------------------------
1634
1635void
1637 bool,
1638 std::shared_ptr<SLE const> const& before,
1640{
1641 if (before && before->getType() != ltPERMISSIONED_DOMAIN)
1642 return;
1643 if (after && after->getType() != ltPERMISSIONED_DOMAIN)
1644 return;
1645
1646 auto check = [](SleStatus& sleStatus,
1647 std::shared_ptr<SLE const> const& sle) {
1648 auto const& credentials = sle->getFieldArray(sfAcceptedCredentials);
1649 sleStatus.credentialsSize_ = credentials.size();
1650 auto const sorted = credentials::makeSorted(credentials);
1651 sleStatus.isUnique_ = !sorted.empty();
1652
1653 // If array have duplicates then all the other checks are invalid
1654 sleStatus.isSorted_ = false;
1655
1656 if (sleStatus.isUnique_)
1657 {
1658 unsigned i = 0;
1659 for (auto const& cred : sorted)
1660 {
1661 auto const& credTx = credentials[i++];
1662 sleStatus.isSorted_ = (cred.first == credTx[sfIssuer]) &&
1663 (cred.second == credTx[sfCredentialType]);
1664 if (!sleStatus.isSorted_)
1665 break;
1666 }
1667 }
1668 };
1669
1670 if (before)
1671 {
1672 sleStatus_[0] = SleStatus();
1673 check(*sleStatus_[0], after);
1674 }
1675
1676 if (after)
1677 {
1678 sleStatus_[1] = SleStatus();
1679 check(*sleStatus_[1], after);
1680 }
1681}
1682
1683bool
1685 STTx const& tx,
1686 TER const result,
1687 XRPAmount const,
1688 ReadView const& view,
1689 beast::Journal const& j)
1690{
1691 if (tx.getTxnType() != ttPERMISSIONED_DOMAIN_SET || result != tesSUCCESS)
1692 return true;
1693
1694 auto check = [](SleStatus const& sleStatus, beast::Journal const& j) {
1695 if (!sleStatus.credentialsSize_)
1696 {
1697 JLOG(j.fatal()) << "Invariant failed: permissioned domain with "
1698 "no rules.";
1699 return false;
1700 }
1701
1702 if (sleStatus.credentialsSize_ >
1704 {
1705 JLOG(j.fatal()) << "Invariant failed: permissioned domain bad "
1706 "credentials size "
1707 << sleStatus.credentialsSize_;
1708 return false;
1709 }
1710
1711 if (!sleStatus.isUnique_)
1712 {
1713 JLOG(j.fatal())
1714 << "Invariant failed: permissioned domain credentials "
1715 "aren't unique";
1716 return false;
1717 }
1718
1719 if (!sleStatus.isSorted_)
1720 {
1721 JLOG(j.fatal())
1722 << "Invariant failed: permissioned domain credentials "
1723 "aren't sorted";
1724 return false;
1725 }
1726
1727 return true;
1728 };
1729
1730 return (sleStatus_[0] ? check(*sleStatus_[0], j) : true) &&
1731 (sleStatus_[1] ? check(*sleStatus_[1], j) : true);
1732}
1733
1734//------------------------------------------------------------------------------
1735
1736void
1738 bool isDelete,
1739 std::shared_ptr<SLE const> const& before,
1741{
1742 if (isDelete)
1743 // Deletion is ignored
1744 return;
1745
1746 if (after && after->getType() == ltACCOUNT_ROOT)
1747 {
1748 bool const isPseudo = [&]() {
1749 // isPseudoAccount checks that any of the pseudo-account fields are
1750 // set.
1752 return true;
1753 // Not all pseudo-accounts have a zero sequence, but all accounts
1754 // with a zero sequence had better be pseudo-accounts.
1755 if (after->at(sfSequence) == 0)
1756 return true;
1757
1758 return false;
1759 }();
1760 if (isPseudo)
1761 {
1762 // Pseudo accounts must have the following properties:
1763 // 1. Exactly one of the pseudo-account fields is set.
1764 // 2. The sequence number is not changed.
1765 // 3. The lsfDisableMaster, lsfDefaultRipple, and lsfDepositAuth
1766 // flags are set.
1767 // 4. The RegularKey is not set.
1768 {
1769 std::vector<SField const*> const& fields =
1771
1772 auto const numFields = std::count_if(
1773 fields.begin(),
1774 fields.end(),
1775 [&after](SField const* sf) -> bool {
1776 return after->isFieldPresent(*sf);
1777 });
1778 if (numFields != 1)
1779 {
1780 std::stringstream error;
1781 error << "pseudo-account has " << numFields
1782 << " pseudo-account fields set";
1783 errors_.emplace_back(error.str());
1784 }
1785 }
1786 if (before && before->at(sfSequence) != after->at(sfSequence))
1787 {
1788 errors_.emplace_back("pseudo-account sequence changed");
1789 }
1790 if (!after->isFlag(
1792 {
1793 errors_.emplace_back("pseudo-account flags are not set");
1794 }
1795 if (after->isFieldPresent(sfRegularKey))
1796 {
1797 errors_.emplace_back("pseudo-account has a regular key");
1798 }
1799 }
1800 }
1801}
1802
1803bool
1805 STTx const& tx,
1806 TER const,
1807 XRPAmount const,
1808 ReadView const& view,
1809 beast::Journal const& j)
1810{
1811 bool const enforce = view.rules().enabled(featureSingleAssetVault);
1812 XRPL_ASSERT(
1813 errors_.empty() || enforce,
1814 "xrpl::ValidPseudoAccounts::finalize : no bad "
1815 "changes or enforce invariant");
1816 if (!errors_.empty())
1817 {
1818 for (auto const& error : errors_)
1819 {
1820 JLOG(j.fatal()) << "Invariant failed: " << error;
1821 }
1822 if (enforce)
1823 return false;
1824 }
1825 return true;
1826}
1827
1828//------------------------------------------------------------------------------
1829
1830void
1832 bool,
1833 std::shared_ptr<SLE const> const& before,
1835{
1836 if (after && after->getType() == ltDIR_NODE)
1837 {
1838 if (after->isFieldPresent(sfDomainID))
1839 domains_.insert(after->getFieldH256(sfDomainID));
1840 }
1841
1842 if (after && after->getType() == ltOFFER)
1843 {
1844 if (after->isFieldPresent(sfDomainID))
1845 domains_.insert(after->getFieldH256(sfDomainID));
1846 else
1847 regularOffers_ = true;
1848
1849 // if a hybrid offer is missing domain or additional book, there's
1850 // something wrong
1851 if (after->isFlag(lsfHybrid) &&
1852 (!after->isFieldPresent(sfDomainID) ||
1853 !after->isFieldPresent(sfAdditionalBooks) ||
1854 after->getFieldArray(sfAdditionalBooks).size() > 1))
1855 badHybrids_ = true;
1856 }
1857}
1858
1859bool
1861 STTx const& tx,
1862 TER const result,
1863 XRPAmount const,
1864 ReadView const& view,
1865 beast::Journal const& j)
1866{
1867 auto const txType = tx.getTxnType();
1868 if ((txType != ttPAYMENT && txType != ttOFFER_CREATE) ||
1869 result != tesSUCCESS)
1870 return true;
1871
1872 // For each offercreate transaction, check if
1873 // permissioned offers are valid
1874 if (txType == ttOFFER_CREATE && badHybrids_)
1875 {
1876 JLOG(j.fatal()) << "Invariant failed: hybrid offer is malformed";
1877 return false;
1878 }
1879
1880 if (!tx.isFieldPresent(sfDomainID))
1881 return true;
1882
1883 auto const domain = tx.getFieldH256(sfDomainID);
1884
1885 if (!view.exists(keylet::permissionedDomain(domain)))
1886 {
1887 JLOG(j.fatal()) << "Invariant failed: domain doesn't exist";
1888 return false;
1889 }
1890
1891 // for both payment and offercreate, there shouldn't be another domain
1892 // that's different from the domain specified
1893 for (auto const& d : domains_)
1894 {
1895 if (d != domain)
1896 {
1897 JLOG(j.fatal()) << "Invariant failed: transaction"
1898 " consumed wrong domains";
1899 return false;
1900 }
1901 }
1902
1903 if (regularOffers_)
1904 {
1905 JLOG(j.fatal()) << "Invariant failed: domain transaction"
1906 " affected regular offers";
1907 return false;
1908 }
1909
1910 return true;
1911}
1912
1913void
1915 bool isDelete,
1916 std::shared_ptr<SLE const> const& before,
1918{
1919 if (isDelete)
1920 return;
1921
1922 if (after)
1923 {
1924 auto const type = after->getType();
1925 // AMM object changed
1926 if (type == ltAMM)
1927 {
1928 ammAccount_ = after->getAccountID(sfAccount);
1929 lptAMMBalanceAfter_ = after->getFieldAmount(sfLPTokenBalance);
1930 }
1931 // AMM pool changed
1932 else if (
1933 (type == ltRIPPLE_STATE && after->getFlags() & lsfAMMNode) ||
1934 (type == ltACCOUNT_ROOT && after->isFieldPresent(sfAMMID)))
1935 {
1936 ammPoolChanged_ = true;
1937 }
1938 }
1939
1940 if (before)
1941 {
1942 // AMM object changed
1943 if (before->getType() == ltAMM)
1944 {
1945 lptAMMBalanceBefore_ = before->getFieldAmount(sfLPTokenBalance);
1946 }
1947 }
1948}
1949
1950static bool
1952 STAmount const& amount,
1953 STAmount const& amount2,
1954 STAmount const& lptAMMBalance,
1955 ValidAMM::ZeroAllowed zeroAllowed)
1956{
1957 bool const positive = amount > beast::zero && amount2 > beast::zero &&
1958 lptAMMBalance > beast::zero;
1959 if (zeroAllowed == ValidAMM::ZeroAllowed::Yes)
1960 return positive ||
1961 (amount == beast::zero && amount2 == beast::zero &&
1962 lptAMMBalance == beast::zero);
1963 return positive;
1964}
1965
1966bool
1967ValidAMM::finalizeVote(bool enforce, beast::Journal const& j) const
1968{
1970 {
1971 // LPTokens and the pool can not change on vote
1972 // LCOV_EXCL_START
1973 JLOG(j.error()) << "AMMVote invariant failed: "
1976 << ammPoolChanged_;
1977 if (enforce)
1978 return false;
1979 // LCOV_EXCL_STOP
1980 }
1981
1982 return true;
1983}
1984
1985bool
1986ValidAMM::finalizeBid(bool enforce, beast::Journal const& j) const
1987{
1988 if (ammPoolChanged_)
1989 {
1990 // The pool can not change on bid
1991 // LCOV_EXCL_START
1992 JLOG(j.error()) << "AMMBid invariant failed: pool changed";
1993 if (enforce)
1994 return false;
1995 // LCOV_EXCL_STOP
1996 }
1997 // LPTokens are burnt, therefore there should be fewer LPTokens
1998 else if (
2001 *lptAMMBalanceAfter_ <= beast::zero))
2002 {
2003 // LCOV_EXCL_START
2004 JLOG(j.error()) << "AMMBid invariant failed: " << *lptAMMBalanceBefore_
2005 << " " << *lptAMMBalanceAfter_;
2006 if (enforce)
2007 return false;
2008 // LCOV_EXCL_STOP
2009 }
2010
2011 return true;
2012}
2013
2014bool
2016 STTx const& tx,
2017 ReadView const& view,
2018 bool enforce,
2019 beast::Journal const& j) const
2020{
2021 if (!ammAccount_)
2022 {
2023 // LCOV_EXCL_START
2024 JLOG(j.error())
2025 << "AMMCreate invariant failed: AMM object is not created";
2026 if (enforce)
2027 return false;
2028 // LCOV_EXCL_STOP
2029 }
2030 else
2031 {
2032 auto const [amount, amount2] = ammPoolHolds(
2033 view,
2034 *ammAccount_,
2035 tx[sfAmount].get<Issue>(),
2036 tx[sfAmount2].get<Issue>(),
2038 j);
2039 // Create invariant:
2040 // sqrt(amount * amount2) == LPTokens
2041 // all balances are greater than zero
2042 if (!validBalances(
2043 amount, amount2, *lptAMMBalanceAfter_, ZeroAllowed::No) ||
2044 ammLPTokens(amount, amount2, lptAMMBalanceAfter_->issue()) !=
2046 {
2047 JLOG(j.error()) << "AMMCreate invariant failed: " << amount << " "
2048 << amount2 << " " << *lptAMMBalanceAfter_;
2049 if (enforce)
2050 return false;
2051 }
2052 }
2053
2054 return true;
2055}
2056
2057bool
2058ValidAMM::finalizeDelete(bool enforce, TER res, beast::Journal const& j) const
2059{
2060 if (ammAccount_)
2061 {
2062 // LCOV_EXCL_START
2063 std::string const msg = (res == tesSUCCESS)
2064 ? "AMM object is not deleted on tesSUCCESS"
2065 : "AMM object is changed on tecINCOMPLETE";
2066 JLOG(j.error()) << "AMMDelete invariant failed: " << msg;
2067 if (enforce)
2068 return false;
2069 // LCOV_EXCL_STOP
2070 }
2071
2072 return true;
2073}
2074
2075bool
2076ValidAMM::finalizeDEX(bool enforce, beast::Journal const& j) const
2077{
2078 if (ammAccount_)
2079 {
2080 // LCOV_EXCL_START
2081 JLOG(j.error()) << "AMM swap invariant failed: AMM object changed";
2082 if (enforce)
2083 return false;
2084 // LCOV_EXCL_STOP
2085 }
2086
2087 return true;
2088}
2089
2090bool
2092 xrpl::STTx const& tx,
2093 xrpl::ReadView const& view,
2094 ZeroAllowed zeroAllowed,
2095 beast::Journal const& j) const
2096{
2097 auto const [amount, amount2] = ammPoolHolds(
2098 view,
2099 *ammAccount_,
2100 tx[sfAsset].get<Issue>(),
2101 tx[sfAsset2].get<Issue>(),
2103 j);
2104 // Deposit and Withdrawal invariant:
2105 // sqrt(amount * amount2) >= LPTokens
2106 // all balances are greater than zero
2107 // unless on last withdrawal
2108 auto const poolProductMean = root2(amount * amount2);
2109 bool const nonNegativeBalances =
2110 validBalances(amount, amount2, *lptAMMBalanceAfter_, zeroAllowed);
2111 bool const strongInvariantCheck = poolProductMean >= *lptAMMBalanceAfter_;
2112 // Allow for a small relative error if strongInvariantCheck fails
2113 auto weakInvariantCheck = [&]() {
2114 return *lptAMMBalanceAfter_ != beast::zero &&
2116 poolProductMean, Number{*lptAMMBalanceAfter_}, Number{1, -11});
2117 };
2118 if (!nonNegativeBalances ||
2119 (!strongInvariantCheck && !weakInvariantCheck()))
2120 {
2121 JLOG(j.error()) << "AMM " << tx.getTxnType() << " invariant failed: "
2123 << ammPoolChanged_ << " " << amount << " " << amount2
2124 << " " << poolProductMean << " "
2125 << lptAMMBalanceAfter_->getText() << " "
2126 << ((*lptAMMBalanceAfter_ == beast::zero)
2127 ? Number{1}
2128 : ((*lptAMMBalanceAfter_ - poolProductMean) /
2129 poolProductMean));
2130 return false;
2131 }
2132
2133 return true;
2134}
2135
2136bool
2138 xrpl::STTx const& tx,
2139 xrpl::ReadView const& view,
2140 bool enforce,
2141 beast::Journal const& j) const
2142{
2143 if (!ammAccount_)
2144 {
2145 // LCOV_EXCL_START
2146 JLOG(j.error()) << "AMMDeposit invariant failed: AMM object is deleted";
2147 if (enforce)
2148 return false;
2149 // LCOV_EXCL_STOP
2150 }
2151 else if (!generalInvariant(tx, view, ZeroAllowed::No, j) && enforce)
2152 return false;
2153
2154 return true;
2155}
2156
2157bool
2159 xrpl::STTx const& tx,
2160 xrpl::ReadView const& view,
2161 bool enforce,
2162 beast::Journal const& j) const
2163{
2164 if (!ammAccount_)
2165 {
2166 // Last Withdraw or Clawback deleted AMM
2167 }
2168 else if (!generalInvariant(tx, view, ZeroAllowed::Yes, j))
2169 {
2170 if (enforce)
2171 return false;
2172 }
2173
2174 return true;
2175}
2176
2177bool
2179 STTx const& tx,
2180 TER const result,
2181 XRPAmount const,
2182 ReadView const& view,
2183 beast::Journal const& j)
2184{
2185 // Delete may return tecINCOMPLETE if there are too many
2186 // trustlines to delete.
2187 if (result != tesSUCCESS && result != tecINCOMPLETE)
2188 return true;
2189
2190 bool const enforce = view.rules().enabled(fixAMMv1_3);
2191
2192 switch (tx.getTxnType())
2193 {
2194 case ttAMM_CREATE:
2195 return finalizeCreate(tx, view, enforce, j);
2196 case ttAMM_DEPOSIT:
2197 return finalizeDeposit(tx, view, enforce, j);
2198 case ttAMM_CLAWBACK:
2199 case ttAMM_WITHDRAW:
2200 return finalizeWithdraw(tx, view, enforce, j);
2201 case ttAMM_BID:
2202 return finalizeBid(enforce, j);
2203 case ttAMM_VOTE:
2204 return finalizeVote(enforce, j);
2205 case ttAMM_DELETE:
2206 return finalizeDelete(enforce, result, j);
2207 case ttCHECK_CASH:
2208 case ttOFFER_CREATE:
2209 case ttPAYMENT:
2210 return finalizeDEX(enforce, j);
2211 default:
2212 break;
2213 }
2214
2215 return true;
2216}
2217
2218//------------------------------------------------------------------------------
2219
2220void
2222 bool isDelete,
2223 std::shared_ptr<SLE const> const& before,
2225{
2226 if (isDelete || !before)
2227 // Creation and deletion are ignored
2228 return;
2229
2230 changedEntries_.emplace(before, after);
2231}
2232
2233bool
2235 STTx const& tx,
2236 TER const,
2237 XRPAmount const,
2238 ReadView const& view,
2239 beast::Journal const& j)
2240{
2241 static auto const fieldChanged =
2242 [](auto const& before, auto const& after, auto const& field) {
2243 bool const beforeField = before->isFieldPresent(field);
2244 bool const afterField = after->isFieldPresent(field);
2245 return beforeField != afterField ||
2246 (afterField && before->at(field) != after->at(field));
2247 };
2248 for (auto const& slePair : changedEntries_)
2249 {
2250 auto const& before = slePair.first;
2251 auto const& after = slePair.second;
2252 auto const type = after->getType();
2253 bool bad = false;
2254 [[maybe_unused]] bool enforce = false;
2255 switch (type)
2256 {
2257 case ltLOAN_BROKER:
2258 /*
2259 * We check this invariant regardless of lending protocol
2260 * amendment status, allowing for detection and logging of
2261 * potential issues even when the amendment is disabled.
2262 */
2263 enforce = view.rules().enabled(featureLendingProtocol);
2264 bad = fieldChanged(before, after, sfLedgerEntryType) ||
2265 fieldChanged(before, after, sfLedgerIndex) ||
2266 fieldChanged(before, after, sfSequence) ||
2267 fieldChanged(before, after, sfOwnerNode) ||
2268 fieldChanged(before, after, sfVaultNode) ||
2269 fieldChanged(before, after, sfVaultID) ||
2270 fieldChanged(before, after, sfAccount) ||
2271 fieldChanged(before, after, sfOwner) ||
2272 fieldChanged(before, after, sfManagementFeeRate) ||
2273 fieldChanged(before, after, sfCoverRateMinimum) ||
2274 fieldChanged(before, after, sfCoverRateLiquidation);
2275 break;
2276 case ltLOAN:
2277 /*
2278 * We check this invariant regardless of lending protocol
2279 * amendment status, allowing for detection and logging of
2280 * potential issues even when the amendment is disabled.
2281 */
2282 enforce = view.rules().enabled(featureLendingProtocol);
2283 bad = fieldChanged(before, after, sfLedgerEntryType) ||
2284 fieldChanged(before, after, sfLedgerIndex) ||
2285 fieldChanged(before, after, sfSequence) ||
2286 fieldChanged(before, after, sfOwnerNode) ||
2287 fieldChanged(before, after, sfLoanBrokerNode) ||
2288 fieldChanged(before, after, sfLoanBrokerID) ||
2289 fieldChanged(before, after, sfBorrower) ||
2290 fieldChanged(before, after, sfLoanOriginationFee) ||
2291 fieldChanged(before, after, sfLoanServiceFee) ||
2292 fieldChanged(before, after, sfLatePaymentFee) ||
2293 fieldChanged(before, after, sfClosePaymentFee) ||
2294 fieldChanged(before, after, sfOverpaymentFee) ||
2295 fieldChanged(before, after, sfInterestRate) ||
2296 fieldChanged(before, after, sfLateInterestRate) ||
2297 fieldChanged(before, after, sfCloseInterestRate) ||
2298 fieldChanged(before, after, sfOverpaymentInterestRate) ||
2299 fieldChanged(before, after, sfStartDate) ||
2300 fieldChanged(before, after, sfPaymentInterval) ||
2301 fieldChanged(before, after, sfGracePeriod) ||
2302 fieldChanged(before, after, sfLoanScale);
2303 break;
2304 default:
2305 /*
2306 * We check this invariant regardless of lending protocol
2307 * amendment status, allowing for detection and logging of
2308 * potential issues even when the amendment is disabled.
2309 *
2310 * We use the lending protocol as a gate, even though
2311 * all transactions are affected because that's when it
2312 * was added.
2313 */
2314 enforce = view.rules().enabled(featureLendingProtocol);
2315 bad = fieldChanged(before, after, sfLedgerEntryType) ||
2316 fieldChanged(before, after, sfLedgerIndex);
2317 }
2318 XRPL_ASSERT(
2319 !bad || enforce,
2320 "xrpl::NoModifiedUnmodifiableFields::finalize : no bad "
2321 "changes or enforce invariant");
2322 if (bad)
2323 {
2324 JLOG(j.fatal())
2325 << "Invariant failed: changed an unchangeable field for "
2326 << tx.getTransactionID();
2327 if (enforce)
2328 return false;
2329 }
2330 }
2331 return true;
2332}
2333
2334//------------------------------------------------------------------------------
2335
2336void
2338 bool isDelete,
2339 std::shared_ptr<SLE const> const& before,
2341{
2342 if (after)
2343 {
2344 if (after->getType() == ltLOAN_BROKER)
2345 {
2346 auto& broker = brokers_[after->key()];
2347 broker.brokerBefore = before;
2348 broker.brokerAfter = after;
2349 }
2350 else if (
2351 after->getType() == ltACCOUNT_ROOT &&
2352 after->isFieldPresent(sfLoanBrokerID))
2353 {
2354 auto const& loanBrokerID = after->at(sfLoanBrokerID);
2355 // create an entry if one doesn't already exist
2356 brokers_.emplace(loanBrokerID, BrokerInfo{});
2357 }
2358 else if (after->getType() == ltRIPPLE_STATE)
2359 {
2360 lines_.emplace_back(after);
2361 }
2362 else if (after->getType() == ltMPTOKEN)
2363 {
2364 mpts_.emplace_back(after);
2365 }
2366 }
2367}
2368
2369bool
2371 ReadView const& view,
2372 SLE::const_ref dir,
2373 beast::Journal const& j) const
2374{
2375 auto const next = dir->at(~sfIndexNext);
2376 auto const prev = dir->at(~sfIndexPrevious);
2377 if ((prev && *prev) || (next && *next))
2378 {
2379 JLOG(j.fatal()) << "Invariant failed: Loan Broker with zero "
2380 "OwnerCount has multiple directory pages";
2381 return false;
2382 }
2383 auto indexes = dir->getFieldV256(sfIndexes);
2384 if (indexes.size() > 1)
2385 {
2386 JLOG(j.fatal())
2387 << "Invariant failed: Loan Broker with zero "
2388 "OwnerCount has multiple indexes in the Directory root";
2389 return false;
2390 }
2391 if (indexes.size() == 1)
2392 {
2393 auto const index = indexes.value().front();
2394 auto const sle = view.read(keylet::unchecked(index));
2395 if (!sle)
2396 {
2397 JLOG(j.fatal())
2398 << "Invariant failed: Loan Broker directory corrupt";
2399 return false;
2400 }
2401 if (sle->getType() != ltRIPPLE_STATE && sle->getType() != ltMPTOKEN)
2402 {
2403 JLOG(j.fatal())
2404 << "Invariant failed: Loan Broker with zero "
2405 "OwnerCount has an unexpected entry in the directory";
2406 return false;
2407 }
2408 }
2409
2410 return true;
2411}
2412
2413bool
2415 STTx const& tx,
2416 TER const,
2417 XRPAmount const,
2418 ReadView const& view,
2419 beast::Journal const& j)
2420{
2421 // Loan Brokers will not exist on ledger if the Lending Protocol amendment
2422 // is not enabled, so there's no need to check it.
2423
2424 for (auto const& line : lines_)
2425 {
2426 for (auto const& field : {&sfLowLimit, &sfHighLimit})
2427 {
2428 auto const account =
2429 view.read(keylet::account(line->at(*field).getIssuer()));
2430 // This Invariant doesn't know about the rules for Trust Lines, so
2431 // if the account is missing, don't treat it as an error. This
2432 // loop is only concerned with finding Broker pseudo-accounts
2433 if (account && account->isFieldPresent(sfLoanBrokerID))
2434 {
2435 auto const& loanBrokerID = account->at(sfLoanBrokerID);
2436 // create an entry if one doesn't already exist
2437 brokers_.emplace(loanBrokerID, BrokerInfo{});
2438 }
2439 }
2440 }
2441 for (auto const& mpt : mpts_)
2442 {
2443 auto const account = view.read(keylet::account(mpt->at(sfAccount)));
2444 // This Invariant doesn't know about the rules for MPTokens, so
2445 // if the account is missing, don't treat is as an error. This
2446 // loop is only concerned with finding Broker pseudo-accounts
2447 if (account && account->isFieldPresent(sfLoanBrokerID))
2448 {
2449 auto const& loanBrokerID = account->at(sfLoanBrokerID);
2450 // create an entry if one doesn't already exist
2451 brokers_.emplace(loanBrokerID, BrokerInfo{});
2452 }
2453 }
2454
2455 for (auto const& [brokerID, broker] : brokers_)
2456 {
2457 auto const& after = broker.brokerAfter
2458 ? broker.brokerAfter
2459 : view.read(keylet::loanbroker(brokerID));
2460
2461 if (!after)
2462 {
2463 JLOG(j.fatal()) << "Invariant failed: Loan Broker missing";
2464 return false;
2465 }
2466
2467 auto const& before = broker.brokerBefore;
2468
2469 // https://github.com/Tapanito/XRPL-Standards/blob/xls-66-lending-protocol/XLS-0066d-lending-protocol/README.md#3123-invariants
2470 // If `LoanBroker.OwnerCount = 0` the `DirectoryNode` will have at most
2471 // one node (the root), which will only hold entries for `RippleState`
2472 // or `MPToken` objects.
2473 if (after->at(sfOwnerCount) == 0)
2474 {
2475 auto const dir = view.read(keylet::ownerDir(after->at(sfAccount)));
2476 if (dir)
2477 {
2478 if (!goodZeroDirectory(view, dir, j))
2479 {
2480 return false;
2481 }
2482 }
2483 }
2484 if (before && before->at(sfLoanSequence) > after->at(sfLoanSequence))
2485 {
2486 JLOG(j.fatal()) << "Invariant failed: Loan Broker sequence number "
2487 "decreased";
2488 return false;
2489 }
2490 if (after->at(sfDebtTotal) < 0)
2491 {
2492 JLOG(j.fatal())
2493 << "Invariant failed: Loan Broker debt total is negative";
2494 return false;
2495 }
2496 if (after->at(sfCoverAvailable) < 0)
2497 {
2498 JLOG(j.fatal())
2499 << "Invariant failed: Loan Broker cover available is negative";
2500 return false;
2501 }
2502 auto const vault = view.read(keylet::vault(after->at(sfVaultID)));
2503 if (!vault)
2504 {
2505 JLOG(j.fatal())
2506 << "Invariant failed: Loan Broker vault ID is invalid";
2507 return false;
2508 }
2509 auto const& vaultAsset = vault->at(sfAsset);
2510 if (after->at(sfCoverAvailable) < accountHolds(
2511 view,
2512 after->at(sfAccount),
2513 vaultAsset,
2516 j))
2517 {
2518 JLOG(j.fatal()) << "Invariant failed: Loan Broker cover available "
2519 "is less than pseudo-account asset balance";
2520 return false;
2521 }
2522 }
2523 return true;
2524}
2525
2526//------------------------------------------------------------------------------
2527
2528void
2530 bool isDelete,
2531 std::shared_ptr<SLE const> const& before,
2533{
2534 if (after && after->getType() == ltLOAN)
2535 {
2536 loans_.emplace_back(before, after);
2537 }
2538}
2539
2540bool
2542 STTx const& tx,
2543 TER const,
2544 XRPAmount const,
2545 ReadView const& view,
2546 beast::Journal const& j)
2547{
2548 // Loans will not exist on ledger if the Lending Protocol amendment
2549 // is not enabled, so there's no need to check it.
2550
2551 for (auto const& [before, after] : loans_)
2552 {
2553 // https://github.com/Tapanito/XRPL-Standards/blob/xls-66-lending-protocol/XLS-0066d-lending-protocol/README.md#3223-invariants
2554 // If `Loan.PaymentRemaining = 0` then the loan MUST be fully paid off
2555 if (after->at(sfPaymentRemaining) == 0 &&
2556 (after->at(sfTotalValueOutstanding) != beast::zero ||
2557 after->at(sfPrincipalOutstanding) != beast::zero ||
2558 after->at(sfManagementFeeOutstanding) != beast::zero))
2559 {
2560 JLOG(j.fatal()) << "Invariant failed: Loan with zero payments "
2561 "remaining has not been paid off";
2562 return false;
2563 }
2564 // If `Loan.PaymentRemaining != 0` then the loan MUST NOT be fully paid
2565 // off
2566 if (after->at(sfPaymentRemaining) != 0 &&
2567 after->at(sfTotalValueOutstanding) == beast::zero &&
2568 after->at(sfPrincipalOutstanding) == beast::zero &&
2569 after->at(sfManagementFeeOutstanding) == beast::zero)
2570 {
2571 JLOG(j.fatal()) << "Invariant failed: Loan with zero payments "
2572 "remaining has not been paid off";
2573 return false;
2574 }
2575 if (before &&
2576 (before->isFlag(lsfLoanOverpayment) !=
2577 after->isFlag(lsfLoanOverpayment)))
2578 {
2579 JLOG(j.fatal())
2580 << "Invariant failed: Loan Overpayment flag changed";
2581 return false;
2582 }
2583 // Must not be negative - STNumber
2584 for (auto const field :
2585 {&sfLoanServiceFee,
2586 &sfLatePaymentFee,
2587 &sfClosePaymentFee,
2588 &sfPrincipalOutstanding,
2589 &sfTotalValueOutstanding,
2590 &sfManagementFeeOutstanding})
2591 {
2592 if (after->at(*field) < 0)
2593 {
2594 JLOG(j.fatal()) << "Invariant failed: " << field->getName()
2595 << " is negative ";
2596 return false;
2597 }
2598 }
2599 // Must be positive - STNumber
2600 for (auto const field : {
2601 &sfPeriodicPayment,
2602 })
2603 {
2604 if (after->at(*field) <= 0)
2605 {
2606 JLOG(j.fatal()) << "Invariant failed: " << field->getName()
2607 << " is zero or negative ";
2608 return false;
2609 }
2610 }
2611 }
2612 return true;
2613}
2614
2617{
2618 XRPL_ASSERT(
2619 from.getType() == ltVAULT,
2620 "ValidVault::Vault::make : from Vault object");
2621
2622 ValidVault::Vault self;
2623 self.key = from.key();
2624 self.asset = from.at(sfAsset);
2625 self.pseudoId = from.getAccountID(sfAccount);
2626 self.owner = from.at(sfOwner);
2627 self.shareMPTID = from.getFieldH192(sfShareMPTID);
2628 self.assetsTotal = from.at(sfAssetsTotal);
2629 self.assetsAvailable = from.at(sfAssetsAvailable);
2630 self.assetsMaximum = from.at(sfAssetsMaximum);
2631 self.lossUnrealized = from.at(sfLossUnrealized);
2632 return self;
2633}
2634
2637{
2638 XRPL_ASSERT(
2639 from.getType() == ltMPTOKEN_ISSUANCE,
2640 "ValidVault::Shares::make : from MPTokenIssuance object");
2641
2642 ValidVault::Shares self;
2643 self.share = MPTIssue(
2644 makeMptID(from.getFieldU32(sfSequence), from.getAccountID(sfIssuer)));
2645 self.sharesTotal = from.at(sfOutstandingAmount);
2646 self.sharesMaximum = from[~sfMaximumAmount].value_or(maxMPTokenAmount);
2647 return self;
2648}
2649
2650void
2652 bool isDelete,
2653 std::shared_ptr<SLE const> const& before,
2655{
2656 // If `before` is empty, this means an object is being created, in which
2657 // case `isDelete` must be false. Otherwise `before` and `after` are set and
2658 // `isDelete` indicates whether an object is being deleted or modified.
2659 XRPL_ASSERT(
2660 after != nullptr && (before != nullptr || !isDelete),
2661 "xrpl::ValidVault::visitEntry : some object is available");
2662
2663 // Number balanceDelta will capture the difference (delta) between "before"
2664 // state (zero if created) and "after" state (zero if destroyed), so the
2665 // invariants can validate that the change in account balances matches the
2666 // change in vault balances, stored to deltas_ at the end of this function.
2667 Number balanceDelta{};
2668
2669 std::int8_t sign = 0;
2670 if (before)
2671 {
2672 switch (before->getType())
2673 {
2674 case ltVAULT:
2675 beforeVault_.push_back(Vault::make(*before));
2676 break;
2677 case ltMPTOKEN_ISSUANCE:
2678 // At this moment we have no way of telling if this object holds
2679 // vault shares or something else. Save it for finalize.
2680 beforeMPTs_.push_back(Shares::make(*before));
2681 balanceDelta = static_cast<std::int64_t>(
2682 before->getFieldU64(sfOutstandingAmount));
2683 sign = 1;
2684 break;
2685 case ltMPTOKEN:
2686 balanceDelta =
2687 static_cast<std::int64_t>(before->getFieldU64(sfMPTAmount));
2688 sign = -1;
2689 break;
2690 case ltACCOUNT_ROOT:
2691 case ltRIPPLE_STATE:
2692 balanceDelta = before->getFieldAmount(sfBalance);
2693 sign = -1;
2694 break;
2695 default:;
2696 }
2697 }
2698
2699 if (!isDelete && after)
2700 {
2701 switch (after->getType())
2702 {
2703 case ltVAULT:
2704 afterVault_.push_back(Vault::make(*after));
2705 break;
2706 case ltMPTOKEN_ISSUANCE:
2707 // At this moment we have no way of telling if this object holds
2708 // vault shares or something else. Save it for finalize.
2709 afterMPTs_.push_back(Shares::make(*after));
2710 balanceDelta -= Number(static_cast<std::int64_t>(
2711 after->getFieldU64(sfOutstandingAmount)));
2712 sign = 1;
2713 break;
2714 case ltMPTOKEN:
2715 balanceDelta -= Number(
2716 static_cast<std::int64_t>(after->getFieldU64(sfMPTAmount)));
2717 sign = -1;
2718 break;
2719 case ltACCOUNT_ROOT:
2720 case ltRIPPLE_STATE:
2721 balanceDelta -= Number(after->getFieldAmount(sfBalance));
2722 sign = -1;
2723 break;
2724 default:;
2725 }
2726 }
2727
2728 uint256 const key = (before ? before->key() : after->key());
2729 // Append to deltas if sign is non-zero, i.e. an object of an interesting
2730 // type has been updated. A transaction may update an object even when
2731 // its balance has not changed, e.g. transaction fee equals the amount
2732 // transferred to the account. We intentionally do not compare balanceDelta
2733 // against zero, to avoid missing such updates.
2734 if (sign != 0)
2735 deltas_[key] = balanceDelta * sign;
2736}
2737
2738bool
2740 STTx const& tx,
2741 TER const ret,
2742 XRPAmount const fee,
2743 ReadView const& view,
2744 beast::Journal const& j)
2745{
2746 bool const enforce = view.rules().enabled(featureSingleAssetVault);
2747
2748 if (!isTesSuccess(ret))
2749 return true; // Do not perform checks
2750
2751 if (afterVault_.empty() && beforeVault_.empty())
2752 {
2754 {
2755 JLOG(j.fatal()) << //
2756 "Invariant failed: vault operation succeeded without modifying "
2757 "a vault";
2758 XRPL_ASSERT(
2759 enforce, "xrpl::ValidVault::finalize : vault noop invariant");
2760 return !enforce;
2761 }
2762
2763 return true; // Not a vault operation
2764 }
2765 else if (!(hasPrivilege(tx, mustModifyVault) ||
2767 {
2768 JLOG(j.fatal()) << //
2769 "Invariant failed: vault updated by a wrong transaction type";
2770 XRPL_ASSERT(
2771 enforce,
2772 "xrpl::ValidVault::finalize : illegal vault transaction "
2773 "invariant");
2774 return !enforce; // Also not a vault operation
2775 }
2776
2777 if (beforeVault_.size() > 1 || afterVault_.size() > 1)
2778 {
2779 JLOG(j.fatal()) << //
2780 "Invariant failed: vault operation updated more than single vault";
2781 XRPL_ASSERT(
2782 enforce, "xrpl::ValidVault::finalize : single vault invariant");
2783 return !enforce; // That's all we can do here
2784 }
2785
2786 auto const txnType = tx.getTxnType();
2787
2788 // We do special handling for ttVAULT_DELETE first, because it's the only
2789 // vault-modifying transaction without an "after" state of the vault
2790 if (afterVault_.empty())
2791 {
2792 if (txnType != ttVAULT_DELETE)
2793 {
2794 JLOG(j.fatal()) << //
2795 "Invariant failed: vault deleted by a wrong transaction type";
2796 XRPL_ASSERT(
2797 enforce,
2798 "xrpl::ValidVault::finalize : illegal vault deletion "
2799 "invariant");
2800 return !enforce; // That's all we can do here
2801 }
2802
2803 // Note, if afterVault_ is empty then we know that beforeVault_ is not
2804 // empty, as enforced at the top of this function
2805 auto const& beforeVault = beforeVault_[0];
2806
2807 // At this moment we only know a vault is being deleted and there
2808 // might be some MPTokenIssuance objects which are deleted in the
2809 // same transaction. Find the one matching this vault.
2810 auto const deletedShares = [&]() -> std::optional<Shares> {
2811 for (auto const& e : beforeMPTs_)
2812 {
2813 if (e.share.getMptID() == beforeVault.shareMPTID)
2814 return std::move(e);
2815 }
2816 return std::nullopt;
2817 }();
2818
2819 if (!deletedShares)
2820 {
2821 JLOG(j.fatal()) << "Invariant failed: deleted vault must also "
2822 "delete shares";
2823 XRPL_ASSERT(
2824 enforce,
2825 "xrpl::ValidVault::finalize : shares deletion invariant");
2826 return !enforce; // That's all we can do here
2827 }
2828
2829 bool result = true;
2830 if (deletedShares->sharesTotal != 0)
2831 {
2832 JLOG(j.fatal()) << "Invariant failed: deleted vault must have no "
2833 "shares outstanding";
2834 result = false;
2835 }
2836 if (beforeVault.assetsTotal != zero)
2837 {
2838 JLOG(j.fatal()) << "Invariant failed: deleted vault must have no "
2839 "assets outstanding";
2840 result = false;
2841 }
2842 if (beforeVault.assetsAvailable != zero)
2843 {
2844 JLOG(j.fatal()) << "Invariant failed: deleted vault must have no "
2845 "assets available";
2846 result = false;
2847 }
2848
2849 return result;
2850 }
2851 else if (txnType == ttVAULT_DELETE)
2852 {
2853 JLOG(j.fatal()) << "Invariant failed: vault deletion succeeded without "
2854 "deleting a vault";
2855 XRPL_ASSERT(
2856 enforce, "xrpl::ValidVault::finalize : vault deletion invariant");
2857 return !enforce; // That's all we can do here
2858 }
2859
2860 // Note, `afterVault_.empty()` is handled above
2861 auto const& afterVault = afterVault_[0];
2862 XRPL_ASSERT(
2863 beforeVault_.empty() || beforeVault_[0].key == afterVault.key,
2864 "xrpl::ValidVault::finalize : single vault operation");
2865
2866 auto const updatedShares = [&]() -> std::optional<Shares> {
2867 // At this moment we only know that a vault is being updated and there
2868 // might be some MPTokenIssuance objects which are also updated in the
2869 // same transaction. Find the one matching the shares to this vault.
2870 // Note, we expect updatedMPTs collection to be extremely small. For
2871 // such collections linear search is faster than lookup.
2872 for (auto const& e : afterMPTs_)
2873 {
2874 if (e.share.getMptID() == afterVault.shareMPTID)
2875 return e;
2876 }
2877
2878 auto const sleShares =
2879 view.read(keylet::mptIssuance(afterVault.shareMPTID));
2880
2881 return sleShares ? std::optional<Shares>(Shares::make(*sleShares))
2882 : std::nullopt;
2883 }();
2884
2885 bool result = true;
2886
2887 // Universal transaction checks
2888 if (!beforeVault_.empty())
2889 {
2890 auto const& beforeVault = beforeVault_[0];
2891 if (afterVault.asset != beforeVault.asset ||
2892 afterVault.pseudoId != beforeVault.pseudoId ||
2893 afterVault.shareMPTID != beforeVault.shareMPTID)
2894 {
2895 JLOG(j.fatal())
2896 << "Invariant failed: violation of vault immutable data";
2897 result = false;
2898 }
2899 }
2900
2901 if (!updatedShares)
2902 {
2903 JLOG(j.fatal()) << "Invariant failed: updated vault must have shares";
2904 XRPL_ASSERT(
2905 enforce, "xrpl::ValidVault::finalize : vault has shares invariant");
2906 return !enforce; // That's all we can do here
2907 }
2908
2909 if (updatedShares->sharesTotal == 0)
2910 {
2911 if (afterVault.assetsTotal != zero)
2912 {
2913 JLOG(j.fatal()) << "Invariant failed: updated zero sized "
2914 "vault must have no assets outstanding";
2915 result = false;
2916 }
2917 if (afterVault.assetsAvailable != zero)
2918 {
2919 JLOG(j.fatal()) << "Invariant failed: updated zero sized "
2920 "vault must have no assets available";
2921 result = false;
2922 }
2923 }
2924 else if (updatedShares->sharesTotal > updatedShares->sharesMaximum)
2925 {
2926 JLOG(j.fatal()) //
2927 << "Invariant failed: updated shares must not exceed maximum "
2928 << updatedShares->sharesMaximum;
2929 result = false;
2930 }
2931
2932 if (afterVault.assetsAvailable < zero)
2933 {
2934 JLOG(j.fatal())
2935 << "Invariant failed: assets available must be positive";
2936 result = false;
2937 }
2938
2939 if (afterVault.assetsAvailable > afterVault.assetsTotal)
2940 {
2941 JLOG(j.fatal()) << "Invariant failed: assets available must "
2942 "not be greater than assets outstanding";
2943 result = false;
2944 }
2945 else if (
2946 afterVault.lossUnrealized >
2947 afterVault.assetsTotal - afterVault.assetsAvailable)
2948 {
2949 JLOG(j.fatal()) //
2950 << "Invariant failed: loss unrealized must not exceed "
2951 "the difference between assets outstanding and available";
2952 result = false;
2953 }
2954
2955 if (afterVault.assetsTotal < zero)
2956 {
2957 JLOG(j.fatal())
2958 << "Invariant failed: assets outstanding must be positive";
2959 result = false;
2960 }
2961
2962 if (afterVault.assetsMaximum < zero)
2963 {
2964 JLOG(j.fatal()) << "Invariant failed: assets maximum must be positive";
2965 result = false;
2966 }
2967
2968 // Thanks to this check we can simply do `assert(!beforeVault_.empty()` when
2969 // enforcing invariants on transaction types other than ttVAULT_CREATE
2970 if (beforeVault_.empty() && txnType != ttVAULT_CREATE)
2971 {
2972 JLOG(j.fatal()) << //
2973 "Invariant failed: vault created by a wrong transaction type";
2974 XRPL_ASSERT(
2975 enforce, "xrpl::ValidVault::finalize : vault creation invariant");
2976 return !enforce; // That's all we can do here
2977 }
2978
2979 if (!beforeVault_.empty() &&
2980 afterVault.lossUnrealized != beforeVault_[0].lossUnrealized &&
2981 txnType != ttLOAN_MANAGE && txnType != ttLOAN_PAY)
2982 {
2983 JLOG(j.fatal()) << //
2984 "Invariant failed: vault transaction must not change loss "
2985 "unrealized";
2986 result = false;
2987 }
2988
2989 auto const beforeShares = [&]() -> std::optional<Shares> {
2990 if (beforeVault_.empty())
2991 return std::nullopt;
2992 auto const& beforeVault = beforeVault_[0];
2993
2994 for (auto const& e : beforeMPTs_)
2995 {
2996 if (e.share.getMptID() == beforeVault.shareMPTID)
2997 return std::move(e);
2998 }
2999 return std::nullopt;
3000 }();
3001
3002 if (!beforeShares &&
3003 (tx.getTxnType() == ttVAULT_DEPOSIT || //
3004 tx.getTxnType() == ttVAULT_WITHDRAW || //
3005 tx.getTxnType() == ttVAULT_CLAWBACK))
3006 {
3007 JLOG(j.fatal()) << "Invariant failed: vault operation succeeded "
3008 "without updating shares";
3009 XRPL_ASSERT(
3010 enforce, "xrpl::ValidVault::finalize : shares noop invariant");
3011 return !enforce; // That's all we can do here
3012 }
3013
3014 auto const& vaultAsset = afterVault.asset;
3015 auto const deltaAssets = [&](AccountID const& id) -> std::optional<Number> {
3016 auto const get = //
3017 [&](auto const& it, std::int8_t sign = 1) -> std::optional<Number> {
3018 if (it == deltas_.end())
3019 return std::nullopt;
3020
3021 return it->second * sign;
3022 };
3023
3024 return std::visit(
3025 [&]<typename TIss>(TIss const& issue) {
3026 if constexpr (std::is_same_v<TIss, Issue>)
3027 {
3028 if (isXRP(issue))
3029 return get(deltas_.find(keylet::account(id).key));
3030 return get(
3031 deltas_.find(keylet::line(id, issue).key),
3032 id > issue.getIssuer() ? -1 : 1);
3033 }
3034 else if constexpr (std::is_same_v<TIss, MPTIssue>)
3035 {
3036 return get(deltas_.find(
3037 keylet::mptoken(issue.getMptID(), id).key));
3038 }
3039 },
3040 vaultAsset.value());
3041 };
3042 auto const deltaAssetsTxAccount = [&]() -> std::optional<Number> {
3043 auto ret = deltaAssets(tx[sfAccount]);
3044 // Nothing returned or not XRP transaction
3045 if (!ret.has_value() || !vaultAsset.native())
3046 return ret;
3047
3048 // Delegated transaction; no need to compensate for fees
3049 if (auto const delegate = tx[~sfDelegate];
3050 delegate.has_value() && *delegate != tx[sfAccount])
3051 return ret;
3052
3053 *ret += fee.drops();
3054 if (*ret == zero)
3055 return std::nullopt;
3056
3057 return ret;
3058 };
3059 auto const deltaShares = [&](AccountID const& id) -> std::optional<Number> {
3060 auto const it = [&]() {
3061 if (id == afterVault.pseudoId)
3062 return deltas_.find(
3063 keylet::mptIssuance(afterVault.shareMPTID).key);
3064 return deltas_.find(keylet::mptoken(afterVault.shareMPTID, id).key);
3065 }();
3066
3067 return it != deltas_.end() ? std::optional<Number>(it->second)
3068 : std::nullopt;
3069 };
3070
3071 auto const vaultHoldsNoAssets = [&](Vault const& vault) {
3072 return vault.assetsAvailable == 0 && vault.assetsTotal == 0;
3073 };
3074
3075 // Technically this does not need to be a lambda, but it's more
3076 // convenient thanks to early "return false"; the not-so-nice
3077 // alternatives are several layers of nested if/else or more complex
3078 // (i.e. brittle) if statements.
3079 result &= [&]() {
3080 switch (txnType)
3081 {
3082 case ttVAULT_CREATE: {
3083 bool result = true;
3084
3085 if (!beforeVault_.empty())
3086 {
3087 JLOG(j.fatal()) //
3088 << "Invariant failed: create operation must not have "
3089 "updated a vault";
3090 result = false;
3091 }
3092
3093 if (afterVault.assetsAvailable != zero ||
3094 afterVault.assetsTotal != zero ||
3095 afterVault.lossUnrealized != zero ||
3096 updatedShares->sharesTotal != 0)
3097 {
3098 JLOG(j.fatal()) //
3099 << "Invariant failed: created vault must be empty";
3100 result = false;
3101 }
3102
3103 if (afterVault.pseudoId != updatedShares->share.getIssuer())
3104 {
3105 JLOG(j.fatal()) //
3106 << "Invariant failed: shares issuer and vault "
3107 "pseudo-account must be the same";
3108 result = false;
3109 }
3110
3111 auto const sleSharesIssuer = view.read(
3112 keylet::account(updatedShares->share.getIssuer()));
3113 if (!sleSharesIssuer)
3114 {
3115 JLOG(j.fatal()) //
3116 << "Invariant failed: shares issuer must exist";
3117 return false;
3118 }
3119
3120 if (!isPseudoAccount(sleSharesIssuer))
3121 {
3122 JLOG(j.fatal()) //
3123 << "Invariant failed: shares issuer must be a "
3124 "pseudo-account";
3125 result = false;
3126 }
3127
3128 if (auto const vaultId = (*sleSharesIssuer)[~sfVaultID];
3129 !vaultId || *vaultId != afterVault.key)
3130 {
3131 JLOG(j.fatal()) //
3132 << "Invariant failed: shares issuer pseudo-account "
3133 "must point back to the vault";
3134 result = false;
3135 }
3136
3137 return result;
3138 }
3139 case ttVAULT_SET: {
3140 bool result = true;
3141
3142 XRPL_ASSERT(
3143 !beforeVault_.empty(),
3144 "xrpl::ValidVault::finalize : set updated a vault");
3145 auto const& beforeVault = beforeVault_[0];
3146
3147 auto const vaultDeltaAssets = deltaAssets(afterVault.pseudoId);
3148 if (vaultDeltaAssets)
3149 {
3150 JLOG(j.fatal()) << //
3151 "Invariant failed: set must not change vault balance";
3152 result = false;
3153 }
3154
3155 if (beforeVault.assetsTotal != afterVault.assetsTotal)
3156 {
3157 JLOG(j.fatal()) << //
3158 "Invariant failed: set must not change assets "
3159 "outstanding";
3160 result = false;
3161 }
3162
3163 if (afterVault.assetsMaximum > zero &&
3164 afterVault.assetsTotal > afterVault.assetsMaximum)
3165 {
3166 JLOG(j.fatal()) << //
3167 "Invariant failed: set assets outstanding must not "
3168 "exceed assets maximum";
3169 result = false;
3170 }
3171
3172 if (beforeVault.assetsAvailable != afterVault.assetsAvailable)
3173 {
3174 JLOG(j.fatal()) << //
3175 "Invariant failed: set must not change assets "
3176 "available";
3177 result = false;
3178 }
3179
3180 if (beforeShares && updatedShares &&
3181 beforeShares->sharesTotal != updatedShares->sharesTotal)
3182 {
3183 JLOG(j.fatal()) << //
3184 "Invariant failed: set must not change shares "
3185 "outstanding";
3186 result = false;
3187 }
3188
3189 return result;
3190 }
3191 case ttVAULT_DEPOSIT: {
3192 bool result = true;
3193
3194 XRPL_ASSERT(
3195 !beforeVault_.empty(),
3196 "xrpl::ValidVault::finalize : deposit updated a vault");
3197 auto const& beforeVault = beforeVault_[0];
3198
3199 auto const vaultDeltaAssets = deltaAssets(afterVault.pseudoId);
3200
3201 if (!vaultDeltaAssets)
3202 {
3203 JLOG(j.fatal()) << //
3204 "Invariant failed: deposit must change vault balance";
3205 return false; // That's all we can do
3206 }
3207
3208 if (*vaultDeltaAssets > tx[sfAmount])
3209 {
3210 JLOG(j.fatal()) << //
3211 "Invariant failed: deposit must not change vault "
3212 "balance by more than deposited amount";
3213 result = false;
3214 }
3215
3216 if (*vaultDeltaAssets <= zero)
3217 {
3218 JLOG(j.fatal()) << //
3219 "Invariant failed: deposit must increase vault balance";
3220 result = false;
3221 }
3222
3223 // Any payments (including deposits) made by the issuer
3224 // do not change their balance, but create funds instead.
3225 bool const issuerDeposit = [&]() -> bool {
3226 if (vaultAsset.native())
3227 return false;
3228 return tx[sfAccount] == vaultAsset.getIssuer();
3229 }();
3230
3231 if (!issuerDeposit)
3232 {
3233 auto const accountDeltaAssets = deltaAssetsTxAccount();
3234 if (!accountDeltaAssets)
3235 {
3236 JLOG(j.fatal()) << //
3237 "Invariant failed: deposit must change depositor "
3238 "balance";
3239 return false;
3240 }
3241
3242 if (*accountDeltaAssets >= zero)
3243 {
3244 JLOG(j.fatal()) << //
3245 "Invariant failed: deposit must decrease depositor "
3246 "balance";
3247 result = false;
3248 }
3249
3250 if (*accountDeltaAssets * -1 != *vaultDeltaAssets)
3251 {
3252 JLOG(j.fatal()) << //
3253 "Invariant failed: deposit must change vault and "
3254 "depositor balance by equal amount";
3255 result = false;
3256 }
3257 }
3258
3259 if (afterVault.assetsMaximum > zero &&
3260 afterVault.assetsTotal > afterVault.assetsMaximum)
3261 {
3262 JLOG(j.fatal()) << //
3263 "Invariant failed: deposit assets outstanding must not "
3264 "exceed assets maximum";
3265 result = false;
3266 }
3267
3268 auto const accountDeltaShares = deltaShares(tx[sfAccount]);
3269 if (!accountDeltaShares)
3270 {
3271 JLOG(j.fatal()) << //
3272 "Invariant failed: deposit must change depositor "
3273 "shares";
3274 return false; // That's all we can do
3275 }
3276
3277 if (*accountDeltaShares <= zero)
3278 {
3279 JLOG(j.fatal()) << //
3280 "Invariant failed: deposit must increase depositor "
3281 "shares";
3282 result = false;
3283 }
3284
3285 auto const vaultDeltaShares = deltaShares(afterVault.pseudoId);
3286 if (!vaultDeltaShares || *vaultDeltaShares == zero)
3287 {
3288 JLOG(j.fatal()) << //
3289 "Invariant failed: deposit must change vault shares";
3290 return false; // That's all we can do
3291 }
3292
3293 if (*vaultDeltaShares * -1 != *accountDeltaShares)
3294 {
3295 JLOG(j.fatal()) << //
3296 "Invariant failed: deposit must change depositor and "
3297 "vault shares by equal amount";
3298 result = false;
3299 }
3300
3301 if (beforeVault.assetsTotal + *vaultDeltaAssets !=
3302 afterVault.assetsTotal)
3303 {
3304 JLOG(j.fatal()) << "Invariant failed: deposit and assets "
3305 "outstanding must add up";
3306 result = false;
3307 }
3308 if (beforeVault.assetsAvailable + *vaultDeltaAssets !=
3309 afterVault.assetsAvailable)
3310 {
3311 JLOG(j.fatal()) << "Invariant failed: deposit and assets "
3312 "available must add up";
3313 result = false;
3314 }
3315
3316 return result;
3317 }
3318 case ttVAULT_WITHDRAW: {
3319 bool result = true;
3320
3321 XRPL_ASSERT(
3322 !beforeVault_.empty(),
3323 "xrpl::ValidVault::finalize : withdrawal updated a "
3324 "vault");
3325 auto const& beforeVault = beforeVault_[0];
3326
3327 auto const vaultDeltaAssets = deltaAssets(afterVault.pseudoId);
3328
3329 if (!vaultDeltaAssets)
3330 {
3331 JLOG(j.fatal()) << "Invariant failed: withdrawal must "
3332 "change vault balance";
3333 return false; // That's all we can do
3334 }
3335
3336 if (*vaultDeltaAssets >= zero)
3337 {
3338 JLOG(j.fatal()) << "Invariant failed: withdrawal must "
3339 "decrease vault balance";
3340 result = false;
3341 }
3342
3343 // Any payments (including withdrawal) going to the issuer
3344 // do not change their balance, but destroy funds instead.
3345 bool const issuerWithdrawal = [&]() -> bool {
3346 if (vaultAsset.native())
3347 return false;
3348 auto const destination =
3349 tx[~sfDestination].value_or(tx[sfAccount]);
3350 return destination == vaultAsset.getIssuer();
3351 }();
3352
3353 if (!issuerWithdrawal)
3354 {
3355 auto const accountDeltaAssets = deltaAssetsTxAccount();
3356 auto const otherAccountDelta =
3357 [&]() -> std::optional<Number> {
3358 if (auto const destination = tx[~sfDestination];
3359 destination && *destination != tx[sfAccount])
3360 return deltaAssets(*destination);
3361 return std::nullopt;
3362 }();
3363
3364 if (accountDeltaAssets.has_value() ==
3365 otherAccountDelta.has_value())
3366 {
3367 JLOG(j.fatal()) << //
3368 "Invariant failed: withdrawal must change one "
3369 "destination balance";
3370 return false;
3371 }
3372
3373 auto const destinationDelta = //
3374 accountDeltaAssets ? *accountDeltaAssets
3375 : *otherAccountDelta;
3376
3377 if (destinationDelta <= zero)
3378 {
3379 JLOG(j.fatal()) << //
3380 "Invariant failed: withdrawal must increase "
3381 "destination balance";
3382 result = false;
3383 }
3384
3385 if (*vaultDeltaAssets * -1 != destinationDelta)
3386 {
3387 JLOG(j.fatal()) << //
3388 "Invariant failed: withdrawal must change vault "
3389 "and destination balance by equal amount";
3390 result = false;
3391 }
3392 }
3393
3394 auto const accountDeltaShares = deltaShares(tx[sfAccount]);
3395 if (!accountDeltaShares)
3396 {
3397 JLOG(j.fatal()) << //
3398 "Invariant failed: withdrawal must change depositor "
3399 "shares";
3400 return false;
3401 }
3402
3403 if (*accountDeltaShares >= zero)
3404 {
3405 JLOG(j.fatal()) << //
3406 "Invariant failed: withdrawal must decrease depositor "
3407 "shares";
3408 result = false;
3409 }
3410
3411 auto const vaultDeltaShares = deltaShares(afterVault.pseudoId);
3412 if (!vaultDeltaShares || *vaultDeltaShares == zero)
3413 {
3414 JLOG(j.fatal()) << //
3415 "Invariant failed: withdrawal must change vault shares";
3416 return false; // That's all we can do
3417 }
3418
3419 if (*vaultDeltaShares * -1 != *accountDeltaShares)
3420 {
3421 JLOG(j.fatal()) << //
3422 "Invariant failed: withdrawal must change depositor "
3423 "and vault shares by equal amount";
3424 result = false;
3425 }
3426
3427 // Note, vaultBalance is negative (see check above)
3428 if (beforeVault.assetsTotal + *vaultDeltaAssets !=
3429 afterVault.assetsTotal)
3430 {
3431 JLOG(j.fatal()) << "Invariant failed: withdrawal and "
3432 "assets outstanding must add up";
3433 result = false;
3434 }
3435
3436 if (beforeVault.assetsAvailable + *vaultDeltaAssets !=
3437 afterVault.assetsAvailable)
3438 {
3439 JLOG(j.fatal()) << "Invariant failed: withdrawal and "
3440 "assets available must add up";
3441 result = false;
3442 }
3443
3444 return result;
3445 }
3446 case ttVAULT_CLAWBACK: {
3447 bool result = true;
3448
3449 XRPL_ASSERT(
3450 !beforeVault_.empty(),
3451 "xrpl::ValidVault::finalize : clawback updated a vault");
3452 auto const& beforeVault = beforeVault_[0];
3453
3454 if (vaultAsset.native() ||
3455 vaultAsset.getIssuer() != tx[sfAccount])
3456 {
3457 // The owner can use clawback to force-burn shares when the
3458 // vault is empty but there are outstanding shares
3459 if (!(beforeShares && beforeShares->sharesTotal > 0 &&
3460 vaultHoldsNoAssets(beforeVault) &&
3461 beforeVault.owner == tx[sfAccount]))
3462 {
3463 JLOG(j.fatal()) << //
3464 "Invariant failed: clawback may only be performed "
3465 "by the asset issuer, or by the vault owner of an "
3466 "empty vault";
3467 return false; // That's all we can do
3468 }
3469 }
3470
3471 auto const vaultDeltaAssets = deltaAssets(afterVault.pseudoId);
3472 if (vaultDeltaAssets)
3473 {
3474 if (*vaultDeltaAssets >= zero)
3475 {
3476 JLOG(j.fatal()) << //
3477 "Invariant failed: clawback must decrease vault "
3478 "balance";
3479 result = false;
3480 }
3481
3482 if (beforeVault.assetsTotal + *vaultDeltaAssets !=
3483 afterVault.assetsTotal)
3484 {
3485 JLOG(j.fatal()) << //
3486 "Invariant failed: clawback and assets outstanding "
3487 "must add up";
3488 result = false;
3489 }
3490
3491 if (beforeVault.assetsAvailable + *vaultDeltaAssets !=
3492 afterVault.assetsAvailable)
3493 {
3494 JLOG(j.fatal()) << //
3495 "Invariant failed: clawback and assets available "
3496 "must add up";
3497 result = false;
3498 }
3499 }
3500 else if (!vaultHoldsNoAssets(beforeVault))
3501 {
3502 JLOG(j.fatal()) << //
3503 "Invariant failed: clawback must change vault balance";
3504 return false; // That's all we can do
3505 }
3506
3507 auto const accountDeltaShares = deltaShares(tx[sfHolder]);
3508 if (!accountDeltaShares)
3509 {
3510 JLOG(j.fatal()) << //
3511 "Invariant failed: clawback must change holder shares";
3512 return false; // That's all we can do
3513 }
3514
3515 if (*accountDeltaShares >= zero)
3516 {
3517 JLOG(j.fatal()) << //
3518 "Invariant failed: clawback must decrease holder "
3519 "shares";
3520 result = false;
3521 }
3522
3523 auto const vaultDeltaShares = deltaShares(afterVault.pseudoId);
3524 if (!vaultDeltaShares || *vaultDeltaShares == zero)
3525 {
3526 JLOG(j.fatal()) << //
3527 "Invariant failed: clawback must change vault shares";
3528 return false; // That's all we can do
3529 }
3530
3531 if (*vaultDeltaShares * -1 != *accountDeltaShares)
3532 {
3533 JLOG(j.fatal()) << //
3534 "Invariant failed: clawback must change holder and "
3535 "vault shares by equal amount";
3536 result = false;
3537 }
3538
3539 return result;
3540 }
3541
3542 case ttLOAN_SET:
3543 case ttLOAN_MANAGE:
3544 case ttLOAN_PAY: {
3545 // TBD
3546 return true;
3547 }
3548
3549 default:
3550 // LCOV_EXCL_START
3551 UNREACHABLE(
3552 "xrpl::ValidVault::finalize : unknown transaction type");
3553 return false;
3554 // LCOV_EXCL_STOP
3555 }
3556 }();
3557
3558 if (!result)
3559 {
3560 // The comment at the top of this file starting with "assert(enforce)"
3561 // explains this assert.
3562 XRPL_ASSERT(enforce, "xrpl::ValidVault::finalize : vault invariants");
3563 return !enforce;
3564 }
3565
3566 return true;
3567}
3568
3569} // namespace xrpl
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
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
std::vector< std::pair< std::shared_ptr< SLE const >, std::shared_ptr< SLE const > > > accountsDeleted_
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 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()
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 &)
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
std::set< std::pair< SLE::const_pointer, SLE::const_pointer > > changedEntries_
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 &)
A view into a ledger.
Definition ReadView.h:32
virtual Rules const & rules() const =0
Returns the tx processing rules.
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 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 std::shared_ptr< SLE const > read(Keylet const &k) const =0
Return the state item associated with a key.
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition Rules.cpp:111
Identifies fields.
Definition SField.h:127
int signum() const noexcept
Definition STAmount.h:506
STAmount zeroed() const
Returns a zero value with the same issuer and currency.
Definition STAmount.h:512
bool native() const noexcept
Definition STAmount.h:450
XRPAmount xrp() const
Definition STAmount.cpp:264
uint256 const & key() const
Returns the 'key' (or 'index') of this item.
LedgerEntryType getType() const
uint192 getFieldH192(SField const &field) const
Definition STObject.cpp:620
T::value_type at(TypedField< T > const &f) const
Get the value of a field.
Definition STObject.h:1055
std::uint32_t getFieldU32(SField const &field) const
Definition STObject.cpp:596
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
AccountID getAccountID(SField const &field) const
Definition STObject.cpp:638
STAmount const & getFieldAmount(SField const &field) const
Definition STObject.cpp:652
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::shared_ptr< SLE const > findIssuer(AccountID const &issuerID, ReadView const &view)
void recordBalance(Issue const &issue, BalanceChange change)
bool isValidEntry(std::shared_ptr< SLE const > const &before, std::shared_ptr< SLE const > const &after)
bool validateIssuerChanges(std::shared_ptr< SLE const > const &issuer, IssuerChanges const &changes, STTx const &tx, beast::Journal const &j, bool enforce)
std::map< AccountID, std::shared_ptr< SLE const > const > possibleIssuers_
STAmount calculateBalanceChange(std::shared_ptr< SLE const > const &before, std::shared_ptr< SLE const > const &after, bool isDelete)
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
bool validateFrozenState(BalanceChange const &change, bool high, STTx const &tx, beast::Journal const &j, bool enforce, bool globalFreeze)
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
void recordBalanceChanges(std::shared_ptr< SLE const > const &after, STAmount const &balanceChange)
std::optional< AccountID > ammAccount_
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 finalizeBid(bool enforce, beast::Journal const &) const
bool finalizeCreate(STTx const &, ReadView const &, bool enforce, beast::Journal const &) const
bool finalizeDEX(bool enforce, beast::Journal const &) const
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE 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 > lptAMMBalanceAfter_
bool finalizeVote(bool enforce, beast::Journal const &) const
bool finalizeDelete(bool enforce, TER res, beast::Journal const &) const
std::optional< STAmount > lptAMMBalanceBefore_
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 &)
std::uint32_t trustlinesChanged
std::uint32_t mptokensChanged
std::map< uint256, BrokerInfo > brokers_
bool goodZeroDirectory(ReadView const &view, SLE::const_ref dir, beast::Journal const &j) const
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
std::vector< SLE::const_pointer > mpts_
std::vector< SLE::const_pointer > lines_
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 &)
std::vector< std::pair< SLE::const_pointer, SLE::const_pointer > > loans_
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
std::uint32_t mptokensCreated_
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::uint32_t mptokensDeleted_
std::uint32_t mptIssuancesCreated_
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 &)
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 &)
hash_set< uint256 > domains_
std::optional< SleStatus > sleStatus_[2]
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 &)
std::vector< std::string > errors_
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 &)
static Number constexpr zero
std::vector< Shares > afterMPTs_
std::unordered_map< uint256, Number > deltas_
std::vector< Vault > afterVault_
std::vector< Shares > beforeMPTs_
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
std::vector< Vault > beforeVault_
constexpr value_type drops() const
Returns the number of drops.
Definition XRPAmount.h:158
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 &)
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 nftpage_max(AccountID const &owner)
A keylet for the owner's last possible NFT page.
Definition Indexes.cpp:393
Keylet unchecked(uint256 const &key) noexcept
Any ledger entry.
Definition Indexes.cpp:350
Keylet loanbroker(AccountID const &owner, std::uint32_t seq) noexcept
Definition Indexes.cpp:552
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition Indexes.cpp:356
Keylet mptIssuance(std::uint32_t seq, AccountID const &issuer) noexcept
Definition Indexes.cpp:508
Keylet vault(AccountID const &owner, std::uint32_t seq) noexcept
Definition Indexes.cpp:546
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:226
Keylet nftpage_min(AccountID const &owner)
NFT page keylets.
Definition Indexes.cpp:385
Keylet mptoken(MPTID const &issuanceID, AccountID const &holder) noexcept
Definition Indexes.cpp:522
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:166
Keylet permissionedDomain(AccountID const &account, std::uint32_t seq) noexcept
Definition Indexes.cpp:564
uint256 constexpr pageMask(std::string_view("0000000000000000000000000000000000000000ffffffffffffffffffffffff"))
bool compareTokens(uint256 const &a, uint256 const &b)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
std::size_t constexpr dirMaxTokensPerPage
The maximum number of items in an NFT page.
Definition Protocol.h:47
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
Definition Issue.h:96
std::vector< SField const * > const & getPseudoAccountFields()
Definition View.cpp:1199
@ fhIGNORE_FREEZE
Definition View.h:59
bool isXRP(AccountID const &c)
Definition AccountID.h:71
T get(Section const &section, std::string const &name, T const &defaultValue=T{})
Retrieve a key/value pair from a section.
constexpr base_uint< Bits, Tag > operator|(base_uint< Bits, Tag > const &a, base_uint< Bits, Tag > const &b)
Definition base_uint.h:596
std::uint64_t constexpr maxMPTokenAmount
The maximum amount of MPTokenIssuance.
Definition Protocol.h:235
STAmount ammLPTokens(STAmount const &asset1, STAmount const &asset2, Issue const &lptIssue)
Calculate LP Tokens given AMM pool reserves.
Definition AMMHelpers.cpp:6
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j)
Definition View.cpp:461
base_uint< 256 > uint256
Definition base_uint.h:539
bool isPseudoAccount(std::shared_ptr< SLE const > sleAcct, std::set< SField const * > const &pseudoFieldFilter={})
Definition View.cpp:1226
@ mustModifyVault
@ destroyMPTIssuance
@ changeNFTCounts
@ createPseudoAcct
@ createMPTIssuance
@ mustAuthorizeMPT
@ mayAuthorizeMPT
static bool validBalances(STAmount const &amount, STAmount const &amount2, STAmount const &lptAMMBalance, ValidAMM::ZeroAllowed zeroAllowed)
bool hasPrivilege(STTx const &tx, Privilege priv)
@ ahIGNORE_AUTH
Definition View.h:62
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition View.cpp:3922
Number root2(Number f)
Definition Number.cpp:709
std::size_t constexpr maxPermissionedDomainCredentialsArraySize
The maximum number of credentials can be passed in array for permissioned domain.
Definition Protocol.h:229
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
@ transactionID
transaction plus signature to give transaction ID
bool isTesSuccess(TER x) noexcept
Definition TER.h:659
Buffer sign(PublicKey const &pk, SecretKey const &sk, Slice const &message)
Generate a signature for a message.
constexpr XRPAmount INITIAL_XRP
Configure the native currency.
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
@ tecINCOMPLETE
Definition TER.h:317
@ lsfDepositAuth
@ lsfDefaultRipple
@ lsfLoanOverpayment
@ lsfLowDeepFreeze
@ lsfGlobalFreeze
@ lsfLowFreeze
@ lsfDisableMaster
@ lsfHighFreeze
@ lsfHighDeepFreeze
Currency const & badCurrency()
We deliberately disallow the currency that looks like "XRP" because too many people were using it ins...
MPTID makeMptID(std::uint32_t sequence, AccountID const &account)
Definition Indexes.cpp:152
@ tesSUCCESS
Definition TER.h:226
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::array< keyletDesc< AccountID const & >, 6 > const directAccountKeylets
Definition Indexes.h:383
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 > senders
std::vector< BalanceChange > receivers
static Shares make(SLE const &)
static Vault make(SLE const &)
T to_string(T... args)
T value_or(T... args)
T visit(T... args)