rippled
Loading...
Searching...
No Matches
InvariantCheck.cpp
1#include <xrpld/app/misc/AMMHelpers.h>
2#include <xrpld/app/misc/AMMUtils.h>
3#include <xrpld/app/tx/detail/InvariantCheck.h>
4#include <xrpld/app/tx/detail/NFTokenUtils.h>
5#include <xrpld/app/tx/detail/PermissionedDomainSet.h>
6
7#include <xrpl/basics/Log.h>
8#include <xrpl/beast/utility/instrumentation.h>
9#include <xrpl/ledger/CredentialHelpers.h>
10#include <xrpl/ledger/ReadView.h>
11#include <xrpl/ledger/View.h>
12#include <xrpl/protocol/Feature.h>
13#include <xrpl/protocol/Indexes.h>
14#include <xrpl/protocol/LedgerFormats.h>
15#include <xrpl/protocol/MPTIssue.h>
16#include <xrpl/protocol/SField.h>
17#include <xrpl/protocol/STArray.h>
18#include <xrpl/protocol/STNumber.h>
19#include <xrpl/protocol/SystemParameters.h>
20#include <xrpl/protocol/TER.h>
21#include <xrpl/protocol/TxFormats.h>
22#include <xrpl/protocol/Units.h>
23#include <xrpl/protocol/nftPageMask.h>
24
25#include <cstdint>
26#include <optional>
27
28namespace ripple {
29
30/*
31assert(enforce)
32
33There are several asserts (or XRPL_ASSERTs) in this file that check a variable
34named `enforce` when an invariant fails. At first glance, those asserts may look
35incorrect, but they are not.
36
37Those asserts take advantage of two facts:
381. `asserts` are not (normally) executed in release builds.
392. Invariants should *never* fail, except in tests that specifically modify
40 the open ledger to break them.
41
42This makes `assert(enforce)` sort of a second-layer of invariant enforcement
43aimed at _developers_. It's designed to fire if a developer writes code that
44violates an invariant, and runs it in unit tests or a develop build that _does
45not have the relevant amendments enabled_. It's intentionally a pain in the neck
46so that bad code gets caught and fixed as early as possible.
47*/
48
50 noPriv =
51 0x0000, // The transaction can not do any of the enumerated operations
53 0x0001, // The transaction can create a new ACCOUNT_ROOT object.
54 createPseudoAcct = 0x0002, // The transaction can create a pseudo account,
55 // which implies createAcct
57 0x0004, // The transaction must delete an ACCOUNT_ROOT object
58 mayDeleteAcct = 0x0008, // The transaction may delete an ACCOUNT_ROOT
59 // object, but does not have to
60 overrideFreeze = 0x0010, // The transaction can override some freeze rules
61 changeNFTCounts = 0x0020, // The transaction can mint or burn an NFT
63 0x0040, // The transaction can create a new MPT issuance
64 destroyMPTIssuance = 0x0080, // The transaction can destroy an MPT issuance
65 mustAuthorizeMPT = 0x0100, // The transaction MUST create or delete an MPT
66 // object (except by issuer)
67 mayAuthorizeMPT = 0x0200, // The transaction MAY create or delete an MPT
68 // object (except by issuer)
70 0x0400, // The transaction MAY delete an MPT object. May not create.
72 0x0800, // The transaction must modify, delete or create, a vault
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, delegatable, 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 // Deprecated types
99 default:
100 return false;
101 }
102};
103
104#undef TRANSACTION
105#pragma pop_macro("TRANSACTION")
106
107void
109 bool,
112{
113 // nothing to do
114}
115
116bool
118 STTx const& tx,
119 TER const,
120 XRPAmount const fee,
121 ReadView const&,
122 beast::Journal const& j)
123{
124 // We should never charge a negative fee
125 if (fee.drops() < 0)
126 {
127 JLOG(j.fatal()) << "Invariant failed: fee paid was negative: "
128 << fee.drops();
129 return false;
130 }
131
132 // We should never charge a fee that's greater than or equal to the
133 // entire XRP supply.
134 if (fee >= INITIAL_XRP)
135 {
136 JLOG(j.fatal()) << "Invariant failed: fee paid exceeds system limit: "
137 << fee.drops();
138 return false;
139 }
140
141 // We should never charge more for a transaction than the transaction
142 // authorizes. It's possible to charge less in some circumstances.
143 if (fee > tx.getFieldAmount(sfFee).xrp())
144 {
145 JLOG(j.fatal()) << "Invariant failed: fee paid is " << fee.drops()
146 << " exceeds fee specified in transaction.";
147 return false;
148 }
149
150 return true;
151}
152
153//------------------------------------------------------------------------------
154
155void
157 bool isDelete,
158 std::shared_ptr<SLE const> const& before,
160{
161 /* We go through all modified ledger entries, looking only at account roots,
162 * escrow payments, and payment channels. We remove from the total any
163 * previous XRP values and add to the total any new XRP values. The net
164 * balance of a payment channel is computed from two fields (amount and
165 * balance) and deletions are ignored for paychan and escrow because the
166 * amount fields have not been adjusted for those in the case of deletion.
167 */
168 if (before)
169 {
170 switch (before->getType())
171 {
172 case ltACCOUNT_ROOT:
173 drops_ -= (*before)[sfBalance].xrp().drops();
174 break;
175 case ltPAYCHAN:
176 drops_ -=
177 ((*before)[sfAmount] - (*before)[sfBalance]).xrp().drops();
178 break;
179 case ltESCROW:
180 if (isXRP((*before)[sfAmount]))
181 drops_ -= (*before)[sfAmount].xrp().drops();
182 break;
183 default:
184 break;
185 }
186 }
187
188 if (after)
189 {
190 switch (after->getType())
191 {
192 case ltACCOUNT_ROOT:
193 drops_ += (*after)[sfBalance].xrp().drops();
194 break;
195 case ltPAYCHAN:
196 if (!isDelete)
197 drops_ += ((*after)[sfAmount] - (*after)[sfBalance])
198 .xrp()
199 .drops();
200 break;
201 case ltESCROW:
202 if (!isDelete && isXRP((*after)[sfAmount]))
203 drops_ += (*after)[sfAmount].xrp().drops();
204 break;
205 default:
206 break;
207 }
208 }
209}
210
211bool
213 STTx const& tx,
214 TER const,
215 XRPAmount const fee,
216 ReadView const&,
217 beast::Journal const& j)
218{
219 // The net change should never be positive, as this would mean that the
220 // transaction created XRP out of thin air. That's not possible.
221 if (drops_ > 0)
222 {
223 JLOG(j.fatal()) << "Invariant failed: XRP net change was positive: "
224 << drops_;
225 return false;
226 }
227
228 // The negative of the net change should be equal to actual fee charged.
229 if (-drops_ != fee.drops())
230 {
231 JLOG(j.fatal()) << "Invariant failed: XRP net change of " << drops_
232 << " doesn't match fee " << fee.drops();
233 return false;
234 }
235
236 return true;
237}
238
239//------------------------------------------------------------------------------
240
241void
243 bool,
244 std::shared_ptr<SLE const> const& before,
246{
247 auto isBad = [](STAmount const& balance) {
248 if (!balance.native())
249 return true;
250
251 auto const drops = balance.xrp();
252
253 // Can't have more than the number of drops instantiated
254 // in the genesis ledger.
255 if (drops > INITIAL_XRP)
256 return true;
257
258 // Can't have a negative balance (0 is OK)
259 if (drops < XRPAmount{0})
260 return true;
261
262 return false;
263 };
264
265 if (before && before->getType() == ltACCOUNT_ROOT)
266 bad_ |= isBad((*before)[sfBalance]);
267
268 if (after && after->getType() == ltACCOUNT_ROOT)
269 bad_ |= isBad((*after)[sfBalance]);
270}
271
272bool
274 STTx const&,
275 TER const,
276 XRPAmount const,
277 ReadView const&,
278 beast::Journal const& j)
279{
280 if (bad_)
281 {
282 JLOG(j.fatal()) << "Invariant failed: incorrect account XRP balance";
283 return false;
284 }
285
286 return true;
287}
288
289//------------------------------------------------------------------------------
290
291void
293 bool isDelete,
294 std::shared_ptr<SLE const> const& before,
296{
297 auto isBad = [](STAmount const& pays, STAmount const& gets) {
298 // An offer should never be negative
299 if (pays < beast::zero)
300 return true;
301
302 if (gets < beast::zero)
303 return true;
304
305 // Can't have an XRP to XRP offer:
306 return pays.native() && gets.native();
307 };
308
309 if (before && before->getType() == ltOFFER)
310 bad_ |= isBad((*before)[sfTakerPays], (*before)[sfTakerGets]);
311
312 if (after && after->getType() == ltOFFER)
313 bad_ |= isBad((*after)[sfTakerPays], (*after)[sfTakerGets]);
314}
315
316bool
318 STTx const&,
319 TER const,
320 XRPAmount const,
321 ReadView const&,
322 beast::Journal const& j)
323{
324 if (bad_)
325 {
326 JLOG(j.fatal()) << "Invariant failed: offer with a bad amount";
327 return false;
328 }
329
330 return true;
331}
332
333//------------------------------------------------------------------------------
334
335void
337 bool isDelete,
338 std::shared_ptr<SLE const> const& before,
340{
341 auto isBad = [](STAmount const& amount) {
342 // XRP case
343 if (amount.native())
344 {
345 if (amount.xrp() <= XRPAmount{0})
346 return true;
347
348 if (amount.xrp() >= INITIAL_XRP)
349 return true;
350 }
351 else
352 {
353 // IOU case
354 if (amount.holds<Issue>())
355 {
356 if (amount <= beast::zero)
357 return true;
358
359 if (badCurrency() == amount.getCurrency())
360 return true;
361 }
362
363 // MPT case
364 if (amount.holds<MPTIssue>())
365 {
366 if (amount <= beast::zero)
367 return true;
368
369 if (amount.mpt() > MPTAmount{maxMPTokenAmount})
370 return true; // LCOV_EXCL_LINE
371 }
372 }
373 return false;
374 };
375
376 if (before && before->getType() == ltESCROW)
377 bad_ |= isBad((*before)[sfAmount]);
378
379 if (after && after->getType() == ltESCROW)
380 bad_ |= isBad((*after)[sfAmount]);
381
382 auto checkAmount = [this](std::int64_t amount) {
383 if (amount > maxMPTokenAmount || amount < 0)
384 bad_ = true;
385 };
386
387 if (after && after->getType() == ltMPTOKEN_ISSUANCE)
388 {
389 auto const outstanding = (*after)[sfOutstandingAmount];
390 checkAmount(outstanding);
391 if (auto const locked = (*after)[~sfLockedAmount])
392 {
393 checkAmount(*locked);
394 bad_ = outstanding < *locked;
395 }
396 }
397
398 if (after && after->getType() == ltMPTOKEN)
399 {
400 auto const mptAmount = (*after)[sfMPTAmount];
401 checkAmount(mptAmount);
402 if (auto const locked = (*after)[~sfLockedAmount])
403 {
404 checkAmount(*locked);
405 }
406 }
407}
408
409bool
411 STTx const& txn,
412 TER const,
413 XRPAmount const,
414 ReadView const& rv,
415 beast::Journal const& j)
416{
417 if (bad_)
418 {
419 JLOG(j.fatal()) << "Invariant failed: escrow specifies invalid amount";
420 return false;
421 }
422
423 return true;
424}
425
426//------------------------------------------------------------------------------
427
428void
430 bool isDelete,
431 std::shared_ptr<SLE const> const& before,
433{
434 if (isDelete && before && before->getType() == ltACCOUNT_ROOT)
436}
437
438bool
440 STTx const& tx,
441 TER const result,
442 XRPAmount const,
443 ReadView const&,
444 beast::Journal const& j)
445{
446 // AMM account root can be deleted as the result of AMM withdraw/delete
447 // transaction when the total AMM LP Tokens balance goes to 0.
448 // A successful AccountDelete or AMMDelete MUST delete exactly
449 // one account root.
450 if (hasPrivilege(tx, mustDeleteAcct) && result == tesSUCCESS)
451 {
452 if (accountsDeleted_ == 1)
453 return true;
454
455 if (accountsDeleted_ == 0)
456 JLOG(j.fatal()) << "Invariant failed: account deletion "
457 "succeeded without deleting an account";
458 else
459 JLOG(j.fatal()) << "Invariant failed: account deletion "
460 "succeeded but deleted multiple accounts!";
461 return false;
462 }
463
464 // A successful AMMWithdraw/AMMClawback MAY delete one account root
465 // when the total AMM LP Tokens balance goes to 0. Not every AMM withdraw
466 // deletes the AMM account, accountsDeleted_ is set if it is deleted.
467 if (hasPrivilege(tx, mayDeleteAcct) && result == tesSUCCESS &&
468 accountsDeleted_ == 1)
469 return true;
470
471 if (accountsDeleted_ == 0)
472 return true;
473
474 JLOG(j.fatal()) << "Invariant failed: an account root was deleted";
475 return false;
476}
477
478//------------------------------------------------------------------------------
479
480void
482 bool isDelete,
483 std::shared_ptr<SLE const> const& before,
485{
486 if (isDelete && before && before->getType() == ltACCOUNT_ROOT)
487 accountsDeleted_.emplace_back(before, after);
488}
489
490bool
492 STTx const& tx,
493 TER const result,
494 XRPAmount const,
495 ReadView const& view,
496 beast::Journal const& j)
497{
498 // Always check for objects in the ledger, but to prevent differing
499 // transaction processing results, however unlikely, only fail if the
500 // feature is enabled. Enabled, or not, though, a fatal-level message will
501 // be logged
502 [[maybe_unused]] bool const enforce =
503 view.rules().enabled(featureInvariantsV1_1) ||
504 view.rules().enabled(featureSingleAssetVault) ||
505 view.rules().enabled(featureLendingProtocol);
506
507 auto const objectExists = [&view, enforce, &j](auto const& keylet) {
508 (void)enforce;
509 if (auto const sle = view.read(keylet))
510 {
511 // Finding the object is bad
512 auto const typeName = [&sle]() {
513 auto item =
514 LedgerFormats::getInstance().findByType(sle->getType());
515
516 if (item != nullptr)
517 return item->getName();
518 return std::to_string(sle->getType());
519 }();
520
521 JLOG(j.fatal())
522 << "Invariant failed: account deletion left behind a "
523 << typeName << " object";
524 // The comment above starting with "assert(enforce)" explains this
525 // assert.
526 XRPL_ASSERT(
527 enforce,
528 "ripple::AccountRootsDeletedClean::finalize::objectExists : "
529 "account deletion left no objects behind");
530 return true;
531 }
532 return false;
533 };
534
535 for (auto const& [before, after] : accountsDeleted_)
536 {
537 auto const accountID = before->getAccountID(sfAccount);
538 // An account should not be deleted with a balance
539 if (after->at(sfBalance) != beast::zero)
540 {
541 JLOG(j.fatal()) << "Invariant failed: account deletion left "
542 "behind a non-zero balance";
543 XRPL_ASSERT(
544 enforce,
545 "ripple::AccountRootsDeletedClean::finalize : "
546 "deleted account has zero balance");
547 if (enforce)
548 return false;
549 }
550 // An account should not be deleted with a non-zero owner count
551 if (after->at(sfOwnerCount) != 0)
552 {
553 JLOG(j.fatal()) << "Invariant failed: account deletion left "
554 "behind a non-zero owner count";
555 XRPL_ASSERT(
556 enforce,
557 "ripple::AccountRootsDeletedClean::finalize : "
558 "deleted account has zero owner count");
559 if (enforce)
560 return false;
561 }
562 // Simple types
563 for (auto const& [keyletfunc, _, __] : directAccountKeylets)
564 {
565 if (objectExists(std::invoke(keyletfunc, accountID)) && enforce)
566 return false;
567 }
568
569 {
570 // NFT pages. ntfpage_min and nftpage_max were already explicitly
571 // checked above as entries in directAccountKeylets. This uses
572 // view.succ() to check for any NFT pages in between the two
573 // endpoints.
574 Keylet const first = keylet::nftpage_min(accountID);
575 Keylet const last = keylet::nftpage_max(accountID);
576
577 std::optional<uint256> key = view.succ(first.key, last.key.next());
578
579 // current page
580 if (key && objectExists(Keylet{ltNFTOKEN_PAGE, *key}) && enforce)
581 return false;
582 }
583
584 // If the account is a pseudo account, then the linked object must
585 // also be deleted. e.g. AMM, Vault, etc.
586 for (auto const& field : getPseudoAccountFields())
587 {
588 if (before->isFieldPresent(*field))
589 {
590 auto const key = before->getFieldH256(*field);
591 if (objectExists(keylet::unchecked(key)) && enforce)
592 return false;
593 }
594 }
595 }
596
597 return true;
598}
599
600//------------------------------------------------------------------------------
601
602void
604 bool,
605 std::shared_ptr<SLE const> const& before,
607{
608 if (before && after && before->getType() != after->getType())
609 typeMismatch_ = true;
610
611 if (after)
612 {
613#pragma push_macro("LEDGER_ENTRY")
614#undef LEDGER_ENTRY
615
616#define LEDGER_ENTRY(tag, ...) case tag:
617
618 switch (after->getType())
619 {
620#include <xrpl/protocol/detail/ledger_entries.macro>
621
622 break;
623 default:
624 invalidTypeAdded_ = true;
625 break;
626 }
627
628#undef LEDGER_ENTRY
629#pragma pop_macro("LEDGER_ENTRY")
630 }
631}
632
633bool
635 STTx const&,
636 TER const,
637 XRPAmount const,
638 ReadView const&,
639 beast::Journal const& j)
640{
641 if ((!typeMismatch_) && (!invalidTypeAdded_))
642 return true;
643
644 if (typeMismatch_)
645 {
646 JLOG(j.fatal()) << "Invariant failed: ledger entry type mismatch";
647 }
648
650 {
651 JLOG(j.fatal()) << "Invariant failed: invalid ledger entry type added";
652 }
653
654 return false;
655}
656
657//------------------------------------------------------------------------------
658
659void
661 bool,
664{
665 if (after && after->getType() == ltRIPPLE_STATE)
666 {
667 // checking the issue directly here instead of
668 // relying on .native() just in case native somehow
669 // were systematically incorrect
671 after->getFieldAmount(sfLowLimit).issue() == xrpIssue() ||
672 after->getFieldAmount(sfHighLimit).issue() == xrpIssue();
673 }
674}
675
676bool
678 STTx const&,
679 TER const,
680 XRPAmount const,
681 ReadView const&,
682 beast::Journal const& j)
683{
684 if (!xrpTrustLine_)
685 return true;
686
687 JLOG(j.fatal()) << "Invariant failed: an XRP trust line was created";
688 return false;
689}
690
691//------------------------------------------------------------------------------
692
693void
695 bool,
698{
699 if (after && after->getType() == ltRIPPLE_STATE)
700 {
701 std::uint32_t const uFlags = after->getFieldU32(sfFlags);
702 bool const lowFreeze = uFlags & lsfLowFreeze;
703 bool const lowDeepFreeze = uFlags & lsfLowDeepFreeze;
704
705 bool const highFreeze = uFlags & lsfHighFreeze;
706 bool const highDeepFreeze = uFlags & lsfHighDeepFreeze;
707
709 (lowDeepFreeze && !lowFreeze) || (highDeepFreeze && !highFreeze);
710 }
711}
712
713bool
715 STTx const&,
716 TER const,
717 XRPAmount const,
718 ReadView const&,
719 beast::Journal const& j)
720{
722 return true;
723
724 JLOG(j.fatal()) << "Invariant failed: a trust line with deep freeze flag "
725 "without normal freeze was created";
726 return false;
727}
728
729//------------------------------------------------------------------------------
730
731void
733 bool isDelete,
734 std::shared_ptr<SLE const> const& before,
736{
737 /*
738 * A trust line freeze state alone doesn't determine if a transfer is
739 * frozen. The transfer must be examined "end-to-end" because both sides of
740 * the transfer may have different freeze states and freeze impact depends
741 * on the transfer direction. This is why first we need to track the
742 * transfers using IssuerChanges senders/receivers.
743 *
744 * Only in validateIssuerChanges, after we collected all changes can we
745 * determine if the transfer is valid.
746 */
747 if (!isValidEntry(before, after))
748 {
749 return;
750 }
751
752 auto const balanceChange = calculateBalanceChange(before, after, isDelete);
753 if (balanceChange.signum() == 0)
754 {
755 return;
756 }
757
758 recordBalanceChanges(after, balanceChange);
759}
760
761bool
763 STTx const& tx,
764 TER const ter,
765 XRPAmount const fee,
766 ReadView const& view,
767 beast::Journal const& j)
768{
769 /*
770 * We check this invariant regardless of deep freeze amendment status,
771 * allowing for detection and logging of potential issues even when the
772 * amendment is disabled.
773 *
774 * If an exploit that allows moving frozen assets is discovered,
775 * we can alert operators who monitor fatal messages and trigger assert in
776 * debug builds for an early warning.
777 *
778 * In an unlikely event that an exploit is found, this early detection
779 * enables encouraging the UNL to expedite deep freeze amendment activation
780 * or deploy hotfixes via new amendments. In case of a new amendment, we'd
781 * only have to change this line setting 'enforce' variable.
782 * enforce = view.rules().enabled(featureDeepFreeze) ||
783 * view.rules().enabled(fixFreezeExploit);
784 */
785 [[maybe_unused]] bool const enforce =
786 view.rules().enabled(featureDeepFreeze);
787
788 for (auto const& [issue, changes] : balanceChanges_)
789 {
790 auto const issuerSle = findIssuer(issue.account, view);
791 // It should be impossible for the issuer to not be found, but check
792 // just in case so rippled doesn't crash in release.
793 if (!issuerSle)
794 {
795 // The comment above starting with "assert(enforce)" explains this
796 // assert.
797 XRPL_ASSERT(
798 enforce,
799 "ripple::TransfersNotFrozen::finalize : enforce "
800 "invariant.");
801 if (enforce)
802 {
803 return false;
804 }
805 continue;
806 }
807
808 if (!validateIssuerChanges(issuerSle, changes, tx, j, enforce))
809 {
810 return false;
811 }
812 }
813
814 return true;
815}
816
817bool
819 std::shared_ptr<SLE const> const& before,
821{
822 // `after` can never be null, even if the trust line is deleted.
823 XRPL_ASSERT(
824 after, "ripple::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 "ripple::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 "ripple::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 ripple
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 "ripple::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 "ripple::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 "ripple::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 ripple::STTx const& tx,
2093 ripple::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 ripple::STTx const& tx,
2139 ripple::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 ripple::STTx const& tx,
2160 ripple::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 "ripple::NoModifiedUnmodifiableFields::finalize : no bad "
2321 "changes or enforce invariant");
2322 if (bad)
2323 {
2324 JLOG(j.fatal())
2325 << "Invariant failed: changed an unchangable 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.shareMPTID = from.getFieldH192(sfShareMPTID);
2627 self.assetsTotal = from.at(sfAssetsTotal);
2628 self.assetsAvailable = from.at(sfAssetsAvailable);
2629 self.assetsMaximum = from.at(sfAssetsMaximum);
2630 self.lossUnrealized = from.at(sfLossUnrealized);
2631 return self;
2632}
2633
2636{
2637 XRPL_ASSERT(
2638 from.getType() == ltMPTOKEN_ISSUANCE,
2639 "ValidVault::Shares::make : from MPTokenIssuance object");
2640
2641 ValidVault::Shares self;
2642 self.share = MPTIssue(
2643 makeMptID(from.getFieldU32(sfSequence), from.getAccountID(sfIssuer)));
2644 self.sharesTotal = from.at(sfOutstandingAmount);
2645 self.sharesMaximum = from[~sfMaximumAmount].value_or(maxMPTokenAmount);
2646 return self;
2647}
2648
2649void
2651 bool isDelete,
2652 std::shared_ptr<SLE const> const& before,
2654{
2655 // If `before` is empty, this means an object is being created, in which
2656 // case `isDelete` must be false. Otherwise `before` and `after` are set and
2657 // `isDelete` indicates whether an object is being deleted or modified.
2658 XRPL_ASSERT(
2659 after != nullptr && (before != nullptr || !isDelete),
2660 "ripple::ValidVault::visitEntry : some object is available");
2661
2662 // Number balanceDelta will capture the difference (delta) between "before"
2663 // state (zero if created) and "after" state (zero if destroyed), so the
2664 // invariants can validate that the change in account balances matches the
2665 // change in vault balances, stored to deltas_ at the end of this function.
2666 Number balanceDelta{};
2667
2668 std::int8_t sign = 0;
2669 if (before)
2670 {
2671 switch (before->getType())
2672 {
2673 case ltVAULT:
2674 beforeVault_.push_back(Vault::make(*before));
2675 break;
2676 case ltMPTOKEN_ISSUANCE:
2677 // At this moment we have no way of telling if this object holds
2678 // vault shares or something else. Save it for finalize.
2679 beforeMPTs_.push_back(Shares::make(*before));
2680 balanceDelta = static_cast<std::int64_t>(
2681 before->getFieldU64(sfOutstandingAmount));
2682 sign = 1;
2683 break;
2684 case ltMPTOKEN:
2685 balanceDelta =
2686 static_cast<std::int64_t>(before->getFieldU64(sfMPTAmount));
2687 sign = -1;
2688 break;
2689 case ltACCOUNT_ROOT:
2690 case ltRIPPLE_STATE:
2691 balanceDelta = before->getFieldAmount(sfBalance);
2692 sign = -1;
2693 break;
2694 default:;
2695 }
2696 }
2697
2698 if (!isDelete && after)
2699 {
2700 switch (after->getType())
2701 {
2702 case ltVAULT:
2703 afterVault_.push_back(Vault::make(*after));
2704 break;
2705 case ltMPTOKEN_ISSUANCE:
2706 // At this moment we have no way of telling if this object holds
2707 // vault shares or something else. Save it for finalize.
2708 afterMPTs_.push_back(Shares::make(*after));
2709 balanceDelta -= Number(static_cast<std::int64_t>(
2710 after->getFieldU64(sfOutstandingAmount)));
2711 sign = 1;
2712 break;
2713 case ltMPTOKEN:
2714 balanceDelta -= Number(
2715 static_cast<std::int64_t>(after->getFieldU64(sfMPTAmount)));
2716 sign = -1;
2717 break;
2718 case ltACCOUNT_ROOT:
2719 case ltRIPPLE_STATE:
2720 balanceDelta -= Number(after->getFieldAmount(sfBalance));
2721 sign = -1;
2722 break;
2723 default:;
2724 }
2725 }
2726
2727 uint256 const key = (before ? before->key() : after->key());
2728 // Append to deltas if sign is non-zero, i.e. an object of an interesting
2729 // type has been updated. A transaction may update an object even when
2730 // its balance has not changed, e.g. transaction fee equals the amount
2731 // transferred to the account. We intentionally do not compare balanceDelta
2732 // against zero, to avoid missing such updates.
2733 if (sign != 0)
2734 deltas_[key] = balanceDelta * sign;
2735}
2736
2737bool
2739 STTx const& tx,
2740 TER const ret,
2741 XRPAmount const fee,
2742 ReadView const& view,
2743 beast::Journal const& j)
2744{
2745 bool const enforce = view.rules().enabled(featureSingleAssetVault);
2746
2747 if (!isTesSuccess(ret))
2748 return true; // Do not perform checks
2749
2750 if (afterVault_.empty() && beforeVault_.empty())
2751 {
2753 {
2754 JLOG(j.fatal()) << //
2755 "Invariant failed: vault operation succeeded without modifying "
2756 "a vault";
2757 XRPL_ASSERT(
2758 enforce, "ripple::ValidVault::finalize : vault noop invariant");
2759 return !enforce;
2760 }
2761
2762 return true; // Not a vault operation
2763 }
2764 else if (!(hasPrivilege(tx, mustModifyVault) ||
2766 {
2767 JLOG(j.fatal()) << //
2768 "Invariant failed: vault updated by a wrong transaction type";
2769 XRPL_ASSERT(
2770 enforce,
2771 "ripple::ValidVault::finalize : illegal vault transaction "
2772 "invariant");
2773 return !enforce; // Also not a vault operation
2774 }
2775
2776 if (beforeVault_.size() > 1 || afterVault_.size() > 1)
2777 {
2778 JLOG(j.fatal()) << //
2779 "Invariant failed: vault operation updated more than single vault";
2780 XRPL_ASSERT(
2781 enforce, "ripple::ValidVault::finalize : single vault invariant");
2782 return !enforce; // That's all we can do here
2783 }
2784
2785 auto const txnType = tx.getTxnType();
2786
2787 // We do special handling for ttVAULT_DELETE first, because it's the only
2788 // vault-modifying transaction without an "after" state of the vault
2789 if (afterVault_.empty())
2790 {
2791 if (txnType != ttVAULT_DELETE)
2792 {
2793 JLOG(j.fatal()) << //
2794 "Invariant failed: vault deleted by a wrong transaction type";
2795 XRPL_ASSERT(
2796 enforce,
2797 "ripple::ValidVault::finalize : illegal vault deletion "
2798 "invariant");
2799 return !enforce; // That's all we can do here
2800 }
2801
2802 // Note, if afterVault_ is empty then we know that beforeVault_ is not
2803 // empty, as enforced at the top of this function
2804 auto const& beforeVault = beforeVault_[0];
2805
2806 // At this moment we only know a vault is being deleted and there
2807 // might be some MPTokenIssuance objects which are deleted in the
2808 // same transaction. Find the one matching this vault.
2809 auto const deletedShares = [&]() -> std::optional<Shares> {
2810 for (auto const& e : beforeMPTs_)
2811 {
2812 if (e.share.getMptID() == beforeVault.shareMPTID)
2813 return std::move(e);
2814 }
2815 return std::nullopt;
2816 }();
2817
2818 if (!deletedShares)
2819 {
2820 JLOG(j.fatal()) << "Invariant failed: deleted vault must also "
2821 "delete shares";
2822 XRPL_ASSERT(
2823 enforce,
2824 "ripple::ValidVault::finalize : shares deletion invariant");
2825 return !enforce; // That's all we can do here
2826 }
2827
2828 bool result = true;
2829 if (deletedShares->sharesTotal != 0)
2830 {
2831 JLOG(j.fatal()) << "Invariant failed: deleted vault must have no "
2832 "shares outstanding";
2833 result = false;
2834 }
2835 if (beforeVault.assetsTotal != zero)
2836 {
2837 JLOG(j.fatal()) << "Invariant failed: deleted vault must have no "
2838 "assets outstanding";
2839 result = false;
2840 }
2841 if (beforeVault.assetsAvailable != zero)
2842 {
2843 JLOG(j.fatal()) << "Invariant failed: deleted vault must have no "
2844 "assets available";
2845 result = false;
2846 }
2847
2848 return result;
2849 }
2850 else if (txnType == ttVAULT_DELETE)
2851 {
2852 JLOG(j.fatal()) << "Invariant failed: vault deletion succeeded without "
2853 "deleting a vault";
2854 XRPL_ASSERT(
2855 enforce, "ripple::ValidVault::finalize : vault deletion invariant");
2856 return !enforce; // That's all we can do here
2857 }
2858
2859 // Note, `afterVault_.empty()` is handled above
2860 auto const& afterVault = afterVault_[0];
2861 XRPL_ASSERT(
2862 beforeVault_.empty() || beforeVault_[0].key == afterVault.key,
2863 "ripple::ValidVault::finalize : single vault operation");
2864
2865 auto const updatedShares = [&]() -> std::optional<Shares> {
2866 // At this moment we only know that a vault is being updated and there
2867 // might be some MPTokenIssuance objects which are also updated in the
2868 // same transaction. Find the one matching the shares to this vault.
2869 // Note, we expect updatedMPTs collection to be extremely small. For
2870 // such collections linear search is faster than lookup.
2871 for (auto const& e : afterMPTs_)
2872 {
2873 if (e.share.getMptID() == afterVault.shareMPTID)
2874 return e;
2875 }
2876
2877 auto const sleShares =
2878 view.read(keylet::mptIssuance(afterVault.shareMPTID));
2879
2880 return sleShares ? std::optional<Shares>(Shares::make(*sleShares))
2881 : std::nullopt;
2882 }();
2883
2884 bool result = true;
2885
2886 // Universal transaction checks
2887 if (!beforeVault_.empty())
2888 {
2889 auto const& beforeVault = beforeVault_[0];
2890 if (afterVault.asset != beforeVault.asset ||
2891 afterVault.pseudoId != beforeVault.pseudoId ||
2892 afterVault.shareMPTID != beforeVault.shareMPTID)
2893 {
2894 JLOG(j.fatal())
2895 << "Invariant failed: violation of vault immutable data";
2896 result = false;
2897 }
2898 }
2899
2900 if (!updatedShares)
2901 {
2902 JLOG(j.fatal()) << "Invariant failed: updated vault must have shares";
2903 XRPL_ASSERT(
2904 enforce,
2905 "ripple::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, "ripple::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, "ripple::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 // Technically this does not need to be a lambda, but it's more
3072 // convenient thanks to early "return false"; the not-so-nice
3073 // alternatives are several layers of nested if/else or more complex
3074 // (i.e. brittle) if statements.
3075 result &= [&]() {
3076 switch (txnType)
3077 {
3078 case ttVAULT_CREATE: {
3079 bool result = true;
3080
3081 if (!beforeVault_.empty())
3082 {
3083 JLOG(j.fatal()) //
3084 << "Invariant failed: create operation must not have "
3085 "updated a vault";
3086 result = false;
3087 }
3088
3089 if (afterVault.assetsAvailable != zero ||
3090 afterVault.assetsTotal != zero ||
3091 afterVault.lossUnrealized != zero ||
3092 updatedShares->sharesTotal != 0)
3093 {
3094 JLOG(j.fatal()) //
3095 << "Invariant failed: created vault must be empty";
3096 result = false;
3097 }
3098
3099 if (afterVault.pseudoId != updatedShares->share.getIssuer())
3100 {
3101 JLOG(j.fatal()) //
3102 << "Invariant failed: shares issuer and vault "
3103 "pseudo-account must be the same";
3104 result = false;
3105 }
3106
3107 auto const sleSharesIssuer = view.read(
3108 keylet::account(updatedShares->share.getIssuer()));
3109 if (!sleSharesIssuer)
3110 {
3111 JLOG(j.fatal()) //
3112 << "Invariant failed: shares issuer must exist";
3113 return false;
3114 }
3115
3116 if (!isPseudoAccount(sleSharesIssuer))
3117 {
3118 JLOG(j.fatal()) //
3119 << "Invariant failed: shares issuer must be a "
3120 "pseudo-account";
3121 result = false;
3122 }
3123
3124 if (auto const vaultId = (*sleSharesIssuer)[~sfVaultID];
3125 !vaultId || *vaultId != afterVault.key)
3126 {
3127 JLOG(j.fatal()) //
3128 << "Invariant failed: shares issuer pseudo-account "
3129 "must point back to the vault";
3130 result = false;
3131 }
3132
3133 return result;
3134 }
3135 case ttVAULT_SET: {
3136 bool result = true;
3137
3138 XRPL_ASSERT(
3139 !beforeVault_.empty(),
3140 "ripple::ValidVault::finalize : set updated a vault");
3141 auto const& beforeVault = beforeVault_[0];
3142
3143 auto const vaultDeltaAssets = deltaAssets(afterVault.pseudoId);
3144 if (vaultDeltaAssets)
3145 {
3146 JLOG(j.fatal()) << //
3147 "Invariant failed: set must not change vault balance";
3148 result = false;
3149 }
3150
3151 if (beforeVault.assetsTotal != afterVault.assetsTotal)
3152 {
3153 JLOG(j.fatal()) << //
3154 "Invariant failed: set must not change assets "
3155 "outstanding";
3156 result = false;
3157 }
3158
3159 if (afterVault.assetsMaximum > zero &&
3160 afterVault.assetsTotal > afterVault.assetsMaximum)
3161 {
3162 JLOG(j.fatal()) << //
3163 "Invariant failed: set assets outstanding must not "
3164 "exceed assets maximum";
3165 result = false;
3166 }
3167
3168 if (beforeVault.assetsAvailable != afterVault.assetsAvailable)
3169 {
3170 JLOG(j.fatal()) << //
3171 "Invariant failed: set must not change assets "
3172 "available";
3173 result = false;
3174 }
3175
3176 if (beforeShares && updatedShares &&
3177 beforeShares->sharesTotal != updatedShares->sharesTotal)
3178 {
3179 JLOG(j.fatal()) << //
3180 "Invariant failed: set must not change shares "
3181 "outstanding";
3182 result = false;
3183 }
3184
3185 return result;
3186 }
3187 case ttVAULT_DEPOSIT: {
3188 bool result = true;
3189
3190 XRPL_ASSERT(
3191 !beforeVault_.empty(),
3192 "ripple::ValidVault::finalize : deposit updated a vault");
3193 auto const& beforeVault = beforeVault_[0];
3194
3195 auto const vaultDeltaAssets = deltaAssets(afterVault.pseudoId);
3196
3197 if (!vaultDeltaAssets)
3198 {
3199 JLOG(j.fatal()) << //
3200 "Invariant failed: deposit must change vault balance";
3201 return false; // That's all we can do
3202 }
3203
3204 if (*vaultDeltaAssets > tx[sfAmount])
3205 {
3206 JLOG(j.fatal()) << //
3207 "Invariant failed: deposit must not change vault "
3208 "balance by more than deposited amount";
3209 result = false;
3210 }
3211
3212 if (*vaultDeltaAssets <= zero)
3213 {
3214 JLOG(j.fatal()) << //
3215 "Invariant failed: deposit must increase vault balance";
3216 result = false;
3217 }
3218
3219 // Any payments (including deposits) made by the issuer
3220 // do not change their balance, but create funds instead.
3221 bool const issuerDeposit = [&]() -> bool {
3222 if (vaultAsset.native())
3223 return false;
3224 return tx[sfAccount] == vaultAsset.getIssuer();
3225 }();
3226
3227 if (!issuerDeposit)
3228 {
3229 auto const accountDeltaAssets = deltaAssetsTxAccount();
3230 if (!accountDeltaAssets)
3231 {
3232 JLOG(j.fatal()) << //
3233 "Invariant failed: deposit must change depositor "
3234 "balance";
3235 return false;
3236 }
3237
3238 if (*accountDeltaAssets >= zero)
3239 {
3240 JLOG(j.fatal()) << //
3241 "Invariant failed: deposit must decrease depositor "
3242 "balance";
3243 result = false;
3244 }
3245
3246 if (*accountDeltaAssets * -1 != *vaultDeltaAssets)
3247 {
3248 JLOG(j.fatal()) << //
3249 "Invariant failed: deposit must change vault and "
3250 "depositor balance by equal amount";
3251 result = false;
3252 }
3253 }
3254
3255 if (afterVault.assetsMaximum > zero &&
3256 afterVault.assetsTotal > afterVault.assetsMaximum)
3257 {
3258 JLOG(j.fatal()) << //
3259 "Invariant failed: deposit assets outstanding must not "
3260 "exceed assets maximum";
3261 result = false;
3262 }
3263
3264 auto const accountDeltaShares = deltaShares(tx[sfAccount]);
3265 if (!accountDeltaShares)
3266 {
3267 JLOG(j.fatal()) << //
3268 "Invariant failed: deposit must change depositor "
3269 "shares";
3270 return false; // That's all we can do
3271 }
3272
3273 if (*accountDeltaShares <= zero)
3274 {
3275 JLOG(j.fatal()) << //
3276 "Invariant failed: deposit must increase depositor "
3277 "shares";
3278 result = false;
3279 }
3280
3281 auto const vaultDeltaShares = deltaShares(afterVault.pseudoId);
3282 if (!vaultDeltaShares || *vaultDeltaShares == zero)
3283 {
3284 JLOG(j.fatal()) << //
3285 "Invariant failed: deposit must change vault shares";
3286 return false; // That's all we can do
3287 }
3288
3289 if (*vaultDeltaShares * -1 != *accountDeltaShares)
3290 {
3291 JLOG(j.fatal()) << //
3292 "Invariant failed: deposit must change depositor and "
3293 "vault shares by equal amount";
3294 result = false;
3295 }
3296
3297 if (beforeVault.assetsTotal + *vaultDeltaAssets !=
3298 afterVault.assetsTotal)
3299 {
3300 JLOG(j.fatal()) << "Invariant failed: deposit and assets "
3301 "outstanding must add up";
3302 result = false;
3303 }
3304 if (beforeVault.assetsAvailable + *vaultDeltaAssets !=
3305 afterVault.assetsAvailable)
3306 {
3307 JLOG(j.fatal()) << "Invariant failed: deposit and assets "
3308 "available must add up";
3309 result = false;
3310 }
3311
3312 return result;
3313 }
3314 case ttVAULT_WITHDRAW: {
3315 bool result = true;
3316
3317 XRPL_ASSERT(
3318 !beforeVault_.empty(),
3319 "ripple::ValidVault::finalize : withdrawal updated a "
3320 "vault");
3321 auto const& beforeVault = beforeVault_[0];
3322
3323 auto const vaultDeltaAssets = deltaAssets(afterVault.pseudoId);
3324
3325 if (!vaultDeltaAssets)
3326 {
3327 JLOG(j.fatal()) << "Invariant failed: withdrawal must "
3328 "change vault balance";
3329 return false; // That's all we can do
3330 }
3331
3332 if (*vaultDeltaAssets >= zero)
3333 {
3334 JLOG(j.fatal()) << "Invariant failed: withdrawal must "
3335 "decrease vault balance";
3336 result = false;
3337 }
3338
3339 // Any payments (including withdrawal) going to the issuer
3340 // do not change their balance, but destroy funds instead.
3341 bool const issuerWithdrawal = [&]() -> bool {
3342 if (vaultAsset.native())
3343 return false;
3344 auto const destination =
3345 tx[~sfDestination].value_or(tx[sfAccount]);
3346 return destination == vaultAsset.getIssuer();
3347 }();
3348
3349 if (!issuerWithdrawal)
3350 {
3351 auto const accountDeltaAssets = deltaAssetsTxAccount();
3352 auto const otherAccountDelta =
3353 [&]() -> std::optional<Number> {
3354 if (auto const destination = tx[~sfDestination];
3355 destination && *destination != tx[sfAccount])
3356 return deltaAssets(*destination);
3357 return std::nullopt;
3358 }();
3359
3360 if (accountDeltaAssets.has_value() ==
3361 otherAccountDelta.has_value())
3362 {
3363 JLOG(j.fatal()) << //
3364 "Invariant failed: withdrawal must change one "
3365 "destination balance";
3366 return false;
3367 }
3368
3369 auto const destinationDelta = //
3370 accountDeltaAssets ? *accountDeltaAssets
3371 : *otherAccountDelta;
3372
3373 if (destinationDelta <= zero)
3374 {
3375 JLOG(j.fatal()) << //
3376 "Invariant failed: withdrawal must increase "
3377 "destination balance";
3378 result = false;
3379 }
3380
3381 if (*vaultDeltaAssets * -1 != destinationDelta)
3382 {
3383 JLOG(j.fatal()) << //
3384 "Invariant failed: withdrawal must change vault "
3385 "and destination balance by equal amount";
3386 result = false;
3387 }
3388 }
3389
3390 auto const accountDeltaShares = deltaShares(tx[sfAccount]);
3391 if (!accountDeltaShares)
3392 {
3393 JLOG(j.fatal()) << //
3394 "Invariant failed: withdrawal must change depositor "
3395 "shares";
3396 return false;
3397 }
3398
3399 if (*accountDeltaShares >= zero)
3400 {
3401 JLOG(j.fatal()) << //
3402 "Invariant failed: withdrawal must decrease depositor "
3403 "shares";
3404 result = false;
3405 }
3406
3407 auto const vaultDeltaShares = deltaShares(afterVault.pseudoId);
3408 if (!vaultDeltaShares || *vaultDeltaShares == zero)
3409 {
3410 JLOG(j.fatal()) << //
3411 "Invariant failed: withdrawal must change vault shares";
3412 return false; // That's all we can do
3413 }
3414
3415 if (*vaultDeltaShares * -1 != *accountDeltaShares)
3416 {
3417 JLOG(j.fatal()) << //
3418 "Invariant failed: withdrawal must change depositor "
3419 "and vault shares by equal amount";
3420 result = false;
3421 }
3422
3423 // Note, vaultBalance is negative (see check above)
3424 if (beforeVault.assetsTotal + *vaultDeltaAssets !=
3425 afterVault.assetsTotal)
3426 {
3427 JLOG(j.fatal()) << "Invariant failed: withdrawal and "
3428 "assets outstanding must add up";
3429 result = false;
3430 }
3431
3432 if (beforeVault.assetsAvailable + *vaultDeltaAssets !=
3433 afterVault.assetsAvailable)
3434 {
3435 JLOG(j.fatal()) << "Invariant failed: withdrawal and "
3436 "assets available must add up";
3437 result = false;
3438 }
3439
3440 return result;
3441 }
3442 case ttVAULT_CLAWBACK: {
3443 bool result = true;
3444
3445 XRPL_ASSERT(
3446 !beforeVault_.empty(),
3447 "ripple::ValidVault::finalize : clawback updated a vault");
3448 auto const& beforeVault = beforeVault_[0];
3449
3450 if (vaultAsset.native() ||
3451 vaultAsset.getIssuer() != tx[sfAccount])
3452 {
3453 JLOG(j.fatal()) << //
3454 "Invariant failed: clawback may only be performed by "
3455 "the asset issuer";
3456 return false; // That's all we can do
3457 }
3458
3459 auto const vaultDeltaAssets = deltaAssets(afterVault.pseudoId);
3460
3461 if (!vaultDeltaAssets)
3462 {
3463 JLOG(j.fatal()) << //
3464 "Invariant failed: clawback must change vault balance";
3465 return false; // That's all we can do
3466 }
3467
3468 if (*vaultDeltaAssets >= zero)
3469 {
3470 JLOG(j.fatal()) << //
3471 "Invariant failed: clawback must decrease vault "
3472 "balance";
3473 result = false;
3474 }
3475
3476 auto const accountDeltaShares = deltaShares(tx[sfHolder]);
3477 if (!accountDeltaShares)
3478 {
3479 JLOG(j.fatal()) << //
3480 "Invariant failed: clawback must change holder shares";
3481 return false; // That's all we can do
3482 }
3483
3484 if (*accountDeltaShares >= zero)
3485 {
3486 JLOG(j.fatal()) << //
3487 "Invariant failed: clawback must decrease holder "
3488 "shares";
3489 result = false;
3490 }
3491
3492 auto const vaultDeltaShares = deltaShares(afterVault.pseudoId);
3493 if (!vaultDeltaShares || *vaultDeltaShares == zero)
3494 {
3495 JLOG(j.fatal()) << //
3496 "Invariant failed: clawback must change vault shares";
3497 return false; // That's all we can do
3498 }
3499
3500 if (*vaultDeltaShares * -1 != *accountDeltaShares)
3501 {
3502 JLOG(j.fatal()) << //
3503 "Invariant failed: clawback must change holder and "
3504 "vault shares by equal amount";
3505 result = false;
3506 }
3507
3508 if (beforeVault.assetsTotal + *vaultDeltaAssets !=
3509 afterVault.assetsTotal)
3510 {
3511 JLOG(j.fatal()) << //
3512 "Invariant failed: clawback and assets outstanding "
3513 "must add up";
3514 result = false;
3515 }
3516
3517 if (beforeVault.assetsAvailable + *vaultDeltaAssets !=
3518 afterVault.assetsAvailable)
3519 {
3520 JLOG(j.fatal()) << //
3521 "Invariant failed: clawback and assets available must "
3522 "add up";
3523 result = false;
3524 }
3525
3526 return result;
3527 }
3528
3529 case ttLOAN_SET:
3530 case ttLOAN_MANAGE:
3531 case ttLOAN_PAY: {
3532 // TBD
3533 return true;
3534 }
3535
3536 default:
3537 // LCOV_EXCL_START
3538 UNREACHABLE(
3539 "ripple::ValidVault::finalize : unknown transaction type");
3540 return false;
3541 // LCOV_EXCL_STOP
3542 }
3543 }();
3544
3545 if (!result)
3546 {
3547 // The comment at the top of this file starting with "assert(enforce)"
3548 // explains this assert.
3549 XRPL_ASSERT(enforce, "ripple::ValidVault::finalize : vault invariants");
3550 return !enforce;
3551 }
3552
3553 return true;
3554}
3555
3556} // namespace ripple
T begin(T... args)
A generic endpoint for log messages.
Definition Journal.h:41
Stream fatal() const
Definition Journal.h:333
Stream error() const
Definition Journal.h:327
Stream debug() const
Definition Journal.h:309
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
std::vector< std::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 &)
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
A currency issued by an account.
Definition Issue.h:14
Item const * findByType(KeyType type) const
Retrieve a format based on its type.
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
static LedgerFormats const & getInstance()
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
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 &)
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
A view into a ledger.
Definition ReadView.h:32
virtual std::shared_ptr< SLE const > read(Keylet const &k) const =0
Return the state item associated with a key.
virtual std::optional< key_type > succ(key_type const &key, std::optional< key_type > const &last=std::nullopt) const =0
Return the key of the next state item.
virtual bool exists(Keylet const &k) const =0
Determine if a state item exists.
LedgerIndex seq() const
Returns the sequence number of the base ledger.
Definition ReadView.h:99
virtual Rules const & rules() const =0
Returns the tx processing rules.
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition Rules.cpp:111
Identifies fields.
Definition SField.h:127
XRPAmount xrp() const
Definition STAmount.cpp:264
int signum() const noexcept
Definition STAmount.h:506
bool native() const noexcept
Definition STAmount.h:450
STAmount zeroed() const
Returns a zero value with the same issuer and currency.
Definition STAmount.h:512
LedgerEntryType getType() const
uint256 const & key() const
Returns the 'key' (or 'index') of this item.
uint192 getFieldH192(SField const &field) const
Definition STObject.cpp:620
AccountID getAccountID(SField const &field) const
Definition STObject.cpp:638
T::value_type at(TypedField< T > const &f) const
Get the value of a field.
Definition STObject.h:1055
std::uint32_t getFieldU32(SField const &field) const
Definition STObject.cpp:596
STAmount const & getFieldAmount(SField const &field) const
Definition STObject.cpp:652
uint256 getHash(HashPrefix prefix) const
Definition STObject.cpp:376
bool isFieldPresent(SField const &field) const
Definition STObject.cpp:465
uint256 getFieldH256(SField const &field) const
Definition STObject.cpp:626
TxType getTxnType() const
Definition STTx.h:187
uint256 getTransactionID() const
Definition STTx.h:199
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
std::map< AccountID, std::shared_ptr< SLE const > const > possibleIssuers_
bool isValidEntry(std::shared_ptr< SLE const > const &before, std::shared_ptr< SLE const > const &after)
void recordBalance(Issue const &issue, BalanceChange change)
std::shared_ptr< SLE const > findIssuer(AccountID const &issuerID, ReadView const &view)
bool validateIssuerChanges(std::shared_ptr< SLE const > const &issuer, IssuerChanges const &changes, STTx const &tx, beast::Journal const &j, bool enforce)
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
STAmount calculateBalanceChange(std::shared_ptr< SLE const > const &before, std::shared_ptr< SLE const > const &after, bool isDelete)
void recordBalanceChanges(std::shared_ptr< SLE const > const &after, STAmount const &balanceChange)
bool validateFrozenState(BalanceChange const &change, bool high, STTx const &tx, beast::Journal const &j, bool enforce, bool globalFreeze)
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
bool finalizeWithdraw(STTx const &, ReadView const &, bool enforce, beast::Journal const &) const
bool finalizeDEX(bool enforce, beast::Journal const &) const
std::optional< STAmount > lptAMMBalanceAfter_
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
bool finalizeBid(bool enforce, beast::Journal const &) const
std::optional< AccountID > ammAccount_
bool finalizeDelete(bool enforce, TER res, beast::Journal const &) const
bool finalizeCreate(STTx const &, ReadView const &, bool enforce, beast::Journal const &) const
bool finalizeVote(bool enforce, beast::Journal const &) const
bool finalizeDeposit(STTx const &, ReadView const &, bool enforce, beast::Journal const &) const
bool generalInvariant(STTx const &, ReadView const &, ZeroAllowed zeroAllowed, beast::Journal const &) const
std::optional< STAmount > lptAMMBalanceBefore_
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
std::uint32_t trustlinesChanged
std::uint32_t mptokensChanged
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
std::vector< SLE::const_pointer > lines_
bool goodZeroDirectory(ReadView const &view, SLE::const_ref dir, beast::Journal const &j) const
std::map< uint256, BrokerInfo > brokers_
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
std::vector< SLE::const_pointer > mpts_
std::vector< std::pair< SLE::const_pointer, SLE::const_pointer > > loans_
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 mptIssuancesCreated_
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
std::uint32_t mptIssuancesDeleted_
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
hash_set< uint256 > domains_
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
std::optional< SleStatus > sleStatus_[2]
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
std::vector< std::string > errors_
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
std::vector< Shares > beforeMPTs_
std::vector< Vault > beforeVault_
std::unordered_map< uint256, Number > deltas_
static Number constexpr zero
std::vector< Shares > afterMPTs_
std::vector< Vault > afterVault_
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
constexpr value_type drops() const
Returns the number of drops.
Definition XRPAmount.h:158
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
base_uint next() const
Definition base_uint.h:436
T count_if(T... args)
T emplace_back(T... args)
T empty(T... args)
T end(T... args)
T invoke(T... args)
T is_same_v
std::set< std::pair< AccountID, Slice > > makeSorted(STArray const &credentials)
Keylet mptoken(MPTID const &issuanceID, AccountID const &holder) noexcept
Definition Indexes.cpp:523
Keylet permissionedDomain(AccountID const &account, std::uint32_t seq) noexcept
Definition Indexes.cpp:565
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:227
Keylet mptIssuance(std::uint32_t seq, AccountID const &issuer) noexcept
Definition Indexes.cpp:509
Keylet vault(AccountID const &owner, std::uint32_t seq) noexcept
Definition Indexes.cpp:547
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:167
Keylet unchecked(uint256 const &key) noexcept
Any ledger entry.
Definition Indexes.cpp:351
Keylet nftpage_min(AccountID const &owner)
NFT page keylets.
Definition Indexes.cpp:386
Keylet nftpage_max(AccountID const &owner)
A keylet for the owner's last possible NFT page.
Definition Indexes.cpp:394
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition Indexes.cpp:357
Keylet loanbroker(AccountID const &owner, std::uint32_t seq) noexcept
Definition Indexes.cpp:553
bool compareTokens(uint256 const &a, uint256 const &b)
uint256 constexpr pageMask(std::string_view("0000000000000000000000000000000000000000ffffffffffffffffffffffff"))
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
Definition Issue.h:96
Currency const & badCurrency()
We deliberately disallow the currency that looks like "XRP" because too many people were using it ins...
bool isPseudoAccount(std::shared_ptr< SLE const > sleAcct, std::set< SField const * > const &pseudoFieldFilter={})
Definition View.cpp:1226
@ fhIGNORE_FREEZE
Definition View.h:59
bool isXRP(AccountID const &c)
Definition AccountID.h:71
constexpr base_uint< Bits, Tag > operator|(base_uint< Bits, Tag > const &a, base_uint< Bits, Tag > const &b)
Definition base_uint.h:596
static bool validBalances(STAmount const &amount, STAmount const &amount2, STAmount const &lptAMMBalance, ValidAMM::ZeroAllowed zeroAllowed)
base_uint< 256 > uint256
Definition base_uint.h:539
std::size_t constexpr maxPermissionedDomainCredentialsArraySize
The maximum number of credentials can be passed in array for permissioned domain.
Definition Protocol.h:229
bool hasPrivilege(STTx const &tx, Privilege priv)
constexpr std::enable_if_t< std::is_integral_v< Dest > &&std::is_integral_v< Src >, Dest > safe_cast(Src s) noexcept
Definition safe_cast.h:22
std::uint64_t constexpr maxMPTokenAmount
The maximum amount of MPTokenIssuance.
Definition Protocol.h:235
@ lsfHighDeepFreeze
@ lsfDefaultRipple
@ lsfLoanOverpayment
@ lsfDisableMaster
@ lsfGlobalFreeze
@ lsfLowDeepFreeze
constexpr XRPAmount INITIAL_XRP
Configure the native currency.
@ ahIGNORE_AUTH
Definition View.h:62
std::size_t constexpr dirMaxTokensPerPage
The maximum number of items in an NFT page.
Definition Protocol.h:47
Buffer sign(PublicKey const &pk, SecretKey const &sk, Slice const &message)
Generate a signature for a message.
std::array< keyletDesc< AccountID const & >, 6 > const directAccountKeylets
Definition Indexes.h:383
std::pair< STAmount, STAmount > ammPoolHolds(ReadView const &view, AccountID const &ammAccountID, Issue const &issue1, Issue const &issue2, FreezeHandling freezeHandling, beast::Journal const j)
Get AMM pool balances.
Definition AMMUtils.cpp:12
@ tecINCOMPLETE
Definition TER.h:317
@ tesSUCCESS
Definition TER.h:226
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j)
Definition View.cpp:461
bool isTesSuccess(TER x) noexcept
Definition TER.h:659
STAmount ammLPTokens(STAmount const &asset1, STAmount const &asset2, Issue const &lptIssue)
Calculate LP Tokens given AMM pool reserves.
Definition AMMHelpers.cpp:6
T get(Section const &section, std::string const &name, T const &defaultValue=T{})
Retrieve a key/value pair from a section.
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition View.cpp:3923
@ transactionID
transaction plus signature to give transaction ID
MPTID makeMptID(std::uint32_t sequence, AccountID const &account)
Definition Indexes.cpp:153
std::vector< SField const * > const & getPseudoAccountFields()
Definition View.cpp:1199
bool withinRelativeDistance(Quality const &calcQuality, Quality const &reqQuality, Number const &dist)
Check if the relative distance between the qualities is within the requested distance.
Definition AMMHelpers.h:110
Number root2(Number f)
Definition Number.cpp:710
A pair of SHAMap key and LedgerEntryType.
Definition Keylet.h:20
uint256 key
Definition Keylet.h:21
std::shared_ptr< SLE const > const line
std::vector< BalanceChange > receivers
std::vector< BalanceChange > senders
static Shares make(SLE const &)
static Vault make(SLE const &)
T to_string(T... args)
T value_or(T... args)
T visit(T... args)