419 bool const enforce = view.
rules().
enabled(featureSingleAssetVault);
420 bool const fix340Enabled = view.
rules().
enabled(fixCleanup3_4_0);
430 "Invariant failed: vault operation succeeded without modifying "
432 XRPL_ASSERT(enforce,
"xrpl::ValidVault::finalize : vault noop invariant");
442 "Invariant failed: vault updated by a wrong transaction type";
445 "xrpl::ValidVault::finalize : illegal vault transaction "
453 "Invariant failed: vault operation updated more than single vault";
454 XRPL_ASSERT(enforce,
"xrpl::ValidVault::finalize : single vault invariant");
464 if (txnType != ttVAULT_DELETE)
467 "Invariant failed: vault deleted by a wrong transaction type";
470 "xrpl::ValidVault::finalize : illegal vault deletion "
485 if (e.share.getMptID() == beforeVault.shareMPTID)
493 JLOG(j.
fatal()) <<
"Invariant failed: deleted vault must also "
495 XRPL_ASSERT(enforce,
"xrpl::ValidVault::finalize : shares deletion invariant");
500 if (deletedShares->sharesTotal != 0)
502 JLOG(j.
fatal()) <<
"Invariant failed: deleted vault must have no "
503 "shares outstanding";
506 if (beforeVault.assetsTotal !=
kZero)
508 JLOG(j.
fatal()) <<
"Invariant failed: deleted vault must have no "
509 "assets outstanding";
512 if (beforeVault.assetsAvailable !=
kZero)
514 JLOG(j.
fatal()) <<
"Invariant failed: deleted vault must have no "
521 if (txnType == ttVAULT_DELETE)
523 JLOG(j.
fatal()) <<
"Invariant failed: vault deletion succeeded without "
525 XRPL_ASSERT(enforce,
"xrpl::ValidVault::finalize : vault deletion invariant");
533 "xrpl::ValidVault::finalize : single vault operation");
543 if (e.share.getMptID() == afterVault.shareMPTID)
559 if (afterVault.asset != beforeVault.asset || afterVault.pseudoId != beforeVault.pseudoId ||
560 afterVault.shareMPTID != beforeVault.shareMPTID)
562 JLOG(j.
fatal()) <<
"Invariant failed: violation of vault immutable data";
569 JLOG(j.
fatal()) <<
"Invariant failed: updated vault must have shares";
570 XRPL_ASSERT(enforce,
"xrpl::ValidVault::finalize : vault has shares invariant");
574 if (updatedShares->sharesTotal == 0)
576 if (afterVault.assetsTotal !=
kZero)
578 JLOG(j.
fatal()) <<
"Invariant failed: updated zero sized "
579 "vault must have no assets outstanding";
582 if (afterVault.assetsAvailable !=
kZero)
584 JLOG(j.
fatal()) <<
"Invariant failed: updated zero sized "
585 "vault must have no assets available";
589 else if (updatedShares->sharesTotal > updatedShares->sharesMaximum)
592 <<
"Invariant failed: updated shares must not exceed maximum "
593 << updatedShares->sharesMaximum;
597 if (afterVault.assetsAvailable <
kZero)
599 JLOG(j.
fatal()) <<
"Invariant failed: assets available must not be negative";
603 if (afterVault.assetsAvailable > afterVault.assetsTotal)
605 JLOG(j.
fatal()) <<
"Invariant failed: assets available must "
606 "not be greater than assets outstanding";
611 bool const gapExceeded = [&] {
614 return afterVault.lossUnrealized >
615 afterVault.assetsTotal - afterVault.assetsAvailable;
618 auto const s =
scale(afterVault.assetsTotal, afterVault.asset);
619 return !lessOrEqualPlusOneUnit(
620 afterVault.lossUnrealized,
621 afterVault.assetsTotal - afterVault.assetsAvailable,
628 <<
"Invariant failed: loss unrealized must not exceed "
629 "the difference between assets outstanding and available";
634 if (fix340Enabled && afterVault.lossUnrealized <
kZero)
636 JLOG(j.
fatal()) <<
"Invariant failed: loss unrealized must not be negative";
640 if (afterVault.assetsTotal <
kZero)
642 JLOG(j.
fatal()) <<
"Invariant failed: assets outstanding must not be negative";
646 if (afterVault.assetsMaximum <
kZero)
648 JLOG(j.
fatal()) <<
"Invariant failed: assets maximum must not be negative";
657 "Invariant failed: vault created by a wrong transaction type";
658 XRPL_ASSERT(enforce,
"xrpl::ValidVault::finalize : vault creation invariant");
663 txnType != ttLOAN_MANAGE && txnType != ttLOAN_PAY)
666 "Invariant failed: vault transaction must not change loss "
681 if (e.share.getMptID() == beforeVault.shareMPTID)
692 JLOG(j.
fatal()) <<
"Invariant failed: vault operation succeeded "
693 "without updating shares";
694 XRPL_ASSERT(enforce,
"xrpl::ValidVault::finalize : shares noop invariant");
698 auto const& vaultAsset = afterVault.asset;
707 case ttVAULT_CREATE: {
713 <<
"Invariant failed: create operation must not have "
718 if (afterVault.assetsAvailable !=
kZero || afterVault.assetsTotal !=
kZero ||
719 afterVault.lossUnrealized !=
kZero || updatedShares->sharesTotal != 0)
722 <<
"Invariant failed: created vault must be empty";
726 if (afterVault.pseudoId != updatedShares->share.getIssuer())
729 <<
"Invariant failed: shares issuer and vault "
730 "pseudo-account must be the same";
734 auto const sleSharesIssuer =
736 if (!sleSharesIssuer)
739 <<
"Invariant failed: shares issuer must exist";
746 <<
"Invariant failed: shares issuer must be a "
751 if (
auto const vaultId = (*sleSharesIssuer)[~sfVaultID];
752 !vaultId || *vaultId != afterVault.key)
755 <<
"Invariant failed: shares issuer pseudo-account "
756 "must point back to the vault";
760 if (isClosedEnded(afterVault.vaultKind))
762 if (!afterVault.subscriptionDate || !afterVault.redemptionDate)
765 <<
"Invariant failed: closed-ended vault must have SubscriptionDate "
766 "and RedemptionDate";
770 *afterVault.subscriptionDate, *afterVault.redemptionDate))
773 <<
"Invariant failed: closed-ended vault RedemptionDate - "
774 "SubscriptionDate must be within [MIN_INVESTMENT_PERIOD, "
775 "MAX_INVESTMENT_PERIOD)";
786 !
beforeVault_.empty(),
"xrpl::ValidVault::finalize : set updated a vault");
789 auto const vaultDeltaAssets =
deltaAssets(afterVault.pseudoId);
790 if (vaultDeltaAssets)
793 "Invariant failed: set must not change vault balance";
797 if (beforeVault.assetsTotal != afterVault.assetsTotal)
800 "Invariant failed: set must not change assets "
808 if (afterVault.assetsMaximum >
kZero &&
809 afterVault.assetsTotal > afterVault.assetsMaximum &&
811 beforeVault.assetsMaximum != afterVault.assetsMaximum))
814 "Invariant failed: set assets outstanding must not "
815 "exceed assets maximum";
819 if (beforeVault.assetsAvailable != afterVault.assetsAvailable)
822 "Invariant failed: set must not change assets "
827 if (beforeShares && updatedShares &&
828 beforeShares->sharesTotal != updatedShares->sharesTotal)
831 "Invariant failed: set must not change shares "
838 case ttVAULT_DEPOSIT: {
842 !
beforeVault_.empty(),
"xrpl::ValidVault::finalize : deposit updated a vault");
849 afterVault.vaultKind,
850 afterVault.subscriptionDate,
851 afterVault.redemptionDate);
855 "Invariant failed: deposit only allowed in "
856 "Subscription or NoPhase";
860 auto const maybeVaultDeltaAssets =
deltaAssets(afterVault.pseudoId);
861 if (!maybeVaultDeltaAssets)
864 "Invariant failed: deposit must change vault balance";
871 auto const vaultDeltaAssets =
872 roundToAsset(vaultAsset, maybeVaultDeltaAssets->delta, minScale);
873 auto const txAmount =
roundToAsset(vaultAsset, tx[sfAmount], minScale);
875 if (vaultDeltaAssets > txAmount)
878 "Invariant failed: deposit must not change vault "
879 "balance by more than deposited amount";
883 if (vaultDeltaAssets <=
kZero)
886 "Invariant failed: deposit must increase vault balance";
892 bool const issuerDeposit = [&]() ->
bool {
893 if (vaultAsset.native())
895 return tx[sfAccount] == vaultAsset.getIssuer();
900 auto const maybeAccDeltaAssets =
902 if (!maybeAccDeltaAssets)
905 <<
"Invariant failed: deposit must change depositor balance";
908 auto const localMinScale =
911 auto const accountDeltaAssets =
912 roundToAsset(vaultAsset, maybeAccDeltaAssets->delta, localMinScale);
913 auto const localVaultDeltaAssets =
914 roundToAsset(vaultAsset, vaultDeltaAssets, localMinScale);
919 if (accountDeltaAssets >=
kZero)
922 <<
"Invariant failed: deposit must decrease depositor balance";
926 bool const acctVaultAddsUp = fix340Enabled
927 ? agreesWithinOneUnit(
928 localVaultDeltaAssets * -1,
932 : localVaultDeltaAssets * -1 == accountDeltaAssets;
933 if (!acctVaultAddsUp)
935 JLOG(j.
fatal()) <<
"Invariant failed: " <<
936 "deposit must change vault and depositor balance by equal amount";
941 if (afterVault.assetsMaximum >
kZero &&
942 afterVault.assetsTotal > afterVault.assetsMaximum)
944 JLOG(j.
fatal()) <<
"Invariant failed: " <<
945 "deposit assets outstanding must not exceed assets maximum";
949 auto const maybeAccDeltaShares =
deltaShares(tx[sfAccount]);
950 if (!maybeAccDeltaShares)
952 JLOG(j.
fatal()) <<
"Invariant failed: deposit must change depositor shares";
956 auto const& accountDeltaShares = *maybeAccDeltaShares;
957 if (accountDeltaShares.delta <=
kZero)
959 JLOG(j.
fatal()) <<
"Invariant failed: deposit must increase depositor shares";
963 auto const maybeVaultDeltaShares =
deltaShares(afterVault.pseudoId);
964 if (!maybeVaultDeltaShares || maybeVaultDeltaShares->delta ==
kZero)
966 JLOG(j.
fatal()) <<
"Invariant failed: deposit must change vault shares";
971 auto const& vaultDeltaShares = *maybeVaultDeltaShares;
972 if (vaultDeltaShares.delta * -1 != accountDeltaShares.delta)
974 JLOG(j.
fatal()) <<
"Invariant failed: " <<
975 "deposit must change depositor and vault shares by equal amount";
980 vaultAsset, afterVault.assetsTotal - beforeVault.assetsTotal, minScale);
981 bool const totalAddsUp = fix340Enabled
982 ? agreesWithinOneUnit(assetTotalDelta, vaultDeltaAssets, vaultAsset, minScale)
983 : assetTotalDelta == vaultDeltaAssets;
987 <<
"Invariant failed: deposit and assets outstanding must add up";
992 vaultAsset, afterVault.assetsAvailable - beforeVault.assetsAvailable, minScale);
993 bool const availableAddsUp = fix340Enabled
994 ? agreesWithinOneUnit(
995 assetAvailableDelta, vaultDeltaAssets, vaultAsset, minScale)
996 : assetAvailableDelta == vaultDeltaAssets;
997 if (!availableAddsUp)
999 JLOG(j.
fatal()) <<
"Invariant failed: deposit and assets available must add up";
1005 case ttVAULT_WITHDRAW: {
1010 "xrpl::ValidVault::finalize : withdrawal updated a vault");
1017 afterVault.vaultKind,
1018 afterVault.subscriptionDate,
1022 "Invariant failed: withdrawal not allowed during "
1027 auto const maybeVaultDeltaAssets =
deltaAssets(afterVault.pseudoId);
1039 bool const zeroDeltaIsLegitimate = fix340Enabled && !maybeVaultDeltaAssets &&
1040 beforeVault.assetsTotal == beforeVault.lossUnrealized;
1042 if (!maybeVaultDeltaAssets && !zeroDeltaIsLegitimate)
1044 JLOG(j.
fatal()) <<
"Invariant failed: withdrawal must change vault balance";
1048 DeltaInfo const vaultDeltaAssets = maybeVaultDeltaAssets.value_or(
1054 auto const vaultPseudoDeltaAssets =
1057 if (!zeroDeltaIsLegitimate && vaultPseudoDeltaAssets >=
kZero)
1059 JLOG(j.
fatal()) <<
"Invariant failed: withdrawal must decrease vault balance";
1065 bool const issuerWithdrawal = [&]() ->
bool {
1066 if (vaultAsset.native())
1068 auto const destination = tx[~sfDestination].value_or(tx[sfAccount]);
1069 return destination == vaultAsset.getIssuer();
1072 if (!issuerWithdrawal)
1082 auto const destinationField = tx[~sfDestination];
1083 AccountID const recipient = destinationField.value_or(tx[sfAccount]);
1084 bool const distinctDestination =
1085 destinationField.has_value() && *destinationField != tx[sfAccount];
1089 if (distinctDestination &&
1094 "Invariant failed: withdrawal must change one destination balance";
1098 auto const maybeRecipientDelta =
1101 if (!maybeRecipientDelta.has_value())
1106 if (!zeroDeltaIsLegitimate)
1109 "Invariant failed: withdrawal must change one destination balance";
1118 auto const destinationDelta = *maybeRecipientDelta;
1123 auto const localMinScale =
std::max(minScale, destinationScale);
1125 auto const roundedDestinationDelta =
1126 roundToAsset(vaultAsset, destinationDelta.delta, localMinScale);
1133 bool const tolerateZeroDelta =
1134 view.
rules().
enabled(fixCleanup3_2_0) && !vaultAsset.integral();
1135 auto const invalidBalanceChange = tolerateZeroDelta
1136 ? roundedDestinationDelta <
kZero
1137 : roundedDestinationDelta <=
kZero;
1138 if (invalidBalanceChange)
1141 "Invariant failed: withdrawal must increase destination balance";
1145 auto const localPseudoDeltaAssets =
1146 roundToAsset(vaultAsset, vaultPseudoDeltaAssets, localMinScale);
1155 auto const destroyedIsSubUlp = tolerateZeroDelta &&
1158 vaultDeltaAssets.
delta * -1 - destinationDelta.delta,
1161 bool const withdrawAddsUp = fix340Enabled
1162 ? agreesWithinOneUnit(
1163 localPseudoDeltaAssets * -1,
1164 roundedDestinationDelta,
1167 : localPseudoDeltaAssets * -1 == roundedDestinationDelta;
1168 if (!destroyedIsSubUlp && !withdrawAddsUp)
1170 JLOG(j.
fatal()) <<
"Invariant failed: " <<
1171 "withdrawal must change vault and destination balance by equal "
1179 auto const accountDeltaShares =
deltaShares(tx[sfAccount]);
1180 if (!accountDeltaShares)
1182 JLOG(j.
fatal()) <<
"Invariant failed: withdrawal must change depositor shares";
1186 if (accountDeltaShares->delta >=
kZero)
1189 <<
"Invariant failed: withdrawal must decrease depositor shares";
1194 auto const vaultDeltaShares =
deltaShares(afterVault.pseudoId);
1195 if (!vaultDeltaShares || vaultDeltaShares->delta ==
kZero)
1197 JLOG(j.
fatal()) <<
"Invariant failed: withdrawal must change vault shares";
1201 if (vaultDeltaShares->delta * -1 != accountDeltaShares->delta)
1203 JLOG(j.
fatal()) <<
"Invariant failed: " <<
1204 "withdrawal must change depositor and vault shares by equal amount";
1209 vaultAsset, afterVault.assetsTotal - beforeVault.assetsTotal, minScale);
1211 bool const totalAddsUp = fix340Enabled
1212 ? agreesWithinOneUnit(
1213 assetTotalDelta, vaultPseudoDeltaAssets, vaultAsset, minScale)
1214 : assetTotalDelta == vaultPseudoDeltaAssets;
1218 <<
"Invariant failed: withdrawal and assets outstanding must add up";
1223 vaultAsset, afterVault.assetsAvailable - beforeVault.assetsAvailable, minScale);
1225 bool const availableAddsUp = fix340Enabled
1226 ? agreesWithinOneUnit(
1227 assetAvailableDelta, vaultPseudoDeltaAssets, vaultAsset, minScale)
1228 : assetAvailableDelta == vaultPseudoDeltaAssets;
1229 if (!availableAddsUp)
1232 <<
"Invariant failed: withdrawal and assets available must add up";
1238 case ttVAULT_CLAWBACK: {
1242 !
beforeVault_.empty(),
"xrpl::ValidVault::finalize : clawback updated a vault");
1245 if (vaultAsset.native() || vaultAsset.getIssuer() != tx[sfAccount])
1249 if (!(beforeShares && beforeShares->sharesTotal > 0 &&
1250 isVaultEmpty(beforeVault) && beforeVault.owner == tx[sfAccount]))
1252 JLOG(j.
fatal()) <<
"Invariant failed: " <<
1253 "clawback may only be performed by the asset issuer, or by the vault "
1254 "owner of an empty vault";
1259 auto const maybeVaultDeltaAssets =
deltaAssets(afterVault.pseudoId);
1260 if (maybeVaultDeltaAssets)
1262 auto const minScale =
1264 auto const vaultDeltaAssets =
1265 roundToAsset(vaultAsset, maybeVaultDeltaAssets->delta, minScale);
1266 if (vaultDeltaAssets >=
kZero)
1268 JLOG(j.
fatal()) <<
"Invariant failed: clawback must decrease vault balance";
1273 vaultAsset, afterVault.assetsTotal - beforeVault.assetsTotal, minScale);
1274 bool const totalAddsUp = fix340Enabled
1275 ? agreesWithinOneUnit(
1276 assetsTotalDelta, vaultDeltaAssets, vaultAsset, minScale)
1277 : assetsTotalDelta == vaultDeltaAssets;
1281 "Invariant failed: clawback and assets outstanding must add up";
1287 afterVault.assetsAvailable - beforeVault.assetsAvailable,
1289 bool const availableAddsUp = fix340Enabled
1290 ? agreesWithinOneUnit(
1291 assetAvailableDelta, vaultDeltaAssets, vaultAsset, minScale)
1292 : assetAvailableDelta == vaultDeltaAssets;
1293 if (!availableAddsUp)
1296 "Invariant failed: clawback and assets available must add up";
1303 "Invariant failed: clawback must change vault balance";
1308 auto const maybeAccountDeltaShares =
deltaShares(tx[sfHolder]);
1309 if (!maybeAccountDeltaShares)
1312 "Invariant failed: clawback must change holder shares";
1315 if (maybeAccountDeltaShares->delta >=
kZero)
1318 "Invariant failed: clawback must decrease holder shares";
1323 auto const vaultDeltaShares =
deltaShares(afterVault.pseudoId);
1324 if (!vaultDeltaShares || vaultDeltaShares->delta ==
kZero)
1327 "Invariant failed: clawback must change vault shares";
1331 if (vaultDeltaShares->delta * -1 != maybeAccountDeltaShares->delta)
1333 JLOG(j.
fatal()) <<
"Invariant failed: " <<
1334 "clawback must change holder and vault shares by equal amount";
1349 UNREACHABLE(
"xrpl::ValidVault::finalize : unknown transaction type");
1359 XRPL_ASSERT(enforce,
"xrpl::ValidVault::finalize : vault invariants");