From 628272a11ecb8613c1ca15b72dc26d1f8ab4f2ed Mon Sep 17 00:00:00 2001 From: Mayukha Vadari Date: Thu, 9 Jul 2026 21:13:54 -0400 Subject: [PATCH] set up framework --- .cspell.config.yaml | 2 + include/xrpl/ledger/helpers/OracleHelpers.h | 31 ++ include/xrpl/ledger/helpers/SLEBase.h | 250 ++++++++++ include/xrpl/ledger/helpers/SLEWrappers.h | 450 ++++++++++++++++++ include/xrpl/ledger/helpers/SponsorHelpers.h | 26 + src/libxrpl/ledger/helpers/SponsorHelpers.cpp | 103 ++++ 6 files changed, 862 insertions(+) create mode 100644 include/xrpl/ledger/helpers/OracleHelpers.h create mode 100644 include/xrpl/ledger/helpers/SLEBase.h create mode 100644 include/xrpl/ledger/helpers/SLEWrappers.h create mode 100644 include/xrpl/ledger/helpers/SponsorHelpers.h create mode 100644 src/libxrpl/ledger/helpers/SponsorHelpers.cpp diff --git a/.cspell.config.yaml b/.cspell.config.yaml index c558fc0984..e4c5d26290 100644 --- a/.cspell.config.yaml +++ b/.cspell.config.yaml @@ -279,6 +279,7 @@ words: - sles - soci - socidb + - sponsee - SRPMS - sslws - statsd @@ -327,6 +328,7 @@ words: - unserviced - unshareable - unshares + - unsponsored - unsquelch - unsquelched - unsquelching diff --git a/include/xrpl/ledger/helpers/OracleHelpers.h b/include/xrpl/ledger/helpers/OracleHelpers.h new file mode 100644 index 0000000000..da04618e46 --- /dev/null +++ b/include/xrpl/ledger/helpers/OracleHelpers.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include // IWYU pragma: keep +#include + +#include +#include + +namespace xrpl { + +constexpr std::uint32_t kMinOracleReserveCount = 1; +constexpr std::uint32_t kMaxOracleReserveCount = 2; +constexpr std::size_t kOracleReserveCountThreshold = 5; + +template + requires requires(T const& t) { t.size(); } +inline std::uint32_t +calculateOracleReserve(T const& priceDataSeries) +{ + return priceDataSeries.size() > kOracleReserveCountThreshold ? kMaxOracleReserveCount + : kMinOracleReserveCount; +} + +inline std::uint32_t +calculateOracleReserve(SLE::const_ref oracleSle) +{ + return calculateOracleReserve(oracleSle->getFieldArray(sfPriceDataSeries)); +} + +} // namespace xrpl diff --git a/include/xrpl/ledger/helpers/SLEBase.h b/include/xrpl/ledger/helpers/SLEBase.h new file mode 100644 index 0000000000..419859e3e5 --- /dev/null +++ b/include/xrpl/ledger/helpers/SLEBase.h @@ -0,0 +1,250 @@ +#pragma once + +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace xrpl { + +// Concept to distinguish read-only vs writable view types +template +concept WritableView = std::derived_from; + +/** + * View-parameterized base class for all ledger entry wrappers. + * + * SLEBase — read-only: holds shared_ptr + ReadView const& + * SLEBase — writable: holds shared_ptr + 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 +class SLEBase +{ +public: + static constexpr bool kIsWritable = WritableView; + + // SLE pointer type: mutable for writable views, const for read-only + using sle_ptr_type = std::conditional_t, SLE::const_pointer>; + + // View reference type: ApplyView& for writable, ReadView const& for read-only + using view_ref_type = std::conditional_t; + + 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(key_); + } + + [[nodiscard]] beast::Journal + journal() const + { + return j_; + } + +protected: + /** 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 to + * SLEBase, so functions taking ReadOnlySLE const& can + * accept WritableSLE. + */ + template + SLEBase(SLEBase 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, + 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) + { + } + + 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 key_{}; + + sle_ptr_type sle_{}; + beast::Journal j_; +}; + +} // namespace xrpl diff --git a/include/xrpl/ledger/helpers/SLEWrappers.h b/include/xrpl/ledger/helpers/SLEWrappers.h new file mode 100644 index 0000000000..f824c248c9 --- /dev/null +++ b/include/xrpl/ledger/helpers/SLEWrappers.h @@ -0,0 +1,450 @@ +#pragma once + +#include +#include + +#include + +namespace xrpl { + +/** + * Concrete, view-parameterized wrappers for each ledger entry type. + * + * Every wrapper derives from SLEBase and, for now, does nothing more + * than translate the entry's "keylet parts" (the arguments to its keylet:: + * function) into a Keylet that the base class resolves against the view: + * + * AccountRoot acct{id, view}; // peek (writable) + * AccountRoot acct{id, readView}; // read (read-only) + * + * Domain-specific accessors will be layered onto each wrapper over time. + */ + +// Ordered to match include/xrpl/protocol/detail/ledger_entries.macro. + +template +class NFTokenOffer : public SLEBase +{ +public: + explicit NFTokenOffer( + AccountID const& owner, + std::uint32_t seq, + typename SLEBase::view_ref_type view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : SLEBase(keylet::nftokenOffer(owner, seq), view, j) + { + } +}; + +template +class Check : public SLEBase +{ +public: + explicit Check( + AccountID const& id, + std::uint32_t seq, + typename SLEBase::view_ref_type view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : SLEBase(keylet::check(id, seq), view, j) + { + } +}; + +template +class DID : public SLEBase +{ +public: + explicit DID( + AccountID const& account, + typename SLEBase::view_ref_type view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : SLEBase(keylet::did(account), view, j) + { + } +}; + +template +class NegativeUNL : public SLEBase +{ +public: + explicit NegativeUNL( + typename SLEBase::view_ref_type view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : SLEBase(keylet::negativeUNL(), view, j) + { + } +}; + +template +class NFTokenPage : public SLEBase +{ +public: + explicit NFTokenPage( + Keylet const& page, + uint256 const& token, + typename SLEBase::view_ref_type view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : SLEBase(keylet::nftokenPage(page, token), view, j) + { + } +}; + +template +class SignerList : public SLEBase +{ +public: + explicit SignerList( + AccountID const& account, + typename SLEBase::view_ref_type view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : SLEBase(keylet::signerList(account), view, j) + { + } +}; + +template +class Ticket : public SLEBase +{ +public: + explicit Ticket( + AccountID const& id, + std::uint32_t ticketSeq, + typename SLEBase::view_ref_type view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : SLEBase(keylet::ticket(id, ticketSeq), view, j) + { + } +}; + +template +class AccountRoot : public SLEBase +{ +public: + explicit AccountRoot( + AccountID const& id, + typename SLEBase::view_ref_type view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : SLEBase(keylet::account(id), view, j) + { + } +}; + +template +class DirectoryNode : public SLEBase +{ +public: + explicit DirectoryNode( + AccountID const& id, + typename SLEBase::view_ref_type view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : SLEBase(keylet::ownerDir(id), view, j) + { + } +}; + +template +class Amendments : public SLEBase +{ +public: + explicit Amendments( + typename SLEBase::view_ref_type view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : SLEBase(keylet::amendments(), view, j) + { + } +}; + +template +class LedgerHashes : public SLEBase +{ +public: + explicit LedgerHashes( + typename SLEBase::view_ref_type view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : SLEBase(keylet::skip(), view, j) + { + } +}; + +template +class Bridge : public SLEBase +{ +public: + explicit Bridge( + STXChainBridge const& bridge, + STXChainBridge::ChainType chainType, + typename SLEBase::view_ref_type view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : SLEBase(keylet::bridge(bridge, chainType), view, j) + { + } +}; + +template +class Offer : public SLEBase +{ +public: + explicit Offer( + AccountID const& id, + std::uint32_t seq, + typename SLEBase::view_ref_type view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : SLEBase(keylet::offer(id, seq), view, j) + { + } +}; + +template +class DepositPreauth : public SLEBase +{ +public: + explicit DepositPreauth( + AccountID const& owner, + AccountID const& preauthorized, + typename SLEBase::view_ref_type view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : SLEBase(keylet::depositPreauth(owner, preauthorized), view, j) + { + } +}; + +template +class XChainOwnedClaimID : public SLEBase +{ +public: + explicit XChainOwnedClaimID( + STXChainBridge const& bridge, + std::uint64_t seq, + typename SLEBase::view_ref_type view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : SLEBase(keylet::xChainClaimID(bridge, seq), view, j) + { + } +}; + +template +class RippleState : public SLEBase +{ +public: + explicit RippleState( + AccountID const& id0, + AccountID const& id1, + Currency const& currency, + typename SLEBase::view_ref_type view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : SLEBase(keylet::trustLine(id0, id1, currency), view, j) + { + } +}; + +template +class FeeSettings : public SLEBase +{ +public: + explicit FeeSettings( + typename SLEBase::view_ref_type view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : SLEBase(keylet::feeSettings(), view, j) + { + } +}; + +template +class XChainOwnedCreateAccountClaimID : public SLEBase +{ +public: + explicit XChainOwnedCreateAccountClaimID( + STXChainBridge const& bridge, + std::uint64_t seq, + typename SLEBase::view_ref_type view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : SLEBase(keylet::xChainCreateAccountClaimID(bridge, seq), view, j) + { + } +}; + +template +class Escrow : public SLEBase +{ +public: + explicit Escrow( + AccountID const& src, + std::uint32_t seq, + typename SLEBase::view_ref_type view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : SLEBase(keylet::escrow(src, seq), view, j) + { + } +}; + +template +class PayChannel : public SLEBase +{ +public: + explicit PayChannel( + AccountID const& src, + AccountID const& dst, + std::uint32_t seq, + typename SLEBase::view_ref_type view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : SLEBase(keylet::payChannel(src, dst, seq), view, j) + { + } +}; + +template +class AMM : public SLEBase +{ +public: + explicit AMM( + Asset const& issue1, + Asset const& issue2, + typename SLEBase::view_ref_type view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : SLEBase(keylet::amm(issue1, issue2), view, j) + { + } +}; + +template +class MPTokenIssuance : public SLEBase +{ +public: + explicit MPTokenIssuance( + std::uint32_t seq, + AccountID const& issuer, + typename SLEBase::view_ref_type view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : SLEBase(keylet::mptokenIssuance(seq, issuer), view, j) + { + } +}; + +template +class MPToken : public SLEBase +{ +public: + explicit MPToken( + MPTID const& issuanceID, + AccountID const& holder, + typename SLEBase::view_ref_type view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : SLEBase(keylet::mptoken(issuanceID, holder), view, j) + { + } +}; + +template +class Oracle : public SLEBase +{ +public: + explicit Oracle( + AccountID const& account, + std::uint32_t documentID, + typename SLEBase::view_ref_type view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : SLEBase(keylet::oracle(account, documentID), view, j) + { + } +}; + +template +class Credential : public SLEBase +{ +public: + explicit Credential( + AccountID const& subject, + AccountID const& issuer, + Slice const& credType, + typename SLEBase::view_ref_type view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : SLEBase(keylet::credential(subject, issuer, credType), view, j) + { + } +}; + +template +class PermissionedDomain : public SLEBase +{ +public: + explicit PermissionedDomain( + AccountID const& account, + std::uint32_t seq, + typename SLEBase::view_ref_type view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : SLEBase(keylet::permissionedDomain(account, seq), view, j) + { + } +}; + +template +class Delegate : public SLEBase +{ +public: + explicit Delegate( + AccountID const& account, + AccountID const& authorizedAccount, + typename SLEBase::view_ref_type view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : SLEBase(keylet::delegate(account, authorizedAccount), view, j) + { + } +}; + +template +class Vault : public SLEBase +{ +public: + explicit Vault( + AccountID const& owner, + std::uint32_t seq, + typename SLEBase::view_ref_type view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : SLEBase(keylet::vault(owner, seq), view, j) + { + } +}; + +template +class LoanBroker : public SLEBase +{ +public: + explicit LoanBroker( + AccountID const& owner, + std::uint32_t seq, + typename SLEBase::view_ref_type view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : SLEBase(keylet::loanBroker(owner, seq), view, j) + { + } +}; + +template +class Loan : public SLEBase +{ +public: + explicit Loan( + uint256 const& loanBrokerID, + std::uint32_t loanSeq, + typename SLEBase::view_ref_type view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : SLEBase(keylet::loan(loanBrokerID, loanSeq), view, j) + { + } +}; + +template +class Sponsorship : public SLEBase +{ +public: + explicit Sponsorship( + AccountID const& sponsor, + AccountID const& sponsee, + typename SLEBase::view_ref_type view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : SLEBase(keylet::sponsorship(sponsor, sponsee), view, j) + { + } +}; + +} // namespace xrpl diff --git a/include/xrpl/ledger/helpers/SponsorHelpers.h b/include/xrpl/ledger/helpers/SponsorHelpers.h new file mode 100644 index 0000000000..074b6a9575 --- /dev/null +++ b/include/xrpl/ledger/helpers/SponsorHelpers.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace xrpl { + +std::optional +getLedgerEntryOwner(ReadView const& view, SLE const& sle, AccountID const& account); + +std::uint32_t +getLedgerEntryOwnerCount(SLE const& sle); + +} // namespace xrpl diff --git a/src/libxrpl/ledger/helpers/SponsorHelpers.cpp b/src/libxrpl/ledger/helpers/SponsorHelpers.cpp new file mode 100644 index 0000000000..30fc92ad8c --- /dev/null +++ b/src/libxrpl/ledger/helpers/SponsorHelpers.cpp @@ -0,0 +1,103 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace xrpl { + +std::optional +getLedgerEntryOwner(ReadView const& view, SLE const& sle, AccountID const& account) +{ + switch (sle.getType()) + { + case ltCHECK: + case ltESCROW: + case ltPAYCHAN: + case ltMPTOKEN: + case ltDELEGATE: + case ltDEPOSIT_PREAUTH: + return sle.getAccountID(sfAccount); + case ltMPTOKEN_ISSUANCE: + return sle.getAccountID(sfIssuer); + case ltSIGNER_LIST: { + auto const signerList = view.read(keylet::signerList(account)); + if (!signerList) + return std::nullopt; + if (signerList->key() == sle.key()) + return account; + return std::nullopt; + } + case ltCREDENTIAL: { + if (sle.isFlag(lsfAccepted)) + return sle.getAccountID(sfSubject); + return sle.getAccountID(sfIssuer); + } + case ltRIPPLE_STATE: { + if (sle.isFlag(lsfHighReserve)) + { + auto const highAccount = sle.getFieldAmount(sfHighLimit).getIssuer(); + if (highAccount == account) + return highAccount; + } + if (sle.isFlag(lsfLowReserve)) + { + auto const lowAccount = sle.getFieldAmount(sfLowLimit).getIssuer(); + if (lowAccount == account) + return lowAccount; + } + return std::nullopt; + } + default: + // LCOV_EXCL_START + UNREACHABLE("xrpl::getLedgerEntryOwner : object is not supported by sponsorship."); + return std::nullopt; + // LCOV_EXCL_STOP + }; +} + +std::uint32_t +getLedgerEntryOwnerCount(SLE const& sle) +{ + switch (sle.getType()) + { + case ltORACLE: { + return calculateOracleReserve(sle.getFieldArray(sfPriceDataSeries)); + } + // Vaults require 2 owner counts (the vault and a pseudo-account) + case ltVAULT: + return 2; + case ltSIGNER_LIST: { + // Mirror SignerListSet's owner-count accounting so that create and + // delete agree. Modern lists (post-MultiSignReserve) carry the + // lsfOneOwnerCount flag and cost a single owner count. Legacy + // pre-MultiSignReserve lists cost 2 + signer_count owner counts + if (sle.isFlag(lsfOneOwnerCount)) + return 1; + return 2 + static_cast(sle.getFieldArray(sfSignerEntries).size()); + } + case ltACCOUNT_ROOT: + UNREACHABLE("AccountRoots are not supported by object sponsorship."); + return 0; + default: + return 1; + } +} + +} // namespace xrpl