Compare commits

...
72 changed files with 2671 additions and 1543 deletions

No files matched your search

+11 -11
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/helpers/SLEWrappers.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/LedgerFormats.h> // IWYU pragma: keep
#include <xrpl/protocol/Protocol.h>
@@ -47,11 +48,9 @@ namespace xrpl {
*/
[[nodiscard]] TER
canApplyToBrokerCover(
ReadView const& view,
SLE::const_ref sleBroker,
LoanBrokerEntry<ReadView> const& sleBroker,
Asset const& vaultAsset,
STAmount const& amount,
beast::Journal j,
std::string_view logPrefix);
// Lending protocol has dependencies, so capture them here.
@@ -217,7 +216,7 @@ adjustImpreciseNumber(
}
inline int
getAssetsTotalScale(SLE::const_ref vaultSle)
getAssetsTotalScale(VaultEntry<ReadView> const& vaultSle)
{
if (!vaultSle)
return Number::kMinExponent - 1; // LCOV_EXCL_LINE
@@ -228,7 +227,10 @@ getAssetsTotalScale(SLE::const_ref vaultSle)
// DebtTotal is a broker-level aggregate maintained at vault scale, so the
// rounding must also use vault scale — never an individual loan's scale.
inline Number
minimumBrokerCover(Number const& debtTotal, TenthBips32 coverRateMinimum, SLE::const_ref vaultSle)
minimumBrokerCover(
Number const& debtTotal,
TenthBips32 coverRateMinimum,
VaultEntry<ReadView> const& vaultSle)
{
XRPL_ASSERT(
vaultSle && vaultSle->getType() == ltVAULT, "xrpl::minimumBrokerCover : valid Vault sle");
@@ -266,7 +268,7 @@ constructLoanState(
// Constructs a valid LoanState object from a Loan object, which always has
// rounded values
LoanState
constructRoundedLoanState(SLE::const_ref loan);
constructRoundedLoanState(LoanEntry<ReadView> const& loan);
Number
computeManagementFee(
@@ -550,11 +552,9 @@ enum class LoanPaymentType { Regular = 0, Late, Full, Overpayment };
std::expected<LoanPaymentParts, TER>
loanMakePayment(
Asset const& asset,
ApplyView& view,
SLE::ref loan,
SLE::const_ref brokerSle,
LoanEntry<ApplyView>& loan,
LoanBrokerEntry<ReadView> const& brokerSle,
STAmount const& amount,
LoanPaymentType const paymentType,
beast::Journal j);
LoanPaymentType const paymentType);
} // namespace xrpl
@@ -1,5 +1,9 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/helpers/SLEBase.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STArray.h> // IWYU pragma: keep
#include <xrpl/protocol/STLedgerEntry.h>
@@ -28,4 +32,35 @@ calculateOracleReserve(SLE::const_ref oracleSle)
return calculateOracleReserve(oracleSle->getFieldArray(sfPriceDataSeries));
}
template <typename ViewT>
class OracleEntry : public SLEBase<ViewT>
{
public:
// Inherit base constructors: adopt an existing SLE, or resolve one from a
// Keylet against the view.
using SLEBase<ViewT>::SLEBase;
explicit OracleEntry(
AccountID const& account,
std::uint32_t documentID,
SLEBase<ViewT>::view_ref_type view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: SLEBase<ViewT>(keylet::oracle(account, documentID), view, j)
{
}
[[nodiscard]] SField const&
ownerField() const override
{
return sfOwner;
}
// An Oracle with more than five price-data pairs occupies two reserve slots.
[[nodiscard]] std::uint32_t
reserveCount() const override
{
return calculateOracleReserve(this->sle()->getFieldArray(sfPriceDataSeries));
}
};
} // namespace xrpl
@@ -3,6 +3,7 @@
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/protocol/Rules.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/TER.h>
@@ -23,7 +24,11 @@ namespace xrpl {
* fails; tefINTERNAL if the source account SLE cannot be found.
*/
TER
closeChannel(SLE::ref slep, ApplyView& view, uint256 const& key, beast::Journal j);
closeChannel(
PayChannelEntry<ApplyView>& slep,
ApplyView& view,
uint256 const& key,
beast::Journal j);
/**
* Add two uint32_t values with saturation at UINT32_MAX.
+599
View File
@@ -0,0 +1,599 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h> // increaseOwnerCount, accountReserve
#include <xrpl/ledger/helpers/DirectoryHelpers.h> // describeOwnerDir
#include <xrpl/ledger/helpers/SponsorHelpers.h> // addSponsorToLedgerEntry, checkReserve
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h> // keylet::account, keylet::ownerDir
#include <xrpl/protocol/Keylet.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/XRPAmount.h>
#include <concepts>
#include <cstdint>
#include <memory>
#include <optional>
#include <type_traits>
#include <utility>
#include <vector>
namespace xrpl {
// Concept to distinguish read-only vs writable view types
template <typename V>
concept WritableView = std::derived_from<V, ApplyView>;
/**
* Describes one directory a ledger entry is linked into, for create()/destroy().
*
* @param owner the account whose owner directory this is (the directory is
* keylet::ownerDir(owner)).
* @param node the field on the entry holding this directory's page index
* (sfOwnerNode, sfDestinationNode, sfSubjectNode, ...).
* @param countsToward whether linking here consumes `owner`'s OwnerCount /
* reserve (true for the owning account, false for
* auxiliary links such as a destination's tracking
* directory).
*/
struct OwnerDirLink
{
AccountID owner;
SField const* node{} {};
bool countsToward{} {};
};
/**
* View-parameterized base class for all ledger entry wrappers.
*
* SLEBase<ReadView> — read-only: holds shared_ptr<SLE const> + ReadView const&
* SLEBase<ApplyView> — writable: holds shared_ptr<SLE> + ApplyView& + Keylet,
* plus insert/update/erase operations
*
* Write-only members are gated by `requires` clauses, providing compile-time
* guarantees that read-only wrappers cannot mutate state.
*
* Derived classes should provide domain-specific accessors that hide
* implementation details of the underlying ledger entry format.
*/
template <typename ViewT>
class SLEBase
{
public:
static constexpr bool kIsWritable = WritableView<ViewT>;
// SLE pointer type: mutable for writable views, const for read-only
using sle_ptr_type = std::conditional_t<kIsWritable, std::shared_ptr<SLE>, SLE::const_pointer>;
// View reference type: ApplyView& for writable, ReadView const& for read-only
using view_ref_type = std::conditional_t<kIsWritable, ApplyView&, ReadView const&>;
virtual ~SLEBase() = default;
SLEBase(SLEBase const&) = default;
SLEBase(SLEBase&&) = default;
SLEBase&
operator=(SLEBase const&) = delete;
SLEBase&
operator=(SLEBase&&) = delete;
SLEBase() = delete;
// --- Common interface (always available) ---
/**
* Returns true if the ledger entry exists
*/
[[nodiscard]] bool
exists() const
{
return sle_ != nullptr;
}
/**
* Explicit conversion to bool for convenient existence checking
*/
explicit
operator bool() const
{
return exists();
}
/**
* Returns the underlying SLE for read access
*/
[[nodiscard]] SLE::const_pointer
sle() const
{
return sle_;
}
/**
* Returns the read view (always available; ApplyView inherits ReadView)
*/
[[nodiscard]] ReadView const&
readView() const
{
return view_;
}
/**
* Const dereference operators (always available)
*/
STLedgerEntry const*
operator->() const
{
XRPL_ASSERT(exists(), "xrpl::SLEBase::operator-> : exists");
return sle_.get();
}
STLedgerEntry const&
operator*() const
{
XRPL_ASSERT(exists(), "xrpl::SLEBase::operator* : exists");
return *sle_;
}
// --- Writable interface (compile-time gated) ---
/**
* Returns a mutable SLE for write operations
*/
[[nodiscard]] sle_ptr_type const&
mutableSle() const
requires kIsWritable
{
return sle_;
}
/**
* Returns true if this wrapper supports write operations
*/
[[nodiscard]] bool
canModify() const
requires kIsWritable
{
return sle_ != nullptr;
}
/**
* Returns the apply view for write operations
*/
[[nodiscard]] ApplyView&
applyView() const
requires kIsWritable
{
return view_;
}
/**
* Mutable dereference operators
*/
STLedgerEntry*
operator->()
requires kIsWritable
{
XRPL_ASSERT(canModify(), "xrpl::SLEBase::operator-> : can modify");
return sle_.get();
}
STLedgerEntry&
operator*()
requires kIsWritable
{
XRPL_ASSERT(canModify(), "xrpl::SLEBase::operator* : can modify");
return *sle_;
}
void
insert()
requires kIsWritable
{
XRPL_ASSERT(canModify(), "xrpl::SLEBase::insert : can modify");
view_.insert(sle_);
}
void
erase()
requires kIsWritable
{
XRPL_ASSERT(canModify(), "xrpl::SLEBase::erase : can modify");
view_.erase(sle_);
}
void
update()
requires kIsWritable
{
XRPL_ASSERT(canModify(), "xrpl::SLEBase::update : can modify");
view_.update(sle_);
}
void
newSLE()
requires kIsWritable
{
XRPL_ASSERT(!canModify(), "xrpl::SLEBase::newSLE : no existing SLE");
sle_ = std::make_shared<SLE>(key_);
}
/**
* The field holding the account that owns this entry (sfAccount, sfOwner,
* sfIssuer, ...).
*
* Single-directory entry types override this to name their owning-account
* field; the default ownerDirs() then links a single owner directory
* keyed on it. Types that live in multiple directories override
* ownerDirs() directly instead. The generic base has no owner.
*/
[[nodiscard]] virtual SField const&
ownerField() const
{
UNREACHABLE("xrpl::SLEBase::ownerField : type does not define an owner field");
return sfAccount; // unreachable; present only to satisfy the return type
}
/**
* The directories this entry is linked into, and where each stores its
* page index.
*
* Default: a single owner directory for ownerField()'s account, recorded
* in sfOwnerNode, counting toward that account's reserve. Types linked
* into more than one directory (e.g. Check/PayChannel/Escrow's
* destination directory, Credential's subject directory) override this to
* list them; only links with `countsToward == true` consume an
* OwnerCount/reserve slot.
*/
[[nodiscard]] virtual std::vector<OwnerDirLink>
ownerDirs() const
{
return {{sle_->getAccountID(ownerField()), &sfOwnerNode, /*countsToward=*/true}};
}
/**
* Number of OwnerCount/reserve slots a counted link consumes (default 1).
* Types whose footprint scales with their contents (e.g. Oracle)
* override.
*/
[[nodiscard]] virtual std::uint32_t
reserveCount() const
{
return 1;
}
/**
* Link this entry into each listed owner directory (keylet::ownerDir),
* and record the assigned page in the link's node field.
*
* This is the shared directory-linking primitive used by create() and by
* the bespoke create() overrides of entries that live in several owner
* directories. The OwnerDirLink::countsToward flag is ignored here —
* reserve and OwnerCount accounting is the caller's responsibility.
* Returns tecDIR_FULL if any directory is full.
*/
[[nodiscard]] TER
linkOwnerDirs(std::vector<OwnerDirLink> const& dirs)
requires kIsWritable
{
for (auto const& d : dirs)
{
auto const page =
view_.dirInsert(keylet::ownerDir(d.owner), sle_->key(), describeOwnerDir(d.owner));
if (!page)
return tecDIR_FULL; // LCOV_EXCL_LINE
sle_->setFieldU64(*d.node, *page);
}
return tesSUCCESS;
}
/**
* Unlink this entry from each listed owner directory, using the page
* stored in each link's node field. Inverse of linkOwnerDirs(). Returns
* tefBAD_LEDGER if any removal fails.
*/
[[nodiscard]] TER
unlinkOwnerDirs(std::vector<OwnerDirLink> const& dirs)
requires kIsWritable
{
for (auto const& d : dirs)
{
{
if (!view_.dirRemove(
keylet::ownerDir(d.owner),
sle_->getFieldU64(*d.node),
sle_->key(),
/*keepRoot=*/false))
return tefBAD_LEDGER; // LCOV_EXCL_LINE
}
}
return tesSUCCESS;
}
/**
* Link a freshly-populated entry into its owner directories and insert
* it.
*
* Handles the create-time boilerplate shared by owned ledger entries:
* 1. reserve check against `ownerReserveBalance` (the owner's pre-fee
* XRP balance — pass the transactor's preFeeBalance_ when the entry
* is owned by the transaction submitter). Pass std::nullopt to skip
* the check entirely, e.g. for entries an internal caller installs
* on a pseudo-account (VaultCreate),
* 2. link into each ownerDirs() directory, recording the page in its
* node field,
* 3. bump the OwnerCount of each counted owner by reserveCount(),
* 4. insert the entry into the view.
*
* The caller must have already called newSLE() and populated the entry's
* domain fields (in particular the account fields ownerDirs() reads).
*/
[[nodiscard]] TER
create(std::optional<XRPAmount> ownerReserveBalance)
requires kIsWritable
{
XRPL_ASSERT(canModify(), "xrpl::SLEBase::create : can modify");
auto const dirs = ownerDirs();
Adjustment const adj{.ownerCountDelta = static_cast<std::int32_t>(reserveCount())};
// The reserve sponsor covers only the transaction submitter's own
// objects, so it is resolved from the counted owner (yielding no sponsor
// when the owner is a pseudo-account or otherwise not the submitter).
// Null unless the wrapper was built with an ApplyViewContext (a create
// path) and the transaction carries a reserve sponsor for the owner.
SLE::pointer sponsorSle;
for (auto const& d : dirs)
{
if (!d.countsToward)
continue;
auto const ownerSle = view_.peek(keylet::account(d.owner));
if (!ownerSle)
return tecNO_ENTRY; // LCOV_EXCL_LINE
if (tx_ != nullptr != nullptr)
{
auto const sponsorExp =
getEffectiveTxReserveSponsor(ApplyViewContext{view_, *tx_}, ownerSle);
if (!sponsorExp)
return sponsorExp.error(); // LCOV_EXCL_LINE
sponsorSle = *sponsorExp;
}
if (!ownerReserveBalance)
continue;
// Route the reserve check through checkReserve() so a reserve
// sponsor is honored; otherwise fall back to the account's own
// reserve.
if (tx_ != nullptr != nullptr)
{
if (auto const ret = checkReserve(
ApplyViewContext{view_, *tx_},
ownerSle,
*ownerReserveBalance,
sponsorSle,
adj,
j_);
!isTesSuccess(ret))
return ret;
}
else if (*ownerReserveBalance < accountReserve(view_, ownerSle, j_, adj))
{
{
{
{
return tecINSUFFICIENT_RESERVE;
}
}
}
}
}
if (auto const ter = linkOwnerDirs(dirs); !isTesSuccess(ter))
return ter; // LCOV_EXCL_LINE
for (auto const& d : dirs)
{
{
if (d.countsToward)
{
{
increaseOwnerCount(
view_,
view_.peek(keylet::account(d.owner)),
sponsorSle,
reserveCount(),
j_);
}
}
}
}
// Stamp the reserve sponsor (if any) onto the new entry so that a later
// delete refunds the sponsor rather than the owner. A no-op when
// sponsorSle is null.
if (tx_ != nullptr != nullptr)
addSponsorToLedgerEntry(sle_, sponsorSle);
view_.insert(sle_);
return tesSUCCESS;
}
/**
* Unlink an owned entry from its directories and erase it.
*
* Inverse of create(): removes the entry from each ownerDirs() directory
* (using the stored node fields), decrements each counted owner's
* OwnerCount by reserveCount(), and erases the entry.
*/
[[nodiscard]] TER
destroy()
requires kIsWritable
{
XRPL_ASSERT(canModify(), "xrpl::SLEBase::destroy : can modify");
auto const dirs = ownerDirs();
if (auto const ter = unlinkOwnerDirs(dirs); !isTesSuccess(ter))
return ter; // LCOV_EXCL_LINE
// decreaseOwnerCountForObject derives the reserve sponsor (if any) from
// the entry's sfSponsor field, refunding the sponsor rather than the
// owner when the object was sponsored.
for (auto const& d : dirs)
{
{
if (d.countsToward)
{
{
if (auto ownerSle = view_.peek(keylet::account(d.owner)))
decreaseOwnerCountForObject(view_, ownerSle, sle_, reserveCount(), j_);
}
}
}
}
view_.erase(sle_);
return tesSUCCESS;
}
[[nodiscard]] beast::Journal
journal() const
{
return j_;
}
// --- Constructors that adopt/resolve an SLE (public so the ReadOnlySLE /
// WritableSLE aliases and the per-type wrappers can be built directly
// from an already-fetched SLE or a keylet). ---
/**
* Constructor for read-only context
*/
explicit SLEBase(
SLE::const_pointer sle,
ReadView const& view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
requires(!kIsWritable)
: view_(view), sle_(std::move(sle)), j_(j)
{
}
/**
* Constructor for read-only context (read from view by keylet)
*/
explicit SLEBase(
Keylet const& key,
ReadView const& view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
requires(!kIsWritable)
: view_(view), sle_(view.read(key)), j_(j)
{
}
/**
* Converting constructor: writable → read-only.
* Enables implicit conversion from SLEBase<ApplyView> to
* SLEBase<ReadView>, so functions taking ReadOnlySLE const& can accept
* WritableSLE.
*/
template <WritableView OtherViewT>
SLEBase(SLEBase<OtherViewT> const& other)
requires(!kIsWritable)
: view_(other.readView()), sle_(other.sle()), j_(other.journal())
{
}
/**
* Constructor for writable context (from existing SLE)
*/
explicit SLEBase(
std::shared_ptr<SLE> sle,
ApplyView& view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
requires kIsWritable
: view_(view)
, key_(sle ? Keylet(sle->getType(), sle->key()) : Keylet(ltANY, uint256{}))
, sle_(std::move(sle))
, j_(j)
{
}
/**
* Constructor for writable context (peek from view by keylet)
*/
explicit SLEBase(
Keylet const& key,
ApplyView& view,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
requires kIsWritable
: view_(view), key_(key), sle_(view_.peek(key)), j_(j)
{
}
/**
* Constructor for writable context carrying the applying transaction
* (from existing SLE). Providing the ApplyViewContext lets create()
* perform reserve-sponsorship-aware accounting.
*/
explicit SLEBase(
std::shared_ptr<SLE> sle,
ApplyViewContext ctx,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
requires kIsWritable
: view_(ctx.view)
, key_(sle ? Keylet(sle->getType(), sle->key()) : Keylet(ltANY, uint256{}))
, sle_(std::move(sle))
, j_(j)
, tx_(&ctx.tx)
{
}
/**
* Constructor for writable context carrying the applying transaction
* (peek from view by keylet).
*/
explicit SLEBase(
Keylet const& key,
ApplyViewContext ctx,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
requires kIsWritable
: view_(ctx.view), key_(key), sle_(view_.peek(key)), j_(j), tx_(&ctx.tx)
{
}
protected:
view_ref_type view_{};
// Keylet is only meaningful for writable views, but we conditionally
// include it to avoid wasting space in read-only wrappers.
struct Empty
{
};
[[no_unique_address]]
std::conditional_t<kIsWritable, Keylet, Empty> key_{};
sle_ptr_type sle_{};
beast::Journal j_;
// The applying transaction, when the wrapper was constructed from an
// ApplyViewContext. Only meaningful for writable wrappers on a create path;
// null otherwise. Enables reserve-sponsorship-aware create().
STTx const* tx_ = nullptr;
};
/**
* Generic (any-entry-type) SLE handles.
*
* Use these when the concrete ledger entry type is not known at a given site;
* otherwise prefer the per-type wrappers in SLEWrappers.h.
*
* SLE::const_pointer / SLE::const_ref -> ReadOnlySLE
* SLE::pointer / SLE::ref -> WritableSLE
*/
using ReadOnlySLE = SLEBase<ReadView>;
using WritableSLE = SLEBase<ApplyView>;
} // namespace xrpl
File diff suppressed because it is too large. Load diff
+17 -8
View File
@@ -1,9 +1,9 @@
#pragma once
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <optional>
@@ -21,7 +21,10 @@ namespace xrpl {
* @return The number of shares, or nullopt on error.
*/
[[nodiscard]] std::optional<STAmount>
assetsToSharesDeposit(SLE::const_ref vault, SLE::const_ref issuance, STAmount const& assets);
assetsToSharesDeposit(
VaultEntry<ReadView> const& vault,
MPTokenIssuanceEntry<ReadView> const& issuance,
STAmount const& assets);
/**
* From the perspective of a vault, return the number of assets to take from
@@ -35,7 +38,10 @@ assetsToSharesDeposit(SLE::const_ref vault, SLE::const_ref issuance, STAmount co
* @return The number of assets, or nullopt on error.
*/
[[nodiscard]] std::optional<STAmount>
sharesToAssetsDeposit(SLE::const_ref vault, SLE::const_ref issuance, STAmount const& shares);
sharesToAssetsDeposit(
VaultEntry<ReadView> const& vault,
MPTokenIssuanceEntry<ReadView> const& issuance,
STAmount const& shares);
/**
* Controls whether to truncate shares instead of rounding.
@@ -68,8 +74,8 @@ enum class WaiveUnrealizedLoss : bool { No = false, Yes = true };
*/
[[nodiscard]] std::optional<STAmount>
assetsToSharesWithdraw(
SLE::const_ref vault,
SLE::const_ref issuance,
VaultEntry<ReadView> const& vault,
MPTokenIssuanceEntry<ReadView> const& issuance,
STAmount const& assets,
TruncateShares truncate = TruncateShares::No,
WaiveUnrealizedLoss waive = WaiveUnrealizedLoss::No);
@@ -89,8 +95,8 @@ assetsToSharesWithdraw(
*/
[[nodiscard]] std::optional<STAmount>
sharesToAssetsWithdraw(
SLE::const_ref vault,
SLE::const_ref issuance,
VaultEntry<ReadView> const& vault,
MPTokenIssuanceEntry<ReadView> const& issuance,
STAmount const& shares,
WaiveUnrealizedLoss waive = WaiveUnrealizedLoss::No);
@@ -105,6 +111,9 @@ sharesToAssetsWithdraw(
* both the share MPTID and the outstanding-amount total.
*/
[[nodiscard]] bool
isSoleShareholder(ReadView const& view, AccountID const& account, SLE::const_ref issuance);
isSoleShareholder(
ReadView const& view,
AccountID const& account,
MPTokenIssuanceEntry<ReadView> const& issuance);
} // namespace xrpl
@@ -2,7 +2,9 @@
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Asset.h>
@@ -169,7 +171,7 @@ public:
static std::pair<TER, bool>
deleteAMMAccountIfEmpty(
Sandbox& sb,
SLE::pointer const ammSle,
AMMEntry<ApplyView>& ammSle,
STAmount const& lpTokenBalance,
Asset const& asset1,
Asset const& asset2,
@@ -4,6 +4,7 @@
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
@@ -41,34 +42,28 @@ public:
*/
static TER
defaultLoan(
ApplyView& view,
SLE::ref loanSle,
SLE::ref brokerSle,
SLE::ref vaultSle,
Asset const& vaultAsset,
beast::Journal j);
LoanEntry<ApplyView>& loanSle,
LoanBrokerEntry<ApplyView>& brokerSle,
VaultEntry<ApplyView>& vaultSle,
Asset const& vaultAsset);
/**
* Helper function that might be needed by other transactors
*/
static TER
impairLoan(
ApplyView& view,
SLE::ref loanSle,
SLE::ref vaultSle,
Asset const& vaultAsset,
beast::Journal j);
LoanEntry<ApplyView>& loanSle,
VaultEntry<ApplyView>& vaultSle,
Asset const& vaultAsset);
/**
* Helper function that might be needed by other transactors
*/
[[nodiscard]] static TER
unimpairLoan(
ApplyView& view,
SLE::ref loanSle,
SLE::ref vaultSle,
Asset const& vaultAsset,
beast::Journal j);
LoanEntry<ApplyView>& loanSle,
VaultEntry<ApplyView>& vaultSle,
Asset const& vaultAsset);
TER
doApply() override;
@@ -3,7 +3,9 @@
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STTx.h>
@@ -21,10 +23,10 @@ private:
pay(AccountID const& from, AccountID const& to, STAmount const& amount);
TER
acceptOffer(SLE::ref offer);
acceptOffer(NFTokenOfferEntry<ApplyView> const& offer);
TER
bridgeOffers(SLE::ref buy, SLE::ref sell);
bridgeOffers(NFTokenOfferEntry<ApplyView> const& buy, NFTokenOfferEntry<ApplyView> const& sell);
TER
transferNFToken(AccountID const& buyer, AccountID const& seller, uint256 const& nfTokenID);
@@ -2,7 +2,9 @@
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STTx.h>
@@ -48,8 +50,8 @@ public:
private:
std::expected<std::pair<STAmount, STAmount>, TER>
assetsToClawback(
SLE::ref vault,
SLE::const_ref sleShareIssuance,
VaultEntry<ApplyView>& vault,
MPTokenIssuanceEntry<ReadView> const& sleShareIssuance,
AccountID const& holder,
STAmount const& clawbackAmount);
};
+16 -27
View File
@@ -12,6 +12,8 @@
#include <xrpl/ledger/Sandbox.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/ledger/helpers/SLEBase.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AMMCore.h>
#include <xrpl/protocol/AccountID.h>
@@ -555,7 +557,7 @@ ammLPHolds(
auto const currency = ammLPTCurrency(asset1, asset2);
STAmount amount;
auto const sle = view.read(keylet::trustLine(lpAccount, ammAccount, currency));
RippleStateEntry<ReadView> const sle{keylet::trustLine(lpAccount, ammAccount, currency), view};
if (!sle)
{
amount.clear(Issue{currency, ammAccount});
@@ -632,7 +634,7 @@ 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));
if (MPTokenEntry<ReadView> const sle{keylet::mptoken(issue, ammAccountID), view};
sle && !isFrozen(view, ammAccountID, issue))
return STAmount{issue, (*sle)[sfMPTAmount]};
return STAmount{asset};
@@ -640,12 +642,12 @@ ammAccountHolds(ReadView const& view, AccountID const& ammAccountID, Asset const
[&](Issue const& issue) {
if (isXRP(issue))
{
if (auto const sle = view.read(keylet::account(ammAccountID)))
if (AccountRootEntry<ReadView> const sle{keylet::account(ammAccountID), view})
return (*sle)[sfBalance];
}
else if (
auto const sle =
view.read(keylet::trustLine(ammAccountID, issue.account, issue.currency));
RippleStateEntry<ReadView> const sle{
keylet::trustLine(ammAccountID, issue.account, issue.currency), view};
sle && !isFrozen(view, ammAccountID, issue.currency, issue.account))
{
STAmount amount = (*sle)[sfBalance];
@@ -746,7 +748,7 @@ deleteAMMMPTokens(Sandbox& sb, AccountID const& ammAccountID, beast::Journal j)
TER
deleteAMMAccount(Sandbox& sb, Asset const& asset, Asset const& asset2, beast::Journal j)
{
auto ammSle = sb.peek(keylet::amm(asset, asset2));
AMMEntry<ApplyView> ammSle{keylet::amm(asset, asset2), sb};
if (!ammSle)
{
// LCOV_EXCL_START
@@ -756,7 +758,7 @@ deleteAMMAccount(Sandbox& sb, Asset const& asset, Asset const& asset2, beast::Jo
}
auto const ammAccountID = (*ammSle)[sfAccount];
auto sleAMMRoot = sb.peek(keylet::account(ammAccountID));
AccountRootEntry<ApplyView> sleAMMRoot{keylet::account(ammAccountID), sb};
if (!sleAMMRoot)
{
// LCOV_EXCL_START
@@ -776,25 +778,12 @@ deleteAMMAccount(Sandbox& sb, Asset const& asset, Asset const& asset2, beast::Jo
if (auto const ter = deleteAMMMPTokens(sb, ammAccountID, j); !isTesSuccess(ter))
return ter;
auto const ownerDirKeylet = keylet::ownerDir(ammAccountID);
if (!sb.dirRemove(ownerDirKeylet, (*ammSle)[sfOwnerNode], ammSle->key(), false))
{
// LCOV_EXCL_START
JLOG(j.error()) << "deleteAMMAccount: failed to remove dir link";
return tecINTERNAL;
// LCOV_EXCL_STOP
}
if (sb.exists(ownerDirKeylet) && !sb.emptyDirDelete(ownerDirKeylet))
{
// LCOV_EXCL_START
JLOG(j.error()) << "deleteAMMAccount: cannot delete root dir node of "
<< toBase58(ammAccountID);
return tecINTERNAL;
// LCOV_EXCL_STOP
}
// Unlink the AMM from its pseudo-account's directory (collapsing the empty
// root) and erase it. See AMMEntry::destroy().
if (auto const ter = ammSle.destroy(); !isTesSuccess(ter))
return ter; // LCOV_EXCL_LINE
sb.erase(ammSle);
sb.erase(sleAMMRoot);
sleAMMRoot.erase();
return tesSUCCESS;
}
@@ -885,12 +874,12 @@ isOnlyLiquidityProvider(ReadView const& view, Issue const& ammIssue, AccountID c
// Iterate over AMM owner directory objects.
while (limit-- >= 1)
{
auto const ownerDir = view.read(currentIndex);
DirectoryNodeEntry<ReadView> const ownerDir{currentIndex, view};
if (!ownerDir)
return std::unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
for (auto const& key : ownerDir->getFieldV256(sfIndexes))
{
auto const sle = view.read(keylet::child(key));
ReadOnlySLE const sle{keylet::child(key), view};
if (!sle)
return std::unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
auto const entryType = sle->getFieldU16(sfLedgerEntryType);
@@ -9,6 +9,7 @@
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/OwnerCounts.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Feature.h>
@@ -39,7 +40,7 @@ isGlobalFrozen(ReadView const& view, AccountID const& issuer)
{
if (isXRP(issuer))
return false;
if (auto const sle = view.read(keylet::account(issuer)))
if (AccountRootEntry<ReadView> const sle{keylet::account(issuer), view})
return sle->isFlag(lsfGlobalFreeze);
return false;
}
@@ -269,17 +270,17 @@ ownerCount(SLE::const_ref sle, beast::Journal j, std::int32_t ownerCountAdj)
XRPAmount
xrpLiquid(ReadView const& view, AccountID const& id, std::int32_t ownerCountAdj, beast::Journal j)
{
auto const sle = view.read(keylet::account(id));
if (sle == nullptr)
AccountRootEntry<ReadView> const sle{keylet::account(id), view};
if (!sle)
return beast::kZero;
// Return balance minus reserve
std::uint32_t const currentOwnerCount =
confineOwnerCount(view.ownerCountHook(id, OwnerCounts(sle)).count(), ownerCountAdj);
std::uint32_t const currentAccountCount = accountCountImpl(sle, 0, j);
confineOwnerCount(view.ownerCountHook(id, OwnerCounts(sle.sle())).count(), ownerCountAdj);
std::uint32_t const currentAccountCount = accountCountImpl(sle.sle(), 0, j);
// Pseudo-accounts have no reserve requirement
auto const reserve = isPseudoAccount(sle)
auto const reserve = isPseudoAccount(sle.sle())
? XRPAmount{0}
: view.fees().accountReserve(currentOwnerCount, currentAccountCount);
@@ -301,7 +302,7 @@ xrpLiquid(ReadView const& view, AccountID const& id, std::int32_t ownerCountAdj,
Rate
transferRate(ReadView const& view, AccountID const& issuer)
{
auto const sle = view.read(keylet::account(issuer));
AccountRootEntry<ReadView> const sle{keylet::account(issuer), view};
if (sle && sle->isFieldPresent(sfTransferRate))
return Rate{sle->getFieldU32(sfTransferRate)};
@@ -7,7 +7,7 @@
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
@@ -53,13 +53,13 @@ removeExpired(ApplyView& view, STVector256 const& arr, beast::Journal const j)
{
// Credentials already checked in preclaim. Look only for expired here.
auto const k = keylet::credential(h);
auto const sleCred = view.peek(k);
CredentialEntry<ApplyView> sleCred{k, view};
if (sleCred && checkExpired(*sleCred, closeTime))
{
JLOG(j.trace()) << "Credentials are expired. Cred: " << sleCred->getText();
// delete expired credentials even if the transaction failed
auto const err = deleteSLE(view, sleCred, j);
auto const err = deleteSLE(view, sleCred.mutableSle(), j);
if (view.rules().enabled(fixCleanup3_1_3) && !isTesSuccess(err))
return std::unexpected(err);
foundExpired = true;
@@ -75,52 +75,26 @@ deleteSLE(ApplyView& view, SLE::ref sleCredential, beast::Journal j)
if (!sleCredential)
return tecNO_ENTRY;
auto delSLE = [&view, &sleCredential, j](
AccountID const& account, SField const& node, bool isOwner) -> TER {
auto const sleAccount = view.peek(keylet::account(account));
if (!sleAccount)
{
// LCOV_EXCL_START
JLOG(j.fatal()) << "Internal error: can't retrieve Owner account.";
return tecINTERNAL;
// LCOV_EXCL_STOP
}
// Remove object from owner directory
std::uint64_t const page = sleCredential->getFieldU64(node);
if (!view.dirRemove(keylet::ownerDir(account), page, sleCredential->key(), false))
{
// LCOV_EXCL_START
JLOG(j.fatal()) << "Unable to delete Credential from owner.";
return tefBAD_LEDGER;
// LCOV_EXCL_STOP
}
if (isOwner)
decreaseOwnerCountForObject(view, sleAccount, sleCredential, 1, j);
return tesSUCCESS;
};
// Historically deleteSLE fetched both the issuer's and (for a third-party
// credential) the subject's account and failed if either was missing, even
// though only one of them is counted against a reserve. Preserve that
// stricter contract: a corrupted view missing one of these accounts must
// report tecINTERNAL rather than silently unlinking.
auto const issuer = sleCredential->getAccountID(sfIssuer);
auto const subject = sleCredential->getAccountID(sfSubject);
bool const accepted = sleCredential->isFlag(lsfAccepted);
auto err = delSLE(issuer, sfIssuerNode, !accepted || (subject == issuer));
if (!isTesSuccess(err))
return err;
if (subject != issuer)
if (!view.exists(keylet::account(issuer)) ||
(subject != issuer && !view.exists(keylet::account(subject))))
{
err = delSLE(subject, sfSubjectNode, accepted);
if (!isTesSuccess(err))
return err;
JLOG(j.fatal()) << "Internal error: can't retrieve Owner account.";
return tecINTERNAL;
}
// Remove object from ledger
view.erase(sleCredential);
return tesSUCCESS;
// Unlink the credential from the issuer's and subject's directories,
// decrementing whichever account currently owns it (the issuer until the
// subject accepts, the subject afterwards), and erase it. See
// CredentialEntry::ownerDirs().
CredentialEntry<ApplyView> cred{sleCredential, view, j};
return cred.destroy();
}
NotTEC
@@ -160,7 +134,7 @@ valid(STTx const& tx, ReadView const& view, AccountID const& src, beast::Journal
auto const& credIDs(tx.getFieldV256(sfCredentialIDs));
for (auto const& h : credIDs)
{
auto const sleCred = view.read(keylet::credential(h));
CredentialEntry<ReadView> const sleCred{keylet::credential(h), view};
if (!sleCred)
{
JLOG(j.trace()) << "Credential doesn't exist. Cred: " << h;
@@ -189,7 +163,7 @@ TER
validDomain(ReadView const& view, uint256 domainID, AccountID const& subject)
{
// Note, permissioned domain objects can be deleted at any time
auto const slePD = view.read(keylet::permissionedDomain(domainID));
PermissionedDomainEntry<ReadView> const slePD{keylet::permissionedDomain(domainID), view};
if (!slePD)
return tecOBJECT_NOT_FOUND;
@@ -200,7 +174,7 @@ validDomain(ReadView const& view, uint256 domainID, AccountID const& subject)
auto const issuer = h.getAccountID(sfIssuer);
auto const type = h.getFieldVL(sfCredentialType);
auto const keyletCredential = keylet::credential(subject, issuer, makeSlice(type));
auto const sleCredential = view.read(keyletCredential);
CredentialEntry<ReadView> const sleCredential{keyletCredential, view};
// We cannot delete expired credentials, that would require ApplyView&
// However we can check if credentials are expired. Expected transaction
@@ -234,14 +208,14 @@ authorizedDepositPreauth(ReadView const& view, STVector256 const& credIDs, Accou
lifeExtender.reserve(credIDs.size());
for (auto const& h : credIDs)
{
auto sleCred = view.read(keylet::credential(h));
CredentialEntry<ReadView> const sleCred{keylet::credential(h), view};
if (!sleCred) // already checked in preclaim
return tefINTERNAL; // LCOV_EXCL_LINE
auto [it, ins] = sorted.emplace((*sleCred)[sfIssuer], (*sleCred)[sfCredentialType]);
if (!ins)
return tefINTERNAL; // LCOV_EXCL_LINE
lifeExtender.push_back(std::move(sleCred));
lifeExtender.push_back(sleCred.sle());
}
if (!view.exists(keylet::depositPreauth(dst, sorted)))
@@ -312,7 +286,7 @@ checkArray(STArray const& credentials, unsigned maxSize, beast::Journal j)
TER
verifyValidDomain(ApplyView& view, AccountID const& account, uint256 domainID, beast::Journal j)
{
auto const slePD = view.read(keylet::permissionedDomain(domainID));
PermissionedDomainEntry<ReadView> const slePD{keylet::permissionedDomain(domainID), view};
if (!slePD)
return tecOBJECT_NOT_FOUND;
@@ -334,7 +308,7 @@ verifyValidDomain(ApplyView& view, AccountID const& account, uint256 domainID, b
for (auto const& h : credentials)
{
auto sleCredential = view.read(keylet::credential(h));
CredentialEntry<ReadView> const sleCredential{keylet::credential(h), view};
if (!sleCredential)
continue; // expired, i.e. deleted in credentials::removeExpired
+13 -10
View File
@@ -9,6 +9,7 @@
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/LedgerFormats.h>
@@ -32,13 +33,14 @@ namespace xrpl {
[[nodiscard]] TER
canApplyToBrokerCover(
ReadView const& view,
SLE::const_ref sleBroker,
LoanBrokerEntry<ReadView> const& sleBroker,
Asset const& vaultAsset,
STAmount const& amount,
beast::Journal j,
std::string_view logPrefix)
{
ReadView const& view = sleBroker.readView();
beast::Journal const j = sleBroker.journal();
XRPL_ASSERT(
sleBroker && sleBroker->getType() == ltLOAN_BROKER,
"xrpl::canApplyToBrokerCover : valid LoanBroker sle");
@@ -1632,7 +1634,7 @@ constructLoanState(
}
LoanState
constructRoundedLoanState(SLE::const_ref loan)
constructRoundedLoanState(LoanEntry<ReadView> const& loan)
{
return constructLoanState(
loan->at(sfTotalValueOutstanding),
@@ -1783,15 +1785,16 @@ computeLoanProperties(
std::expected<LoanPaymentParts, TER>
loanMakePayment(
Asset const& asset,
ApplyView& view,
SLE::ref loan,
SLE::const_ref brokerSle,
LoanEntry<ApplyView>& loan,
LoanBrokerEntry<ReadView> const& brokerSle,
STAmount const& amount,
LoanPaymentType const paymentType,
beast::Journal j)
LoanPaymentType const paymentType)
{
using namespace Lending;
ApplyView& view = loan.applyView();
beast::Journal const j = loan.journal();
auto principalOutstandingProxy = loan->at(sfPrincipalOutstanding);
auto paymentRemainingProxy = loan->at(sfPaymentRemaining);
@@ -1836,7 +1839,7 @@ loanMakePayment(
XRPL_ASSERT(*totalValueOutstandingProxy > 0, "xrpl::loanMakePayment : valid total value");
view.update(loan);
loan.update();
// -------------------------------------------------------------
// A late payment not flagged as late overrides all other options.
+70 -87
View File
@@ -10,6 +10,8 @@
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/CredentialHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/SLEBase.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
@@ -41,7 +43,8 @@ namespace xrpl {
bool
isGlobalFrozen(ReadView const& view, MPTIssue const& mptIssue)
{
if (auto const sle = view.read(keylet::mptokenIssuance(mptIssue.getMptID())))
if (MPTokenIssuanceEntry<ReadView> const sle{
keylet::mptokenIssuance(mptIssue.getMptID()), view})
return sle->isFlag(lsfMPTLocked);
return false;
}
@@ -49,7 +52,7 @@ isGlobalFrozen(ReadView const& view, MPTIssue const& mptIssue)
bool
isIndividualFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue)
{
if (auto const sle = view.read(keylet::mptoken(mptIssue.getMptID(), account)))
if (MPTokenEntry<ReadView> const sle{keylet::mptoken(mptIssue.getMptID(), account), view})
return sle->isFlag(lsfMPTLocked);
return false;
}
@@ -92,7 +95,7 @@ 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));
if (MPTokenIssuanceEntry<ReadView> const sle{keylet::mptokenIssuance(issuanceID), view};
sle && sle->isFieldPresent(sfTransferFee))
{
auto const fee = sle->getFieldU16(sfTransferFee);
@@ -107,7 +110,7 @@ transferRate(ReadView const& view, MPTID const& issuanceID)
canAddHolding(ReadView const& view, MPTIssue const& mptIssue)
{
auto mptID = mptIssue.getMptID();
auto issuance = view.read(keylet::mptokenIssuance(mptID));
MPTokenIssuanceEntry<ReadView> const issuance{keylet::mptokenIssuance(mptID), view};
if (!issuance)
{
return tecOBJECT_NOT_FOUND;
@@ -129,7 +132,7 @@ addEmptyHolding(
beast::Journal journal)
{
auto const& mptID = mptIssue.getMptID();
auto const mpt = ctx.view.peek(keylet::mptokenIssuance(mptID));
MPTokenIssuanceEntry<ApplyView> const mpt{keylet::mptokenIssuance(mptID), ctx.view};
if (!mpt)
return tefINTERNAL; // LCOV_EXCL_LINE
if (mpt->isFlag(lsfMPTLocked))
@@ -152,7 +155,7 @@ authorizeMPToken(
std::uint32_t flags,
std::optional<AccountID> holderID)
{
auto const sleAcct = ctx.view.peek(keylet::account(account));
AccountRootEntry<ApplyView> const sleAcct{keylet::account(account), ctx.view};
if (!sleAcct)
return tecINTERNAL; // LCOV_EXCL_LINE
@@ -167,50 +170,28 @@ authorizeMPToken(
if ((flags & tfMPTUnauthorize) != 0u)
{
auto const mptokenKey = keylet::mptoken(mptIssuanceID, account);
auto const sleMpt = ctx.view.peek(mptokenKey);
MPTokenEntry<ApplyView> sleMpt{mptokenKey, ctx.view, journal};
if (!sleMpt || (*sleMpt)[sfMPTAmount] != 0 ||
(ctx.view.rules().enabled(fixCleanup3_1_3) &&
(*sleMpt)[~sfLockedAmount].valueOr(0) != 0))
return tecINTERNAL; // LCOV_EXCL_LINE
if (!ctx.view.dirRemove(
keylet::ownerDir(account), (*sleMpt)[sfOwnerNode], sleMpt->key(), false))
return tecINTERNAL; // LCOV_EXCL_LINE
decreaseOwnerCountForObject(ctx.view, sleAcct, sleMpt, 1, journal);
ctx.view.erase(sleMpt);
return tesSUCCESS;
// Unlink from the holder's owner directory, decrement its
// OwnerCount (refunding a reserve sponsor if present), and erase.
// See MPTokenEntry.
return sleMpt.destroy();
}
// A potential holder wants to authorize/hold a mpt, the ledger must:
// - add the new mptokenKey to the owner directory
// - create the MPToken object for the holder
// A reserve sponsor only covers tx.Account's own objects.
auto const sponsorExp = getEffectiveTxReserveSponsor(ctx, sleAcct);
if (!sponsorExp)
return sponsorExp.error(); // LCOV_EXCL_LINE
auto const sponsorSle = *sponsorExp;
// The reserve that is required to create the MPToken. Note
// that although the reserve increases with every item
// an account owns, in the case of MPTokens we only
// *enforce* a reserve if the user owns more than two
// items. This is similar to the reserve requirements of trust lines.
// The "free-tier" shortcut (ownerCount < 2) does not apply once a sponsor is on
// the tx — the sponsor must always cover the reserve (via balance or prefunded
// budget), so this check always runs for sponsored transactions.
if (sponsorSle || ownerCount(sleAcct, journal) >= 2)
{
if (auto const ret = checkReserve(
ctx, sleAcct, priorBalance, sponsorSle, {.ownerCountDelta = 1}, journal);
!isTesSuccess(ret))
return ret;
}
// A potential holder wants to authorize/hold a mpt. The trust-line-style
// free-tier reserve rule and reserve-sponsorship accounting are applied
// by MPTokenEntry::create() below.
// Defensive check before we attempt to create MPToken for the issuer
auto const mpt = ctx.view.read(keylet::mptokenIssuance(mptIssuanceID));
MPTokenIssuanceEntry<ReadView> const mpt{keylet::mptokenIssuance(mptIssuanceID), ctx.view};
if (!mpt || mpt->getAccountID(sfIssuer) == account)
{
// LCOV_EXCL_START
@@ -221,23 +202,22 @@ authorizeMPToken(
}
auto const mptokenKey = keylet::mptoken(mptIssuanceID, account);
auto mptoken = std::make_shared<SLE>(mptokenKey);
if (auto ter = dirLink(ctx.view, account, mptoken))
return ter; // LCOV_EXCL_LINE
// Build with the ApplyViewContext so create() can apply reserve
// sponsorship (reserve check, OwnerCount, and sponsor stamp).
MPTokenEntry<ApplyView> mptoken{mptokenKey, ctx, journal};
mptoken.newSLE();
(*mptoken)[sfAccount] = account;
(*mptoken)[sfMPTokenIssuanceID] = mptIssuanceID;
(*mptoken)[sfFlags] = 0;
ctx.view.insert(mptoken);
// Update owner count.
increaseOwnerCount(ctx.view, sleAcct, sponsorSle, 1, journal);
addSponsorToLedgerEntry(mptoken, sponsorSle);
return tesSUCCESS;
// Trust-line-style free-tier reserve check + link into the holder's
// owner directory + bump the holder's OwnerCount + stamp any reserve
// sponsor + insert. See MPTokenEntry::create().
return mptoken.create(priorBalance);
}
auto const sleMptIssuance = ctx.view.read(keylet::mptokenIssuance(mptIssuanceID));
MPTokenIssuanceEntry<ReadView> const sleMptIssuance{
keylet::mptokenIssuance(mptIssuanceID), ctx.view};
if (!sleMptIssuance)
return tecINTERNAL; // LCOV_EXCL_LINE
@@ -247,7 +227,7 @@ authorizeMPToken(
if (account != (*sleMptIssuance)[sfIssuer])
return tecINTERNAL; // LCOV_EXCL_LINE
auto const sleMpt = ctx.view.peek(keylet::mptoken(mptIssuanceID, *holderID));
MPTokenEntry<ApplyView> sleMpt{keylet::mptoken(mptIssuanceID, *holderID), ctx.view};
if (!sleMpt)
return tecINTERNAL; // LCOV_EXCL_LINE
@@ -270,7 +250,7 @@ authorizeMPToken(
if (flagsIn != flagsOut)
sleMpt->setFieldU32(sfFlags, flagsOut);
ctx.view.update(sleMpt);
sleMpt.update();
return tesSUCCESS;
}
@@ -286,7 +266,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));
MPTokenEntry<ApplyView> mptoken{keylet::mptoken(mptID, accountID), ctx.view};
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
@@ -337,7 +317,7 @@ requireAuth(
};
auto const mptID = keylet::mptokenIssuance(mptIssue.getMptID());
auto const sleIssuance = view.read(mptID);
MPTokenIssuanceEntry<ReadView> const sleIssuance{mptID, view};
if (!sleIssuance)
return tecOBJECT_NOT_FOUND;
@@ -362,13 +342,14 @@ requireAuth(
}
// requireAuth is recursive if the issuer is a vault pseudo-account
auto const sleIssuer = view.read(keylet::account(mptIssuer));
AccountRootEntry<ReadView> const sleIssuer{keylet::account(mptIssuer), view};
if (!sleIssuer)
return tefINTERNAL; // LCOV_EXCL_LINE
if (sleIssuer->isFieldPresent(sfVaultID))
{
auto const sleVault = view.read(keylet::vault(sleIssuer->getFieldH256(sfVaultID)));
VaultEntry<ReadView> const sleVault{
keylet::vault(sleIssuer->getFieldH256(sfVaultID)), view};
if (!sleVault)
return tefINTERNAL; // LCOV_EXCL_LINE
@@ -384,7 +365,7 @@ requireAuth(
}
auto const mptokenID = keylet::mptoken(mptID.key, account);
auto const sleToken = view.read(mptokenID);
MPTokenEntry<ReadView> const sleToken{mptokenID, view};
// if account has no MPToken, fail
if (!sleToken && (authType == AuthType::StrongAuth || authType == AuthType::Legacy))
@@ -432,7 +413,8 @@ enforceMPTokenAuthorization(
XRPAmount const& priorBalance, // for MPToken authorization
beast::Journal j)
{
auto const sleIssuance = ctx.view.read(keylet::mptokenIssuance(mptIssuanceID));
MPTokenIssuanceEntry<ReadView> const sleIssuance{
keylet::mptokenIssuance(mptIssuanceID), ctx.view};
if (!sleIssuance)
return tefINTERNAL; // LCOV_EXCL_LINE
@@ -444,7 +426,7 @@ enforceMPTokenAuthorization(
return tefINTERNAL; // LCOV_EXCL_LINE
auto const keylet = keylet::mptoken(mptIssuanceID, account);
auto const sleToken = ctx.view.read(keylet); // NOTE: might be null
MPTokenEntry<ReadView> const sleToken{keylet, ctx.view}; // NOTE: might be null
auto const maybeDomainID = sleIssuance->at(~sfDomainID);
bool expired = false;
bool const authorizedByDomain = [&]() -> bool {
@@ -460,7 +442,7 @@ enforceMPTokenAuthorization(
return false;
}();
if (!authorizedByDomain && sleToken == nullptr)
if (!authorizedByDomain && !sleToken)
{
// Could not find MPToken and won't create one, could be either of:
//
@@ -483,14 +465,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 && !maybeDomainID.has_value(),
"xrpl::enforceMPTokenAuthorization : found MPToken");
if (sleToken->isFlag(lsfMPTAuthorized))
return tesSUCCESS;
return tecNO_AUTH;
}
if (authorizedByDomain && sleToken != nullptr)
if (authorizedByDomain && sleToken)
{
// Found an MPToken, authorized by the domain. Ignore authorization flag
// lsfMPTAuthorized because it is meaningless. Return tesSUCCESS
@@ -504,7 +486,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,
"xrpl::enforceMPTokenAuthorization : new MPToken for domain");
if (auto const err = authorizeMPToken(
ctx,
@@ -557,7 +539,7 @@ canTransfer(
std::uint8_t depth)
{
auto const mptID = keylet::mptokenIssuance(mptIssue.getMptID());
auto const sleIssuance = view.read(mptID);
MPTokenIssuanceEntry<ReadView> const sleIssuance{mptID, view};
if (!sleIssuance)
return tecOBJECT_NOT_FOUND;
@@ -587,8 +569,8 @@ canTransfer(
// LCOV_EXCL_STOP
}
auto const sleHolding =
view.read(keylet::unchecked(sleIssuance->getFieldH256(sfReferenceHolding)));
ReadOnlySLE const sleHolding{
keylet::unchecked(sleIssuance->getFieldH256(sfReferenceHolding)), view};
if (!sleHolding)
return tefINTERNAL; // LCOV_EXCL_LINE
@@ -610,7 +592,8 @@ 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()));
MPTokenIssuanceEntry<ReadView> const sleIssuance{
keylet::mptokenIssuance(mptIssue.getMptID()), view};
if (!sleIssuance)
return tecOBJECT_NOT_FOUND;
if (!sleIssuance->isFlag(lsfMPTCanTrade))
@@ -632,8 +615,8 @@ canTrade(ReadView const& view, Asset const& asset, std::uint8_t depth)
return tecINTERNAL;
// LCOV_EXCL_STOP
}
auto const sleHolding =
view.read(keylet::unchecked(sleIssuance->getFieldH256(sfReferenceHolding)));
ReadOnlySLE const sleHolding{
keylet::unchecked(sleIssuance->getFieldH256(sfReferenceHolding)), view};
if (!sleHolding)
return tefINTERNAL; // LCOV_EXCL_LINE
@@ -665,7 +648,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);
MPTokenIssuanceEntry<ApplyView> sleIssuance{mptID, view};
if (!sleIssuance)
{ // LCOV_EXCL_START
JLOG(j.error()) << "lockEscrowMPT: MPT issuance not found for " << mptIssue.getMptID();
@@ -682,7 +665,7 @@ lockEscrowMPT(ApplyView& view, AccountID const& sender, STAmount const& amount,
// 2. Increase the MPT Holder EscrowedAmount
{
auto const mptokenID = keylet::mptoken(mptID.key, sender);
auto sle = view.peek(mptokenID);
MPTokenEntry<ApplyView> sle{mptokenID, view};
if (!sle)
{ // LCOV_EXCL_START
JLOG(j.error()) << "lockEscrowMPT: MPToken not found for " << sender;
@@ -721,7 +704,7 @@ lockEscrowMPT(ApplyView& view, AccountID const& sender, STAmount const& amount,
sle->setFieldU64(sfLockedAmount, pay);
}
view.update(sle);
sle.update();
}
// 1. Increase the Issuance EscrowedAmount
@@ -748,7 +731,7 @@ lockEscrowMPT(ApplyView& view, AccountID const& sender, STAmount const& amount,
sleIssuance->setFieldU64(sfLockedAmount, pay);
}
view.update(sleIssuance);
sleIssuance.update();
}
return tesSUCCESS;
}
@@ -770,7 +753,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);
MPTokenIssuanceEntry<ApplyView> sleIssuance{mptID, view};
if (!sleIssuance)
{ // LCOV_EXCL_START
JLOG(j.error()) << "unlockEscrowMPT: MPT issuance not found for " << mptIssue.getMptID();
@@ -806,14 +789,14 @@ unlockEscrowMPT(
{
sleIssuance->setFieldU64(sfLockedAmount, newLocked);
}
view.update(sleIssuance);
sleIssuance.update();
}
if (issuer != receiver)
{
// Increase the MPT Holder MPTAmount
auto const mptokenID = keylet::mptoken(mptID.key, receiver);
auto sle = view.peek(mptokenID);
MPTokenEntry<ApplyView> sle{mptokenID, view};
if (!sle)
{ // LCOV_EXCL_START
JLOG(j.error()) << "unlockEscrowMPT: MPToken not found for " << receiver;
@@ -832,7 +815,7 @@ unlockEscrowMPT(
} // LCOV_EXCL_STOP
(*sle)[sfMPTAmount] += delta;
view.update(sle);
sle.update();
}
else
{
@@ -849,7 +832,7 @@ unlockEscrowMPT(
} // LCOV_EXCL_STOP
sleIssuance->setFieldU64(sfOutstandingAmount, outstanding - redeem);
view.update(sleIssuance);
sleIssuance.update();
}
if (issuer == sender)
@@ -860,7 +843,7 @@ unlockEscrowMPT(
} // LCOV_EXCL_STOP
// Decrease the MPT Holder EscrowedAmount
auto const mptokenID = keylet::mptoken(mptID.key, sender);
auto sle = view.peek(mptokenID);
MPTokenEntry<ApplyView> sle{mptokenID, view};
if (!sle)
{ // LCOV_EXCL_START
JLOG(j.error()) << "unlockEscrowMPT: MPToken not found for " << sender;
@@ -893,7 +876,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
@@ -912,7 +895,7 @@ unlockEscrowMPT(
} // LCOV_EXCL_STOP
sleIssuance->setFieldU64(sfOutstandingAmount, outstanding - diff);
view.update(sleIssuance);
sleIssuance.update();
}
return tesSUCCESS;
}
@@ -933,15 +916,16 @@ createMPToken(
if (!ownerNode)
return tecDIR_FULL; // LCOV_EXCL_LINE
auto mptoken = std::make_shared<SLE>(mptokenKey);
MPTokenEntry<ApplyView> mptoken{mptokenKey, view};
mptoken.newSLE();
(*mptoken)[sfAccount] = account;
(*mptoken)[sfMPTokenIssuanceID] = mptIssuanceID;
(*mptoken)[sfFlags] = flags;
(*mptoken)[sfOwnerNode] = *ownerNode;
addSponsorToLedgerEntry(mptoken, sponsorSle);
addSponsorToLedgerEntry(mptoken.mutableSle(), sponsorSle);
view.insert(mptoken);
mptoken.insert();
return tesSUCCESS;
}
@@ -966,13 +950,12 @@ checkCreateMPT(
{
return err;
}
auto const sleAcct = view.peek(keylet::account(holder));
AccountRootEntry<ApplyView> const sleAcct{keylet::account(holder), view};
if (!sleAcct)
{
return tecINTERNAL;
}
increaseOwnerCount(view, sleAcct, sponsorSle, 1, j);
increaseOwnerCount(view, sleAcct.mutableSle(), sponsorSle, 1, j);
}
return tesSUCCESS;
}
@@ -994,7 +977,7 @@ availableMPTAmount(SLE const& sleIssuance)
std::int64_t
availableMPTAmount(ReadView const& view, MPTID const& mptID)
{
auto const sle = view.read(keylet::mptokenIssuance(mptID));
MPTokenIssuanceEntry<ReadView> const sle{keylet::mptokenIssuance(mptID), view};
if (!sle)
Throw<std::runtime_error>(transHuman(tecINTERNAL));
return availableMPTAmount(*sle);
@@ -1018,7 +1001,7 @@ issuerFundsToSelfIssue(ReadView const& view, MPTIssue const& issue)
{
STAmount amount{issue};
auto const sle = view.read(keylet::mptokenIssuance(issue));
MPTokenIssuanceEntry<ReadView> const sle{keylet::mptokenIssuance(issue), view};
if (!sle)
return amount;
auto const available = availableMPTAmount(*sle);
+67 -109
View File
@@ -8,8 +8,8 @@
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Feature.h>
@@ -182,7 +182,8 @@ getPageForToken(
? narr[kDirMaxTokensPerPage - 1].getFieldH256(sfNFTokenID).next()
: carr[0].getFieldH256(sfNFTokenID);
auto np = std::make_shared<SLE>(keylet::nftokenPage(base, tokenIDForNewPage));
NFTokenPageEntry<ApplyView> np{base, tokenIDForNewPage, view};
np.newSLE();
XRPL_ASSERT(np->key() > base.key, "xrpl::nft::getPageForToken : valid NFT page index");
np->setFieldArray(sfNFTokens, narr);
np->setFieldH256(sfNextPageMin, cp->key());
@@ -191,14 +192,14 @@ getPageForToken(
{
np->setFieldH256(sfPreviousPageMin, *ppm);
if (auto p3 = view.peek(Keylet(ltNFTOKEN_PAGE, *ppm)))
if (NFTokenPageEntry<ApplyView> p3{Keylet(ltNFTOKEN_PAGE, *ppm), view})
{
p3->setFieldH256(sfNextPageMin, np->key());
view.update(p3);
p3.update();
}
}
view.insert(np);
np.insert();
cp->setFieldArray(sfNFTokens, carr);
cp->setFieldH256(sfPreviousPageMin, np->key());
@@ -206,7 +207,7 @@ getPageForToken(
createCallback(view, owner);
return (first.key < np->key()) ? np : cp;
return (first.key < np->key()) ? np.mutableSle() : cp;
}
bool
@@ -230,7 +231,7 @@ changeTokenURI(
uint256 const& nftokenID,
std::optional<xrpl::Slice> const& uri)
{
SLE::pointer const page = locatePage(view, owner, nftokenID);
NFTokenPageEntry<ApplyView> page{locatePage(view, owner, nftokenID), view};
// If the page couldn't be found, the given NFT isn't owned by this account
if (!page)
@@ -254,7 +255,7 @@ changeTokenURI(
nftIter->makeFieldAbsent(sfURI);
}
view.update(page);
page.update();
return tesSUCCESS;
}
@@ -269,10 +270,16 @@ insertToken(ApplyView& view, AccountID owner, STObject&& nft)
// First, we need to locate the page the NFT belongs to, creating it
// if necessary. This operation may fail if it is impossible to insert
// the NFT.
SLE::pointer const page =
getPageForToken(view, owner, nft[sfNFTokenID], [](ApplyView& view, AccountID const& owner) {
increaseOwnerCount(view, owner, {}, 1, beast::Journal{beast::Journal::getNullSink()});
});
NFTokenPageEntry<ApplyView> page{
getPageForToken(
view,
owner,
nft[sfNFTokenID],
[](ApplyView& view, AccountID const& owner) {
increaseOwnerCount(
view, owner, {}, 1, beast::Journal{beast::Journal::getNullSink()});
}),
view};
if (!page)
return tecNO_SUITABLE_NFTOKEN_PAGE;
@@ -288,7 +295,7 @@ insertToken(ApplyView& view, AccountID owner, STObject&& nft)
page->setFieldArray(sfNFTokens, arr);
}
view.update(page);
page.update();
return tesSUCCESS;
}
@@ -332,13 +339,13 @@ mergePages(ApplyView& view, SLE::ref p1, SLE::ref p2)
if (auto const ppm = (*p1)[~sfPreviousPageMin])
{
auto p0 = view.peek(Keylet(ltNFTOKEN_PAGE, *ppm));
NFTokenPageEntry<ApplyView> p0{Keylet(ltNFTOKEN_PAGE, *ppm), view};
if (!p0)
Throw<std::runtime_error>("mergePages: p0 can't be located!");
p0->setFieldH256(sfNextPageMin, p2->key());
view.update(p0);
p0.update();
p2->setFieldH256(sfPreviousPageMin, *ppm);
}
@@ -355,13 +362,13 @@ mergePages(ApplyView& view, SLE::ref p1, SLE::ref p2)
TER
removeToken(ApplyView& view, AccountID const& owner, uint256 const& nftokenID)
{
SLE::pointer const page = locatePage(view, owner, nftokenID);
NFTokenPageEntry<ApplyView> const page{locatePage(view, owner, nftokenID), view};
// If the page couldn't be found, the given NFT isn't owned by this account
if (!page)
return tecNO_ENTRY;
return removeToken(view, owner, nftokenID, page);
return removeToken(view, owner, nftokenID, page.mutableSle());
}
/**
@@ -402,8 +409,8 @@ removeToken(ApplyView& view, AccountID const& owner, uint256 const& nftokenID, S
return page2;
};
auto const prev = loadPage(curr, sfPreviousPageMin);
auto const next = loadPage(curr, sfNextPageMin);
NFTokenPageEntry<ApplyView> prev{loadPage(curr, sfPreviousPageMin), view};
NFTokenPageEntry<ApplyView> next{loadPage(curr, sfNextPageMin), view};
if (!arr.empty())
{
@@ -415,10 +422,10 @@ removeToken(ApplyView& view, AccountID const& owner, uint256 const& nftokenID, S
std::uint32_t cnt = 0;
if (prev && mergePages(view, prev, curr))
if (prev && mergePages(view, prev.mutableSle(), curr))
++cnt;
if (next && mergePages(view, curr, next))
if (next && mergePages(view, curr, next.mutableSle()))
++cnt;
if (cnt != 0)
@@ -449,9 +456,9 @@ removeToken(ApplyView& view, AccountID const& owner, uint256 const& nftokenID, S
curr->at(sfPreviousPageMin) = *prevLink;
// Also fix up the NextPageMin link in the new Previous.
auto const newPrev = loadPage(curr, sfPreviousPageMin);
NFTokenPageEntry<ApplyView> newPrev{loadPage(curr, sfPreviousPageMin), view};
newPrev->at(sfNextPageMin) = curr->key();
view.update(newPrev);
newPrev.update();
}
else
{
@@ -461,7 +468,7 @@ removeToken(ApplyView& view, AccountID const& owner, uint256 const& nftokenID, S
decreaseOwnerCount(view, owner, {}, 1, beast::Journal{beast::Journal::getNullSink()});
view.update(curr);
view.erase(prev);
prev.erase();
return tesSUCCESS;
}
@@ -476,7 +483,7 @@ removeToken(ApplyView& view, AccountID const& owner, uint256 const& nftokenID, S
prev->makeFieldAbsent(sfNextPageMin);
}
view.update(prev);
prev.update();
}
if (next)
@@ -491,7 +498,7 @@ removeToken(ApplyView& view, AccountID const& owner, uint256 const& nftokenID, S
next->makeFieldAbsent(sfPreviousPageMin);
}
view.update(next);
next.update();
}
view.erase(curr);
@@ -521,7 +528,7 @@ removeToken(ApplyView& view, AccountID const& owner, uint256 const& nftokenID, S
std::optional<STObject>
findToken(ReadView const& view, AccountID const& owner, uint256 const& nftokenID)
{
SLE::const_pointer const page = locatePage(view, owner, nftokenID);
NFTokenPageEntry<ReadView> const page{locatePage(view, owner, nftokenID), view};
// If the page couldn't be found, the given NFT isn't owned by this account
if (!page)
@@ -569,7 +576,7 @@ removeTokenOffersWithLimit(ApplyView& view, Keylet const& directory, std::size_t
do
{
auto const page = view.peek(keylet::page(directory, *pageIndex));
DirectoryNodeEntry<ApplyView> const page{keylet::page(directory, *pageIndex), view};
if (!page)
break;
@@ -587,9 +594,10 @@ removeTokenOffersWithLimit(ApplyView& view, Keylet const& directory, std::size_t
// deleting during iteration.
for (int i = offerIndexes.size() - 1; i >= 0; --i)
{
if (auto const offer = view.peek(keylet::nftokenOffer(offerIndexes[i])))
if (NFTokenOfferEntry<ApplyView> const offer{
keylet::nftokenOffer(offerIndexes[i]), view})
{
if (deleteTokenOffer(view, offer))
if (deleteTokenOffer(view, offer.mutableSle()))
{
++deletedOffersCount;
}
@@ -614,25 +622,11 @@ deleteTokenOffer(ApplyView& view, SLE::ref offer)
if (offer->getType() != ltNFTOKEN_OFFER)
return false;
auto const owner = (*offer)[sfOwner];
if (!view.dirRemove(keylet::ownerDir(owner), (*offer)[sfOwnerNode], offer->key(), false))
return false;
auto const nftokenID = (*offer)[sfNFTokenID];
if (!view.dirRemove(
offer->isFlag(lsfSellNFToken) ? keylet::nftSells(nftokenID)
: keylet::nftBuys(nftokenID),
(*offer)[sfNFTokenOfferNode],
offer->key(),
false))
return false;
decreaseOwnerCount(view, owner, {}, 1, beast::Journal{beast::Journal::getNullSink()});
view.erase(offer);
return true;
// Unlink from the owner directory and the token's buy/sell offer directory,
// decrement the owner's OwnerCount, and erase. See
// NFTokenOfferEntry::destroy().
NFTokenOfferEntry<ApplyView> offerEntry{offer, view};
return isTesSuccess(offerEntry.destroy());
}
bool
@@ -729,7 +723,7 @@ repairNFTokenDirectoryLinks(ApplyView& view, AccountID const& owner)
nextPage->at(sfPreviousPageMin) = *prevLink;
// Also fix up the NextPageMin link in the new Previous.
auto const newPrev = view.peek(Keylet(ltNFTOKEN_PAGE, *prevLink));
NFTokenPageEntry<ApplyView> newPrev{Keylet(ltNFTOKEN_PAGE, *prevLink), view};
if (!newPrev)
{
// LCOV_EXCL_START
@@ -739,7 +733,7 @@ repairNFTokenDirectoryLinks(ApplyView& view, AccountID const& owner)
// LCOV_EXCL_STOP
}
newPrev->at(sfNextPageMin) = nextPage->key();
view.update(newPrev);
newPrev.update();
}
view.erase(page);
view.insert(nextPage);
@@ -844,7 +838,7 @@ tokenOfferCreatePreclaim(
if (nftIssuer != acctID && ((nftFlags & nft::kFlagTransferable) == 0))
{
auto const root = view.read(keylet::account(nftIssuer));
AccountRootEntry<ReadView> const root{keylet::account(nftIssuer), view};
XRPL_ASSERT(root, "xrpl::nft::tokenOfferCreatePreclaim : non-null account");
if (auto minter = (*root)[~sfNFTokenMinter]; minter != acctID)
@@ -869,7 +863,7 @@ tokenOfferCreatePreclaim(
{
// If a destination is specified, the destination must already be in
// the ledger.
auto const sleDst = view.read(keylet::account(*dest));
AccountRootEntry<ReadView> const sleDst{keylet::account(*dest), view};
if (!sleDst)
return tecNO_DST;
@@ -881,7 +875,7 @@ tokenOfferCreatePreclaim(
if (owner)
{
auto const sleOwner = view.read(keylet::account(*owner));
AccountRootEntry<ReadView> const sleOwner{keylet::account(*owner), view};
// defensively check
// it should not be possible to specify owner that doesn't exist
@@ -920,63 +914,27 @@ tokenOfferCreateApply(
beast::Journal j,
std::uint32_t txFlags)
{
Keylet const acctKeylet = keylet::account(acctID);
if (auto const acct = view.read(acctKeylet);
priorBalance < accountReserve(view, acct, j, {.ownerCountDelta = 1}))
return tecINSUFFICIENT_RESERVE;
auto const offerID = keylet::nftokenOffer(acctID, seqProxy.value());
// Create the offer:
{
// Token offers are always added to the owner's owner directory:
auto const ownerNode =
view.dirInsert(keylet::ownerDir(acctID), offerID, describeOwnerDir(acctID));
bool const isSellOffer = (txFlags & tfSellNFToken) != 0u;
if (!ownerNode)
return tecDIR_FULL; // LCOV_EXCL_LINE
NFTokenOfferEntry<ApplyView> offer{offerID, view, j};
offer.newSLE();
(*offer)[sfOwner] = acctID;
(*offer)[sfNFTokenID] = nftokenID;
(*offer)[sfAmount] = amount;
(*offer)[sfFlags] = isSellOffer ? lsfSellNFToken : 0u;
bool const isSellOffer = (txFlags & tfSellNFToken) != 0u;
if (expiration)
(*offer)[sfExpiration] = *expiration;
// Token offers are also added to the token's buy or sell offer
// directory
auto const offerNode = view.dirInsert(
isSellOffer ? keylet::nftSells(nftokenID) : keylet::nftBuys(nftokenID),
offerID,
[&nftokenID, isSellOffer](SLE::ref sle) {
(*sle)[sfFlags] = isSellOffer ? lsfNFTokenSellOffers : lsfNFTokenBuyOffers;
(*sle)[sfNFTokenID] = nftokenID;
});
if (dest)
(*offer)[sfDestination] = *dest;
if (!offerNode)
return tecDIR_FULL; // LCOV_EXCL_LINE
std::uint32_t sleFlags = 0;
if (isSellOffer)
sleFlags |= lsfSellNFToken;
auto offer = std::make_shared<SLE>(offerID);
(*offer)[sfOwner] = acctID;
(*offer)[sfNFTokenID] = nftokenID;
(*offer)[sfAmount] = amount;
(*offer)[sfFlags] = sleFlags;
(*offer)[sfOwnerNode] = *ownerNode;
(*offer)[sfNFTokenOfferNode] = *offerNode;
if (expiration)
(*offer)[sfExpiration] = *expiration;
if (dest)
(*offer)[sfDestination] = *dest;
view.insert(offer);
}
// Update owner count.
increaseOwnerCount(view, acctID, {}, 1, j);
return tesSUCCESS;
// Reserve check + link into the owner directory and the token's buy/sell
// offer directory + bump the owner's OwnerCount + insert. See
// NFTokenOfferEntry::create().
return offer.create(priorBalance);
}
TER
@@ -991,7 +949,7 @@ checkTrustlineAuthorized(
if (view.rules().enabled(fixEnforceNFTokenTrustlineV2))
{
auto const issuerAccount = view.read(keylet::account(issue.account));
AccountRootEntry<ReadView> const issuerAccount{keylet::account(issue.account), view};
if (!issuerAccount)
{
JLOG(j.debug()) << "xrpl::nft::checkTrustlineAuthorized: can't "
@@ -1011,7 +969,7 @@ checkTrustlineAuthorized(
if (issuerAccount->isFlag(lsfRequireAuth))
{
auto const trustLine = view.read(keylet::trustLine(id, issue.account, issue.currency));
RippleStateEntry<ReadView> const trustLine{id, issue.account, issue.currency, view};
if (!trustLine)
{
@@ -1043,7 +1001,7 @@ checkTrustlineDeepFrozen(
if (view.rules().enabled(featureDeepFreeze))
{
auto const issuerAccount = view.read(keylet::account(issue.account));
AccountRootEntry<ReadView> const issuerAccount{keylet::account(issue.account), view};
if (!issuerAccount)
{
JLOG(j.debug()) << "xrpl::nft::checkTrustlineDeepFrozen: can't "
@@ -1061,7 +1019,7 @@ checkTrustlineDeepFrozen(
return tesSUCCESS;
}
auto const trustLine = view.read(keylet::trustLine(id, issue.account, issue.currency));
RippleStateEntry<ReadView> const trustLine{id, issue.account, issue.currency, view};
if (!trustLine)
{
+8 -46
View File
@@ -1,14 +1,10 @@
#include <xrpl/ledger/helpers/OfferHelpers.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/protocol/LedgerFormats.h> // IWYU pragma: keep
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STArray.h> // IWYU pragma: keep
#include <xrpl/protocol/STArray.h> // IWYU pragma: keep
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/TER.h>
@@ -19,47 +15,13 @@ offerDelete(ApplyView& view, SLE::ref sle, beast::Journal j)
{
if (!sle)
return tesSUCCESS;
auto offerIndex = sle->key();
auto owner = sle->getAccountID(sfAccount);
// Detect legacy directories.
uint256 const uDirectory = sle->getFieldH256(sfBookDirectory);
if (!view.dirRemove(keylet::ownerDir(owner), sle->getFieldU64(sfOwnerNode), offerIndex, false))
{
return tefBAD_LEDGER; // LCOV_EXCL_LINE
}
if (!view.dirRemove(keylet::page(uDirectory), sle->getFieldU64(sfBookNode), offerIndex, false))
{
return tefBAD_LEDGER; // LCOV_EXCL_LINE
}
if (sle->isFieldPresent(sfAdditionalBooks))
{
XRPL_ASSERT(
sle->isFlag(lsfHybrid) && sle->isFieldPresent(sfDomainID),
"xrpl::offerDelete : should be a hybrid domain offer");
auto const& additionalBookDirs = sle->getFieldArray(sfAdditionalBooks);
for (auto const& bookDir : additionalBookDirs)
{
auto const& dirIndex = bookDir.getFieldH256(sfBookDirectory);
auto const& dirNode = bookDir.getFieldU64(sfBookNode);
if (!view.dirRemove(keylet::page(dirIndex), dirNode, offerIndex, false))
{
return tefBAD_LEDGER; // LCOV_EXCL_LINE
}
}
}
decreaseOwnerCountForObject(view, owner, sle, 1, j);
view.erase(sle);
return tesSUCCESS;
// Unlink the offer from its owner directory and every order-book page it
// sits in (including a hybrid offer's additional books), decrement the
// owner's OwnerCount (refunding a reserve sponsor when present), and erase
// it. See OfferEntry::destroy().
OfferEntry<ApplyView> offer{sle, view, j};
return offer.destroy();
}
} // namespace xrpl
@@ -1,15 +1,13 @@
#include <xrpl/ledger/helpers/PaymentChannelHelpers.h>
#include <xrpl/basics/Log.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/TER.h>
@@ -22,48 +20,28 @@
namespace xrpl {
TER
closeChannel(SLE::ref slep, ApplyView& view, uint256 const& key, beast::Journal j)
closeChannel(
PayChannelEntry<ApplyView>& slep,
ApplyView& view,
uint256 const& key,
beast::Journal j)
{
AccountID const src = (*slep)[sfAccount];
// Remove PayChan from owner directory
{
auto const page = (*slep)[sfOwnerNode];
if (!view.dirRemove(keylet::ownerDir(src), page, key, true))
{
// LCOV_EXCL_START
JLOG(j.fatal()) << "Could not remove paychan from src owner directory";
return tefBAD_LEDGER;
// LCOV_EXCL_STOP
}
}
// Remove PayChan from recipient's owner directory, if present.
if (auto const page = (*slep)[~sfDestinationNode])
{
auto const dst = (*slep)[sfDestination];
if (!view.dirRemove(keylet::ownerDir(dst), *page, key, true))
{
// LCOV_EXCL_START
JLOG(j.fatal()) << "Could not remove paychan from dst owner directory";
return tefBAD_LEDGER;
// LCOV_EXCL_STOP
}
}
// Transfer amount back to owner, decrement owner count
auto const sle = view.peek(keylet::account(src));
// Transfer any remaining balance back to the owner.
AccountRootEntry<ApplyView> sle{src, view};
if (!sle)
return tefINTERNAL; // LCOV_EXCL_LINE
XRPL_ASSERT(
(*slep)[sfAmount] >= (*slep)[sfBalance], "xrpl::closeChannel : minimum channel amount");
(*sle)[sfBalance] = (*sle)[sfBalance] + (*slep)[sfAmount] - (*slep)[sfBalance];
decreaseOwnerCountForObject(view, sle, slep, 1, j);
view.update(sle);
sle.update();
// Remove PayChan from ledger
view.erase(slep);
return tesSUCCESS;
// Unlink the channel from the owner and destination directories, decrement
// the owner's OwnerCount (refunding any reserve sponsor), and erase it. See
// PayChannelEntry::ownerDirs().
return slep.destroy();
}
uint32_t
@@ -7,6 +7,7 @@
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/CredentialHelpers.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
@@ -31,7 +32,7 @@ accountInDomain(ReadView const& view, AccountID const& account, Domain const& do
// LCOV_EXCL_STOP
}
auto const sleDomain = view.read(keylet::permissionedDomain(domainID));
PermissionedDomainEntry<ReadView> const sleDomain{keylet::permissionedDomain(domainID), view};
if (!sleDomain)
return false;
@@ -42,8 +43,8 @@ accountInDomain(ReadView const& view, AccountID const& account, Domain const& do
auto const& credentials = sleDomain->getFieldArray(sfAcceptedCredentials);
bool const inDomain = std::ranges::any_of(credentials, [&](auto const& credential) {
auto const sleCred = view.read(
keylet::credential(account, credential[sfIssuer], credential[sfCredentialType]));
CredentialEntry<ReadView> const sleCred{
keylet::credential(account, credential[sfIssuer], credential[sfCredentialType]), view};
if (!sleCred || !sleCred->isFlag(lsfAccepted))
return false;
@@ -60,7 +61,7 @@ offerInDomain(
Domain const& domainID,
beast::Journal j)
{
auto const sleOffer = view.read(keylet::offer(offerID));
OfferEntry<ReadView> const sleOffer{keylet::offer(offerID), view};
// The following are defensive checks that should never happen, since this
// function is used to check against the order book offers, which should not
+18 -8
View File
@@ -3,6 +3,7 @@
#include <xrpl/basics/Number.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h> // IWYU pragma: keep
@@ -17,7 +18,10 @@
namespace xrpl {
[[nodiscard]] std::optional<STAmount>
assetsToSharesDeposit(SLE::const_ref vault, SLE::const_ref issuance, STAmount const& assets)
assetsToSharesDeposit(
VaultEntry<ReadView> const& vault,
MPTokenIssuanceEntry<ReadView> const& issuance,
STAmount const& assets)
{
XRPL_ASSERT(!assets.negative(), "xrpl::assetsToSharesDeposit : non-negative assets");
XRPL_ASSERT(
@@ -41,7 +45,10 @@ assetsToSharesDeposit(SLE::const_ref vault, SLE::const_ref issuance, STAmount co
}
[[nodiscard]] std::optional<STAmount>
sharesToAssetsDeposit(SLE::const_ref vault, SLE::const_ref issuance, STAmount const& shares)
sharesToAssetsDeposit(
VaultEntry<ReadView> const& vault,
MPTokenIssuanceEntry<ReadView> const& issuance,
STAmount const& shares)
{
XRPL_ASSERT(!shares.negative(), "xrpl::sharesToAssetsDeposit : non-negative shares");
XRPL_ASSERT(
@@ -65,8 +72,8 @@ sharesToAssetsDeposit(SLE::const_ref vault, SLE::const_ref issuance, STAmount co
[[nodiscard]] std::optional<STAmount>
assetsToSharesWithdraw(
SLE::const_ref vault,
SLE::const_ref issuance,
VaultEntry<ReadView> const& vault,
MPTokenIssuanceEntry<ReadView> const& issuance,
STAmount const& assets,
TruncateShares truncate,
WaiveUnrealizedLoss waive)
@@ -94,8 +101,8 @@ assetsToSharesWithdraw(
[[nodiscard]] std::optional<STAmount>
sharesToAssetsWithdraw(
SLE::const_ref vault,
SLE::const_ref issuance,
VaultEntry<ReadView> const& vault,
MPTokenIssuanceEntry<ReadView> const& issuance,
STAmount const& shares,
WaiveUnrealizedLoss waive)
{
@@ -118,7 +125,10 @@ sharesToAssetsWithdraw(
}
[[nodiscard]] bool
isSoleShareholder(ReadView const& view, AccountID const& account, SLE::const_ref issuance)
isSoleShareholder(
ReadView const& view,
AccountID const& account,
MPTokenIssuanceEntry<ReadView> const& issuance)
{
XRPL_ASSERT(
issuance && issuance->getType() == ltMPTOKEN_ISSUANCE,
@@ -130,7 +140,7 @@ isSoleShareholder(ReadView const& view, AccountID const& account, SLE::const_ref
auto const shareMPTID =
makeMptID(issuance->getFieldU32(sfSequence), issuance->getAccountID(sfIssuer));
auto const sleToken = view.read(keylet::mptoken(shareMPTID, account));
MPTokenEntry<ReadView> const sleToken{keylet::mptoken(shareMPTID, account), view};
if (!sleToken)
return false; // LCOV_EXCL_LINE
@@ -13,6 +13,8 @@
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/NFTokenHelpers.h>
#include <xrpl/ledger/helpers/OfferHelpers.h>
#include <xrpl/ledger/helpers/SLEBase.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
@@ -224,7 +226,7 @@ AccountDelete::preclaim(PreclaimContext const& ctx)
AccountID const account{ctx.tx[sfAccount]};
AccountID const dst{ctx.tx[sfDestination]};
auto sleDst = ctx.view.read(keylet::account(dst));
AccountRootEntry<ReadView> const sleDst{keylet::account(dst), ctx.view};
if (!sleDst)
return tecNO_DST;
@@ -248,7 +250,7 @@ AccountDelete::preclaim(PreclaimContext const& ctx)
}
}
auto sleAccount = ctx.view.read(keylet::account(account));
AccountRootEntry<ReadView> const sleAccount{keylet::account(account), ctx.view};
XRPL_ASSERT(sleAccount, "xrpl::AccountDelete::preclaim : non-null account");
if (!sleAccount)
return terNO_ACCOUNT;
@@ -262,8 +264,9 @@ AccountDelete::preclaim(PreclaimContext const& ctx)
Keylet const first = keylet::nftokenPageMin(account);
Keylet const last = keylet::nftokenPageMax(account);
auto const cp = ctx.view.read(
Keylet(ltNFTOKEN_PAGE, ctx.view.succ(first.key, last.key.next()).value_or(last.key)));
NFTokenPageEntry<ReadView> const cp{
Keylet(ltNFTOKEN_PAGE, ctx.view.succ(first.key, last.key.next()).value_or(last.key)),
ctx.view};
if (cp)
return tecHAS_OBLIGATIONS;
@@ -322,7 +325,7 @@ AccountDelete::preclaim(PreclaimContext const& ctx)
{
// Make sure any directory node types that we find are the kind
// we can delete.
auto sleItem = ctx.view.read(keylet::child(dirEntry));
ReadOnlySLE const sleItem{keylet::child(dirEntry), ctx.view};
if (!sleItem)
{
// Directory node has an invalid index. Bail out.
@@ -351,11 +354,11 @@ AccountDelete::preclaim(PreclaimContext const& ctx)
TER
AccountDelete::doApply()
{
auto src = view().peek(keylet::account(accountID_));
AccountRootEntry<ApplyView> src{keylet::account(accountID_), view()};
XRPL_ASSERT(src, "xrpl::AccountDelete::doApply : non-null source account");
auto const dstID = ctx_.tx[sfDestination];
auto dst = view().peek(keylet::account(dstID));
AccountRootEntry<ApplyView> dst{keylet::account(dstID), view()};
XRPL_ASSERT(dst, "xrpl::AccountDelete::doApply : non-null destination account");
if (!src || !dst)
@@ -363,8 +366,8 @@ AccountDelete::doApply()
if (ctx_.tx.isFieldPresent(sfCredentialIDs))
{
if (auto err =
verifyDepositPreauth(ctx_.tx, ctx_.view(), accountID_, dstID, dst, ctx_.journal);
if (auto err = verifyDepositPreauth(
ctx_.tx, ctx_.view(), accountID_, dstID, dst.sle(), ctx_.journal);
!isTesSuccess(err))
return err;
}
@@ -446,8 +449,8 @@ AccountDelete::doApply()
if (remainingBalance > XRPAmount(0) && dst->isFlag(lsfPasswordSpent))
dst->clearFlag(lsfPasswordSpent);
view().update(dst);
view().erase(src);
dst.update();
src.erase();
return tesSUCCESS;
}
@@ -7,6 +7,7 @@
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
@@ -170,7 +171,7 @@ AccountSet::preclaim(PreclaimContext const& ctx)
{
auto const id = ctx.tx[sfAccount];
auto const sle = ctx.view.read(keylet::account(id));
AccountRootEntry<ReadView> const sle{keylet::account(id), ctx.view};
if (!sle)
return terNO_ACCOUNT;
@@ -224,7 +225,7 @@ AccountSet::preclaim(PreclaimContext const& ctx)
TER
AccountSet::doApply()
{
auto const sle = view().peek(keylet::account(accountID_));
AccountRootEntry<ApplyView> sle{keylet::account(accountID_), view()};
if (!sle)
return tefINTERNAL; // LCOV_EXCL_LINE
@@ -312,7 +313,8 @@ AccountSet::doApply()
return tecNEED_MASTER_KEY;
}
if ((!sle->isFieldPresent(sfRegularKey)) && (!view().peek(keylet::signerList(accountID_))))
if ((!sle->isFieldPresent(sfRegularKey)) &&
(!SignerListEntry<ApplyView>{keylet::signerList(accountID_), view()}))
{
// Account has no regular key or multi-signer signer list.
return tecNO_ALTERNATIVE_KEY;
@@ -582,7 +584,7 @@ AccountSet::doApply()
if (uFlagsIn != uFlagsOut)
sle->setFieldU32(sfFlags, uFlagsOut);
ctx_.view().update(sle);
sle.update();
return tesSUCCESS;
}
@@ -2,7 +2,9 @@
#include <xrpl/basics/Slice.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/PublicKey.h>
@@ -25,7 +27,7 @@ SetRegularKey::calculateBaseFee(ReadView const& view, STTx const& tx)
{
if (calcAccountID(PublicKey(makeSlice(spk))) == id)
{
auto const sle = view.read(keylet::account(id));
AccountRootEntry<ReadView> const sle{keylet::account(id), view};
if (sle && !sle->isFlag(lsfPasswordSpent))
{
@@ -53,7 +55,7 @@ SetRegularKey::preflight(PreflightContext const& ctx)
TER
SetRegularKey::doApply()
{
auto const sle = view().peek(keylet::account(accountID_));
AccountRootEntry<ApplyView> sle{keylet::account(accountID_), view()};
if (!sle)
return tefINTERNAL; // LCOV_EXCL_LINE
@@ -67,13 +69,14 @@ SetRegularKey::doApply()
else
{
// Account has disabled master key and no multi-signer signer list.
if (sle->isFlag(lsfDisableMaster) && !view().peek(keylet::signerList(accountID_)))
if (sle->isFlag(lsfDisableMaster) &&
!SignerListEntry<ApplyView>{keylet::signerList(accountID_), view()})
return tecNO_ALTERNATIVE_KEY;
sle->makeFieldAbsent(sfRegularKey);
}
ctx_.view().update(sle);
sle.update();
return tesSUCCESS;
}
@@ -11,7 +11,7 @@
#include <xrpl/ledger/RawView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
@@ -719,23 +719,15 @@ finalizeClaimHelper(
}
}
if (auto const sleClaimID = outerSb.peek(claimIDKeylet))
if (XChainOwnedClaimIDEntry<ApplyView> sleClaimID{claimIDKeylet, outerSb, j})
{
auto const cidOwner = (*sleClaimID)[sfAccount];
// Unlink the claim id from its owner's directory, decrement the owner's
// OwnerCount, and erase it. See XChainOwnedClaimIDEntry.
if (auto const ter = sleClaimID.destroy(); !isTesSuccess(ter))
{
// Remove the claim id
auto const sleOwner = outerSb.peek(keylet::account(cidOwner));
auto const page = (*sleClaimID)[sfOwnerNode];
if (!outerSb.dirRemove(keylet::ownerDir(cidOwner), page, sleClaimID->key(), true))
{
JLOG(j.fatal()) << "Unable to delete xchain seq number from owner.";
result.rmSleTer = tefBAD_LEDGER;
return result;
}
// Remove the claim id from the ledger
decreaseOwnerCountForObject(outerSb, sleOwner, sleClaimID, 1, j);
outerSb.erase(sleClaimID);
JLOG(j.fatal()) << "Unable to delete xchain seq number from owner.";
result.rmSleTer = ter;
return result;
}
}
@@ -805,24 +797,27 @@ readOrpeekBridge(F&& getter, STXChainBridge const& bridgeSpec)
return tryGet(STXChainBridge::ChainType::Issuing);
}
SLE::pointer
BridgeEntry<ApplyView>
peekBridge(ApplyView& v, STXChainBridge const& bridgeSpec)
{
return readOrpeekBridge<SLE>(
[&v](STXChainBridge const& b, STXChainBridge::ChainType ct) -> SLE::pointer {
return v.peek(keylet::bridge(b, ct));
},
bridgeSpec);
return BridgeEntry<ApplyView>{
readOrpeekBridge<SLE>(
[&v](STXChainBridge const& b, STXChainBridge::ChainType ct) -> std::shared_ptr<SLE> {
return v.peek(keylet::bridge(b, ct));
},
bridgeSpec),
v};
}
SLE::const_pointer
BridgeEntry<ReadView>
readBridge(ReadView const& v, STXChainBridge const& bridgeSpec)
{
return readOrpeekBridge<SLE const>(
[&v](STXChainBridge const& b, STXChainBridge::ChainType ct) -> SLE::const_pointer {
return v.read(keylet::bridge(b, ct));
},
bridgeSpec);
return BridgeEntry<ReadView>{
readOrpeekBridge<SLE const>(
[&v](STXChainBridge const& b, STXChainBridge::ChainType ct)
-> std::shared_ptr<SLE const> { return v.read(keylet::bridge(b, ct)); },
bridgeSpec),
v};
}
// Precondition: all the claims in the range are consistent. They must sign for
@@ -1127,27 +1122,18 @@ applyCreateAccountAttestations(
}
else if (createCID)
{
auto const createdSleClaimID = std::make_shared<SLE>(claimIDKeylet);
XChainOwnedCreateAccountClaimIDEntry<ApplyView> createdSleClaimID{claimIDKeylet, psb, j};
createdSleClaimID.newSLE();
(*createdSleClaimID)[sfAccount] = doorAccount;
(*createdSleClaimID)[sfXChainBridge] = bridgeSpec;
(*createdSleClaimID)[sfXChainAccountCreateCount] = attBegin->createCount;
createdSleClaimID->setFieldArray(sfXChainCreateAccountAttestations, curAtts.toSTArray());
// Add to owner directory of the door account
auto const page = psb.dirInsert(
keylet::ownerDir(doorAccount), claimIDKeylet, describeOwnerDir(doorAccount));
if (!page)
return tecDIR_FULL; // LCOV_EXCL_LINE
(*createdSleClaimID)[sfOwnerNode] = *page;
auto const sleDoor = psb.peek(doorK);
if (!sleDoor)
return tecINTERNAL; // LCOV_EXCL_LINE
// Reserve was already checked
increaseOwnerCount(psb, sleDoor, {}, 1, j);
psb.insert(createdSleClaimID);
psb.update(sleDoor);
// Link into the door account's owner directory, bump its OwnerCount, and
// insert. The reserve was already checked against the door account
// above, so skip create()'s check. See XChainOwnedCreateAccountClaimIDEntry.
if (auto const ter = createdSleClaimID.create(std::nullopt); !isTesSuccess(ter))
return ter;
}
psb.apply(rawView);
@@ -1472,7 +1458,8 @@ XChainCreateBridge::doApply()
STXChainBridge::srcChain(account == bridgeSpec.lockingChainDoor());
Keylet const bridgeKeylet = keylet::bridge(bridgeSpec, chainType);
auto const sleBridge = std::make_shared<SLE>(bridgeKeylet);
BridgeEntry<ApplyView> sleBridge{bridgeKeylet, ctx_.view(), ctx_.journal};
sleBridge.newSLE();
(*sleBridge)[sfAccount] = account;
(*sleBridge)[sfSignatureReward] = reward;
@@ -1483,21 +1470,9 @@ XChainCreateBridge::doApply()
(*sleBridge)[sfXChainAccountCreateCount] = 0;
(*sleBridge)[sfXChainAccountClaimCount] = 0;
// Add to owner directory
{
auto const page = ctx_.view().dirInsert(
keylet::ownerDir(account), bridgeKeylet, describeOwnerDir(account));
if (!page)
return tecDIR_FULL; // LCOV_EXCL_LINE
(*sleBridge)[sfOwnerNode] = *page;
}
increaseOwnerCount(ctx_.view(), sleAcct, {}, 1, ctx_.journal);
ctx_.view().insert(sleBridge);
ctx_.view().update(sleAcct);
return tesSUCCESS;
// Reserve check (submitter's pre-fee balance) + link into the owner
// directory + bump the owner's OwnerCount + insert. See BridgeEntry.
return sleBridge.create(preFeeBalance_);
}
//------------------------------------------------------------------------------
@@ -2015,7 +1990,7 @@ XChainCreateClaimID::doApply()
if (!sleAcct)
return tecINTERNAL; // LCOV_EXCL_LINE
auto const sleBridge = peekBridge(ctx_.view(), bridgeSpec);
auto sleBridge = peekBridge(ctx_.view(), bridgeSpec);
if (!sleBridge)
return tecINTERNAL; // LCOV_EXCL_LINE
@@ -2029,14 +2004,14 @@ XChainCreateClaimID::doApply()
(*sleBridge)[sfXChainClaimID] = claimID;
Keylet const claimIDKeylet = keylet::xChainClaimID(bridgeSpec, claimID);
if (ctx_.view().exists(claimIDKeylet))
XChainOwnedClaimIDEntry<ApplyView> sleClaimID{claimIDKeylet, ctx_.view(), ctx_.journal};
if (sleClaimID)
{
// already checked out!?!
return tecINTERNAL; // LCOV_EXCL_LINE
}
auto const sleClaimID = std::make_shared<SLE>(claimIDKeylet);
sleClaimID.newSLE();
(*sleClaimID)[sfAccount] = account;
(*sleClaimID)[sfXChainBridge] = bridgeSpec;
(*sleClaimID)[sfXChainClaimID] = claimID;
@@ -2044,21 +2019,13 @@ XChainCreateClaimID::doApply()
(*sleClaimID)[sfSignatureReward] = reward;
sleClaimID->setFieldArray(sfXChainClaimAttestations, STArray{sfXChainClaimAttestations});
// Add to owner directory
{
auto const page = ctx_.view().dirInsert(
keylet::ownerDir(account), claimIDKeylet, describeOwnerDir(account));
if (!page)
return tecDIR_FULL; // LCOV_EXCL_LINE
(*sleClaimID)[sfOwnerNode] = *page;
}
increaseOwnerCount(ctx_.view(), sleAcct, {}, 1, ctx_.journal);
ctx_.view().insert(sleClaimID);
ctx_.view().update(sleBridge);
ctx_.view().update(sleAcct);
// Reserve check (submitter's pre-fee balance) + link into the owner
// directory + bump the owner's OwnerCount + insert. See
// XChainOwnedClaimIDEntry.
if (auto const ter = sleClaimID.create(preFeeBalance_); !isTesSuccess(ter))
return ter;
sleBridge.update();
return tesSUCCESS;
}
@@ -2199,7 +2166,7 @@ XChainCreateAccountCommit::doApply()
if (!sle)
return tecINTERNAL; // LCOV_EXCL_LINE
auto const sleBridge = peekBridge(psb, bridge);
auto sleBridge = peekBridge(psb, bridge);
if (!sleBridge)
return tecINTERNAL; // LCOV_EXCL_LINE
@@ -2227,7 +2194,7 @@ XChainCreateAccountCommit::doApply()
return thTer;
(*sleBridge)[sfXChainAccountCreateCount] = (*sleBridge)[sfXChainAccountCreateCount] + 1;
psb.update(sleBridge);
sleBridge.update();
psb.apply(ctx_.rawView());
@@ -2,8 +2,9 @@
#include <xrpl/basics/Log.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/SField.h>
@@ -13,7 +14,6 @@
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/Transactor.h>
#include <cstdint>
namespace xrpl {
NotTEC
@@ -25,7 +25,7 @@ CheckCancel::preflight(PreflightContext const& ctx)
TER
CheckCancel::preclaim(PreclaimContext const& ctx)
{
auto const sleCheck = ctx.view.read(keylet::check(ctx.tx[sfCheckID]));
CheckEntry<ReadView> const sleCheck{keylet::check(ctx.tx[sfCheckID]), ctx.view};
if (!sleCheck)
{
JLOG(ctx.j.warn()) << "Check does not exist.";
@@ -54,7 +54,7 @@ CheckCancel::preclaim(PreclaimContext const& ctx)
TER
CheckCancel::doApply()
{
auto const sleCheck = view().peek(keylet::check(ctx_.tx[sfCheckID]));
CheckEntry<ApplyView> sleCheck{keylet::check(ctx_.tx[sfCheckID]), view()};
if (!sleCheck)
{
// Error should have been caught in preclaim.
@@ -62,40 +62,10 @@ CheckCancel::doApply()
return tecNO_ENTRY;
}
AccountID const srcId{sleCheck->getAccountID(sfAccount)};
AccountID const dstId{sleCheck->getAccountID(sfDestination)};
auto viewJ = ctx_.registry.get().getJournal("View");
// If the check is not written to self (and it shouldn't be), remove the
// check from the destination account root.
if (srcId != dstId)
{
std::uint64_t const page{(*sleCheck)[sfDestinationNode]};
if (!view().dirRemove(keylet::ownerDir(dstId), page, sleCheck->key(), true))
{
// LCOV_EXCL_START
JLOG(j_.fatal()) << "Unable to delete check from destination.";
return tefBAD_LEDGER;
// LCOV_EXCL_STOP
}
}
{
std::uint64_t const page{(*sleCheck)[sfOwnerNode]};
if (!view().dirRemove(keylet::ownerDir(srcId), page, sleCheck->key(), true))
{
// LCOV_EXCL_START
JLOG(j_.fatal()) << "Unable to delete check from owner.";
return tefBAD_LEDGER;
// LCOV_EXCL_STOP
}
}
// If we succeeded, update the check owner's reserve.
decreaseOwnerCountForObject(view(), srcId, sleCheck, 1, viewJ);
// Remove check from ledger.
view().erase(sleCheck);
return tesSUCCESS;
// Unlink the check from the source (and destination) directories, decrement
// the source's OwnerCount (refunding any reserve sponsor), and erase it. See
// CheckEntry::ownerDirs().
return sleCheck.destroy();
}
void
+14 -33
View File
@@ -9,6 +9,7 @@
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
@@ -82,7 +83,7 @@ CheckCash::preflight(PreflightContext const& ctx)
TER
CheckCash::preclaim(PreclaimContext const& ctx)
{
auto const sleCheck = ctx.view.read(keylet::check(ctx.tx[sfCheckID]));
CheckEntry<ReadView> const sleCheck{keylet::check(ctx.tx[sfCheckID]), ctx.view};
if (!sleCheck)
{
JLOG(ctx.j.warn()) << "Check does not exist.";
@@ -107,8 +108,8 @@ CheckCash::preclaim(PreclaimContext const& ctx)
// LCOV_EXCL_STOP
}
{
auto const sleSrc = ctx.view.read(keylet::account(srcId));
auto const sleDst = ctx.view.read(keylet::account(dstId));
AccountRootEntry<ReadView> const sleSrc{keylet::account(srcId), ctx.view};
AccountRootEntry<ReadView> const sleDst{keylet::account(dstId), ctx.view};
if (!sleSrc || !sleDst)
{
// If the check exists this should never occur.
@@ -193,10 +194,10 @@ CheckCash::preclaim(PreclaimContext const& ctx)
return value.asset().visit(
[&](Issue const& issue) -> TER {
Currency const currency{issue.currency};
auto const sleTrustLine =
ctx.view.read(keylet::trustLine(dstId, issuerId, currency));
RippleStateEntry<ReadView> const sleTrustLine{
keylet::trustLine(dstId, issuerId, currency), ctx.view};
auto const sleIssuer = ctx.view.read(keylet::account(issuerId));
AccountRootEntry<ReadView> const sleIssuer{keylet::account(issuerId), ctx.view};
if (!sleIssuer)
{
JLOG(ctx.j.warn()) << "Can't receive IOUs from "
@@ -248,7 +249,7 @@ CheckCash::preclaim(PreclaimContext const& ctx)
return tesSUCCESS;
},
[&](MPTIssue const& issue) -> TER {
auto const sleIssuer = ctx.view.read(keylet::account(issuerId));
AccountRootEntry<ReadView> const sleIssuer{keylet::account(issuerId), ctx.view};
if (!sleIssuer)
{
JLOG(ctx.j.warn()) << "Can't receive MPTs from "
@@ -576,32 +577,12 @@ CheckCash::doApply()
}
}
// Check was cashed. If not a self send (and it shouldn't be), remove
// check link from destination directory.
if (srcId != accountID_ &&
!psb.dirRemove(
keylet::ownerDir(accountID_), sleCheck->at(sfDestinationNode), sleCheck->key(), true))
{
// LCOV_EXCL_START
JLOG(j_.fatal()) << "Unable to delete check from destination.";
return tefBAD_LEDGER;
// LCOV_EXCL_STOP
}
// Remove check from check owner's directory.
if (!psb.dirRemove(keylet::ownerDir(srcId), sleCheck->at(sfOwnerNode), sleCheck->key(), true))
{
// LCOV_EXCL_START
JLOG(j_.fatal()) << "Unable to delete check from owner.";
return tefBAD_LEDGER;
// LCOV_EXCL_STOP
}
// If we succeeded, update the check owner's reserve.
decreaseOwnerCountForObject(psb, srcId, sleCheck, 1, viewJ);
// Remove check from ledger.
psb.erase(sleCheck);
// Check was cashed. Unlink it from the owner (and destination) directories,
// decrement the source's OwnerCount (refunding any reserve sponsor), and
// erase it. See CheckEntry::ownerDirs().
CheckEntry<ApplyView> checkEntry{sleCheck, psb};
if (auto const ter = checkEntry.destroy(); !isTesSuccess(ter))
return ter; // LCOV_EXCL_LINE
psb.apply(ctx_.rawView());
return tesSUCCESS;
@@ -3,11 +3,12 @@
#include <xrpl/basics/Log.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Asset.h>
@@ -26,7 +27,6 @@
#include <xrpl/tx/Transactor.h>
#include <cstdint>
#include <memory>
#include <optional>
namespace xrpl {
@@ -80,7 +80,7 @@ CheckCreate::preclaim(PreclaimContext const& ctx)
{
AccountID const dstId{ctx.tx[sfDestination]};
AccountID const srcId{ctx.tx[sfAccount]};
auto const sleDst = ctx.view.read(keylet::account(dstId));
AccountRootEntry<ReadView> const sleDst{keylet::account(dstId), ctx.view};
if (!sleDst)
{
JLOG(ctx.j.warn()) << "Destination account does not exist.";
@@ -95,7 +95,7 @@ CheckCreate::preclaim(PreclaimContext const& ctx)
// because all writes to pseudo-account discriminator fields **are**
// amendment gated, hence the behaviour of this check will always match the
// currently active amendments.
if (isPseudoAccount(sleDst))
if (isPseudoAccount(sleDst.sle()))
return tecNO_PERMISSION;
if (sleDst->isFlag(lsfRequireDestTag) && !ctx.tx.isFieldPresent(sfDestinationTag))
@@ -127,8 +127,8 @@ CheckCreate::preclaim(PreclaimContext const& ctx)
if (issuerId != srcId)
{
// Check if the issuer froze the line
auto const sleTrust =
ctx.view.read(keylet::trustLine(srcId, issuerId, issue.currency));
RippleStateEntry<ReadView> const sleTrust{
keylet::trustLine(srcId, issuerId, issue.currency), ctx.view};
if (sleTrust &&
sleTrust->isFlag((issuerId > srcId) ? lsfHighFreeze : lsfLowFreeze))
{
@@ -139,8 +139,8 @@ CheckCreate::preclaim(PreclaimContext const& ctx)
if (issuerId != dstId)
{
// Check if dst froze the line.
auto const sleTrust =
ctx.view.read(keylet::trustLine(issuerId, dstId, issue.currency));
RippleStateEntry<ReadView> const sleTrust{
keylet::trustLine(issuerId, dstId, issue.currency), ctx.view};
if (sleTrust &&
sleTrust->isFlag((dstId > issuerId) ? lsfHighFreeze : lsfLowFreeze))
{
@@ -188,22 +188,17 @@ CheckCreate::preclaim(PreclaimContext const& ctx)
TER
CheckCreate::doApply()
{
auto const sle = view().peek(keylet::account(accountID_));
AccountRootEntry<ApplyView> const sle{keylet::account(accountID_), view()};
if (!sle)
return tefINTERNAL; // LCOV_EXCL_LINE
// A check counts against the reserve of the issuing account, but we
// check the starting balance because we want to allow dipping into the
// reserve to pay fees.
if (auto const ret = checkReserve(
ctx_.getApplyViewContext(), sle, preFeeBalance_, {.ownerCountDelta = 1}, ctx_.journal);
!isTesSuccess(ret))
return ret;
// Note that we use the value from the sequence or ticket as the
// Check sequence. For more explanation see comments in SeqProxy.h.
std::uint32_t const seq = ctx_.tx.getSeqValue();
Keylet const checkKeylet = keylet::check(accountID_, seq);
auto sleCheck = std::make_shared<SLE>(checkKeylet);
// Build with the ApplyViewContext so create() honors reserve sponsorship.
CheckEntry<ApplyView> sleCheck{checkKeylet, ctx_.getApplyViewContext()};
sleCheck.newSLE();
sleCheck->setAccountID(sfAccount, accountID_);
AccountID const dstAccountId = ctx_.tx[sfDestination];
@@ -219,42 +214,11 @@ CheckCreate::doApply()
if (auto const expiry = ctx_.tx[~sfExpiration])
sleCheck->setFieldU32(sfExpiration, *expiry);
view().insert(sleCheck);
auto viewJ = ctx_.registry.get().getJournal("View");
// If it's not a self-send (and it shouldn't be), add Check to the
// destination's owner directory.
if (dstAccountId != accountID_)
{
auto const page = view().dirInsert(
keylet::ownerDir(dstAccountId), checkKeylet, describeOwnerDir(dstAccountId));
JLOG(j_.trace()) << "Adding Check to destination directory " << to_string(checkKeylet.key)
<< ": " << (page ? "success" : "failure");
if (!page)
return tecDIR_FULL; // LCOV_EXCL_LINE
sleCheck->setFieldU64(sfDestinationNode, *page);
}
{
auto const page = view().dirInsert(
keylet::ownerDir(accountID_), checkKeylet, describeOwnerDir(accountID_));
JLOG(j_.trace()) << "Adding Check to owner directory " << to_string(checkKeylet.key) << ": "
<< (page ? "success" : "failure");
if (!page)
return tecDIR_FULL; // LCOV_EXCL_LINE
sleCheck->setFieldU64(sfOwnerNode, *page);
}
// If we succeeded, the new entry counts against the creator's reserve.
increaseOwnerCount(ctx_.getApplyViewContext(), sle, 1, viewJ);
addSponsorToLedgerEntry(ctx_.getApplyViewContext(), sleCheck);
return tesSUCCESS;
// Reserve check (source's pre-fee balance, honoring any reserve sponsor) +
// link into the source owner directory and the destination tracking
// directory + bump the source's OwnerCount + stamp the reserve sponsor +
// insert. See CheckEntry::ownerDirs() and SLEBase::create().
return sleCheck.create(preFeeBalance_);
}
void
@@ -4,10 +4,8 @@
#include <xrpl/basics/base_uint.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/CredentialHelpers.h> // IWYU pragma: keep
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Keylet.h>
@@ -23,7 +21,6 @@
#include <chrono>
#include <cstdint>
#include <memory>
namespace xrpl {
@@ -106,7 +103,9 @@ CredentialCreate::doApply()
auto const credType(ctx_.tx[sfCredentialType]);
Keylet const credentialKey = keylet::credential(subject, accountID_, credType);
auto const sleCred = std::make_shared<SLE>(credentialKey);
// Build with the ApplyViewContext so create() honors reserve sponsorship.
CredentialEntry<ApplyView> sleCred{credentialKey, ctx_.getApplyViewContext()};
sleCred.newSLE();
if (!sleCred)
return tefINTERNAL; // LCOV_EXCL_LINE
@@ -126,15 +125,6 @@ CredentialCreate::doApply()
sleCred->setFieldU32(sfExpiration, *optExp);
}
auto const sleIssuer = view().peek(keylet::account(accountID_));
if (!sleIssuer)
return tefINTERNAL; // LCOV_EXCL_LINE
if (auto const ret = checkReserve(
ctx_.getApplyViewContext(), sleIssuer, preFeeBalance_, {.ownerCountDelta = 1}, j_);
!isTesSuccess(ret))
return ret;
sleCred->setAccountID(sfSubject, subject);
sleCred->setAccountID(sfIssuer, accountID_);
sleCred->setFieldVL(sfCredentialType, credType);
@@ -142,39 +132,15 @@ CredentialCreate::doApply()
if (ctx_.tx.isFieldPresent(sfURI))
sleCred->setFieldVL(sfURI, ctx_.tx.getFieldVL(sfURI));
{
auto const page = view().dirInsert(
keylet::ownerDir(accountID_), credentialKey, describeOwnerDir(accountID_));
JLOG(j_.trace()) << "Adding Credential to owner directory " << to_string(credentialKey.key)
<< ": " << (page ? "success" : "failure");
if (!page)
return tecDIR_FULL;
sleCred->setFieldU64(sfIssuerNode, *page);
increaseOwnerCount(ctx_.getApplyViewContext(), sleIssuer, 1, j_);
addSponsorToLedgerEntry(ctx_.getApplyViewContext(), sleCred);
}
// A self-issued credential is accepted immediately.
if (subject == accountID_)
{
sleCred->setFieldU32(sfFlags, lsfAccepted);
}
else
{
// Added to both dirs, owned only by issuer. CredentialAccept will transfer ownership to
// subject. CredentialDelete will remove from both dirs and decrement 1 ownerCount.
auto const page =
view().dirInsert(keylet::ownerDir(subject), credentialKey, describeOwnerDir(subject));
JLOG(j_.trace()) << "Adding Credential to subject directory "
<< to_string(credentialKey.key) << ": " << (page ? "success" : "failure");
if (!page)
return tecDIR_FULL;
sleCred->setFieldU64(sfSubjectNode, *page);
}
view().insert(sleCred);
return tesSUCCESS;
// Reserve check (issuer's pre-fee balance) + link into the issuer's owner
// directory (counted) and, for a third-party subject, the subject's tracking
// directory + bump the issuer's OwnerCount + insert. Ownership transfers to
// the subject on CredentialAccept. See CredentialEntry::ownerDirs().
return sleCred.create(preFeeBalance_);
}
void
@@ -3,6 +3,7 @@
#include <xrpl/basics/Log.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/helpers/CredentialHelpers.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
@@ -80,7 +81,7 @@ CredentialDelete::doApply()
auto const issuer = ctx_.tx[~sfIssuer].value_or(accountID_);
auto const credType(ctx_.tx[sfCredentialType]);
auto const sleCred = view().peek(keylet::credential(subject, issuer, credType));
CredentialEntry<ApplyView> sleCred{keylet::credential(subject, issuer, credType), view()};
if (!sleCred)
return tefINTERNAL; // LCOV_EXCL_LINE
@@ -91,7 +92,7 @@ CredentialDelete::doApply()
return tecNO_PERMISSION;
}
return deleteSLE(view(), sleCred, j_);
return deleteSLE(view(), sleCred.mutableSle(), j_);
}
void
@@ -1,11 +1,10 @@
#include <xrpl/tx/transactors/delegate/DelegateSet.h>
#include <xrpl/basics/Log.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/SField.h>
@@ -72,25 +71,26 @@ DelegateSet::preclaim(PreclaimContext const& ctx)
TER
DelegateSet::doApply()
{
auto const sleOwner = ctx_.view().peek(keylet::account(accountID_));
AccountRootEntry<ApplyView> const sleOwner{keylet::account(accountID_), ctx_.view()};
if (!sleOwner)
return tefINTERNAL; // LCOV_EXCL_LINE
auto const& authAccount = ctx_.tx[sfAuthorize];
auto const delegateKey = keylet::delegate(accountID_, authAccount);
auto sle = ctx_.view().peek(delegateKey);
// Build with the ApplyViewContext so create() honors reserve sponsorship.
DelegateEntry<ApplyView> sle{delegateKey, ctx_.getApplyViewContext()};
if (sle)
{
auto const& permissions = ctx_.tx.getFieldArray(sfPermissions);
if (permissions.empty())
{
// if permissions array is empty, delete the ledger object.
return deleteDelegate(view(), sle, j_);
return deleteDelegate(view(), sle.mutableSle(), j_);
}
sle->setFieldArray(sfPermissions, permissions);
ctx_.view().update(sle);
sle.update();
return tesSUCCESS;
}
@@ -98,45 +98,16 @@ DelegateSet::doApply()
if (permissions.empty())
return tecINTERNAL; // LCOV_EXCL_LINE
if (auto const ret = checkReserve(
ctx_.getApplyViewContext(),
sleOwner,
preFeeBalance_,
{.ownerCountDelta = 1},
ctx_.journal);
!isTesSuccess(ret))
return ret;
sle = std::make_shared<SLE>(delegateKey);
sle.newSLE();
sle->setAccountID(sfAccount, accountID_);
sle->setAccountID(sfAuthorize, authAccount);
sle->setFieldArray(sfPermissions, permissions);
// Add to delegating account's owner directory
auto const page = ctx_.view().dirInsert(
keylet::ownerDir(accountID_), delegateKey, describeOwnerDir(accountID_));
if (!page)
return tecDIR_FULL; // LCOV_EXCL_LINE
(*sle)[sfOwnerNode] = *page;
// Add to authorized account's owner directory so AccountDelete can find
// and clean up inbound delegations when the authorized account is deleted.
auto const destPage = ctx_.view().dirInsert(
keylet::ownerDir(authAccount), delegateKey, describeOwnerDir(authAccount));
if (!destPage)
return tecDIR_FULL; // LCOV_EXCL_LINE
(*sle)[sfDestinationNode] = *destPage;
ctx_.view().insert(sle);
increaseOwnerCount(ctx_.getApplyViewContext(), sleOwner, 1, ctx_.journal);
addSponsorToLedgerEntry(ctx_.getApplyViewContext(), sle);
return tesSUCCESS;
// Reserve check (delegator's pre-fee balance, honoring any reserve sponsor)
// + link into the delegator's owner directory and the authorized account's
// directory + bump the delegator's OwnerCount + stamp the reserve sponsor +
// insert. See DelegateEntry::ownerDirs() and SLEBase::create().
return sle.create(preFeeBalance_);
}
TER
@@ -145,40 +116,12 @@ DelegateSet::deleteDelegate(ApplyView& view, SLE::ref sle, beast::Journal j)
if (!sle)
return tecINTERNAL; // LCOV_EXCL_LINE
auto const delegator = (*sle)[sfAccount];
auto const delegatee = (*sle)[sfAuthorize];
// Remove from delegating account's owner directory
if (!view.dirRemove(keylet::ownerDir(delegator), (*sle)[sfOwnerNode], sle->key(), false))
{
// LCOV_EXCL_START
JLOG(j.fatal()) << "Unable to delete Delegate from owner.";
return tefBAD_LEDGER;
// LCOV_EXCL_STOP
}
// Remove from authorized account's owner directory, if present
if (auto const optPage = (*sle)[~sfDestinationNode])
{
if (!view.dirRemove(keylet::ownerDir(delegatee), *optPage, sle->key(), false))
{
// LCOV_EXCL_START
JLOG(j.fatal()) << "Unable to delete Delegate from authorized account.";
return tefBAD_LEDGER;
// LCOV_EXCL_STOP
}
}
// Only the delegating account's owner count was incremented on creation
auto const sleOwner = view.peek(keylet::account(delegator));
if (!sleOwner)
return tecINTERNAL; // LCOV_EXCL_LINE
decreaseOwnerCountForObject(view, sleOwner, sle, 1, j);
view.erase(sle);
return tesSUCCESS;
// Unlink from the delegator's owner directory and the authorized account's
// directory, decrement the delegator's OwnerCount (refunding a reserve
// sponsor if present), and erase. Only the delegator's count was bumped on
// creation. See DelegateEntry::ownerDirs().
DelegateEntry<ApplyView> delegate{sle, view, j};
return delegate.destroy();
}
void
@@ -4,8 +4,10 @@
#include <xrpl/basics/Number.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/Sandbox.h>
#include <xrpl/ledger/helpers/AMMHelpers.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/AmountConversions.h>
@@ -268,8 +270,9 @@ AMMClawback::applyGuts(Sandbox& sb)
}
}
AMMEntry<ApplyView> ammEntry{ammSle, sb};
auto const res =
AMMWithdraw::deleteAMMAccountIfEmpty(sb, ammSle, newLPTokenBalance, asset, asset2, j_);
AMMWithdraw::deleteAMMAccountIfEmpty(sb, ammEntry, newLPTokenBalance, asset, asset2, j_);
if (!res.second)
return res.first; // LCOV_EXCL_LINE
+12 -9
View File
@@ -3,13 +3,14 @@
#include <xrpl/basics/Log.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/OrderBookDB.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/Sandbox.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/AMMHelpers.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AMMCore.h>
#include <xrpl/protocol/AccountID.h>
@@ -293,22 +294,24 @@ applyCreate(ApplyContext& ctx, Sandbox& sb, AccountID const& account, beast::Jou
auto const lpTokens = ammLPTokens(amount, amount2, lptIss);
// Create ltAMM
auto ammSle = std::make_shared<SLE>(ammKeylet);
AMMEntry<ApplyView> ammSle{ammKeylet, sb};
ammSle.newSLE();
ammSle->setAccountID(sfAccount, accountId);
ammSle->setFieldAmount(sfLPTokenBalance, lpTokens);
auto const& [asset1, asset2] = std::minmax(amount.asset(), amount2.asset());
ammSle->setFieldIssue(sfAsset, STIssue{sfAsset, asset1});
ammSle->setFieldIssue(sfAsset2, STIssue{sfAsset2, asset2});
// AMM creator gets the auction slot and the voting slot.
initializeFeeAuctionVote(ctx.view(), ammSle, account, lptIss, ctx.tx[sfTradingFee]);
SLE::pointer ammSlePtr = ammSle.mutableSle();
initializeFeeAuctionVote(ctx.view(), ammSlePtr, account, lptIss, ctx.tx[sfTradingFee]);
// Add owner directory to link the root account and AMM object.
if (auto ter = dirLink(sb, accountId, ammSle); ter)
// Link the AMM into its pseudo-account's directory and insert it (no reserve
// or OwnerCount for a pseudo-account). See AMMEntry.
if (auto const ter = ammSle.create(std::nullopt); !isTesSuccess(ter))
{
JLOG(j.debug()) << "AMM Instance: failed to insert owner dir";
return {ter, false};
}
sb.insert(ammSle);
// Send LPT to LP.
auto res = accountSend(sb, accountId, account, lpTokens, ctx.journal);
@@ -360,8 +363,8 @@ applyCreate(ApplyContext& ctx, Sandbox& sb, AccountID const& account, beast::Jou
// Set AMM flag on AMM trustline
if (!isXRP(amount))
{
SLE::pointer const sleRippleState =
sb.peek(keylet::trustLine(accountId, issue));
RippleStateEntry<ApplyView> sleRippleState{
keylet::trustLine(accountId, issue), sb};
if (!sleRippleState)
{
return tecINTERNAL; // LCOV_EXCL_LINE
@@ -369,7 +372,7 @@ applyCreate(ApplyContext& ctx, Sandbox& sb, AccountID const& account, beast::Jou
auto const flags = sleRippleState->getFlags();
sleRippleState->setFieldU32(sfFlags, flags | lsfAMMNode);
sb.update(sleRippleState);
sleRippleState.update();
}
return tesSUCCESS;
});
@@ -10,6 +10,7 @@
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AMMCore.h>
#include <xrpl/protocol/AccountID.h>
@@ -447,8 +448,9 @@ AMMWithdraw::applyGuts(Sandbox& sb)
}
}
AMMEntry<ApplyView> ammEntry{ammSle, sb};
auto const res = deleteAMMAccountIfEmpty(
sb, ammSle, newLPTokenBalance, ctx_.tx[sfAsset], ctx_.tx[sfAsset2], j_);
sb, ammEntry, newLPTokenBalance, ctx_.tx[sfAsset], ctx_.tx[sfAsset2], j_);
// LCOV_EXCL_START
if (!res.second)
return {res.first, false};
@@ -799,7 +801,7 @@ AMMWithdraw::equalWithdrawTokens(
std::pair<TER, bool>
AMMWithdraw::deleteAMMAccountIfEmpty(
Sandbox& sb,
SLE::pointer const ammSle,
AMMEntry<ApplyView>& ammSle,
STAmount const& lpTokenBalance,
Asset const& asset1,
Asset const& asset2,
@@ -819,7 +821,7 @@ AMMWithdraw::deleteAMMAccountIfEmpty(
if (updateBalance)
{
ammSle->setFieldAmount(sfLPTokenBalance, lpTokenBalance);
sb.update(ammSle);
ammSle.update();
}
return {ter, true};
+6 -23
View File
@@ -1,14 +1,13 @@
#include <xrpl/tx/transactors/did/DIDDelete.h>
#include <xrpl/basics/Log.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/SLEBase.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Keylet.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
@@ -27,34 +26,18 @@ DIDDelete::preflight(PreflightContext const& ctx)
TER
DIDDelete::deleteSLE(ApplyContext& ctx, Keylet sleKeylet, AccountID const owner)
{
auto const sle = ctx.view().peek(sleKeylet);
WritableSLE const sle{sleKeylet, ctx.view()};
if (!sle)
return tecNO_ENTRY;
return DIDDelete::deleteSLE(ctx.view(), sle, owner, ctx.journal);
return DIDDelete::deleteSLE(ctx.view(), sle.mutableSle(), owner, ctx.journal);
}
TER
DIDDelete::deleteSLE(ApplyView& view, SLE::pointer sle, AccountID const owner, beast::Journal j)
{
// Remove object from owner directory
if (!view.dirRemove(keylet::ownerDir(owner), (*sle)[sfOwnerNode], sle->key(), true))
{
// LCOV_EXCL_START
JLOG(j.fatal()) << "Unable to delete DID from owner.";
return tefBAD_LEDGER;
// LCOV_EXCL_STOP
}
auto const sleOwner = view.peek(keylet::account(owner));
if (!sleOwner)
return tecINTERNAL; // LCOV_EXCL_LINE
decreaseOwnerCountForObject(view, sleOwner, sle, 1, j);
// Remove object from ledger
view.erase(sle);
return tesSUCCESS;
DIDEntry<ApplyView> did{sle, view, j};
return did.destroy();
}
TER
+7 -43
View File
@@ -1,11 +1,10 @@
#include <xrpl/tx/transactors/did/DIDSet.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Keylet.h>
@@ -19,7 +18,6 @@
#include <xrpl/tx/Transactor.h>
#include <cstddef>
#include <memory>
namespace xrpl {
@@ -62,46 +60,12 @@ DIDSet::preflight(PreflightContext const& ctx)
return tesSUCCESS;
}
static TER
addSLE(ApplyContext& ctx, SLE::ref sle, AccountID const& owner)
{
auto const sleAccount = ctx.view().peek(keylet::account(owner));
if (!sleAccount)
return tefINTERNAL; // LCOV_EXCL_LINE
// Check reserve availability for new object creation
{
auto const balance = STAmount((*sleAccount)[sfBalance]).xrp();
auto const reserve =
accountReserve(ctx.view(), sleAccount, ctx.journal, {.ownerCountDelta = 1});
if (balance < reserve)
return tecINSUFFICIENT_RESERVE;
}
// Add ledger object to ledger
ctx.view().insert(sle);
// Add ledger object to owner's page
{
auto page =
ctx.view().dirInsert(keylet::ownerDir(owner), sle->key(), describeOwnerDir(owner));
if (!page)
return tecDIR_FULL; // LCOV_EXCL_LINE
(*sle)[sfOwnerNode] = *page;
}
increaseOwnerCount(ctx.view(), sleAccount, {}, 1, ctx.journal);
ctx.view().update(sleAccount);
return tesSUCCESS;
}
TER
DIDSet::doApply()
{
// Edit ledger object if it already exists
Keylet const didKeylet = keylet::did(accountID_);
if (auto const sleDID = ctx_.view().peek(didKeylet))
DIDEntry<ApplyView> sleDID{accountID_, ctx_.view()};
if (sleDID.exists())
{
auto update = [&](auto const& sField) {
if (auto const field = ctx_.tx[~sField])
@@ -125,12 +89,12 @@ DIDSet::doApply()
{
return tecEMPTY_DID;
}
ctx_.view().update(sleDID);
sleDID.update();
return tesSUCCESS;
}
// Create new ledger object otherwise
auto const sleDID = std::make_shared<SLE>(didKeylet);
sleDID.newSLE();
(*sleDID)[sfAccount] = accountID_;
auto set = [&](auto const& sField) {
@@ -147,7 +111,7 @@ DIDSet::doApply()
return tecEMPTY_DID;
}
return addSLE(ctx_, sleDID, accountID_);
return sleDID.create(preFeeBalance_);
}
void
@@ -2,11 +2,13 @@
#include <xrpl/basics/Log.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/EscrowHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Concepts.h>
@@ -73,7 +75,7 @@ escrowCancelPreclaimHelper<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);
MPTokenIssuanceEntry<ReadView> const sleIssuance{issuanceKey, ctx.view};
if (!sleIssuance)
return tecOBJECT_NOT_FOUND;
@@ -93,7 +95,7 @@ EscrowCancel::preclaim(PreclaimContext const& ctx)
if (ctx.view.rules().enabled(featureTokenEscrow))
{
auto const k = keylet::escrow(ctx.tx[sfOwner], ctx.tx[sfOfferSequence]);
auto const slep = ctx.view.read(k);
EscrowEntry<ReadView> const slep{k, ctx.view};
if (!slep)
return tecNO_TARGET;
@@ -118,7 +120,7 @@ TER
EscrowCancel::doApply()
{
auto const k = keylet::escrow(ctx_.tx[sfOwner], ctx_.tx[sfOfferSequence]);
auto const slep = ctx_.view().peek(k);
EscrowEntry<ApplyView> slep{k, ctx_.view()};
if (!slep)
{
if (ctx_.view().rules().enabled(featureTokenEscrow))
@@ -139,6 +141,9 @@ EscrowCancel::doApply()
AccountID const account = (*slep)[sfAccount];
// Escrow deletion is inlined here (rather than EscrowEntry::destroy()) so the
// directory removals stay ordered before token delivery, matching develop
// for consensus.
// Remove escrow from owner directory
{
auto const page = (*slep)[sfOwnerNode];
@@ -163,7 +168,7 @@ EscrowCancel::doApply()
}
}
auto const sle = ctx_.view().peek(keylet::account(account));
AccountRootEntry<ApplyView> sle{keylet::account(account), ctx_.view()};
STAmount const amount = slep->getFieldAmount(sfAmount);
// Transfer amount back to the owner
@@ -183,7 +188,8 @@ EscrowCancel::doApply()
return escrowUnlockApplyHelper<T>(
ctx_.getApplyViewContext(),
kParityRate,
ctx_.view().rules().enabled(fixCleanup3_2_0) ? sle : slep,
ctx_.view().rules().enabled(fixCleanup3_2_0) ? sle.mutableSle()
: slep.mutableSle(),
preFeeBalance_,
amount,
issuer,
@@ -196,24 +202,25 @@ EscrowCancel::doApply()
!isTesSuccess(ret))
return ret; // LCOV_EXCL_LINE
// Remove escrow from issuers owner directory, if present.
// Remove escrow from issuer's owner directory, if present.
if (auto const optPage = (*slep)[~sfIssuerNode]; optPage)
{
if (!ctx_.view().dirRemove(keylet::ownerDir(issuer), *optPage, k.key, true))
if (!ctx_.view().dirRemove(keylet::ownerDir(amount.getIssuer()), *optPage, k.key, true))
{
// LCOV_EXCL_START
JLOG(j_.fatal()) << "Unable to delete Escrow from recipient.";
JLOG(j_.fatal()) << "Unable to delete Escrow from issuer.";
return tefBAD_LEDGER;
// LCOV_EXCL_STOP
}
}
}
decreaseOwnerCountForObject(ctx_.view(), sle, slep, 1, ctx_.journal);
// Remove escrow from ledger
ctx_.view().erase(slep);
// Decrement the owner's OwnerCount (refunding any reserve sponsor); this
// also persists the balance credit above.
decreaseOwnerCountForObject(ctx_.view(), sle.mutableSle(), slep.mutableSle(), 1, ctx_.journal);
// Remove escrow from ledger.
slep.erase();
return tesSUCCESS;
}
@@ -6,11 +6,12 @@
#include <xrpl/conditions/Condition.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
@@ -33,7 +34,6 @@
#include <xrpl/tx/Transactor.h>
#include <xrpl/tx/applySteps.h>
#include <memory>
#include <system_error>
#include <variant>
@@ -202,14 +202,15 @@ escrowCreatePreclaimHelper<Issue>(
return tecNO_PERMISSION;
// If the lsfAllowTrustLineLocking is not enabled, return tecNO_PERMISSION
auto const sleIssuer = ctx.view.read(keylet::account(issuer));
AccountRootEntry<ReadView> const sleIssuer{keylet::account(issuer), ctx.view};
if (!sleIssuer)
return tecNO_ISSUER;
if (!sleIssuer->isFlag(lsfAllowTrustLineLocking))
return tecNO_PERMISSION;
// If the account does not have a trustline to the issuer, return tecNO_LINE
auto const sleRippleState = ctx.view.read(keylet::trustLine(account, issuer, issue.currency));
RippleStateEntry<ReadView> const sleRippleState{
keylet::trustLine(account, issuer, issue.currency), ctx.view};
if (!sleRippleState)
return tecNO_LINE;
@@ -273,7 +274,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);
MPTokenIssuanceEntry<ReadView> const sleIssuance{issuanceKey, ctx.view};
if (!sleIssuance)
return tecOBJECT_NOT_FOUND;
@@ -342,7 +343,7 @@ EscrowCreate::preclaim(PreclaimContext const& ctx)
AccountID const account{ctx.tx[sfAccount]};
AccountID const dest{ctx.tx[sfDestination]};
auto const sled = ctx.view.read(keylet::account(dest));
AccountRootEntry<ReadView> const sled{keylet::account(dest), ctx.view};
if (!sled)
return tecNO_DST;
@@ -350,7 +351,7 @@ EscrowCreate::preclaim(PreclaimContext const& ctx)
// because all writes to pseudo-account discriminator fields **are**
// amendment gated, hence the behaviour of this check will always match the
// currently active amendments.
if (isPseudoAccount(sled))
if (isPseudoAccount(sled.sle()))
return tecNO_PERMISSION;
if (!isXRP(amount))
@@ -428,7 +429,7 @@ EscrowCreate::doApply()
if (ctx_.tx[~sfFinishAfter] && after(closeTime, ctx_.tx[sfFinishAfter]))
return tecNO_PERMISSION;
auto const sle = ctx_.view().peek(keylet::account(accountID_));
AccountRootEntry<ApplyView> sle{keylet::account(accountID_), ctx_.view()};
if (!sle)
return tefINTERNAL; // LCOV_EXCL_LINE
@@ -441,8 +442,8 @@ EscrowCreate::doApply()
// validates the sponsor's reserve + remaining credit. When
// unsponsored this hits the source branch and validates the
// source's pre-lock balance against base + (currentOC+1)*increment.
if (auto const ret =
checkReserve(ctx_.getApplyViewContext(), sle, balance, {.ownerCountDelta = 1}, j_);
if (auto const ret = checkReserve(
ctx_.getApplyViewContext(), sle.sle(), balance, {.ownerCountDelta = 1}, j_);
!isTesSuccess(ret))
return ret;
@@ -460,14 +461,17 @@ EscrowCreate::doApply()
// source only owes reserve for its current owners.
// - unsponsored: 1 — source owes reserve including the new increment.
auto const sourceReserve = accountReserve(
ctx_.view(), sle, j_, {.ownerCountDelta = getTxReserveSponsorID(ctx_.tx) ? 0 : 1});
ctx_.view(),
sle.sle(),
j_,
{.ownerCountDelta = getTxReserveSponsorID(ctx_.tx) ? 0 : 1});
if (balance - STAmount(amount).xrp() < sourceReserve)
return tecUNFUNDED;
}
// Check destination account
{
auto const sled = ctx_.view().read(keylet::account(ctx_.tx[sfDestination]));
AccountRootEntry<ReadView> const sled{keylet::account(ctx_.tx[sfDestination]), ctx_.view()};
if (!sled)
return tecNO_DST; // LCOV_EXCL_LINE
if (sled->isFlag(lsfRequireDestTag) && !ctx_.tx[~sfDestinationTag])
@@ -477,7 +481,9 @@ EscrowCreate::doApply()
// Create escrow in ledger. Note that we use the value from the
// sequence or ticket. For more explanation see comments in SeqProxy.h.
Keylet const escrowKeylet = keylet::escrow(accountID_, ctx_.tx.getSeqValue());
auto const slep = std::make_shared<SLE>(escrowKeylet);
// Build with the ApplyViewContext so create() honors reserve sponsorship.
EscrowEntry<ApplyView> slep{escrowKeylet, ctx_.getApplyViewContext()};
slep.newSLE();
(*slep)[sfAmount] = amount;
(*slep)[sfAccount] = accountID_;
(*slep)[~sfCondition] = ctx_.tx[~sfCondition];
@@ -499,45 +505,12 @@ EscrowCreate::doApply()
(*slep)[sfTransferRate] = xferRate.value;
}
ctx_.view().insert(slep);
// Add escrow to sender's owner directory
{
auto page = ctx_.view().dirInsert(
keylet::ownerDir(accountID_), escrowKeylet, describeOwnerDir(accountID_));
if (!page)
return tecDIR_FULL; // LCOV_EXCL_LINE
(*slep)[sfOwnerNode] = *page;
}
// If it's not a self-send, add escrow to recipient's owner directory.
AccountID const dest = ctx_.tx[sfDestination];
if (dest != accountID_)
{
auto page =
ctx_.view().dirInsert(keylet::ownerDir(dest), escrowKeylet, describeOwnerDir(dest));
if (!page)
return tecDIR_FULL; // LCOV_EXCL_LINE
(*slep)[sfDestinationNode] = *page;
}
// IOU escrow objects are added to the issuer's owner directory to help
// track the total locked balance. For MPT, this isn't necessary because the
// locked balance is already stored directly in the MPTokenIssuance object.
// Deduct/lock the escrowed amount from the sender.
AccountID const issuer = amount.getIssuer();
if (!isXRP(amount) && issuer != accountID_ && issuer != dest && !amount.holds<MPTIssue>())
{
auto page =
ctx_.view().dirInsert(keylet::ownerDir(issuer), escrowKeylet, describeOwnerDir(issuer));
if (!page)
return tecDIR_FULL; // LCOV_EXCL_LINE
(*slep)[sfIssuerNode] = *page;
}
// Deduct owner's balance
if (isXRP(amount))
{
(*sle)[sfBalance] = (*sle)[sfBalance] - amount;
sle.update();
}
else
{
@@ -552,11 +525,11 @@ EscrowCreate::doApply()
}
}
// increment owner count
increaseOwnerCount(ctx_.getApplyViewContext(), sle, 1, ctx_.journal);
addSponsorToLedgerEntry(ctx_.getApplyViewContext(), slep);
ctx_.view().update(sle);
return tesSUCCESS;
// Link the escrow into the sender's owner directory (counts toward reserve),
// plus the destination and (for IOU) issuer tracking directories where
// applicable, bump the sender's OwnerCount, stamp any reserve sponsor, and
// insert. See EscrowEntry::ownerDirs() and SLEBase::create().
return slep.create(preFeeBalance_);
}
void
@@ -14,6 +14,7 @@
#include <xrpl/ledger/helpers/EscrowHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Concepts.h>
@@ -173,7 +174,7 @@ escrowFinishPreclaimHelper<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);
MPTokenIssuanceEntry<ReadView> const sleIssuance{issuanceKey, ctx.view};
if (!sleIssuance)
return tecOBJECT_NOT_FOUND;
@@ -204,7 +205,7 @@ EscrowFinish::preclaim(PreclaimContext const& ctx)
if (ctx.view.rules().enabled(featureTokenEscrow))
{
auto const k = keylet::escrow(ctx.tx[sfOwner], ctx.tx[sfOfferSequence]);
auto const slep = ctx.view.read(k);
EscrowEntry<ReadView> const slep{k, ctx.view};
if (!slep)
return tecNO_TARGET;
@@ -229,7 +230,7 @@ TER
EscrowFinish::doApply()
{
auto const k = keylet::escrow(ctx_.tx[sfOwner], ctx_.tx[sfOfferSequence]);
auto const slep = ctx_.view().peek(k);
EscrowEntry<ApplyView> slep{k, ctx_.view()};
if (!slep)
{
if (ctx_.view().rules().enabled(featureTokenEscrow))
@@ -305,17 +306,21 @@ EscrowFinish::doApply()
// NOTE: Escrow payments cannot be used to fund accounts.
AccountID const destID = (*slep)[sfDestination];
auto const sled = ctx_.view().peek(keylet::account(destID));
AccountRootEntry<ApplyView> sled{keylet::account(destID), ctx_.view()};
if (!sled)
return tecNO_DST;
if (auto err =
verifyDepositPreauth(ctx_.tx, ctx_.view(), accountID_, destID, sled, ctx_.journal);
if (auto err = verifyDepositPreauth(
ctx_.tx, ctx_.view(), accountID_, destID, sled.sle(), ctx_.journal);
!isTesSuccess(err))
return err;
AccountID const account = (*slep)[sfAccount];
// Escrow deletion is not expressed via EscrowEntry::destroy() here because
// the reserve release is consensus-ordered relative to token delivery (see
// below), which the wrapper cannot express.
//
// Remove escrow from owner directory
{
auto const page = (*slep)[sfOwnerNode];
@@ -348,7 +353,7 @@ EscrowFinish::doApply()
// arithmetic for self-escrows and would break consensus if not gated.
bool const sponsorEnabled = ctx_.view().rules().enabled(featureSponsor);
if (sponsorEnabled)
decreaseOwnerCountForObject(ctx_.view(), account, slep, 1, ctx_.journal);
decreaseOwnerCountForObject(ctx_.view(), account, slep.mutableSle(), 1, ctx_.journal);
STAmount const amount = slep->getFieldAmount(sfAmount);
// Transfer amount to destination
@@ -371,7 +376,7 @@ EscrowFinish::doApply()
return escrowUnlockApplyHelper<T>(
ctx_.getApplyViewContext(),
lockedRate,
sled,
sled.mutableSle(),
preFeeBalance_,
amount,
issuer,
@@ -384,27 +389,27 @@ EscrowFinish::doApply()
!isTesSuccess(ret))
return ret;
// Remove escrow from issuers owner directory, if present.
// Remove escrow from issuer's owner directory, if present.
if (auto const optPage = (*slep)[~sfIssuerNode]; optPage)
{
if (!ctx_.view().dirRemove(keylet::ownerDir(issuer), *optPage, k.key, true))
{
// LCOV_EXCL_START
JLOG(j_.fatal()) << "Unable to delete Escrow from recipient.";
JLOG(j_.fatal()) << "Unable to delete Escrow from issuer.";
return tefBAD_LEDGER;
// LCOV_EXCL_STOP
}
}
}
ctx_.view().update(sled);
sled.update();
// Adjust source owner count (legacy position, pre-Sponsor)
// Adjust source owner count (legacy position, pre-Sponsor).
if (!sponsorEnabled)
decreaseOwnerCountForObject(ctx_.view(), account, slep, 1, ctx_.journal);
decreaseOwnerCountForObject(ctx_.view(), account, slep.mutableSle(), 1, ctx_.journal);
// Remove escrow from ledger
ctx_.view().erase(slep);
slep.erase();
return tesSUCCESS;
}
@@ -7,6 +7,7 @@
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/LendingHelpers.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Asset.h>
@@ -161,7 +162,7 @@ determineClawAmount(
SLE const& sleBroker,
Asset const& vaultAsset,
std::optional<STAmount> const& amount,
SLE::const_ref vaultSle,
VaultEntry<ReadView> const& vaultSle,
Rules const& rules)
{
auto const maxClawAmount = [&]() {
@@ -293,8 +294,8 @@ LoanBrokerCoverClawback::preclaim(PreclaimContext const& ctx)
}
}
auto const findClawAmount =
determineClawAmount(*sleBroker, vaultAsset, amount, vault, ctx.view.rules());
auto const findClawAmount = determineClawAmount(
*sleBroker, vaultAsset, amount, VaultEntry<ReadView>{vault, ctx.view}, ctx.view.rules());
if (!findClawAmount)
{
JLOG(ctx.j.warn()) << "LoanBroker cover is already at minimum.";
@@ -303,7 +304,10 @@ LoanBrokerCoverClawback::preclaim(PreclaimContext const& ctx)
STAmount const& clawAmount = *findClawAmount;
if (auto const ret = canApplyToBrokerCover(
ctx.view, sleBroker, vaultAsset, clawAmount, ctx.j, "LoanBrokerCoverClawback"))
LoanBrokerEntry<ReadView>{sleBroker, ctx.view, ctx.j},
vaultAsset,
clawAmount,
"LoanBrokerCoverClawback"))
return ret;
// Explicitly check the balance of the trust line / MPT to make sure the
@@ -356,8 +360,8 @@ LoanBrokerCoverClawback::doApply()
auto const vaultAsset = vault->at(sfAsset);
auto const findClawAmount =
determineClawAmount(*sleBroker, vaultAsset, amount, vault, view().rules());
auto const findClawAmount = determineClawAmount(
*sleBroker, vaultAsset, amount, VaultEntry<ReadView>{vault, view()}, view().rules());
if (!findClawAmount)
return tecINTERNAL; // LCOV_EXCL_LINE
STAmount const& clawAmount = *findClawAmount;
@@ -6,6 +6,7 @@
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/LendingHelpers.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
@@ -96,7 +97,10 @@ LoanBrokerCoverWithdraw::preclaim(PreclaimContext const& ctx)
// Helper handles both IOU and MPT correctly without explicit branching.
if (auto const ret = canApplyToBrokerCover(
ctx.view, sleBroker, vaultAsset, amount, ctx.j, "LoanBrokerCoverWithdraw"))
LoanBrokerEntry<ReadView>{sleBroker, ctx.view, ctx.j},
vaultAsset,
amount,
"LoanBrokerCoverWithdraw"))
return ret;
// The broker's pseudo-account is the source of funds.
@@ -152,7 +156,9 @@ LoanBrokerCoverWithdraw::preclaim(PreclaimContext const& ctx)
if (fix320Enabled)
{
return minimumBrokerCover(
currentDebtTotal, TenthBips32{sleBroker->at(sfCoverRateMinimum)}, vault);
currentDebtTotal,
TenthBips32{sleBroker->at(sfCoverRateMinimum)},
VaultEntry<ReadView>{vault, ctx.view});
}
// Always round the minimum required up.
@@ -3,8 +3,9 @@
#include <xrpl/basics/Log.h>
#include <xrpl/basics/Number.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/helpers/LendingHelpers.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/Feature.h>
@@ -78,7 +79,7 @@ LoanBrokerDelete::preclaim(PreclaimContext const& ctx)
{
// Any remaining debt should have been wiped out by the last Loan
// Delete. This check is purely defensive.
auto const scale = getAssetsTotalScale(vault);
auto const scale = getAssetsTotalScale(VaultEntry<ReadView>{vault, ctx.view});
auto const rounded =
roundToAsset(asset, debtTotal, scale, Number::RoundingMode::TowardsZero);
@@ -129,29 +130,17 @@ LoanBrokerDelete::doApply()
auto const brokerID = tx[sfLoanBrokerID];
// Delete the loan broker
auto broker = view().peek(keylet::loanBroker(brokerID));
LoanBrokerEntry<ApplyView> broker{keylet::loanBroker(brokerID), view(), j_};
if (!broker)
return tefBAD_LEDGER; // LCOV_EXCL_LINE
auto const vaultID = broker->at(sfVaultID);
auto const sleVault = view().read(keylet::vault(vaultID));
if (!sleVault)
return tefBAD_LEDGER; // LCOV_EXCL_LINE
auto const vaultPseudoID = sleVault->at(sfAccount);
auto const vaultAsset = sleVault->at(sfAsset);
auto const brokerPseudoID = broker->at(sfAccount);
if (!view().dirRemove(
keylet::ownerDir(accountID_), broker->at(sfOwnerNode), broker->key(), false))
{
return tefBAD_LEDGER; // LCOV_EXCL_LINE
}
if (!view().dirRemove(
keylet::ownerDir(vaultPseudoID), broker->at(sfVaultNode), broker->key(), false))
{
return tefBAD_LEDGER; // LCOV_EXCL_LINE
}
{
auto const coverAvailable = STAmount{vaultAsset, broker->at(sfCoverAvailable)};
if (auto const ter = accountSend(
@@ -186,17 +175,11 @@ LoanBrokerDelete::doApply()
view().erase(brokerPseudoSLE);
{
auto owner = view().peek(keylet::account(accountID_));
if (!owner)
return tefBAD_LEDGER; // LCOV_EXCL_LINE
// Decreases the owner count by two: one for the LoanBroker object, and
// one for the pseudo-account.
decreaseOwnerCountForObject(view(), owner, broker, 2, j_);
}
view().erase(broker);
// Unlink the broker from its owner's directory and the vault pseudo-account's
// directory, decrement the owner's OwnerCount by 2 (broker + pseudo-account,
// refunding any reserve sponsor), and erase it. See LoanBrokerEntry.
if (auto const ter = broker.destroy(); !isTesSuccess(ter))
return ter; // LCOV_EXCL_LINE
associateAsset(*broker, vaultAsset);
@@ -4,9 +4,9 @@
#include <xrpl/basics/Number.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/LendingHelpers.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/Indexes.h>
@@ -216,7 +216,6 @@ LoanBrokerSet::doApply()
return tefBAD_LEDGER;
// LCOV_EXCL_STOP
}
auto const vaultPseudoID = sleVault->at(sfAccount);
auto const vaultAsset = sleVault->at(sfAsset);
auto const sequence = tx.getSeqValue();
@@ -229,18 +228,13 @@ LoanBrokerSet::doApply()
return tefBAD_LEDGER;
// LCOV_EXCL_STOP
}
auto broker = std::make_shared<SLE>(keylet::loanBroker(accountID_, sequence));
LoanBrokerEntry<ApplyView> broker{keylet::loanBroker(accountID_, sequence), view, j_};
broker.newSLE();
if (auto const ter = dirLink(view, accountID_, broker))
return ter; // LCOV_EXCL_LINE
if (auto const ter = dirLink(view, vaultPseudoID, broker, sfVaultNode))
return ter; // LCOV_EXCL_LINE
// Increases the owner count by two: one for the LoanBroker object, and
// one for the pseudo-account.
increaseOwnerCount(view, owner, {}, 2, j_);
if (preFeeBalance_ < accountReserve(view, owner, j_))
return tecINSUFFICIENT_RESERVE;
// sfVaultID and sfOwner must be set before create(), which reads them
// (and looks up the vault's pseudo-account) to link the directories.
broker->at(sfVaultID) = vaultID;
broker->at(sfOwner) = accountID_;
auto maybePseudo = createPseudoAccount(view, broker->key(), sfLoanBrokerID);
if (!maybePseudo)
@@ -254,8 +248,6 @@ LoanBrokerSet::doApply()
// Initialize data fields:
broker->at(sfSequence) = sequence;
broker->at(sfVaultID) = vaultID;
broker->at(sfOwner) = accountID_;
broker->at(sfAccount) = pseudoId;
// The LoanSequence indexes loans created by this broker, starting at 1
broker->at(sfLoanSequence) = 1;
@@ -270,7 +262,12 @@ LoanBrokerSet::doApply()
if (auto const coverLiq = tx[~sfCoverRateLiquidation])
broker->at(sfCoverRateLiquidation) = *coverLiq;
view.insert(broker);
// Reserve check (owner's pre-fee balance, charging two slots for the
// broker and its pseudo-account) + link into the owner's directory
// (counted) and the vault pseudo-account's directory (tracking) + bump
// the owner's OwnerCount by 2 + insert. See LoanBrokerEntry.
if (auto const ter = broker.create(preFeeBalance_); !isTesSuccess(ter))
return ter;
associateAsset(*broker, vaultAsset);
}
@@ -4,8 +4,10 @@
#include <xrpl/basics/Number.h> // IWYU pragma: keep
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/LendingHelpers.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STAmount.h> // IWYU pragma: keep
@@ -76,7 +78,7 @@ LoanDelete::doApply()
auto& view = ctx_.view();
auto const loanID = tx[sfLoanID];
auto const loanSle = view.peek(keylet::loan(loanID));
LoanEntry<ApplyView> loanSle{keylet::loan(loanID), view, j_};
if (!loanSle)
return tefBAD_LEDGER; // LCOV_EXCL_LINE
auto const borrower = loanSle->at(sfBorrower);
@@ -88,23 +90,16 @@ LoanDelete::doApply()
auto const brokerSle = view.peek(keylet::loanBroker(brokerID));
if (!brokerSle)
return tefBAD_LEDGER; // LCOV_EXCL_LINE
auto const brokerPseudoAccount = brokerSle->at(sfAccount);
auto const vaultSle = view.peek(keylet::vault(brokerSle->at(sfVaultID)));
if (!vaultSle)
return tefBAD_LEDGER; // LCOV_EXCL_LINE
auto const vaultAsset = vaultSle->at(sfAsset);
// Remove LoanID from Directory of the LoanBroker pseudo-account.
if (!view.dirRemove(
keylet::ownerDir(brokerPseudoAccount), loanSle->at(sfLoanBrokerNode), loanID, false))
return tefBAD_LEDGER; // LCOV_EXCL_LINE
// Remove LoanID from Directory of the Borrower.
if (!view.dirRemove(keylet::ownerDir(borrower), loanSle->at(sfOwnerNode), loanID, false))
return tefBAD_LEDGER; // LCOV_EXCL_LINE
// Delete the Loan object
view.erase(loanSle);
// Unlink the loan from the LoanBroker pseudo-account's directory and the
// borrower's directory, and erase it. See LoanEntry::destroy().
if (auto const ter = loanSle.destroy(); !isTesSuccess(ter))
return ter; // LCOV_EXCL_LINE
// Decrement the LoanBroker's owner count.
adjustLoanBrokerOwnerCount(view, brokerSle, -1, j_);
@@ -120,7 +115,7 @@ LoanDelete::doApply()
roundToAsset(
vaultSle->at(sfAsset),
debtTotalProxy,
getAssetsTotalScale(vaultSle),
getAssetsTotalScale(VaultEntry<ReadView>{vaultSle, view}),
Number::RoundingMode::TowardsZero) == beast::kZero,
"xrpl::LoanDelete::doApply",
"last loan, remaining debt rounds to zero");
@@ -128,7 +123,7 @@ LoanDelete::doApply()
}
}
// Decrement the borrower's owner count
decreaseOwnerCountForObject(view, borrowerSle, loanSle, 1, j_);
decreaseOwnerCountForObject(view, borrowerSle, loanSle.mutableSle(), 1, j_);
// These associations shouldn't do anything, but do them just to be safe
associateAsset(*loanSle, vaultAsset);
@@ -7,6 +7,7 @@
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/LendingHelpers.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/Feature.h>
@@ -128,7 +129,7 @@ LoanManage::preclaim(PreclaimContext const& ctx)
}
static Number
owedToVault(SLE::ref loanSle)
owedToVault(LoanEntry<ApplyView> const& loanSle)
{
// Spec section 3.2.3.2, defines the default amount as
//
@@ -146,13 +147,14 @@ owedToVault(SLE::ref loanSle)
TER
LoanManage::defaultLoan(
ApplyView& view,
SLE::ref loanSle,
SLE::ref brokerSle,
SLE::ref vaultSle,
Asset const& vaultAsset,
beast::Journal j)
LoanEntry<ApplyView>& loanSle,
LoanBrokerEntry<ApplyView>& brokerSle,
VaultEntry<ApplyView>& vaultSle,
Asset const& vaultAsset)
{
ApplyView& view = loanSle.applyView();
beast::Journal const j = loanSle.journal();
// Calculate the amount of the Default that First-Loss Capital covers:
std::int32_t const loanScale = loanSle->at(sfLoanScale);
@@ -251,7 +253,7 @@ LoanManage::defaultLoan(
adjustImpreciseNumber(
vaultLossUnrealizedProxy, -totalDefaultAmount, vaultAsset, vaultScale);
}
view.update(vaultSle);
vaultSle.update();
}
// Update the LoanBroker object:
@@ -269,7 +271,7 @@ LoanManage::defaultLoan(
// LCOV_EXCL_STOP
}
coverAvailableProxy -= defaultCovered;
view.update(brokerSle);
brokerSle.update();
}
// Update the Loan object:
@@ -282,7 +284,7 @@ LoanManage::defaultLoan(
// Zero out the next due date. Since it's default, it'll be removed from
// the object.
loanSle->at(sfNextPaymentDueDate) = 0;
view.update(loanSle);
loanSle.update();
// Return funds from the LoanBroker pseudo-account to the
// Vault pseudo-account:
@@ -298,12 +300,13 @@ LoanManage::defaultLoan(
TER
LoanManage::impairLoan(
ApplyView& view,
SLE::ref loanSle,
SLE::ref vaultSle,
Asset const& vaultAsset,
beast::Journal j)
LoanEntry<ApplyView>& loanSle,
VaultEntry<ApplyView>& vaultSle,
Asset const& vaultAsset)
{
ApplyView const& view = loanSle.applyView();
beast::Journal const j = loanSle.journal();
Number const lossUnrealized = owedToVault(loanSle);
// The vault may be at a different scale than the loan. Reduce rounding
@@ -322,7 +325,7 @@ LoanManage::impairLoan(
"corrupt the vault.";
return tecLIMIT_EXCEEDED;
}
view.update(vaultSle);
vaultSle.update();
// Update the Loan object
loanSle->setFlag(lsfLoanImpaired);
@@ -333,19 +336,20 @@ LoanManage::impairLoan(
// move the next payment due date to now
loanNextDueProxy = view.parentCloseTime().time_since_epoch().count();
}
view.update(loanSle);
loanSle.update();
return tesSUCCESS;
}
[[nodiscard]] TER
LoanManage::unimpairLoan(
ApplyView& view,
SLE::ref loanSle,
SLE::ref vaultSle,
Asset const& vaultAsset,
beast::Journal j)
LoanEntry<ApplyView>& loanSle,
VaultEntry<ApplyView>& vaultSle,
Asset const& vaultAsset)
{
ApplyView const& view = loanSle.applyView();
beast::Journal const j = loanSle.journal();
// The vault may be at a different scale than the loan. Reduce rounding
// errors during the accounting by rounding some of the values to that
// scale.
@@ -364,7 +368,7 @@ LoanManage::unimpairLoan(
// Reverse the "paper loss"
adjustImpreciseNumber(vaultLossUnrealizedProxy, -lossReversed, vaultAsset, vaultScale);
view.update(vaultSle);
vaultSle.update();
// Update the Loan object
loanSle->clearFlag(lsfLoanImpaired);
@@ -382,7 +386,7 @@ LoanManage::unimpairLoan(
loanSle->at(sfNextPaymentDueDate) =
view.parentCloseTime().time_since_epoch().count() + paymentInterval;
}
view.update(loanSle);
loanSle.update();
return tesSUCCESS;
}
@@ -394,16 +398,16 @@ LoanManage::doApply()
auto& view = ctx_.view();
auto const loanID = tx[sfLoanID];
auto const loanSle = view.peek(keylet::loan(loanID));
LoanEntry<ApplyView> loanSle{keylet::loan(loanID), view, j_};
if (!loanSle)
return tefBAD_LEDGER; // LCOV_EXCL_LINE
auto const brokerID = loanSle->at(sfLoanBrokerID);
auto const brokerSle = view.peek(keylet::loanBroker(brokerID));
LoanBrokerEntry<ApplyView> brokerSle{keylet::loanBroker(brokerID), view, j_};
if (!brokerSle)
return tefBAD_LEDGER; // LCOV_EXCL_LINE
auto const vaultSle = view.peek(keylet::vault(brokerSle->at(sfVaultID)));
VaultEntry<ApplyView> vaultSle{keylet::vault(brokerSle->at(sfVaultID)), view, j_};
if (!vaultSle)
return tefBAD_LEDGER; // LCOV_EXCL_LINE
auto const vaultAsset = vaultSle->at(sfAsset);
@@ -412,11 +416,11 @@ LoanManage::doApply()
// Valid flag combinations are checked in preflight. No flags is valid -
// just a noop.
if (tx.isFlag(tfLoanDefault))
return defaultLoan(view, loanSle, brokerSle, vaultSle, vaultAsset, j_);
return defaultLoan(loanSle, brokerSle, vaultSle, vaultAsset);
if (tx.isFlag(tfLoanImpair))
return impairLoan(view, loanSle, vaultSle, vaultAsset, j_);
return impairLoan(loanSle, vaultSle, vaultAsset);
if (tx.isFlag(tfLoanUnimpair))
return unimpairLoan(view, loanSle, vaultSle, vaultAsset, j_);
return unimpairLoan(loanSle, vaultSle, vaultAsset);
// NoOp, as described above.
return tesSUCCESS;
}();
+10 -8
View File
@@ -5,9 +5,11 @@
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/json/to_string.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/LendingHelpers.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
@@ -287,19 +289,19 @@ LoanPay::doApply()
auto const amount = tx[sfAmount];
auto const loanID = tx[sfLoanID];
auto const loanSle = view.peek(keylet::loan(loanID));
LoanEntry<ApplyView> loanSle{keylet::loan(loanID), view, j_};
if (!loanSle)
return tefBAD_LEDGER; // LCOV_EXCL_LINE
std::int32_t const loanScale = loanSle->at(sfLoanScale);
auto const brokerID = loanSle->at(sfLoanBrokerID);
auto const brokerSle = view.peek(keylet::loanBroker(brokerID));
LoanBrokerEntry<ApplyView> brokerSle{keylet::loanBroker(brokerID), view};
if (!brokerSle)
return tefBAD_LEDGER; // LCOV_EXCL_LINE
auto const brokerOwner = brokerSle->at(sfOwner);
auto const brokerPseudoAccount = brokerSle->at(sfAccount);
auto const vaultID = brokerSle->at(sfVaultID);
auto const vaultSle = view.peek(keylet::vault(vaultID));
VaultEntry<ApplyView> vaultSle{keylet::vault(vaultID), view, j_};
if (!vaultSle)
return tefBAD_LEDGER; // LCOV_EXCL_LINE
auto const vaultPseudoAccount = vaultSle->at(sfAccount);
@@ -362,7 +364,7 @@ LoanPay::doApply()
// change will be discarded.
if (loanSle->isFlag(lsfLoanImpaired))
{
if (auto const ret = LoanManage::unimpairLoan(view, loanSle, vaultSle, asset, j_))
if (auto const ret = LoanManage::unimpairLoan(loanSle, vaultSle, asset))
{
JLOG(j_.fatal()) << "Failed to unimpair loan before payment.";
return ret; // LCOV_EXCL_LINE
@@ -381,7 +383,7 @@ LoanPay::doApply()
}();
std::expected<LoanPaymentParts, TER> const paymentParts =
loanMakePayment(asset, view, loanSle, brokerSle, amount, paymentType, j_);
loanMakePayment(asset, loanSle, brokerSle, amount, paymentType);
if (!paymentParts)
{
@@ -392,7 +394,7 @@ LoanPay::doApply()
// If the payment computation completed without error, the loanSle object
// has been modified.
view.update(loanSle);
loanSle.update();
XRPL_ASSERT_PARTS(
// It is possible to pay 0 principal
@@ -427,7 +429,7 @@ LoanPay::doApply()
//------------------------------------------------------
// LoanBroker object state changes
view.update(brokerSle);
brokerSle.update();
auto assetsAvailableProxy = vaultSle->at(sfAssetsAvailable);
auto assetsTotalProxy = vaultSle->at(sfAssetsTotal);
@@ -468,7 +470,7 @@ LoanPay::doApply()
//------------------------------------------------------
// Vault object state changes
view.update(vaultSle);
vaultSle.update();
Number const assetsAvailableBefore = *assetsAvailableProxy;
Number const assetsTotalBefore = *assetsTotalProxy;
+10 -10
View File
@@ -5,9 +5,9 @@
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/LendingHelpers.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
@@ -388,7 +388,7 @@ LoanSet::doApply()
if (!brokerOwnerSle)
return tefBAD_LEDGER; // LCOV_EXCL_LINE
auto const vaultSle = view.peek(keylet ::vault(brokerSle->at(sfVaultID)));
VaultEntry<ApplyView> vaultSle{keylet::vault(brokerSle->at(sfVaultID)), view};
if (!vaultSle)
return tefBAD_LEDGER; // LCOV_EXCL_LINE
auto const vaultPseudo = vaultSle->at(sfAccount);
@@ -593,7 +593,8 @@ LoanSet::doApply()
auto loanSequenceProxy = brokerSle->at(sfLoanSequence);
// Create the loan
auto loan = std::make_shared<SLE>(keylet::loan(brokerID, *loanSequenceProxy));
LoanEntry<ApplyView> loan{keylet::loan(brokerID, *loanSequenceProxy), view, j_};
loan.newSLE();
// Prevent copy/paste errors
auto setLoanField = [&loan, &tx](auto const& field, std::uint32_t const defValue = 0) {
@@ -630,7 +631,6 @@ LoanSet::doApply()
loan->at(sfPreviousPaymentDueDate) = 0;
loan->at(sfNextPaymentDueDate) = startDate + paymentInterval;
loan->at(sfPaymentRemaining) = paymentTotal;
view.insert(loan);
// Update the balances in the vault
vaultAvailableProxy -= principalRequested;
@@ -639,7 +639,7 @@ LoanSet::doApply()
*vaultAvailableProxy <= *vaultTotalProxy,
"xrpl::LoanSet::doApply",
"assets available must not be greater than assets outstanding");
view.update(vaultSle);
vaultSle.update();
// Update the balances in the loan broker
adjustImpreciseNumber(brokerSle->at(sfDebtTotal), newDebtDelta, vaultAsset, vaultScale);
@@ -651,11 +651,11 @@ LoanSet::doApply()
return tecMAX_SEQUENCE_REACHED;
view.update(brokerSle);
// Put the loan into the pseudo-account's directory
if (auto const ter = dirLink(view, brokerPseudo, loan, sfLoanBrokerNode))
return ter;
// Borrower is the owner of the loan
if (auto const ter = dirLink(view, borrower, loan, sfOwnerNode))
// Insert the loan and link it into the broker pseudo-account's directory
// (sfLoanBrokerNode) and the borrower's directory (sfOwnerNode). The
// OwnerCount bumps (borrower account, LoanBroker object) were applied above.
// See LoanEntry::create().
if (auto const ter = loan.create(); !isTesSuccess(ter))
return ter;
associateAsset(*vaultSle, vaultAsset);
@@ -4,9 +4,11 @@
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/NFTokenHelpers.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
@@ -55,16 +57,16 @@ TER
NFTokenAcceptOffer::preclaim(PreclaimContext const& ctx)
{
auto const checkOffer =
[&ctx](std::optional<uint256> id) -> std::pair<SLE::const_pointer, TER> {
[&ctx](std::optional<uint256> id) -> std::pair<NFTokenOfferEntry<ReadView>, TER> {
if (id)
{
if (id->isZero())
return {nullptr, tecOBJECT_NOT_FOUND};
return {NFTokenOfferEntry<ReadView>{nullptr, ctx.view}, tecOBJECT_NOT_FOUND};
auto offerSLE = ctx.view.read(keylet::nftokenOffer(*id));
NFTokenOfferEntry<ReadView> offerSLE{keylet::nftokenOffer(*id), ctx.view};
if (!offerSLE)
return {nullptr, tecOBJECT_NOT_FOUND};
return {NFTokenOfferEntry<ReadView>{nullptr, ctx.view}, tecOBJECT_NOT_FOUND};
if (hasExpired(ctx.view, (*offerSLE)[~sfExpiration]))
{
@@ -72,16 +74,16 @@ NFTokenAcceptOffer::preclaim(PreclaimContext const& ctx)
// leaving them on ledger forever. After the amendment, we allow expired offers to
// reach doApply() where they get deleted and tecEXPIRED is returned.
if (!ctx.view.rules().enabled(fixCleanup3_1_3))
return {nullptr, tecEXPIRED};
return {NFTokenOfferEntry<ReadView>{nullptr, ctx.view}, tecEXPIRED};
// Amendment enabled: return the expired offer to be handled in doApply.
}
if ((*offerSLE)[sfAmount].negative())
return {nullptr, temBAD_OFFER};
return {NFTokenOfferEntry<ReadView>{nullptr, ctx.view}, temBAD_OFFER};
return {std::move(offerSLE), tesSUCCESS};
}
return {nullptr, tesSUCCESS};
return {NFTokenOfferEntry<ReadView>{nullptr, ctx.view}, tesSUCCESS};
};
auto const [bo, err1] = checkOffer(ctx.tx[~sfNFTokenBuyOffer]);
@@ -396,7 +398,7 @@ NFTokenAcceptOffer::transferNFToken(
}
TER
NFTokenAcceptOffer::acceptOffer(SLE::ref offer)
NFTokenAcceptOffer::acceptOffer(NFTokenOfferEntry<ApplyView> const& offer)
{
bool const isSell = offer->isFlag(lsfSellNFToken);
AccountID const owner = (*offer)[sfOwner];
@@ -434,10 +436,9 @@ TER
NFTokenAcceptOffer::doApply()
{
auto const loadToken = [this](std::optional<uint256> const& id) {
SLE::pointer sle;
if (id)
sle = view().peek(keylet::nftokenOffer(*id));
return sle;
return NFTokenOfferEntry<ApplyView>{keylet::nftokenOffer(*id), view()};
return NFTokenOfferEntry<ApplyView>{SLE::pointer{}, view()};
};
auto bo = loadToken(ctx_.tx[~sfNFTokenBuyOffer]);
@@ -449,11 +450,12 @@ NFTokenAcceptOffer::doApply()
{
bool foundExpired = false;
auto const deleteOfferIfExpired = [this, &foundExpired](SLE::ref offer) -> TER {
auto const deleteOfferIfExpired =
[this, &foundExpired](NFTokenOfferEntry<ApplyView>& offer) -> TER {
if (offer && hasExpired(view(), (*offer)[~sfExpiration]))
{
JLOG(j_.trace()) << "Offer is expired, deleting: " << offer->key();
if (!nft::deleteTokenOffer(view(), offer))
if (!nft::deleteTokenOffer(view(), offer.mutableSle()))
{
// LCOV_EXCL_START
JLOG(j_.fatal())
@@ -476,7 +478,7 @@ NFTokenAcceptOffer::doApply()
return tecEXPIRED;
}
if (bo && !nft::deleteTokenOffer(view(), bo))
if (bo && !nft::deleteTokenOffer(view(), bo.mutableSle()))
{
// LCOV_EXCL_START
JLOG(j_.fatal()) << "Unable to delete buy offer '" << to_string(bo->key()) << "': ignoring";
@@ -484,7 +486,7 @@ NFTokenAcceptOffer::doApply()
// LCOV_EXCL_STOP
}
if (so && !nft::deleteTokenOffer(view(), so))
if (so && !nft::deleteTokenOffer(view(), so.mutableSle()))
{
// LCOV_EXCL_START
JLOG(j_.fatal()) << "Unable to delete sell offer '" << to_string(so->key())
@@ -3,10 +3,9 @@
#include <xrpl/basics/Log.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/OracleHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STTx.h>
@@ -14,8 +13,6 @@
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/Transactor.h>
#include <cstdint>
namespace xrpl {
NotTEC
@@ -27,12 +24,10 @@ OracleDelete::preflight(PreflightContext const& ctx)
TER
OracleDelete::preclaim(PreclaimContext const& ctx)
{
if (!ctx.view.exists(keylet::account(ctx.tx.getAccountID(sfAccount))))
return terNO_ACCOUNT; // LCOV_EXCL_LINE
auto const sle = OracleEntry<ReadView>{
ctx.tx.getAccountID(sfAccount), ctx.tx[sfOracleDocumentID], ctx.view, ctx.j};
auto const sle =
ctx.view.read(keylet::oracle(ctx.tx.getAccountID(sfAccount), ctx.tx[sfOracleDocumentID]));
if (!sle)
if (!sle.exists())
{
JLOG(ctx.j.debug()) << "Oracle Delete: Oracle does not exist.";
return tecNO_ENTRY;
@@ -59,32 +54,21 @@ OracleDelete::deleteOracle(
if (!sle)
return tecINTERNAL; // LCOV_EXCL_LINE
if (!view.dirRemove(keylet::ownerDir(account), (*sle)[sfOwnerNode], sle->key(), true))
{
// LCOV_EXCL_START
JLOG(j.fatal()) << "Unable to delete Oracle from owner.";
return tefBAD_LEDGER;
// LCOV_EXCL_STOP
}
auto const sleOwner = view.peek(keylet::account(account));
if (!sleOwner)
return tecINTERNAL; // LCOV_EXCL_LINE
std::uint32_t const count = calculateOracleReserve(sle);
decreaseOwnerCountForObject(view, sleOwner, sle, count, j);
view.erase(sle);
return tesSUCCESS;
// Unlink the Oracle from its owner's directory, decrement the owner's
// OwnerCount by reserveCount() (1, or 2 for a large price-data series,
// refunding any reserve sponsor), and erase it. See OracleEntry.
OracleEntry<ApplyView> oracle{sle, view, j};
return oracle.destroy();
}
TER
OracleDelete::doApply()
{
if (auto sle = ctx_.view().peek(keylet::oracle(accountID_, ctx_.tx[sfOracleDocumentID])))
return deleteOracle(ctx_.view(), sle, accountID_, j_);
auto const sle = OracleEntry<ApplyView>{accountID_, ctx_.tx[sfOracleDocumentID], ctx_.view()};
if (!sle.exists())
return tecINTERNAL; // LCOV_EXCL_LINE
return tecINTERNAL; // LCOV_EXCL_LINE
return deleteOracle(ctx_.view(), sle.mutableSle(), accountID_, j_);
}
void
+16 -23
View File
@@ -3,9 +3,10 @@
#include <xrpl/basics/chrono.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/OracleHelpers.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/InnerObjectFormats.h>
@@ -64,7 +65,8 @@ OracleSet::preflight(PreflightContext const& ctx)
TER
OracleSet::preclaim(PreclaimContext const& ctx)
{
auto const sleSetter = ctx.view.read(keylet::account(ctx.tx.getAccountID(sfAccount)));
AccountRootEntry<ReadView> const sleSetter{
keylet::account(ctx.tx.getAccountID(sfAccount)), ctx.view};
if (!sleSetter)
return terNO_ACCOUNT; // LCOV_EXCL_LINE
@@ -83,8 +85,8 @@ OracleSet::preclaim(PreclaimContext const& ctx)
lastUpdateTimeEpoch > (closeTime + kMaxLastUpdateTimeDelta))
return tecINVALID_UPDATE_TIME;
auto const sle =
ctx.view.read(keylet::oracle(ctx.tx.getAccountID(sfAccount), ctx.tx[sfOracleDocumentID]));
OracleEntry<ReadView> sle{
ctx.tx.getAccountID(sfAccount), ctx.tx[sfOracleDocumentID], ctx.view, ctx.j};
// token pairs to add/update
std::set<std::pair<Currency, Currency>> pairs;
@@ -153,7 +155,7 @@ OracleSet::preclaim(PreclaimContext const& ctx)
if (!pairsDel.empty())
return tecTOKEN_PAIR_NOT_FOUND;
auto const oldCount = calculateOracleReserve(sle);
auto const oldCount = sle.reserveCount();
auto const newCount = calculateOracleReserve(pairs);
adjustReserve = newCount - oldCount;
@@ -173,7 +175,7 @@ OracleSet::preclaim(PreclaimContext const& ctx)
return tecARRAY_TOO_LARGE;
auto const reserve =
accountReserve(ctx.view, sleSetter, ctx.j, {.ownerCountDelta = adjustReserve});
accountReserve(ctx.view, sleSetter.sle(), ctx.j, {.ownerCountDelta = adjustReserve});
auto const& balance = sleSetter->getFieldAmount(sfBalance);
if (balance < reserve)
@@ -216,8 +218,6 @@ setPriceDataInnerObjTemplate(STObject& obj)
TER
OracleSet::doApply()
{
auto const oracleID = keylet::oracle(accountID_, ctx_.tx[sfOracleDocumentID]);
auto populatePriceData = [](STObject& priceData, STObject const& entry) {
setPriceDataInnerObjTemplate(priceData);
priceData.setFieldCurrency(sfBaseAsset, entry.getFieldCurrency(sfBaseAsset));
@@ -227,7 +227,8 @@ OracleSet::doApply()
priceData.setFieldU8(sfScale, entry.getFieldU8(sfScale));
};
if (auto sle = ctx_.view().peek(oracleID))
OracleEntry<ApplyView> sle{accountID_, ctx_.tx[sfOracleDocumentID], ctx_.view()};
if (sle)
{
// update
// the token pair that doesn't have their price updated will not
@@ -287,13 +288,13 @@ OracleSet::doApply()
if (adjust != 0 && !adjustOracleOwnerCount(ctx_, adjust))
return tefINTERNAL; // LCOV_EXCL_LINE
ctx_.view().update(sle);
sle.update();
}
else
{
// create
sle = std::make_shared<SLE>(oracleID);
sle.newSLE();
sle->setAccountID(sfOwner, ctx_.tx.getAccountID(sfAccount));
if (ctx_.view().rules().enabled(fixIncludeKeyletFields))
{
@@ -326,18 +327,10 @@ OracleSet::doApply()
sle->setFieldVL(sfAssetClass, ctx_.tx[sfAssetClass]);
sle->setFieldU32(sfLastUpdateTime, ctx_.tx[sfLastUpdateTime]);
auto page = ctx_.view().dirInsert(
keylet::ownerDir(accountID_), sle->key(), describeOwnerDir(accountID_));
if (!page)
return tecDIR_FULL; // LCOV_EXCL_LINE
(*sle)[sfOwnerNode] = *page;
auto const count = calculateOracleReserve(series);
if (!adjustOracleOwnerCount(ctx_, count))
return tefINTERNAL; // LCOV_EXCL_LINE
ctx_.view().insert(sle);
// Reserve check (owner's pre-fee balance) + link into the owner
// directory + bump the owner's OwnerCount by reserveCount() (1, or 2
// for a large price-data series) + insert. See OracleEntry.
return sle.create(preFeeBalance_);
}
return tesSUCCESS;
@@ -4,10 +4,9 @@
#include <xrpl/basics/Slice.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/helpers/CredentialHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
@@ -21,7 +20,6 @@
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/Transactor.h>
#include <cstdint>
#include <memory>
#include <optional>
#include <set>
@@ -154,44 +152,20 @@ DepositPreauth::doApply()
auto applyViewContext = ctx_.getApplyViewContext();
if (ctx_.tx.isFieldPresent(sfAuthorize))
{
auto const sleOwner = view().peek(keylet::account(accountID_));
if (!sleOwner)
return {tefINTERNAL};
// A preauth counts against the reserve of the issuing account, but we
// check the starting balance because we want to allow dipping into the
// reserve to pay fees.
if (auto const ret = checkReserve(
applyViewContext, sleOwner, preFeeBalance_, {.ownerCountDelta = 1}, j_);
!isTesSuccess(ret))
return ret;
// Preclaim already verified that the Preauth entry does not yet exist.
// Create and populate the Preauth entry.
AccountID const auth{ctx_.tx[sfAuthorize]};
Keylet const preauthKeylet = keylet::depositPreauth(accountID_, auth);
auto slePreauth = std::make_shared<SLE>(preauthKeylet);
// Build with the ApplyViewContext so create() honors reserve sponsorship.
DepositPreauthEntry<ApplyView> slePreauth{preauthKeylet, applyViewContext};
slePreauth.newSLE();
slePreauth->setAccountID(sfAccount, accountID_);
slePreauth->setAccountID(sfAuthorize, auth);
view().insert(slePreauth);
auto const page = view().dirInsert(
keylet::ownerDir(accountID_), preauthKeylet, describeOwnerDir(accountID_));
JLOG(j_.trace()) << "Adding DepositPreauth to owner directory "
<< to_string(preauthKeylet.key) << ": " << (page ? "success" : "failure");
if (!page)
return tecDIR_FULL; // LCOV_EXCL_LINE
slePreauth->setFieldU64(sfOwnerNode, *page);
// If we succeeded, the new entry counts against the creator's reserve.
increaseOwnerCount(applyViewContext, sleOwner, 1, j_);
addSponsorToLedgerEntry(applyViewContext, slePreauth);
return slePreauth.create(preFeeBalance_);
}
else if (ctx_.tx.isFieldPresent(sfUnauthorize))
if (ctx_.tx.isFieldPresent(sfUnauthorize))
{
auto const preauth = keylet::depositPreauth(accountID_, ctx_.tx[sfUnauthorize]);
@@ -199,18 +173,6 @@ DepositPreauth::doApply()
}
else if (ctx_.tx.isFieldPresent(sfAuthorizeCredentials))
{
auto const sleOwner = view().peek(keylet::account(accountID_));
if (!sleOwner)
return tefINTERNAL; // LCOV_EXCL_LINE
// A preauth counts against the reserve of the issuing account, but we
// check the starting balance because we want to allow dipping into the
// reserve to pay fees.
if (auto const ret = checkReserve(
applyViewContext, sleOwner, preFeeBalance_, {.ownerCountDelta = 1}, j_);
!isTesSuccess(ret))
return ret;
// Preclaim already verified that the Preauth entry does not yet exist.
// Create and populate the Preauth entry.
@@ -226,29 +188,14 @@ DepositPreauth::doApply()
}
Keylet const preauthKey = keylet::depositPreauth(accountID_, sortedTX);
auto slePreauth = std::make_shared<SLE>(preauthKey);
if (!slePreauth)
return tefINTERNAL; // LCOV_EXCL_LINE
// Build with the ApplyViewContext so create() honors reserve sponsorship.
DepositPreauthEntry<ApplyView> slePreauth{preauthKey, applyViewContext};
slePreauth.newSLE();
slePreauth->setAccountID(sfAccount, accountID_);
slePreauth->peekFieldArray(sfAuthorizeCredentials) = std::move(sortedLE);
view().insert(slePreauth);
auto const page = view().dirInsert(
keylet::ownerDir(accountID_), preauthKey, describeOwnerDir(accountID_));
JLOG(j_.trace()) << "Adding DepositPreauth to owner directory " << to_string(preauthKey.key)
<< ": " << (page ? "success" : "failure");
if (!page)
return tecDIR_FULL; // LCOV_EXCL_LINE
slePreauth->setFieldU64(sfOwnerNode, *page);
// If we succeeded, the new entry counts against the creator's reserve.
increaseOwnerCount(applyViewContext, sleOwner, 1, j_);
addSponsorToLedgerEntry(applyViewContext, slePreauth);
return slePreauth.create(preFeeBalance_);
}
else if (ctx_.tx.isFieldPresent(sfUnauthorizeCredentials))
{
@@ -271,26 +218,8 @@ DepositPreauth::removeFromLedger(ApplyView& view, uint256 const& preauthIndex, b
return tecNO_ENTRY;
}
AccountID const account{(*slePreauth)[sfAccount]};
std::uint64_t const page{(*slePreauth)[sfOwnerNode]};
if (!view.dirRemove(keylet::ownerDir(account), page, preauthIndex, false))
{
// LCOV_EXCL_START
JLOG(j.fatal()) << "Unable to delete DepositPreauth from owner.";
return tefBAD_LEDGER;
// LCOV_EXCL_STOP
}
// If we succeeded, update the DepositPreauth owner's reserve.
auto const sleOwner = view.peek(keylet::account(account));
if (!sleOwner)
return tefINTERNAL; // LCOV_EXCL_LINE
decreaseOwnerCountForObject(view, sleOwner, slePreauth, 1, j);
// Remove DepositPreauth from ledger.
view.erase(slePreauth);
return tesSUCCESS;
DepositPreauthEntry<ApplyView> preauth{slePreauth, view, j};
return preauth.destroy();
}
void
@@ -5,9 +5,9 @@
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/helpers/CredentialHelpers.h>
#include <xrpl/ledger/helpers/PaymentChannelHelpers.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Keylet.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/PayChan.h>
@@ -110,7 +110,7 @@ TER
PaymentChannelClaim::doApply()
{
Keylet const k(ltPAYCHAN, ctx_.tx[sfChannel]);
auto const slep = ctx_.view().peek(k);
PayChannelEntry<ApplyView> slep{k, ctx_.view()};
if (!slep)
return tecNO_TARGET;
@@ -159,12 +159,12 @@ PaymentChannelClaim::doApply()
return tecUNFUNDED_PAYMENT;
}
auto const sled = ctx_.view().peek(keylet::account(dst));
AccountRootEntry<ApplyView> sled{dst, ctx_.view()};
if (!sled)
return tecNO_DST;
if (auto err =
verifyDepositPreauth(ctx_.tx, ctx_.view(), txAccount, dst, sled, ctx_.journal);
if (auto err = verifyDepositPreauth(
ctx_.tx, ctx_.view(), txAccount, dst, sled.sle(), ctx_.journal);
!isTesSuccess(err))
return err;
@@ -173,8 +173,8 @@ PaymentChannelClaim::doApply()
XRPL_ASSERT(
reqDelta >= beast::kZero, "xrpl::PaymentChannelClaim::doApply : minimum balance delta");
(*sled)[sfBalance] = (*sled)[sfBalance] + reqDelta;
ctx_.view().update(sled);
ctx_.view().update(slep);
sled.update();
slep.update();
}
if (ctx_.tx.isFlag(tfRenew))
@@ -182,7 +182,7 @@ PaymentChannelClaim::doApply()
if (src != txAccount)
return tecNO_PERMISSION;
(*slep)[~sfExpiration] = std::nullopt;
ctx_.view().update(slep);
slep.update();
}
if (ctx_.tx.isFlag(tfClose))
@@ -199,7 +199,7 @@ PaymentChannelClaim::doApply()
if (!curExpiration || *curExpiration > settleExpiration)
{
(*slep)[~sfExpiration] = settleExpiration;
ctx_.view().update(slep);
slep.update();
}
}
@@ -6,7 +6,7 @@
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
@@ -170,7 +170,9 @@ PaymentChannelCreate::doApply()
// Note that we use the value from the sequence or ticket as the
// payChan sequence. For more explanation see comments in SeqProxy.h.
Keylet const payChanKeylet = keylet::payChannel(account, dst, ctx_.tx.getSeqValue());
auto const slep = std::make_shared<SLE>(payChanKeylet);
// Build with the ApplyViewContext so create() honors reserve sponsorship.
PayChannelEntry<ApplyView> slep{payChanKeylet, ctx_.getApplyViewContext()};
slep.newSLE();
// Funds held in this channel
(*slep)[sfAmount] = ctx_.tx[sfAmount];
@@ -188,33 +190,14 @@ PaymentChannelCreate::doApply()
(*slep)[sfSequence] = ctx_.tx.getSeqValue();
}
ctx_.view().insert(slep);
// Add PayChan to owner directory
{
auto const page = ctx_.view().dirInsert(
keylet::ownerDir(account), payChanKeylet, describeOwnerDir(account));
if (!page)
return tecDIR_FULL; // LCOV_EXCL_LINE
(*slep)[sfOwnerNode] = *page;
}
// Add PayChan to the recipient's owner directory
{
auto const page =
ctx_.view().dirInsert(keylet::ownerDir(dst), payChanKeylet, describeOwnerDir(dst));
if (!page)
return tecDIR_FULL; // LCOV_EXCL_LINE
(*slep)[sfDestinationNode] = *page;
}
// Deduct owner's balance, increment owner count
// Deduct the channel amount from the owner's balance.
(*sle)[sfBalance] = (*sle)[sfBalance] - ctx_.tx[sfAmount];
increaseOwnerCount(ctx_.getApplyViewContext(), sle, 1, ctx_.journal);
addSponsorToLedgerEntry(ctx_.getApplyViewContext(), slep);
ctx_.view().update(sle);
return tesSUCCESS;
// Reserve check + link into the owner and destination directories + bump
// the owner's OwnerCount + stamp any reserve sponsor + insert. See
// PayChannelEntry::ownerDirs() and SLEBase::create().
return slep.create(preFeeBalance_);
}
void
@@ -6,6 +6,7 @@
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/PaymentChannelHelpers.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
@@ -44,7 +45,7 @@ TER
PaymentChannelFund::doApply()
{
Keylet const k(ltPAYCHAN, ctx_.tx[sfChannel]);
auto const slep = ctx_.view().peek(k);
PayChannelEntry<ApplyView> slep{k, ctx_.view()};
if (!slep)
return tecNO_ENTRY;
@@ -79,17 +80,18 @@ PaymentChannelFund::doApply()
: TER{temBAD_EXPIRATION};
}
(*slep)[~sfExpiration] = *newExpiration;
ctx_.view().update(slep);
slep.update();
}
auto const sle = ctx_.view().peek(keylet::account(txAccount));
AccountRootEntry<ApplyView> sle{txAccount, ctx_.view()};
if (!sle)
return tefINTERNAL; // LCOV_EXCL_LINE
{
// Check reserve and funds availability
STAmount const balance = (*sle)[sfBalance];
if (auto const ret = checkReserve(ctx_.getApplyViewContext(), sle, balance.xrp(), {}, j_);
if (auto const ret =
checkReserve(ctx_.getApplyViewContext(), sle.sle(), balance.xrp(), {}, j_);
!isTesSuccess(ret))
return ret;
@@ -99,7 +101,7 @@ PaymentChannelFund::doApply()
// when a sponsor is present and would ignore the source's post-lock
// balance entirely. Funding an existing channel adds no owned object,
// so there is no owner-count delta.
if (balance < accountReserve(ctx_.view(), sle, j_) + ctx_.tx[sfAmount])
if (balance < accountReserve(ctx_.view(), sle.sle(), j_) + ctx_.tx[sfAmount])
return tecUNFUNDED;
}
@@ -110,10 +112,10 @@ PaymentChannelFund::doApply()
}
(*slep)[sfAmount] = (*slep)[sfAmount] + ctx_.tx[sfAmount];
ctx_.view().update(slep);
slep.update();
(*sle)[sfBalance] = (*sle)[sfBalance] - ctx_.tx[sfAmount];
ctx_.view().update(sle);
sle.update();
return tesSUCCESS;
}
@@ -1,9 +1,11 @@
#include <xrpl/tx/transactors/permissioned_domain/PermissionedDomainDelete.h>
#include <xrpl/basics/Log.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STLedgerEntry.h>
@@ -28,7 +30,7 @@ TER
PermissionedDomainDelete::preclaim(PreclaimContext const& ctx)
{
auto const domain = ctx.tx.getFieldH256(sfDomainID);
auto const sleDomain = ctx.view.read(keylet::permissionedDomain(domain));
PermissionedDomainEntry<ReadView> const sleDomain{keylet::permissionedDomain(domain), ctx.view};
if (!sleDomain)
return tecNO_ENTRY;
@@ -52,25 +54,10 @@ PermissionedDomainDelete::doApply()
ctx_.tx.isFieldPresent(sfDomainID),
"xrpl::PermissionedDomainDelete::doApply : required field present");
auto const slePd = view().peek(keylet::permissionedDomain(ctx_.tx.at(sfDomainID)));
auto const page = (*slePd)[sfOwnerNode];
PermissionedDomainEntry<ApplyView> slePd{
keylet::permissionedDomain(ctx_.tx.at(sfDomainID)), view()};
if (!view().dirRemove(keylet::ownerDir(accountID_), page, slePd->key(), true))
{
// LCOV_EXCL_START
JLOG(j_.fatal()) << "Unable to delete permissioned domain directory entry.";
return tefBAD_LEDGER;
// LCOV_EXCL_STOP
}
auto const ownerSle = view().peek(keylet::account(accountID_));
XRPL_ASSERT(
ownerSle && ownerSle->getFieldU32(sfOwnerCount) > 0,
"xrpl::PermissionedDomainDelete::doApply : nonzero owner count");
decreaseOwnerCountForObject(view(), ownerSle, slePd, 1, ctx_.journal);
view().erase(slePd);
return tesSUCCESS;
return slePd.destroy();
}
void
@@ -2,9 +2,9 @@
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/helpers/CredentialHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Keylet.h>
@@ -62,8 +62,8 @@ PermissionedDomainSet::preclaim(PreclaimContext const& ctx)
if (ctx.tx.isFieldPresent(sfDomainID))
{
auto const sleDomain =
ctx.view.read(keylet::permissionedDomain(ctx.tx.getFieldH256(sfDomainID)));
PermissionedDomainEntry<ReadView> const sleDomain{
keylet::permissionedDomain(ctx.tx.getFieldH256(sfDomainID)), ctx.view};
if (!sleDomain)
return tecNO_ENTRY;
if (sleDomain->getAccountID(sfOwner) != account)
@@ -79,7 +79,7 @@ PermissionedDomainSet::preclaim(PreclaimContext const& ctx)
TER
PermissionedDomainSet::doApply()
{
auto const ownerSle = view().peek(keylet::account(accountID_));
AccountRootEntry<ApplyView> const ownerSle{keylet::account(accountID_), view()};
if (!ownerSle)
return tefINTERNAL; // LCOV_EXCL_LINE
@@ -97,39 +97,30 @@ PermissionedDomainSet::doApply()
if (ctx_.tx.isFieldPresent(sfDomainID))
{
// Modify existing permissioned domain.
auto slePd = view().peek(keylet::permissionedDomain(ctx_.tx.getFieldH256(sfDomainID)));
PermissionedDomainEntry<ApplyView> slePd{
keylet::permissionedDomain(ctx_.tx.getFieldH256(sfDomainID)), view()};
if (!slePd)
return tefINTERNAL; // LCOV_EXCL_LINE
slePd->peekFieldArray(sfAcceptedCredentials) = std::move(sortedLE);
view().update(slePd);
slePd.update();
}
else
{
// Create new permissioned domain.
// Check reserve availability for new object creation
auto const balance = STAmount((*ownerSle)[sfBalance]).xrp();
auto const reserve =
accountReserve(ctx_.view(), ownerSle, ctx_.journal, {.ownerCountDelta = 1});
if (balance < reserve)
return tecINSUFFICIENT_RESERVE;
bool const fixEnabled = view().rules().enabled(fixCleanup3_1_3);
auto const seq = fixEnabled ? ctx_.tx.getSeqValue() : ctx_.tx.getFieldU32(sfSequence);
Keylet const pdKeylet = keylet::permissionedDomain(accountID_, seq);
auto slePd = std::make_shared<SLE>(pdKeylet);
// Adopt a fresh SLE (rather than peek + newSLE) so that the pre-
// fixCleanup3_1_3 ticket case, where sfSequence is 0 and the keylet can
// collide with an existing domain, throws at insert() (-> tefEXCEPTION)
// instead of tripping the newSLE() "no existing SLE" assertion.
PermissionedDomainEntry<ApplyView> slePd{std::make_shared<SLE>(pdKeylet), view()};
slePd->setAccountID(sfOwner, accountID_);
slePd->setFieldU32(sfSequence, seq);
slePd->peekFieldArray(sfAcceptedCredentials) = std::move(sortedLE);
auto const page =
view().dirInsert(keylet::ownerDir(accountID_), pdKeylet, describeOwnerDir(accountID_));
if (!page)
return tecDIR_FULL; // LCOV_EXCL_LINE
slePd->setFieldU64(sfOwnerNode, *page);
// If we succeeded, the new entry counts against the creator's reserve.
increaseOwnerCount(view(), ownerSle, {}, 1, ctx_.journal);
view().insert(slePd);
return slePd.create(preFeeBalance_);
}
return tesSUCCESS;
+15 -20
View File
@@ -8,8 +8,9 @@
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/AmendmentTable.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/PublicKey.h>
#include <xrpl/protocol/SField.h>
@@ -25,7 +26,6 @@
#include <xrpl/tx/Transactor.h>
#include <algorithm>
#include <memory>
namespace xrpl {
@@ -162,14 +162,12 @@ Change::applyAmendment()
{
uint256 const amendment(ctx_.tx.getFieldH256(sfAmendment));
auto const k = keylet::amendments();
SLE::pointer amendmentObject = view().peek(k);
AmendmentsEntry<ApplyView> amendmentObject{view()};
if (!amendmentObject)
{
amendmentObject = std::make_shared<SLE>(k);
view().insert(amendmentObject);
amendmentObject.newSLE();
amendmentObject.insert();
}
STVector256 amendments = amendmentObject->getFieldV256(sfAmendments);
@@ -246,7 +244,7 @@ Change::applyAmendment()
amendmentObject->setFieldArray(sfMajorities, newMajorities);
}
view().update(amendmentObject);
amendmentObject.update();
return tesSUCCESS;
}
@@ -254,16 +252,14 @@ Change::applyAmendment()
TER
Change::applyFee()
{
auto const k = keylet::feeSettings();
SLE::pointer feeObject = view().peek(k);
FeeSettingsEntry<ApplyView> feeObject{view()};
if (!feeObject)
{
feeObject = std::make_shared<SLE>(k);
view().insert(feeObject);
feeObject.newSLE();
feeObject.insert();
}
auto set = [](SLE::pointer& feeObject, STTx const& tx, auto const& field) {
auto set = [](auto& feeObject, STTx const& tx, auto const& field) {
feeObject->at(field) = tx[field];
};
if (view().rules().enabled(featureXRPFees))
@@ -285,7 +281,7 @@ Change::applyFee()
set(feeObject, ctx_.tx, sfReserveIncrement);
}
view().update(feeObject);
feeObject.update();
JLOG(j_.warn()) << "Fees have been changed";
return tesSUCCESS;
@@ -326,12 +322,11 @@ Change::applyUNLModify()
JLOG(j_.info()) << "N-UNL: applyUNLModify, " << (disabling ? "ToDisable" : "ToReEnable")
<< " seq=" << seq << " validator data:" << strHex(validator);
auto const k = keylet::negativeUNL();
SLE::pointer negUnlObject = view().peek(k);
NegativeUNLEntry<ApplyView> negUnlObject{view()};
if (!negUnlObject)
{
negUnlObject = std::make_shared<SLE>(k);
view().insert(negUnlObject);
negUnlObject.newSLE();
negUnlObject.insert();
}
bool const found = [&] {
@@ -404,7 +399,7 @@ Change::applyUNLModify()
negUnlObject->setFieldVL(sfValidatorToReEnable, validator);
}
view().update(negUnlObject);
negUnlObject.update();
return tesSUCCESS;
}
@@ -1,11 +1,9 @@
#include <xrpl/tx/transactors/system/TicketCreate.h>
#include <xrpl/basics/Log.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Keylet.h>
#include <xrpl/protocol/SField.h>
@@ -67,22 +65,11 @@ TicketCreate::preclaim(PreclaimContext const& ctx)
TER
TicketCreate::doApply()
{
SLE::pointer const sleAccountRoot = view().peek(keylet::account(accountID_));
AccountRootEntry<ApplyView> sleAccountRoot{keylet::account(accountID_), view()};
if (!sleAccountRoot)
return tefINTERNAL; // LCOV_EXCL_LINE
// Each ticket counts against the reserve of the issuing account, but we
// check the starting balance because we want to allow dipping into the
// reserve to pay fees.
std::uint32_t const ticketCount = ctx_.tx[sfTicketCount];
if (preFeeBalance_ < accountReserve(
view(),
sleAccountRoot,
j_,
{.ownerCountDelta = static_cast<std::int32_t>(ticketCount)}))
return tecINSUFFICIENT_RESERVE;
beast::Journal const viewJ{ctx_.registry.get().getJournal("View")};
// The starting ticket sequence is the same as the current account
// root sequence. Before we got here to doApply(), the transaction
@@ -100,23 +87,20 @@ TicketCreate::doApply()
{
std::uint32_t const curTicketSeq = firstTicketSeq + i;
Keylet const ticketKeylet = keylet::ticket(accountID_, curTicketSeq);
SLE::pointer const sleTicket = std::make_shared<SLE>(ticketKeylet);
TicketEntry<ApplyView> sleTicket{ticketKeylet, view()};
sleTicket.newSLE();
sleTicket->setAccountID(sfAccount, accountID_);
sleTicket->setFieldU32(sfTicketSequence, curTicketSeq);
view().insert(sleTicket);
auto const page = view().dirInsert(
keylet::ownerDir(accountID_), ticketKeylet, describeOwnerDir(accountID_));
JLOG(j_.trace()) << "Creating ticket " << to_string(ticketKeylet.key) << ": "
<< (page ? "success" : "failure");
if (!page)
return tecDIR_FULL; // LCOV_EXCL_LINE
sleTicket->setFieldU64(sfOwnerNode, *page);
// Each ticket counts against the reserve of the issuing account, but
// the reserve is checked against the starting balance (preFeeBalance_)
// because we want to allow dipping into the reserve to pay fees. This
// reserve check + owner directory link + OwnerCount bump is handled by
// create(). The final ticket's create() enforces the reserve for the
// full ticketCount, since OwnerCount grows with each iteration.
if (auto const ter = sleTicket.create(preFeeBalance_); !isTesSuccess(ter))
return ter;
}
// Update the record of the number of Tickets this account owns.
@@ -124,9 +108,6 @@ TicketCreate::doApply()
sleAccountRoot->setFieldU32(sfTicketCount, oldTicketCount + ticketCount);
// Every added Ticket counts against the creator's reserve.
increaseOwnerCount(view(), sleAccountRoot, {}, ticketCount, viewJ);
// TicketCreate is the only transaction that can cause an account root's
// Sequence field to increase by more than one. October 2018.
sleAccountRoot->setFieldU32(sfSequence, firstTicketSeq + ticketCount);
@@ -1,8 +1,10 @@
#include <xrpl/tx/transactors/token/MPTokenAuthorize.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
@@ -46,8 +48,8 @@ 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));
MPTokenEntry<ReadView> const sleMpt{
keylet::mptoken(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.
@@ -5,9 +5,7 @@
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
@@ -119,83 +117,63 @@ MPTokenIssuanceCreate::create(
beast::Journal journal,
MPTCreateArgs const& args)
{
auto const acct = ctx.view.peek(keylet::account(args.account));
if (!acct)
if (!ctx.view.exists(keylet::account(args.account)))
return std::unexpected(tecINTERNAL); // LCOV_EXCL_LINE
// A reserve sponsor only covers tx.Account's own objects.
auto const sponsorExp = getEffectiveTxReserveSponsor(ctx, acct);
if (!sponsorExp)
return std::unexpected(sponsorExp.error()); // LCOV_EXCL_LINE
auto const sponsorSle = *sponsorExp;
if (args.priorBalance)
{
if (auto const ret = checkReserve(
ctx, acct, *(args.priorBalance), sponsorSle, {.ownerCountDelta = 1}, journal);
!isTesSuccess(ret))
return std::unexpected(ret);
}
auto const mptId = makeMptID(args.sequence, args.account);
auto const mptIssuanceKeylet = keylet::mptokenIssuance(mptId);
// create the MPTokenIssuance
// create the MPTokenIssuance. Build with the ApplyViewContext so create()
// performs reserve-sponsorship-aware accounting (a no-op when the issuer is
// a pseudo-account, e.g. VaultCreate).
MPTokenIssuanceEntry<ApplyView> mptIssuance{mptIssuanceKeylet, ctx, journal};
mptIssuance.newSLE();
(*mptIssuance)[sfFlags] = args.flags & ~tfUniversal;
(*mptIssuance)[sfIssuer] = args.account;
(*mptIssuance)[sfOutstandingAmount] = 0;
(*mptIssuance)[sfSequence] = args.sequence;
if (args.maxAmount)
(*mptIssuance)[sfMaximumAmount] = *args.maxAmount;
if (args.assetScale)
(*mptIssuance)[sfAssetScale] = *args.assetScale;
if (args.transferFee)
(*mptIssuance)[sfTransferFee] = *args.transferFee;
if (args.metadata)
(*mptIssuance)[sfMPTokenMetadata] = *args.metadata;
if (args.domainId)
(*mptIssuance)[sfDomainID] = *args.domainId;
if (args.mutableFlags)
(*mptIssuance)[sfMutableFlags] = *args.mutableFlags;
if (args.referenceHolding)
{
auto const ownerNode = ctx.view.dirInsert(
keylet::ownerDir(args.account), mptIssuanceKeylet, describeOwnerDir(args.account));
if (!ownerNode)
return std::unexpected(tecDIR_FULL); // LCOV_EXCL_LINE
auto mptIssuance = std::make_shared<SLE>(mptIssuanceKeylet);
(*mptIssuance)[sfFlags] = args.flags & ~tfUniversal;
(*mptIssuance)[sfIssuer] = args.account;
(*mptIssuance)[sfOutstandingAmount] = 0;
(*mptIssuance)[sfOwnerNode] = *ownerNode;
(*mptIssuance)[sfSequence] = args.sequence;
if (args.maxAmount)
(*mptIssuance)[sfMaximumAmount] = *args.maxAmount;
if (args.assetScale)
(*mptIssuance)[sfAssetScale] = *args.assetScale;
if (args.transferFee)
(*mptIssuance)[sfTransferFee] = *args.transferFee;
if (args.metadata)
(*mptIssuance)[sfMPTokenMetadata] = *args.metadata;
if (args.domainId)
(*mptIssuance)[sfDomainID] = *args.domainId;
if (args.mutableFlags)
(*mptIssuance)[sfMutableFlags] = *args.mutableFlags;
if (args.referenceHolding)
{
// Defensive: the holding must already exist and be of an
// expected type. Callers (currently only VaultCreate)
// populate this after the pseudo-account's MPToken /
// RippleState has been installed. A missing holding here
// would dangle the pointer and is a programmer error.
auto const sleHolding = ctx.view.read(keylet::unchecked(*args.referenceHolding));
if (!sleHolding)
return std::unexpected(tecINTERNAL); // LCOV_EXCL_LINE
auto const type = sleHolding->getType();
if (type != ltMPTOKEN && type != ltRIPPLE_STATE)
return std::unexpected(tecINTERNAL); // LCOV_EXCL_LINE
(*mptIssuance)[sfReferenceHolding] = *args.referenceHolding;
}
addSponsorToLedgerEntry(mptIssuance, sponsorSle);
ctx.view.insert(mptIssuance);
// Defensive: the holding must already exist and be of an
// expected type. Callers (currently only VaultCreate)
// populate this after the pseudo-account's MPToken /
// RippleState has been installed. A missing holding here
// would dangle the pointer and is a programmer error.
auto const sleHolding = ctx.view.read(keylet::unchecked(*args.referenceHolding));
if (!sleHolding)
return std::unexpected(tecINTERNAL); // LCOV_EXCL_LINE
auto const type = sleHolding->getType();
if (type != ltMPTOKEN && type != ltRIPPLE_STATE)
return std::unexpected(tecINTERNAL); // LCOV_EXCL_LINE
(*mptIssuance)[sfReferenceHolding] = *args.referenceHolding;
}
// Update owner count.
increaseOwnerCount(ctx.view, acct, sponsorSle, 1, journal);
// Reserve check against the issuer's pre-fee balance (skipped when
// priorBalance is std::nullopt, i.e. VaultCreate's pseudo-account, and
// honoring any reserve sponsor) + link into the issuer's owner directory +
// bump the issuer's OwnerCount + stamp the reserve sponsor + insert. See
// MPTokenIssuanceEntry and SLEBase::create().
if (auto const ter = mptIssuance.create(args.priorBalance); !isTesSuccess(ter))
return std::unexpected(ter);
return mptId;
}
@@ -1,6 +1,8 @@
#include <xrpl/tx/transactors/token/MPTokenIssuanceDestroy.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STLedgerEntry.h>
@@ -42,17 +44,12 @@ MPTokenIssuanceDestroy::preclaim(PreclaimContext const& ctx)
TER
MPTokenIssuanceDestroy::doApply()
{
auto const mpt = view().peek(keylet::mptokenIssuance(ctx_.tx[sfMPTokenIssuanceID]));
MPTokenIssuanceEntry<ApplyView> mpt{
keylet::mptokenIssuance(ctx_.tx[sfMPTokenIssuanceID]), view()};
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);
return tesSUCCESS;
return mpt.destroy();
}
void
@@ -5,6 +5,7 @@
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/SLEBase.h>
#include <xrpl/protocol/ConfidentialTransfer.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
@@ -301,16 +302,10 @@ MPTokenIssuanceSet::doApply()
auto const mptIssuanceID = ctx_.tx[sfMPTokenIssuanceID];
auto const holderID = ctx_.tx[~sfHolder];
auto const domainID = ctx_.tx[~sfDomainID];
SLE::pointer sle;
if (holderID)
{
sle = view().peek(keylet::mptoken(mptIssuanceID, *holderID));
}
else
{
sle = view().peek(keylet::mptokenIssuance(mptIssuanceID));
}
WritableSLE sle{
holderID ? keylet::mptoken(mptIssuanceID, *holderID)
: keylet::mptokenIssuance(mptIssuanceID),
view()};
if (!sle)
return tecINTERNAL; // LCOV_EXCL_LINE
@@ -410,7 +405,7 @@ MPTokenIssuanceSet::doApply()
sle->setFieldVL(sfAuditorEncryptionKey, *pubKey);
}
view().update(sle);
sle.update();
return tesSUCCESS;
}
@@ -6,6 +6,9 @@
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/ledger/helpers/VaultHelpers.h>
#include <xrpl/protocol/AccountID.h>
@@ -60,7 +63,7 @@ VaultClawback::preflight(PreflightContext const& ctx)
[[nodiscard]] STAmount
clawbackAmount(
SLE::const_ref vault,
VaultEntry<ReadView> const& vault,
std::optional<STAmount> const& maybeAmount,
AccountID const& account)
{
@@ -77,7 +80,7 @@ clawbackAmount(
TER
VaultClawback::preclaim(PreclaimContext const& ctx)
{
auto const vault = ctx.view.read(keylet::vault(ctx.tx[sfVaultID]));
VaultEntry<ReadView> const vault{keylet::vault(ctx.tx[sfVaultID]), ctx.view};
if (!vault)
return tecNO_ENTRY;
@@ -86,7 +89,8 @@ 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));
MPTokenIssuanceEntry<ReadView> const sleShareIssuance{
keylet::mptokenIssuance(mptIssuanceID), ctx.view};
if (!sleShareIssuance)
{
// LCOV_EXCL_START
@@ -180,8 +184,9 @@ 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)
MPTokenIssuanceEntry<ReadView> const mptIssue{
keylet::mptokenIssuance(issue.getMptID()), ctx.view};
if (!mptIssue)
return tecOBJECT_NOT_FOUND;
if (!mptIssue->isFlag(lsfMPTCanClawback))
@@ -194,7 +199,7 @@ VaultClawback::preclaim(PreclaimContext const& ctx)
return tesSUCCESS;
},
[&](Issue const&) -> TER {
auto const issuerSle = ctx.view.read(keylet::account(account));
AccountRootEntry<ReadView> const issuerSle{keylet::account(account), ctx.view};
if (!issuerSle)
{
// LCOV_EXCL_START
@@ -220,8 +225,8 @@ VaultClawback::preclaim(PreclaimContext const& ctx)
std::expected<std::pair<STAmount, STAmount>, TER>
VaultClawback::assetsToClawback(
SLE::ref vault,
SLE::const_ref sleShareIssuance,
VaultEntry<ApplyView>& vault,
MPTokenIssuanceEntry<ReadView> const& sleShareIssuance,
AccountID const& holder,
STAmount const& clawbackAmount)
{
@@ -332,12 +337,13 @@ TER
VaultClawback::doApply()
{
auto const& tx = ctx_.tx;
auto const vault = view().peek(keylet::vault(tx[sfVaultID]));
VaultEntry<ApplyView> vault{keylet::vault(tx[sfVaultID]), view()};
if (!vault)
return tefINTERNAL; // LCOV_EXCL_LINE
auto const mptIssuanceID = *vault->at(sfShareMPTID);
auto const sleIssuance = view().read(keylet::mptokenIssuance(mptIssuanceID));
MPTokenIssuanceEntry<ReadView> const sleIssuance{
keylet::mptokenIssuance(mptIssuanceID), view()};
if (!sleIssuance)
{
// LCOV_EXCL_START
@@ -385,7 +391,7 @@ VaultClawback::doApply()
assetsTotal -= assetsRecovered;
assetsAvailable -= assetsRecovered;
view().update(vault);
vault.update();
auto const& vaultAccount = vault->at(sfAccount);
// Transfer shares from holder to vault.
@@ -4,9 +4,10 @@
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Asset.h>
@@ -148,18 +149,12 @@ VaultCreate::doApply()
auto const& tx = ctx_.tx;
auto applyViewContext = ctx_.getApplyViewContext();
auto const sequence = tx.getSeqValue();
auto const owner = view().peek(keylet::account(accountID_));
if (owner == nullptr)
AccountRootEntry<ApplyView> const owner{keylet::account(accountID_), view()};
if (!owner)
return tefINTERNAL; // LCOV_EXCL_LINE
auto vault = std::make_shared<SLE>(keylet::vault(accountID_, sequence));
if (auto ter = dirLink(view(), accountID_, vault))
return ter;
// We will create Vault and PseudoAccount, hence increase OwnerCount by 2
increaseOwnerCount(view(), owner, {}, 2, j_);
if (preFeeBalance_ < accountReserve(view(), owner, j_))
return tecINSUFFICIENT_RESERVE;
VaultEntry<ApplyView> vault{keylet::vault(accountID_, sequence), view(), j_};
vault.newSLE();
auto maybePseudo = createPseudoAccount(view(), vault->key(), sfVaultID);
if (!maybePseudo)
@@ -241,7 +236,12 @@ VaultCreate::doApply()
}
if (scale != 0u)
vault->at(sfScale) = scale;
view().insert(vault);
// Reserve check (owner's pre-fee balance, charging two slots for the vault
// and its pseudo-account) + link into the owner's directory + bump the
// owner's OwnerCount by 2 + insert. See VaultEntry.
if (auto const ter = vault.create(preFeeBalance_); !isTesSuccess(ter))
return ter;
// Explicitly create MPToken for the vault owner
if (auto const err = authorizeMPToken(
@@ -3,8 +3,11 @@
#include <xrpl/basics/Log.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Feature.h>
@@ -42,7 +45,7 @@ VaultDelete::preflight(PreflightContext const& ctx)
TER
VaultDelete::preclaim(PreclaimContext const& ctx)
{
auto const vault = ctx.view.read(keylet::vault(ctx.tx[sfVaultID]));
VaultEntry<ReadView> const vault{keylet::vault(ctx.tx[sfVaultID]), ctx.view};
if (!vault)
return tecNO_ENTRY;
@@ -65,7 +68,8 @@ VaultDelete::preclaim(PreclaimContext const& ctx)
}
// Verify we can destroy MPTokenIssuance
auto const sleMPT = ctx.view.read(keylet::mptokenIssuance(vault->at(sfShareMPTID)));
MPTokenIssuanceEntry<ReadView> const sleMPT{
keylet::mptokenIssuance(vault->at(sfShareMPTID)), ctx.view};
if (!sleMPT)
{
@@ -95,7 +99,7 @@ VaultDelete::preclaim(PreclaimContext const& ctx)
TER
VaultDelete::doApply()
{
auto const vault = view().peek(keylet::vault(ctx_.tx[sfVaultID]));
VaultEntry<ApplyView> vault{keylet::vault(ctx_.tx[sfVaultID]), view()};
auto applyViewContext = ctx_.getApplyViewContext();
if (!vault)
return tefINTERNAL; // LCOV_EXCL_LINE
@@ -108,7 +112,7 @@ VaultDelete::doApply()
return ter;
auto const& pseudoID = vault->at(sfAccount);
auto const pseudoAcct = view().peek(keylet::account(pseudoID));
AccountRootEntry<ApplyView> const pseudoAcct{keylet::account(pseudoID), view()};
if (!pseudoAcct)
{
// LCOV_EXCL_START
@@ -120,7 +124,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));
MPTokenIssuanceEntry<ApplyView> mpt{keylet::mptokenIssuance(shareMPTID), view()};
if (!mpt)
{
// LCOV_EXCL_START
@@ -130,7 +134,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 (MPTokenEntry<ApplyView> const mptoken{keylet::mptoken(shareMPTID, accountID_), view()})
{
if (auto const ter =
removeEmptyHolding(applyViewContext, accountID_, MPTIssue(shareMPTID), j_);
@@ -154,16 +158,16 @@ VaultDelete::doApply()
return tefBAD_LEDGER;
// LCOV_EXCL_STOP
}
decreaseOwnerCountForObject(view(), pseudoAcct, mpt, 1, j_);
decreaseOwnerCountForObject(view(), pseudoAcct.mutableSle(), mpt.mutableSle(), 1, j_);
view().erase(mpt);
mpt.erase();
// The pseudo-account's directory should have been deleted already.
if (view().peek(keylet::ownerDir(pseudoID)))
if (DirectoryNodeEntry<ApplyView>{keylet::ownerDir(pseudoID), view()})
return tecHAS_OBLIGATIONS; // LCOV_EXCL_LINE
// Destroy the pseudo-account.
auto vaultPseudoSLE = view().peek(keylet::account(pseudoID));
AccountRootEntry<ApplyView> vaultPseudoSLE{keylet::account(pseudoID), view()};
if (!vaultPseudoSLE || vaultPseudoSLE->at(~sfVaultID) != vault->key())
return tefBAD_LEDGER; // LCOV_EXCL_LINE
@@ -191,34 +195,12 @@ VaultDelete::doApply()
// LCOV_EXCL_STOP
}
view().erase(vaultPseudoSLE);
vaultPseudoSLE.erase();
// Remove the vault from its owner's directory.
auto const ownerID = vault->at(sfOwner);
if (!view().dirRemove(keylet::ownerDir(ownerID), vault->at(sfOwnerNode), vault->key(), false))
{
// LCOV_EXCL_START
JLOG(j_.error()) << "VaultDelete: failed to delete vault object.";
return tefBAD_LEDGER;
// LCOV_EXCL_STOP
}
auto const owner = view().peek(keylet::account(ownerID));
if (!owner)
{
// LCOV_EXCL_START
JLOG(j_.error()) << "VaultDelete: missing vault owner account.";
return tefBAD_LEDGER;
// LCOV_EXCL_STOP
}
// We are destroying Vault and PseudoAccount, hence decrease by 2
decreaseOwnerCountForObject(view(), owner, vault, 2, j_);
// Destroy the vault.
view().erase(vault);
return tesSUCCESS;
// Unlink the vault from its owner's directory, decrement the owner's
// OwnerCount by 2 (the vault and its now-destroyed pseudo-account, refunding
// any reserve sponsor), and erase it. See VaultEntry.
return vault.destroy();
}
void
@@ -4,8 +4,10 @@
#include <xrpl/basics/Number.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/CredentialHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/ledger/helpers/VaultHelpers.h>
#include <xrpl/protocol/Feature.h>
@@ -30,7 +32,7 @@ namespace xrpl {
[[nodiscard]]
static STAmount
roundToVaultScale(STAmount const& amount, SLE::const_ref vault)
roundToVaultScale(STAmount const& amount, VaultEntry<ReadView> const& vault)
{
XRPL_ASSERT(vault && vault->getType() == ltVAULT, "xrpl::roundToVaultScale : valid vault sle");
XRPL_ASSERT(
@@ -67,7 +69,7 @@ VaultDeposit::preclaim(PreclaimContext const& ctx)
auto const fix320Enabled = ctx.view.rules().enabled(fixCleanup3_2_0);
auto const fix330Enabled = ctx.view.rules().enabled(fixCleanup3_3_0);
auto const vault = ctx.view.read(keylet::vault(ctx.tx[sfVaultID]));
VaultEntry<ReadView> const vault{keylet::vault(ctx.tx[sfVaultID]), ctx.view};
if (!vault)
return tecNO_ENTRY;
@@ -94,7 +96,8 @@ VaultDeposit::preclaim(PreclaimContext const& ctx)
// LCOV_EXCL_STOP
}
auto const sleIssuance = ctx.view.read(keylet::mptokenIssuance(mptIssuanceID));
MPTokenIssuanceEntry<ReadView> const sleIssuance{
keylet::mptokenIssuance(mptIssuanceID), ctx.view};
if (!sleIssuance)
{
// LCOV_EXCL_START
@@ -196,7 +199,7 @@ TER
VaultDeposit::doApply()
{
bool const fix320Enabled = view().rules().enabled(fixCleanup3_2_0);
auto const vault = view().peek(keylet::vault(ctx_.tx[sfVaultID]));
VaultEntry<ApplyView> vault{keylet::vault(ctx_.tx[sfVaultID]), view()};
auto applyViewContext = ctx_.getApplyViewContext();
if (!vault)
return tefINTERNAL; // LCOV_EXCL_LINE
@@ -218,7 +221,8 @@ VaultDeposit::doApply()
// Make sure the depositor can hold shares.
auto const mptIssuanceID = (*vault)[sfShareMPTID];
auto const sleIssuance = view().read(keylet::mptokenIssuance(mptIssuanceID));
MPTokenIssuanceEntry<ReadView> const sleIssuance{
keylet::mptokenIssuance(mptIssuanceID), view()};
if (!sleIssuance)
{
// LCOV_EXCL_START
@@ -316,7 +320,7 @@ VaultDeposit::doApply()
vault->at(sfAssetsTotal) += assetsDeposited;
vault->at(sfAssetsAvailable) += assetsDeposited;
view().update(vault);
vault.update();
// A deposit must not push the vault over its limit.
auto const maximum = *vault->at(sfAssetsMaximum);
+12 -7
View File
@@ -2,6 +2,9 @@
#include <xrpl/basics/Log.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
@@ -63,7 +66,7 @@ VaultSet::preflight(PreflightContext const& ctx)
TER
VaultSet::preclaim(PreclaimContext const& ctx)
{
auto const vault = ctx.view.read(keylet::vault(ctx.tx[sfVaultID]));
VaultEntry<ReadView> const vault{keylet::vault(ctx.tx[sfVaultID]), ctx.view};
if (!vault)
return tecNO_ENTRY;
@@ -75,7 +78,8 @@ VaultSet::preclaim(PreclaimContext const& ctx)
}
auto const mptIssuanceID = (*vault)[sfShareMPTID];
auto const sleIssuance = ctx.view.read(keylet::mptokenIssuance(mptIssuanceID));
MPTokenIssuanceEntry<ReadView> const sleIssuance{
keylet::mptokenIssuance(mptIssuanceID), ctx.view};
if (!sleIssuance)
{
// LCOV_EXCL_START
@@ -95,7 +99,8 @@ VaultSet::preclaim(PreclaimContext const& ctx)
if (*domain != beast::kZero)
{
auto const sleDomain = ctx.view.read(keylet::permissionedDomain(*domain));
PermissionedDomainEntry<ReadView> const sleDomain{
keylet::permissionedDomain(*domain), ctx.view};
if (!sleDomain)
return tecOBJECT_NOT_FOUND;
}
@@ -123,14 +128,14 @@ VaultSet::doApply()
auto const& tx = ctx_.tx;
// Update existing object.
auto vault = view().peek(keylet::vault(tx[sfVaultID]));
VaultEntry<ApplyView> vault{keylet::vault(tx[sfVaultID]), view()};
if (!vault)
return tefINTERNAL; // LCOV_EXCL_LINE
auto const vaultAsset = vault->at(sfAsset);
auto const mptIssuanceID = (*vault)[sfShareMPTID];
auto const sleIssuance = view().peek(keylet::mptokenIssuance(mptIssuanceID));
MPTokenIssuanceEntry<ApplyView> sleIssuance{keylet::mptokenIssuance(mptIssuanceID), view()};
if (!sleIssuance)
{
// LCOV_EXCL_START
@@ -164,13 +169,13 @@ VaultSet::doApply()
{
sleIssuance->makeFieldAbsent(sfDomainID);
}
view().update(sleIssuance);
sleIssuance.update();
}
// Note, we must update Vault object even if only DomainID is being updated
// in Issuance object. Otherwise it's really difficult for Vault invariants
// to verify the operation.
view().update(vault);
vault.update();
associateAsset(*vault, vaultAsset);
@@ -4,8 +4,10 @@
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Zero.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/SLEWrappers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/ledger/helpers/VaultHelpers.h>
#include <xrpl/protocol/AccountID.h>
@@ -28,7 +30,10 @@
namespace xrpl {
static WaiveUnrealizedLoss
shouldWaiveWithdrawal(ReadView const& view, AccountID const& account, SLE::const_ref issuance)
shouldWaiveWithdrawal(
ReadView const& view,
AccountID const& account,
MPTokenIssuanceEntry<ReadView> const& issuance)
{
XRPL_ASSERT(
issuance && issuance->getType() == ltMPTOKEN_ISSUANCE,
@@ -69,7 +74,7 @@ VaultWithdraw::preclaim(PreclaimContext const& ctx)
auto const fix320Enabled = ctx.view.rules().enabled(fixCleanup3_2_0);
auto const fix330Enabled = ctx.view.rules().enabled(fixCleanup3_3_0);
auto const vault = ctx.view.read(keylet::vault(ctx.tx[sfVaultID]));
VaultEntry<ReadView> const vault{keylet::vault(ctx.tx[sfVaultID]), ctx.view};
if (!vault)
return tecNO_ENTRY;
@@ -109,7 +114,8 @@ 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));
MPTokenIssuanceEntry<ReadView> const sleIssuance{
keylet::mptokenIssuance(vaultShare), ctx.view};
if (!sleIssuance)
{
// LCOV_EXCL_START
@@ -193,13 +199,14 @@ VaultWithdraw::preclaim(PreclaimContext const& ctx)
TER
VaultWithdraw::doApply()
{
auto const vault = view().peek(keylet::vault(ctx_.tx[sfVaultID]));
VaultEntry<ApplyView> vault{keylet::vault(ctx_.tx[sfVaultID]), view()};
auto applyViewContext = ctx_.getApplyViewContext();
if (!vault)
return tefINTERNAL; // LCOV_EXCL_LINE
auto const mptIssuanceID = *((*vault)[sfShareMPTID]);
auto const sleIssuance = view().read(keylet::mptokenIssuance(mptIssuanceID));
MPTokenIssuanceEntry<ReadView> const sleIssuance{
keylet::mptokenIssuance(mptIssuanceID), view()};
if (!sleIssuance)
{
// LCOV_EXCL_START
@@ -347,7 +354,7 @@ VaultWithdraw::doApply()
assetsTotal -= assetsWithdrawn;
assetsAvailable -= assetsWithdrawn;
}
view().update(vault);
vault.update();
auto const& vaultAccount = vault->at(sfAccount);
+5 -3
View File
@@ -67,9 +67,11 @@ struct DID_test : public beast::unit_test::Suite
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 0);
// Pay alice almost enough to make the reserve for a DID.
env(pay(env.master, alice, drops(incReserve + 2 * baseFee - 1)));
BEAST_EXPECT(env.balance(alice) == acctReserve + incReserve + drops(baseFee - 1));
// Pay alice almost enough to make the reserve for a DID. The reserve is
// checked against the pre-fee balance, so leave her one drop short of
// the object reserve at the point the DIDSet is evaluated.
env(pay(env.master, alice, drops(incReserve + baseFee - 1)));
BEAST_EXPECT(env.balance(alice) == acctReserve + incReserve - drops(1));
env.close();
// alice still does not have enough XRP for the reserve of a DID.
+10 -14
View File
@@ -6,7 +6,9 @@
#include <xrpl/basics/Number.h>
#include <xrpl/basics/chrono.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/LendingHelpers.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/SField.h>
@@ -1525,27 +1527,21 @@ public:
for (auto const& tc : testCases)
{
testcase("canApplyToBrokerCover: " + tc.name);
auto sle = std::make_shared<SLE>(ltLOAN_BROKER, uint256{1u});
sle->at(sfCoverAvailable) = tc.coverAvailable;
BEAST_EXPECT(
canApplyToBrokerCover(*env.current(), sle, iou, tc.amount, env.journal, "test") ==
tc.expected);
auto broker = std::make_shared<SLE>(Keylet{ltLOAN_BROKER, uint256{1u}});
broker->at(sfCoverAvailable) = tc.coverAvailable;
LoanBrokerEntry<ReadView> const sle{broker, *env.current(), env.journal};
BEAST_EXPECT(canApplyToBrokerCover(sle, iou, tc.amount, "test") == tc.expected);
}
// Amendment off → guard is bypassed regardless of amount.
{
testcase("canApplyToBrokerCover: amendment disabled");
Env const envOff{*this, testableAmendments() - fixCleanup3_2_0};
auto sle = std::make_shared<SLE>(ltLOAN_BROKER, uint256{1u});
sle->at(sfCoverAvailable) = Number{10};
auto broker = std::make_shared<SLE>(Keylet{ltLOAN_BROKER, uint256{1u}});
broker->at(sfCoverAvailable) = Number{10};
LoanBrokerEntry<ReadView> const sle{broker, *envOff.current(), envOff.journal};
BEAST_EXPECT(
canApplyToBrokerCover(
*envOff.current(),
sle,
iou,
STAmount{iou, Number{0}},
envOff.journal,
"test") == tesSUCCESS);
canApplyToBrokerCover(sle, iou, STAmount{iou, Number{0}}, "test") == tesSUCCESS);
}
}
+14 -12
View File
@@ -29,7 +29,9 @@
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/OpenView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
@@ -141,9 +143,9 @@ class LoanBroker_test : public beast::unit_test::Suite
VaultInfo const& vault,
VaultInfo const& badVault,
std::function<jtx::JTx(jtx::JTx const&)> modifyJTx,
std::function<void(SLE::const_ref)> checkBroker,
std::function<void(SLE::const_ref)> changeBroker,
std::function<void(SLE::const_ref)> checkChangedBroker)
std::function<void(LoanBrokerEntry<ReadView> const&)> checkBroker,
std::function<void(LoanBrokerEntry<ReadView> const&)> changeBroker,
std::function<void(LoanBrokerEntry<ReadView> const&)> checkChangedBroker)
{
{
auto const& asset = vault.asset.raw();
@@ -221,7 +223,7 @@ class LoanBroker_test : public beast::unit_test::Suite
BEAST_EXPECT(broker->at(sfDebtTotal) == 0);
BEAST_EXPECT(broker->at(sfCoverAvailable) == 0);
if (checkBroker)
checkBroker(broker);
checkBroker(LoanBrokerEntry<ReadView>{broker, *env.current()});
// if (auto const vaultSLE = env.le(keylet::vault(vault.vaultID)))
//{
@@ -471,14 +473,14 @@ class LoanBroker_test : public beast::unit_test::Suite
// Make modifications to the broker
if (changeBroker)
changeBroker(broker);
changeBroker(LoanBrokerEntry<ReadView>{broker, *env.current()});
env.close();
// Check the results of modifications
broker = env.le(keylet);
if (BEAST_EXPECT(broker) && checkChangedBroker)
checkChangedBroker(broker);
checkChangedBroker(LoanBrokerEntry<ReadView>{broker, *env.current()});
// Verify that fields get removed when set to default values
// Debt maximum: explicit 0
@@ -724,7 +726,7 @@ class LoanBroker_test : public beast::unit_test::Suite
badVault,
// No modifications
{},
[&](SLE::const_ref broker) {
[&](LoanBrokerEntry<ReadView> const& broker) {
// Extra checks
BEAST_EXPECT(!broker->isFieldPresent(sfManagementFeeRate));
BEAST_EXPECT(!broker->isFieldPresent(sfCoverRateMinimum));
@@ -737,7 +739,7 @@ class LoanBroker_test : public beast::unit_test::Suite
BEAST_EXPECT(env.ownerCount(alice) == aliceOriginalCount + 4);
},
[&](SLE::const_ref broker) {
[&](LoanBrokerEntry<ReadView> const& broker) {
// Modifications
// Update the fields
@@ -797,7 +799,7 @@ class LoanBroker_test : public beast::unit_test::Suite
kData(testData),
kDebtMaximum(debtMax));
},
[&](SLE::const_ref broker) {
[&](LoanBrokerEntry<ReadView> const& broker) {
// Check the updated fields
BEAST_EXPECT(checkVL(broker->at(sfData), testData));
Number const expected = STAmount{vault.asset, Number(175, -1)};
@@ -828,7 +830,7 @@ class LoanBroker_test : public beast::unit_test::Suite
kCoverRateMinimum(TenthBips32(100)),
kCoverRateLiquidation(TenthBips32(200)));
},
[&](SLE::const_ref broker) {
[&](LoanBrokerEntry<ReadView> const& broker) {
// Extra checks
BEAST_EXPECT(broker->at(sfManagementFeeRate) == 123);
BEAST_EXPECT(broker->at(sfCoverRateMinimum) == 100);
@@ -836,14 +838,14 @@ class LoanBroker_test : public beast::unit_test::Suite
BEAST_EXPECT(broker->at(sfDebtMaximum) == Number(9));
BEAST_EXPECT(checkVL(broker->at(sfData), testData));
},
[&](SLE::const_ref broker) {
[&](LoanBrokerEntry<ReadView> const& broker) {
// Reset Data & Debt maximum to default values
env(set(alice, vault.vaultID),
kLoanBrokerId(broker->key()),
kData(""),
kDebtMaximum(Number(0)));
},
[&](SLE::const_ref broker) {
[&](LoanBrokerEntry<ReadView> const& broker) {
// Check the updated fields
BEAST_EXPECT(!broker->isFieldPresent(sfData));
BEAST_EXPECT(!broker->isFieldPresent(sfDebtMaximum));
+18 -8
View File
@@ -37,8 +37,10 @@
#include <xrpl/beast/xor_shift_engine.h>
#include <xrpl/json/json_value.h>
#include <xrpl/json/to_string.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/LendingHelpers.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/HashPrefix.h>
@@ -213,7 +215,7 @@ protected:
using namespace jtx;
auto const vaultSle = env.le(keylet::vault(vaultID));
return getAssetsTotalScale(vaultSle);
return getAssetsTotalScale(VaultEntry<ReadView>{vaultSle, *env.current()});
}
};
@@ -446,7 +448,8 @@ protected:
env.test.BEAST_EXPECT(loan->at(sfPeriodicPayment) == periodicPayment);
env.test.BEAST_EXPECT(loan->at(sfFlags) == flags);
auto const ls = constructRoundedLoanState(loan);
auto const ls =
constructRoundedLoanState(LoanEntry<ReadView>{loan, *env.current()});
auto const interestRate = TenthBips32{loan->at(sfInterestRate)};
auto const paymentInterval = loan->at(sfPaymentInterval);
@@ -1119,7 +1122,8 @@ protected:
// No reason for this not to exist
return;
}
auto const current = constructRoundedLoanState(loanSle);
auto const current =
constructRoundedLoanState(LoanEntry<ReadView>{loanSle, *env.current()});
auto const errors = nextTrueState - current;
log << currencyLabel << " Loan balances: "
<< "\n\tAmount taken: " << paymentComponents.trackedValueDelta
@@ -6056,7 +6060,8 @@ protected:
auto const loanSle = env.le(loanKeylet);
if (!BEAST_EXPECT(loanSle))
return;
auto const state = constructRoundedLoanState(loanSle);
auto const state =
constructRoundedLoanState(LoanEntry<ReadView>{loanSle, *env.current()});
log << "Loan state:" << std::endl;
log << " ValueOutstanding: " << state.valueOutstanding << std::endl;
@@ -8280,7 +8285,8 @@ protected:
return std::nullopt;
if (!BEAST_EXPECT(tinyLoanSle->at(sfLoanScale) == -12) ||
!BEAST_EXPECT(bigLoanSle->at(sfLoanScale) == -11) ||
!BEAST_EXPECT(getAssetsTotalScale(vaultSle) == -11))
!BEAST_EXPECT(
getAssetsTotalScale(VaultEntry<ReadView>{vaultSle, *env.current()}) == -11))
return std::nullopt;
// Use issuer clawback to reduce cover to the minimum the
@@ -8407,7 +8413,8 @@ protected:
auto const coverAvail = brokerSle->at(sfCoverAvailable);
auto const debtTotal = brokerSle->at(sfDebtTotal);
auto const vaultScale = getAssetsTotalScale(vaultSle);
auto const vaultScale =
getAssetsTotalScale(VaultEntry<ReadView>{vaultSle, *env.current()});
auto const debtScale = scale(debtTotal, asset);
// Sanity: debt scale differs from vault scale for this setup.
@@ -8421,7 +8428,9 @@ protected:
debtScale);
}();
auto const newMin = minimumBrokerCover(
debtTotal, TenthBips32{c.brokerParams.coverRateMin}, vaultSle);
debtTotal,
TenthBips32{c.brokerParams.coverRateMin},
VaultEntry<ReadView>{vaultSle, *env.current()});
// The new (vaultScale) minimum must be strictly larger than
// the old (debtScale) minimum — that is the gap the amendment
@@ -8505,7 +8514,8 @@ protected:
auto const vaultSle = env.le(keylet::vault(c.broker.vaultID));
if (!BEAST_EXPECT(vaultSle))
return;
auto const vaultScale = getAssetsTotalScale(vaultSle);
auto const vaultScale =
getAssetsTotalScale(VaultEntry<ReadView>{vaultSle, *env.current()});
BEAST_EXPECT(vaultScale == -11);
// Now try to create a tiny additional loan. Principal is
+5 -3
View File
@@ -509,9 +509,11 @@ class PermissionedDomains_test : public beast::unit_test::Suite
auto const baseFee = env.current()->fees().base.drops();
// Pay alice almost enough to make the reserve.
env(pay(env.master, alice, incReserve + drops(2 * baseFee) - drops(1)));
BEAST_EXPECT(env.balance(alice) == acctReserve + incReserve + drops(baseFee) - drops(1));
// Pay alice almost enough to make the reserve. The reserve is checked
// against the pre-fee balance, so leave her one drop short of the object
// reserve at the point the PermissionedDomainSet is evaluated.
env(pay(env.master, alice, incReserve + drops(baseFee) - drops(1)));
BEAST_EXPECT(env.balance(alice) == acctReserve + incReserve - drops(1));
env.close();
// alice still does not have enough XRP for the reserve.