1#include <xrpl/tx/invariants/MPTInvariant.h>
3#include <xrpl/basics/Log.h>
4#include <xrpl/basics/base_uint.h>
5#include <xrpl/beast/utility/Journal.h>
6#include <xrpl/beast/utility/Zero.h>
7#include <xrpl/beast/utility/instrumentation.h>
8#include <xrpl/ledger/ReadView.h>
9#include <xrpl/ledger/helpers/AccountRootHelpers.h>
10#include <xrpl/ledger/helpers/LendingHelpers.h>
11#include <xrpl/ledger/helpers/MPTokenHelpers.h>
12#include <xrpl/protocol/AccountID.h>
13#include <xrpl/protocol/Feature.h>
14#include <xrpl/protocol/Indexes.h>
15#include <xrpl/protocol/LedgerFormats.h>
16#include <xrpl/protocol/MPTIssue.h>
17#include <xrpl/protocol/Protocol.h>
18#include <xrpl/protocol/Rules.h>
19#include <xrpl/protocol/SField.h>
20#include <xrpl/protocol/STLedgerEntry.h>
21#include <xrpl/protocol/STTx.h>
22#include <xrpl/protocol/TER.h>
23#include <xrpl/protocol/TxFormats.h>
24#include <xrpl/protocol/UintTypes.h>
25#include <xrpl/protocol/XRPAmount.h>
26#include <xrpl/tx/invariants/InvariantCheckPrivilege.h>
37constexpr auto kConfidentialMptTxTypes = std::to_array<TxType>({
38 ttCONFIDENTIAL_MPT_SEND,
39 ttCONFIDENTIAL_MPT_CONVERT,
40 ttCONFIDENTIAL_MPT_CONVERT_BACK,
41 ttCONFIDENTIAL_MPT_MERGE_INBOX,
42 ttCONFIDENTIAL_MPT_CLAWBACK,
43 ttCONFIDENTIAL_MPT_MIRROR_UPDATE,
44 ttCONFIDENTIAL_MPT_HOLDER_KEY_UPDATE,
51toSignedMPTAmount(std::uint64_t amount)
57addMPTAmountDelta(std::int64_t delta, std::uint64_t amount)
59 return delta + toSignedMPTAmount(amount);
63subtractMPTAmountDelta(std::int64_t delta, std::uint64_t amount)
65 return delta - toSignedMPTAmount(amount);
80 if (
after &&
after->getType() == ltMPTOKEN_ISSUANCE)
89 if (fix320Enabled &&
after->isFieldPresent(sfReferenceHolding))
101 bool const beforePresent = before->isFieldPresent(sfReferenceHolding);
102 bool const afterPresent =
after->isFieldPresent(sfReferenceHolding);
103 if (beforePresent != afterPresent ||
105 before->getFieldH256(sfReferenceHolding) !=
106 after->getFieldH256(sfReferenceHolding)))
136 if (fix320Enabled && isDelete &&
after &&
after->getType() == ltRIPPLE_STATE)
148 auto const& rules = view.
rules();
149 bool const mptV2Enabled = rules.
enabled(featureMPTokensV2);
157 if (rules.enabled(fixCleanup3_2_0))
161 bool invariantPasses =
true;
164 JLOG(j.
fatal()) <<
"Invariant failed: sfReferenceHolding was modified "
165 "on an existing MPTokenIssuance";
166 invariantPasses =
false;
170 JLOG(j.
fatal()) <<
"Invariant failed: sfReferenceHolding set on a new "
171 "MPTokenIssuance by a non-VaultCreate transaction";
172 invariantPasses =
false;
176 auto const isVaultPseudo = [&](
AccountID const& acct) {
178 return sle && sle->isFieldPresent(sfVaultID);
182 bool offending =
false;
183 if (sleHolding->getType() == ltMPTOKEN)
185 offending = isVaultPseudo(sleHolding->at(sfAccount));
189 auto const lowLimit = sleHolding->getFieldAmount(sfLowLimit);
190 auto const highLimit = sleHolding->getFieldAmount(sfHighLimit);
195 isVaultPseudo(lowLimit.getIssuer()) || isVaultPseudo(highLimit.getIssuer());
199 JLOG(j.
fatal()) <<
"Invariant failed: vault pseudo-account holding "
200 "deleted by a non-VaultDelete transaction";
201 invariantPasses =
false;
205 if (!invariantPasses)
214 JLOG(j.
fatal()) <<
"Invariant failed: immutable MPTokenIssuance flag cleared: "
220 if (rules.enabled(fixCleanup3_5_0))
224 if (sleHolding->getType() == ltMPTOKEN && sleHolding->getFieldU64(sfMPTAmount) > 0)
226 JLOG(j.
fatal()) <<
"Invariant failed: MPToken deleted with non-zero balance";
235 bool const enforceCreatedByIssuer =
236 rules.enabled(featureSingleAssetVault) || rules.enabled(featureLendingProtocol);
239 JLOG(j.
fatal()) <<
"Invariant failed: MPToken created for the MPT issuer";
243 enforceCreatedByIssuer,
"xrpl::ValidMPTIssuance::finalize",
"no issuer MPToken");
244 if (enforceCreatedByIssuer)
253 JLOG(j.
fatal()) <<
"Invariant failed: transaction "
254 "succeeded without creating a MPT issuance";
258 JLOG(j.
fatal()) <<
"Invariant failed: transaction "
259 "succeeded while removing MPT issuances";
263 JLOG(j.
fatal()) <<
"Invariant failed: transaction "
264 "succeeded but created multiple issuances";
274 JLOG(j.
fatal()) <<
"Invariant failed: MPT issuance deletion "
275 "succeeded without removing a MPT issuance";
279 JLOG(j.
fatal()) <<
"Invariant failed: MPT issuance deletion "
280 "succeeded while creating MPT issuances";
284 JLOG(j.
fatal()) <<
"Invariant failed: MPT issuance deletion "
285 "succeeded but deleted multiple issuances";
291 bool const lendingProtocolEnabled = rules.enabled(featureLendingProtocol);
295 bool const enforceEscrowFinish = (txnType == ttESCROW_FINISH) &&
296 (rules.enabled(featureSingleAssetVault) || lendingProtocolEnabled);
304 JLOG(j.
fatal()) <<
"Invariant failed: MPT authorize "
305 "succeeded but created MPT issuances";
310 JLOG(j.
fatal()) <<
"Invariant failed: MPT authorize "
311 "succeeded but deleted issuances";
315 (txnType == ttAMM_WITHDRAW || txnType == ttAMM_CLAWBACK))
319 JLOG(j.
fatal()) <<
"Invariant failed: MPT authorize "
320 "submitted by issuer succeeded "
321 "but created bad number of mptokens";
329 SOMETIMES(
mptokensCreated_ == 2,
"AMM withdraw/clawback recreated two MPTokens");
332 JLOG(j.
fatal()) <<
"Invariant failed: MPT authorize succeeded "
333 "but created/deleted bad number of mptokens";
344 auto const mptokensExceedAuthorizeCap = [&] {
345 if (!lendingProtocolEnabled)
347 if (rules.enabled(fixCleanup3_4_0))
349 if (txnType == ttLOAN_SET)
351 if (txnType == ttVAULT_WITHDRAW)
356 if (mptokensExceedAuthorizeCap())
358 JLOG(j.
fatal()) <<
"Invariant failed: MPT authorize succeeded "
359 "but created/deleted bad number mptokens";
364 JLOG(j.
fatal()) <<
"Invariant failed: MPT authorize submitted by issuer "
365 "succeeded but created/deleted mptokens";
373 JLOG(j.
fatal()) <<
"Invariant failed: MPT authorize submitted by holder "
374 "succeeded but created/deleted bad number of mptokens";
388 JLOG(j.
fatal()) <<
"Invariant failed: MPT authorize "
389 "succeeded but created MPT issuances";
394 JLOG(j.
fatal()) <<
"Invariant failed: MPT authorize "
395 "succeeded but deleted issuances";
400 JLOG(j.
fatal()) <<
"Invariant failed: MPT authorize "
401 "succeeded but deleted MPTokens";
410 JLOG(j.
fatal()) <<
"Invariant failed: MPT authorize "
411 "succeeded but created bad number of mptokens";
414 if (submittedByIssuer)
416 JLOG(j.
fatal()) <<
"Invariant failed: MPT authorize submitted by issuer "
417 "succeeded but created mptokens";
427 if (txnType == ttESCROW_FINISH)
433 !enforceEscrowFinish,
"xrpl::ValidMPTIssuance::finalize",
"not escrow finish tx");
445 JLOG(j.
fatal()) <<
"Invariant failed: a MPT issuance was created";
449 JLOG(j.
fatal()) <<
"Invariant failed: a MPT issuance was deleted";
453 JLOG(j.
fatal()) <<
"Invariant failed: a MPToken was created";
457 JLOG(j.
fatal()) <<
"Invariant failed: a MPToken was deleted";
470 auto makeKey = [](
SLE const& sle) {
471 if (sle.getType() == ltMPTOKEN_ISSUANCE)
472 return makeMptID(sle[sfSequence], sle[sfIssuer]);
473 return sle[sfMPTokenIssuanceID];
476 auto update = [&](
SLE const& sle,
Order order) ->
bool {
477 auto const type = sle.
getType();
478 if (type == ltMPTOKEN_ISSUANCE)
480 auto const outstanding = sle[sfOutstandingAmount];
486 data_[makeKey(sle)].outstanding[
static_cast<std::size_t>(order)] = outstanding;
488 else if (type == ltMPTOKEN)
490 auto const mptAmt = sle[sfMPTAmount];
491 auto const lockedAmt = sle[~sfLockedAmount].value_or(0);
498 auto const res =
static_cast<std::int64_t>(mptAmt + lockedAmt);
502 data_[makeKey(sle)].mptAmount -= res;
506 data_[makeKey(sle)].mptAmount += res;
517 if (
after->getType() == ltMPTOKEN_ISSUANCE)
534 auto const fix340Enabled = view.
rules().
enabled(fixCleanup3_4_0);
543 kConfidentialMptTxTypes.end())
550 auto const invariantPasses = !(view.
rules().enabled(featureMPTokensV2) || fix340Enabled);
553 JLOG(j.
fatal()) <<
"Invariant failed: OutstandingAmount overflow";
554 return invariantPasses;
558 for (
auto const& [
id, data] :
data_)
563 bool const addOverflows =
564 (data.mptAmount > 0 && data.outstanding[kIBefore] > (signedMax - data.mptAmount)) ||
565 (data.mptAmount < 0 && data.outstanding[kIBefore] < (-signedMax - data.mptAmount));
567 data.outstanding[kIAfter] != (data.outstanding[kIBefore] + data.mptAmount))
569 JLOG(j.
fatal()) <<
"Invariant failed: invalid OutstandingAmount balance "
570 << data.outstanding[kIBefore] <<
" " << data.outstanding[kIAfter]
571 <<
" " << data.mptAmount;
572 return invariantPasses;
582 JLOG(j.
fatal()) <<
"Invariant failed: OutstandingAmount balance changed on failure "
584 return invariantPasses;
602 if (sle->getType() == ltMPTOKEN)
603 return sle->getFieldH192(sfMPTokenIssuanceID);
604 if (sle->getType() == ltMPTOKEN_ISSUANCE)
605 return makeMptID(sle->getFieldU32(sfSequence), sle->getAccountID(sfIssuer));
609 if (before && before->getType() == ltMPTOKEN)
611 UInt192 const id = getMptID(before);
613 change.mptAmountDelta =
614 subtractMPTAmountDelta(change.mptAmountDelta, before->getFieldU64(sfMPTAmount));
630 if (!
isFeatureEnabled(fixCleanup3_5_0) && before->getFieldU64(sfMPTAmount) > 0)
631 changes_[id].deletedWithBalanceBefore =
true;
633 if (before->isFieldPresent(sfConfidentialBalanceSpending) ||
634 before->isFieldPresent(sfConfidentialBalanceInbox) ||
635 before->isFieldPresent(sfIssuerEncryptedBalance) ||
636 before->isFieldPresent(sfAuditorEncryptedBalance))
637 changes_[id].deletedWithEncrypted =
true;
645 change.mptAmountDelta =
646 addMPTAmountDelta(change.mptAmountDelta,
after->getFieldU64(sfMPTAmount));
649 bool const hasIssuerBalance =
after->isFieldPresent(sfIssuerEncryptedBalance);
650 bool const hasHolderInbox =
after->isFieldPresent(sfConfidentialBalanceInbox);
651 bool const hasHolderSpending =
after->isFieldPresent(sfConfidentialBalanceSpending);
652 bool const hasAuditorBalance =
after->isFieldPresent(sfAuditorEncryptedBalance);
656 if (hasHolderInbox != hasHolderSpending || hasHolderInbox != hasIssuerBalance ||
657 (hasAuditorBalance && !hasIssuerBalance))
660 auto const confidentialBalanceFieldChanged = [&before, &
after](
auto const& field) {
661 auto const afterValue = (*after)[~field];
665 if (!before || before->getType() != ltMPTOKEN)
668 return (*before)[~field] != afterValue;
671 if (confidentialBalanceFieldChanged(sfConfidentialBalanceInbox) ||
672 confidentialBalanceFieldChanged(sfConfidentialBalanceSpending) ||
673 confidentialBalanceFieldChanged(sfIssuerEncryptedBalance) ||
674 confidentialBalanceFieldChanged(sfAuditorEncryptedBalance))
676 changes_[id].changesConfidentialFields =
true;
680 if (before && before->getType() == ltMPTOKEN_ISSUANCE)
682 UInt192 const id = getMptID(before);
684 if (before->isFieldPresent(sfConfidentialOutstandingAmount))
686 change.coaDelta = subtractMPTAmountDelta(
687 change.coaDelta, before->getFieldU64(sfConfidentialOutstandingAmount));
689 change.outstandingDelta = subtractMPTAmountDelta(
690 change.outstandingDelta, before->getFieldU64(sfOutstandingAmount));
693 if (
after &&
after->getType() == ltMPTOKEN_ISSUANCE)
698 bool const hasCOA =
after->isFieldPresent(sfConfidentialOutstandingAmount);
699 std::uint64_t const coa = (*after)[~sfConfidentialOutstandingAmount].value_or(0);
703 change.coaDelta = addMPTAmountDelta(change.coaDelta, coa);
705 change.outstandingDelta = addMPTAmountDelta(change.outstandingDelta, oa);
706 change.issuance =
after;
710 change.badCOA =
true;
713 if (before &&
after && before->getType() == ltMPTOKEN &&
after->getType() == ltMPTOKEN)
718 auto const spendingBefore = (*before)[~sfConfidentialBalanceSpending];
719 auto const spendingAfter = (*after)[~sfConfidentialBalanceSpending];
720 auto const versionBefore = (*before)[~sfConfidentialBalanceVersion];
721 auto const versionAfter = (*after)[~sfConfidentialBalanceVersion];
723 if (spendingBefore.has_value() && spendingBefore != spendingAfter)
725 if (versionBefore == versionAfter)
742 bool const fix350Enabled = view.
rules().
enabled(fixCleanup3_5_0);
744 for (
auto const& [
id, checks] :
changes_)
749 return checks.issuance;
767 bool const deletedWithEncrypted = fix350Enabled
768 ? checks.deletedWithEncrypted
769 : (checks.deletedWithEncrypted || checks.deletedWithBalanceBefore);
771 if (deletedWithEncrypted)
773 if ((*issuance)[~sfConfidentialOutstandingAmount].value_or(0) > 0)
776 <<
"Invariant failed: MPToken deleted with encrypted fields while COA > 0";
782 if (checks.badConsistency)
784 JLOG(j.
fatal()) <<
"Invariant failed: MPToken encrypted field "
785 "existence inconsistency";
792 JLOG(j.
fatal()) <<
"Invariant failed: Confidential outstanding amount "
793 "exceeds total outstanding amount";
800 if (checks.changesConfidentialFields)
802 if (!issuance->isFlag(lsfMPTCanHoldConfidentialBalance))
804 JLOG(j.
fatal()) <<
"Invariant failed: MPToken has encrypted "
805 "fields but Issuance does not have "
806 "lsfMPTCanHoldConfidentialBalance set";
822 if (checks.coaDelta != 0)
824 if (checks.mptAmountDelta + checks.coaDelta != checks.outstandingDelta)
826 JLOG(j.
fatal()) <<
"Invariant failed: Token conservation "
834 kConfidentialMptTxTypes.end())
838 if (checks.mptAmountDelta != 0)
840 JLOG(j.
fatal()) <<
"Invariant failed: MPTAmount changed by confidential "
841 "transaction that should not modify this field."
850 if (checks.outstandingDelta != 0)
852 JLOG(j.
fatal()) <<
"Invariant failed: OutstandingAmount changed "
853 "by confidential transaction that should not "
860 if (checks.badVersion)
863 <<
"Invariant failed: MPToken sfConfidentialBalanceVersion not updated when "
864 "sfConfidentialBalanceSpending changed";
880 auto update = [&](
SLE const& sle,
bool isBefore) {
881 if (sle.
getType() == ltMPTOKEN)
883 auto const issuanceID = sle[sfMPTokenIssuanceID];
884 auto const account = sle[sfAccount];
885 auto const amount = sle[sfMPTAmount];
888 amount_[issuanceID][account].amtBefore = amount;
892 amount_[issuanceID][account].amtAfter = amount;
894 if (isDelete && isBefore)
902 update(*before,
true);
905 update(*
after,
false);
912 if (before && before->getType() == ltACCOUNT_ROOT)
936 bool const isPseudo =
944 return !reqAuth || it->second;
971 auto const isDEX = [&] {
972 if (txnType == ttPAYMENT)
976 auto const amount = tx[sfAmount];
977 return tx[~sfSendMax].value_or(amount).asset() != amount.asset();
979 return txnType == ttAMM_CREATE || txnType == ttAMM_DEPOSIT || txnType == ttOFFER_CREATE;
982 auto const fix340Enabled = view.
rules().
enabled(fixCleanup3_4_0);
985 auto const invariantPasses = !(view.
rules().enabled(featureMPTokensV2) || fix340Enabled);
991 JLOG(j.
fatal()) <<
"Invariant failed: MPToken deleted on failure " << txnType <<
" "
993 return invariantPasses;
996 for (
auto const& [mptID, values] :
amount_)
1000 bool invalidTransfer =
false;
1009 for (
auto const& [account, value] : values)
1011 if (value.amtAfter.has_value() && value.amtBefore.value_or(0) != *value.amtAfter)
1013 JLOG(j.
fatal()) <<
"Invariant failed: orphaned MPToken balance changed "
1014 << txnType <<
" " << result;
1015 return invariantPasses;
1024 auto const waivesCanTransfer = txnType == ttAMM_WITHDRAW ||
1025 (view.
rules().enabled(fixCleanup3_2_0) &&
1026 (txnType == ttVAULT_WITHDRAW || txnType == ttLOAN_BROKER_COVER_WITHDRAW ||
1027 txnType == ttLOAN_PAY));
1028 auto const canTransfer = sleIssuance->isFlag(lsfMPTCanTransfer) || waivesCanTransfer;
1029 auto const canTrade = sleIssuance->isFlag(lsfMPTCanTrade);
1030 auto const reqAuth = sleIssuance->isFlag(lsfMPTRequireAuth);
1035 bool const isLoanDefaultAsset = loanDefaultAccounts &&
1036 loanDefaultAccounts->asset.holds<
MPTIssue>() &&
1039 for (
auto const& [account, value] : values)
1044 if (value.amtAfter.has_value() && value.amtBefore.value_or(0) != *value.amtAfter)
1046 if (!value.amtBefore.has_value() || *value.amtAfter > *value.amtBefore)
1061 bool const exemptFromFreeze = isLoanDefaultAsset && loanDefaultAccounts &&
1062 (account == loanDefaultAccounts->broker ||
1063 account == loanDefaultAccounts->vault);
1064 if (!invalidTransfer &&
1065 ((!exemptFromFreeze &&
isFrozen(view, account, *sleIssuance)) ||
1068 invalidTransfer =
true;
1078 JLOG(j.
fatal()) <<
"Invariant failed: invalid MPToken transfer between holders";
1079 return invariantPasses;
1085 if (fix340Enabled && !
isTesSuccess(result) && (senders > 0 || receivers > 0))
1087 JLOG(j.
fatal()) <<
"Invariant failed: MPToken balance changed on failure " << txnType
1089 return invariantPasses;
A generic endpoint for log messages.
constexpr MPTID const & getMptID() const
AccountID const & getIssuer() const
virtual Rules const & rules() const =0
Returns the tx processing rules.
virtual SLE::const_pointer 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.
UInt256 const & key() const
Returns the 'key' (or 'index') of this item.
LedgerEntryType getType() const
std::shared_ptr< STLedgerEntry const > const & ConstRef
bool isFlag(std::uint32_t) const
bool isFieldPresent(SField const &field) const
TxType getTxnType() const
void visitEntry(bool isDelete, std::shared_ptr< SLE const > const &before, std::shared_ptr< SLE const > const &after)
Track confidential MPT balance, issuance, and version changes.
bool finalize(STTx const &tx, TER const result, XRPAmount const fee, ReadView const &view, beast::Journal const &j)
Verify confidential MPT accounting and encrypted-field invariants.
std::map< UInt192, Changes > changes_
void visitEntry(bool isDelete, SLE::ConstRef before, SLE::ConstRef after)
Track MPT amount and outstanding amount changes.
HashMap< UInt192, MPTData > data_
bool finalize(STTx const &tx, TER const result, XRPAmount const fee, ReadView const &view, beast::Journal const &j)
Verify public MPT payment accounting invariants.
std::uint32_t mptokensCreated_
bool referenceHoldingSetOnCreate_
sfReferenceHolding is intended to be set exactly once at vault creation and immutable thereafter; tru...
std::uint32_t mptokensDeleted_
bool finalize(STTx const &tx, TER const result, XRPAmount const fee, ReadView const &view, beast::Journal const &j) const
Verify MPT issuance invariants after transaction application.
void visitEntry(bool isDelete, SLE::ConstRef before, SLE::ConstRef after)
Track MPT issuance and holding creations, deletions, and mutations.
bool referenceHoldingMutated_
True when sfReferenceHolding was mutated on an existing MPTokenIssuance.
std::uint32_t mptIssuancesCreated_
std::vector< std::shared_ptr< SLE const > > deletedHoldings_
MPTokens and RippleStates deleted during apply.
std::uint32_t issuanceFlagsCleared_
Flags cleared on an existing MPTokenIssuance, except lsfMPTLocked, which tfMPTUnlock clears legitimat...
std::uint32_t mptIssuancesDeleted_
HashMap< AccountID, bool > pseudoAccountsBefore_
bool isAuthorized(ReadView const &view, MPTID const &mptid, AccountID const &holder, bool requireAuth) const
Check whether a holder is authorized to send or receive an MPToken.
void visitEntry(bool isDelete, std::shared_ptr< SLE const > const &before, std::shared_ptr< SLE const > const &after)
Track MPT balance changes and deleted authorization state.
HashMap< UInt192, HashMap< AccountID, Value > > amount_
HashMap< UInt256, bool > deletedAuthorized_
bool finalize(STTx const &tx, TER const result, XRPAmount const fee, ReadView const &view, beast::Journal const &j)
Verify MPT transfer authorization invariants.
Keylet mptoken(MPTID const &issuanceID, AccountID const &holder) noexcept
Keylet account(AccountID const &id) noexcept
AccountID root.
Keylet mptokenIssuance(MPTID const &issuanceID) noexcept
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
std::int64_t maxMPTAmount(SLE const &sleIssuance)
bool isFeatureEnabled(UInt256 const &feature, bool resultIfNoRules)
Check whether a feature is enabled in the current ledger rules.
TER canTransfer(ReadView const &view, MPTIssue const &mptIssue, AccountID const &from, AccountID const &to, WaiveMPTCanTransfer waive=WaiveMPTCanTransfer::No, std::uint8_t depth=0)
Check whether to may receive the given MPT from from.
std::string to_string(BaseUInt< Bits, Tag > const &a)
TER canTrade(ReadView const &view, Asset const &asset, std::uint8_t depth=0)
Check whether asset may be traded on the DEX.
bool hasPrivilege(STTx const &tx, Privilege priv)
std::optional< LoanDefaultFreezeExemptAccounts > getLoanDefaultFreezeExemptAccounts(ReadView const &view, STTx const &tx)
Resolves the accounts and asset a LoanManage default transaction is exempt from freeze/lock for.
BaseUInt< 192 > MPTID
MPTID is a 192-bit value representing MPT Issuance ID, which is a concatenation of a 32-bit sequence ...
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
bool isFrozen(ReadView const &view, AccountID const &account, MPTIssue const &mptIssue, std::uint8_t depth=0)
Returns true if account cannot send or receive tokens of mptIssue because a freeze applies.
MPTID makeMptID(std::uint32_t const sequence, AccountID const &account)
bool isPseudoAccount(SLE::const_pointer sleAcct)
Returns true if and only if sleAcct is a pseudo-account of any kind (i.e.
BaseUInt< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
bool isTesSuccess(TER x) noexcept
TERSubset< CanCvtToTER > TER
TER requireAuth(ReadView const &view, MPTIssue const &mptIssue, AccountID const &account, AuthType authType=AuthType::Legacy, std::uint8_t depth=0)
Check if the account lacks required authorization for MPT.
constexpr std::uint64_t kMaxMpTokenAmount
The maximum amount of MPTokenIssuance.