diff --git a/include/xrpl/ledger/View.h b/include/xrpl/ledger/View.h index 74ba0e13e8..4225191881 100644 --- a/include/xrpl/ledger/View.h +++ b/include/xrpl/ledger/View.h @@ -19,6 +19,11 @@ namespace xrpl { +// Forward declarations for SLE wrappers +template +class AccountRoot; +using ReadOnlyAccountRoot = AccountRoot; + enum class SkipEntry : bool { No = false, Yes }; //------------------------------------------------------------------------------ @@ -157,7 +162,7 @@ canWithdraw( ReadView const& view, AccountID const& from, AccountID const& to, - AccountRoot const& toWrapped, + ReadOnlyAccountRoot const& toWrapped, STAmount const& amount, bool hasDestinationTag); diff --git a/include/xrpl/ledger/helpers/AccountRootHelpers.h b/include/xrpl/ledger/helpers/AccountRootHelpers.h index c0f02d52eb..031d8db8f7 100644 --- a/include/xrpl/ledger/helpers/AccountRootHelpers.h +++ b/include/xrpl/ledger/helpers/AccountRootHelpers.h @@ -18,27 +18,60 @@ namespace xrpl { /** - * Read-only wrapper for AccountRoot ledger entries. + * View-parameterized wrapper for AccountRoot ledger entries. * - * Provides read-only access to account data. + * AccountRoot — read-only access to account data + * AccountRoot — read-write access, with insert/update/erase + * and domain-specific write methods */ -class AccountRoot : public ReadOnlySLE +template +class AccountRoot : public SLEBase { -protected: + static constexpr bool is_writable = SLEBase::is_writable; + AccountID const id_; public: + /** Constructor for read-only context */ AccountRoot(AccountID const& id, ReadView const& view) - : ReadOnlySLE(view.read(keylet::account(id)), view), id_(id) + requires(!is_writable) + : SLEBase(view.read(keylet::account(id)), view), id_(id) { } + /** Constructor for writable context */ + AccountRoot(AccountID const& id, ApplyView& view) + requires is_writable + : SLEBase(keylet::account(id), view), id_(id) + { + } + + /** Converting constructor: writable → read-only. */ + template + AccountRoot(AccountRoot const& other) + requires(!is_writable) + : SLEBase(other), id_(other.id()) + { + } + + /** Create an AccountRoot backed by a brand-new SLE + * (not yet inserted into the view). + */ + [[nodiscard]] static AccountRoot + makeNew(AccountID const& id, ApplyView& view) + requires is_writable + { + return AccountRoot(id, view, std::make_shared(keylet::account(id))); + } + AccountID const& id() const { return id_; } + // --- Read-only domain methods (available on both specializations) --- + /** Check if the issuer has the global freeze flag set. @return true if the account has global freeze set */ @@ -94,52 +127,36 @@ public: { return id_ == other; } -}; -/** - * Writable wrapper for AccountRoot ledger entries. - * - * Provides read-write access to account data. - * Inherits from AccountRoot to reuse read-only methods, - * and adds write capabilities. - */ -class WritableAccountRoot : public AccountRoot, public WritableSLE -{ -public: - WritableAccountRoot(AccountID const& id, ApplyView& view) - : AccountRoot(id, view), WritableSLE(keylet::account(id), view) - { - } - - /** Create a WritableAccountRoot backed by a brand-new SLE - * (not yet inserted into the view). - */ - [[nodiscard]] static WritableAccountRoot - makeNew(AccountID const& id, ApplyView& view) - { - return WritableAccountRoot(id, view, std::make_shared(keylet::account(id))); - } - -private: - // This is a private constructor only used by `makeNew` - WritableAccountRoot(AccountID const& id, ApplyView& view, std::shared_ptr sle) - : AccountRoot(id, view), WritableSLE(std::move(sle), view) - { - insert(); - } - -public: - // Resolve ambiguity: use writable operator-> for non-const, read-only for const - using WritableSLE::operator->; - using AccountRoot::operator->; - using WritableSLE::operator*; - using AccountRoot::operator*; + // --- Write-only domain methods (compile-time gated) --- /** Adjust the owner count up or down. */ void - adjustOwnerCount(std::int32_t amount, beast::Journal j); + adjustOwnerCount(std::int32_t amount, beast::Journal j) + requires is_writable; + +private: + // Private constructor only used by `makeNew` + AccountRoot(AccountID const& id, ApplyView& view, std::shared_ptr sle) + requires is_writable + : SLEBase(std::move(sle), view), id_(id) + { + this->insert(); + } }; +// CTAD deduction guide — bare AccountRoot(id, view) always deduces read-only. +// For writable access, use WritableAccountRoot(id, applyView) explicitly. +AccountRoot(AccountID const&, ReadView const&) -> AccountRoot; + +// Backward-compatible aliases +using ReadOnlyAccountRoot = AccountRoot; +using WritableAccountRoot = AccountRoot; + +// Explicit instantiation declarations (definitions in .cpp) +extern template class AccountRoot; +extern template class AccountRoot; + /** Generate a pseudo-account address from a pseudo owner key. @param pseudoOwnerKey The key to generate the address from @return The generated account ID @@ -177,7 +194,7 @@ isPseudoAccount( AccountID const& accountId, std::set const& pseudoFieldFilter = {}) { - AccountRoot const acct(accountId, view); + AccountRoot const acct(accountId, view); if (!acct) return false; return acct.isPseudoAccount(pseudoFieldFilter); diff --git a/include/xrpl/ledger/helpers/CredentialHelpers.h b/include/xrpl/ledger/helpers/CredentialHelpers.h index 9a96a13c85..d660645997 100644 --- a/include/xrpl/ledger/helpers/CredentialHelpers.h +++ b/include/xrpl/ledger/helpers/CredentialHelpers.h @@ -74,7 +74,7 @@ verifyDepositPreauth( STTx const& tx, ApplyView& view, AccountID const& src, - AccountRoot const& dst, + ReadOnlyAccountRoot const& dst, beast::Journal j); } // namespace xrpl diff --git a/include/xrpl/ledger/helpers/SLEBase.h b/include/xrpl/ledger/helpers/SLEBase.h index e2183eabd7..a83631bb57 100644 --- a/include/xrpl/ledger/helpers/SLEBase.h +++ b/include/xrpl/ledger/helpers/SLEBase.h @@ -4,33 +4,53 @@ #include #include +#include #include #include +#include namespace xrpl { +// Concept to distinguish read-only vs writable view types +template +concept WritableView = std::derived_from; + /** - * Read-only base class for all ledger entry view classes. + * View-parameterized base class for all ledger entry wrappers. * - * Provides common functionality for existence checking and raw SLE read access. - * Supports read-only (ReadView) contexts. + * 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. */ -class ReadOnlySLE +template +class SLEBase { public: - virtual ~ReadOnlySLE() = default; + static constexpr bool is_writable = WritableView; - // Copy/move constructors are fine (reference can be initialized from another) - ReadOnlySLE(ReadOnlySLE const&) = default; - ReadOnlySLE(ReadOnlySLE&&) = default; - // Assignment operators are deleted (cannot rebind reference members) - ReadOnlySLE& - operator=(ReadOnlySLE const&) = delete; - ReadOnlySLE& - operator=(ReadOnlySLE&&) = delete; + // SLE pointer type: mutable for writable views, const for read-only + using sle_ptr_type = + std::conditional_t, std::shared_ptr>; + + // 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; + + // --- Common interface (always available) --- /** Returns true if the ledger entry exists */ bool @@ -46,155 +66,163 @@ public: return exists(); } - /** Returns the underlying SLE for read access (always available) */ - std::shared_ptr const& + /** Returns the underlying SLE for read access */ + std::shared_ptr sle() const { return sle_; } - /** Returns the read view (always available) */ + /** Returns the read view (always available; ApplyView inherits ReadView) */ ReadView const& readView() const { - return readView_; + return view_; } + /** Const dereference operators (always available) */ STLedgerEntry const* operator->() const { - XRPL_ASSERT(exists(), "xrpl::ReadOnlySLE::operator-> : exists"); + XRPL_ASSERT(exists(), "xrpl::SLEBase::operator-> : exists"); return sle_.get(); } STLedgerEntry const& operator*() const { - XRPL_ASSERT(exists(), "xrpl::ReadOnlySLE::operator* : exists"); + XRPL_ASSERT(exists(), "xrpl::SLEBase::operator* : exists"); return *sle_; } -protected: - // Default constructor is deleted (cannot leave reference uninitialized) - ReadOnlySLE() = delete; - - /** Constructor for read-only context (ReadView) */ - explicit ReadOnlySLE(std::shared_ptr sle, ReadView const& view) - : sle_(std::move(sle)), readView_(view) - { - } - - std::shared_ptr sle_; // Always valid (const view) - ReadView const& readView_; // Always valid -}; - -/** - * Writable base class for all ledger entry view classes. - * - * Extends ReadOnlySLE with write access capabilities. - * Supports read-write (ApplyView) contexts. - * - * Derived classes should provide domain-specific accessors that hide - * implementation details of the underlying ledger entry format. - */ -class WritableSLE -{ -public: - virtual ~WritableSLE() = default; - - // Copy/move constructors are fine (reference can be initialized from another) - WritableSLE(WritableSLE const&) = default; - WritableSLE(WritableSLE&&) = default; - // Assignment operators are deleted (cannot rebind reference members) - WritableSLE& - operator=(WritableSLE const&) = delete; - WritableSLE& - operator=(WritableSLE&&) = delete; + // --- Writable interface (compile-time gated) --- /** Returns a mutable SLE for write operations */ - std::shared_ptr const& + sle_ptr_type const& mutableSle() const + requires is_writable { - return mutableSle_; + return sle_; } /** Returns true if this wrapper supports write operations */ bool canModify() const + requires is_writable { - return mutableSle_ != nullptr; + return sle_ != nullptr; } /** Returns the apply view for write operations */ ApplyView& applyView() const + requires is_writable { - return applyView_; + return view_; } + /** Mutable dereference operators */ STLedgerEntry* operator->() + requires is_writable { - XRPL_ASSERT(canModify(), "xrpl::WritableSLE::operator-> : can modify"); - return mutableSle_.get(); + XRPL_ASSERT(canModify(), "xrpl::SLEBase::operator-> : can modify"); + return sle_.get(); } STLedgerEntry& operator*() + requires is_writable { - XRPL_ASSERT(canModify(), "xrpl::WritableSLE::operator* : can modify"); - return *mutableSle_; + XRPL_ASSERT(canModify(), "xrpl::SLEBase::operator* : can modify"); + return *sle_; } void insert() + requires is_writable { - XRPL_ASSERT(canModify(), "xrpl::WritableSLE::insert : can modify"); - applyView_.insert(mutableSle_); + XRPL_ASSERT(canModify(), "xrpl::SLEBase::insert : can modify"); + view_.insert(sle_); } void erase() + requires is_writable { - XRPL_ASSERT(canModify(), "xrpl::WritableSLE::erase : can modify"); - applyView_.erase(mutableSle_); + XRPL_ASSERT(canModify(), "xrpl::SLEBase::erase : can modify"); + view_.erase(sle_); } void update() + requires is_writable { - XRPL_ASSERT(canModify(), "xrpl::WritableSLE::update : can modify"); - applyView_.update(mutableSle_); + XRPL_ASSERT(canModify(), "xrpl::SLEBase::update : can modify"); + view_.update(sle_); } void newSLE() + requires is_writable { - XRPL_ASSERT(!canModify(), "xrpl::WritableSLE::newSLE : mutableSle_ is not null"); - mutableSle_ = std::make_shared(key_); + XRPL_ASSERT(!canModify(), "xrpl::SLEBase::newSLE : sle_ is not null"); + sle_ = std::make_shared(key_); } protected: - // Default constructor is deleted (cannot leave reference uninitialized) - WritableSLE() = delete; + SLEBase() = delete; - /** Constructor for read-write context (ApplyView) */ - explicit WritableSLE(std::shared_ptr sle, ApplyView& view) - : applyView_(view) + /** Constructor for read-only context */ + explicit SLEBase(std::shared_ptr sle, ReadView const& view) + requires(!is_writable) + : view_(view), sle_(std::move(sle)) + { + } + + /** 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(!is_writable) + : view_(other.readView()), sle_(other.sle()) + { + } + + /** Constructor for writable context (from existing SLE) */ + explicit SLEBase(std::shared_ptr sle, ApplyView& view) + requires is_writable + : view_(view) , key_(sle ? Keylet(sle->getType(), sle->key()) : Keylet(ltANY, uint256{})) - , mutableSle_(std::move(sle)) + , sle_(std::move(sle)) { } - /** Constructor for read-write context (ApplyView) */ - explicit WritableSLE(Keylet const& key, ApplyView& view) - : applyView_(view), key_(key), mutableSle_(applyView_.peek(key)) + /** Constructor for writable context (peek from view by keylet) */ + explicit SLEBase(Keylet const& key, ApplyView& view) + requires is_writable + : view_(view), key_(key), sle_(view_.peek(key)) { } - ApplyView& applyView_; // ApplyView for write contexts (first for init order) - Keylet const key_; - std::shared_ptr mutableSle_; // Mutable SLE for write contexts + 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_; }; +// Backward-compatible aliases +using ReadOnlySLE = SLEBase; +using WritableSLE = SLEBase; + } // namespace xrpl diff --git a/src/libxrpl/ledger/View.cpp b/src/libxrpl/ledger/View.cpp index b51a06bb9f..0c0a8fbaf5 100644 --- a/src/libxrpl/ledger/View.cpp +++ b/src/libxrpl/ledger/View.cpp @@ -357,7 +357,7 @@ canWithdraw( ReadView const& view, AccountID const& from, AccountID const& to, - AccountRoot const& toWrapped, + ReadOnlyAccountRoot const& toWrapped, STAmount const& amount, bool hasDestinationTag) { diff --git a/src/libxrpl/ledger/helpers/AccountRootHelpers.cpp b/src/libxrpl/ledger/helpers/AccountRootHelpers.cpp index 961d09e466..e3c62e3b5a 100644 --- a/src/libxrpl/ledger/helpers/AccountRootHelpers.cpp +++ b/src/libxrpl/ledger/helpers/AccountRootHelpers.cpp @@ -11,12 +11,13 @@ namespace xrpl { +template bool -AccountRoot::isGlobalFrozen() const +AccountRoot::isGlobalFrozen() const { - if (!exists()) + if (!this->exists()) return false; - return sle_->isFlag(lsfGlobalFreeze); + return this->sle_->isFlag(lsfGlobalFreeze); } // An owner count cannot be negative. If adjustment would cause a negative @@ -61,23 +62,24 @@ confineOwnerCount( return adjusted; } +template XRPAmount -AccountRoot::xrpLiquid(std::int32_t ownerCountAdj, beast::Journal j) const +AccountRoot::xrpLiquid(std::int32_t ownerCountAdj, beast::Journal j) const { - if (!exists()) + if (!this->exists()) return beast::zero; // Return balance minus reserve std::uint32_t const ownerCount = confineOwnerCount( - readView_.ownerCountHook(id_, sle_->getFieldU32(sfOwnerCount)), ownerCountAdj); + this->readView().ownerCountHook(id_, this->sle_->getFieldU32(sfOwnerCount)), ownerCountAdj); // Pseudo-accounts have no reserve requirement auto const reserve = - isPseudoAccount() ? XRPAmount{0} : readView_.fees().accountReserve(ownerCount); + this->isPseudoAccount() ? XRPAmount{0} : this->readView().fees().accountReserve(ownerCount); - auto const fullBalance = sle_->getFieldAmount(sfBalance); + auto const fullBalance = this->sle_->getFieldAmount(sfBalance); - auto const balance = readView_.balanceHook(id_, xrpAccount(), fullBalance); + auto const balance = this->readView().balanceHook(id_, xrpAccount(), fullBalance); STAmount const amount = (balance < reserve) ? STAmount{0} : balance - reserve; @@ -90,26 +92,29 @@ AccountRoot::xrpLiquid(std::int32_t ownerCountAdj, beast::Journal j) const return amount.xrp(); } +template Rate -AccountRoot::transferRate() const +AccountRoot::transferRate() const { - if (sle_ && sle_->isFieldPresent(sfTransferRate)) - return Rate{sle_->getFieldU32(sfTransferRate)}; + if (this->sle_ && this->sle_->isFieldPresent(sfTransferRate)) + return Rate{this->sle_->getFieldU32(sfTransferRate)}; return parityRate; } +template void -WritableAccountRoot::adjustOwnerCount(std::int32_t amount, beast::Journal j) +AccountRoot::adjustOwnerCount(std::int32_t amount, beast::Journal j) + requires is_writable { - XRPL_ASSERT(canModify(), "xrpl::adjustOwnerCount : can modify"); + XRPL_ASSERT(this->canModify(), "xrpl::adjustOwnerCount : can modify"); XRPL_ASSERT(amount, "xrpl::adjustOwnerCount : nonzero amount input"); - std::uint32_t const current{mutableSle_->getFieldU32(sfOwnerCount)}; - AccountID const id = (*mutableSle_)[sfAccount]; + std::uint32_t const current{this->sle_->getFieldU32(sfOwnerCount)}; + AccountID const id = (*this->sle_)[sfAccount]; std::uint32_t const adjusted = confineOwnerCount(current, amount, id, j); - applyView_.adjustOwnerCountHook(id_, current, adjusted); - mutableSle_->at(sfOwnerCount) = adjusted; - update(); + this->applyView().adjustOwnerCountHook(id_, current, adjusted); + this->sle_->at(sfOwnerCount) = adjusted; + this->update(); } AccountID @@ -161,17 +166,18 @@ getPseudoAccountFields() return pseudoFields; } +template [[nodiscard]] bool -AccountRoot::isPseudoAccount(std::set const& pseudoFieldFilter) const +AccountRoot::isPseudoAccount(std::set const& pseudoFieldFilter) const { auto const& fields = getPseudoAccountFields(); // Intentionally use defensive coding here because it's cheap and makes the // semantics of true return value clean. - return sle_ && sle_->getType() == ltACCOUNT_ROOT && + return this->sle_ && this->sle_->getType() == ltACCOUNT_ROOT && std::count_if( fields.begin(), fields.end(), [this, &pseudoFieldFilter](SField const* sf) -> bool { - return sle_->isFieldPresent(*sf) && + return this->sle_->isFieldPresent(*sf) && (pseudoFieldFilter.empty() || pseudoFieldFilter.contains(sf)); }) > 0; } @@ -235,18 +241,23 @@ createPseudoAccount(ApplyView& view, uint256 const& pseudoOwnerKey, SField const return account; } +template [[nodiscard]] TER -AccountRoot::checkDestinationAndTag(bool hasDestinationTag) const +AccountRoot::checkDestinationAndTag(bool hasDestinationTag) const { - if (sle_ == nullptr) + if (this->sle_ == nullptr) return tecNO_DST; // The tag is basically account-specific information we don't // understand, but we can require someone to fill it in. - if (sle_->isFlag(lsfRequireDestTag) && !hasDestinationTag) + if (this->sle_->isFlag(lsfRequireDestTag) && !hasDestinationTag) return tecDST_TAG_NEEDED; // Cannot send without a tag return tesSUCCESS; } +// Explicit template instantiations +template class AccountRoot; +template class AccountRoot; + } // namespace xrpl diff --git a/src/libxrpl/ledger/helpers/CredentialHelpers.cpp b/src/libxrpl/ledger/helpers/CredentialHelpers.cpp index 56c669e35a..4867c6366e 100644 --- a/src/libxrpl/ledger/helpers/CredentialHelpers.cpp +++ b/src/libxrpl/ledger/helpers/CredentialHelpers.cpp @@ -321,7 +321,7 @@ verifyDepositPreauth( STTx const& tx, ApplyView& view, AccountID const& src, - AccountRoot const& dst, + ReadOnlyAccountRoot const& dst, beast::Journal j) { // If depositPreauth is enabled, then an account that requires diff --git a/src/xrpld/rpc/detail/TransactionSign.cpp b/src/xrpld/rpc/detail/TransactionSign.cpp index cf59164d7b..c761f88d1f 100644 --- a/src/xrpld/rpc/detail/TransactionSign.cpp +++ b/src/xrpld/rpc/detail/TransactionSign.cpp @@ -127,7 +127,7 @@ public: //------------------------------------------------------------------------------ static error_code_i -acctMatchesPubKey(AccountRoot const& account, PublicKey const& publicKey) +acctMatchesPubKey(ReadOnlyAccountRoot const& account, PublicKey const& publicKey) { auto const publicKeyAcctID = calcAccountID(publicKey); bool const isMasterKey = publicKeyAcctID == account.id(); @@ -457,7 +457,7 @@ transactionPreProcessImpl( if (!verify && !tx_json.isMember(jss::Sequence)) return RPC::missing_field_error("tx_json.Sequence"); - std::optional acctSrc; + std::optional acctSrc; if (verify) acctSrc.emplace(srcAddressID, *app.openLedger().current()); diff --git a/src/xrpld/rpc/handlers/AccountInfo.cpp b/src/xrpld/rpc/handlers/AccountInfo.cpp index e197ec9483..2cb8f60047 100644 --- a/src/xrpld/rpc/handlers/AccountInfo.cpp +++ b/src/xrpld/rpc/handlers/AccountInfo.cpp @@ -29,7 +29,7 @@ namespace xrpl { * If the entry is not an account root, sets the 'Invalid' field to true. */ void -injectSLE(Json::Value& jv, AccountRoot const& account) +injectSLE(Json::Value& jv, ReadOnlyAccountRoot const& account) { jv = account->getJson(JsonOptions::none); if (account->isFieldPresent(sfEmailHash))