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/ledger/CredentialHelpers.h>
28#include <xrpl/ledger/ReadView.h>
29#include <xrpl/ledger/View.h>
30#include <xrpl/protocol/Feature.h>
31#include <xrpl/protocol/STArray.h>
32#include <xrpl/protocol/STNumber.h>
33#include <xrpl/protocol/SystemParameters.h>
34#include <xrpl/protocol/TxFormats.h>
35#include <xrpl/protocol/Units.h>
36#include <xrpl/protocol/nftPageMask.h>
37
38namespace ripple {
39
40/*
41assert(enforce)
42
43There are several asserts (or XRPL_ASSERTs) in this file that check a variable
44named `enforce` when an invariant fails. At first glance, those asserts may look
45incorrect, but they are not.
46
47Those asserts take advantage of two facts:
481. `asserts` are not (normally) executed in release builds.
492. Invariants should *never* fail, except in tests that specifically modify
50 the open ledger to break them.
51
52This makes `assert(enforce)` sort of a second-layer of invariant enforcement
53aimed at _developers_. It's designed to fire if a developer writes code that
54violates an invariant, and runs it in unit tests or a develop build that _does
55not have the relevant amendments enabled_. It's intentionally a pain in the neck
56so that bad code gets caught and fixed as early as possible.
57*/
58
60 noPriv =
61 0x0000, // The transaction can not do any of the enumerated operations
63 0x0001, // The transaction can create a new ACCOUNT_ROOT object.
64 createPseudoAcct = 0x0002, // The transaction can create a pseudo account,
65 // which implies createAcct
67 0x0004, // The transaction must delete an ACCOUNT_ROOT object
68 mayDeleteAcct = 0x0008, // The transaction may delete an ACCOUNT_ROOT
69 // object, but does not have to
70 overrideFreeze = 0x0010, // The transaction can override some freeze rules
71 changeNFTCounts = 0x0020, // The transaction can mint or burn an NFT
73 0x0040, // The transaction can create a new MPT issuance
74 destroyMPTIssuance = 0x0080, // The transaction can destroy an MPT issuance
75 mustAuthorizeMPT = 0x0100, // The transaction MUST create or delete an MPT
76 // object (except by issuer)
77 mayAuthorizeMPT = 0x0200, // The transaction MAY create or delete an MPT
78 // object (except by issuer)
80 0x0400, // The transaction MAY delete an MPT object. May not create.
81};
82constexpr Privilege
84{
85 return safe_cast<Privilege>(
88}
89
90#pragma push_macro("TRANSACTION")
91#undef TRANSACTION
92
93#define TRANSACTION(tag, value, name, delegatable, amendment, privileges, ...) \
94 case tag: { \
95 return (privileges) & priv; \
96 }
97
98bool
99hasPrivilege(STTx const& tx, Privilege priv)
100{
101 switch (tx.getTxnType())
102 {
103#include <xrpl/protocol/detail/transactions.macro>
104 // Deprecated types
105 default:
106 return false;
107 }
108};
109
110#undef TRANSACTION
111#pragma pop_macro("TRANSACTION")
112
113void
115 bool,
118{
119 // nothing to do
120}
121
122bool
124 STTx const& tx,
125 TER const,
126 XRPAmount const fee,
127 ReadView const&,
128 beast::Journal const& j)
129{
130 // We should never charge a negative fee
131 if (fee.drops() < 0)
132 {
133 JLOG(j.fatal()) << "Invariant failed: fee paid was negative: "
134 << fee.drops();
135 return false;
136 }
137
138 // We should never charge a fee that's greater than or equal to the
139 // entire XRP supply.
140 if (fee >= INITIAL_XRP)
141 {
142 JLOG(j.fatal()) << "Invariant failed: fee paid exceeds system limit: "
143 << fee.drops();
144 return false;
145 }
146
147 // We should never charge more for a transaction than the transaction
148 // authorizes. It's possible to charge less in some circumstances.
149 if (fee > tx.getFieldAmount(sfFee).xrp())
150 {
151 JLOG(j.fatal()) << "Invariant failed: fee paid is " << fee.drops()
152 << " exceeds fee specified in transaction.";
153 return false;
154 }
155
156 return true;
157}
158
159//------------------------------------------------------------------------------
160
161void
163 bool isDelete,
164 std::shared_ptr<SLE const> const& before,
166{
167 /* We go through all modified ledger entries, looking only at account roots,
168 * escrow payments, and payment channels. We remove from the total any
169 * previous XRP values and add to the total any new XRP values. The net
170 * balance of a payment channel is computed from two fields (amount and
171 * balance) and deletions are ignored for paychan and escrow because the
172 * amount fields have not been adjusted for those in the case of deletion.
173 */
174 if (before)
175 {
176 switch (before->getType())
177 {
178 case ltACCOUNT_ROOT:
179 drops_ -= (*before)[sfBalance].xrp().drops();
180 break;
181 case ltPAYCHAN:
182 drops_ -=
183 ((*before)[sfAmount] - (*before)[sfBalance]).xrp().drops();
184 break;
185 case ltESCROW:
186 if (isXRP((*before)[sfAmount]))
187 drops_ -= (*before)[sfAmount].xrp().drops();
188 break;
189 default:
190 break;
191 }
192 }
193
194 if (after)
195 {
196 switch (after->getType())
197 {
198 case ltACCOUNT_ROOT:
199 drops_ += (*after)[sfBalance].xrp().drops();
200 break;
201 case ltPAYCHAN:
202 if (!isDelete)
203 drops_ += ((*after)[sfAmount] - (*after)[sfBalance])
204 .xrp()
205 .drops();
206 break;
207 case ltESCROW:
208 if (!isDelete && isXRP((*after)[sfAmount]))
209 drops_ += (*after)[sfAmount].xrp().drops();
210 break;
211 default:
212 break;
213 }
214 }
215}
216
217bool
219 STTx const& tx,
220 TER const,
221 XRPAmount const fee,
222 ReadView const&,
223 beast::Journal const& j)
224{
225 // The net change should never be positive, as this would mean that the
226 // transaction created XRP out of thin air. That's not possible.
227 if (drops_ > 0)
228 {
229 JLOG(j.fatal()) << "Invariant failed: XRP net change was positive: "
230 << drops_;
231 return false;
232 }
233
234 // The negative of the net change should be equal to actual fee charged.
235 if (-drops_ != fee.drops())
236 {
237 JLOG(j.fatal()) << "Invariant failed: XRP net change of " << drops_
238 << " doesn't match fee " << fee.drops();
239 return false;
240 }
241
242 return true;
243}
244
245//------------------------------------------------------------------------------
246
247void
249 bool,
250 std::shared_ptr<SLE const> const& before,
252{
253 auto isBad = [](STAmount const& balance) {
254 if (!balance.native())
255 return true;
256
257 auto const drops = balance.xrp();
258
259 // Can't have more than the number of drops instantiated
260 // in the genesis ledger.
261 if (drops > INITIAL_XRP)
262 return true;
263
264 // Can't have a negative balance (0 is OK)
265 if (drops < XRPAmount{0})
266 return true;
267
268 return false;
269 };
270
271 if (before && before->getType() == ltACCOUNT_ROOT)
272 bad_ |= isBad((*before)[sfBalance]);
273
274 if (after && after->getType() == ltACCOUNT_ROOT)
275 bad_ |= isBad((*after)[sfBalance]);
276}
277
278bool
280 STTx const&,
281 TER const,
282 XRPAmount const,
283 ReadView const&,
284 beast::Journal const& j)
285{
286 if (bad_)
287 {
288 JLOG(j.fatal()) << "Invariant failed: incorrect account XRP balance";
289 return false;
290 }
291
292 return true;
293}
294
295//------------------------------------------------------------------------------
296
297void
299 bool isDelete,
300 std::shared_ptr<SLE const> const& before,
302{
303 auto isBad = [](STAmount const& pays, STAmount const& gets) {
304 // An offer should never be negative
305 if (pays < beast::zero)
306 return true;
307
308 if (gets < beast::zero)
309 return true;
310
311 // Can't have an XRP to XRP offer:
312 return pays.native() && gets.native();
313 };
314
315 if (before && before->getType() == ltOFFER)
316 bad_ |= isBad((*before)[sfTakerPays], (*before)[sfTakerGets]);
317
318 if (after && after->getType() == ltOFFER)
319 bad_ |= isBad((*after)[sfTakerPays], (*after)[sfTakerGets]);
320}
321
322bool
324 STTx const&,
325 TER const,
326 XRPAmount const,
327 ReadView const&,
328 beast::Journal const& j)
329{
330 if (bad_)
331 {
332 JLOG(j.fatal()) << "Invariant failed: offer with a bad amount";
333 return false;
334 }
335
336 return true;
337}
338
339//------------------------------------------------------------------------------
340
341void
343 bool isDelete,
344 std::shared_ptr<SLE const> const& before,
346{
347 auto isBad = [](STAmount const& amount) {
348 // XRP case
349 if (amount.native())
350 {
351 if (amount.xrp() <= XRPAmount{0})
352 return true;
353
354 if (amount.xrp() >= INITIAL_XRP)
355 return true;
356 }
357 else
358 {
359 // IOU case
360 if (amount.holds<Issue>())
361 {
362 if (amount <= beast::zero)
363 return true;
364
365 if (badCurrency() == amount.getCurrency())
366 return true;
367 }
368
369 // MPT case
370 if (amount.holds<MPTIssue>())
371 {
372 if (amount <= beast::zero)
373 return true;
374
375 if (amount.mpt() > MPTAmount{maxMPTokenAmount})
376 return true; // LCOV_EXCL_LINE
377 }
378 }
379 return false;
380 };
381
382 if (before && before->getType() == ltESCROW)
383 bad_ |= isBad((*before)[sfAmount]);
384
385 if (after && after->getType() == ltESCROW)
386 bad_ |= isBad((*after)[sfAmount]);
387
388 auto checkAmount = [this](std::int64_t amount) {
389 if (amount > maxMPTokenAmount || amount < 0)
390 bad_ = true;
391 };
392
393 if (after && after->getType() == ltMPTOKEN_ISSUANCE)
394 {
395 auto const outstanding = (*after)[sfOutstandingAmount];
396 checkAmount(outstanding);
397 if (auto const locked = (*after)[~sfLockedAmount])
398 {
399 checkAmount(*locked);
400 bad_ = outstanding < *locked;
401 }
402 }
403
404 if (after && after->getType() == ltMPTOKEN)
405 {
406 auto const mptAmount = (*after)[sfMPTAmount];
407 checkAmount(mptAmount);
408 if (auto const locked = (*after)[~sfLockedAmount])
409 {
410 checkAmount(*locked);
411 }
412 }
413}
414
415bool
417 STTx const& txn,
418 TER const,
419 XRPAmount const,
420 ReadView const& rv,
421 beast::Journal const& j)
422{
423 if (bad_)
424 {
425 JLOG(j.fatal()) << "Invariant failed: escrow specifies invalid amount";
426 return false;
427 }
428
429 return true;
430}
431
432//------------------------------------------------------------------------------
433
434void
436 bool isDelete,
437 std::shared_ptr<SLE const> const& before,
439{
440 if (isDelete && before && before->getType() == ltACCOUNT_ROOT)
442}
443
444bool
446 STTx const& tx,
447 TER const result,
448 XRPAmount const,
449 ReadView const&,
450 beast::Journal const& j)
451{
452 // AMM account root can be deleted as the result of AMM withdraw/delete
453 // transaction when the total AMM LP Tokens balance goes to 0.
454 // A successful AccountDelete or AMMDelete MUST delete exactly
455 // one account root.
456 if (hasPrivilege(tx, mustDeleteAcct) && result == tesSUCCESS)
457 {
458 if (accountsDeleted_ == 1)
459 return true;
460
461 if (accountsDeleted_ == 0)
462 JLOG(j.fatal()) << "Invariant failed: account deletion "
463 "succeeded without deleting an account";
464 else
465 JLOG(j.fatal()) << "Invariant failed: account deletion "
466 "succeeded but deleted multiple accounts!";
467 return false;
468 }
469
470 // A successful AMMWithdraw/AMMClawback MAY delete one account root
471 // when the total AMM LP Tokens balance goes to 0. Not every AMM withdraw
472 // deletes the AMM account, accountsDeleted_ is set if it is deleted.
473 if (hasPrivilege(tx, mayDeleteAcct) && result == tesSUCCESS &&
474 accountsDeleted_ == 1)
475 return true;
476
477 if (accountsDeleted_ == 0)
478 return true;
479
480 JLOG(j.fatal()) << "Invariant failed: an account root was deleted";
481 return false;
482}
483
484//------------------------------------------------------------------------------
485
486void
488 bool isDelete,
489 std::shared_ptr<SLE const> const& before,
491{
492 if (isDelete && before && before->getType() == ltACCOUNT_ROOT)
493 accountsDeleted_.emplace_back(before);
494}
495
496bool
498 STTx const& tx,
499 TER const result,
500 XRPAmount const,
501 ReadView const& view,
502 beast::Journal const& j)
503{
504 // Always check for objects in the ledger, but to prevent differing
505 // transaction processing results, however unlikely, only fail if the
506 // feature is enabled. Enabled, or not, though, a fatal-level message will
507 // be logged
508 [[maybe_unused]] bool const enforce =
509 view.rules().enabled(featureInvariantsV1_1) ||
510 view.rules().enabled(featureSingleAssetVault);
511
512 auto const objectExists = [&view, enforce, &j](auto const& keylet) {
513 (void)enforce;
514 if (auto const sle = view.read(keylet))
515 {
516 // Finding the object is bad
517 auto const typeName = [&sle]() {
518 auto item =
519 LedgerFormats::getInstance().findByType(sle->getType());
520
521 if (item != nullptr)
522 return item->getName();
523 return std::to_string(sle->getType());
524 }();
525
526 JLOG(j.fatal())
527 << "Invariant failed: account deletion left behind a "
528 << typeName << " object";
529 // The comment above starting with "assert(enforce)" explains this
530 // assert.
531 XRPL_ASSERT(
532 enforce,
533 "ripple::AccountRootsDeletedClean::finalize::objectExists : "
534 "account deletion left no objects behind");
535 return true;
536 }
537 return false;
538 };
539
540 for (auto const& accountSLE : accountsDeleted_)
541 {
542 auto const accountID = accountSLE->getAccountID(sfAccount);
543 // Simple types
544 for (auto const& [keyletfunc, _, __] : directAccountKeylets)
545 {
546 if (objectExists(std::invoke(keyletfunc, accountID)) && enforce)
547 return false;
548 }
549
550 {
551 // NFT pages. ntfpage_min and nftpage_max were already explicitly
552 // checked above as entries in directAccountKeylets. This uses
553 // view.succ() to check for any NFT pages in between the two
554 // endpoints.
555 Keylet const first = keylet::nftpage_min(accountID);
556 Keylet const last = keylet::nftpage_max(accountID);
557
558 std::optional<uint256> key = view.succ(first.key, last.key.next());
559
560 // current page
561 if (key && objectExists(Keylet{ltNFTOKEN_PAGE, *key}) && enforce)
562 return false;
563 }
564
565 // If the account is a pseudo account, then the linked object must
566 // also be deleted. e.g. AMM, Vault, etc.
567 for (auto const& field : getPseudoAccountFields())
568 {
569 if (accountSLE->isFieldPresent(*field))
570 {
571 auto const key = accountSLE->getFieldH256(*field);
572 if (objectExists(keylet::unchecked(key)) && enforce)
573 return false;
574 }
575 }
576 }
577
578 return true;
579}
580
581//------------------------------------------------------------------------------
582
583void
585 bool,
586 std::shared_ptr<SLE const> const& before,
588{
589 if (before && after && before->getType() != after->getType())
590 typeMismatch_ = true;
591
592 if (after)
593 {
594#pragma push_macro("LEDGER_ENTRY")
595#undef LEDGER_ENTRY
596
597#define LEDGER_ENTRY(tag, ...) case tag:
598
599 switch (after->getType())
600 {
601#include <xrpl/protocol/detail/ledger_entries.macro>
602
603 break;
604 default:
605 invalidTypeAdded_ = true;
606 break;
607 }
608
609#undef LEDGER_ENTRY
610#pragma pop_macro("LEDGER_ENTRY")
611 }
612}
613
614bool
616 STTx const&,
617 TER const,
618 XRPAmount const,
619 ReadView const&,
620 beast::Journal const& j)
621{
622 if ((!typeMismatch_) && (!invalidTypeAdded_))
623 return true;
624
625 if (typeMismatch_)
626 {
627 JLOG(j.fatal()) << "Invariant failed: ledger entry type mismatch";
628 }
629
631 {
632 JLOG(j.fatal()) << "Invariant failed: invalid ledger entry type added";
633 }
634
635 return false;
636}
637
638//------------------------------------------------------------------------------
639
640void
642 bool,
645{
646 if (after && after->getType() == ltRIPPLE_STATE)
647 {
648 // checking the issue directly here instead of
649 // relying on .native() just in case native somehow
650 // were systematically incorrect
652 after->getFieldAmount(sfLowLimit).issue() == xrpIssue() ||
653 after->getFieldAmount(sfHighLimit).issue() == xrpIssue();
654 }
655}
656
657bool
659 STTx const&,
660 TER const,
661 XRPAmount const,
662 ReadView const&,
663 beast::Journal const& j)
664{
665 if (!xrpTrustLine_)
666 return true;
667
668 JLOG(j.fatal()) << "Invariant failed: an XRP trust line was created";
669 return false;
670}
671
672//------------------------------------------------------------------------------
673
674void
676 bool,
679{
680 if (after && after->getType() == ltRIPPLE_STATE)
681 {
682 std::uint32_t const uFlags = after->getFieldU32(sfFlags);
683 bool const lowFreeze = uFlags & lsfLowFreeze;
684 bool const lowDeepFreeze = uFlags & lsfLowDeepFreeze;
685
686 bool const highFreeze = uFlags & lsfHighFreeze;
687 bool const highDeepFreeze = uFlags & lsfHighDeepFreeze;
688
690 (lowDeepFreeze && !lowFreeze) || (highDeepFreeze && !highFreeze);
691 }
692}
693
694bool
696 STTx const&,
697 TER const,
698 XRPAmount const,
699 ReadView const&,
700 beast::Journal const& j)
701{
703 return true;
704
705 JLOG(j.fatal()) << "Invariant failed: a trust line with deep freeze flag "
706 "without normal freeze was created";
707 return false;
708}
709
710//------------------------------------------------------------------------------
711
712void
714 bool isDelete,
715 std::shared_ptr<SLE const> const& before,
717{
718 /*
719 * A trust line freeze state alone doesn't determine if a transfer is
720 * frozen. The transfer must be examined "end-to-end" because both sides of
721 * the transfer may have different freeze states and freeze impact depends
722 * on the transfer direction. This is why first we need to track the
723 * transfers using IssuerChanges senders/receivers.
724 *
725 * Only in validateIssuerChanges, after we collected all changes can we
726 * determine if the transfer is valid.
727 */
728 if (!isValidEntry(before, after))
729 {
730 return;
731 }
732
733 auto const balanceChange = calculateBalanceChange(before, after, isDelete);
734 if (balanceChange.signum() == 0)
735 {
736 return;
737 }
738
739 recordBalanceChanges(after, balanceChange);
740}
741
742bool
744 STTx const& tx,
745 TER const ter,
746 XRPAmount const fee,
747 ReadView const& view,
748 beast::Journal const& j)
749{
750 /*
751 * We check this invariant regardless of deep freeze amendment status,
752 * allowing for detection and logging of potential issues even when the
753 * amendment is disabled.
754 *
755 * If an exploit that allows moving frozen assets is discovered,
756 * we can alert operators who monitor fatal messages and trigger assert in
757 * debug builds for an early warning.
758 *
759 * In an unlikely event that an exploit is found, this early detection
760 * enables encouraging the UNL to expedite deep freeze amendment activation
761 * or deploy hotfixes via new amendments. In case of a new amendment, we'd
762 * only have to change this line setting 'enforce' variable.
763 * enforce = view.rules().enabled(featureDeepFreeze) ||
764 * view.rules().enabled(fixFreezeExploit);
765 */
766 [[maybe_unused]] bool const enforce =
767 view.rules().enabled(featureDeepFreeze);
768
769 for (auto const& [issue, changes] : balanceChanges_)
770 {
771 auto const issuerSle = findIssuer(issue.account, view);
772 // It should be impossible for the issuer to not be found, but check
773 // just in case so rippled doesn't crash in release.
774 if (!issuerSle)
775 {
776 // The comment above starting with "assert(enforce)" explains this
777 // assert.
778 XRPL_ASSERT(
779 enforce,
780 "ripple::TransfersNotFrozen::finalize : enforce "
781 "invariant.");
782 if (enforce)
783 {
784 return false;
785 }
786 continue;
787 }
788
789 if (!validateIssuerChanges(issuerSle, changes, tx, j, enforce))
790 {
791 return false;
792 }
793 }
794
795 return true;
796}
797
798bool
800 std::shared_ptr<SLE const> const& before,
802{
803 // `after` can never be null, even if the trust line is deleted.
804 XRPL_ASSERT(
805 after, "ripple::TransfersNotFrozen::isValidEntry : valid after.");
806 if (!after)
807 {
808 return false;
809 }
810
811 if (after->getType() == ltACCOUNT_ROOT)
812 {
813 possibleIssuers_.emplace(after->at(sfAccount), after);
814 return false;
815 }
816
817 /* While LedgerEntryTypesMatch invariant also checks types, all invariants
818 * are processed regardless of previous failures.
819 *
820 * This type check is still necessary here because it prevents potential
821 * issues in subsequent processing.
822 */
823 return after->getType() == ltRIPPLE_STATE &&
824 (!before || before->getType() == ltRIPPLE_STATE);
825}
826
829 std::shared_ptr<SLE const> const& before,
831 bool isDelete)
832{
833 auto const getBalance = [](auto const& line, auto const& other, bool zero) {
834 STAmount amt =
835 line ? line->at(sfBalance) : other->at(sfBalance).zeroed();
836 return zero ? amt.zeroed() : amt;
837 };
838
839 /* Trust lines can be created dynamically by other transactions such as
840 * Payment and OfferCreate that cross offers. Such trust line won't be
841 * created frozen, but the sender might be, so the starting balance must be
842 * treated as zero.
843 */
844 auto const balanceBefore = getBalance(before, after, false);
845
846 /* Same as above, trust lines can be dynamically deleted, and for frozen
847 * trust lines, payments not involving the issuer must be blocked. This is
848 * achieved by treating the final balance as zero when isDelete=true to
849 * ensure frozen line restrictions are enforced even during deletion.
850 */
851 auto const balanceAfter = getBalance(after, before, isDelete);
852
853 return balanceAfter - balanceBefore;
854}
855
856void
858{
859 XRPL_ASSERT(
860 change.balanceChangeSign,
861 "ripple::TransfersNotFrozen::recordBalance : valid trustline "
862 "balance sign.");
863 auto& changes = balanceChanges_[issue];
864 if (change.balanceChangeSign < 0)
865 changes.senders.emplace_back(std::move(change));
866 else
867 changes.receivers.emplace_back(std::move(change));
868}
869
870void
873 STAmount const& balanceChange)
874{
875 auto const balanceChangeSign = balanceChange.signum();
876 auto const currency = after->at(sfBalance).getCurrency();
877
878 // Change from low account's perspective, which is trust line default
880 {currency, after->at(sfHighLimit).getIssuer()},
881 {after, balanceChangeSign});
882
883 // Change from high account's perspective, which reverses the sign.
885 {currency, after->at(sfLowLimit).getIssuer()},
886 {after, -balanceChangeSign});
887}
888
891{
892 if (auto it = possibleIssuers_.find(issuerID); it != possibleIssuers_.end())
893 {
894 return it->second;
895 }
896
897 return view.read(keylet::account(issuerID));
898}
899
900bool
902 std::shared_ptr<SLE const> const& issuer,
903 IssuerChanges const& changes,
904 STTx const& tx,
905 beast::Journal const& j,
906 bool enforce)
907{
908 if (!issuer)
909 {
910 return false;
911 }
912
913 bool const globalFreeze = issuer->isFlag(lsfGlobalFreeze);
914 if (changes.receivers.empty() || changes.senders.empty())
915 {
916 /* If there are no receivers, then the holder(s) are returning
917 * their tokens to the issuer. Likewise, if there are no
918 * senders, then the issuer is issuing tokens to the holder(s).
919 * This is allowed regardless of the issuer's freeze flags. (The
920 * holder may have contradicting freeze flags, but that will be
921 * checked when the holder is treated as issuer.)
922 */
923 return true;
924 }
925
926 for (auto const& actors : {changes.senders, changes.receivers})
927 {
928 for (auto const& change : actors)
929 {
930 bool const high = change.line->at(sfLowLimit).getIssuer() ==
931 issuer->at(sfAccount);
932
934 change, high, tx, j, enforce, globalFreeze))
935 {
936 return false;
937 }
938 }
939 }
940 return true;
941}
942
943bool
945 BalanceChange const& change,
946 bool high,
947 STTx const& tx,
948 beast::Journal const& j,
949 bool enforce,
950 bool globalFreeze)
951{
952 bool const freeze = change.balanceChangeSign < 0 &&
953 change.line->isFlag(high ? lsfLowFreeze : lsfHighFreeze);
954 bool const deepFreeze =
955 change.line->isFlag(high ? lsfLowDeepFreeze : lsfHighDeepFreeze);
956 bool const frozen = globalFreeze || deepFreeze || freeze;
957
958 bool const isAMMLine = change.line->isFlag(lsfAMMNode);
959
960 if (!frozen)
961 {
962 return true;
963 }
964
965 // AMMClawbacks are allowed to override some freeze rules
966 if ((!isAMMLine || globalFreeze) && hasPrivilege(tx, overrideFreeze))
967 {
968 JLOG(j.debug()) << "Invariant check allowing funds to be moved "
969 << (change.balanceChangeSign > 0 ? "to" : "from")
970 << " a frozen trustline for AMMClawback "
971 << tx.getTransactionID();
972 return true;
973 }
974
975 JLOG(j.fatal()) << "Invariant failed: Attempting to move frozen funds for "
976 << tx.getTransactionID();
977 // The comment above starting with "assert(enforce)" explains this assert.
978 XRPL_ASSERT(
979 enforce,
980 "ripple::TransfersNotFrozen::validateFrozenState : enforce "
981 "invariant.");
982
983 if (enforce)
984 {
985 return false;
986 }
987
988 return true;
989}
990
991//------------------------------------------------------------------------------
992
993void
995 bool,
996 std::shared_ptr<SLE const> const& before,
998{
999 if (!before && after->getType() == ltACCOUNT_ROOT)
1000 {
1002 accountSeq_ = (*after)[sfSequence];
1004 flags_ = after->getFlags();
1005 }
1006}
1007
1008bool
1010 STTx const& tx,
1011 TER const result,
1012 XRPAmount const,
1013 ReadView const& view,
1014 beast::Journal const& j)
1015{
1016 if (accountsCreated_ == 0)
1017 return true;
1018
1019 if (accountsCreated_ > 1)
1020 {
1021 JLOG(j.fatal()) << "Invariant failed: multiple accounts "
1022 "created in a single transaction";
1023 return false;
1024 }
1025
1026 // From this point on we know exactly one account was created.
1027 if (hasPrivilege(tx, createAcct | createPseudoAcct) && result == tesSUCCESS)
1028 {
1029 bool const pseudoAccount =
1030 (pseudoAccount_ && view.rules().enabled(featureSingleAssetVault));
1031
1032 if (pseudoAccount && !hasPrivilege(tx, createPseudoAcct))
1033 {
1034 JLOG(j.fatal()) << "Invariant failed: pseudo-account created by a "
1035 "wrong transaction type";
1036 return false;
1037 }
1038
1039 std::uint32_t const startingSeq = //
1040 pseudoAccount //
1041 ? 0 //
1042 : view.rules().enabled(featureDeletableAccounts) //
1043 ? view.seq() //
1044 : 1;
1045
1046 if (accountSeq_ != startingSeq)
1047 {
1048 JLOG(j.fatal()) << "Invariant failed: account created with "
1049 "wrong starting sequence number";
1050 return false;
1051 }
1052
1053 if (pseudoAccount)
1054 {
1055 std::uint32_t const expected =
1057 if (flags_ != expected)
1058 {
1059 JLOG(j.fatal())
1060 << "Invariant failed: pseudo-account created with "
1061 "wrong flags";
1062 return false;
1063 }
1064 }
1065
1066 return true;
1067 }
1068
1069 JLOG(j.fatal()) << "Invariant failed: account root created illegally";
1070 return false;
1071} // namespace ripple
1072
1073//------------------------------------------------------------------------------
1074
1075void
1077 bool isDelete,
1078 std::shared_ptr<SLE const> const& before,
1080{
1081 static constexpr uint256 const& pageBits = nft::pageMask;
1082 static constexpr uint256 const accountBits = ~pageBits;
1083
1084 if ((before && before->getType() != ltNFTOKEN_PAGE) ||
1085 (after && after->getType() != ltNFTOKEN_PAGE))
1086 return;
1087
1088 auto check = [this, isDelete](std::shared_ptr<SLE const> const& sle) {
1089 uint256 const account = sle->key() & accountBits;
1090 uint256 const hiLimit = sle->key() & pageBits;
1091 std::optional<uint256> const prev = (*sle)[~sfPreviousPageMin];
1092
1093 // Make sure that any page links...
1094 // 1. Are properly associated with the owning account and
1095 // 2. The page is correctly ordered between links.
1096 if (prev)
1097 {
1098 if (account != (*prev & accountBits))
1099 badLink_ = true;
1100
1101 if (hiLimit <= (*prev & pageBits))
1102 badLink_ = true;
1103 }
1104
1105 if (auto const next = (*sle)[~sfNextPageMin])
1106 {
1107 if (account != (*next & accountBits))
1108 badLink_ = true;
1109
1110 if (hiLimit >= (*next & pageBits))
1111 badLink_ = true;
1112 }
1113
1114 {
1115 auto const& nftokens = sle->getFieldArray(sfNFTokens);
1116
1117 // An NFTokenPage should never contain too many tokens or be empty.
1118 if (std::size_t const nftokenCount = nftokens.size();
1119 (!isDelete && nftokenCount == 0) ||
1120 nftokenCount > dirMaxTokensPerPage)
1121 invalidSize_ = true;
1122
1123 // If prev is valid, use it to establish a lower bound for
1124 // page entries. If prev is not valid the lower bound is zero.
1125 uint256 const loLimit =
1126 prev ? *prev & pageBits : uint256(beast::zero);
1127
1128 // Also verify that all NFTokenIDs in the page are sorted.
1129 uint256 loCmp = loLimit;
1130 for (auto const& obj : nftokens)
1131 {
1132 uint256 const tokenID = obj[sfNFTokenID];
1133 if (!nft::compareTokens(loCmp, tokenID))
1134 badSort_ = true;
1135 loCmp = tokenID;
1136
1137 // None of the NFTs on this page should belong on lower or
1138 // higher pages.
1139 if (uint256 const tokenPageBits = tokenID & pageBits;
1140 tokenPageBits < loLimit || tokenPageBits >= hiLimit)
1141 badEntry_ = true;
1142
1143 if (auto uri = obj[~sfURI]; uri && uri->empty())
1144 badURI_ = true;
1145 }
1146 }
1147 };
1148
1149 if (before)
1150 {
1151 check(before);
1152
1153 // While an account's NFToken directory contains any NFTokens, the last
1154 // NFTokenPage (with 96 bits of 1 in the low part of the index) should
1155 // never be deleted.
1156 if (isDelete && (before->key() & nft::pageMask) == nft::pageMask &&
1157 before->isFieldPresent(sfPreviousPageMin))
1158 {
1159 deletedFinalPage_ = true;
1160 }
1161 }
1162
1163 if (after)
1164 check(after);
1165
1166 if (!isDelete && before && after)
1167 {
1168 // If the NFTokenPage
1169 // 1. Has a NextMinPage field in before, but loses it in after, and
1170 // 2. This is not the last page in the directory
1171 // Then we have identified a corruption in the links between the
1172 // NFToken pages in the NFToken directory.
1173 if ((before->key() & nft::pageMask) != nft::pageMask &&
1174 before->isFieldPresent(sfNextPageMin) &&
1175 !after->isFieldPresent(sfNextPageMin))
1176 {
1177 deletedLink_ = true;
1178 }
1179 }
1180}
1181
1182bool
1184 STTx const& tx,
1185 TER const result,
1186 XRPAmount const,
1187 ReadView const& view,
1188 beast::Journal const& j)
1189{
1190 if (badLink_)
1191 {
1192 JLOG(j.fatal()) << "Invariant failed: NFT page is improperly linked.";
1193 return false;
1194 }
1195
1196 if (badEntry_)
1197 {
1198 JLOG(j.fatal()) << "Invariant failed: NFT found in incorrect page.";
1199 return false;
1200 }
1201
1202 if (badSort_)
1203 {
1204 JLOG(j.fatal()) << "Invariant failed: NFTs on page are not sorted.";
1205 return false;
1206 }
1207
1208 if (badURI_)
1209 {
1210 JLOG(j.fatal()) << "Invariant failed: NFT contains empty URI.";
1211 return false;
1212 }
1213
1214 if (invalidSize_)
1215 {
1216 JLOG(j.fatal()) << "Invariant failed: NFT page has invalid size.";
1217 return false;
1218 }
1219
1220 if (view.rules().enabled(fixNFTokenPageLinks))
1221 {
1223 {
1224 JLOG(j.fatal()) << "Invariant failed: Last NFT page deleted with "
1225 "non-empty directory.";
1226 return false;
1227 }
1228 if (deletedLink_)
1229 {
1230 JLOG(j.fatal()) << "Invariant failed: Lost NextMinPage link.";
1231 return false;
1232 }
1233 }
1234
1235 return true;
1236}
1237
1238//------------------------------------------------------------------------------
1239void
1241 bool,
1242 std::shared_ptr<SLE const> const& before,
1244{
1245 if (before && before->getType() == ltACCOUNT_ROOT)
1246 {
1247 beforeMintedTotal += (*before)[~sfMintedNFTokens].value_or(0);
1248 beforeBurnedTotal += (*before)[~sfBurnedNFTokens].value_or(0);
1249 }
1250
1251 if (after && after->getType() == ltACCOUNT_ROOT)
1252 {
1253 afterMintedTotal += (*after)[~sfMintedNFTokens].value_or(0);
1254 afterBurnedTotal += (*after)[~sfBurnedNFTokens].value_or(0);
1255 }
1256}
1257
1258bool
1260 STTx const& tx,
1261 TER const result,
1262 XRPAmount const,
1263 ReadView const& view,
1264 beast::Journal const& j)
1265{
1266 if (!hasPrivilege(tx, changeNFTCounts))
1267 {
1269 {
1270 JLOG(j.fatal()) << "Invariant failed: the number of minted tokens "
1271 "changed without a mint transaction!";
1272 return false;
1273 }
1274
1276 {
1277 JLOG(j.fatal()) << "Invariant failed: the number of burned tokens "
1278 "changed without a burn transaction!";
1279 return false;
1280 }
1281
1282 return true;
1283 }
1284
1285 if (tx.getTxnType() == ttNFTOKEN_MINT)
1286 {
1287 if (result == tesSUCCESS && beforeMintedTotal >= afterMintedTotal)
1288 {
1289 JLOG(j.fatal())
1290 << "Invariant failed: successful minting didn't increase "
1291 "the number of minted tokens.";
1292 return false;
1293 }
1294
1295 if (result != tesSUCCESS && beforeMintedTotal != afterMintedTotal)
1296 {
1297 JLOG(j.fatal()) << "Invariant failed: failed minting changed the "
1298 "number of minted tokens.";
1299 return false;
1300 }
1301
1303 {
1304 JLOG(j.fatal())
1305 << "Invariant failed: minting changed the number of "
1306 "burned tokens.";
1307 return false;
1308 }
1309 }
1310
1311 if (tx.getTxnType() == ttNFTOKEN_BURN)
1312 {
1313 if (result == tesSUCCESS)
1314 {
1316 {
1317 JLOG(j.fatal())
1318 << "Invariant failed: successful burning didn't increase "
1319 "the number of burned tokens.";
1320 return false;
1321 }
1322 }
1323
1324 if (result != tesSUCCESS && beforeBurnedTotal != afterBurnedTotal)
1325 {
1326 JLOG(j.fatal()) << "Invariant failed: failed burning changed the "
1327 "number of burned tokens.";
1328 return false;
1329 }
1330
1332 {
1333 JLOG(j.fatal())
1334 << "Invariant failed: burning changed the number of "
1335 "minted tokens.";
1336 return false;
1337 }
1338 }
1339
1340 return true;
1341}
1342
1343//------------------------------------------------------------------------------
1344
1345void
1347 bool,
1348 std::shared_ptr<SLE const> const& before,
1350{
1351 if (before && before->getType() == ltRIPPLE_STATE)
1353
1354 if (before && before->getType() == ltMPTOKEN)
1356}
1357
1358bool
1360 STTx const& tx,
1361 TER const result,
1362 XRPAmount const,
1363 ReadView const& view,
1364 beast::Journal const& j)
1365{
1366 if (tx.getTxnType() != ttCLAWBACK)
1367 return true;
1368
1369 if (result == tesSUCCESS)
1370 {
1371 if (trustlinesChanged > 1)
1372 {
1373 JLOG(j.fatal())
1374 << "Invariant failed: more than one trustline changed.";
1375 return false;
1376 }
1377
1378 if (mptokensChanged > 1)
1379 {
1380 JLOG(j.fatal())
1381 << "Invariant failed: more than one mptokens changed.";
1382 return false;
1383 }
1384
1385 if (trustlinesChanged == 1)
1386 {
1387 AccountID const issuer = tx.getAccountID(sfAccount);
1388 STAmount const& amount = tx.getFieldAmount(sfAmount);
1389 AccountID const& holder = amount.getIssuer();
1390 STAmount const holderBalance = accountHolds(
1391 view, holder, amount.getCurrency(), issuer, fhIGNORE_FREEZE, j);
1392
1393 if (holderBalance.signum() < 0)
1394 {
1395 JLOG(j.fatal())
1396 << "Invariant failed: trustline balance is negative";
1397 return false;
1398 }
1399 }
1400 }
1401 else
1402 {
1403 if (trustlinesChanged != 0)
1404 {
1405 JLOG(j.fatal()) << "Invariant failed: some trustlines were changed "
1406 "despite failure of the transaction.";
1407 return false;
1408 }
1409
1410 if (mptokensChanged != 0)
1411 {
1412 JLOG(j.fatal()) << "Invariant failed: some mptokens were changed "
1413 "despite failure of the transaction.";
1414 return false;
1415 }
1416 }
1417
1418 return true;
1419}
1420
1421//------------------------------------------------------------------------------
1422
1423void
1425 bool isDelete,
1426 std::shared_ptr<SLE const> const& before,
1428{
1429 if (after && after->getType() == ltMPTOKEN_ISSUANCE)
1430 {
1431 if (isDelete)
1433 else if (!before)
1435 }
1436
1437 if (after && after->getType() == ltMPTOKEN)
1438 {
1439 if (isDelete)
1441 else if (!before)
1443 }
1444}
1445
1446bool
1448 STTx const& tx,
1449 TER const result,
1450 XRPAmount const _fee,
1451 ReadView const& view,
1452 beast::Journal const& j)
1453{
1454 if (result == tesSUCCESS)
1455 {
1457 {
1458 if (mptIssuancesCreated_ == 0)
1459 {
1460 JLOG(j.fatal()) << "Invariant failed: transaction "
1461 "succeeded without creating a MPT issuance";
1462 }
1463 else if (mptIssuancesDeleted_ != 0)
1464 {
1465 JLOG(j.fatal()) << "Invariant failed: transaction "
1466 "succeeded while removing MPT issuances";
1467 }
1468 else if (mptIssuancesCreated_ > 1)
1469 {
1470 JLOG(j.fatal()) << "Invariant failed: transaction "
1471 "succeeded but created multiple issuances";
1472 }
1473
1474 return mptIssuancesCreated_ == 1 && mptIssuancesDeleted_ == 0;
1475 }
1476
1478 {
1479 if (mptIssuancesDeleted_ == 0)
1480 {
1481 JLOG(j.fatal()) << "Invariant failed: MPT issuance deletion "
1482 "succeeded without removing a MPT issuance";
1483 }
1484 else if (mptIssuancesCreated_ > 0)
1485 {
1486 JLOG(j.fatal()) << "Invariant failed: MPT issuance deletion "
1487 "succeeded while creating MPT issuances";
1488 }
1489 else if (mptIssuancesDeleted_ > 1)
1490 {
1491 JLOG(j.fatal()) << "Invariant failed: MPT issuance deletion "
1492 "succeeded but deleted multiple issuances";
1493 }
1494
1495 return mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 1;
1496 }
1497
1498 // ttESCROW_FINISH may authorize an MPT, but it can't have the
1499 // mayAuthorizeMPT privilege, because that may cause
1500 // non-amendment-gated side effects.
1501 bool const enforceEscrowFinish = (tx.getTxnType() == ttESCROW_FINISH) &&
1502 (view.rules().enabled(featureSingleAssetVault)
1503 /*
1504 TODO: Uncomment when LendingProtocol is defined
1505 || view.rules().enabled(featureLendingProtocol)*/
1506 );
1508 enforceEscrowFinish)
1509 {
1510 bool const submittedByIssuer = tx.isFieldPresent(sfHolder);
1511
1512 if (mptIssuancesCreated_ > 0)
1513 {
1514 JLOG(j.fatal()) << "Invariant failed: MPT authorize "
1515 "succeeded but created MPT issuances";
1516 return false;
1517 }
1518 else if (mptIssuancesDeleted_ > 0)
1519 {
1520 JLOG(j.fatal()) << "Invariant failed: MPT authorize "
1521 "succeeded but deleted issuances";
1522 return false;
1523 }
1524 else if (
1525 submittedByIssuer &&
1527 {
1528 JLOG(j.fatal())
1529 << "Invariant failed: MPT authorize submitted by issuer "
1530 "succeeded but created/deleted mptokens";
1531 return false;
1532 }
1533 else if (
1534 !submittedByIssuer && hasPrivilege(tx, mustAuthorizeMPT) &&
1536 {
1537 // if the holder submitted this tx, then a mptoken must be
1538 // either created or deleted.
1539 JLOG(j.fatal())
1540 << "Invariant failed: MPT authorize submitted by holder "
1541 "succeeded but created/deleted bad number of mptokens";
1542 return false;
1543 }
1544
1545 return true;
1546 }
1547 if (tx.getTxnType() == ttESCROW_FINISH)
1548 {
1549 // ttESCROW_FINISH may authorize an MPT, but it can't have the
1550 // mayAuthorizeMPT privilege, because that may cause
1551 // non-amendment-gated side effects.
1552 XRPL_ASSERT_PARTS(
1553 !enforceEscrowFinish,
1554 "ripple::ValidMPTIssuance::finalize",
1555 "not escrow finish tx");
1556 return true;
1557 }
1558
1559 if (hasPrivilege(tx, mayDeleteMPT) && mptokensDeleted_ == 1 &&
1562 return true;
1563 }
1564
1565 if (mptIssuancesCreated_ != 0)
1566 {
1567 JLOG(j.fatal()) << "Invariant failed: a MPT issuance was created";
1568 }
1569 else if (mptIssuancesDeleted_ != 0)
1570 {
1571 JLOG(j.fatal()) << "Invariant failed: a MPT issuance was deleted";
1572 }
1573 else if (mptokensCreated_ != 0)
1574 {
1575 JLOG(j.fatal()) << "Invariant failed: a MPToken was created";
1576 }
1577 else if (mptokensDeleted_ != 0)
1578 {
1579 JLOG(j.fatal()) << "Invariant failed: a MPToken was deleted";
1580 }
1581
1582 return mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 0 &&
1584}
1585
1586//------------------------------------------------------------------------------
1587
1588void
1590 bool,
1591 std::shared_ptr<SLE const> const& before,
1593{
1594 if (before && before->getType() != ltPERMISSIONED_DOMAIN)
1595 return;
1596 if (after && after->getType() != ltPERMISSIONED_DOMAIN)
1597 return;
1598
1599 auto check = [](SleStatus& sleStatus,
1600 std::shared_ptr<SLE const> const& sle) {
1601 auto const& credentials = sle->getFieldArray(sfAcceptedCredentials);
1602 sleStatus.credentialsSize_ = credentials.size();
1603 auto const sorted = credentials::makeSorted(credentials);
1604 sleStatus.isUnique_ = !sorted.empty();
1605
1606 // If array have duplicates then all the other checks are invalid
1607 sleStatus.isSorted_ = false;
1608
1609 if (sleStatus.isUnique_)
1610 {
1611 unsigned i = 0;
1612 for (auto const& cred : sorted)
1613 {
1614 auto const& credTx = credentials[i++];
1615 sleStatus.isSorted_ = (cred.first == credTx[sfIssuer]) &&
1616 (cred.second == credTx[sfCredentialType]);
1617 if (!sleStatus.isSorted_)
1618 break;
1619 }
1620 }
1621 };
1622
1623 if (before)
1624 {
1625 sleStatus_[0] = SleStatus();
1626 check(*sleStatus_[0], after);
1627 }
1628
1629 if (after)
1630 {
1631 sleStatus_[1] = SleStatus();
1632 check(*sleStatus_[1], after);
1633 }
1634}
1635
1636bool
1638 STTx const& tx,
1639 TER const result,
1640 XRPAmount const,
1641 ReadView const& view,
1642 beast::Journal const& j)
1643{
1644 if (tx.getTxnType() != ttPERMISSIONED_DOMAIN_SET || result != tesSUCCESS)
1645 return true;
1646
1647 auto check = [](SleStatus const& sleStatus, beast::Journal const& j) {
1648 if (!sleStatus.credentialsSize_)
1649 {
1650 JLOG(j.fatal()) << "Invariant failed: permissioned domain with "
1651 "no rules.";
1652 return false;
1653 }
1654
1655 if (sleStatus.credentialsSize_ >
1657 {
1658 JLOG(j.fatal()) << "Invariant failed: permissioned domain bad "
1659 "credentials size "
1660 << sleStatus.credentialsSize_;
1661 return false;
1662 }
1663
1664 if (!sleStatus.isUnique_)
1665 {
1666 JLOG(j.fatal())
1667 << "Invariant failed: permissioned domain credentials "
1668 "aren't unique";
1669 return false;
1670 }
1671
1672 if (!sleStatus.isSorted_)
1673 {
1674 JLOG(j.fatal())
1675 << "Invariant failed: permissioned domain credentials "
1676 "aren't sorted";
1677 return false;
1678 }
1679
1680 return true;
1681 };
1682
1683 return (sleStatus_[0] ? check(*sleStatus_[0], j) : true) &&
1684 (sleStatus_[1] ? check(*sleStatus_[1], j) : true);
1685}
1686
1687//------------------------------------------------------------------------------
1688
1689void
1691 bool isDelete,
1692 std::shared_ptr<SLE const> const& before,
1694{
1695 if (isDelete)
1696 // Deletion is ignored
1697 return;
1698
1699 if (after && after->getType() == ltACCOUNT_ROOT)
1700 {
1701 bool const isPseudo = [&]() {
1702 // isPseudoAccount checks that any of the pseudo-account fields are
1703 // set.
1705 return true;
1706 // Not all pseudo-accounts have a zero sequence, but all accounts
1707 // with a zero sequence had better be pseudo-accounts.
1708 if (after->at(sfSequence) == 0)
1709 return true;
1710
1711 return false;
1712 }();
1713 if (isPseudo)
1714 {
1715 // Pseudo accounts must have the following properties:
1716 // 1. Exactly one of the pseudo-account fields is set.
1717 // 2. The sequence number is not changed.
1718 // 3. The lsfDisableMaster, lsfDefaultRipple, and lsfDepositAuth
1719 // flags are set.
1720 // 4. The RegularKey is not set.
1721 {
1722 std::vector<SField const*> const& fields =
1724
1725 auto const numFields = std::count_if(
1726 fields.begin(),
1727 fields.end(),
1728 [&after](SField const* sf) -> bool {
1729 return after->isFieldPresent(*sf);
1730 });
1731 if (numFields != 1)
1732 {
1733 std::stringstream error;
1734 error << "pseudo-account has " << numFields
1735 << " pseudo-account fields set";
1736 errors_.emplace_back(error.str());
1737 }
1738 }
1739 if (before && before->at(sfSequence) != after->at(sfSequence))
1740 {
1741 errors_.emplace_back("pseudo-account sequence changed");
1742 }
1743 if (!after->isFlag(
1745 {
1746 errors_.emplace_back("pseudo-account flags are not set");
1747 }
1748 if (after->isFieldPresent(sfRegularKey))
1749 {
1750 errors_.emplace_back("pseudo-account has a regular key");
1751 }
1752 }
1753 }
1754}
1755
1756bool
1758 STTx const& tx,
1759 TER const,
1760 XRPAmount const,
1761 ReadView const& view,
1762 beast::Journal const& j)
1763{
1764 bool const enforce = view.rules().enabled(featureSingleAssetVault);
1765
1766 // The comment above starting with "assert(enforce)" explains this assert.
1767 XRPL_ASSERT(
1768 errors_.empty() || enforce,
1769 "ripple::ValidPseudoAccounts::finalize : no bad "
1770 "changes or enforce invariant");
1771 if (!errors_.empty())
1772 {
1773 for (auto const& error : errors_)
1774 {
1775 JLOG(j.fatal()) << "Invariant failed: " << error;
1776 }
1777 if (enforce)
1778 return false;
1779 }
1780 return true;
1781}
1782
1783//------------------------------------------------------------------------------
1784
1785void
1787 bool,
1788 std::shared_ptr<SLE const> const& before,
1790{
1791 if (after && after->getType() == ltDIR_NODE)
1792 {
1793 if (after->isFieldPresent(sfDomainID))
1794 domains_.insert(after->getFieldH256(sfDomainID));
1795 }
1796
1797 if (after && after->getType() == ltOFFER)
1798 {
1799 if (after->isFieldPresent(sfDomainID))
1800 domains_.insert(after->getFieldH256(sfDomainID));
1801 else
1802 regularOffers_ = true;
1803
1804 // if a hybrid offer is missing domain or additional book, there's
1805 // something wrong
1806 if (after->isFlag(lsfHybrid) &&
1807 (!after->isFieldPresent(sfDomainID) ||
1808 !after->isFieldPresent(sfAdditionalBooks) ||
1809 after->getFieldArray(sfAdditionalBooks).size() > 1))
1810 badHybrids_ = true;
1811 }
1812}
1813
1814bool
1816 STTx const& tx,
1817 TER const result,
1818 XRPAmount const,
1819 ReadView const& view,
1820 beast::Journal const& j)
1821{
1822 auto const txType = tx.getTxnType();
1823 if ((txType != ttPAYMENT && txType != ttOFFER_CREATE) ||
1824 result != tesSUCCESS)
1825 return true;
1826
1827 // For each offercreate transaction, check if
1828 // permissioned offers are valid
1829 if (txType == ttOFFER_CREATE && badHybrids_)
1830 {
1831 JLOG(j.fatal()) << "Invariant failed: hybrid offer is malformed";
1832 return false;
1833 }
1834
1835 if (!tx.isFieldPresent(sfDomainID))
1836 return true;
1837
1838 auto const domain = tx.getFieldH256(sfDomainID);
1839
1840 if (!view.exists(keylet::permissionedDomain(domain)))
1841 {
1842 JLOG(j.fatal()) << "Invariant failed: domain doesn't exist";
1843 return false;
1844 }
1845
1846 // for both payment and offercreate, there shouldn't be another domain
1847 // that's different from the domain specified
1848 for (auto const& d : domains_)
1849 {
1850 if (d != domain)
1851 {
1852 JLOG(j.fatal()) << "Invariant failed: transaction"
1853 " consumed wrong domains";
1854 return false;
1855 }
1856 }
1857
1858 if (regularOffers_)
1859 {
1860 JLOG(j.fatal()) << "Invariant failed: domain transaction"
1861 " affected regular offers";
1862 return false;
1863 }
1864
1865 return true;
1866}
1867
1868void
1870 bool isDelete,
1871 std::shared_ptr<SLE const> const& before,
1873{
1874 if (isDelete)
1875 return;
1876
1877 if (after)
1878 {
1879 auto const type = after->getType();
1880 // AMM object changed
1881 if (type == ltAMM)
1882 {
1883 ammAccount_ = after->getAccountID(sfAccount);
1884 lptAMMBalanceAfter_ = after->getFieldAmount(sfLPTokenBalance);
1885 }
1886 // AMM pool changed
1887 else if (
1888 (type == ltRIPPLE_STATE && after->getFlags() & lsfAMMNode) ||
1889 (type == ltACCOUNT_ROOT && after->isFieldPresent(sfAMMID)))
1890 {
1891 ammPoolChanged_ = true;
1892 }
1893 }
1894
1895 if (before)
1896 {
1897 // AMM object changed
1898 if (before->getType() == ltAMM)
1899 {
1900 lptAMMBalanceBefore_ = before->getFieldAmount(sfLPTokenBalance);
1901 }
1902 }
1903}
1904
1905static bool
1907 STAmount const& amount,
1908 STAmount const& amount2,
1909 STAmount const& lptAMMBalance,
1910 ValidAMM::ZeroAllowed zeroAllowed)
1911{
1912 bool const positive = amount > beast::zero && amount2 > beast::zero &&
1913 lptAMMBalance > beast::zero;
1914 if (zeroAllowed == ValidAMM::ZeroAllowed::Yes)
1915 return positive ||
1916 (amount == beast::zero && amount2 == beast::zero &&
1917 lptAMMBalance == beast::zero);
1918 return positive;
1919}
1920
1921bool
1922ValidAMM::finalizeVote(bool enforce, beast::Journal const& j) const
1923{
1925 {
1926 // LPTokens and the pool can not change on vote
1927 // LCOV_EXCL_START
1928 JLOG(j.error()) << "AMMVote invariant failed: "
1931 << ammPoolChanged_;
1932 if (enforce)
1933 return false;
1934 // LCOV_EXCL_STOP
1935 }
1936
1937 return true;
1938}
1939
1940bool
1941ValidAMM::finalizeBid(bool enforce, beast::Journal const& j) const
1942{
1943 if (ammPoolChanged_)
1944 {
1945 // The pool can not change on bid
1946 // LCOV_EXCL_START
1947 JLOG(j.error()) << "AMMBid invariant failed: pool changed";
1948 if (enforce)
1949 return false;
1950 // LCOV_EXCL_STOP
1951 }
1952 // LPTokens are burnt, therefore there should be fewer LPTokens
1953 else if (
1956 *lptAMMBalanceAfter_ <= beast::zero))
1957 {
1958 // LCOV_EXCL_START
1959 JLOG(j.error()) << "AMMBid invariant failed: " << *lptAMMBalanceBefore_
1960 << " " << *lptAMMBalanceAfter_;
1961 if (enforce)
1962 return false;
1963 // LCOV_EXCL_STOP
1964 }
1965
1966 return true;
1967}
1968
1969bool
1971 STTx const& tx,
1972 ReadView const& view,
1973 bool enforce,
1974 beast::Journal const& j) const
1975{
1976 if (!ammAccount_)
1977 {
1978 // LCOV_EXCL_START
1979 JLOG(j.error())
1980 << "AMMCreate invariant failed: AMM object is not created";
1981 if (enforce)
1982 return false;
1983 // LCOV_EXCL_STOP
1984 }
1985 else
1986 {
1987 auto const [amount, amount2] = ammPoolHolds(
1988 view,
1989 *ammAccount_,
1990 tx[sfAmount].get<Issue>(),
1991 tx[sfAmount2].get<Issue>(),
1993 j);
1994 // Create invariant:
1995 // sqrt(amount * amount2) == LPTokens
1996 // all balances are greater than zero
1997 if (!validBalances(
1998 amount, amount2, *lptAMMBalanceAfter_, ZeroAllowed::No) ||
1999 ammLPTokens(amount, amount2, lptAMMBalanceAfter_->issue()) !=
2001 {
2002 JLOG(j.error()) << "AMMCreate invariant failed: " << amount << " "
2003 << amount2 << " " << *lptAMMBalanceAfter_;
2004 if (enforce)
2005 return false;
2006 }
2007 }
2008
2009 return true;
2010}
2011
2012bool
2013ValidAMM::finalizeDelete(bool enforce, TER res, beast::Journal const& j) const
2014{
2015 if (ammAccount_)
2016 {
2017 // LCOV_EXCL_START
2018 std::string const msg = (res == tesSUCCESS)
2019 ? "AMM object is not deleted on tesSUCCESS"
2020 : "AMM object is changed on tecINCOMPLETE";
2021 JLOG(j.error()) << "AMMDelete invariant failed: " << msg;
2022 if (enforce)
2023 return false;
2024 // LCOV_EXCL_STOP
2025 }
2026
2027 return true;
2028}
2029
2030bool
2031ValidAMM::finalizeDEX(bool enforce, beast::Journal const& j) const
2032{
2033 if (ammAccount_)
2034 {
2035 // LCOV_EXCL_START
2036 JLOG(j.error()) << "AMM swap invariant failed: AMM object changed";
2037 if (enforce)
2038 return false;
2039 // LCOV_EXCL_STOP
2040 }
2041
2042 return true;
2043}
2044
2045bool
2047 ripple::STTx const& tx,
2048 ripple::ReadView const& view,
2049 ZeroAllowed zeroAllowed,
2050 beast::Journal const& j) const
2051{
2052 auto const [amount, amount2] = ammPoolHolds(
2053 view,
2054 *ammAccount_,
2055 tx[sfAsset].get<Issue>(),
2056 tx[sfAsset2].get<Issue>(),
2058 j);
2059 // Deposit and Withdrawal invariant:
2060 // sqrt(amount * amount2) >= LPTokens
2061 // all balances are greater than zero
2062 // unless on last withdrawal
2063 auto const poolProductMean = root2(amount * amount2);
2064 bool const nonNegativeBalances =
2065 validBalances(amount, amount2, *lptAMMBalanceAfter_, zeroAllowed);
2066 bool const strongInvariantCheck = poolProductMean >= *lptAMMBalanceAfter_;
2067 // Allow for a small relative error if strongInvariantCheck fails
2068 auto weakInvariantCheck = [&]() {
2069 return *lptAMMBalanceAfter_ != beast::zero &&
2071 poolProductMean, Number{*lptAMMBalanceAfter_}, Number{1, -11});
2072 };
2073 if (!nonNegativeBalances ||
2074 (!strongInvariantCheck && !weakInvariantCheck()))
2075 {
2076 JLOG(j.error()) << "AMM " << tx.getTxnType() << " invariant failed: "
2078 << ammPoolChanged_ << " " << amount << " " << amount2
2079 << " " << poolProductMean << " "
2080 << lptAMMBalanceAfter_->getText() << " "
2081 << ((*lptAMMBalanceAfter_ == beast::zero)
2082 ? Number{1}
2083 : ((*lptAMMBalanceAfter_ - poolProductMean) /
2084 poolProductMean));
2085 return false;
2086 }
2087
2088 return true;
2089}
2090
2091bool
2093 ripple::STTx const& tx,
2094 ripple::ReadView const& view,
2095 bool enforce,
2096 beast::Journal const& j) const
2097{
2098 if (!ammAccount_)
2099 {
2100 // LCOV_EXCL_START
2101 JLOG(j.error()) << "AMMDeposit invariant failed: AMM object is deleted";
2102 if (enforce)
2103 return false;
2104 // LCOV_EXCL_STOP
2105 }
2106 else if (!generalInvariant(tx, view, ZeroAllowed::No, j) && enforce)
2107 return false;
2108
2109 return true;
2110}
2111
2112bool
2114 ripple::STTx const& tx,
2115 ripple::ReadView const& view,
2116 bool enforce,
2117 beast::Journal const& j) const
2118{
2119 if (!ammAccount_)
2120 {
2121 // Last Withdraw or Clawback deleted AMM
2122 }
2123 else if (!generalInvariant(tx, view, ZeroAllowed::Yes, j))
2124 {
2125 if (enforce)
2126 return false;
2127 }
2128
2129 return true;
2130}
2131
2132bool
2134 STTx const& tx,
2135 TER const result,
2136 XRPAmount const,
2137 ReadView const& view,
2138 beast::Journal const& j)
2139{
2140 // Delete may return tecINCOMPLETE if there are too many
2141 // trustlines to delete.
2142 if (result != tesSUCCESS && result != tecINCOMPLETE)
2143 return true;
2144
2145 bool const enforce = view.rules().enabled(fixAMMv1_3);
2146
2147 switch (tx.getTxnType())
2148 {
2149 case ttAMM_CREATE:
2150 return finalizeCreate(tx, view, enforce, j);
2151 case ttAMM_DEPOSIT:
2152 return finalizeDeposit(tx, view, enforce, j);
2153 case ttAMM_CLAWBACK:
2154 case ttAMM_WITHDRAW:
2155 return finalizeWithdraw(tx, view, enforce, j);
2156 case ttAMM_BID:
2157 return finalizeBid(enforce, j);
2158 case ttAMM_VOTE:
2159 return finalizeVote(enforce, j);
2160 case ttAMM_DELETE:
2161 return finalizeDelete(enforce, result, j);
2162 case ttCHECK_CASH:
2163 case ttOFFER_CREATE:
2164 case ttPAYMENT:
2165 return finalizeDEX(enforce, j);
2166 default:
2167 break;
2168 }
2169
2170 return true;
2171}
2172
2173} // 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:306
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
AccountID getAccountID(SField const &field) const
Definition STObject.cpp:657
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:207
uint256 getTransactionID() const
Definition STTx.h:219
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 &)
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)
std::set< std::pair< AccountID, Slice > > makeSorted(STArray const &credentials)
Keylet permissionedDomain(AccountID const &account, std::uint32_t seq) noexcept
Definition Indexes.cpp:570
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:110
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:116
@ 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:62
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:335
@ tesSUCCESS
Definition TER.h:244
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j)
Definition View.cpp:384
STAmount ammLPTokens(STAmount const &asset1, STAmount const &asset2, Issue const &lptIssue)
Calculate LP Tokens given AMM pool reserves.
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition View.cpp:3239
@ transactionID
transaction plus signature to give transaction ID
std::vector< SField const * > const & getPseudoAccountFields()
Definition View.cpp:1089
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:1115
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
T to_string(T... args)
T value_or(T... args)