rippled
Loading...
Searching...
No Matches
VaultInvariant.cpp
1#include <xrpl/tx/invariants/VaultInvariant.h>
2//
3#include <xrpl/basics/Log.h>
4#include <xrpl/beast/utility/instrumentation.h>
5#include <xrpl/ledger/View.h>
6#include <xrpl/ledger/helpers/AccountRootHelpers.h>
7#include <xrpl/protocol/Feature.h>
8#include <xrpl/protocol/Indexes.h>
9#include <xrpl/protocol/LedgerFormats.h>
10#include <xrpl/protocol/Protocol.h>
11#include <xrpl/protocol/SField.h>
12#include <xrpl/protocol/STNumber.h>
13#include <xrpl/protocol/TxFormats.h>
14#include <xrpl/tx/invariants/InvariantCheckPrivilege.h>
15
16namespace xrpl {
17
18ValidVault::Vault
20{
21 XRPL_ASSERT(from.getType() == ltVAULT, "ValidVault::Vault::make : from Vault object");
22
24 self.key = from.key();
25 self.asset = from.at(sfAsset);
26 self.pseudoId = from.getAccountID(sfAccount);
27 self.owner = from.at(sfOwner);
28 self.shareMPTID = from.getFieldH192(sfShareMPTID);
29 self.assetsTotal = from.at(sfAssetsTotal);
30 self.assetsAvailable = from.at(sfAssetsAvailable);
31 self.assetsMaximum = from.at(sfAssetsMaximum);
32 self.lossUnrealized = from.at(sfLossUnrealized);
33 return self;
34}
35
38{
39 XRPL_ASSERT(
40 from.getType() == ltMPTOKEN_ISSUANCE,
41 "ValidVault::Shares::make : from MPTokenIssuance object");
42
44 self.share = MPTIssue(makeMptID(from.getFieldU32(sfSequence), from.getAccountID(sfIssuer)));
45 self.sharesTotal = from.at(sfOutstandingAmount);
46 self.sharesMaximum = from[~sfMaximumAmount].value_or(maxMPTokenAmount);
47 return self;
48}
49
50void
52 bool isDelete,
53 std::shared_ptr<SLE const> const& before,
55{
56 // If `before` is empty, this means an object is being created, in which
57 // case `isDelete` must be false. Otherwise `before` and `after` are set and
58 // `isDelete` indicates whether an object is being deleted or modified.
59 XRPL_ASSERT(
60 after != nullptr && (before != nullptr || !isDelete),
61 "xrpl::ValidVault::visitEntry : some object is available");
62
63 // Number balanceDelta will capture the difference (delta) between "before"
64 // state (zero if created) and "after" state (zero if destroyed), so the
65 // invariants can validate that the change in account balances matches the
66 // change in vault balances, stored to deltas_ at the end of this function.
67 Number balanceDelta{};
68
69 std::int8_t sign = 0;
70 if (before)
71 {
72 switch (before->getType())
73 {
74 case ltVAULT:
75 beforeVault_.push_back(Vault::make(*before));
76 break;
77 case ltMPTOKEN_ISSUANCE:
78 // At this moment we have no way of telling if this object holds
79 // vault shares or something else. Save it for finalize.
80 beforeMPTs_.push_back(Shares::make(*before));
81 balanceDelta = static_cast<std::int64_t>(before->getFieldU64(sfOutstandingAmount));
82 sign = 1;
83 break;
84 case ltMPTOKEN:
85 balanceDelta = static_cast<std::int64_t>(before->getFieldU64(sfMPTAmount));
86 sign = -1;
87 break;
88 case ltACCOUNT_ROOT:
89 case ltRIPPLE_STATE:
90 balanceDelta = before->getFieldAmount(sfBalance);
91 sign = -1;
92 break;
93 default:;
94 }
95 }
96
97 if (!isDelete && after)
98 {
99 switch (after->getType())
100 {
101 case ltVAULT:
102 afterVault_.push_back(Vault::make(*after));
103 break;
104 case ltMPTOKEN_ISSUANCE:
105 // At this moment we have no way of telling if this object holds
106 // vault shares or something else. Save it for finalize.
107 afterMPTs_.push_back(Shares::make(*after));
108 balanceDelta -=
109 Number(static_cast<std::int64_t>(after->getFieldU64(sfOutstandingAmount)));
110 sign = 1;
111 break;
112 case ltMPTOKEN:
113 balanceDelta -= Number(static_cast<std::int64_t>(after->getFieldU64(sfMPTAmount)));
114 sign = -1;
115 break;
116 case ltACCOUNT_ROOT:
117 case ltRIPPLE_STATE:
118 balanceDelta -= Number(after->getFieldAmount(sfBalance));
119 sign = -1;
120 break;
121 default:;
122 }
123 }
124
125 uint256 const key = (before ? before->key() : after->key());
126 // Append to deltas if sign is non-zero, i.e. an object of an interesting
127 // type has been updated. A transaction may update an object even when
128 // its balance has not changed, e.g. transaction fee equals the amount
129 // transferred to the account. We intentionally do not compare balanceDelta
130 // against zero, to avoid missing such updates.
131 if (sign != 0)
132 deltas_[key] = balanceDelta * sign;
133}
134
135bool
137 STTx const& tx,
138 TER const ret,
139 XRPAmount const fee,
140 ReadView const& view,
141 beast::Journal const& j)
142{
143 bool const enforce = view.rules().enabled(featureSingleAssetVault);
144
145 if (!isTesSuccess(ret))
146 return true; // Do not perform checks
147
148 if (afterVault_.empty() && beforeVault_.empty())
149 {
151 {
152 JLOG(j.fatal()) << //
153 "Invariant failed: vault operation succeeded without modifying "
154 "a vault";
155 XRPL_ASSERT(enforce, "xrpl::ValidVault::finalize : vault noop invariant");
156 return !enforce;
157 }
158
159 return true; // Not a vault operation
160 }
162 {
163 JLOG(j.fatal()) << //
164 "Invariant failed: vault updated by a wrong transaction type";
165 XRPL_ASSERT(
166 enforce,
167 "xrpl::ValidVault::finalize : illegal vault transaction "
168 "invariant");
169 return !enforce; // Also not a vault operation
170 }
171
172 if (beforeVault_.size() > 1 || afterVault_.size() > 1)
173 {
174 JLOG(j.fatal()) << //
175 "Invariant failed: vault operation updated more than single vault";
176 XRPL_ASSERT(enforce, "xrpl::ValidVault::finalize : single vault invariant");
177 return !enforce; // That's all we can do here
178 }
179
180 auto const txnType = tx.getTxnType();
181
182 // We do special handling for ttVAULT_DELETE first, because it's the only
183 // vault-modifying transaction without an "after" state of the vault
184 if (afterVault_.empty())
185 {
186 if (txnType != ttVAULT_DELETE)
187 {
188 JLOG(j.fatal()) << //
189 "Invariant failed: vault deleted by a wrong transaction type";
190 XRPL_ASSERT(
191 enforce,
192 "xrpl::ValidVault::finalize : illegal vault deletion "
193 "invariant");
194 return !enforce; // That's all we can do here
195 }
196
197 // Note, if afterVault_ is empty then we know that beforeVault_ is not
198 // empty, as enforced at the top of this function
199 auto const& beforeVault = beforeVault_[0];
200
201 // At this moment we only know a vault is being deleted and there
202 // might be some MPTokenIssuance objects which are deleted in the
203 // same transaction. Find the one matching this vault.
204 auto const deletedShares = [&]() -> std::optional<Shares> {
205 for (auto const& e : beforeMPTs_)
206 {
207 if (e.share.getMptID() == beforeVault.shareMPTID)
208 return e;
209 }
210 return std::nullopt;
211 }();
212
213 if (!deletedShares)
214 {
215 JLOG(j.fatal()) << "Invariant failed: deleted vault must also "
216 "delete shares";
217 XRPL_ASSERT(enforce, "xrpl::ValidVault::finalize : shares deletion invariant");
218 return !enforce; // That's all we can do here
219 }
220
221 bool result = true;
222 if (deletedShares->sharesTotal != 0)
223 {
224 JLOG(j.fatal()) << "Invariant failed: deleted vault must have no "
225 "shares outstanding";
226 result = false;
227 }
228 if (beforeVault.assetsTotal != zero)
229 {
230 JLOG(j.fatal()) << "Invariant failed: deleted vault must have no "
231 "assets outstanding";
232 result = false;
233 }
234 if (beforeVault.assetsAvailable != zero)
235 {
236 JLOG(j.fatal()) << "Invariant failed: deleted vault must have no "
237 "assets available";
238 result = false;
239 }
240
241 return result;
242 }
243 if (txnType == ttVAULT_DELETE)
244 {
245 JLOG(j.fatal()) << "Invariant failed: vault deletion succeeded without "
246 "deleting a vault";
247 XRPL_ASSERT(enforce, "xrpl::ValidVault::finalize : vault deletion invariant");
248 return !enforce; // That's all we can do here
249 }
250
251 // Note, `afterVault_.empty()` is handled above
252 auto const& afterVault = afterVault_[0];
253 XRPL_ASSERT(
254 beforeVault_.empty() || beforeVault_[0].key == afterVault.key,
255 "xrpl::ValidVault::finalize : single vault operation");
256
257 auto const updatedShares = [&]() -> std::optional<Shares> {
258 // At this moment we only know that a vault is being updated and there
259 // might be some MPTokenIssuance objects which are also updated in the
260 // same transaction. Find the one matching the shares to this vault.
261 // Note, we expect updatedMPTs collection to be extremely small. For
262 // such collections linear search is faster than lookup.
263 for (auto const& e : afterMPTs_)
264 {
265 if (e.share.getMptID() == afterVault.shareMPTID)
266 return e;
267 }
268
269 auto const sleShares = view.read(keylet::mptIssuance(afterVault.shareMPTID));
270
271 return sleShares ? std::optional<Shares>(Shares::make(*sleShares)) : std::nullopt;
272 }();
273
274 bool result = true;
275
276 // Universal transaction checks
277 if (!beforeVault_.empty())
278 {
279 auto const& beforeVault = beforeVault_[0];
280 if (afterVault.asset != beforeVault.asset || afterVault.pseudoId != beforeVault.pseudoId ||
281 afterVault.shareMPTID != beforeVault.shareMPTID)
282 {
283 JLOG(j.fatal()) << "Invariant failed: violation of vault immutable data";
284 result = false;
285 }
286 }
287
288 if (!updatedShares)
289 {
290 JLOG(j.fatal()) << "Invariant failed: updated vault must have shares";
291 XRPL_ASSERT(enforce, "xrpl::ValidVault::finalize : vault has shares invariant");
292 return !enforce; // That's all we can do here
293 }
294
295 if (updatedShares->sharesTotal == 0)
296 {
297 if (afterVault.assetsTotal != zero)
298 {
299 JLOG(j.fatal()) << "Invariant failed: updated zero sized "
300 "vault must have no assets outstanding";
301 result = false;
302 }
303 if (afterVault.assetsAvailable != zero)
304 {
305 JLOG(j.fatal()) << "Invariant failed: updated zero sized "
306 "vault must have no assets available";
307 result = false;
308 }
309 }
310 else if (updatedShares->sharesTotal > updatedShares->sharesMaximum)
311 {
312 JLOG(j.fatal()) //
313 << "Invariant failed: updated shares must not exceed maximum "
314 << updatedShares->sharesMaximum;
315 result = false;
316 }
317
318 if (afterVault.assetsAvailable < zero)
319 {
320 JLOG(j.fatal()) << "Invariant failed: assets available must be positive";
321 result = false;
322 }
323
324 if (afterVault.assetsAvailable > afterVault.assetsTotal)
325 {
326 JLOG(j.fatal()) << "Invariant failed: assets available must "
327 "not be greater than assets outstanding";
328 result = false;
329 }
330 else if (afterVault.lossUnrealized > afterVault.assetsTotal - afterVault.assetsAvailable)
331 {
332 JLOG(j.fatal()) //
333 << "Invariant failed: loss unrealized must not exceed "
334 "the difference between assets outstanding and available";
335 result = false;
336 }
337
338 if (afterVault.assetsTotal < zero)
339 {
340 JLOG(j.fatal()) << "Invariant failed: assets outstanding must be positive";
341 result = false;
342 }
343
344 if (afterVault.assetsMaximum < zero)
345 {
346 JLOG(j.fatal()) << "Invariant failed: assets maximum must be positive";
347 result = false;
348 }
349
350 // Thanks to this check we can simply do `assert(!beforeVault_.empty()` when
351 // enforcing invariants on transaction types other than ttVAULT_CREATE
352 if (beforeVault_.empty() && txnType != ttVAULT_CREATE)
353 {
354 JLOG(j.fatal()) << //
355 "Invariant failed: vault created by a wrong transaction type";
356 XRPL_ASSERT(enforce, "xrpl::ValidVault::finalize : vault creation invariant");
357 return !enforce; // That's all we can do here
358 }
359
360 if (!beforeVault_.empty() && afterVault.lossUnrealized != beforeVault_[0].lossUnrealized &&
361 txnType != ttLOAN_MANAGE && txnType != ttLOAN_PAY)
362 {
363 JLOG(j.fatal()) << //
364 "Invariant failed: vault transaction must not change loss "
365 "unrealized";
366 result = false;
367 }
368
369 auto const beforeShares = [&]() -> std::optional<Shares> {
370 if (beforeVault_.empty())
371 return std::nullopt;
372 auto const& beforeVault = beforeVault_[0];
373
374 for (auto const& e : beforeMPTs_)
375 {
376 if (e.share.getMptID() == beforeVault.shareMPTID)
377 return e;
378 }
379 return std::nullopt;
380 }();
381
382 if (!beforeShares &&
383 (tx.getTxnType() == ttVAULT_DEPOSIT || //
384 tx.getTxnType() == ttVAULT_WITHDRAW || //
385 tx.getTxnType() == ttVAULT_CLAWBACK))
386 {
387 JLOG(j.fatal()) << "Invariant failed: vault operation succeeded "
388 "without updating shares";
389 XRPL_ASSERT(enforce, "xrpl::ValidVault::finalize : shares noop invariant");
390 return !enforce; // That's all we can do here
391 }
392
393 auto const& vaultAsset = afterVault.asset;
394 auto const deltaAssets = [&](AccountID const& id) -> std::optional<Number> {
395 auto const get = //
396 [&](auto const& it, std::int8_t sign = 1) -> std::optional<Number> {
397 if (it == deltas_.end())
398 return std::nullopt;
399
400 return it->second * sign;
401 };
402
403 return std::visit(
404 [&]<typename TIss>(TIss const& issue) {
405 if constexpr (std::is_same_v<TIss, Issue>)
406 {
407 if (isXRP(issue))
408 return get(deltas_.find(keylet::account(id).key));
409 return get(
410 deltas_.find(keylet::line(id, issue).key), id > issue.getIssuer() ? -1 : 1);
411 }
412 else if constexpr (std::is_same_v<TIss, MPTIssue>)
413 {
414 return get(deltas_.find(keylet::mptoken(issue.getMptID(), id).key));
415 }
416 },
417 vaultAsset.value());
418 };
419 auto const deltaAssetsTxAccount = [&]() -> std::optional<Number> {
420 auto ret = deltaAssets(tx[sfAccount]);
421 // Nothing returned or not XRP transaction
422 if (!ret.has_value() || !vaultAsset.native())
423 return ret;
424
425 // Delegated transaction; no need to compensate for fees
426 if (auto const delegate = tx[~sfDelegate];
427 delegate.has_value() && *delegate != tx[sfAccount])
428 return ret;
429
430 *ret += fee.drops();
431 if (*ret == zero)
432 return std::nullopt;
433
434 return ret;
435 };
436 auto const deltaShares = [&](AccountID const& id) -> std::optional<Number> {
437 auto const it = [&]() {
438 if (id == afterVault.pseudoId)
439 return deltas_.find(keylet::mptIssuance(afterVault.shareMPTID).key);
440 return deltas_.find(keylet::mptoken(afterVault.shareMPTID, id).key);
441 }();
442
443 return it != deltas_.end() ? std::optional<Number>(it->second) : std::nullopt;
444 };
445
446 auto const vaultHoldsNoAssets = [&](Vault const& vault) {
447 return vault.assetsAvailable == 0 && vault.assetsTotal == 0;
448 };
449
450 // Technically this does not need to be a lambda, but it's more
451 // convenient thanks to early "return false"; the not-so-nice
452 // alternatives are several layers of nested if/else or more complex
453 // (i.e. brittle) if statements.
454 result &= [&]() {
455 switch (txnType)
456 {
457 case ttVAULT_CREATE: {
458 bool result = true;
459
460 if (!beforeVault_.empty())
461 {
462 JLOG(j.fatal()) //
463 << "Invariant failed: create operation must not have "
464 "updated a vault";
465 result = false;
466 }
467
468 if (afterVault.assetsAvailable != zero || afterVault.assetsTotal != zero ||
469 afterVault.lossUnrealized != zero || updatedShares->sharesTotal != 0)
470 {
471 JLOG(j.fatal()) //
472 << "Invariant failed: created vault must be empty";
473 result = false;
474 }
475
476 if (afterVault.pseudoId != updatedShares->share.getIssuer())
477 {
478 JLOG(j.fatal()) //
479 << "Invariant failed: shares issuer and vault "
480 "pseudo-account must be the same";
481 result = false;
482 }
483
484 auto const sleSharesIssuer =
485 view.read(keylet::account(updatedShares->share.getIssuer()));
486 if (!sleSharesIssuer)
487 {
488 JLOG(j.fatal()) //
489 << "Invariant failed: shares issuer must exist";
490 return false;
491 }
492
493 if (!isPseudoAccount(sleSharesIssuer))
494 {
495 JLOG(j.fatal()) //
496 << "Invariant failed: shares issuer must be a "
497 "pseudo-account";
498 result = false;
499 }
500
501 if (auto const vaultId = (*sleSharesIssuer)[~sfVaultID];
502 !vaultId || *vaultId != afterVault.key)
503 {
504 JLOG(j.fatal()) //
505 << "Invariant failed: shares issuer pseudo-account "
506 "must point back to the vault";
507 result = false;
508 }
509
510 return result;
511 }
512 case ttVAULT_SET: {
513 bool result = true;
514
515 XRPL_ASSERT(
516 !beforeVault_.empty(), "xrpl::ValidVault::finalize : set updated a vault");
517 auto const& beforeVault = beforeVault_[0];
518
519 auto const vaultDeltaAssets = deltaAssets(afterVault.pseudoId);
520 if (vaultDeltaAssets)
521 {
522 JLOG(j.fatal()) << //
523 "Invariant failed: set must not change vault balance";
524 result = false;
525 }
526
527 if (beforeVault.assetsTotal != afterVault.assetsTotal)
528 {
529 JLOG(j.fatal()) << //
530 "Invariant failed: set must not change assets "
531 "outstanding";
532 result = false;
533 }
534
535 if (afterVault.assetsMaximum > zero &&
536 afterVault.assetsTotal > afterVault.assetsMaximum)
537 {
538 JLOG(j.fatal()) << //
539 "Invariant failed: set assets outstanding must not "
540 "exceed assets maximum";
541 result = false;
542 }
543
544 if (beforeVault.assetsAvailable != afterVault.assetsAvailable)
545 {
546 JLOG(j.fatal()) << //
547 "Invariant failed: set must not change assets "
548 "available";
549 result = false;
550 }
551
552 if (beforeShares && updatedShares &&
553 beforeShares->sharesTotal != updatedShares->sharesTotal)
554 {
555 JLOG(j.fatal()) << //
556 "Invariant failed: set must not change shares "
557 "outstanding";
558 result = false;
559 }
560
561 return result;
562 }
563 case ttVAULT_DEPOSIT: {
564 bool result = true;
565
566 XRPL_ASSERT(
567 !beforeVault_.empty(), "xrpl::ValidVault::finalize : deposit updated a vault");
568 auto const& beforeVault = beforeVault_[0];
569
570 auto const vaultDeltaAssets = deltaAssets(afterVault.pseudoId);
571
572 if (!vaultDeltaAssets)
573 {
574 JLOG(j.fatal()) << //
575 "Invariant failed: deposit must change vault balance";
576 return false; // That's all we can do
577 }
578
579 if (*vaultDeltaAssets > tx[sfAmount])
580 {
581 JLOG(j.fatal()) << //
582 "Invariant failed: deposit must not change vault "
583 "balance by more than deposited amount";
584 result = false;
585 }
586
587 if (*vaultDeltaAssets <= zero)
588 {
589 JLOG(j.fatal()) << //
590 "Invariant failed: deposit must increase vault balance";
591 result = false;
592 }
593
594 // Any payments (including deposits) made by the issuer
595 // do not change their balance, but create funds instead.
596 bool const issuerDeposit = [&]() -> bool {
597 if (vaultAsset.native())
598 return false;
599 return tx[sfAccount] == vaultAsset.getIssuer();
600 }();
601
602 if (!issuerDeposit)
603 {
604 auto const accountDeltaAssets = deltaAssetsTxAccount();
605 if (!accountDeltaAssets)
606 {
607 JLOG(j.fatal()) << //
608 "Invariant failed: deposit must change depositor "
609 "balance";
610 return false;
611 }
612
613 if (*accountDeltaAssets >= zero)
614 {
615 JLOG(j.fatal()) << //
616 "Invariant failed: deposit must decrease depositor "
617 "balance";
618 result = false;
619 }
620
621 if (*accountDeltaAssets * -1 != *vaultDeltaAssets)
622 {
623 JLOG(j.fatal()) << //
624 "Invariant failed: deposit must change vault and "
625 "depositor balance by equal amount";
626 result = false;
627 }
628 }
629
630 if (afterVault.assetsMaximum > zero &&
631 afterVault.assetsTotal > afterVault.assetsMaximum)
632 {
633 JLOG(j.fatal()) << //
634 "Invariant failed: deposit assets outstanding must not "
635 "exceed assets maximum";
636 result = false;
637 }
638
639 auto const accountDeltaShares = deltaShares(tx[sfAccount]);
640 if (!accountDeltaShares)
641 {
642 JLOG(j.fatal()) << //
643 "Invariant failed: deposit must change depositor "
644 "shares";
645 return false; // That's all we can do
646 }
647
648 if (*accountDeltaShares <= zero)
649 {
650 JLOG(j.fatal()) << //
651 "Invariant failed: deposit must increase depositor "
652 "shares";
653 result = false;
654 }
655
656 auto const vaultDeltaShares = deltaShares(afterVault.pseudoId);
657 if (!vaultDeltaShares || *vaultDeltaShares == zero)
658 {
659 JLOG(j.fatal()) << //
660 "Invariant failed: deposit must change vault shares";
661 return false; // That's all we can do
662 }
663
664 if (*vaultDeltaShares * -1 != *accountDeltaShares)
665 {
666 JLOG(j.fatal()) << //
667 "Invariant failed: deposit must change depositor and "
668 "vault shares by equal amount";
669 result = false;
670 }
671
672 if (beforeVault.assetsTotal + *vaultDeltaAssets != afterVault.assetsTotal)
673 {
674 JLOG(j.fatal()) << "Invariant failed: deposit and assets "
675 "outstanding must add up";
676 result = false;
677 }
678 if (beforeVault.assetsAvailable + *vaultDeltaAssets != afterVault.assetsAvailable)
679 {
680 JLOG(j.fatal()) << "Invariant failed: deposit and assets "
681 "available must add up";
682 result = false;
683 }
684
685 return result;
686 }
687 case ttVAULT_WITHDRAW: {
688 bool result = true;
689
690 XRPL_ASSERT(
691 !beforeVault_.empty(),
692 "xrpl::ValidVault::finalize : withdrawal updated a "
693 "vault");
694 auto const& beforeVault = beforeVault_[0];
695
696 auto const vaultDeltaAssets = deltaAssets(afterVault.pseudoId);
697
698 if (!vaultDeltaAssets)
699 {
700 JLOG(j.fatal()) << "Invariant failed: withdrawal must "
701 "change vault balance";
702 return false; // That's all we can do
703 }
704
705 if (*vaultDeltaAssets >= zero)
706 {
707 JLOG(j.fatal()) << "Invariant failed: withdrawal must "
708 "decrease vault balance";
709 result = false;
710 }
711
712 // Any payments (including withdrawal) going to the issuer
713 // do not change their balance, but destroy funds instead.
714 bool const issuerWithdrawal = [&]() -> bool {
715 if (vaultAsset.native())
716 return false;
717 auto const destination = tx[~sfDestination].value_or(tx[sfAccount]);
718 return destination == vaultAsset.getIssuer();
719 }();
720
721 if (!issuerWithdrawal)
722 {
723 auto const accountDeltaAssets = deltaAssetsTxAccount();
724 auto const otherAccountDelta = [&]() -> std::optional<Number> {
725 if (auto const destination = tx[~sfDestination];
726 destination && *destination != tx[sfAccount])
727 return deltaAssets(*destination);
728 return std::nullopt;
729 }();
730
731 if (accountDeltaAssets.has_value() == otherAccountDelta.has_value())
732 {
733 JLOG(j.fatal()) << //
734 "Invariant failed: withdrawal must change one "
735 "destination balance";
736 return false;
737 }
738
739 auto const destinationDelta = //
740 accountDeltaAssets ? *accountDeltaAssets : *otherAccountDelta;
741
742 if (destinationDelta <= zero)
743 {
744 JLOG(j.fatal()) << //
745 "Invariant failed: withdrawal must increase "
746 "destination balance";
747 result = false;
748 }
749
750 if (*vaultDeltaAssets * -1 != destinationDelta)
751 {
752 JLOG(j.fatal()) << //
753 "Invariant failed: withdrawal must change vault "
754 "and destination balance by equal amount";
755 result = false;
756 }
757 }
758
759 auto const accountDeltaShares = deltaShares(tx[sfAccount]);
760 if (!accountDeltaShares)
761 {
762 JLOG(j.fatal()) << //
763 "Invariant failed: withdrawal must change depositor "
764 "shares";
765 return false;
766 }
767
768 if (*accountDeltaShares >= zero)
769 {
770 JLOG(j.fatal()) << //
771 "Invariant failed: withdrawal must decrease depositor "
772 "shares";
773 result = false;
774 }
775
776 auto const vaultDeltaShares = deltaShares(afterVault.pseudoId);
777 if (!vaultDeltaShares || *vaultDeltaShares == zero)
778 {
779 JLOG(j.fatal()) << //
780 "Invariant failed: withdrawal must change vault shares";
781 return false; // That's all we can do
782 }
783
784 if (*vaultDeltaShares * -1 != *accountDeltaShares)
785 {
786 JLOG(j.fatal()) << //
787 "Invariant failed: withdrawal must change depositor "
788 "and vault shares by equal amount";
789 result = false;
790 }
791
792 // Note, vaultBalance is negative (see check above)
793 if (beforeVault.assetsTotal + *vaultDeltaAssets != afterVault.assetsTotal)
794 {
795 JLOG(j.fatal()) << "Invariant failed: withdrawal and "
796 "assets outstanding must add up";
797 result = false;
798 }
799
800 if (beforeVault.assetsAvailable + *vaultDeltaAssets != afterVault.assetsAvailable)
801 {
802 JLOG(j.fatal()) << "Invariant failed: withdrawal and "
803 "assets available must add up";
804 result = false;
805 }
806
807 return result;
808 }
809 case ttVAULT_CLAWBACK: {
810 bool result = true;
811
812 XRPL_ASSERT(
813 !beforeVault_.empty(), "xrpl::ValidVault::finalize : clawback updated a vault");
814 auto const& beforeVault = beforeVault_[0];
815
816 if (vaultAsset.native() || vaultAsset.getIssuer() != tx[sfAccount])
817 {
818 // The owner can use clawback to force-burn shares when the
819 // vault is empty but there are outstanding shares
820 if (!(beforeShares && beforeShares->sharesTotal > 0 &&
821 vaultHoldsNoAssets(beforeVault) && beforeVault.owner == tx[sfAccount]))
822 {
823 JLOG(j.fatal()) << //
824 "Invariant failed: clawback may only be performed "
825 "by the asset issuer, or by the vault owner of an "
826 "empty vault";
827 return false; // That's all we can do
828 }
829 }
830
831 auto const vaultDeltaAssets = deltaAssets(afterVault.pseudoId);
832 if (vaultDeltaAssets)
833 {
834 if (*vaultDeltaAssets >= zero)
835 {
836 JLOG(j.fatal()) << //
837 "Invariant failed: clawback must decrease vault "
838 "balance";
839 result = false;
840 }
841
842 if (beforeVault.assetsTotal + *vaultDeltaAssets != afterVault.assetsTotal)
843 {
844 JLOG(j.fatal()) << //
845 "Invariant failed: clawback and assets outstanding "
846 "must add up";
847 result = false;
848 }
849
850 if (beforeVault.assetsAvailable + *vaultDeltaAssets !=
851 afterVault.assetsAvailable)
852 {
853 JLOG(j.fatal()) << //
854 "Invariant failed: clawback and assets available "
855 "must add up";
856 result = false;
857 }
858 }
859 else if (!vaultHoldsNoAssets(beforeVault))
860 {
861 JLOG(j.fatal()) << //
862 "Invariant failed: clawback must change vault balance";
863 return false; // That's all we can do
864 }
865
866 auto const accountDeltaShares = deltaShares(tx[sfHolder]);
867 if (!accountDeltaShares)
868 {
869 JLOG(j.fatal()) << //
870 "Invariant failed: clawback must change holder shares";
871 return false; // That's all we can do
872 }
873
874 if (*accountDeltaShares >= zero)
875 {
876 JLOG(j.fatal()) << //
877 "Invariant failed: clawback must decrease holder "
878 "shares";
879 result = false;
880 }
881
882 auto const vaultDeltaShares = deltaShares(afterVault.pseudoId);
883 if (!vaultDeltaShares || *vaultDeltaShares == zero)
884 {
885 JLOG(j.fatal()) << //
886 "Invariant failed: clawback must change vault shares";
887 return false; // That's all we can do
888 }
889
890 if (*vaultDeltaShares * -1 != *accountDeltaShares)
891 {
892 JLOG(j.fatal()) << //
893 "Invariant failed: clawback must change holder and "
894 "vault shares by equal amount";
895 result = false;
896 }
897
898 return result;
899 }
900
901 case ttLOAN_SET:
902 case ttLOAN_MANAGE:
903 case ttLOAN_PAY: {
904 // TBD
905 return true;
906 }
907
908 default:
909 // LCOV_EXCL_START
910 UNREACHABLE("xrpl::ValidVault::finalize : unknown transaction type");
911 return false;
912 // LCOV_EXCL_STOP
913 }
914 }();
915
916 if (!result)
917 {
918 // The comment at the top of this file starting with "assert(enforce)"
919 // explains this assert.
920 XRPL_ASSERT(enforce, "xrpl::ValidVault::finalize : vault invariants");
921 return !enforce;
922 }
923
924 return true;
925}
926
927} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:40
Stream fatal() const
Definition Journal.h:325
Number is a floating point type that can represent a wide range of values.
Definition Number.h:207
A view into a ledger.
Definition ReadView.h:31
virtual Rules const & rules() const =0
Returns the tx processing rules.
virtual std::shared_ptr< SLE const > read(Keylet const &k) const =0
Return the state item associated with a key.
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition Rules.cpp:120
uint256 const & key() const
Returns the 'key' (or 'index') of this item.
LedgerEntryType getType() const
uint192 getFieldH192(SField const &field) const
Definition STObject.cpp:617
T::value_type at(TypedField< T > const &f) const
Get the value of a field.
Definition STObject.h:1055
std::uint32_t getFieldU32(SField const &field) const
Definition STObject.cpp:593
AccountID getAccountID(SField const &field) const
Definition STObject.cpp:635
TxType getTxnType() const
Definition STTx.h:188
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
static Number constexpr zero
std::vector< Shares > afterMPTs_
std::unordered_map< uint256, Number > deltas_
std::vector< Vault > afterVault_
std::vector< Shares > beforeMPTs_
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
std::vector< Vault > beforeVault_
constexpr value_type drops() const
Returns the number of drops.
Definition XRPAmount.h:157
T is_same_v
Keylet mptIssuance(std::uint32_t seq, AccountID const &issuer) noexcept
Definition Indexes.cpp:474
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:220
Keylet mptoken(MPTID const &issuanceID, AccountID const &holder) noexcept
Definition Indexes.cpp:486
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:165
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
bool isXRP(AccountID const &c)
Definition AccountID.h:70
T get(Section const &section, std::string const &name, T const &defaultValue=T{})
Retrieve a key/value pair from a section.
std::uint64_t constexpr maxMPTokenAmount
The maximum amount of MPTokenIssuance.
Definition Protocol.h:234
bool isPseudoAccount(std::shared_ptr< SLE const > sleAcct, std::set< SField const * > const &pseudoFieldFilter={})
Returns true if and only if sleAcct is a pseudo-account or specific pseudo-accounts in pseudoFieldFil...
bool hasPrivilege(STTx const &tx, Privilege priv)
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition View.cpp:523
bool isTesSuccess(TER x) noexcept
Definition TER.h:651
Buffer sign(PublicKey const &pk, SecretKey const &sk, Slice const &message)
Generate a signature for a message.
MPTID makeMptID(std::uint32_t sequence, AccountID const &account)
Definition Indexes.cpp:151
uint256 key
Definition Keylet.h:20
static Shares make(SLE const &)
static Vault make(SLE const &)
T visit(T... args)