diff --git a/include/xrpl/ledger/helpers/MPToken.h b/include/xrpl/ledger/helpers/MPToken.h new file mode 100644 index 0000000000..0b39325739 --- /dev/null +++ b/include/xrpl/ledger/helpers/MPToken.h @@ -0,0 +1,96 @@ +#pragma once + +#include +#include + +namespace xrpl { + +class MPToken : public virtual TokenHolderBase +{ +public: + MPToken(ReadView const& view, MPTokenIssuance const& issuance, AccountID const& holder) + : ReadOnlySLE(view.read(keylet::mptoken(issuance.getMptID(), holder)), view) + , TokenHolderBase( + view, + view.read(keylet::mptoken(issuance.getMptID(), holder)), + issuance, + holder) + , issuance_(issuance) + { + } + + MPTokenIssuance const& + getIssuance() const + { + return issuance_; + } + +protected: + MPTokenIssuance const& issuance_; +}; + +class WritableMPToken : public virtual WritableTokenHolderBase, public virtual MPToken +{ +public: + WritableMPToken(ApplyView& view, WritableMPTokenIssuance& issuance, AccountID const& holder) + : ReadOnlySLE(view.peek(keylet::mptoken(issuance.getMptID(), holder)), view) + , TokenHolderBase( + view, + view.peek(keylet::mptoken(issuance.getMptID(), holder)), + issuance, + holder) + , WritableSLE(view.peek(keylet::mptoken(issuance.getMptID(), holder)), view) + , WritableTokenHolderBase( + view, + view.peek(keylet::mptoken(issuance.getMptID(), holder)), + issuance, + holder) + , MPToken(view, issuance, holder) + , writableIssuance_(issuance) + { + } + + // Resolve ambiguity: use writable operator-> for non-const, read-only for const + using WritableSLE::operator->; + using MPToken::operator->; + using WritableSLE::operator*; + using MPToken::operator*; + + WritableMPTokenIssuance& + getWritableIssuance() + { + return writableIssuance_; + } + + static TER + createMPToken( + ApplyView& view, + WritableMPTokenIssuance& issuance, + AccountID const& account, + std::uint32_t const flags) + { + WritableMPToken mptoken(view, issuance, account); + + auto const ownerNode = + view.dirInsert(keylet::ownerDir(account), mptoken.key(), describeOwnerDir(account)); + + if (!ownerNode) + return tecDIR_FULL; // LCOV_EXCL_LINE + + mptoken.newSLE(); + + (*mptoken)[sfAccount] = account; + (*mptoken)[sfMPTokenIssuanceID] = issuance.getMptID(); + (*mptoken)[sfFlags] = flags; + (*mptoken)[sfOwnerNode] = *ownerNode; + + mptoken.insert(); + + return tesSUCCESS; + } + +protected: + WritableMPTokenIssuance& writableIssuance_; +}; + +} // namespace xrpl diff --git a/include/xrpl/ledger/helpers/RippleState.h b/include/xrpl/ledger/helpers/RippleState.h new file mode 100644 index 0000000000..756b0b7dba --- /dev/null +++ b/include/xrpl/ledger/helpers/RippleState.h @@ -0,0 +1,146 @@ +#pragma once + +#include +#include +#include + +namespace xrpl { + +class RippleState : public virtual TokenHolderBase +{ +public: + RippleState(ReadView const& view, IOUToken const& token, AccountID const& holder) + : ReadOnlySLE(view.read(keylet::line(holder, token.getIssuer(), token.getCurrency())), view) + , TokenHolderBase( + view, + view.read(keylet::line(holder, token.getIssuer(), token.getCurrency())), + token, + holder) + , iouToken_(token) + { + } + + /** Constructor with explicit SLE (for when SLE is already available) */ + RippleState( + ReadView const& view, + std::shared_ptr sle, + IOUToken const& token, + AccountID const& holder) + : ReadOnlySLE(sle, view), TokenHolderBase(view, sle, token, holder), iouToken_(token) + { + } + + IOUToken const& + getIOUToken() const + { + return iouToken_; + } + +protected: + IOUToken const& iouToken_; +}; + +class WritableRippleState : public virtual WritableTokenHolderBase, public virtual RippleState +{ +public: + WritableRippleState(ApplyView& view, WritableIOUToken& token, AccountID const& holder) + : ReadOnlySLE(view.peek(keylet::line(holder, token.getIssuer(), token.getCurrency())), view) + , TokenHolderBase( + view, + view.peek(keylet::line(holder, token.getIssuer(), token.getCurrency())), + token, + holder) + , WritableSLE(view.peek(keylet::line(holder, token.getIssuer(), token.getCurrency())), view) + , WritableTokenHolderBase( + view, + view.peek(keylet::line(holder, token.getIssuer(), token.getCurrency())), + token, + holder) + , RippleState(view, token, holder) + , writableIOUToken_(token) + { + } + + /** Constructor with explicit SLE (for creation or when SLE is already available) */ + WritableRippleState( + ApplyView& view, + std::shared_ptr sle, + WritableIOUToken& token, + AccountID const& holder) + : ReadOnlySLE(sle, view) + , TokenHolderBase(view, sle, token, holder) + , WritableSLE(sle, view) + , WritableTokenHolderBase(view, sle, token, holder) + , RippleState(view, sle, token, holder) + , writableIOUToken_(token) + { + } + + // Resolve ambiguity: use writable operator-> for non-const, read-only for const + using WritableSLE::operator->; + using RippleState::operator->; + using WritableSLE::operator*; + using RippleState::operator*; + + WritableIOUToken& + getWritableIOUToken() + { + return writableIOUToken_; + } + + static Expected, TER> + createHolding( + ApplyView& view, + WritableIOUToken& token, + AccountID const& accountID, + beast::Journal journal) + { + auto const ter = token.addEmptyHolding(accountID, XRPAmount{0}, journal); + if (ter != tesSUCCESS) + return Unexpected(ter); + return std::make_unique(view, token, accountID); + } + + //-------------------------------------------------------------------------- + // + // Trust line operations + // + //-------------------------------------------------------------------------- + + /** Create a trust line + + This can set an initial balance. + */ + [[nodiscard]] static TER + trustCreate( + ApplyView& view, + bool const bSrcHigh, + AccountID const& uSrcAccountID, + AccountID const& uDstAccountID, + uint256 const& uIndex, // ripple state entry + WritableAccountRoot& wrappedAcct, // the account being set. + bool const bAuth, // authorize account. + bool const bNoRipple, // others cannot ripple through + bool const bFreeze, // funds cannot leave + bool bDeepFreeze, // can neither receive nor send funds + STAmount const& saBalance, // balance of account being set. + // Issuer should be noAccount() + STAmount const& saLimit, // limit for account being set. + // Issuer should be the account being set. + std::uint32_t uQualityIn, + std::uint32_t uQualityOut, + beast::Journal j); + + [[nodiscard]] static TER + trustDelete( + ApplyView& view, + std::shared_ptr const& sleRippleState, + AccountID const& uLowAccountID, + AccountID const& uHighAccountID, + beast::Journal j); + +protected: + WritableIOUToken& writableIOUToken_; +}; + +} // namespace xrpl diff --git a/include/xrpl/ledger/helpers/RippleStateHelpers.h b/include/xrpl/ledger/helpers/RippleStateHelpers.h index a6a545efa7..7d69bc0e42 100644 --- a/include/xrpl/ledger/helpers/RippleStateHelpers.h +++ b/include/xrpl/ledger/helpers/RippleStateHelpers.h @@ -216,44 +216,6 @@ creditBalance( Currency const& currency); /** @} */ -//------------------------------------------------------------------------------ -// -// Trust line operations -// -//------------------------------------------------------------------------------ - -/** Create a trust line - - This can set an initial balance. -*/ -[[nodiscard]] TER -trustCreate( - ApplyView& view, - bool const bSrcHigh, - AccountID const& uSrcAccountID, - AccountID const& uDstAccountID, - uint256 const& uIndex, // ripple state entry - WritableAccountRoot& wrappedAcct, // the account being set. - bool const bAuth, // authorize account. - bool const bNoRipple, // others cannot ripple through - bool const bFreeze, // funds cannot leave - bool bDeepFreeze, // can neither receive nor send funds - STAmount const& saBalance, // balance of account being set. - // Issuer should be noAccount() - STAmount const& saLimit, // limit for account being set. - // Issuer should be the account being set. - std::uint32_t uQualityIn, - std::uint32_t uQualityOut, - beast::Journal j); - -[[nodiscard]] TER -trustDelete( - ApplyView& view, - std::shared_ptr const& sleRippleState, - AccountID const& uLowAccountID, - AccountID const& uHighAccountID, - beast::Journal j); - //------------------------------------------------------------------------------ // // IOU issuance/redemption diff --git a/include/xrpl/ledger/helpers/TokenHolderBase.h b/include/xrpl/ledger/helpers/TokenHolderBase.h new file mode 100644 index 0000000000..da2756be5b --- /dev/null +++ b/include/xrpl/ledger/helpers/TokenHolderBase.h @@ -0,0 +1,155 @@ +#pragma once + +#include +#include +#include +#include + +namespace xrpl { + +class TokenHolderBase : public virtual ReadOnlySLE +{ +public: + TokenHolderBase( + ReadView const& view, + std::shared_ptr sle, + TokenBase const& token, + AccountID const& holder) + : ReadOnlySLE(sle, view), token_(token), holder_(holder), holderAccount_(holder, view) + { + } + + /** Constructor with explicit keylet (for when SLE lookup is needed) */ + TokenHolderBase( + ReadView const& view, + Keylet const& key, + TokenBase const& token, + AccountID const& holder) + : ReadOnlySLE(key, view), token_(token), holder_(holder), holderAccount_(holder, view) + { + } + + TokenHolderBase() = delete; + + AccountID const& + getHolder() const + { + return holder_; + } + + TokenBase const& + getToken() const + { + return token_; + } + + [[nodiscard]] bool + isFrozen(int depth = 0) const + { + return token_.isFrozen(holder_, depth); + } + + [[nodiscard]] bool + isDeepFrozen(int depth = 0) const + { + return token_.isDeepFrozen(holder_, depth); + } + + [[nodiscard]] TER + checkFrozen() const + { + return token_.checkFrozen(holder_); + } + + [[nodiscard]] TER + checkDeepFrozen() const + { + return token_.checkDeepFrozen(holder_); + } + + STAmount + accountHolds( + FreezeHandling zeroIfFrozen, + beast::Journal j, + SpendableHandling includeFullBalance = shSIMPLE_BALANCE) const + { + return token_.accountHolds(holder_, zeroIfFrozen, j, includeFullBalance); + } + + [[nodiscard]] STAmount + accountHolds( + FreezeHandling zeroIfFrozen, + AuthHandling zeroIfUnauthorized, + beast::Journal j, + SpendableHandling includeFullBalance = shSIMPLE_BALANCE) const + { + return token_.accountHolds( + holder_, zeroIfFrozen, zeroIfUnauthorized, j, includeFullBalance); + } + + [[nodiscard]] TER + requireAuth(AuthType authType = AuthType::Legacy, int depth = 0) const + { + return token_.requireAuth(holder_, authType, depth); + } + + [[nodiscard]] TER + canTransfer(AccountID const& to) const + { + return token_.canTransfer(holder_, to); + } + + [[nodiscard]] TER + canTransfer(TokenHolderBase const& to) const + { + return token_.canTransfer(holder_, to.getHolder()); + } + +protected: + TokenBase const& token_; + AccountID const holder_; + AccountRoot holderAccount_; +}; + +class WritableTokenHolderBase : public virtual TokenHolderBase, public virtual WritableSLE +{ +public: + WritableTokenHolderBase( + ApplyView& view, + std::shared_ptr sle, + WritableTokenBase& token, + AccountID const& holder) + : ReadOnlySLE(sle, view) + , TokenHolderBase(view, sle, token, holder) + , WritableSLE(sle, view) + , writableToken_(token) + , writableHolderAccount_(holder, view) + { + } + + /** Constructor with explicit keylet (for creation or lookup by key) */ + WritableTokenHolderBase( + ApplyView& view, + Keylet const& key, + WritableTokenBase& token, + AccountID const& holder) + : ReadOnlySLE(key, view) + , TokenHolderBase(view, key, token, holder) + , WritableSLE(key, view) + , writableToken_(token) + , writableHolderAccount_(holder, view) + { + } + + WritableTokenBase& + getWritableToken() + { + return writableToken_; + } + +protected: + WritableTokenBase& writableToken_; + WritableAccountRoot writableHolderAccount_; +}; + +} // namespace xrpl diff --git a/include/xrpl/ledger/helpers/WrappedSLEBase.h b/include/xrpl/ledger/helpers/WrappedSLEBase.h index e2183eabd7..22d85ba21b 100644 --- a/include/xrpl/ledger/helpers/WrappedSLEBase.h +++ b/include/xrpl/ledger/helpers/WrappedSLEBase.h @@ -60,6 +60,12 @@ public: return readView_; } + Keylet const& + key() const + { + return key_; + } + STLedgerEntry const* operator->() const { @@ -80,12 +86,21 @@ protected: /** Constructor for read-only context (ReadView) */ explicit ReadOnlySLE(std::shared_ptr sle, ReadView const& view) - : sle_(std::move(sle)), readView_(view) + : sle_(std::move(sle)) + , readView_(view) + , key_(sle_ ? Keylet(sle_->getType(), sle_->key()) : Keylet(ltANY, uint256{})) + { + } + + /** Constructor for read-only context (ReadView) with explicit keylet */ + explicit ReadOnlySLE(Keylet const& key, ReadView const& view) + : sle_(view.read(key)), readView_(view), key_(key) { } std::shared_ptr sle_; // Always valid (const view) ReadView const& readView_; // Always valid + Keylet key_; // Keylet for this entry }; /** @@ -192,8 +207,8 @@ protected: { } - ApplyView& applyView_; // ApplyView for write contexts (first for init order) - Keylet const key_; + ApplyView& applyView_; // ApplyView for write contexts (first for init order) + Keylet key_; // Keylet for this entry std::shared_ptr mutableSle_; // Mutable SLE for write contexts }; diff --git a/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp b/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp index 705f41680a..3201864587 100644 --- a/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp +++ b/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -301,7 +302,7 @@ IOUToken::transferRate() const //------------------------------------------------------------------------------ TER -trustCreate( +WritableRippleState::trustCreate( ApplyView& view, bool const bSrcHigh, AccountID const& uSrcAccountID, @@ -415,7 +416,7 @@ trustCreate( } TER -trustDelete( +WritableRippleState::trustDelete( ApplyView& view, std::shared_ptr const& sleRippleState, AccountID const& uLowAccountID, @@ -551,7 +552,7 @@ issueIOU( state->setFieldAmount(sfBalance, final_balance); if (must_delete) { - return trustDelete( + return WritableRippleState::trustDelete( view, state, bSenderHigh ? account : issue.account, @@ -578,7 +579,7 @@ issueIOU( bool noRipple = (receiverAccount->getFlags() & lsfDefaultRipple) == 0; - return trustCreate( + return WritableRippleState::trustCreate( view, bSenderHigh, issue.account, @@ -644,7 +645,7 @@ redeemIOU( if (must_delete) { - return trustDelete( + return WritableRippleState::trustDelete( applyView, state, bSenderHigh ? issue.account : account, @@ -792,7 +793,7 @@ WritableIOUToken::addEmptyHolding( if (priorBalance < readView_.fees().accountReserve(ownerCount + 1)) return tecNO_LINE_INSUF_RESERVE; - return trustCreate( + return WritableRippleState::trustCreate( applyView_, high, srcId, @@ -865,7 +866,7 @@ WritableIOUToken::removeEmptyHolding(AccountID const& accountID, beast::Journal line->clearFlag(lsfHighReserve); } - return trustDelete( + return WritableRippleState::trustDelete( applyView_, line, line->at(sfLowLimit)->getIssuer(), @@ -906,7 +907,8 @@ deleteAMMTrustLine( if (ammAccountID && (low != *ammAccountID && high != *ammAccountID)) return terNO_AMM; - if (auto const ter = trustDelete(view, sleState, low, high, j); !isTesSuccess(ter)) + if (auto const ter = WritableRippleState::trustDelete(view, sleState, low, high, j); + !isTesSuccess(ter)) { JLOG(j.error()) << "deleteAMMTrustLine: failed to delete the trustline."; return ter; diff --git a/src/libxrpl/ledger/helpers/TokenHelpers.cpp b/src/libxrpl/ledger/helpers/TokenHelpers.cpp index 547431c79f..2bc8459404 100644 --- a/src/libxrpl/ledger/helpers/TokenHelpers.cpp +++ b/src/libxrpl/ledger/helpers/TokenHelpers.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -385,7 +386,7 @@ rippleCreditIOU( if (bDelete) { - return trustDelete( + return WritableRippleState::trustDelete( view, sleRippleState, bSenderHigh ? uReceiverID : uSenderID, @@ -413,7 +414,7 @@ rippleCreditIOU( bool const noRipple = (wrappedAccount->getFlags() & lsfDefaultRipple) == 0; - return trustCreate( + return WritableRippleState::trustCreate( view, bSenderHigh, uSenderID, diff --git a/src/libxrpl/tx/transactors/check/CheckCash.cpp b/src/libxrpl/tx/transactors/check/CheckCash.cpp index db63e1a5e8..ea3911afd4 100644 --- a/src/libxrpl/tx/transactors/check/CheckCash.cpp +++ b/src/libxrpl/tx/transactors/check/CheckCash.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -335,7 +336,7 @@ CheckCash::doApply() initialBalance.setIssuer(noAccount()); // clang-format off - if (TER const ter = trustCreate( + if (TER const ter = WritableRippleState::trustCreate( psb, // payment sandbox destLow, // is dest low? issuer, // source diff --git a/src/libxrpl/tx/transactors/escrow/EscrowHelpers.h b/src/libxrpl/tx/transactors/escrow/EscrowHelpers.h index a48b6cf50f..217788ff98 100644 --- a/src/libxrpl/tx/transactors/escrow/EscrowHelpers.h +++ b/src/libxrpl/tx/transactors/escrow/EscrowHelpers.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -76,7 +77,7 @@ escrowUnlockApplyHelper( initialBalance.setIssuer(noAccount()); // clang-format off - if (TER const ter = trustCreate( + if (TER const ter = WritableRippleState::trustCreate( view, // payment sandbox recvLow, // is dest low? issuer, // source diff --git a/src/libxrpl/tx/transactors/token/MPTokenAuthorize.cpp b/src/libxrpl/tx/transactors/token/MPTokenAuthorize.cpp index 002acf29e0..02ad144d9a 100644 --- a/src/libxrpl/tx/transactors/token/MPTokenAuthorize.cpp +++ b/src/libxrpl/tx/transactors/token/MPTokenAuthorize.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -28,6 +29,8 @@ MPTokenAuthorize::preclaim(PreclaimContext const& ctx) { auto const accountID = ctx.tx[sfAccount]; auto const holderID = ctx.tx[~sfHolder]; + MPTokenIssuance const mptIssuance(ctx.view, ctx.tx[sfMPTokenIssuanceID]); + MPToken const mpt(ctx.view, mptIssuance, accountID); // if non-issuer account submits this tx, then they are trying either: // 1. Unauthorize/delete MPToken @@ -37,9 +40,6 @@ MPTokenAuthorize::preclaim(PreclaimContext const& ctx) // `holderID` is NOT used if (!holderID) { - std::shared_ptr sleMpt = - ctx.view.read(keylet::mptoken(ctx.tx[sfMPTokenIssuanceID], accountID)); - // There is an edge case where all holders have zero balance, issuance // is legally destroyed, then outstanding MPT(s) are deleted afterwards. // Thus, there is no need to check for the existence of the issuance if @@ -49,36 +49,31 @@ MPTokenAuthorize::preclaim(PreclaimContext const& ctx) // if holder wants to delete/unauthorize a mpt if (ctx.tx.getFlags() & tfMPTUnauthorize) { - if (!sleMpt) + if (!mpt) return tecOBJECT_NOT_FOUND; - if ((*sleMpt)[sfMPTAmount] != 0) + if ((*mpt)[sfMPTAmount] != 0) { - auto const sleMptIssuance = - ctx.view.read(keylet::mptIssuance(ctx.tx[sfMPTokenIssuanceID])); - if (!sleMptIssuance) + if (!mptIssuance) return tefINTERNAL; // LCOV_EXCL_LINE return tecHAS_OBLIGATIONS; } - if ((*sleMpt)[~sfLockedAmount].value_or(0) != 0) + if ((*mpt)[~sfLockedAmount].value_or(0) != 0) { - auto const sleMptIssuance = - ctx.view.read(keylet::mptIssuance(ctx.tx[sfMPTokenIssuanceID])); - if (!sleMptIssuance) + if (!mptIssuance) return tefINTERNAL; // LCOV_EXCL_LINE return tecHAS_OBLIGATIONS; } - if (ctx.view.rules().enabled(featureSingleAssetVault) && sleMpt->isFlag(lsfMPTLocked)) + if (ctx.view.rules().enabled(featureSingleAssetVault) && mpt->isFlag(lsfMPTLocked)) return tecNO_PERMISSION; return tesSUCCESS; } // Now test when the holder wants to hold/create/authorize a new MPT - MPTokenIssuance const mptIssuance(ctx.view, MPTIssue{ctx.tx[sfMPTokenIssuanceID]}); if (!mptIssuance) return tecOBJECT_NOT_FOUND; @@ -87,7 +82,7 @@ MPTokenAuthorize::preclaim(PreclaimContext const& ctx) return tecNO_PERMISSION; // if holder wants to use and create a mpt - if (sleMpt) + if (mpt) return tecDUPLICATE; return tesSUCCESS; @@ -96,7 +91,6 @@ MPTokenAuthorize::preclaim(PreclaimContext const& ctx) if (AccountRoot const acctHolder(*holderID, ctx.view); !acctHolder) return tecNO_DST; - MPTokenIssuance const mptIssuance(ctx.view, MPTIssue{ctx.tx[sfMPTokenIssuanceID]}); if (!mptIssuance) return tecOBJECT_NOT_FOUND; diff --git a/src/libxrpl/tx/transactors/token/TrustSet.cpp b/src/libxrpl/tx/transactors/token/TrustSet.cpp index 9642af2e13..8e94f3bcdb 100644 --- a/src/libxrpl/tx/transactors/token/TrustSet.cpp +++ b/src/libxrpl/tx/transactors/token/TrustSet.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -581,7 +582,8 @@ TrustSet::doApply() { // Delete. - terResult = trustDelete(view(), sleRippleState, uLowAccountID, uHighAccountID, viewJ); + terResult = WritableRippleState::trustDelete( + view(), sleRippleState, uLowAccountID, uHighAccountID, viewJ); } // Reserve is not scaled by load. else if (bReserveIncrease && preFeeBalance_ < reserveCreate) @@ -632,7 +634,7 @@ TrustSet::doApply() JLOG(j_.trace()) << "doTrustSet: Creating ripple line: " << to_string(k.key); // Create a new ripple line. - terResult = trustCreate( + terResult = WritableRippleState::trustCreate( view(), bHigh, accountID_, diff --git a/src/test/app/Invariants_test.cpp b/src/test/app/Invariants_test.cpp index f4de000004..4f9015a031 100644 --- a/src/test/app/Invariants_test.cpp +++ b/src/test/app/Invariants_test.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -404,7 +405,7 @@ class Invariants_test : public beast::unit_test::suite STAmount const lowLimit = line->at(sfLowLimit); STAmount const highLimit = line->at(sfHighLimit); BEAST_EXPECT( - trustDelete( + WritableRippleState::trustDelete( ac.view(), line, lowLimit.getIssuer(),