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>
26#include <xrpl/basics/Log.h>
27#include <xrpl/beast/utility/instrumentation.h>
28#include <xrpl/ledger/CredentialHelpers.h>
29#include <xrpl/ledger/ReadView.h>
30#include <xrpl/ledger/View.h>
31#include <xrpl/protocol/Feature.h>
32#include <xrpl/protocol/Indexes.h>
33#include <xrpl/protocol/LedgerFormats.h>
34#include <xrpl/protocol/MPTIssue.h>
35#include <xrpl/protocol/SField.h>
36#include <xrpl/protocol/STArray.h>
37#include <xrpl/protocol/STNumber.h>
38#include <xrpl/protocol/SystemParameters.h>
39#include <xrpl/protocol/TER.h>
40#include <xrpl/protocol/TxFormats.h>
41#include <xrpl/protocol/Units.h>
42#include <xrpl/protocol/nftPageMask.h>
96 return safe_cast<Privilege>(
101#pragma push_macro("TRANSACTION")
104#define TRANSACTION(tag, value, name, delegatable, amendment, privileges, ...) \
106 return (privileges) & priv; \
114#include <xrpl/protocol/detail/transactions.macro>
122#pragma pop_macro("TRANSACTION")
144 JLOG(j.
fatal()) <<
"Invariant failed: fee paid was negative: "
153 JLOG(j.
fatal()) <<
"Invariant failed: fee paid exceeds system limit: "
162 JLOG(j.
fatal()) <<
"Invariant failed: fee paid is " << fee.
drops()
163 <<
" exceeds fee specified in transaction.";
187 switch (before->getType())
190 drops_ -= (*before)[sfBalance].xrp().drops();
194 ((*before)[sfAmount] - (*before)[sfBalance]).xrp().drops();
197 if (
isXRP((*before)[sfAmount]))
198 drops_ -= (*before)[sfAmount].xrp().drops();
207 switch (
after->getType())
210 drops_ += (*after)[sfBalance].xrp().drops();
214 drops_ += ((*after)[sfAmount] - (*after)[sfBalance])
220 drops_ += (*after)[sfAmount].xrp().drops();
240 JLOG(j.
fatal()) <<
"Invariant failed: XRP net change was positive: "
248 JLOG(j.
fatal()) <<
"Invariant failed: XRP net change of " <<
drops_
249 <<
" doesn't match fee " << fee.
drops();
264 auto isBad = [](
STAmount const& balance) {
265 if (!balance.native())
268 auto const drops = balance.xrp();
282 if (before && before->getType() == ltACCOUNT_ROOT)
283 bad_ |= isBad((*before)[sfBalance]);
285 if (
after &&
after->getType() == ltACCOUNT_ROOT)
299 JLOG(j.
fatal()) <<
"Invariant failed: incorrect account XRP balance";
316 if (pays < beast::zero)
319 if (gets < beast::zero)
323 return pays.
native() && gets.native();
326 if (before && before->getType() == ltOFFER)
327 bad_ |= isBad((*before)[sfTakerPays], (*before)[sfTakerGets]);
330 bad_ |= isBad((*
after)[sfTakerPays], (*after)[sfTakerGets]);
343 JLOG(j.
fatal()) <<
"Invariant failed: offer with a bad amount";
358 auto isBad = [](
STAmount const& amount) {
371 if (amount.holds<
Issue>())
373 if (amount <= beast::zero)
383 if (amount <= beast::zero)
393 if (before && before->getType() == ltESCROW)
394 bad_ |= isBad((*before)[sfAmount]);
404 if (
after &&
after->getType() == ltMPTOKEN_ISSUANCE)
406 auto const outstanding = (*after)[sfOutstandingAmount];
407 checkAmount(outstanding);
408 if (
auto const locked = (*
after)[~sfLockedAmount])
410 checkAmount(*locked);
411 bad_ = outstanding < *locked;
417 auto const mptAmount = (*after)[sfMPTAmount];
418 checkAmount(mptAmount);
419 if (
auto const locked = (*
after)[~sfLockedAmount])
421 checkAmount(*locked);
436 JLOG(j.
fatal()) <<
"Invariant failed: escrow specifies invalid amount";
451 if (isDelete && before && before->getType() == ltACCOUNT_ROOT)
473 JLOG(j.
fatal()) <<
"Invariant failed: account deletion "
474 "succeeded without deleting an account";
476 JLOG(j.
fatal()) <<
"Invariant failed: account deletion "
477 "succeeded but deleted multiple accounts!";
491 JLOG(j.
fatal()) <<
"Invariant failed: an account root was deleted";
503 if (isDelete && before && before->getType() == ltACCOUNT_ROOT)
519 [[maybe_unused]]
bool const enforce =
523 auto const objectExists = [&view, enforce, &j](
auto const& keylet) {
525 if (
auto const sle = view.
read(keylet))
528 auto const typeName = [&sle]() {
533 return item->getName();
538 <<
"Invariant failed: account deletion left behind a "
539 << typeName <<
" object";
544 "ripple::AccountRootsDeletedClean::finalize::objectExists : "
545 "account deletion left no objects behind");
553 auto const accountID = accountSLE->getAccountID(sfAccount);
557 if (objectExists(
std::invoke(keyletfunc, accountID)) && enforce)
572 if (key && objectExists(
Keylet{ltNFTOKEN_PAGE, *key}) && enforce)
580 if (accountSLE->isFieldPresent(*field))
582 auto const key = accountSLE->getFieldH256(*field);
600 if (before &&
after && before->getType() !=
after->getType())
605#pragma push_macro("LEDGER_ENTRY")
608#define LEDGER_ENTRY(tag, ...) case tag:
610 switch (
after->getType())
612#include <xrpl/protocol/detail/ledger_entries.macro>
621#pragma pop_macro("LEDGER_ENTRY")
638 JLOG(j.
fatal()) <<
"Invariant failed: ledger entry type mismatch";
643 JLOG(j.
fatal()) <<
"Invariant failed: invalid ledger entry type added";
657 if (
after &&
after->getType() == ltRIPPLE_STATE)
679 JLOG(j.
fatal()) <<
"Invariant failed: an XRP trust line was created";
691 if (
after &&
after->getType() == ltRIPPLE_STATE)
701 (lowDeepFreeze && !lowFreeze) || (highDeepFreeze && !highFreeze);
716 JLOG(j.
fatal()) <<
"Invariant failed: a trust line with deep freeze flag "
717 "without normal freeze was created";
745 if (balanceChange.signum() == 0)
777 [[maybe_unused]]
bool const enforce =
782 auto const issuerSle =
findIssuer(issue.account, view);
791 "ripple::TransfersNotFrozen::finalize : enforce "
816 after,
"ripple::TransfersNotFrozen::isValidEntry : valid after.");
822 if (
after->getType() == ltACCOUNT_ROOT)
834 return after->getType() == ltRIPPLE_STATE &&
835 (!before || before->getType() == ltRIPPLE_STATE);
844 auto const getBalance = [](
auto const& line,
auto const& other,
bool zero) {
846 line ? line->at(sfBalance) : other->at(sfBalance).zeroed();
847 return zero ? amt.
zeroed() : amt;
855 auto const balanceBefore = getBalance(before,
after,
false);
862 auto const balanceAfter = getBalance(
after, before, isDelete);
864 return balanceAfter - balanceBefore;
872 "ripple::TransfersNotFrozen::recordBalance : valid trustline "
876 changes.senders.emplace_back(std::move(change));
878 changes.receivers.emplace_back(std::move(change));
886 auto const balanceChangeSign = balanceChange.
signum();
887 auto const currency =
after->at(sfBalance).getCurrency();
891 {currency,
after->at(sfHighLimit).getIssuer()},
892 {
after, balanceChangeSign});
896 {currency,
after->at(sfLowLimit).getIssuer()},
897 {
after, -balanceChangeSign});
939 for (
auto const& change : actors)
941 bool const high = change.line->at(sfLowLimit).getIssuer() ==
942 issuer->at(sfAccount);
945 change, high, tx, j, enforce, globalFreeze))
965 bool const deepFreeze =
967 bool const frozen = globalFreeze || deepFreeze || freeze;
979 JLOG(j.
debug()) <<
"Invariant check allowing funds to be moved "
981 <<
" a frozen trustline for AMMClawback "
986 JLOG(j.
fatal()) <<
"Invariant failed: Attempting to move frozen funds for "
991 "ripple::TransfersNotFrozen::validateFrozenState : enforce "
1010 if (!before &&
after->getType() == ltACCOUNT_ROOT)
1032 JLOG(j.
fatal()) <<
"Invariant failed: multiple accounts "
1033 "created in a single transaction";
1040 bool const pseudoAccount =
1045 JLOG(j.
fatal()) <<
"Invariant failed: pseudo-account created by a "
1046 "wrong transaction type";
1059 JLOG(j.
fatal()) <<
"Invariant failed: account created with "
1060 "wrong starting sequence number";
1071 <<
"Invariant failed: pseudo-account created with "
1080 JLOG(j.
fatal()) <<
"Invariant failed: account root created illegally";
1093 static constexpr uint256 const accountBits = ~pageBits;
1095 if ((before && before->getType() != ltNFTOKEN_PAGE) ||
1096 (
after &&
after->getType() != ltNFTOKEN_PAGE))
1100 uint256 const account = sle->key() & accountBits;
1101 uint256 const hiLimit = sle->key() & pageBits;
1109 if (account != (*prev & accountBits))
1112 if (hiLimit <= (*prev & pageBits))
1116 if (
auto const next = (*sle)[~sfNextPageMin])
1118 if (account != (*next & accountBits))
1121 if (hiLimit >= (*next & pageBits))
1126 auto const& nftokens = sle->getFieldArray(sfNFTokens);
1129 if (
std::size_t const nftokenCount = nftokens.size();
1130 (!isDelete && nftokenCount == 0) ||
1137 prev ? *prev & pageBits :
uint256(beast::zero);
1141 for (
auto const& obj : nftokens)
1143 uint256 const tokenID = obj[sfNFTokenID];
1150 if (
uint256 const tokenPageBits = tokenID & pageBits;
1151 tokenPageBits < loLimit || tokenPageBits >= hiLimit)
1154 if (
auto uri = obj[~sfURI]; uri && uri->empty())
1168 before->isFieldPresent(sfPreviousPageMin))
1177 if (!isDelete && before &&
after)
1185 before->isFieldPresent(sfNextPageMin) &&
1186 !
after->isFieldPresent(sfNextPageMin))
1203 JLOG(j.
fatal()) <<
"Invariant failed: NFT page is improperly linked.";
1209 JLOG(j.
fatal()) <<
"Invariant failed: NFT found in incorrect page.";
1215 JLOG(j.
fatal()) <<
"Invariant failed: NFTs on page are not sorted.";
1221 JLOG(j.
fatal()) <<
"Invariant failed: NFT contains empty URI.";
1227 JLOG(j.
fatal()) <<
"Invariant failed: NFT page has invalid size.";
1235 JLOG(j.
fatal()) <<
"Invariant failed: Last NFT page deleted with "
1236 "non-empty directory.";
1241 JLOG(j.
fatal()) <<
"Invariant failed: Lost NextMinPage link.";
1256 if (before && before->getType() == ltACCOUNT_ROOT)
1262 if (
after &&
after->getType() == ltACCOUNT_ROOT)
1281 JLOG(j.
fatal()) <<
"Invariant failed: the number of minted tokens "
1282 "changed without a mint transaction!";
1288 JLOG(j.
fatal()) <<
"Invariant failed: the number of burned tokens "
1289 "changed without a burn transaction!";
1301 <<
"Invariant failed: successful minting didn't increase "
1302 "the number of minted tokens.";
1308 JLOG(j.
fatal()) <<
"Invariant failed: failed minting changed the "
1309 "number of minted tokens.";
1316 <<
"Invariant failed: minting changed the number of "
1329 <<
"Invariant failed: successful burning didn't increase "
1330 "the number of burned tokens.";
1337 JLOG(j.
fatal()) <<
"Invariant failed: failed burning changed the "
1338 "number of burned tokens.";
1345 <<
"Invariant failed: burning changed the number of "
1362 if (before && before->getType() == ltRIPPLE_STATE)
1365 if (before && before->getType() == ltMPTOKEN)
1385 <<
"Invariant failed: more than one trustline changed.";
1392 <<
"Invariant failed: more than one mptokens changed.";
1404 if (holderBalance.
signum() < 0)
1407 <<
"Invariant failed: trustline balance is negative";
1416 JLOG(j.
fatal()) <<
"Invariant failed: some trustlines were changed "
1417 "despite failure of the transaction.";
1423 JLOG(j.
fatal()) <<
"Invariant failed: some mptokens were changed "
1424 "despite failure of the transaction.";
1440 if (
after &&
after->getType() == ltMPTOKEN_ISSUANCE)
1471 JLOG(j.
fatal()) <<
"Invariant failed: transaction "
1472 "succeeded without creating a MPT issuance";
1476 JLOG(j.
fatal()) <<
"Invariant failed: transaction "
1477 "succeeded while removing MPT issuances";
1481 JLOG(j.
fatal()) <<
"Invariant failed: transaction "
1482 "succeeded but created multiple issuances";
1492 JLOG(j.
fatal()) <<
"Invariant failed: MPT issuance deletion "
1493 "succeeded without removing a MPT issuance";
1497 JLOG(j.
fatal()) <<
"Invariant failed: MPT issuance deletion "
1498 "succeeded while creating MPT issuances";
1502 JLOG(j.
fatal()) <<
"Invariant failed: MPT issuance deletion "
1503 "succeeded but deleted multiple issuances";
1512 bool const enforceEscrowFinish = (tx.
getTxnType() == ttESCROW_FINISH) &&
1519 enforceEscrowFinish)
1525 JLOG(j.
fatal()) <<
"Invariant failed: MPT authorize "
1526 "succeeded but created MPT issuances";
1531 JLOG(j.
fatal()) <<
"Invariant failed: MPT authorize "
1532 "succeeded but deleted issuances";
1536 submittedByIssuer &&
1540 <<
"Invariant failed: MPT authorize submitted by issuer "
1541 "succeeded but created/deleted mptokens";
1551 <<
"Invariant failed: MPT authorize submitted by holder "
1552 "succeeded but created/deleted bad number of mptokens";
1564 !enforceEscrowFinish,
1565 "ripple::ValidMPTIssuance::finalize",
1566 "not escrow finish tx");
1578 JLOG(j.
fatal()) <<
"Invariant failed: a MPT issuance was created";
1582 JLOG(j.
fatal()) <<
"Invariant failed: a MPT issuance was deleted";
1586 JLOG(j.
fatal()) <<
"Invariant failed: a MPToken was created";
1590 JLOG(j.
fatal()) <<
"Invariant failed: a MPToken was deleted";
1605 if (before && before->getType() != ltPERMISSIONED_DOMAIN)
1607 if (
after &&
after->getType() != ltPERMISSIONED_DOMAIN)
1612 auto const& credentials = sle->getFieldArray(sfAcceptedCredentials);
1623 for (
auto const& cred : sorted)
1625 auto const& credTx = credentials[i++];
1626 sleStatus.
isSorted_ = (cred.first == credTx[sfIssuer]) &&
1627 (cred.second == credTx[sfCredentialType]);
1661 JLOG(j.
fatal()) <<
"Invariant failed: permissioned domain with "
1669 JLOG(j.
fatal()) <<
"Invariant failed: permissioned domain bad "
1678 <<
"Invariant failed: permissioned domain credentials "
1686 <<
"Invariant failed: permissioned domain credentials "
1710 if (
after &&
after->getType() == ltACCOUNT_ROOT)
1712 bool const isPseudo = [&]() {
1719 if (
after->at(sfSequence) == 0)
1740 return after->isFieldPresent(*sf);
1745 error <<
"pseudo-account has " << numFields
1746 <<
" pseudo-account fields set";
1750 if (before && before->at(sfSequence) !=
after->at(sfSequence))
1759 if (
after->isFieldPresent(sfRegularKey))
1775 bool const enforce = view.
rules().
enabled(featureSingleAssetVault);
1780 "ripple::ValidPseudoAccounts::finalize : no bad "
1781 "changes or enforce invariant");
1784 for (
auto const& error :
errors_)
1786 JLOG(j.
fatal()) <<
"Invariant failed: " << error;
1804 if (
after->isFieldPresent(sfDomainID))
1810 if (
after->isFieldPresent(sfDomainID))
1818 (!
after->isFieldPresent(sfDomainID) ||
1819 !
after->isFieldPresent(sfAdditionalBooks) ||
1820 after->getFieldArray(sfAdditionalBooks).size() > 1))
1834 if ((txType != ttPAYMENT && txType != ttOFFER_CREATE) ||
1842 JLOG(j.
fatal()) <<
"Invariant failed: hybrid offer is malformed";
1853 JLOG(j.
fatal()) <<
"Invariant failed: domain doesn't exist";
1863 JLOG(j.
fatal()) <<
"Invariant failed: transaction"
1864 " consumed wrong domains";
1871 JLOG(j.
fatal()) <<
"Invariant failed: domain transaction"
1872 " affected regular offers";
1890 auto const type =
after->getType();
1900 (type == ltACCOUNT_ROOT &&
after->isFieldPresent(sfAMMID)))
1909 if (before->getType() == ltAMM)
1923 bool const positive = amount > beast::zero && amount2 > beast::zero &&
1924 lptAMMBalance > beast::zero;
1927 (amount == beast::zero && amount2 == beast::zero &&
1928 lptAMMBalance == beast::zero);
1939 JLOG(j.
error()) <<
"AMMVote invariant failed: "
1958 JLOG(j.
error()) <<
"AMMBid invariant failed: pool changed";
1991 <<
"AMMCreate invariant failed: AMM object is not created";
2001 tx[sfAmount].get<Issue>(),
2002 tx[sfAmount2].get<Issue>(),
2013 JLOG(j.
error()) <<
"AMMCreate invariant failed: " << amount <<
" "
2030 ?
"AMM object is not deleted on tesSUCCESS"
2031 :
"AMM object is changed on tecINCOMPLETE";
2032 JLOG(j.
error()) <<
"AMMDelete invariant failed: " << msg;
2047 JLOG(j.
error()) <<
"AMM swap invariant failed: AMM object changed";
2066 tx[sfAsset].get<Issue>(),
2067 tx[sfAsset2].get<Issue>(),
2074 auto const poolProductMean =
root2(amount * amount2);
2075 bool const nonNegativeBalances =
2079 auto weakInvariantCheck = [&]() {
2084 if (!nonNegativeBalances ||
2085 (!strongInvariantCheck && !weakInvariantCheck()))
2090 <<
" " << poolProductMean <<
" "
2112 JLOG(j.
error()) <<
"AMMDeposit invariant failed: AMM object is deleted";
2164 case ttAMM_CLAWBACK:
2165 case ttAMM_WITHDRAW:
2174 case ttOFFER_CREATE:
2191 "ValidVault::Vault::make : from Vault object");
2195 self.
asset = from.
at(sfAsset);
2209 from.
getType() == ltMPTOKEN_ISSUANCE,
2210 "ValidVault::Shares::make : from MPTokenIssuance object");
2230 after !=
nullptr && (before !=
nullptr || !isDelete),
2231 "ripple::ValidVault::visitEntry : some object is available");
2242 switch (before->getType())
2247 case ltMPTOKEN_ISSUANCE:
2252 before->getFieldU64(sfOutstandingAmount));
2257 static_cast<std::int64_t>(before->getFieldU64(sfMPTAmount));
2260 case ltACCOUNT_ROOT:
2261 case ltRIPPLE_STATE:
2262 balanceDelta = before->getFieldAmount(sfBalance);
2269 if (!isDelete &&
after)
2271 switch (
after->getType())
2276 case ltMPTOKEN_ISSUANCE:
2281 after->getFieldU64(sfOutstandingAmount)));
2289 case ltACCOUNT_ROOT:
2290 case ltRIPPLE_STATE:
2291 balanceDelta -=
Number(
after->getFieldAmount(sfBalance));
2298 uint256 const key = (before ? before->key() :
after->key());
2316 bool const enforce = view.
rules().
enabled(featureSingleAssetVault);
2326 "Invariant failed: vault operation succeeded without modifying "
2329 enforce,
"ripple::ValidVault::finalize : vault noop invariant");
2338 "Invariant failed: vault updated by a wrong transaction type";
2341 "ripple::ValidVault::finalize : illegal vault transaction "
2349 "Invariant failed: vault operation updated more than single vault";
2351 enforce,
"ripple::ValidVault::finalize : single vault invariant");
2361 if (txnType != ttVAULT_DELETE)
2364 "Invariant failed: vault deleted by a wrong transaction type";
2367 "ripple::ValidVault::finalize : illegal vault deletion "
2382 if (e.share.getMptID() == beforeVault.shareMPTID)
2383 return std::move(e);
2390 JLOG(j.
fatal()) <<
"Invariant failed: deleted vault must also "
2394 "ripple::ValidVault::finalize : shares deletion invariant");
2399 if (deletedShares->sharesTotal != 0)
2401 JLOG(j.
fatal()) <<
"Invariant failed: deleted vault must have no "
2402 "shares outstanding";
2405 if (beforeVault.assetsTotal !=
zero)
2407 JLOG(j.
fatal()) <<
"Invariant failed: deleted vault must have no "
2408 "assets outstanding";
2411 if (beforeVault.assetsAvailable !=
zero)
2413 JLOG(j.
fatal()) <<
"Invariant failed: deleted vault must have no "
2420 else if (txnType == ttVAULT_DELETE)
2422 JLOG(j.
fatal()) <<
"Invariant failed: vault deletion succeeded without "
2425 enforce,
"ripple::ValidVault::finalize : vault deletion invariant");
2433 "ripple::ValidVault::finalize : single vault operation");
2443 if (e.share.getMptID() == afterVault.shareMPTID)
2447 auto const sleShares =
2460 if (afterVault.asset != beforeVault.asset ||
2461 afterVault.pseudoId != beforeVault.pseudoId ||
2462 afterVault.shareMPTID != beforeVault.shareMPTID)
2465 <<
"Invariant failed: violation of vault immutable data";
2472 JLOG(j.
fatal()) <<
"Invariant failed: updated vault must have shares";
2475 "ripple::ValidVault::finalize : vault has shares invariant");
2479 if (updatedShares->sharesTotal == 0)
2481 if (afterVault.assetsTotal !=
zero)
2483 JLOG(j.
fatal()) <<
"Invariant failed: updated zero sized "
2484 "vault must have no assets outstanding";
2487 if (afterVault.assetsAvailable !=
zero)
2489 JLOG(j.
fatal()) <<
"Invariant failed: updated zero sized "
2490 "vault must have no assets available";
2494 else if (updatedShares->sharesTotal > updatedShares->sharesMaximum)
2497 <<
"Invariant failed: updated shares must not exceed maximum "
2498 << updatedShares->sharesMaximum;
2502 if (afterVault.assetsAvailable <
zero)
2505 <<
"Invariant failed: assets available must be positive";
2509 if (afterVault.assetsAvailable > afterVault.assetsTotal)
2511 JLOG(j.
fatal()) <<
"Invariant failed: assets available must "
2512 "not be greater than assets outstanding";
2516 afterVault.lossUnrealized >
2517 afterVault.assetsTotal - afterVault.assetsAvailable)
2520 <<
"Invariant failed: loss unrealized must not exceed "
2521 "the difference between assets outstanding and available";
2525 if (afterVault.assetsTotal <
zero)
2528 <<
"Invariant failed: assets outstanding must be positive";
2532 if (afterVault.assetsMaximum <
zero)
2534 JLOG(j.
fatal()) <<
"Invariant failed: assets maximum must be positive";
2543 "Invariant failed: vault created by a wrong transaction type";
2545 enforce,
"ripple::ValidVault::finalize : vault creation invariant");
2550 afterVault.lossUnrealized !=
beforeVault_[0].lossUnrealized)
2553 "Invariant failed: vault transaction must not change loss "
2565 if (e.share.getMptID() == beforeVault.shareMPTID)
2566 return std::move(e);
2571 if (!beforeShares &&
2576 JLOG(j.
fatal()) <<
"Invariant failed: vault operation succeeded "
2577 "without updating shares";
2579 enforce,
"ripple::ValidVault::finalize : shares noop invariant");
2583 auto const& vaultAsset = afterVault.asset;
2590 return it->second *
sign;
2594 [&]<
typename TIss>(TIss
const& issue) {
2601 id > issue.getIssuer() ? -1 : 1);
2609 vaultAsset.value());
2612 auto ret = deltaAssets(tx[sfAccount]);
2614 if (!ret.has_value() || !vaultAsset.native())
2618 if (
auto const delegate = tx[~sfDelegate];
2619 delegate.has_value() && *delegate != tx[sfAccount])
2622 *ret += fee.
drops();
2629 auto const it = [&]() {
2630 if (
id == afterVault.pseudoId)
2647 case ttVAULT_CREATE: {
2653 <<
"Invariant failed: create operation must not have "
2658 if (afterVault.assetsAvailable !=
zero ||
2659 afterVault.assetsTotal !=
zero ||
2660 afterVault.lossUnrealized !=
zero ||
2661 updatedShares->sharesTotal != 0)
2664 <<
"Invariant failed: created vault must be empty";
2668 if (afterVault.pseudoId != updatedShares->share.getIssuer())
2671 <<
"Invariant failed: shares issuer and vault "
2672 "pseudo-account must be the same";
2676 auto const sleSharesIssuer = view.
read(
2678 if (!sleSharesIssuer)
2681 <<
"Invariant failed: shares issuer must exist";
2688 <<
"Invariant failed: shares issuer must be a "
2693 if (
auto const vaultId = (*sleSharesIssuer)[~sfVaultID];
2694 !vaultId || *vaultId != afterVault.key)
2697 <<
"Invariant failed: shares issuer pseudo-account "
2698 "must point back to the vault";
2709 "ripple::ValidVault::finalize : set updated a vault");
2712 auto const vaultDeltaAssets = deltaAssets(afterVault.pseudoId);
2713 if (vaultDeltaAssets)
2716 "Invariant failed: set must not change vault balance";
2720 if (beforeVault.assetsTotal != afterVault.assetsTotal)
2723 "Invariant failed: set must not change assets "
2728 if (afterVault.assetsMaximum >
zero &&
2729 afterVault.assetsTotal > afterVault.assetsMaximum)
2732 "Invariant failed: set assets outstanding must not "
2733 "exceed assets maximum";
2737 if (beforeVault.assetsAvailable != afterVault.assetsAvailable)
2740 "Invariant failed: set must not change assets "
2745 if (beforeShares && updatedShares &&
2746 beforeShares->sharesTotal != updatedShares->sharesTotal)
2749 "Invariant failed: set must not change shares "
2756 case ttVAULT_DEPOSIT: {
2761 "ripple::ValidVault::finalize : deposit updated a vault");
2764 auto const vaultDeltaAssets = deltaAssets(afterVault.pseudoId);
2766 if (!vaultDeltaAssets)
2769 "Invariant failed: deposit must change vault balance";
2773 if (*vaultDeltaAssets > tx[sfAmount])
2776 "Invariant failed: deposit must not change vault "
2777 "balance by more than deposited amount";
2781 if (*vaultDeltaAssets <=
zero)
2784 "Invariant failed: deposit must increase vault balance";
2790 bool const issuerDeposit = [&]() ->
bool {
2791 if (vaultAsset.native())
2793 return tx[sfAccount] == vaultAsset.getIssuer();
2798 auto const accountDeltaAssets = deltaAssetsTxAccount();
2799 if (!accountDeltaAssets)
2802 "Invariant failed: deposit must change depositor "
2807 if (*accountDeltaAssets >=
zero)
2810 "Invariant failed: deposit must decrease depositor "
2815 if (*accountDeltaAssets * -1 != *vaultDeltaAssets)
2818 "Invariant failed: deposit must change vault and "
2819 "depositor balance by equal amount";
2824 if (afterVault.assetsMaximum >
zero &&
2825 afterVault.assetsTotal > afterVault.assetsMaximum)
2828 "Invariant failed: deposit assets outstanding must not "
2829 "exceed assets maximum";
2833 auto const accountDeltaShares = deltaShares(tx[sfAccount]);
2834 if (!accountDeltaShares)
2837 "Invariant failed: deposit must change depositor "
2842 if (*accountDeltaShares <=
zero)
2845 "Invariant failed: deposit must increase depositor "
2850 auto const vaultDeltaShares = deltaShares(afterVault.pseudoId);
2851 if (!vaultDeltaShares || *vaultDeltaShares ==
zero)
2854 "Invariant failed: deposit must change vault shares";
2858 if (*vaultDeltaShares * -1 != *accountDeltaShares)
2861 "Invariant failed: deposit must change depositor and "
2862 "vault shares by equal amount";
2866 if (beforeVault.assetsTotal + *vaultDeltaAssets !=
2867 afterVault.assetsTotal)
2869 JLOG(j.
fatal()) <<
"Invariant failed: deposit and assets "
2870 "outstanding must add up";
2873 if (beforeVault.assetsAvailable + *vaultDeltaAssets !=
2874 afterVault.assetsAvailable)
2876 JLOG(j.
fatal()) <<
"Invariant failed: deposit and assets "
2877 "available must add up";
2883 case ttVAULT_WITHDRAW: {
2888 "ripple::ValidVault::finalize : withdrawal updated a "
2892 auto const vaultDeltaAssets = deltaAssets(afterVault.pseudoId);
2894 if (!vaultDeltaAssets)
2896 JLOG(j.
fatal()) <<
"Invariant failed: withdrawal must "
2897 "change vault balance";
2901 if (*vaultDeltaAssets >=
zero)
2903 JLOG(j.
fatal()) <<
"Invariant failed: withdrawal must "
2904 "decrease vault balance";
2910 bool const issuerWithdrawal = [&]() ->
bool {
2911 if (vaultAsset.native())
2913 auto const destination =
2914 tx[~sfDestination].value_or(tx[sfAccount]);
2915 return destination == vaultAsset.getIssuer();
2918 if (!issuerWithdrawal)
2920 auto const accountDeltaAssets = deltaAssetsTxAccount();
2921 auto const otherAccountDelta =
2923 if (
auto const destination = tx[~sfDestination];
2924 destination && *destination != tx[sfAccount])
2925 return deltaAssets(*destination);
2929 if (accountDeltaAssets.has_value() ==
2930 otherAccountDelta.has_value())
2933 "Invariant failed: withdrawal must change one "
2934 "destination balance";
2938 auto const destinationDelta =
2939 accountDeltaAssets ? *accountDeltaAssets
2940 : *otherAccountDelta;
2942 if (destinationDelta <=
zero)
2945 "Invariant failed: withdrawal must increase "
2946 "destination balance";
2950 if (*vaultDeltaAssets * -1 != destinationDelta)
2953 "Invariant failed: withdrawal must change vault "
2954 "and destination balance by equal amount";
2959 auto const accountDeltaShares = deltaShares(tx[sfAccount]);
2960 if (!accountDeltaShares)
2963 "Invariant failed: withdrawal must change depositor "
2968 if (*accountDeltaShares >=
zero)
2971 "Invariant failed: withdrawal must decrease depositor "
2976 auto const vaultDeltaShares = deltaShares(afterVault.pseudoId);
2977 if (!vaultDeltaShares || *vaultDeltaShares ==
zero)
2980 "Invariant failed: withdrawal must change vault shares";
2984 if (*vaultDeltaShares * -1 != *accountDeltaShares)
2987 "Invariant failed: withdrawal must change depositor "
2988 "and vault shares by equal amount";
2993 if (beforeVault.assetsTotal + *vaultDeltaAssets !=
2994 afterVault.assetsTotal)
2996 JLOG(j.
fatal()) <<
"Invariant failed: withdrawal and "
2997 "assets outstanding must add up";
3001 if (beforeVault.assetsAvailable + *vaultDeltaAssets !=
3002 afterVault.assetsAvailable)
3004 JLOG(j.
fatal()) <<
"Invariant failed: withdrawal and "
3005 "assets available must add up";
3011 case ttVAULT_CLAWBACK: {
3016 "ripple::ValidVault::finalize : clawback updated a vault");
3019 if (vaultAsset.native() ||
3020 vaultAsset.getIssuer() != tx[sfAccount])
3023 "Invariant failed: clawback may only be performed by "
3028 auto const vaultDeltaAssets = deltaAssets(afterVault.pseudoId);
3030 if (!vaultDeltaAssets)
3033 "Invariant failed: clawback must change vault balance";
3037 if (*vaultDeltaAssets >=
zero)
3040 "Invariant failed: clawback must decrease vault "
3045 auto const accountDeltaShares = deltaShares(tx[sfHolder]);
3046 if (!accountDeltaShares)
3049 "Invariant failed: clawback must change holder shares";
3053 if (*accountDeltaShares >=
zero)
3056 "Invariant failed: clawback must decrease holder "
3061 auto const vaultDeltaShares = deltaShares(afterVault.pseudoId);
3062 if (!vaultDeltaShares || *vaultDeltaShares ==
zero)
3065 "Invariant failed: clawback must change vault shares";
3069 if (*vaultDeltaShares * -1 != *accountDeltaShares)
3072 "Invariant failed: clawback must change holder and "
3073 "vault shares by equal amount";
3077 if (beforeVault.assetsTotal + *vaultDeltaAssets !=
3078 afterVault.assetsTotal)
3081 "Invariant failed: clawback and assets outstanding "
3086 if (beforeVault.assetsAvailable + *vaultDeltaAssets !=
3087 afterVault.assetsAvailable)
3090 "Invariant failed: clawback and assets available must "
3101 "ripple::ValidVault::finalize : unknown transaction type");
3111 XRPL_ASSERT(enforce,
"ripple::ValidVault::finalize : vault invariants");
A generic endpoint for log messages.
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_
std::uint32_t 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.
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::uint32_t afterMintedTotal
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
std::uint32_t afterBurnedTotal
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
std::uint32_t beforeBurnedTotal
std::uint32_t beforeMintedTotal
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 deepFreezeWithoutFreeze_
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 &)
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.
virtual Rules const & rules() const =0
Returns the tx processing rules.
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Currency const & getCurrency() const
int signum() const noexcept
AccountID const & getIssuer() const
bool native() const noexcept
STAmount zeroed() const
Returns a zero value with the same issuer and currency.
LedgerEntryType getType() const
uint256 const & key() const
Returns the 'key' (or 'index') of this item.
uint192 getFieldH192(SField const &field) const
AccountID getAccountID(SField const &field) const
T::value_type at(TypedField< T > const &f) const
Get the value of a field.
std::uint32_t getFieldU32(SField const &field) const
STAmount const & getFieldAmount(SField const &field) const
uint256 getHash(HashPrefix prefix) const
bool isFieldPresent(SField const &field) const
uint256 getFieldH256(SField const &field) const
TxType getTxnType() const
uint256 getTransactionID() 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 &)
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 mptokensCreated_
std::uint32_t mptIssuancesDeleted_
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
std::uint32_t mptokensDeleted_
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::uint32_t accountsCreated_
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
std::uint32_t accountSeq_
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
hash_set< uint256 > domains_
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
std::optional< SleStatus > sleStatus_[2]
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
std::vector< std::string > errors_
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
std::vector< Shares > beforeMPTs_
std::vector< Vault > beforeVault_
std::unordered_map< uint256, Number > deltas_
static Number constexpr zero
std::vector< Shares > afterMPTs_
std::vector< Vault > afterVault_
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
constexpr value_type drops() const
Returns the number of drops.
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 &)
T emplace_back(T... args)
std::set< std::pair< AccountID, Slice > > makeSorted(STArray const &credentials)
Keylet mptoken(MPTID const &issuanceID, AccountID const &holder) noexcept
Keylet permissionedDomain(AccountID const &account, std::uint32_t seq) noexcept
Keylet line(AccountID const &id0, AccountID const &id1, Currency const ¤cy) noexcept
The index of a trust line for a given currency.
Keylet mptIssuance(std::uint32_t seq, AccountID const &issuer) noexcept
Keylet account(AccountID const &id) noexcept
AccountID root.
Keylet unchecked(uint256 const &key) noexcept
Any ledger entry.
Keylet nftpage_min(AccountID const &owner)
NFT page keylets.
Keylet nftpage_max(AccountID const &owner)
A keylet for the owner's last possible NFT page.
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.
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
Currency const & badCurrency()
We deliberately disallow the currency that looks like "XRP" because too many people were using it ins...
bool isXRP(AccountID const &c)
constexpr base_uint< Bits, Tag > operator|(base_uint< Bits, Tag > const &a, base_uint< Bits, Tag > const &b)
static bool validBalances(STAmount const &amount, STAmount const &amount2, STAmount const &lptAMMBalance, ValidAMM::ZeroAllowed zeroAllowed)
std::size_t constexpr maxPermissionedDomainCredentialsArraySize
The maximum number of credentials can be passed in array for permissioned domain.
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
std::uint64_t constexpr maxMPTokenAmount
The maximum amount of MPTokenIssuance.
constexpr XRPAmount INITIAL_XRP
Configure the native currency.
std::size_t constexpr dirMaxTokensPerPage
The maximum number of items in an NFT page.
Buffer sign(PublicKey const &pk, SecretKey const &sk, Slice const &message)
Generate a signature for a message.
std::array< keyletDesc< AccountID const & >, 6 > const directAccountKeylets
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.
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const ¤cy, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j)
bool isTesSuccess(TER x) noexcept
STAmount ammLPTokens(STAmount const &asset1, STAmount const &asset2, Issue const &lptIssue)
Calculate LP Tokens given AMM pool reserves.
T get(Section const §ion, std::string const &name, T const &defaultValue=T{})
Retrieve a key/value pair from a section.
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
@ transactionID
transaction plus signature to give transaction ID
MPTID makeMptID(std::uint32_t sequence, AccountID const &account)
std::vector< SField const * > const & getPseudoAccountFields()
bool withinRelativeDistance(Quality const &calcQuality, Quality const &reqQuality, Number const &dist)
Check if the relative distance between the qualities is within the requested distance.
bool isPseudoAccount(std::shared_ptr< SLE const > sleAcct)
A pair of SHAMap key and LedgerEntryType.
int const balanceChangeSign
std::shared_ptr< SLE const > const line
std::vector< BalanceChange > receivers
std::vector< BalanceChange > senders
std::size_t credentialsSize_
std::uint64_t sharesMaximum
static Shares make(SLE const &)
std::uint64_t sharesTotal
static Vault make(SLE const &)