Compare commits

...

27 Commits

Author SHA1 Message Date
Mayukha Vadari
256c1086c2 Document canTrade parameter and return value 2026-10-05 15:48:10 +01:00
Mayukha Vadari
c65edeafe4 refactor: Move canTrade onto MPTokenIssuanceEntry
Add the const member MPTokenIssuanceEntry::canTrade(depth), which holds
the MPT branch of the free canTrade: lsfMPTCanTrade and the
sfReferenceHolding recursion into the underlying asset (still through
the free Asset overload). It returns tecOBJECT_NOT_FOUND for a missing
issuance, so the MPT branch of canTrade(view, Asset, depth) becomes a
one-line call to the member.
2026-10-05 15:47:59 +01:00
Mayukha Vadari
48a07702be Document canTransfer parameters 2026-10-05 15:47:52 +01:00
Mayukha Vadari
1426579f03 refactor: Move canTransfer onto MPTokenIssuanceEntry
Add the const member MPTokenIssuanceEntry::canTransfer(from, to, waive,
depth), which holds the issuance-side logic: the issuer and waive
exemptions, lsfMPTCanTransfer, and the sfReferenceHolding recursion
into the underlying asset (still through the free Asset overload). It
returns tecOBJECT_NOT_FOUND for a missing issuance, so the free
canTransfer(view, MPTIssue, ...) becomes a one-line shim.
2026-10-05 15:47:41 +01:00
Mayukha Vadari
3049cbf5e0 Document isSoleShareholder account parameter and return value 2026-10-05 15:47:34 +01:00
Mayukha Vadari
d2a7d4b0a6 refactor: Move isSoleShareholder onto MPTokenIssuanceEntry
Replace the free isSoleShareholder(view, account,
MPTokenIssuanceEntryR const&) from VaultHelpers with the const member
MPTokenIssuanceEntry::isSoleShareholder(account). The holder's MPToken
is still read raw through the entry's view. The callers in
VaultWithdraw and VaultClawback pass an entry over the same view they
passed before.
2026-10-05 15:47:24 +01:00
Mayukha Vadari
a050c33365 Document isVaultPseudoAccountFrozen account parameter and return value 2026-10-05 15:47:16 +01:00
Mayukha Vadari
a9e6b0a626 refactor: Move isVaultPseudoAccountFrozen onto MPTokenIssuanceEntry
Replace the free isVaultPseudoAccountFrozen(view, account,
MPTokenIssuanceEntryR const&, depth) with the const member
MPTokenIssuanceEntry::isVaultPseudoAccountFrozen(account, depth). It
holds the amendment and depth preconditions and the issuance body
from View.cpp; the recursion still goes through the free isAnyFrozen.
The member returns false for a missing issuance, so the
(view, account, MPTIssue, depth) overload becomes a one-line shim and
isAnyFrozen no longer needs two paths.

View.h no longer uses MPTokenIssuanceEntry, so drop its include.
2026-10-05 15:47:05 +01:00
Mayukha Vadari
1a316007e0 Document issuerSelfDebitHook amount parameter 2026-10-05 15:46:57 +01:00
Mayukha Vadari
d18aaab143 refactor: Move issuerSelfDebitHookMPT onto MPTokenIssuanceEntry
Add the writable member MPTokenIssuanceEntry::issuerSelfDebitHook().
It builds the MPTIssue from the issuance's Sequence and Issuer and
calls the view's issuerSelfDebitHookMPT() with availableAmount(). The
free issuerSelfDebitHookMPT(ApplyView, MPTIssue, amount) stays as a
shim: it throws tecINTERNAL if the issuance is missing, as before, and
otherwise calls the member.
2026-10-05 15:46:49 +01:00
Mayukha Vadari
6994e386c6 Document issuerFundsToSelfIssue return value 2026-10-05 15:46:40 +01:00
Mayukha Vadari
404df08f1a refactor: Move issuerFundsToSelfIssue onto MPTokenIssuanceEntry
Add the const member MPTokenIssuanceEntry::issuerFundsToSelfIssue().
It builds the MPTIssue from the issuance's Sequence and Issuer and
passes availableAmount() to the view's balanceHookSelfIssueMPT(). The
free issuerFundsToSelfIssue(ReadView, MPTIssue) stays as a shim: it
returns a zero amount if the issuance is missing and otherwise calls
the member.
2026-10-05 15:46:31 +01:00
Mayukha Vadari
068da5de11 Document canAddHolding return value 2026-10-05 15:46:22 +01:00
Mayukha Vadari
f269a5810b refactor: Move canAddHolding onto MPTokenIssuanceEntry
Add the const member MPTokenIssuanceEntry::canAddHolding(), which
returns tecOBJECT_NOT_FOUND for a missing issuance and tecNO_AUTH when
lsfMPTCanTransfer is not set. The free canAddHolding(ReadView, MPTIssue)
stays as a one-line shim that builds the entry and calls the member.
2026-10-05 15:46:14 +01:00
Mayukha Vadari
b17e613e1f Document transferRate return value 2026-10-05 15:45:53 +01:00
Mayukha Vadari
d4b462e560 refactor: Move transferRate onto MPTokenIssuanceEntry
Add the const member MPTokenIssuanceEntry::transferRate(), which holds
the TransferFee-to-Rate logic, in MPTokenIssuanceEntry.cpp. The free
transferRate(ReadView, MPTID) stays as a shim: it builds the entry,
returns parity if the issuance is missing, and otherwise calls the
member.
2026-10-05 15:45:42 +01:00
Mayukha Vadari
8fa25ee5a7 Document availableAmount return value 2026-10-05 15:45:28 +01:00
Mayukha Vadari
7aa7f357e6 refactor: Move availableMPTAmount onto MPTokenIssuanceEntry
Replace the free availableMPTAmount(MPTokenIssuanceEntryR const&) with
the const member MPTokenIssuanceEntry::availableAmount(). The
(ReadView, MPTID) overload stays as a shim that builds the entry,
throws if it is missing, and calls the member.
2026-10-05 15:45:21 +01:00
Mayukha Vadari
0a40c8e642 Document maxAmount return value 2026-10-05 15:45:03 +01:00
Mayukha Vadari
4d14f5ac77 Include Keylet.h in the SLEBase test
(cherry picked from commit 3cca9d3266)
2026-10-05 15:44:19 +01:00
Mayukha Vadari
517a2f2320 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:44:19 +01:00
Mayukha Vadari
2a4f714370 refactor: Move maxMPTAmount onto MPTokenIssuanceEntry
Add the const member MPTokenIssuanceEntry::maxAmount() and use it at
every call site that already holds an issuance entry. The member body
lives in the new src/libxrpl/ledger/entries/MPTokenIssuanceEntry.cpp,
which explicitly instantiates the read-only and writable entries.

The free maxMPTAmount(SLE const&) stays and holds the implementation:
ValidMPTBalanceChanges::visitEntry and the RPC AssetCache call it with
a bare SLE and no view, so they cannot build an entry.
2026-10-05 15:44:19 +01:00
Mayukha Vadari
2dfc9d0d02 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:44:19 +01:00
Mayukha Vadari
63751ecbb0 Document isGlobalFrozen return value 2026-10-05 15:43:21 +01:00
Mayukha Vadari
6e7265e3d0 refactor: Move isGlobalFrozen onto MPTokenIssuanceEntry
Replace the free isGlobalFrozen(MPTokenIssuanceEntryR const&) with a
const member MPTokenIssuanceEntry::isGlobalFrozen(). The
(ReadView, MPTIssue) overload stays as a shim that builds the entry and
calls the member. The old type assertion is dropped: the entry type is
now bound at compile time and checked by the SLEBase constructors.
2026-10-05 14:18:25 +01:00
Mayukha Vadari
aad945ffb0 refactor: Use MPTokenIssuanceEntry in ConfidentialMPTHolderKeyUpdate
Develop added the ConfidentialMPTHolderKeyUpdate transactor (#8266),
which reads the issuance with a raw view.read(). Use
MPTokenIssuanceEntryR there too, so that this PR still covers every
MPTokenIssuance read.
2026-10-05 14:18:04 +01:00
Mayukha Vadari
5252fbf8a2 refactor: Use MPTokenIssuanceEntry everywhere
Replace raw MPTokenIssuance SLE pointers with MPTokenIssuanceEntryR
(read-only) and MPTokenIssuanceEntryW (writable) in the ledger
helpers, transactors, and payment paths. view.read()/peek() become
entry constructors, and insert/update/erase go through the entry.

Helpers that take an issuance SLE now take MPTokenIssuanceEntryR:
isGlobalFrozen, assetOfHolding (issuance parameter only),
availableMPTAmount, isVaultPseudoAccountFrozen, the vault share
conversion helpers, isSoleShareholder, checkVaultDomain, and a few
file-local helpers. They stay free functions.

maxMPTAmount keeps its SLE parameter because an invariant calls it
from visitEntry(), where no view exists to build an entry. Code that
branches on MPToken vs. MPTokenIssuance keeps its generic SLE.

Pure refactor: no behavior change.
2026-10-05 13:33:20 +01:00
36 changed files with 588 additions and 388 deletions

View File

@@ -79,13 +79,6 @@ isVaultPseudoAccountFrozen(
MPTIssue const& mptShare,
std::uint8_t depth);
[[nodiscard]] bool
isVaultPseudoAccountFrozen(
ReadView const& view,
AccountID const& account,
SLE const& issuanceSle,
std::uint8_t depth);
[[nodiscard]] bool
isLPTokenFrozen(
ReadView const& view,

View File

@@ -5,9 +5,14 @@
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/Rate.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/UintTypes.h>
#include <cstdint>
@@ -48,6 +53,160 @@ public:
: Base(keylet::mptokenIssuance(issuanceKey), view, j)
{
}
/**
* Returns true if the issuance is locked (lsfMPTLocked).
*
* @return true if lsfMPTLocked is set, false otherwise.
*/
[[nodiscard]] bool
isGlobalFrozen() const
{
return (*this)->isFlag(lsfMPTLocked);
}
/**
* Returns the issuance's MaximumAmount, or the protocol maximum if the
* field is absent. Never exceeds 2**63-1.
*
* @return the maximum amount that may be outstanding for this issuance.
*/
[[nodiscard]] std::int64_t
maxAmount() const;
/**
* Returns maxAmount() minus OutstandingAmount.
*
* OutstandingAmount may overflow, so the result might be negative, but it
* is always <= |MaximumAmount - OutstandingAmount|.
*
* @return the amount of this issuance still available to be issued.
*/
[[nodiscard]] std::int64_t
availableAmount() const
{
auto const max = maxAmount();
auto const outstanding = (**this)[sfOutstandingAmount];
return max - outstanding;
}
/**
* Returns the MPT transfer fee as a Rate, in fractions of 1 billion
* (a 1% fee is 1,010,000,000). Returns parity if TransferFee is absent.
*
* @return the transfer fee rate for this issuance.
*/
[[nodiscard]] Rate
transferRate() const;
/**
* Returns tesSUCCESS if a holding of this issuance may be added:
* tecOBJECT_NOT_FOUND if the issuance does not exist, tecNO_AUTH if
* lsfMPTCanTransfer is not set.
*
* @return tesSUCCESS, tecOBJECT_NOT_FOUND, or tecNO_AUTH.
*/
[[nodiscard]] TER
canAddHolding() const
{
if (!this->exists())
{
return tecOBJECT_NOT_FOUND;
}
if (!(*this)->isFlag(lsfMPTCanTransfer))
{
return tecNO_AUTH;
}
return tesSUCCESS;
}
/**
* Returns the funds the issuer can still self-issue through an
* issuer-owned MPT sell offer: availableAmount(), less amounts already
* self-sold, as tracked by the view's balanceHookSelfIssueMPT().
*
* @return the amount the issuer may still self-issue.
*/
[[nodiscard]] STAmount
issuerFundsToSelfIssue() const;
/**
* Records @p amount of this MPT sold by the issuer through an
* issuer-owned sell offer, via the view's issuerSelfDebitHookMPT().
* See ApplyView::issuerSelfDebitHookMPT().
*
* @param amount the amount of this MPT the issuer sold.
*/
void
issuerSelfDebitHook(std::uint64_t amount)
requires Base::kIsWritable;
/**
* Returns true if @p account is frozen through the vault behind this
* issuance: if these are vault shares, checks whether the issuer (the
* vault pseudo-account) or @p account is frozen for the vault's
* underlying asset, recursing up to kMaxAssetCheckDepth. Returns false
* if featureSingleAssetVault is disabled or the issuance does not exist.
*
* @param account the account to check for freeze.
* @param depth Current recursion depth; callers outside the freeze
* checks should pass 0.
* @return true if @p account is frozen through the vault, false otherwise.
*/
[[nodiscard]] bool
isVaultPseudoAccountFrozen(AccountID const& account, std::uint8_t depth) const;
/**
* Returns true iff @p account holds all of this vault share issuance's
* outstanding shares, i.e. is the sole remaining shareholder. Returns
* false if the account holds no shares or fewer than the total
* outstanding.
*
* @param account the account to check.
* @return true if @p account is the sole shareholder, false otherwise.
*/
[[nodiscard]] bool
isSoleShareholder(AccountID const& account) const;
/**
* Check whether @p to may receive this MPT from @p from.
*
* Returns tecOBJECT_NOT_FOUND if the issuance does not exist. Otherwise
* the check passes when @p waive is WaiveMPTCanTransfer::Yes, @p from or
* @p to is the issuer, or lsfMPTCanTransfer is set. For vault shares
* (sfReferenceHolding present) it recurses into the underlying asset's
* transferability; @p depth is bounded at kMaxAssetCheckDepth.
*
* @param from the account the MPT is transferred from.
* @param to the account the MPT is transferred to.
* @param waive whether to waive the lsfMPTCanTransfer check.
* @param depth Current recursion depth; callers outside the
* transferability checks should pass 0.
* @return tesSUCCESS if the transfer is allowed, tecNO_AUTH otherwise.
*/
[[nodiscard]] TER
canTransfer(
AccountID const& from,
AccountID const& to,
WaiveMPTCanTransfer waive = WaiveMPTCanTransfer::No,
std::uint8_t depth = 0) const;
/**
* Check whether this MPT may be traded on the DEX.
*
* Returns tecOBJECT_NOT_FOUND if the issuance does not exist and
* tecNO_PERMISSION if lsfMPTCanTrade is not set. Vault shares recurse
* into the underlying asset's tradability via sfReferenceHolding;
* @p depth is bounded at kMaxAssetCheckDepth.
*
* @param depth Current recursion depth; callers outside the
* tradability checks should pass 0.
* @return tesSUCCESS if the MPT may be traded, tecOBJECT_NOT_FOUND, or
* tecNO_PERMISSION otherwise.
*/
[[nodiscard]] TER
canTrade(std::uint8_t depth = 0) const;
};
using MPTokenIssuanceEntryR = MPTokenIssuanceEntry<ReadView>;

View File

@@ -3,6 +3,7 @@
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/MPTokenIssuanceEntry.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Asset.h>
@@ -29,9 +30,6 @@ namespace xrpl {
[[nodiscard]] bool
isGlobalFrozen(ReadView const& view, MPTIssue const& mptIssue);
[[nodiscard]] bool
isGlobalFrozen(SLE const& issuanceSle);
/**
* Returns true if @p account's MPToken for @p mptIssue carries the
* individual-lock flag (lsfMPTLocked).
@@ -178,7 +176,7 @@ enforceMPTokenAuthorization(
* @return The underlying Asset (MPTIssue or Issue).
*/
[[nodiscard]] Asset
assetOfHolding(SLE const& sleShareIssuance, SLE const& sleHolding);
assetOfHolding(MPTokenIssuanceEntryR const& sleShareIssuance, SLE const& sleHolding);
/**
* Check whether @p to may receive the given MPT from @p from.
@@ -318,9 +316,6 @@ maxMPTAmount(SLE const& sleIssuance);
// OutstandingAmount may overflow and available amount might be negative.
// But available amount is always <= |MaximumAmount - OutstandingAmount|.
std::int64_t
availableMPTAmount(SLE const& sleIssuance);
std::int64_t
availableMPTAmount(ReadView const& view, MPTID const& mptID);

View File

@@ -2,6 +2,7 @@
#include <xrpl/basics/Number.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/MPTokenIssuanceEntry.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/Protocol.h>
@@ -29,7 +30,10 @@ class STTx;
* @return The number of shares, or nullopt on error.
*/
[[nodiscard]] std::optional<STAmount>
assetsToSharesDeposit(SLE::ConstRef vault, SLE::ConstRef issuance, STAmount const& assets);
assetsToSharesDeposit(
SLE::ConstRef vault,
MPTokenIssuanceEntryR const& issuance,
STAmount const& assets);
/**
* From the perspective of a vault, return the number of assets to take from
@@ -43,7 +47,10 @@ assetsToSharesDeposit(SLE::ConstRef vault, SLE::ConstRef issuance, STAmount cons
* @return The number of assets, or nullopt on error.
*/
[[nodiscard]] std::optional<STAmount>
sharesToAssetsDeposit(SLE::ConstRef vault, SLE::ConstRef issuance, STAmount const& shares);
sharesToAssetsDeposit(
SLE::ConstRef vault,
MPTokenIssuanceEntryR const& issuance,
STAmount const& shares);
/**
* Adjusts a requested asset change (`delta`) to match the decimal scale of the
@@ -132,7 +139,7 @@ debitIsNonZeroDust(Asset const& asset, Number const& total, Number const& amount
[[nodiscard]] std::optional<STAmount>
assetsToSharesWithdraw(
SLE::ConstRef vault,
SLE::ConstRef issuance,
MPTokenIssuanceEntryR const& issuance,
STAmount const& assets,
TruncateShares truncate = TruncateShares::No,
WaiveUnrealizedLoss waive = WaiveUnrealizedLoss::No);
@@ -153,23 +160,10 @@ assetsToSharesWithdraw(
[[nodiscard]] std::optional<STAmount>
sharesToAssetsWithdraw(
SLE::ConstRef vault,
SLE::ConstRef issuance,
MPTokenIssuanceEntryR const& issuance,
STAmount const& shares,
WaiveUnrealizedLoss waive = WaiveUnrealizedLoss::No);
/**
* Returns true iff `account` holds all of the vault's outstanding shares —
* i.e. is the sole remaining shareholder. Returns false if the account
* holds no shares or fewer than the total outstanding.
*
* @param view The ledger view.
* @param account The candidate sole shareholder.
* @param issuance The MPTokenIssuance SLE for the vault's shares; provides
* both the share MPTID and the outstanding-amount total.
*/
[[nodiscard]] bool
isSoleShareholder(ReadView const& view, AccountID const& account, SLE::ConstRef issuance);
/**
* Resolves a Vault's LEVersion, the single point every accounting touch
* point should call to determine which recognition model (instant interest
@@ -295,7 +289,7 @@ enum class SuppressExpired : bool { No = false, Yes = true };
[[nodiscard]] TER
checkVaultDomain(
ReadView const& view,
SLE::ConstRef issuance,
MPTokenIssuanceEntryR const& issuance,
AccountID const& subject,
SuppressExpired suppressExpired);

View File

@@ -3,6 +3,7 @@
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/MPTokenIssuanceEntry.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STTx.h>
@@ -49,7 +50,7 @@ private:
std::expected<std::pair<STAmount, STAmount>, TER>
assetsToClawback(
SLE::Ref vault,
SLE::ConstRef sleShareIssuance,
MPTokenIssuanceEntryR const& sleShareIssuance,
AccountID const& holder,
STAmount const& clawbackAmount);
};

View File

@@ -9,6 +9,7 @@
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/MPTokenIssuanceEntry.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/CredentialHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
@@ -62,97 +63,6 @@ hasExpired(
: view.parentCloseTime() > boundary;
}
namespace {
std::optional<bool>
checkVaultPseudoAccountFrozenPreconditions(ReadView const& view, std::uint8_t depth)
{
if (!view.rules().enabled(featureSingleAssetVault))
return false;
if (depth >= kMaxAssetCheckDepth)
{
// LCOV_EXCL_START
UNREACHABLE(
"xrpl::View::checkVaultPseudoAccountFrozenPreconditions : reached asset check depth");
return true;
// LCOV_EXCL_STOP
}
return std::nullopt;
}
bool
isVaultPseudoAccountFrozenForIssuance(
ReadView const& view,
AccountID const& account,
SLE const& issuanceSle,
std::uint8_t depth)
{
XRPL_ASSERT(
issuanceSle.getType() == ltMPTOKEN_ISSUANCE,
"xrpl::isVaultPseudoAccountFrozenForIssuance : MPTokenIssuance SLE");
auto const issuer = issuanceSle.getAccountID(sfIssuer);
// Post-fixCleanup3_2_0: vault shares carry sfReferenceHolding pointing
// to the vault pseudo's MPToken or RippleState for the underlying.
// Read it to derive the underlying asset and recurse, skipping the
// issuer-account-then-vault chain. Pre-amendment shares (no field)
// fall back to the chain lookup below.
if (issuanceSle.isFieldPresent(sfReferenceHolding))
{
auto const sleHolding =
view.read(keylet::unchecked(issuanceSle.getFieldH256(sfReferenceHolding)));
if (!sleHolding)
{
// LCOV_EXCL_START
UNREACHABLE("xrpl::isVaultPseudoAccountFrozen : dangling sfReferenceHolding");
return false;
// LCOV_EXCL_STOP
}
return isAnyFrozen(
view, {issuer, account}, assetOfHolding(issuanceSle, *sleHolding), depth + 1);
}
auto const mptIssuer = view.read(keylet::account(issuer));
if (mptIssuer == nullptr)
{
// LCOV_EXCL_START
UNREACHABLE("xrpl::isVaultPseudoAccountFrozen : null MPToken issuer");
return false;
// LCOV_EXCL_STOP
}
if (!mptIssuer->isFieldPresent(sfVaultID))
return false; // not a Vault pseudo-account, common case
auto const vault = view.read(keylet::vault(mptIssuer->getFieldH256(sfVaultID)));
if (vault == nullptr)
{ // LCOV_EXCL_START
UNREACHABLE("xrpl::isVaultPseudoAccountFrozen : null vault");
return false;
// LCOV_EXCL_STOP
}
return isAnyFrozen(view, {issuer, account}, vault->at(sfAsset), depth + 1);
}
} // namespace
bool
isVaultPseudoAccountFrozen(
ReadView const& view,
AccountID const& account,
SLE const& issuanceSle,
std::uint8_t depth)
{
if (auto const result = checkVaultPseudoAccountFrozenPreconditions(view, depth))
return *result;
return isVaultPseudoAccountFrozenForIssuance(view, account, issuanceSle, depth);
}
bool
isVaultPseudoAccountFrozen(
ReadView const& view,
@@ -160,14 +70,8 @@ isVaultPseudoAccountFrozen(
MPTIssue const& mptShare,
std::uint8_t depth)
{
if (auto const result = checkVaultPseudoAccountFrozenPreconditions(view, depth))
return *result;
auto const issuanceSle = view.read(keylet::mptokenIssuance(mptShare.getMptID()));
if (issuanceSle == nullptr)
return false; // zero MPToken won't block deletion of MPTokenIssuance
return isVaultPseudoAccountFrozenForIssuance(view, account, *issuanceSle, depth);
return MPTokenIssuanceEntryR(mptShare.getMptID(), view)
.isVaultPseudoAccountFrozen(account, depth);
}
bool

View File

@@ -0,0 +1,241 @@
#include <xrpl/ledger/entries/MPTokenIssuanceEntry.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/Rate.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/TER.h>
#include <cstdint>
namespace xrpl {
template <typename ViewT>
std::int64_t
MPTokenIssuanceEntry<ViewT>::maxAmount() const
{
return maxMPTAmount(**this);
}
template <typename ViewT>
Rate
MPTokenIssuanceEntry<ViewT>::transferRate() const
{
// fee is 0-50,000 (0-50%), rate is 1,000,000,000-2,000,000,000
// For example, if transfer fee is 50% then 10,000 * 50,000 = 500,000
// which represents 50% of 1,000,000,000
if ((*this)->isFieldPresent(sfTransferFee))
{
auto const fee = (*this)->getFieldU16(sfTransferFee);
XRPL_ASSERT(fee <= kMaxTransferFee, "xrpl::transferRate : fee is too large");
return Rate{1'000'000'000u + (10'000 * fee)};
}
return kParityRate;
}
template <typename ViewT>
STAmount
MPTokenIssuanceEntry<ViewT>::issuerFundsToSelfIssue() const
{
MPTIssue const issue{(**this)[sfSequence], (**this)[sfIssuer]};
auto const available = availableAmount();
return this->readView().balanceHookSelfIssueMPT(issue, available);
}
template <typename ViewT>
void
MPTokenIssuanceEntry<ViewT>::issuerSelfDebitHook(std::uint64_t amount)
requires Base::kIsWritable
{
MPTIssue const issue{(**this)[sfSequence], (**this)[sfIssuer]};
auto const available = availableAmount();
this->applyView().issuerSelfDebitHookMPT(issue, amount, available);
}
template <typename ViewT>
bool
MPTokenIssuanceEntry<ViewT>::isVaultPseudoAccountFrozen(
AccountID const& account,
std::uint8_t depth) const
{
auto const& view = this->readView();
if (!view.rules().enabled(featureSingleAssetVault))
return false;
if (depth >= kMaxAssetCheckDepth)
{
// LCOV_EXCL_START
UNREACHABLE(
"xrpl::View::checkVaultPseudoAccountFrozenPreconditions : reached asset check depth");
return true;
// LCOV_EXCL_STOP
}
if (!this->exists())
return false; // zero MPToken won't block deletion of MPTokenIssuance
auto const issuer = (*this)->getAccountID(sfIssuer);
// Post-fixCleanup3_2_0: vault shares carry sfReferenceHolding pointing
// to the vault pseudo's MPToken or RippleState for the underlying.
// Read it to derive the underlying asset and recurse, skipping the
// issuer-account-then-vault chain. Pre-amendment shares (no field)
// fall back to the chain lookup below.
if ((*this)->isFieldPresent(sfReferenceHolding))
{
auto const sleHolding =
view.read(keylet::unchecked((*this)->getFieldH256(sfReferenceHolding)));
if (!sleHolding)
{
// LCOV_EXCL_START
UNREACHABLE("xrpl::isVaultPseudoAccountFrozen : dangling sfReferenceHolding");
return false;
// LCOV_EXCL_STOP
}
return isAnyFrozen(view, {issuer, account}, assetOfHolding(*this, *sleHolding), depth + 1);
}
auto const mptIssuer = view.read(keylet::account(issuer));
if (mptIssuer == nullptr)
{
// LCOV_EXCL_START
UNREACHABLE("xrpl::isVaultPseudoAccountFrozen : null MPToken issuer");
return false;
// LCOV_EXCL_STOP
}
if (!mptIssuer->isFieldPresent(sfVaultID))
return false; // not a Vault pseudo-account, common case
auto const vault = view.read(keylet::vault(mptIssuer->getFieldH256(sfVaultID)));
if (vault == nullptr)
{ // LCOV_EXCL_START
UNREACHABLE("xrpl::isVaultPseudoAccountFrozen : null vault");
return false;
// LCOV_EXCL_STOP
}
return isAnyFrozen(view, {issuer, account}, vault->at(sfAsset), depth + 1);
}
template <typename ViewT>
bool
MPTokenIssuanceEntry<ViewT>::isSoleShareholder(AccountID const& account) const
{
std::uint64_t const outstanding = (*this)->at(sfOutstandingAmount);
if (outstanding == 0)
return false;
auto const shareMPTID =
makeMptID((*this)->getFieldU32(sfSequence), (*this)->getAccountID(sfIssuer));
auto const sleToken = this->readView().read(keylet::mptoken(shareMPTID, account));
if (!sleToken)
return false; // LCOV_EXCL_LINE
return sleToken->getFieldU64(sfMPTAmount) == outstanding;
}
template <typename ViewT>
TER
MPTokenIssuanceEntry<ViewT>::canTransfer(
AccountID const& from,
AccountID const& to,
WaiveMPTCanTransfer waive,
std::uint8_t depth) const
{
if (!this->exists())
return tecOBJECT_NOT_FOUND;
auto const& view = this->readView();
auto const issuer = (**this)[sfIssuer];
if (waive == WaiveMPTCanTransfer::Yes || from == issuer || to == issuer)
return tesSUCCESS;
if (!(*this)->isFlag(lsfMPTCanTransfer))
return TER{tecNO_AUTH};
// Post-fixCleanup3_2_0: vault shares carry sfReferenceHolding pointing
// to the vault pseudo's MPToken or RippleState for the underlying asset.
// Third-party transfers inherit the underlying's transferability.
// Issuer-involving transfers and waived callers returned tesSUCCESS above.
//
// The recursive call always passes WaiveMPTCanTransfer::No so that
// a waived outer caller does not transitively unlock the underlying.
if (view.rules().enabled(fixCleanup3_2_0) && (*this)->isFieldPresent(sfReferenceHolding))
{
// Defensive depth bound on the inheritance recursion. Unreachable
// in practice (vault-of-vault-shares is forbidden at VaultCreate).
if (depth >= kMaxAssetCheckDepth)
{
// LCOV_EXCL_START
UNREACHABLE("xrpl::MPTokenHelpers::canTransfer : reached asset check depth");
return tecINTERNAL;
// LCOV_EXCL_STOP
}
auto const sleHolding =
view.read(keylet::unchecked((*this)->getFieldH256(sfReferenceHolding)));
if (!sleHolding)
return tefINTERNAL; // LCOV_EXCL_LINE
return xrpl::canTransfer(
view, assetOfHolding(*this, *sleHolding), from, to, WaiveMPTCanTransfer::No, depth + 1);
}
return tesSUCCESS;
}
template <typename ViewT>
TER
MPTokenIssuanceEntry<ViewT>::canTrade(std::uint8_t depth) const
{
if (!this->exists())
return tecOBJECT_NOT_FOUND;
if (!(*this)->isFlag(lsfMPTCanTrade))
return tecNO_PERMISSION;
auto const& view = this->readView();
// Post-fixCleanup3_2_0: vault shares inherit the underlying
// asset's tradability. A share whose underlying has been
// removed from trading cannot itself be placed on the DEX.
if (view.rules().enabled(fixCleanup3_2_0) && (*this)->isFieldPresent(sfReferenceHolding))
{
// Defensive depth bound on the inheritance recursion.
// Unreachable in practice (vault-of-vault-shares
// forbidden at VaultCreate).
if (depth >= kMaxAssetCheckDepth)
{
// LCOV_EXCL_START
UNREACHABLE("xrpl::MPTokenHelpers::canTrade : reached asset check depth");
return tecINTERNAL;
// LCOV_EXCL_STOP
}
auto const sleHolding =
view.read(keylet::unchecked((*this)->getFieldH256(sfReferenceHolding)));
if (!sleHolding)
return tefINTERNAL; // LCOV_EXCL_LINE
return xrpl::canTrade(view, assetOfHolding(*this, *sleHolding), depth + 1);
}
return tesSUCCESS;
}
template class MPTokenIssuanceEntry<ReadView>;
template class MPTokenIssuanceEntry<ApplyView>;
} // namespace xrpl

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/MPTokenIssuanceEntry.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/CredentialHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
@@ -41,20 +42,11 @@ namespace xrpl {
bool
isGlobalFrozen(ReadView const& view, MPTIssue const& mptIssue)
{
if (auto const sle = view.read(keylet::mptokenIssuance(mptIssue.getMptID())))
return isGlobalFrozen(*sle);
if (auto const sle = MPTokenIssuanceEntryR(mptIssue.getMptID(), view))
return sle.isGlobalFrozen();
return false;
}
bool
isGlobalFrozen(SLE const& issuanceSle)
{
XRPL_ASSERT(
issuanceSle.getType() == ltMPTOKEN_ISSUANCE, "xrpl::isGlobalFrozen : MPTokenIssuance SLE");
return issuanceSle.isFlag(lsfMPTLocked);
}
bool
isIndividualFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue)
{
@@ -94,20 +86,23 @@ isFrozen(ReadView const& view, AccountID const& account, SLE const& sle, std::ui
XRPL_ASSERT(sle[sfAccount] == account, "xrpl::isFrozen : valid MPToken holder");
MPTID const mptID = sle[sfMPTokenIssuanceID];
auto const issuanceSle = view.read(keylet::mptokenIssuance(mptID));
MPTokenIssuanceEntryR const issuanceSle(mptID, view);
if ((issuanceSle && isGlobalFrozen(*issuanceSle)) || isIndividualFrozen(sle))
if ((issuanceSle && issuanceSle.isGlobalFrozen()) || isIndividualFrozen(sle))
return true;
if (issuanceSle)
return isVaultPseudoAccountFrozen(view, account, *issuanceSle, depth);
return issuanceSle.isVaultPseudoAccountFrozen(account, depth);
return isVaultPseudoAccountFrozen(view, account, MPTIssue{mptID}, depth);
}
// Non-owning pointer (aliasing constructor): the caller owns sle for the
// whole call, so this avoids copying the SLE.
MPTokenIssuanceEntryR const issuanceSle(SLE::const_pointer(SLE::const_pointer{}, &sle), view);
MPTIssue const mptIssue{sle[sfSequence], sle[sfIssuer]};
return isGlobalFrozen(sle) || isIndividualFrozen(view, account, mptIssue) ||
isVaultPseudoAccountFrozen(view, account, sle, depth);
return issuanceSle.isGlobalFrozen() || isIndividualFrozen(view, account, mptIssue) ||
issuanceSle.isVaultPseudoAccountFrozen(account, depth);
}
[[nodiscard]] bool
@@ -117,8 +112,8 @@ isAnyFrozen(
MPTIssue const& mptIssue,
std::uint8_t depth)
{
auto const issuanceSle = view.read(keylet::mptokenIssuance(mptIssue.getMptID()));
if (issuanceSle && isGlobalFrozen(*issuanceSle))
MPTokenIssuanceEntryR const issuanceSle(mptIssue.getMptID(), view);
if (issuanceSle && issuanceSle.isGlobalFrozen())
return true;
for (auto const& account : accounts)
@@ -127,49 +122,25 @@ isAnyFrozen(
return true;
}
// Pass the issuance SLE when we have it to avoid re-reading it per account;
// otherwise defer to the MPTIssue overload, which handles a missing issuance.
auto const anyVaultFrozen = [&](auto const& shareOrIssuance) {
return std::ranges::any_of(accounts, [&](auto const& account) {
return isVaultPseudoAccountFrozen(view, account, shareOrIssuance, depth);
});
};
return issuanceSle ? anyVaultFrozen(*issuanceSle) : anyVaultFrozen(mptIssue);
// Reuse the issuance entry to avoid re-reading it per account; the member
// also handles a missing issuance.
return std::ranges::any_of(accounts, [&](auto const& account) {
return issuanceSle.isVaultPseudoAccountFrozen(account, depth);
});
}
Rate
transferRate(ReadView const& view, MPTID const& issuanceID)
{
// fee is 0-50,000 (0-50%), rate is 1,000,000,000-2,000,000,000
// For example, if transfer fee is 50% then 10,000 * 50,000 = 500,000
// which represents 50% of 1,000,000,000
if (auto const sle = view.read(keylet::mptokenIssuance(issuanceID));
sle && sle->isFieldPresent(sfTransferFee))
{
auto const fee = sle->getFieldU16(sfTransferFee);
XRPL_ASSERT(fee <= kMaxTransferFee, "xrpl::transferRate : fee is too large");
return Rate{1'000'000'000u + (10'000 * fee)};
}
if (auto const sle = MPTokenIssuanceEntryR(issuanceID, view))
return sle.transferRate();
return kParityRate;
}
[[nodiscard]] TER
canAddHolding(ReadView const& view, MPTIssue const& mptIssue)
{
auto mptID = mptIssue.getMptID();
auto issuance = view.read(keylet::mptokenIssuance(mptID));
if (!issuance)
{
return tecOBJECT_NOT_FOUND;
}
if (!issuance->isFlag(lsfMPTCanTransfer))
{
return tecNO_AUTH;
}
return tesSUCCESS;
return MPTokenIssuanceEntryR(mptIssue.getMptID(), view).canAddHolding();
}
[[nodiscard]] TER
@@ -181,7 +152,7 @@ addEmptyHolding(
beast::Journal journal)
{
auto const& mptID = mptIssue.getMptID();
auto const mpt = ctx.view.peek(keylet::mptokenIssuance(mptID));
MPTokenIssuanceEntryW mpt(mptID, ctx.view);
if (!mpt)
return tefINTERNAL; // LCOV_EXCL_LINE
// Unlike IOU addEmptyHolding (post-fixCleanup3_4_0), a locked issuance is
@@ -264,7 +235,7 @@ authorizeMPToken(
}
// Defensive check before we attempt to create MPToken for the issuer
auto const mpt = ctx.view.read(keylet::mptokenIssuance(mptIssuanceID));
MPTokenIssuanceEntryR const mpt(mptIssuanceID, ctx.view);
if (!mpt || mpt->getAccountID(sfIssuer) == account)
{
// LCOV_EXCL_START
@@ -291,7 +262,7 @@ authorizeMPToken(
return tesSUCCESS;
}
auto const sleMptIssuance = ctx.view.read(keylet::mptokenIssuance(mptIssuanceID));
MPTokenIssuanceEntryR const sleMptIssuance(mptIssuanceID, ctx.view);
if (!sleMptIssuance)
return tecINTERNAL; // LCOV_EXCL_LINE
@@ -390,7 +361,7 @@ requireAuth(
};
auto const mptID = keylet::mptokenIssuance(mptIssue.getMptID());
auto const sleIssuance = view.read(mptID);
MPTokenIssuanceEntryR const sleIssuance(mptID, view);
if (!sleIssuance)
return tecOBJECT_NOT_FOUND;
@@ -485,7 +456,7 @@ enforceMPTokenAuthorization(
XRPAmount const& priorBalance, // for MPToken authorization
beast::Journal j)
{
auto const sleIssuance = ctx.view.read(keylet::mptokenIssuance(mptIssuanceID));
MPTokenIssuanceEntryR const sleIssuance(mptIssuanceID, ctx.view);
if (!sleIssuance)
return tefINTERNAL; // LCOV_EXCL_LINE
@@ -578,21 +549,21 @@ enforceMPTokenAuthorization(
}
[[nodiscard]] Asset
assetOfHolding(SLE const& sleShareIssuance, SLE const& sleHolding)
assetOfHolding(MPTokenIssuanceEntryR const& sleShareIssuance, SLE const& sleHolding)
{
XRPL_ASSERT_PARTS(
sleHolding.getType() == ltRIPPLE_STATE || sleHolding.getType() == ltMPTOKEN,
"xrpl::assetOfHolding",
"unexpected holding type");
XRPL_ASSERT_PARTS(
sleShareIssuance.getType() == ltMPTOKEN_ISSUANCE,
sleShareIssuance->getType() == ltMPTOKEN_ISSUANCE,
"xrpl::assetOfHolding",
"not SLE MPTokenIssuance");
if (sleHolding.getType() == ltMPTOKEN)
return MPTIssue{sleHolding.getFieldH192(sfMPTokenIssuanceID)};
auto const vaultPseudo = sleShareIssuance.at(sfIssuer);
auto const vaultPseudo = sleShareIssuance->at(sfIssuer);
auto const lowLimit = sleHolding.getFieldAmount(sfLowLimit);
auto const highLimit = sleHolding.getFieldAmount(sfHighLimit);
auto const& iouIssuer =
@@ -609,52 +580,7 @@ canTransfer(
WaiveMPTCanTransfer waive,
std::uint8_t depth)
{
auto const mptID = keylet::mptokenIssuance(mptIssue.getMptID());
auto const sleIssuance = view.read(mptID);
if (!sleIssuance)
return tecOBJECT_NOT_FOUND;
auto const issuer = (*sleIssuance)[sfIssuer];
if (waive == WaiveMPTCanTransfer::Yes || from == issuer || to == issuer)
return tesSUCCESS;
if (!sleIssuance->isFlag(lsfMPTCanTransfer))
return TER{tecNO_AUTH};
// Post-fixCleanup3_2_0: vault shares carry sfReferenceHolding pointing
// to the vault pseudo's MPToken or RippleState for the underlying asset.
// Third-party transfers inherit the underlying's transferability.
// Issuer-involving transfers and waived callers returned tesSUCCESS above.
//
// The recursive call always passes WaiveMPTCanTransfer::No so that
// a waived outer caller does not transitively unlock the underlying.
if (view.rules().enabled(fixCleanup3_2_0) && sleIssuance->isFieldPresent(sfReferenceHolding))
{
// Defensive depth bound on the inheritance recursion. Unreachable
// in practice (vault-of-vault-shares is forbidden at VaultCreate).
if (depth >= kMaxAssetCheckDepth)
{
// LCOV_EXCL_START
UNREACHABLE("xrpl::MPTokenHelpers::canTransfer : reached asset check depth");
return tecINTERNAL;
// LCOV_EXCL_STOP
}
auto const sleHolding =
view.read(keylet::unchecked(sleIssuance->getFieldH256(sfReferenceHolding)));
if (!sleHolding)
return tefINTERNAL; // LCOV_EXCL_LINE
return canTransfer(
view,
assetOfHolding(*sleIssuance, *sleHolding),
from,
to,
WaiveMPTCanTransfer::No,
depth + 1);
}
return tesSUCCESS;
return MPTokenIssuanceEntryR(mptIssue.getMptID(), view).canTransfer(from, to, waive, depth);
}
TER
@@ -663,37 +589,7 @@ canTrade(ReadView const& view, Asset const& asset, std::uint8_t depth)
return asset.visit(
[&](Issue const&) -> TER { return tesSUCCESS; },
[&](MPTIssue const& mptIssue) -> TER {
auto const sleIssuance = view.read(keylet::mptokenIssuance(mptIssue.getMptID()));
if (!sleIssuance)
return tecOBJECT_NOT_FOUND;
if (!sleIssuance->isFlag(lsfMPTCanTrade))
return tecNO_PERMISSION;
// Post-fixCleanup3_2_0: vault shares inherit the underlying
// asset's tradability. A share whose underlying has been
// removed from trading cannot itself be placed on the DEX.
if (view.rules().enabled(fixCleanup3_2_0) &&
sleIssuance->isFieldPresent(sfReferenceHolding))
{
// Defensive depth bound on the inheritance recursion.
// Unreachable in practice (vault-of-vault-shares
// forbidden at VaultCreate).
if (depth >= kMaxAssetCheckDepth)
{
// LCOV_EXCL_START
UNREACHABLE("xrpl::MPTokenHelpers::canTrade : reached asset check depth");
return tecINTERNAL;
// LCOV_EXCL_STOP
}
auto const sleHolding =
view.read(keylet::unchecked(sleIssuance->getFieldH256(sfReferenceHolding)));
if (!sleHolding)
return tefINTERNAL; // LCOV_EXCL_LINE
return canTrade(view, assetOfHolding(*sleIssuance, *sleHolding), depth + 1);
}
return tesSUCCESS;
return MPTokenIssuanceEntryR(mptIssue.getMptID(), view).canTrade(depth);
});
}
@@ -718,7 +614,7 @@ lockEscrowMPT(ApplyView& view, AccountID const& sender, STAmount const& amount,
{
auto const mptIssue = amount.get<MPTIssue>();
auto const mptID = keylet::mptokenIssuance(mptIssue.getMptID());
auto sleIssuance = view.peek(mptID);
MPTokenIssuanceEntryW sleIssuance(mptID, view);
if (!sleIssuance)
{ // LCOV_EXCL_START
JLOG(j.error()) << "lockEscrowMPT: MPT issuance not found for " << mptIssue.getMptID();
@@ -801,7 +697,7 @@ lockEscrowMPT(ApplyView& view, AccountID const& sender, STAmount const& amount,
sleIssuance->setFieldU64(sfLockedAmount, pay);
}
view.update(sleIssuance);
sleIssuance.update();
}
return tesSUCCESS;
}
@@ -823,7 +719,7 @@ unlockEscrowMPT(
auto const& issuer = netAmount.getIssuer();
auto const& mptIssue = netAmount.get<MPTIssue>();
auto const mptID = keylet::mptokenIssuance(mptIssue.getMptID());
auto sleIssuance = view.peek(mptID);
MPTokenIssuanceEntryW sleIssuance(mptID, view);
if (!sleIssuance)
{ // LCOV_EXCL_START
JLOG(j.error()) << "unlockEscrowMPT: MPT issuance not found for " << mptIssue.getMptID();
@@ -859,7 +755,7 @@ unlockEscrowMPT(
{
sleIssuance->setFieldU64(sfLockedAmount, newLocked);
}
view.update(sleIssuance);
sleIssuance.update();
}
if (issuer != receiver)
@@ -902,7 +798,7 @@ unlockEscrowMPT(
} // LCOV_EXCL_STOP
sleIssuance->setFieldU64(sfOutstandingAmount, outstanding - redeem);
view.update(sleIssuance);
sleIssuance.update();
}
if (issuer == sender)
@@ -965,7 +861,7 @@ unlockEscrowMPT(
} // LCOV_EXCL_STOP
sleIssuance->setFieldU64(sfOutstandingAmount, outstanding - diff);
view.update(sleIssuance);
sleIssuance.update();
}
return tesSUCCESS;
}
@@ -1047,21 +943,13 @@ maxMPTAmount(SLE const& sleIssuance)
return sleIssuance[~sfMaximumAmount].value_or(kMaxMpTokenAmount);
}
std::int64_t
availableMPTAmount(SLE const& sleIssuance)
{
auto const max = maxMPTAmount(sleIssuance);
auto const outstanding = sleIssuance[sfOutstandingAmount];
return max - outstanding;
}
std::int64_t
availableMPTAmount(ReadView const& view, MPTID const& mptID)
{
auto const sle = view.read(keylet::mptokenIssuance(mptID));
MPTokenIssuanceEntryR const sle(mptID, view);
if (!sle)
Throw<std::runtime_error>(transHuman(tecINTERNAL));
return availableMPTAmount(*sle);
return sle.availableAmount();
}
bool
@@ -1082,18 +970,19 @@ issuerFundsToSelfIssue(ReadView const& view, MPTIssue const& issue)
{
STAmount amount{issue};
auto const sle = view.read(keylet::mptokenIssuance(issue));
MPTokenIssuanceEntryR const sle(issue.getMptID(), view);
if (!sle)
return amount;
auto const available = availableMPTAmount(*sle);
return view.balanceHookSelfIssueMPT(issue, available);
return sle.issuerFundsToSelfIssue();
}
void
issuerSelfDebitHookMPT(ApplyView& view, MPTIssue const& issue, std::uint64_t amount)
{
auto const available = availableMPTAmount(view, issue);
view.issuerSelfDebitHookMPT(issue, amount, available);
MPTokenIssuanceEntryW sle(issue.getMptID(), view);
if (!sle)
Throw<std::runtime_error>(transHuman(tecINTERNAL));
sle.issuerSelfDebitHook(amount);
}
} // namespace xrpl

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/MPTokenIssuanceEntry.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
@@ -424,13 +425,13 @@ accountHolds(
{
// if the account is the issuer, and the issuance exists, their limit is
// the issuance limit minus the outstanding value
auto const issuance = view.read(keylet::mptokenIssuance(mptIssue.getMptID()));
MPTokenIssuanceEntryR const issuance(mptIssue.getMptID(), view);
if (!issuance)
{
return amount;
}
auto const available = availableMPTAmount(*issuance);
auto const available = issuance.availableAmount();
if (!mptokensV2)
return STAmount{mptIssue, available};
return view.balanceHookMPT(issuer, mptIssue, available);
@@ -459,7 +460,7 @@ accountHolds(
}
else if (zeroIfUnauthorized == AuthHandling::ZeroIfUnauthorized)
{
auto const sleIssuance = view.read(keylet::mptokenIssuance(mptIssue.getMptID()));
MPTokenIssuanceEntryR const sleIssuance(mptIssue.getMptID(), view);
// if auth is enabled on the issuance and mpt is not authorized,
// clear amount
@@ -1217,13 +1218,13 @@ directSendNoFeeMPT(
// Do not check MPT authorization here - it must have been checked earlier
auto const mptID = keylet::mptokenIssuance(saAmount.get<MPTIssue>().getMptID());
auto const& issuer = saAmount.getIssuer();
auto sleIssuance = view.peek(mptID);
MPTokenIssuanceEntryW sleIssuance(mptID, view);
if (!sleIssuance)
return tecOBJECT_NOT_FOUND;
auto const maxAmount = maxMPTAmount(*sleIssuance);
auto const maxAmount = sleIssuance.maxAmount();
auto const outstanding = sleIssuance->getFieldU64(sfOutstandingAmount);
auto const available = availableMPTAmount(*sleIssuance);
auto const available = sleIssuance.availableAmount();
auto const amt = saAmount.mpt().value();
if (uSenderID == issuer)
@@ -1234,7 +1235,7 @@ directSendNoFeeMPT(
return tecPATH_DRY;
}
(*sleIssuance)[sfOutstandingAmount] += amt;
view.update(sleIssuance);
sleIssuance.update();
}
else
{
@@ -1259,7 +1260,7 @@ directSendNoFeeMPT(
if (outstanding >= amt)
{
sleIssuance->setFieldU64(sfOutstandingAmount, outstanding - amt);
view.update(sleIssuance);
sleIssuance.update();
}
else
{
@@ -1307,7 +1308,7 @@ directSendNoLimitMPT(
// Safe to get MPT since directSendNoLimitMPT is only called by accountSendMPT
auto const& issuer = saAmount.getIssuer();
auto const sle = view.read(keylet::mptokenIssuance(saAmount.get<MPTIssue>().getMptID()));
MPTokenIssuanceEntryR const sle(saAmount.get<MPTIssue>().getMptID(), view);
if (!sle)
return tecOBJECT_NOT_FOUND;
@@ -1318,7 +1319,7 @@ directSendNoLimitMPT(
if (uSenderID == issuer)
{
auto const sendAmount = saAmount.mpt().value();
auto const maxAmount = maxMPTAmount(*sle);
auto const maxAmount = sle.maxAmount();
auto const outstanding = sle->getFieldU64(sfOutstandingAmount);
auto const mptokensV2 = view.rules().enabled(featureMPTokensV2);
allowOverflow = (allowOverflow == AllowMPTOverflow::Yes && mptokensV2)
@@ -1364,7 +1365,7 @@ directSendNoLimitMultiMPT(
{
auto const& issuer = mptIssue.getIssuer();
auto const sle = view.read(keylet::mptokenIssuance(mptIssue.getMptID()));
MPTokenIssuanceEntryR const sle(mptIssue.getMptID(), view);
if (!sle)
return tecOBJECT_NOT_FOUND;

View File

@@ -5,9 +5,9 @@
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/entries/MPTokenIssuanceEntry.h>
#include <xrpl/ledger/helpers/CredentialHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h> // IWYU pragma: keep
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/SField.h>
@@ -25,7 +25,10 @@
namespace xrpl {
[[nodiscard]] std::optional<STAmount>
assetsToSharesDeposit(SLE::ConstRef vault, SLE::ConstRef issuance, STAmount const& assets)
assetsToSharesDeposit(
SLE::ConstRef vault,
MPTokenIssuanceEntryR const& issuance,
STAmount const& assets)
{
XRPL_ASSERT(!assets.negative(), "xrpl::assetsToSharesDeposit : non-negative assets");
XRPL_ASSERT(
@@ -49,7 +52,10 @@ assetsToSharesDeposit(SLE::ConstRef vault, SLE::ConstRef issuance, STAmount cons
}
[[nodiscard]] std::optional<STAmount>
sharesToAssetsDeposit(SLE::ConstRef vault, SLE::ConstRef issuance, STAmount const& shares)
sharesToAssetsDeposit(
SLE::ConstRef vault,
MPTokenIssuanceEntryR const& issuance,
STAmount const& shares)
{
XRPL_ASSERT(!shares.negative(), "xrpl::sharesToAssetsDeposit : non-negative shares");
XRPL_ASSERT(
@@ -150,7 +156,7 @@ debitIsNonZeroDust(Asset const& asset, Number const& total, Number const& amount
[[nodiscard]] std::optional<STAmount>
assetsToSharesWithdraw(
SLE::ConstRef vault,
SLE::ConstRef issuance,
MPTokenIssuanceEntryR const& issuance,
STAmount const& assets,
TruncateShares truncate,
WaiveUnrealizedLoss waive)
@@ -177,7 +183,7 @@ assetsToSharesWithdraw(
[[nodiscard]] std::optional<STAmount>
sharesToAssetsWithdraw(
SLE::ConstRef vault,
SLE::ConstRef issuance,
MPTokenIssuanceEntryR const& issuance,
STAmount const& shares,
WaiveUnrealizedLoss waive)
{
@@ -197,26 +203,6 @@ sharesToAssetsWithdraw(
return assets;
}
[[nodiscard]] bool
isSoleShareholder(ReadView const& view, AccountID const& account, SLE::ConstRef issuance)
{
XRPL_ASSERT(
issuance && issuance->getType() == ltMPTOKEN_ISSUANCE,
"xrpl::isSoleShareholder : valid issuance SLE");
std::uint64_t const outstanding = issuance->at(sfOutstandingAmount);
if (outstanding == 0)
return false;
auto const shareMPTID =
makeMptID(issuance->getFieldU32(sfSequence), issuance->getAccountID(sfIssuer));
auto const sleToken = view.read(keylet::mptoken(shareMPTID, account));
if (!sleToken)
return false; // LCOV_EXCL_LINE
return sleToken->getFieldU64(sfMPTAmount) == outstanding;
}
[[nodiscard]] VaultVersion
getVaultVersion(SLE::ConstRef vault)
{
@@ -308,7 +294,7 @@ getVaultPhase(
[[nodiscard]] TER
checkVaultDomain(
ReadView const& view,
SLE::ConstRef issuance,
MPTokenIssuanceEntryR const& issuance,
AccountID const& subject,
SuppressExpired suppressExpired)
{

View File

@@ -6,6 +6,7 @@
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/PaymentSandbox.h>
#include <xrpl/ledger/entries/MPTokenIssuanceEntry.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
@@ -471,7 +472,7 @@ MPTEndpointStep<TDerived>::maxPaymentFlow(ReadView const& sb) const
return {toAmount<MPTAmount>(maxFlow), DebtDirection::Redeems};
// From an issuer to a holder
if (auto const sle = sb.read(keylet::mptokenIssuance(mptIssue_)))
if (auto const sle = MPTokenIssuanceEntryR(mptIssue_.getMptID(), sb))
{
// If issuer is the source account, and it is direct payment then
// MPTEndpointStep is the only step. Provide available maxFlow.
@@ -483,7 +484,7 @@ MPTEndpointStep<TDerived>::maxPaymentFlow(ReadView const& sb) const
// the previous step may issue or redeem. Allow OutstandingAmount
// to temporarily overflow. Let the previous step figure out how
// to limit the flow.
std::int64_t const maxAmount = maxMPTAmount(*sle);
std::int64_t const maxAmount = sle.maxAmount();
return {MPTAmount{maxAmount}, DebtDirection::Issues};
}

View File

@@ -5,6 +5,7 @@
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/Sandbox.h>
#include <xrpl/ledger/entries/MPTokenIssuanceEntry.h>
#include <xrpl/ledger/helpers/AMMHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
@@ -136,7 +137,7 @@ AMMClawback::preclaim(PreclaimContext const& ctx)
!sleIssuer->isFlag(lsfNoFreeze);
},
[&](MPTIssue const& issue) {
auto const sleIssuance = ctx.view.read(keylet::mptokenIssuance(issue.getMptID()));
MPTokenIssuanceEntryR const sleIssuance(issue.getMptID(), ctx.view);
return sleIssuance && sleIssuance->isFlag(lsfMPTCanClawback) &&
sleIssuance->getAccountID(sfIssuer) == ctx.tx[sfAccount];

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/MPTokenIssuanceEntry.h>
#include <xrpl/ledger/helpers/AMMHelpers.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
@@ -227,7 +228,7 @@ AMMCreate::preclaim(PreclaimContext const& ctx)
auto clawbackDisabled = [&](Asset const& asset) -> TER {
return asset.visit(
[&](MPTIssue const& issue) -> TER {
auto const sle = ctx.view.read(keylet::mptokenIssuance(issue.getMptID()));
MPTokenIssuanceEntryR const sle(issue.getMptID(), ctx.view);
if (!sle)
return tecINTERNAL; // LCOV_EXCL_LINE
if (sle->isFlag(lsfMPTCanClawback))

View File

@@ -3,6 +3,7 @@
#include <xrpl/basics/Log.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/entries/MPTokenIssuanceEntry.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/EscrowHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
@@ -73,8 +74,7 @@ escrowCancelPreclaimHelper<MPTIssue>(
return tecINTERNAL; // LCOV_EXCL_LINE
// If the mpt does not exist, return tecOBJECT_NOT_FOUND
auto const issuanceKey = keylet::mptokenIssuance(amount.get<MPTIssue>().getMptID());
auto const sleIssuance = ctx.view.read(issuanceKey);
MPTokenIssuanceEntryR const sleIssuance(amount.get<MPTIssue>().getMptID(), ctx.view);
if (!sleIssuance)
return tecOBJECT_NOT_FOUND;

View File

@@ -7,6 +7,7 @@
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/entries/MPTokenIssuanceEntry.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
@@ -278,7 +279,7 @@ escrowCreatePreclaimHelper<MPTIssue>(
// If the mpt does not exist, return tecOBJECT_NOT_FOUND
auto const issuanceKey = keylet::mptokenIssuance(amount.get<MPTIssue>().getMptID());
auto const sleIssuance = ctx.view.read(issuanceKey);
MPTokenIssuanceEntryR const sleIssuance(issuanceKey, ctx.view);
if (!sleIssuance)
return tecOBJECT_NOT_FOUND;

View File

@@ -9,6 +9,7 @@
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/entries/MPTokenIssuanceEntry.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/CredentialHelpers.h>
#include <xrpl/ledger/helpers/EscrowHelpers.h>
@@ -179,8 +180,7 @@ escrowFinishPreclaimHelper<MPTIssue>(
return tesSUCCESS;
// If the mpt does not exist, return tecOBJECT_NOT_FOUND
auto const issuanceKey = keylet::mptokenIssuance(amount.get<MPTIssue>().getMptID());
auto const sleIssuance = ctx.view.read(issuanceKey);
MPTokenIssuanceEntryR const sleIssuance(amount.get<MPTIssue>().getMptID(), ctx.view);
if (!sleIssuance)
return tecOBJECT_NOT_FOUND;

View File

@@ -6,6 +6,7 @@
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/MPTokenIssuanceEntry.h>
#include <xrpl/ledger/helpers/LendingHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
@@ -218,8 +219,7 @@ preclaimHelper<MPTIssue>(
SLE const& sleIssuer,
STAmount const& clawAmount)
{
auto const issuanceKey = keylet::mptokenIssuance(clawAmount.get<MPTIssue>().getMptID());
auto const sleIssuance = ctx.view.read(issuanceKey);
MPTokenIssuanceEntryR const sleIssuance(clawAmount.get<MPTIssue>().getMptID(), ctx.view);
if (!sleIssuance)
return tecOBJECT_NOT_FOUND;

View File

@@ -2,6 +2,7 @@
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/entries/MPTokenIssuanceEntry.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
@@ -154,7 +155,7 @@ preclaimHelper<MPTIssue>(
STAmount const& clawAmount)
{
auto const issuanceKey = keylet::mptokenIssuance(clawAmount.get<MPTIssue>().getMptID());
auto const sleIssuance = ctx.view.read(issuanceKey);
MPTokenIssuanceEntryR const sleIssuance(issuanceKey, ctx.view);
if (!sleIssuance)
return tecOBJECT_NOT_FOUND;

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/MPTokenIssuanceEntry.h>
#include <xrpl/protocol/ConfidentialTransfer.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
@@ -66,7 +67,7 @@ ConfidentialMPTClawback::preclaim(PreclaimContext const& ctx)
// Check if MPT issuance exists
auto const mptIssuanceID = ctx.tx[sfMPTokenIssuanceID];
auto const sleIssuance = ctx.view.read(keylet::mptokenIssuance(mptIssuanceID));
MPTokenIssuanceEntryR const sleIssuance(mptIssuanceID, ctx.view);
if (!sleIssuance)
return tecOBJECT_NOT_FOUND;
@@ -132,7 +133,7 @@ ConfidentialMPTClawback::doApply()
auto const mptIssuanceID = ctx_.tx[sfMPTokenIssuanceID];
auto const holder = ctx_.tx[sfHolder];
auto sleIssuance = view().peek(keylet::mptokenIssuance(mptIssuanceID));
MPTokenIssuanceEntryW sleIssuance(mptIssuanceID, view(), j_);
auto sleHolderMPToken = view().peek(keylet::mptoken(mptIssuanceID, holder));
if (!sleIssuance || !sleHolderMPToken)
@@ -229,7 +230,7 @@ ConfidentialMPTClawback::doApply()
(*sleIssuance)[sfOutstandingAmount] = oldOA - clawAmount;
view().update(sleHolderMPToken);
view().update(sleIssuance);
sleIssuance.update();
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/MPTokenIssuanceEntry.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/ConfidentialTransfer.h>
#include <xrpl/protocol/Feature.h>
@@ -78,7 +79,7 @@ ConfidentialMPTConvert::preclaim(PreclaimContext const& ctx)
auto const amount = ctx.tx[sfMPTAmount];
// ensure that issuance exists
auto const sleIssuance = ctx.view.read(keylet::mptokenIssuance(issuanceID));
MPTokenIssuanceEntryR const sleIssuance(issuanceID, ctx.view);
if (!sleIssuance)
return tecOBJECT_NOT_FOUND;
@@ -236,7 +237,7 @@ ConfidentialMPTConvert::doApply()
// LCOV_EXCL_STOP
}
auto sleIssuance = view().peek(keylet::mptokenIssuance(mptIssuanceID));
MPTokenIssuanceEntryW sleIssuance(mptIssuanceID, view(), j_);
if (!sleIssuance)
{
// LCOV_EXCL_START
@@ -376,7 +377,7 @@ ConfidentialMPTConvert::doApply()
// LCOV_EXCL_STOP
}
view().update(sleIssuance);
sleIssuance.update();
view().update(sleMptoken);
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/MPTokenIssuanceEntry.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/ConfidentialTransfer.h>
#include <xrpl/protocol/Feature.h>
@@ -70,7 +71,7 @@ ConfidentialMPTConvertBack::calculateBaseFee(ReadView const& view, STTx const& t
static TER
verifyProofs(
STTx const& tx,
std::shared_ptr<SLE const> const& issuance,
MPTokenIssuanceEntryR const& issuance,
std::shared_ptr<SLE const> const& mptoken)
{
if (!mptoken->isFieldPresent(sfHolderEncryptionKey))
@@ -154,7 +155,7 @@ ConfidentialMPTConvertBack::preclaim(PreclaimContext const& ctx)
auto const amount = ctx.tx[sfMPTAmount];
// ensure that issuance exists
auto const sleIssuance = ctx.view.read(keylet::mptokenIssuance(mptIssuanceID));
MPTokenIssuanceEntryR const sleIssuance(mptIssuanceID, ctx.view);
if (!sleIssuance)
return tecOBJECT_NOT_FOUND;
@@ -255,7 +256,7 @@ ConfidentialMPTConvertBack::doApply()
// LCOV_EXCL_STOP
}
auto sleIssuance = view().peek(keylet::mptokenIssuance(mptIssuanceID));
MPTokenIssuanceEntryW sleIssuance(mptIssuanceID, view(), j_);
if (!sleIssuance)
{
// LCOV_EXCL_START
@@ -333,7 +334,7 @@ ConfidentialMPTConvertBack::doApply()
incrementConfidentialVersion(*sleMptoken);
view().update(sleIssuance);
sleIssuance.update();
view().update(sleMptoken);
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/MPTokenIssuanceEntry.h>
#include <xrpl/protocol/ConfidentialTransfer.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
@@ -109,7 +110,7 @@ ConfidentialMPTHolderKeyUpdate::preclaim(PreclaimContext const& ctx)
auto const account = ctx.tx[sfAccount];
auto const mptIssuanceID = ctx.tx[sfMPTokenIssuanceID];
auto const sleIssuance = ctx.view.read(keylet::mptokenIssuance(mptIssuanceID));
auto const sleIssuance = MPTokenIssuanceEntryR(mptIssuanceID, ctx.view);
if (!sleIssuance)
return tecOBJECT_NOT_FOUND;

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/MPTokenIssuanceEntry.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/ConfidentialTransfer.h>
#include <xrpl/protocol/Indexes.h>
@@ -40,7 +41,7 @@ ConfidentialMPTMergeInbox::calculateBaseFee(ReadView const& view, STTx const& tx
TER
ConfidentialMPTMergeInbox::preclaim(PreclaimContext const& ctx)
{
auto const sleIssuance = ctx.view.read(keylet::mptokenIssuance(ctx.tx[sfMPTokenIssuanceID]));
MPTokenIssuanceEntryR const sleIssuance(ctx.tx[sfMPTokenIssuanceID], ctx.view);
if (!sleIssuance)
return tecOBJECT_NOT_FOUND;

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/MPTokenIssuanceEntry.h>
#include <xrpl/protocol/ConfidentialTransfer.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
@@ -99,7 +100,7 @@ ConfidentialMPTMirrorUpdate::preclaim(PreclaimContext const& ctx)
// The issuance must exist and have confidential balances enabled with a
// registered issuer encryption key; otherwise there is no mirror to update.
auto const mptIssuanceID = ctx.tx[sfMPTokenIssuanceID];
auto const sleIssuance = ctx.view.read(keylet::mptokenIssuance(mptIssuanceID));
MPTokenIssuanceEntryR const sleIssuance(mptIssuanceID, ctx.view);
if (!sleIssuance)
return tecOBJECT_NOT_FOUND;
@@ -209,7 +210,7 @@ ConfidentialMPTMirrorUpdate::doApply()
{
auto const mptIssuanceID = ctx_.tx[sfMPTokenIssuanceID];
auto const sleIssuance = view().read(keylet::mptokenIssuance(mptIssuanceID));
MPTokenIssuanceEntryR const sleIssuance(mptIssuanceID, view());
if (!sleIssuance)
{
// LCOV_EXCL_START

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/MPTokenIssuanceEntry.h>
#include <xrpl/ledger/helpers/CredentialHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/ConfidentialTransfer.h>
@@ -102,7 +103,7 @@ verifySendProofs(
PreclaimContext const& ctx,
std::shared_ptr<SLE const> const& sleSenderMPToken,
std::shared_ptr<SLE const> const& sleDestinationMPToken,
std::shared_ptr<SLE const> const& sleIssuance)
MPTokenIssuanceEntryR const& sleIssuance)
{
// Sanity check
if (!sleSenderMPToken || !sleDestinationMPToken || !sleIssuance)
@@ -180,7 +181,7 @@ ConfidentialMPTSend::preclaim(PreclaimContext const& ctx)
// Check if MPT issuance exists
auto const mptIssuanceID = ctx.tx[sfMPTokenIssuanceID];
auto const sleIssuance = ctx.view.read(keylet::mptokenIssuance(mptIssuanceID));
MPTokenIssuanceEntryR const sleIssuance(mptIssuanceID, ctx.view);
if (!sleIssuance)
return tecOBJECT_NOT_FOUND;
@@ -307,7 +308,7 @@ ConfidentialMPTSend::doApply()
auto sleSenderMPToken = view().peek(keylet::mptoken(mptIssuanceID, accountID_));
auto sleDestinationMPToken = view().peek(keylet::mptoken(mptIssuanceID, destination));
auto const sleIssuance = view().read(keylet::mptokenIssuance(mptIssuanceID));
MPTokenIssuanceEntryR const sleIssuance(mptIssuanceID, view());
auto const sleDestAcct = view().read(keylet::account(destination));

View File

@@ -1,6 +1,7 @@
#include <xrpl/tx/transactors/token/MPTokenAuthorize.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/entries/MPTokenIssuanceEntry.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/protocol/Feature.h>
@@ -37,7 +38,7 @@ MPTokenAuthorize::preclaim(PreclaimContext const& ctx)
{
auto const accountID = ctx.tx[sfAccount];
auto const holderID = ctx.tx[~sfHolder];
auto const sleMptIssuance = ctx.view.read(keylet::mptokenIssuance(ctx.tx[sfMPTokenIssuanceID]));
MPTokenIssuanceEntryR const sleMptIssuance(ctx.tx[sfMPTokenIssuanceID], ctx.view);
// if non-issuer account submits this tx, then they are trying either:
// 1. Unauthorize/delete MPToken

View File

@@ -5,6 +5,7 @@
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/MPTokenIssuanceEntry.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
@@ -154,7 +155,8 @@ MPTokenIssuanceCreate::create(
if (!ownerNode)
return std::unexpected(tecDIR_FULL); // LCOV_EXCL_LINE
auto mptIssuance = std::make_shared<SLE>(mptIssuanceKeylet);
MPTokenIssuanceEntryW mptIssuance(mptIssuanceKeylet, ctx.view, journal);
mptIssuance.newSLE();
(*mptIssuance)[sfFlags] = args.flags & ~tfUniversal;
(*mptIssuance)[sfIssuer] = args.account;
(*mptIssuance)[sfOutstandingAmount] = 0;
@@ -195,9 +197,9 @@ MPTokenIssuanceCreate::create(
(*mptIssuance)[sfReferenceHolding] = *args.referenceHolding;
}
addSponsorToLedgerEntry(mptIssuance, sponsorSle);
addSponsorToLedgerEntry(mptIssuance.mutableRawSle(), sponsorSle);
ctx.view.insert(mptIssuance);
mptIssuance.insert();
}
// Update owner count.

View File

@@ -1,5 +1,7 @@
#include <xrpl/tx/transactors/token/MPTokenIssuanceDestroy.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/MPTokenIssuanceEntry.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/SField.h>
@@ -21,7 +23,7 @@ TER
MPTokenIssuanceDestroy::preclaim(PreclaimContext const& ctx)
{
// ensure that issuance exists
auto const sleMPT = ctx.view.read(keylet::mptokenIssuance(ctx.tx[sfMPTokenIssuanceID]));
MPTokenIssuanceEntryR const sleMPT(ctx.tx[sfMPTokenIssuanceID], ctx.view);
if (!sleMPT)
return tecOBJECT_NOT_FOUND;
@@ -42,15 +44,15 @@ MPTokenIssuanceDestroy::preclaim(PreclaimContext const& ctx)
TER
MPTokenIssuanceDestroy::doApply()
{
auto const mpt = view().peek(keylet::mptokenIssuance(ctx_.tx[sfMPTokenIssuanceID]));
MPTokenIssuanceEntryW mpt(ctx_.tx[sfMPTokenIssuanceID], view(), j_);
if (accountID_ != mpt->getAccountID(sfIssuer))
return tecINTERNAL; // LCOV_EXCL_LINE
if (!view().dirRemove(keylet::ownerDir(accountID_), (*mpt)[sfOwnerNode], mpt->key(), false))
return tefBAD_LEDGER; // LCOV_EXCL_LINE
decreaseOwnerCountForObject(view(), accountID_, mpt, 1, j_);
view().erase(mpt);
decreaseOwnerCountForObject(view(), accountID_, mpt.mutableRawSle(), 1, j_);
mpt.erase();
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/MPTokenIssuanceEntry.h>
#include <xrpl/protocol/ConfidentialTransfer.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
@@ -143,7 +144,7 @@ TER
MPTokenIssuanceSet::preclaim(PreclaimContext const& ctx)
{
// ensure that issuance exists
auto const sleMptIssuance = ctx.view.read(keylet::mptokenIssuance(ctx.tx[sfMPTokenIssuanceID]));
MPTokenIssuanceEntryR const sleMptIssuance(ctx.tx[sfMPTokenIssuanceID], ctx.view);
if (!sleMptIssuance)
return tecOBJECT_NOT_FOUND;

View File

@@ -6,6 +6,7 @@
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/entries/MPTokenIssuanceEntry.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/ledger/helpers/VaultHelpers.h>
@@ -87,7 +88,7 @@ VaultClawback::preclaim(PreclaimContext const& ctx)
auto const holder = ctx.tx[sfHolder];
auto const maybeAmount = ctx.tx[~sfAmount];
auto const mptIssuanceID = vault->at(sfShareMPTID);
auto const sleShareIssuance = ctx.view.read(keylet::mptokenIssuance(mptIssuanceID));
MPTokenIssuanceEntryR const sleShareIssuance(mptIssuanceID, ctx.view);
if (!sleShareIssuance)
{
// LCOV_EXCL_START
@@ -192,8 +193,8 @@ VaultClawback::preclaim(PreclaimContext const& ctx)
return vaultAsset.visit(
[&](MPTIssue const& issue) -> TER {
auto const mptIssue = ctx.view.read(keylet::mptokenIssuance(issue.getMptID()));
if (mptIssue == nullptr)
MPTokenIssuanceEntryR const mptIssue(issue.getMptID(), ctx.view);
if (!mptIssue)
return tecOBJECT_NOT_FOUND;
if (!mptIssue->isFlag(lsfMPTCanClawback))
@@ -233,7 +234,7 @@ VaultClawback::preclaim(PreclaimContext const& ctx)
std::expected<std::pair<STAmount, STAmount>, TER>
VaultClawback::assetsToClawback(
SLE::Ref vault,
SLE::ConstRef sleShareIssuance,
MPTokenIssuanceEntryR const& sleShareIssuance,
AccountID const& holder,
STAmount const& clawbackAmount)
{
@@ -274,8 +275,7 @@ VaultClawback::assetsToClawback(
{
// Do not discount a sole holder's shares: clawing back AssetsAvailable
// at the discounted rate can burn every share while loan assets remain.
auto const waiveUnrealizedLoss =
fix340Enabled && isSoleShareholder(view(), holder, sleShareIssuance)
auto const waiveUnrealizedLoss = fix340Enabled && sleShareIssuance.isSoleShareholder(holder)
? WaiveUnrealizedLoss::Yes
: WaiveUnrealizedLoss::No;
@@ -395,7 +395,7 @@ VaultClawback::doApply()
return tefINTERNAL; // LCOV_EXCL_LINE
auto const mptIssuanceID = *vault->at(sfShareMPTID);
auto const sleIssuance = view().read(keylet::mptokenIssuance(mptIssuanceID));
MPTokenIssuanceEntryR const sleIssuance(mptIssuanceID, view());
if (!sleIssuance)
{
// LCOV_EXCL_START

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/MPTokenIssuanceEntry.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
@@ -66,7 +67,7 @@ VaultDelete::preclaim(PreclaimContext const& ctx)
}
// Verify we can destroy MPTokenIssuance
auto const sleMPT = ctx.view.read(keylet::mptokenIssuance(vault->at(sfShareMPTID)));
MPTokenIssuanceEntryR const sleMPT(vault->at(sfShareMPTID), ctx.view);
if (!sleMPT)
{
@@ -121,7 +122,7 @@ VaultDelete::doApply()
// Destroy the share issuance. Do not use MPTokenIssuanceDestroy for this,
// no special logic needed. First run few checks, duplicated from preclaim.
auto const shareMPTID = *vault->at(sfShareMPTID);
auto const mpt = view().peek(keylet::mptokenIssuance(shareMPTID));
MPTokenIssuanceEntryW mpt(shareMPTID, view(), j_);
if (!mpt)
{
// LCOV_EXCL_START
@@ -155,9 +156,9 @@ VaultDelete::doApply()
return tefBAD_LEDGER;
// LCOV_EXCL_STOP
}
decreaseOwnerCountForObject(view(), pseudoAcct, mpt, 1, j_);
decreaseOwnerCountForObject(view(), pseudoAcct, mpt.mutableRawSle(), 1, j_);
view().erase(mpt);
mpt.erase();
// The pseudo-account's directory should have been deleted already.
if (view().peek(keylet::ownerDir(pseudoID)))

View File

@@ -6,6 +6,7 @@
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/MPTokenIssuanceEntry.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/ledger/helpers/VaultHelpers.h>
@@ -141,7 +142,7 @@ VaultDeposit::preclaim(PreclaimContext const& ctx)
// LCOV_EXCL_STOP
}
auto const sleIssuance = ctx.view.read(keylet::mptokenIssuance(mptIssuanceID));
MPTokenIssuanceEntryR const sleIssuance(mptIssuanceID, ctx.view);
if (!sleIssuance)
{
// LCOV_EXCL_START
@@ -253,7 +254,7 @@ VaultDeposit::doApply()
// Make sure the depositor can hold shares.
auto const mptIssuanceID = (*vault)[sfShareMPTID];
auto const sleIssuance = view().read(keylet::mptokenIssuance(mptIssuanceID));
MPTokenIssuanceEntryR const sleIssuance(mptIssuanceID, view());
if (!sleIssuance)
{
// LCOV_EXCL_START

View File

@@ -2,6 +2,7 @@
#include <xrpl/basics/Log.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/ledger/entries/MPTokenIssuanceEntry.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
@@ -75,7 +76,7 @@ VaultSet::preclaim(PreclaimContext const& ctx)
}
auto const mptIssuanceID = (*vault)[sfShareMPTID];
auto const sleIssuance = ctx.view.read(keylet::mptokenIssuance(mptIssuanceID));
MPTokenIssuanceEntryR const sleIssuance(mptIssuanceID, ctx.view);
if (!sleIssuance)
{
// LCOV_EXCL_START
@@ -130,7 +131,7 @@ VaultSet::doApply()
auto const vaultAsset = vault->at(sfAsset);
auto const mptIssuanceID = (*vault)[sfShareMPTID];
auto const sleIssuance = view().peek(keylet::mptokenIssuance(mptIssuanceID));
MPTokenIssuanceEntryW sleIssuance(mptIssuanceID, view(), j_);
if (!sleIssuance)
{
// LCOV_EXCL_START
@@ -164,7 +165,7 @@ VaultSet::doApply()
{
sleIssuance->makeFieldAbsent(sfDomainID);
}
view().update(sleIssuance);
sleIssuance.update();
}
// Note, we must update Vault object even if only DomainID is being updated

View File

@@ -7,6 +7,7 @@
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/entries/MPTokenIssuanceEntry.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/CredentialHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
@@ -38,13 +39,16 @@ VaultWithdraw::checkExtraFeatures(PreflightContext const& ctx)
}
static WaiveUnrealizedLoss
shouldWaiveWithdrawal(ReadView const& view, AccountID const& account, SLE::ConstRef issuance)
shouldWaiveWithdrawal(
ReadView const& view,
AccountID const& account,
MPTokenIssuanceEntryR const& issuance)
{
XRPL_ASSERT(
issuance && issuance->getType() == ltMPTOKEN_ISSUANCE,
"xrpl::shouldWaiveWithdrawal : valid issuance sle");
return view.rules().enabled(fixCleanup3_2_0) && isSoleShareholder(view, account, issuance)
return view.rules().enabled(fixCleanup3_2_0) && issuance.isSoleShareholder(account)
? WaiveUnrealizedLoss::Yes
: WaiveUnrealizedLoss::No;
}
@@ -150,7 +154,7 @@ VaultWithdraw::preclaim(PreclaimContext const& ctx)
// to the equivalent asset amount before checking withdrawal
// limits. Pre-amendment the limit check was skipped for
// share-denominated withdrawals.
auto const sleIssuance = ctx.view.read(keylet::mptokenIssuance(vaultShare));
MPTokenIssuanceEntryR const sleIssuance(vaultShare, ctx.view);
if (!sleIssuance)
{
// LCOV_EXCL_START
@@ -227,7 +231,7 @@ VaultWithdraw::preclaim(PreclaimContext const& ctx)
if (fix340Enabled && vault->isFlag(lsfVaultPrivate) && dstAcct != account &&
dstAcct != vaultAsset.getIssuer())
{
auto const sleIssuance = ctx.view.read(keylet::mptokenIssuance(vaultShare));
MPTokenIssuanceEntryR const sleIssuance(vaultShare, ctx.view);
if (!sleIssuance)
{
// LCOV_EXCL_START
@@ -284,7 +288,7 @@ VaultWithdraw::doApply()
return tefINTERNAL; // LCOV_EXCL_LINE
auto const mptIssuanceID = *((*vault)[sfShareMPTID]);
auto const sleIssuance = view().read(keylet::mptokenIssuance(mptIssuanceID));
MPTokenIssuanceEntryR const sleIssuance(mptIssuanceID, view());
if (!sleIssuance)
{
// LCOV_EXCL_START

View File

@@ -22,6 +22,7 @@
#include <xrpl/beast/unit_test/suite.h>
#include <xrpl/json/json_forwards.h>
#include <xrpl/json/json_value.h>
#include <xrpl/ledger/entries/MPTokenIssuanceEntry.h>
#include <xrpl/ledger/helpers/VaultHelpers.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/Feature.h>
@@ -1522,7 +1523,7 @@ private:
// assuming an exact 90/10 split holds under truncation.
auto const maybeSharesDestroyed = assetsToSharesWithdraw(
vaultBefore,
issuanceBefore,
MPTokenIssuanceEntryR(issuanceBefore, *env.current()),
setup.usd(9'000).value(),
TruncateShares::Yes,
WaiveUnrealizedLoss::Yes);

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>