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