Compare commits

...

6 Commits

Author SHA1 Message Date
Mayukha Vadari
712d243d0e Document reserveCount parameters and return value
reserveCount() had no params to document but was missing an @return
tag describing what the reserve count means.
2026-10-05 15:47:04 +01:00
Mayukha Vadari
558e381d7c Include Keylet.h in the SLEBase test
(cherry picked from commit 3cca9d3266)
2026-10-05 14:36:55 +01:00
Mayukha Vadari
ca84b7cfdd test: Drop extern template declarations from SLEBase test
An explicit instantiation declaration stops clang from emitting the
inline constructors of the header-only entries at -O0, so the debug
builds fail to link xrpl_tests (undefined AccountRootEntry constructors).
Naming each class in a static_assert already instantiates its definition,
which is all the exhaustiveness check needs.

(cherry picked from commit 611415e1f9)
2026-10-05 14:36:55 +01:00
Mayukha Vadari
11210e1f46 test: Declare, not define, entry instantiations in SLEBase test
The exhaustive per-entry-type check in the SLEBase gtest used explicit
instantiation definitions. Once an entry class gets out-of-line member
bodies in its own .cpp (with the explicit instantiation definition
there), the test would instantiate the class a second time, and MSVC
rejects it with C4661 because those member bodies are not visible in
the test translation unit. Explicit instantiation declarations still
instantiate the class definition, which is all the check needs.

(cherry picked from commit 8b07abc3cd)
2026-10-05 13:40:48 +01:00
Mayukha Vadari
e9cd5f3640 Drop unused includes from OracleDelete.h 2026-10-05 13:40:48 +01:00
Mayukha Vadari
ea04f6a56d refactor: Use OracleEntry everywhere
Replace raw Oracle SLE pointers with OracleEntryR/OracleEntryW in
OracleSet, OracleDelete and AccountDelete, and move the Oracle helpers
onto the wrapper:

- calculateOracleReserve(SLE::const_ref) becomes
  OracleEntry::reserveCount(); the series-array overload stays free.
- OracleDelete::deleteOracle() becomes OracleEntry::removeFromLedger().
  AccountDelete's removeOracleFromLedger() forwarded to it, so both
  callers now use the one member.

The member bodies live in src/libxrpl/ledger/entries/OracleEntry.cpp
with explicit instantiation. No behavior change.
2026-10-05 13:40:48 +01:00
8 changed files with 109 additions and 67 deletions

View File

@@ -4,9 +4,13 @@
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/ledger/helpers/OracleHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STArray.h> // IWYU pragma: keep
#include <xrpl/protocol/TER.h>
#include <cstdint>
@@ -30,6 +34,33 @@ public:
: Base(keylet::oracle(account, documentID), view, j)
{
}
/**
* Returns the number of owner reserves this Oracle holds, based on the
* size of its PriceDataSeries.
*
* @throws std::logic_error if exists() is false.
*
* @return The number of owner reserves: kMinOracleReserveCount, or
* kMaxOracleReserveCount if the PriceDataSeries size exceeds
* kOracleReserveCountThreshold.
*/
[[nodiscard]] std::uint32_t
reserveCount() const
{
return calculateOracleReserve((*this)->getFieldArray(sfPriceDataSeries));
}
/**
* Removes this Oracle from its owner's directory, releases its owner
* reserves, and erases it from the view.
*
* @param owner The Oracle's owner.
* @return tesSUCCESS, or tecINTERNAL / tefBAD_LEDGER on ledger corruption.
*/
[[nodiscard]] TER
removeFromLedger(AccountID const& owner)
requires Base::kIsWritable;
};
using OracleEntryR = OracleEntry<ReadView>;

View File

@@ -1,9 +1,5 @@
#pragma once
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STArray.h> // IWYU pragma: keep
#include <xrpl/protocol/STLedgerEntry.h>
#include <cstddef>
#include <cstdint>
@@ -22,10 +18,4 @@ calculateOracleReserve(T const& priceDataSeries)
: kMinOracleReserveCount;
}
inline std::uint32_t
calculateOracleReserve(SLE::ConstRef oracleSle)
{
return calculateOracleReserve(oracleSle->getFieldArray(sfPriceDataSeries));
}
} // namespace xrpl

View File

@@ -2,9 +2,7 @@
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/XRPAmount.h>
@@ -50,9 +48,6 @@ public:
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
static TER
deleteOracle(ApplyView& view, SLE::Ref sle, AccountID const& account, beast::Journal j);
};
} // namespace xrpl

View File

@@ -0,0 +1,49 @@
#include <xrpl/ledger/entries/OracleEntry.h>
#include <xrpl/basics/Log.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/TER.h>
#include <cstdint>
namespace xrpl {
template <typename ViewT>
TER
OracleEntry<ViewT>::removeFromLedger(AccountID const& owner)
requires Base::kIsWritable
{
if (!this->exists())
return tecINTERNAL; // LCOV_EXCL_LINE
auto& view = this->applyView();
auto const& sle = this->mutableRawSle();
if (!view.dirRemove(keylet::ownerDir(owner), (*sle)[sfOwnerNode], sle->key(), true))
{
// LCOV_EXCL_START
JLOG(this->journal().fatal()) << "Unable to delete Oracle from owner.";
return tefBAD_LEDGER;
// LCOV_EXCL_STOP
}
auto const sleOwner = view.peek(keylet::account(owner));
if (!sleOwner)
return tecINTERNAL; // LCOV_EXCL_LINE
std::uint32_t const count = reserveCount();
decreaseOwnerCountForObject(view, sleOwner, sle, count, this->journal());
this->erase();
return tesSUCCESS;
}
template class OracleEntry<ReadView>;
template class OracleEntry<ApplyView>;
} // namespace xrpl

View File

@@ -9,6 +9,7 @@
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/entries/OracleEntry.h>
#include <xrpl/ledger/helpers/CredentialHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/NFTokenHelpers.h>
@@ -28,7 +29,6 @@
#include <xrpl/tx/transactors/account/SignerListSet.h>
#include <xrpl/tx/transactors/delegate/DelegateSet.h>
#include <xrpl/tx/transactors/did/DIDDelete.h>
#include <xrpl/tx/transactors/oracle/OracleDelete.h>
#include <xrpl/tx/transactors/payment/DepositPreauth.h>
#include <cstdint>
@@ -154,11 +154,11 @@ removeOracleFromLedger(
ServiceRegistry&,
ApplyView& view,
AccountID const& account,
UInt256 const&,
SLE::Ref sleDel,
UInt256 const& delIndex,
SLE::Ref,
beast::Journal j)
{
return OracleDelete::deleteOracle(view, sleDel, account, j);
return OracleEntryW(Keylet(ltORACLE, delIndex), view, j).removeFromLedger(account);
}
TER

View File

@@ -2,10 +2,7 @@
#include <xrpl/basics/Log.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/OracleHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/ledger/entries/OracleEntry.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STLedgerEntry.h>
@@ -14,8 +11,6 @@
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/Transactor.h>
#include <cstdint>
namespace xrpl {
NotTEC
@@ -31,7 +26,7 @@ OracleDelete::preclaim(PreclaimContext const& ctx)
return terNO_ACCOUNT; // LCOV_EXCL_LINE
auto const sle =
ctx.view.read(keylet::oracle(ctx.tx.getAccountID(sfAccount), ctx.tx[sfOracleDocumentID]));
OracleEntryR(ctx.tx.getAccountID(sfAccount), ctx.tx[sfOracleDocumentID], ctx.view);
if (!sle)
{
JLOG(ctx.j.debug()) << "Oracle Delete: Oracle does not exist.";
@@ -49,40 +44,11 @@ OracleDelete::preclaim(PreclaimContext const& ctx)
return tesSUCCESS;
}
TER
OracleDelete::deleteOracle(
ApplyView& view,
SLE::Ref sle,
AccountID const& account,
beast::Journal j)
{
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;
}
TER
OracleDelete::doApply()
{
if (auto sle = ctx_.view().peek(keylet::oracle(accountID_, ctx_.tx[sfOracleDocumentID])))
return deleteOracle(ctx_.view(), sle, accountID_, j_);
if (auto oracle = OracleEntryW(accountID_, ctx_.tx[sfOracleDocumentID], ctx_.view(), j_))
return oracle.removeFromLedger(accountID_);
return tecINTERNAL; // LCOV_EXCL_LINE
}

View File

@@ -3,6 +3,7 @@
#include <xrpl/basics/chrono.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/entries/OracleEntry.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/OracleHelpers.h>
@@ -26,7 +27,6 @@
#include <cstdint>
#include <cstdlib>
#include <map>
#include <memory>
#include <set>
#include <utility>
@@ -84,7 +84,7 @@ OracleSet::preclaim(PreclaimContext const& ctx)
return tecINVALID_UPDATE_TIME;
auto const sle =
ctx.view.read(keylet::oracle(ctx.tx.getAccountID(sfAccount), ctx.tx[sfOracleDocumentID]));
OracleEntryR(ctx.tx.getAccountID(sfAccount), ctx.tx[sfOracleDocumentID], ctx.view);
// token pairs to add/update
std::set<std::pair<Currency, Currency>> pairs;
@@ -153,7 +153,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;
@@ -227,7 +227,7 @@ OracleSet::doApply()
priceData.setFieldU8(sfScale, entry.getFieldU8(sfScale));
};
if (auto sle = ctx_.view().peek(oracleID))
if (auto sle = OracleEntryW(oracleID, ctx_.view()))
{
// update
// the token pair that doesn't have their price updated will not
@@ -287,13 +287,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))
{
@@ -337,7 +337,7 @@ OracleSet::doApply()
if (!adjustOracleOwnerCount(ctx_, count))
return tefINTERNAL; // LCOV_EXCL_LINE
ctx_.view().insert(sle);
sle.insert();
}
return tesSUCCESS;

View File

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