Compare commits

...

11 Commits

Author SHA1 Message Date
Mayukha Vadari
01a21f5fb8 Document MPTokenEntry::create parameters 2026-10-05 15:45:59 +01:00
Mayukha Vadari
86bc544d7d refactor: Move createMPToken onto MPTokenEntry
Replace the free createMPToken() with the static factory
MPTokenEntry<ApplyView>::create(). The body is unchanged: peek, owner
directory insert, newSLE(), fields, sponsor, insert(). Update the
callers in checkCreateMPT(), AMMCreate and EscrowHelpers.h.

Pure refactor, no behavior change.
2026-10-05 15:45:49 +01:00
Mayukha Vadari
5e2e5bf4e8 Document isFrozen return value 2026-10-05 15:45:25 +01:00
Mayukha Vadari
7f0ce98820 Include Keylet.h in the SLEBase test
(cherry picked from commit 3cca9d3266)
2026-10-05 15:45:19 +01:00
Mayukha Vadari
1b8074cb41 test: Drop extern template declarations from SLEBase test
An explicit instantiation declaration stops clang from emitting the
inline constructors of the header-only entries at -O0, so the debug
builds fail to link xrpl_tests (undefined AccountRootEntry constructors).
Naming each class in a static_assert already instantiates its definition,
which is all the exhaustiveness check needs.

(cherry picked from commit 611415e1f9)
2026-10-05 15:45:19 +01:00
Mayukha Vadari
11a9ae0eb6 refactor: Move isFrozen MPToken branch onto MPTokenEntry
Split the ltMPTOKEN branch of isFrozen(view, account, SLE const&,
depth) into a const member MPTokenEntry::isFrozen(account, depth). The
free dispatcher stays and calls the member for an MPToken SLE; its
MPTokenIssuance branch is unchanged. accountHolds() and
ammAccountHolds() already hold an MPTokenEntryR, so they call the member
directly.

The member body goes to the new src/libxrpl/ledger/entries/
MPTokenEntry.cpp with explicit instantiation.

Pure refactor, no behavior change.
2026-10-05 15:45:18 +01:00
Mayukha Vadari
ac380ea18b test: Declare, not define, entry instantiations in SLEBase test
The exhaustive per-entry-type check in the SLEBase gtest used explicit
instantiation definitions. Once an entry class gets out-of-line member
bodies in its own .cpp (with the explicit instantiation definition
there), the test would instantiate the class a second time, and MSVC
rejects it with C4661 because those member bodies are not visible in
the test translation unit. Explicit instantiation declarations still
instantiate the class definition, which is all the check needs.

(cherry picked from commit 8b07abc3cd)
2026-10-05 15:45:18 +01:00
Mayukha Vadari
b1903290a4 Document isIndividualFrozen return value 2026-10-05 15:44:53 +01:00
Mayukha Vadari
a057e3de26 refactor: Move isIndividualFrozen onto MPTokenEntry
Replace the free isIndividualFrozen(MPTokenEntryR const&) overload with
a const member MPTokenEntry::isIndividualFrozen(). The
(view, account, MPTIssue) free function stays and now calls the member.

Pure refactor, no behavior change.
2026-10-05 13:57:32 +01:00
Mayukha Vadari
a65d559516 refactor: Use MPTokenEntry in ConfidentialMPTHolderKeyUpdate
Develop added ConfidentialMPTHolderKeyUpdate (#8266) after this PR was
opened. Migrate its MPToken read and peek to MPTokenEntryR and
MPTokenEntryW so the PR still covers every MPToken pointer site.

Pure refactor: no behavior change.
2026-10-05 13:57:03 +01:00
Mayukha Vadari
2d1818ed97 refactor: Use MPTokenEntry everywhere
Replace raw MPToken (ltMPTOKEN) SLE pointers with MPTokenEntryR and
MPTokenEntryW in the ledger helpers and transactors. Helpers that take
an MPToken SLE (isIndividualFrozen, deleteAMMMPToken, and the
static proof helpers in ConfidentialMPTConvertBack and
ConfidentialMPTSend) now take the wrapper instead. They stay free
functions.

MPTokenIssuance pointers, and parameters that accept either an
MPToken or an MPTokenIssuance (isFrozen, assetOfHolding, the
MPTokenIssuanceSet target SLE), are unchanged.

Pure refactor: no behavior change.
2026-10-05 13:32:46 +01:00
21 changed files with 246 additions and 150 deletions

View File

@@ -8,8 +8,12 @@
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/UintTypes.h>
#include <cstdint>
namespace xrpl {
template <typename ViewT>
@@ -47,6 +51,59 @@ public:
: Base(keylet::mptoken(mptokenKey), view, j)
{
}
/**
* Returns true if this MPToken carries the individual-lock flag
* (lsfMPTLocked).
*
* @warning This checks only the raw per-holder lock bit. It does not
* perform the transitive vault pseudo-account check. Use isFrozen() to
* decide whether the holder may send or receive tokens.
*
* @return true if lsfMPTLocked is set on this MPToken
*/
[[nodiscard]] bool
isIndividualFrozen() const
{
return (*this)->isFlag(lsfMPTLocked);
}
/**
* Returns true if @p account cannot send or receive tokens of this
* MPToken's issuance because a freeze applies: the issuance is globally
* locked, this MPToken is individually locked, or (for a vault share)
* the vault pseudo-account's underlying asset is frozen.
*
* The issuance is read once for the global-freeze and vault checks.
*
* @param account The holder of this MPToken.
* @param depth Current recursion depth for the vault-share walk.
* @return true if @p account is frozen out of this MPToken's issuance
*/
[[nodiscard]] bool
isFrozen(AccountID const& account, std::uint8_t depth = 0) const;
/**
* Creates the MPToken of @p account for @p mptIssuanceID: links it into
* the owner directory, sets its fields and sponsor, and inserts it.
*
* Does not change the owner count.
*
* @param view The apply view to create the MPToken in.
* @param mptIssuanceID The issuance this MPToken is for.
* @param account The holder to create the MPToken for.
* @param sponsorSle The sponsor to record on the MPToken, or nullptr.
* @param flags The flags to set on the new MPToken.
* @return tesSUCCESS, or tecDIR_FULL if the owner directory is full
*/
[[nodiscard]] static TER
create(
ApplyView& view,
MPTID const& mptIssuanceID,
AccountID const& account,
SLE::Ref sponsorSle,
std::uint32_t flags)
requires Base::kIsWritable;
};
using MPTokenEntryR = MPTokenEntry<ReadView>;

View File

@@ -4,6 +4,7 @@
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/entries/MPTokenEntry.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
@@ -213,7 +214,7 @@ escrowUnlockApplyHelper<MPTIssue>(
!isTesSuccess(ret))
return ret;
if (auto const ter = createMPToken(ctx.view, mptID, receiver, *sponsorSle, 0);
if (auto const ter = MPTokenEntryW::create(ctx.view, mptID, receiver, *sponsorSle, 0);
!isTesSuccess(ter))
{
return ter; // LCOV_EXCL_LINE

View File

@@ -47,9 +47,6 @@ isGlobalFrozen(SLE const& issuanceSle);
[[nodiscard]] bool
isIndividualFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue);
[[nodiscard]] bool
isIndividualFrozen(SLE const& mptSle);
/**
* Returns true if @p account cannot send or receive tokens of @p mptIssue
* because a freeze applies. This is the complete check callers should use
@@ -282,14 +279,6 @@ unlockEscrowMPT(
STAmount const& grossAmount,
beast::Journal j);
TER
createMPToken(
ApplyView& view,
MPTID const& mptIssuanceID,
AccountID const& account,
SLE::Ref sponsorSle,
std::uint32_t const flags);
TER
checkCreateMPT(
xrpl::ApplyView& view,

View File

@@ -4,6 +4,7 @@
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/MPTokenEntry.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/IOUAmount.h>
@@ -275,13 +276,12 @@ deleteAMMTrustLine(
beast::Journal j);
/**
* Delete AMMs MPToken. The passed `sle` must be obtained from a prior
* call to view.peek().
* Delete AMMs MPToken.
*/
[[nodiscard]] TER
deleteAMMMPToken(
ApplyView& view,
SLE::pointer sleMPT,
MPTokenEntryW& sleMPT,
AccountID const& ammAccountID,
beast::Journal j);

View File

@@ -0,0 +1,78 @@
#include <xrpl/ledger/entries/MPTokenEntry.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/UintTypes.h>
#include <cstdint>
namespace xrpl {
template <typename ViewT>
bool
MPTokenEntry<ViewT>::isFrozen(AccountID const& account, std::uint8_t depth) const
{
auto const& view = this->readView();
auto const& sle = **this;
XRPL_ASSERT(sle[sfAccount] == account, "xrpl::MPTokenEntry::isFrozen : valid MPToken holder");
MPTID const mptID = sle[sfMPTokenIssuanceID];
auto const issuanceSle = view.read(keylet::mptokenIssuance(mptID));
if ((issuanceSle && isGlobalFrozen(*issuanceSle)) || sle.isFlag(lsfMPTLocked))
return true;
if (issuanceSle)
return isVaultPseudoAccountFrozen(view, account, *issuanceSle, depth);
return isVaultPseudoAccountFrozen(view, account, MPTIssue{mptID}, depth);
}
template <typename ViewT>
TER
MPTokenEntry<ViewT>::create(
ApplyView& view,
MPTID const& mptIssuanceID,
AccountID const& account,
SLE::Ref sponsorSle,
std::uint32_t flags)
requires Base::kIsWritable
{
MPTokenEntry mptoken(mptIssuanceID, account, view);
auto const ownerNode =
view.dirInsert(keylet::ownerDir(account), mptoken.keylet(), describeOwnerDir(account));
if (!ownerNode)
return tecDIR_FULL; // LCOV_EXCL_LINE
mptoken.newSLE();
(*mptoken)[sfAccount] = account;
(*mptoken)[sfMPTokenIssuanceID] = mptIssuanceID;
(*mptoken)[sfFlags] = flags;
(*mptoken)[sfOwnerNode] = *ownerNode;
addSponsorToLedgerEntry(mptoken.mutableRawSle(), sponsorSle);
mptoken.insert();
return tesSUCCESS;
}
template class MPTokenEntry<ReadView>;
template class MPTokenEntry<ApplyView>;
} // namespace xrpl

View File

@@ -11,7 +11,7 @@
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/Sandbox.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/entries/MPTokenEntry.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AMMCore.h>
@@ -633,8 +633,8 @@ ammAccountHolds(ReadView const& view, AccountID const& ammAccountID, Asset const
// Get the actual AMM balance without factoring in the balance hook
return asset.visit(
[&](MPTIssue const& issue) {
if (auto const sle = view.read(keylet::mptoken(issue, ammAccountID));
sle && !isFrozen(view, ammAccountID, *sle))
if (auto const sle = MPTokenEntryR(issue, ammAccountID, view);
sle && !sle.isFrozen(ammAccountID))
return STAmount{issue, (*sle)[sfMPTAmount]};
return STAmount{asset};
},
@@ -725,7 +725,8 @@ deleteAMMMPTokens(Sandbox& sb, AccountID const& ammAccountID, beast::Journal j)
// LCOV_EXCL_STOP
}
return {deleteAMMMPToken(sb, sleItem, ammAccountID, j), SkipEntry::No};
MPTokenEntryW sleMpt(sleItem->key(), sb, j);
return {deleteAMMMPToken(sb, sleMpt, ammAccountID, j), SkipEntry::No};
}
if (nodeType == ltRIPPLE_STATE)
{

View File

@@ -7,9 +7,9 @@
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/entries/MPTokenEntry.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/CredentialHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
@@ -58,19 +58,11 @@ isGlobalFrozen(SLE const& issuanceSle)
bool
isIndividualFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue)
{
if (auto const sle = view.read(keylet::mptoken(mptIssue.getMptID(), account)))
return isIndividualFrozen(*sle);
if (auto const sle = MPTokenEntryR(mptIssue.getMptID(), account, view))
return sle.isIndividualFrozen();
return false;
}
bool
isIndividualFrozen(SLE const& mptSle)
{
XRPL_ASSERT(mptSle.getType() == ltMPTOKEN, "xrpl::isIndividualFrozen : MPToken SLE");
return mptSle.isFlag(lsfMPTLocked);
}
bool
isFrozen(
ReadView const& view,
@@ -91,18 +83,9 @@ isFrozen(ReadView const& view, AccountID const& account, SLE const& sle, std::ui
if (sle.getType() == ltMPTOKEN)
{
XRPL_ASSERT(sle[sfAccount] == account, "xrpl::isFrozen : valid MPToken holder");
MPTID const mptID = sle[sfMPTokenIssuanceID];
auto const issuanceSle = view.read(keylet::mptokenIssuance(mptID));
if ((issuanceSle && isGlobalFrozen(*issuanceSle)) || isIndividualFrozen(sle))
return true;
if (issuanceSle)
return isVaultPseudoAccountFrozen(view, account, *issuanceSle, depth);
return isVaultPseudoAccountFrozen(view, account, MPTIssue{mptID}, depth);
// The entry needs an owning pointer, so this copies the SLE. Callers
// that already hold an MPTokenEntry call its isFrozen() directly.
return MPTokenEntryR(std::make_shared<SLE const>(sle), view).isFrozen(account, depth);
}
MPTIssue const mptIssue{sle[sfSequence], sle[sfIssuer]};
@@ -188,7 +171,7 @@ addEmptyHolding(
// still rejected before the "MPToken already exists" short circuit.
if (mpt->isFlag(lsfMPTLocked))
return tefINTERNAL; // LCOV_EXCL_LINE
if (ctx.view.peek(keylet::mptoken(mptID, accountID)))
if (MPTokenEntryW(mptID, accountID, ctx.view))
return tecDUPLICATE;
if (accountID == mptIssue.getIssuer())
return tesSUCCESS;
@@ -220,8 +203,7 @@ authorizeMPToken(
// - delete the MPToken
if ((flags & tfMPTUnauthorize) != 0u)
{
auto const mptokenKey = keylet::mptoken(mptIssuanceID, account);
auto const sleMpt = ctx.view.peek(mptokenKey);
MPTokenEntryW sleMpt(mptIssuanceID, account, ctx.view, journal);
if (!sleMpt || (*sleMpt)[sfMPTAmount] != 0 ||
(ctx.view.rules().enabled(fixCleanup3_1_3) &&
(*sleMpt)[~sfLockedAmount].valueOr(0) != 0))
@@ -231,9 +213,9 @@ authorizeMPToken(
keylet::ownerDir(account), (*sleMpt)[sfOwnerNode], sleMpt->key(), false))
return tecINTERNAL; // LCOV_EXCL_LINE
decreaseOwnerCountForObject(ctx.view, sleAcct, sleMpt, 1, journal);
decreaseOwnerCountForObject(ctx.view, sleAcct, sleMpt.mutableRawSle(), 1, journal);
ctx.view.erase(sleMpt);
sleMpt.erase();
return tesSUCCESS;
}
@@ -274,19 +256,21 @@ authorizeMPToken(
// LCOV_EXCL_STOP
}
auto const mptokenKey = keylet::mptoken(mptIssuanceID, account);
auto mptoken = std::make_shared<SLE>(mptokenKey);
if (auto ter = dirLink(ctx.view, account, mptoken))
MPTokenEntryW mptoken(mptIssuanceID, account, ctx.view, journal);
mptoken.newSLE();
// dirLink takes a non-const SLE::pointer&, so pass a copy of the pointer.
auto mptokenSle = mptoken.mutableRawSle();
if (auto ter = dirLink(ctx.view, account, mptokenSle))
return ter; // LCOV_EXCL_LINE
(*mptoken)[sfAccount] = account;
(*mptoken)[sfMPTokenIssuanceID] = mptIssuanceID;
(*mptoken)[sfFlags] = 0;
ctx.view.insert(mptoken);
mptoken.insert();
// Update owner count.
increaseOwnerCount(ctx.view, sleAcct, sponsorSle, 1, journal);
addSponsorToLedgerEntry(mptoken, sponsorSle);
addSponsorToLedgerEntry(mptoken.mutableRawSle(), sponsorSle);
return tesSUCCESS;
}
@@ -301,7 +285,7 @@ authorizeMPToken(
if (account != (*sleMptIssuance)[sfIssuer])
return tecINTERNAL; // LCOV_EXCL_LINE
auto const sleMpt = ctx.view.peek(keylet::mptoken(mptIssuanceID, *holderID));
MPTokenEntryW sleMpt(mptIssuanceID, *holderID, ctx.view, journal);
if (!sleMpt)
return tecINTERNAL; // LCOV_EXCL_LINE
@@ -324,7 +308,7 @@ authorizeMPToken(
if (flagsIn != flagsOut)
sleMpt->setFieldU32(sfFlags, flagsOut);
ctx.view.update(sleMpt);
sleMpt.update();
return tesSUCCESS;
}
@@ -340,7 +324,7 @@ removeEmptyHolding(
// a token does exist, it will get deleted. If not, return success.
bool const accountIsIssuer = accountID == mptIssue.getIssuer();
auto const& mptID = mptIssue.getMptID();
auto const mptoken = ctx.view.peek(keylet::mptoken(mptID, accountID));
MPTokenEntryW mptoken(mptID, accountID, ctx.view, journal);
if (!mptoken)
return accountIsIssuer ? (TER)tesSUCCESS : (TER)tecOBJECT_NOT_FOUND;
// Unlike a trust line, if the account is the issuer, and the token has a
@@ -436,8 +420,7 @@ requireAuth(
}
}
auto const mptokenID = keylet::mptoken(mptID.key, account);
auto const sleToken = view.read(mptokenID);
MPTokenEntryR const sleToken(mptID.key, account, view);
// if account has no MPToken, fail
if (!sleToken && (authType == AuthType::StrongAuth || authType == AuthType::Legacy))
@@ -496,8 +479,7 @@ enforceMPTokenAuthorization(
if (account == sleIssuance->at(sfIssuer))
return tefINTERNAL; // LCOV_EXCL_LINE
auto const keylet = keylet::mptoken(mptIssuanceID, account);
auto const sleToken = ctx.view.read(keylet); // NOTE: might be null
MPTokenEntryR const sleToken(mptIssuanceID, account, ctx.view, j); // NOTE: might not exist
auto const maybeDomainID = sleIssuance->at(~sfDomainID);
bool expired = false;
bool const authorizedByDomain = [&]() -> bool {
@@ -513,7 +495,7 @@ enforceMPTokenAuthorization(
return false;
}();
if (!authorizedByDomain && sleToken == nullptr)
if (!authorizedByDomain && !sleToken)
{
// Could not find MPToken and won't create one, could be either of:
//
@@ -536,14 +518,14 @@ enforceMPTokenAuthorization(
// We found an MPToken, but sfDomainID is not set, so this is a classic
// MPToken which requires authorization by the token issuer.
XRPL_ASSERT(
sleToken != nullptr && !maybeDomainID.has_value(),
sleToken.exists() && !maybeDomainID.has_value(),
"xrpl::enforceMPTokenAuthorization : found MPToken");
if (sleToken->isFlag(lsfMPTAuthorized))
return tesSUCCESS;
return tecNO_AUTH;
}
if (authorizedByDomain && sleToken != nullptr)
if (authorizedByDomain && sleToken.exists())
{
// Found an MPToken, authorized by the domain. Ignore authorization flag
// lsfMPTAuthorized because it is meaningless. Return tesSUCCESS
@@ -557,7 +539,7 @@ enforceMPTokenAuthorization(
// Could not find MPToken but there should be one because we are
// authorized by domain. Proceed to create it, then return tesSUCCESS
XRPL_ASSERT(
maybeDomainID.has_value() && sleToken == nullptr,
maybeDomainID.has_value() && !sleToken.exists(),
"xrpl::enforceMPTokenAuthorization : new MPToken for domain");
if (auto const err = authorizeMPToken(
ctx,
@@ -734,8 +716,7 @@ lockEscrowMPT(ApplyView& view, AccountID const& sender, STAmount const& amount,
// 1. Decrease the MPT Holder MPTAmount
// 2. Increase the MPT Holder EscrowedAmount
{
auto const mptokenID = keylet::mptoken(mptID.key, sender);
auto sle = view.peek(mptokenID);
MPTokenEntryW sle(mptID.key, sender, view, j);
if (!sle)
{ // LCOV_EXCL_START
JLOG(j.error()) << "lockEscrowMPT: MPToken not found for " << sender;
@@ -774,7 +755,7 @@ lockEscrowMPT(ApplyView& view, AccountID const& sender, STAmount const& amount,
sle->setFieldU64(sfLockedAmount, pay);
}
view.update(sle);
sle.update();
}
// 1. Increase the Issuance EscrowedAmount
@@ -865,8 +846,7 @@ unlockEscrowMPT(
if (issuer != receiver)
{
// Increase the MPT Holder MPTAmount
auto const mptokenID = keylet::mptoken(mptID.key, receiver);
auto sle = view.peek(mptokenID);
MPTokenEntryW sle(mptID.key, receiver, view, j);
if (!sle)
{ // LCOV_EXCL_START
JLOG(j.error()) << "unlockEscrowMPT: MPToken not found for " << receiver;
@@ -885,7 +865,7 @@ unlockEscrowMPT(
} // LCOV_EXCL_STOP
(*sle)[sfMPTAmount] += delta;
view.update(sle);
sle.update();
}
else
{
@@ -912,8 +892,7 @@ unlockEscrowMPT(
return tecINTERNAL;
} // LCOV_EXCL_STOP
// Decrease the MPT Holder EscrowedAmount
auto const mptokenID = keylet::mptoken(mptID.key, sender);
auto sle = view.peek(mptokenID);
MPTokenEntryW sle(mptID.key, sender, view, j);
if (!sle)
{ // LCOV_EXCL_START
JLOG(j.error()) << "unlockEscrowMPT: MPToken not found for " << sender;
@@ -946,7 +925,7 @@ unlockEscrowMPT(
{
sle->setFieldU64(sfLockedAmount, newLocked);
}
view.update(sle);
sle.update();
// Note: The gross amount is the amount that was locked, the net
// amount is the amount that is being unlocked. The difference is the fee
@@ -970,35 +949,6 @@ unlockEscrowMPT(
return tesSUCCESS;
}
TER
createMPToken(
ApplyView& view,
MPTID const& mptIssuanceID,
AccountID const& account,
SLE::Ref sponsorSle,
std::uint32_t const flags)
{
auto const mptokenKey = keylet::mptoken(mptIssuanceID, account);
auto const ownerNode =
view.dirInsert(keylet::ownerDir(account), mptokenKey, describeOwnerDir(account));
if (!ownerNode)
return tecDIR_FULL; // LCOV_EXCL_LINE
auto mptoken = std::make_shared<SLE>(mptokenKey);
(*mptoken)[sfAccount] = account;
(*mptoken)[sfMPTokenIssuanceID] = mptIssuanceID;
(*mptoken)[sfFlags] = flags;
(*mptoken)[sfOwnerNode] = *ownerNode;
addSponsorToLedgerEntry(mptoken, sponsorSle);
view.insert(mptoken);
return tesSUCCESS;
}
TER
checkCreateMPT(
xrpl::ApplyView& view,
@@ -1015,7 +965,8 @@ checkCreateMPT(
auto const mptokenID = keylet::mptoken(mptIssuanceID.key, holder);
if (!view.exists(mptokenID))
{
if (auto const err = createMPToken(view, mptIssue.getMptID(), holder, sponsorSle, flags);
if (auto const err =
MPTokenEntryW::create(view, mptIssue.getMptID(), holder, sponsorSle, flags);
!isTesSuccess(err))
{
return err;

View File

@@ -7,6 +7,7 @@
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/MPTokenEntry.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
@@ -846,7 +847,7 @@ deleteAMMTrustLine(
TER
deleteAMMMPToken(
ApplyView& view,
SLE::pointer sleMpt,
MPTokenEntryW& sleMpt,
AccountID const& ammAccountID,
beast::Journal j)
{
@@ -854,7 +855,7 @@ deleteAMMMPToken(
keylet::ownerDir(ammAccountID), (*sleMpt)[sfOwnerNode], sleMpt->key(), false))
return tefBAD_LEDGER; // LCOV_EXCL_LINE
view.erase(sleMpt);
sleMpt.erase();
return tesSUCCESS;
}

View File

@@ -7,6 +7,7 @@
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/entries/MPTokenEntry.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
@@ -436,10 +437,9 @@ accountHolds(
return view.balanceHookMPT(issuer, mptIssue, available);
}
auto const sleMpt = view.read(keylet::mptoken(mptIssue.getMptID(), account));
MPTokenEntryR const sleMpt(mptIssue.getMptID(), account, view, j);
if (!sleMpt ||
(zeroIfFrozen == FreezeHandling::ZeroIfFrozen && isFrozen(view, account, *sleMpt)))
if (!sleMpt || (zeroIfFrozen == FreezeHandling::ZeroIfFrozen && sleMpt.isFrozen(account)))
{
amount.clear(mptIssue);
}
@@ -1238,15 +1238,14 @@ directSendNoFeeMPT(
}
else
{
auto const mptokenID = keylet::mptoken(mptID.key, uSenderID);
if (auto sle = view.peek(mptokenID))
if (auto sle = MPTokenEntryW(mptID.key, uSenderID, view, j))
{
auto const senderBalance = sle->getFieldU64(sfMPTAmount);
if (senderBalance < amt)
return tecINSUFFICIENT_FUNDS;
view.creditHookMPT(uSenderID, uReceiverID, saAmount, (*sle)[sfMPTAmount], available);
(*sle)[sfMPTAmount] = senderBalance - amt;
view.update(sle);
sle.update();
}
else
{
@@ -1268,8 +1267,7 @@ directSendNoFeeMPT(
}
else
{
auto const mptokenID = keylet::mptoken(mptID.key, uReceiverID);
if (auto sle = view.peek(mptokenID))
if (auto sle = MPTokenEntryW(mptID.key, uReceiverID, view, j))
{
if (view.rules().enabled(featureMPTokensV2))
{
@@ -1280,7 +1278,7 @@ directSendNoFeeMPT(
}
view.creditHookMPT(uSenderID, uReceiverID, saAmount, (*sle)[sfMPTAmount], available);
(*sle)[sfMPTAmount] += amt;
view.update(sle);
sle.update();
}
else
{

View File

@@ -5,6 +5,7 @@
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/entries/MPTokenEntry.h>
#include <xrpl/ledger/helpers/CredentialHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
@@ -210,7 +211,7 @@ isSoleShareholder(ReadView const& view, AccountID const& account, SLE::ConstRef
auto const shareMPTID =
makeMptID(issuance->getFieldU32(sfSequence), issuance->getAccountID(sfIssuer));
auto const sleToken = view.read(keylet::mptoken(shareMPTID, account));
MPTokenEntryR const sleToken(shareMPTID, account, view);
if (!sleToken)
return false; // LCOV_EXCL_LINE

View File

@@ -5,6 +5,7 @@
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/Sandbox.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/entries/MPTokenEntry.h>
#include <xrpl/ledger/helpers/AMMHelpers.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
@@ -330,7 +331,7 @@ applyCreate(ApplyContext& ctx, Sandbox& sb, AccountID const& account, beast::Jou
return err;
}
if (auto const err = createMPToken(sb, mptID, accountId, {}, flags);
if (auto const err = MPTokenEntryW::create(sb, mptID, accountId, {}, flags);
!isTesSuccess(err))
return err;
// Don't adjust AMM owner count.

View File

@@ -4,6 +4,7 @@
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/MPTokenEntry.h>
#include <xrpl/protocol/ConfidentialTransfer.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
@@ -94,7 +95,7 @@ ConfidentialMPTClawback::preclaim(PreclaimContext const& ctx)
return tecNO_PERMISSION;
// Check holder's MPToken
auto const sleHolderMPToken = ctx.view.read(keylet::mptoken(mptIssuanceID, holder));
MPTokenEntryR const sleHolderMPToken(mptIssuanceID, holder, ctx.view);
if (!sleHolderMPToken)
return tecOBJECT_NOT_FOUND;
@@ -133,7 +134,7 @@ ConfidentialMPTClawback::doApply()
auto const holder = ctx_.tx[sfHolder];
auto sleIssuance = view().peek(keylet::mptokenIssuance(mptIssuanceID));
auto sleHolderMPToken = view().peek(keylet::mptoken(mptIssuanceID, holder));
MPTokenEntryW sleHolderMPToken(mptIssuanceID, holder, view(), j_);
if (!sleIssuance || !sleHolderMPToken)
{
@@ -228,7 +229,7 @@ ConfidentialMPTClawback::doApply()
return tecINTERNAL; // LCOV_EXCL_LINE
(*sleIssuance)[sfOutstandingAmount] = oldOA - clawAmount;
view().update(sleHolderMPToken);
sleHolderMPToken.update();
view().update(sleIssuance);
return tesSUCCESS;

View File

@@ -6,6 +6,7 @@
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/MPTokenEntry.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/ConfidentialTransfer.h>
#include <xrpl/protocol/Feature.h>
@@ -108,7 +109,7 @@ ConfidentialMPTConvert::preclaim(PreclaimContext const& ctx)
if (requiresAuditor != hasAuditor)
return tecNO_PERMISSION;
auto const sleMptoken = ctx.view.read(keylet::mptoken(issuanceID, account));
MPTokenEntryR const sleMptoken(issuanceID, account, ctx.view);
if (!sleMptoken)
return tecOBJECT_NOT_FOUND;
@@ -225,7 +226,7 @@ ConfidentialMPTConvert::doApply()
{
auto const mptIssuanceID = ctx_.tx[sfMPTokenIssuanceID];
auto sleMptoken = view().peek(keylet::mptoken(mptIssuanceID, accountID_));
MPTokenEntryW sleMptoken(mptIssuanceID, accountID_, view(), j_);
if (!sleMptoken)
{
// LCOV_EXCL_START
@@ -377,7 +378,7 @@ ConfidentialMPTConvert::doApply()
}
view().update(sleIssuance);
view().update(sleMptoken);
sleMptoken.update();
return tesSUCCESS;
}

View File

@@ -5,6 +5,7 @@
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/MPTokenEntry.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/ConfidentialTransfer.h>
#include <xrpl/protocol/Feature.h>
@@ -71,7 +72,7 @@ static TER
verifyProofs(
STTx const& tx,
std::shared_ptr<SLE const> const& issuance,
std::shared_ptr<SLE const> const& mptoken)
MPTokenEntryR const& mptoken)
{
if (!mptoken->isFieldPresent(sfHolderEncryptionKey))
{
@@ -187,7 +188,7 @@ ConfidentialMPTConvertBack::preclaim(PreclaimContext const& ctx)
// LCOV_EXCL_STOP
}
auto const sleMptoken = ctx.view.read(keylet::mptoken(mptIssuanceID, account));
MPTokenEntryR const sleMptoken(mptIssuanceID, account, ctx.view);
if (!sleMptoken)
return tecOBJECT_NOT_FOUND;
@@ -244,7 +245,7 @@ ConfidentialMPTConvertBack::doApply()
{
auto const mptIssuanceID = ctx_.tx[sfMPTokenIssuanceID];
auto sleMptoken = view().peek(keylet::mptoken(mptIssuanceID, accountID_));
MPTokenEntryW sleMptoken(mptIssuanceID, accountID_, view(), j_);
if (!sleMptoken)
{
// LCOV_EXCL_START
@@ -334,7 +335,7 @@ ConfidentialMPTConvertBack::doApply()
incrementConfidentialVersion(*sleMptoken);
view().update(sleIssuance);
view().update(sleMptoken);
sleMptoken.update();
return tesSUCCESS;
}

View File

@@ -5,6 +5,7 @@
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/MPTokenEntry.h>
#include <xrpl/protocol/ConfidentialTransfer.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
@@ -116,7 +117,7 @@ ConfidentialMPTHolderKeyUpdate::preclaim(PreclaimContext const& ctx)
if (!sleIssuance->isFlag(lsfMPTCanHoldConfidentialBalance))
return tecNO_PERMISSION;
auto const sleMptoken = ctx.view.read(keylet::mptoken(mptIssuanceID, account));
MPTokenEntryR const sleMptoken(mptIssuanceID, account, ctx.view);
if (!sleMptoken)
return tecOBJECT_NOT_FOUND;
@@ -150,7 +151,7 @@ TER
ConfidentialMPTHolderKeyUpdate::doApply()
{
auto const mptIssuanceID = ctx_.tx[sfMPTokenIssuanceID];
auto sleMptoken = view().peek(keylet::mptoken(mptIssuanceID, accountID_));
MPTokenEntryW sleMptoken(mptIssuanceID, accountID_, view(), j_);
if (!sleMptoken)
{
// LCOV_EXCL_START
@@ -166,7 +167,7 @@ ConfidentialMPTHolderKeyUpdate::doApply()
// The holder revokes their pending recovery authorization; the
// current key and balances are left untouched.
sleMptoken->makeFieldAbsent(sfRecoveryKey);
view().update(sleMptoken);
sleMptoken.update();
return tesSUCCESS;
}
@@ -197,7 +198,7 @@ ConfidentialMPTHolderKeyUpdate::doApply()
// LCOV_EXCL_STOP
}
view().update(sleMptoken);
sleMptoken.update();
return tesSUCCESS;
}

View File

@@ -5,6 +5,7 @@
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/MPTokenEntry.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/ConfidentialTransfer.h>
#include <xrpl/protocol/Indexes.h>
@@ -59,8 +60,7 @@ ConfidentialMPTMergeInbox::preclaim(PreclaimContext const& ctx)
// LCOV_EXCL_STOP
}
auto const sleMptoken =
ctx.view.read(keylet::mptoken(ctx.tx[sfMPTokenIssuanceID], ctx.tx[sfAccount]));
MPTokenEntryR const sleMptoken(ctx.tx[sfMPTokenIssuanceID], ctx.tx[sfAccount], ctx.view);
if (!sleMptoken)
return tecOBJECT_NOT_FOUND;
@@ -88,7 +88,7 @@ TER
ConfidentialMPTMergeInbox::doApply()
{
auto const mptIssuanceID = ctx_.tx[sfMPTokenIssuanceID];
auto sleMptoken = view().peek(keylet::mptoken(mptIssuanceID, accountID_));
MPTokenEntryW sleMptoken(mptIssuanceID, accountID_, view(), j_);
if (!sleMptoken)
{
// LCOV_EXCL_START
@@ -147,7 +147,7 @@ ConfidentialMPTMergeInbox::doApply()
incrementConfidentialVersion(*sleMptoken);
view().update(sleMptoken);
sleMptoken.update();
return tesSUCCESS;
}

View File

@@ -5,6 +5,7 @@
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/MPTokenEntry.h>
#include <xrpl/protocol/ConfidentialTransfer.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
@@ -131,7 +132,7 @@ ConfidentialMPTMirrorUpdate::preclaim(PreclaimContext const& ctx)
return tecNO_TARGET;
// In either issuer or holder mode, check the existence of the MPToken object.
auto const sleMptoken = ctx.view.read(keylet::mptoken(mptIssuanceID, holderID));
MPTokenEntryR const sleMptoken(mptIssuanceID, holderID, ctx.view);
if (!sleMptoken)
return tecOBJECT_NOT_FOUND;
@@ -224,7 +225,7 @@ ConfidentialMPTMirrorUpdate::doApply()
auto const holder = ctx_.tx[~sfHolder];
auto const holderID = holder.value_or(accountID_);
auto sleMptoken = view().peek(keylet::mptoken(mptIssuanceID, holderID));
MPTokenEntryW sleMptoken(mptIssuanceID, holderID, view(), j_);
if (!sleMptoken)
{
// LCOV_EXCL_START
@@ -250,7 +251,7 @@ ConfidentialMPTMirrorUpdate::doApply()
setAuditorMirrorEpoch(*sleIssuance, *sleMptoken);
}
view().update(sleMptoken);
sleMptoken.update();
return tesSUCCESS;
}

View File

@@ -5,6 +5,7 @@
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/MPTokenEntry.h>
#include <xrpl/ledger/helpers/CredentialHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/ConfidentialTransfer.h>
@@ -100,8 +101,8 @@ namespace detail {
static TER
verifySendProofs(
PreclaimContext const& ctx,
std::shared_ptr<SLE const> const& sleSenderMPToken,
std::shared_ptr<SLE const> const& sleDestinationMPToken,
MPTokenEntryR const& sleSenderMPToken,
MPTokenEntryR const& sleDestinationMPToken,
std::shared_ptr<SLE const> const& sleIssuance)
{
// Sanity check
@@ -222,7 +223,7 @@ ConfidentialMPTSend::preclaim(PreclaimContext const& ctx)
}
// Check sender's MPToken existence
auto const sleSenderMPToken = ctx.view.read(keylet::mptoken(mptIssuanceID, account));
MPTokenEntryR const sleSenderMPToken(mptIssuanceID, account, ctx.view);
if (!sleSenderMPToken)
return tecOBJECT_NOT_FOUND;
@@ -235,7 +236,7 @@ ConfidentialMPTSend::preclaim(PreclaimContext const& ctx)
}
// Check destination's MPToken existence
auto const sleDestinationMPToken = ctx.view.read(keylet::mptoken(mptIssuanceID, destination));
MPTokenEntryR const sleDestinationMPToken(mptIssuanceID, destination, ctx.view);
if (!sleDestinationMPToken)
return tecOBJECT_NOT_FOUND;
@@ -305,8 +306,8 @@ ConfidentialMPTSend::doApply()
auto const mptIssuanceID = ctx_.tx[sfMPTokenIssuanceID];
auto const destination = ctx_.tx[sfDestination];
auto sleSenderMPToken = view().peek(keylet::mptoken(mptIssuanceID, accountID_));
auto sleDestinationMPToken = view().peek(keylet::mptoken(mptIssuanceID, destination));
MPTokenEntryW sleSenderMPToken(mptIssuanceID, accountID_, view(), j_);
MPTokenEntryW sleDestinationMPToken(mptIssuanceID, destination, view(), j_);
auto const sleIssuance = view().read(keylet::mptokenIssuance(mptIssuanceID));
auto const sleDestAcct = view().read(keylet::account(destination));
@@ -469,8 +470,8 @@ ConfidentialMPTSend::doApply()
// increment sender version only; receiver version is not modified by incoming sends
incrementConfidentialVersion(*sleSenderMPToken);
view().update(sleSenderMPToken);
view().update(sleDestinationMPToken);
sleSenderMPToken.update();
sleDestinationMPToken.update();
return tesSUCCESS;
}

View File

@@ -1,6 +1,7 @@
#include <xrpl/tx/transactors/token/MPTokenAuthorize.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/entries/MPTokenEntry.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/protocol/Feature.h>
@@ -47,8 +48,7 @@ MPTokenAuthorize::preclaim(PreclaimContext const& ctx)
// `holderID` is NOT used
if (!holderID)
{
SLE::const_pointer const sleMpt =
ctx.view.read(keylet::mptoken(ctx.tx[sfMPTokenIssuanceID], accountID));
MPTokenEntryR const sleMpt(ctx.tx[sfMPTokenIssuanceID], accountID, ctx.view);
// There is an edge case where all holders have zero balance, issuance
// is legally destroyed, then outstanding MPT(s) are deleted afterwards.

View File

@@ -3,6 +3,7 @@
#include <xrpl/basics/Log.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/ledger/entries/MPTokenEntry.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
@@ -131,7 +132,7 @@ VaultDelete::doApply()
}
// Try to remove MPToken for vault shares for the vault owner if it exists.
if (auto const mptoken = view().peek(keylet::mptoken(shareMPTID, accountID_)))
if (MPTokenEntryW(shareMPTID, accountID_, view(), j_))
{
if (auto const ter =
removeEmptyHolding(applyViewContext, accountID_, MPTIssue(shareMPTID), j_);

View File

@@ -38,6 +38,7 @@
#include <xrpl/ledger/entries/XChainOwnedClaimIDEntry.h> // IWYU pragma: keep
#include <xrpl/ledger/entries/XChainOwnedCreateAccountClaimIDEntry.h> // IWYU pragma: keep
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Keylet.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STLedgerEntry.h>
@@ -76,12 +77,22 @@ template class SLEBase<ApplyView>;
#pragma push_macro("LEDGER_ENTRY")
#undef LEDGER_ENTRY
// No explicit instantiation here, neither a definition nor a declaration.
// A definition would instantiate an entry a second time once its member
// bodies live in its own .cpp (MSVC rejects that with C4661 under /WX), and a
// declaration (`extern template`) stops clang from emitting the inline
// constructors of the header-only entries at -O0, which leaves them undefined
// at link time in the debug builds. Naming the class in a static_assert is
// enough to instantiate its definition, which is all this exhaustiveness
// check needs; the per-type suites exercise the members.
#define LEDGER_ENTRY(tag, value, name, ...) \
template class name##Entry<ReadView>; \
template class name##Entry<ApplyView>; \
static_assert( \
name##Entry<ReadView>::kEntryType == tag && name##Entry<ApplyView>::kEntryType == tag, \
#name "Entry must be bound to " #tag);
#name "Entry must be bound to " #tag); \
static_assert( \
std::is_constructible_v<name##Entry<ReadView>, Keylet const&, ReadView const&> && \
std::is_constructible_v<name##Entry<ApplyView>, Keylet const&, ApplyView&>, \
#name "Entry must be constructible from a Keylet and a view");
#include <xrpl/protocol/detail/ledger_entries.macro>