342 bool const enforce = view.
rules().
enabled(featureSingleAssetVault);
352 "Invariant failed: vault operation succeeded without modifying "
354 XRPL_ASSERT(enforce,
"xrpl::ValidVault::finalize : vault noop invariant");
363 "Invariant failed: vault updated by a wrong transaction type";
366 "xrpl::ValidVault::finalize : illegal vault transaction "
374 "Invariant failed: vault operation updated more than single vault";
375 XRPL_ASSERT(enforce,
"xrpl::ValidVault::finalize : single vault invariant");
385 if (txnType != ttVAULT_DELETE)
388 "Invariant failed: vault deleted by a wrong transaction type";
391 "xrpl::ValidVault::finalize : illegal vault deletion "
406 if (e.share.getMptID() == beforeVault.shareMPTID)
414 JLOG(j.
fatal()) <<
"Invariant failed: deleted vault must also "
416 XRPL_ASSERT(enforce,
"xrpl::ValidVault::finalize : shares deletion invariant");
421 if (deletedShares->sharesTotal != 0)
423 JLOG(j.
fatal()) <<
"Invariant failed: deleted vault must have no "
424 "shares outstanding";
427 if (beforeVault.assetsTotal !=
kZero)
429 JLOG(j.
fatal()) <<
"Invariant failed: deleted vault must have no "
430 "assets outstanding";
433 if (beforeVault.assetsAvailable !=
kZero)
435 JLOG(j.
fatal()) <<
"Invariant failed: deleted vault must have no "
442 if (txnType == ttVAULT_DELETE)
444 JLOG(j.
fatal()) <<
"Invariant failed: vault deletion succeeded without "
446 XRPL_ASSERT(enforce,
"xrpl::ValidVault::finalize : vault deletion invariant");
454 "xrpl::ValidVault::finalize : single vault operation");
464 if (e.share.getMptID() == afterVault.shareMPTID)
479 if (afterVault.asset != beforeVault.asset || afterVault.pseudoId != beforeVault.pseudoId ||
480 afterVault.shareMPTID != beforeVault.shareMPTID)
482 JLOG(j.
fatal()) <<
"Invariant failed: violation of vault immutable data";
489 JLOG(j.
fatal()) <<
"Invariant failed: updated vault must have shares";
490 XRPL_ASSERT(enforce,
"xrpl::ValidVault::finalize : vault has shares invariant");
494 if (updatedShares->sharesTotal == 0)
496 if (afterVault.assetsTotal !=
kZero)
498 JLOG(j.
fatal()) <<
"Invariant failed: updated zero sized "
499 "vault must have no assets outstanding";
502 if (afterVault.assetsAvailable !=
kZero)
504 JLOG(j.
fatal()) <<
"Invariant failed: updated zero sized "
505 "vault must have no assets available";
509 else if (updatedShares->sharesTotal > updatedShares->sharesMaximum)
512 <<
"Invariant failed: updated shares must not exceed maximum "
513 << updatedShares->sharesMaximum;
517 if (afterVault.assetsAvailable <
kZero)
519 JLOG(j.
fatal()) <<
"Invariant failed: assets available must not be negative";
523 if (afterVault.assetsAvailable > afterVault.assetsTotal)
525 JLOG(j.
fatal()) <<
"Invariant failed: assets available must "
526 "not be greater than assets outstanding";
529 else if (afterVault.lossUnrealized > afterVault.assetsTotal - afterVault.assetsAvailable)
532 <<
"Invariant failed: loss unrealized must not exceed "
533 "the difference between assets outstanding and available";
539 JLOG(j.
fatal()) <<
"Invariant failed: loss unrealized must not be negative";
543 if (afterVault.assetsTotal <
kZero)
545 JLOG(j.
fatal()) <<
"Invariant failed: assets outstanding must not be negative";
549 if (afterVault.assetsMaximum <
kZero)
551 JLOG(j.
fatal()) <<
"Invariant failed: assets maximum must not be negative";
560 "Invariant failed: vault created by a wrong transaction type";
561 XRPL_ASSERT(enforce,
"xrpl::ValidVault::finalize : vault creation invariant");
566 txnType != ttLOAN_MANAGE && txnType != ttLOAN_PAY)
569 "Invariant failed: vault transaction must not change loss "
584 if (e.share.getMptID() == beforeVault.shareMPTID)
595 JLOG(j.
fatal()) <<
"Invariant failed: vault operation succeeded "
596 "without updating shares";
597 XRPL_ASSERT(enforce,
"xrpl::ValidVault::finalize : shares noop invariant");
601 auto const& vaultAsset = afterVault.asset;
610 case ttVAULT_CREATE: {
616 <<
"Invariant failed: create operation must not have "
621 if (afterVault.assetsAvailable !=
kZero || afterVault.assetsTotal !=
kZero ||
622 afterVault.lossUnrealized !=
kZero || updatedShares->sharesTotal != 0)
625 <<
"Invariant failed: created vault must be empty";
629 if (afterVault.pseudoId != updatedShares->share.getIssuer())
632 <<
"Invariant failed: shares issuer and vault "
633 "pseudo-account must be the same";
637 auto const sleSharesIssuer =
639 if (!sleSharesIssuer)
642 <<
"Invariant failed: shares issuer must exist";
649 <<
"Invariant failed: shares issuer must be a "
654 if (
auto const vaultId = (*sleSharesIssuer)[~sfVaultID];
655 !vaultId || *vaultId != afterVault.key)
658 <<
"Invariant failed: shares issuer pseudo-account "
659 "must point back to the vault";
663 if (isClosedEnded(afterVault.vaultKind))
665 if (!afterVault.subscriptionDate || !afterVault.redemptionDate)
668 <<
"Invariant failed: closed-ended vault must have SubscriptionDate "
669 "and RedemptionDate";
673 *afterVault.subscriptionDate, *afterVault.redemptionDate))
676 <<
"Invariant failed: closed-ended vault RedemptionDate - "
677 "SubscriptionDate must be within [MIN_INVESTMENT_PERIOD, "
678 "MAX_INVESTMENT_PERIOD)";
689 !
beforeVault_.empty(),
"xrpl::ValidVault::finalize : set updated a vault");
692 auto const vaultDeltaAssets =
deltaAssets(afterVault.pseudoId);
693 if (vaultDeltaAssets)
696 "Invariant failed: set must not change vault balance";
700 if (beforeVault.assetsTotal != afterVault.assetsTotal)
703 "Invariant failed: set must not change assets "
708 if (afterVault.assetsMaximum >
kZero &&
709 afterVault.assetsTotal > afterVault.assetsMaximum)
712 "Invariant failed: set assets outstanding must not "
713 "exceed assets maximum";
717 if (beforeVault.assetsAvailable != afterVault.assetsAvailable)
720 "Invariant failed: set must not change assets "
725 if (beforeShares && updatedShares &&
726 beforeShares->sharesTotal != updatedShares->sharesTotal)
729 "Invariant failed: set must not change shares "
736 case ttVAULT_DEPOSIT: {
740 !
beforeVault_.empty(),
"xrpl::ValidVault::finalize : deposit updated a vault");
747 afterVault.vaultKind,
748 afterVault.subscriptionDate,
749 afterVault.redemptionDate);
753 "Invariant failed: deposit only allowed in "
754 "Subscription or NoPhase";
758 auto const maybeVaultDeltaAssets =
deltaAssets(afterVault.pseudoId);
759 if (!maybeVaultDeltaAssets)
762 "Invariant failed: deposit must change vault balance";
769 auto const vaultDeltaAssets =
770 roundToAsset(vaultAsset, maybeVaultDeltaAssets->delta, minScale);
771 auto const txAmount =
roundToAsset(vaultAsset, tx[sfAmount], minScale);
773 if (vaultDeltaAssets > txAmount)
776 "Invariant failed: deposit must not change vault "
777 "balance by more than deposited amount";
781 if (vaultDeltaAssets <=
kZero)
784 "Invariant failed: deposit must increase vault balance";
790 bool const issuerDeposit = [&]() ->
bool {
791 if (vaultAsset.native())
793 return tx[sfAccount] == vaultAsset.getIssuer();
799 if (!maybeAccDeltaAssets)
802 <<
"Invariant failed: deposit must change depositor balance";
805 auto const localMinScale =
808 auto const accountDeltaAssets =
809 roundToAsset(vaultAsset, maybeAccDeltaAssets->delta, localMinScale);
810 auto const localVaultDeltaAssets =
811 roundToAsset(vaultAsset, vaultDeltaAssets, localMinScale);
816 if (accountDeltaAssets >=
kZero)
819 <<
"Invariant failed: deposit must decrease depositor balance";
823 if (localVaultDeltaAssets * -1 != accountDeltaAssets)
825 JLOG(j.
fatal()) <<
"Invariant failed: " <<
826 "deposit must change vault and depositor balance by equal amount";
831 if (afterVault.assetsMaximum >
kZero &&
832 afterVault.assetsTotal > afterVault.assetsMaximum)
834 JLOG(j.
fatal()) <<
"Invariant failed: " <<
835 "deposit assets outstanding must not exceed assets maximum";
839 auto const maybeAccDeltaShares =
deltaShares(tx[sfAccount]);
840 if (!maybeAccDeltaShares)
842 JLOG(j.
fatal()) <<
"Invariant failed: deposit must change depositor shares";
846 auto const& accountDeltaShares = *maybeAccDeltaShares;
847 if (accountDeltaShares.delta <=
kZero)
849 JLOG(j.
fatal()) <<
"Invariant failed: deposit must increase depositor shares";
853 auto const maybeVaultDeltaShares =
deltaShares(afterVault.pseudoId);
854 if (!maybeVaultDeltaShares || maybeVaultDeltaShares->delta ==
kZero)
856 JLOG(j.
fatal()) <<
"Invariant failed: deposit must change vault shares";
861 auto const& vaultDeltaShares = *maybeVaultDeltaShares;
862 if (vaultDeltaShares.delta * -1 != accountDeltaShares.delta)
864 JLOG(j.
fatal()) <<
"Invariant failed: " <<
865 "deposit must change depositor and vault shares by equal amount";
870 vaultAsset, afterVault.assetsTotal - beforeVault.assetsTotal, minScale);
871 if (assetTotalDelta != vaultDeltaAssets)
874 <<
"Invariant failed: deposit and assets outstanding must add up";
879 vaultAsset, afterVault.assetsAvailable - beforeVault.assetsAvailable, minScale);
880 if (assetAvailableDelta != vaultDeltaAssets)
882 JLOG(j.
fatal()) <<
"Invariant failed: deposit and assets available must add up";
888 case ttVAULT_WITHDRAW: {
893 "xrpl::ValidVault::finalize : withdrawal updated a vault");
900 afterVault.vaultKind,
901 afterVault.subscriptionDate,
905 "Invariant failed: withdrawal not allowed during "
910 auto const maybeVaultDeltaAssets =
deltaAssets(afterVault.pseudoId);
911 if (!maybeVaultDeltaAssets)
913 JLOG(j.
fatal()) <<
"Invariant failed: withdrawal must change vault balance";
920 auto const vaultPseudoDeltaAssets =
921 roundToAsset(vaultAsset, maybeVaultDeltaAssets->delta, minScale);
923 if (vaultPseudoDeltaAssets >=
kZero)
925 JLOG(j.
fatal()) <<
"Invariant failed: withdrawal must decrease vault balance";
931 bool const issuerWithdrawal = [&]() ->
bool {
932 if (vaultAsset.native())
934 auto const destination = tx[~sfDestination].value_or(tx[sfAccount]);
935 return destination == vaultAsset.getIssuer();
938 if (!issuerWithdrawal)
942 if (
auto const destination = tx[~sfDestination];
943 destination && *destination != tx[sfAccount])
948 if (maybeAccDelta.has_value() == maybeOtherAccDelta.has_value())
951 "Invariant failed: withdrawal must change one destination balance";
955 auto const destinationDelta =
956 maybeAccDelta ? *maybeAccDelta : *maybeOtherAccDelta;
961 auto const localMinScale =
std::max(minScale, destinationScale);
963 auto const roundedDestinationDelta =
964 roundToAsset(vaultAsset, destinationDelta.delta, localMinScale);
972 bool const tolerateZeroDelta =
973 view.
rules().
enabled(fixCleanup3_2_0) && !vaultAsset.integral();
974 auto const invalidBalanceChange = tolerateZeroDelta
975 ? roundedDestinationDelta <
kZero
976 : roundedDestinationDelta <=
kZero;
977 if (invalidBalanceChange)
980 "Invariant failed: withdrawal must increase destination balance";
984 auto const localPseudoDeltaAssets =
985 roundToAsset(vaultAsset, vaultPseudoDeltaAssets, localMinScale);
994 auto const destroyedIsSubUlp = tolerateZeroDelta &&
997 maybeVaultDeltaAssets->delta * -1 - destinationDelta.delta,
1000 if (!destroyedIsSubUlp &&
1001 localPseudoDeltaAssets * -1 != roundedDestinationDelta)
1003 JLOG(j.
fatal()) <<
"Invariant failed: " <<
1004 "withdrawal must change vault and destination balance by equal "
1011 auto const accountDeltaShares =
deltaShares(tx[sfAccount]);
1012 if (!accountDeltaShares)
1014 JLOG(j.
fatal()) <<
"Invariant failed: withdrawal must change depositor shares";
1018 if (accountDeltaShares->delta >=
kZero)
1021 <<
"Invariant failed: withdrawal must decrease depositor shares";
1026 auto const vaultDeltaShares =
deltaShares(afterVault.pseudoId);
1027 if (!vaultDeltaShares || vaultDeltaShares->delta ==
kZero)
1029 JLOG(j.
fatal()) <<
"Invariant failed: withdrawal must change vault shares";
1033 if (vaultDeltaShares->delta * -1 != accountDeltaShares->delta)
1035 JLOG(j.
fatal()) <<
"Invariant failed: " <<
1036 "withdrawal must change depositor and vault shares by equal amount";
1041 vaultAsset, afterVault.assetsTotal - beforeVault.assetsTotal, minScale);
1043 if (assetTotalDelta != vaultPseudoDeltaAssets)
1046 <<
"Invariant failed: withdrawal and assets outstanding must add up";
1051 vaultAsset, afterVault.assetsAvailable - beforeVault.assetsAvailable, minScale);
1053 if (assetAvailableDelta != vaultPseudoDeltaAssets)
1056 <<
"Invariant failed: withdrawal and assets available must add up";
1062 case ttVAULT_CLAWBACK: {
1066 !
beforeVault_.empty(),
"xrpl::ValidVault::finalize : clawback updated a vault");
1069 if (vaultAsset.native() || vaultAsset.getIssuer() != tx[sfAccount])
1073 if (!(beforeShares && beforeShares->sharesTotal > 0 &&
1074 isVaultEmpty(beforeVault) && beforeVault.owner == tx[sfAccount]))
1076 JLOG(j.
fatal()) <<
"Invariant failed: " <<
1077 "clawback may only be performed by the asset issuer, or by the vault "
1078 "owner of an empty vault";
1083 auto const maybeVaultDeltaAssets =
deltaAssets(afterVault.pseudoId);
1084 if (maybeVaultDeltaAssets)
1086 auto const minScale =
1088 auto const vaultDeltaAssets =
1089 roundToAsset(vaultAsset, maybeVaultDeltaAssets->delta, minScale);
1090 if (vaultDeltaAssets >=
kZero)
1092 JLOG(j.
fatal()) <<
"Invariant failed: clawback must decrease vault balance";
1097 vaultAsset, afterVault.assetsTotal - beforeVault.assetsTotal, minScale);
1098 if (assetsTotalDelta != vaultDeltaAssets)
1101 "Invariant failed: clawback and assets outstanding must add up";
1107 afterVault.assetsAvailable - beforeVault.assetsAvailable,
1109 if (assetAvailableDelta != vaultDeltaAssets)
1112 "Invariant failed: clawback and assets available must add up";
1119 "Invariant failed: clawback must change vault balance";
1124 auto const maybeAccountDeltaShares =
deltaShares(tx[sfHolder]);
1125 if (!maybeAccountDeltaShares)
1128 "Invariant failed: clawback must change holder shares";
1131 if (maybeAccountDeltaShares->delta >=
kZero)
1134 "Invariant failed: clawback must decrease holder shares";
1139 auto const vaultDeltaShares =
deltaShares(afterVault.pseudoId);
1140 if (!vaultDeltaShares || vaultDeltaShares->delta ==
kZero)
1143 "Invariant failed: clawback must change vault shares";
1147 if (vaultDeltaShares->delta * -1 != maybeAccountDeltaShares->delta)
1149 JLOG(j.
fatal()) <<
"Invariant failed: " <<
1150 "clawback must change holder and vault shares by equal amount";
1165 UNREACHABLE(
"xrpl::ValidVault::finalize : unknown transaction type");
1175 XRPL_ASSERT(enforce,
"xrpl::ValidVault::finalize : vault invariants");