diff --git a/include/xrpl/ledger/helpers/MPToken.h b/include/xrpl/ledger/helpers/MPToken.h new file mode 100644 index 00000000000..8706dc2c8e9 --- /dev/null +++ b/include/xrpl/ledger/helpers/MPToken.h @@ -0,0 +1,124 @@ +#pragma once + +#include +#include +#include + +namespace xrpl { + +class MPToken : public virtual TokenHolderBase +{ +public: + MPToken(MPTokenIssuance const& issuance, AccountID const& holder) + : ReadOnlySLE( + issuance.readView().read(keylet::mptoken(issuance.getMptID(), holder)), + issuance.readView()) + , TokenHolderBase( + issuance.readView(), + issuance.readView().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(WritableMPTokenIssuance& issuance, AccountID const& holder) + : ReadOnlySLE( + issuance.applyView().peek(keylet::mptoken(issuance.getMptID(), holder)), + issuance.applyView()) + , TokenHolderBase( + issuance.applyView(), + issuance.applyView().peek(keylet::mptoken(issuance.getMptID(), holder)), + issuance, + holder) + , WritableSLE( + issuance.applyView().peek(keylet::mptoken(issuance.getMptID(), holder)), + issuance.applyView()) + , WritableTokenHolderBase( + issuance.applyView(), + issuance.applyView().peek(keylet::mptoken(issuance.getMptID(), holder)), + issuance, + holder) + , MPToken(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( + WritableMPTokenIssuance& issuance, + AccountID const& account, + std::uint32_t const flags) + { + WritableMPToken mptoken = makeNew(issuance, account); + + auto const ownerNode = mptoken.applyView().dirInsert( + keylet::ownerDir(account), mptoken->key(), describeOwnerDir(account)); + + if (!ownerNode) + return tecDIR_FULL; // LCOV_EXCL_LINE + + (*mptoken)[sfAccount] = account; + (*mptoken)[sfMPTokenIssuanceID] = issuance.getMptID(); + (*mptoken)[sfFlags] = flags; + (*mptoken)[sfOwnerNode] = *ownerNode; + + return tesSUCCESS; + } + + /** Create a WritableMPToken backed by a brand-new SLE + * (not yet inserted into the view). + */ + [[nodiscard]] static WritableMPToken + makeNew(WritableMPTokenIssuance& issuance, AccountID const& holder) + { + return WritableMPToken( + issuance, holder, std::make_shared(keylet::mptoken(issuance.getMptID(), holder))); + } + +private: + // This is a private constructor only used by `makeNew` + WritableMPToken( + WritableMPTokenIssuance& issuance, + AccountID const& holder, + std::shared_ptr sle) + : ReadOnlySLE(sle, issuance.applyView()) + , TokenHolderBase(issuance.applyView(), sle, issuance, holder) + , WritableSLE(sle, issuance.applyView()) + , WritableTokenHolderBase(issuance.applyView(), sle, issuance, holder) + , MPToken(issuance, holder) + , writableIssuance_(issuance) + { + insert(); + } + +protected: + WritableMPTokenIssuance& writableIssuance_; +}; + +} // namespace xrpl diff --git a/include/xrpl/ledger/helpers/MPTokenHelpers.h b/include/xrpl/ledger/helpers/MPTokenHelpers.h index ae6e507031d..3c768b2401a 100644 --- a/include/xrpl/ledger/helpers/MPTokenHelpers.h +++ b/include/xrpl/ledger/helpers/MPTokenHelpers.h @@ -130,6 +130,12 @@ class MPTokenIssuance : public virtual TokenBase [[nodiscard]] bool requiresAuth() const override; + [[nodiscard]] bool + hasHolder(AccountID const& holder) const override + { + return readView_.exists(keylet::mptoken(mptID_, holder)); + } + STAmount accountHolds( AccountID const& account, diff --git a/include/xrpl/ledger/helpers/RippleState.h b/include/xrpl/ledger/helpers/RippleState.h new file mode 100644 index 00000000000..7969eaa22a2 --- /dev/null +++ b/include/xrpl/ledger/helpers/RippleState.h @@ -0,0 +1,158 @@ +#pragma once + +#include +#include +#include + +namespace xrpl { + +class RippleState : public virtual TokenHolderBase +{ +public: + RippleState(IOUToken const& token, AccountID const& holder) + : ReadOnlySLE( + token.readView().read(keylet::line(holder, token.getIssuer(), token.getCurrency())), + token.readView()) + , TokenHolderBase( + token.readView(), + token.readView().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(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_; + } + + //-------------------------------------------------------------------------- + // + // 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); + + /** Create a WritableRippleState backed by a brand-new SLE + * (not yet inserted into the view). + */ + [[nodiscard]] static WritableRippleState + makeNew(WritableIOUToken& token, AccountID const& holder) + { + return WritableRippleState( + token, holder, std::make_shared(keylet::line(holder, token.getIssue()))); + } + +private: + // This is a private constructor only used by `makeNew` + WritableRippleState(WritableIOUToken& token, AccountID const& holder, std::shared_ptr sle) + : ReadOnlySLE(sle, token.applyView()) + , TokenHolderBase(token.applyView(), sle, token, holder) + , WritableSLE(sle, token.applyView()) + , WritableTokenHolderBase(token.applyView(), sle, token, holder) + , RippleState(token, holder) + , writableIOUToken_(token) + { + insert(); + } + +protected: + WritableIOUToken& writableIOUToken_; +}; + +} // namespace xrpl diff --git a/include/xrpl/ledger/helpers/RippleStateHelpers.h b/include/xrpl/ledger/helpers/RippleStateHelpers.h index a176ce4e33e..d5148e29972 100644 --- a/include/xrpl/ledger/helpers/RippleStateHelpers.h +++ b/include/xrpl/ledger/helpers/RippleStateHelpers.h @@ -49,6 +49,12 @@ class IOUToken : public virtual TokenBase return currency_; } + [[nodiscard]] Issue const& + getIssue() const + { + return issue_; + } + [[nodiscard]] bool isGlobalFrozen() const override { @@ -132,6 +138,12 @@ class IOUToken : public virtual TokenBase [[nodiscard]] bool requiresAuth() const override; + [[nodiscard]] bool + hasHolder(AccountID const& holder) const override + { + return readView_.exists(keylet::line(issuer_, holder, currency_)); + } + protected: Issue const issue_; AccountID const issuer_; @@ -244,44 +256,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/TokenHelpers.h b/include/xrpl/ledger/helpers/TokenHelpers.h index 6fd7ec05863..f69a4da5633 100644 --- a/include/xrpl/ledger/helpers/TokenHelpers.h +++ b/include/xrpl/ledger/helpers/TokenHelpers.h @@ -160,6 +160,9 @@ class TokenBase : public virtual ReadOnlySLE [[nodiscard]] virtual bool requiresAuth() const = 0; + [[nodiscard]] virtual bool + hasHolder(AccountID const& holder) const = 0; + protected: TokenBase(ReadView const& view, std::shared_ptr sle) : ReadOnlySLE(sle, view) { diff --git a/include/xrpl/ledger/helpers/TokenHolderBase.h b/include/xrpl/ledger/helpers/TokenHolderBase.h new file mode 100644 index 00000000000..2a7537cd4a6 --- /dev/null +++ b/include/xrpl/ledger/helpers/TokenHolderBase.h @@ -0,0 +1,132 @@ +#pragma once + +#include +#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) + { + } + + 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) + { + } + + WritableTokenBase& + getWritableToken() + { + return writableToken_; + } + +protected: + WritableTokenBase& writableToken_; + WritableAccountRoot writableHolderAccount_; +}; + +} // namespace xrpl diff --git a/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp b/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp index a208ba2306a..ab6dd118a0d 100644 --- a/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp +++ b/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -60,10 +61,7 @@ MPTokenIssuance::isAnyFrozen(std::initializer_list const& accounts, i { if (isIndividualFrozen(account)) return true; - } - for (auto const& account : accounts) - { if (isVaultPseudoAccountFrozen(readView_, account, mptIssue_, depth)) return true; } @@ -156,18 +154,17 @@ WritableMPTokenIssuance::authorizeMPToken( // - delete the MPToken if (flags & tfMPTUnauthorize) { - auto const mptokenKey = keylet::mptoken(mptID_, account); - auto const sleMpt = applyView_.peek(mptokenKey); - if (!sleMpt || (*sleMpt)[sfMPTAmount] != 0) + WritableMPToken mptoken(*this, account); + if (!mptoken.exists() || (*mptoken)[sfMPTAmount] != 0) return tecINTERNAL; // LCOV_EXCL_LINE if (!applyView_.dirRemove( - keylet::ownerDir(account), (*sleMpt)[sfOwnerNode], sleMpt->key(), false)) + keylet::ownerDir(account), (*mptoken)[sfOwnerNode], mptoken->key(), false)) return tecINTERNAL; // LCOV_EXCL_LINE wrappedAcct.adjustOwnerCount(-1, journal); - applyView_.erase(sleMpt); + mptoken.erase(); return tesSUCCESS; } @@ -370,8 +367,7 @@ WritableMPTokenIssuance::enforceMPTokenAuthorization( if (account == mutableSle_->at(sfIssuer)) return tefINTERNAL; // LCOV_EXCL_LINE - auto const keylet = keylet::mptoken(mptID_, account); - auto const sleToken = readView_.read(keylet); // NOTE: might be null + MPToken mptoken(*this, account); auto const maybeDomainID = mutableSle_->at(~sfDomainID); bool expired = false; bool const authorizedByDomain = [&]() -> bool { @@ -387,7 +383,7 @@ WritableMPTokenIssuance::enforceMPTokenAuthorization( return false; }(); - if (!authorizedByDomain && sleToken == nullptr) + if (!authorizedByDomain && !mptoken.exists()) { // Could not find MPToken and won't create one, could be either of: // @@ -410,14 +406,14 @@ WritableMPTokenIssuance::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, + mptoken.exists() && !maybeDomainID, "xrpl::enforceMPTokenAuthorization : found MPToken"); - if (sleToken->isFlag(lsfMPTAuthorized)) + if (mptoken->isFlag(lsfMPTAuthorized)) return tesSUCCESS; return tecNO_AUTH; } - if (authorizedByDomain && sleToken != nullptr) + if (authorizedByDomain && mptoken.exists()) { // Found an MPToken, authorized by the domain. Ignore authorization flag // lsfMPTAuthorized because it is meaningless. Return tesSUCCESS @@ -429,7 +425,7 @@ WritableMPTokenIssuance::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 && sleToken == nullptr, + maybeDomainID && !mptoken.exists(), "xrpl::enforceMPTokenAuthorization : new MPToken for domain"); if (auto const err = authorizeMPToken( priorBalance, // priorBalance @@ -640,15 +636,14 @@ rippleUnlockEscrowMPT( if (issuer != receiver) { // Increase the MPT Holder MPTAmount - auto const mptokenID = keylet::mptoken(mptIssue.getMptID(), receiver); - auto sle = view.peek(mptokenID); - if (!sle) + WritableMPToken mpt(mptIssuance, receiver); + if (!mpt) { // LCOV_EXCL_START JLOG(j.error()) << "rippleUnlockEscrowMPT: MPToken not found for " << receiver; return tecOBJECT_NOT_FOUND; } // LCOV_EXCL_STOP - auto current = sle->getFieldU64(sfMPTAmount); + auto current = mpt->getFieldU64(sfMPTAmount); auto delta = netAmount.mpt().value(); // Overflow check for addition @@ -659,8 +654,8 @@ rippleUnlockEscrowMPT( return tecINTERNAL; } // LCOV_EXCL_STOP - (*sle)[sfMPTAmount] += delta; - view.update(sle); + (*mpt)[sfMPTAmount] += delta; + mpt.update(); } else { @@ -687,22 +682,21 @@ rippleUnlockEscrowMPT( return tecINTERNAL; } // LCOV_EXCL_STOP // Decrease the MPT Holder EscrowedAmount - auto const mptokenID = keylet::mptoken(mptIssue.getMptID(), sender); - auto sle = view.peek(mptokenID); - if (!sle) + WritableMPToken mpt(mptIssuance, sender); + if (!mpt.exists()) { // LCOV_EXCL_START JLOG(j.error()) << "rippleUnlockEscrowMPT: MPToken not found for " << sender; return tecOBJECT_NOT_FOUND; } // LCOV_EXCL_STOP - if (!sle->isFieldPresent(sfLockedAmount)) + if (!mpt->isFieldPresent(sfLockedAmount)) { // LCOV_EXCL_START JLOG(j.error()) << "rippleUnlockEscrowMPT: no locked amount in MPToken for " << to_string(sender); return tecINTERNAL; } // LCOV_EXCL_STOP - auto const locked = sle->getFieldU64(sfLockedAmount); + auto const locked = mpt->getFieldU64(sfLockedAmount); auto const delta = grossAmount.mpt().value(); // Underflow check for subtraction @@ -716,13 +710,13 @@ rippleUnlockEscrowMPT( auto const newLocked = locked - delta; if (newLocked == 0) { - sle->makeFieldAbsent(sfLockedAmount); + mpt->makeFieldAbsent(sfLockedAmount); } else { - sle->setFieldU64(sfLockedAmount, newLocked); + mpt->setFieldU64(sfLockedAmount, newLocked); } - view.update(sle); + mpt.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 @@ -782,9 +776,9 @@ MPTokenIssuance::accountHolds( STAmount amount; - auto const sleMpt = readView_.read(keylet::mptoken(mptID_, account)); + MPToken mpt(*this, account); - if (!sleMpt) + if (!mpt.exists()) { amount.clear(mptIssue_); } @@ -794,7 +788,7 @@ MPTokenIssuance::accountHolds( } else { - amount = STAmount{mptIssue_, sleMpt->getFieldU64(sfMPTAmount)}; + amount = STAmount{mptIssue_, mpt->getFieldU64(sfMPTAmount)}; // Only if auth check is needed, as it needs to do an additional read // operation. Note featureSingleAssetVault will affect error codes. @@ -808,7 +802,7 @@ MPTokenIssuance::accountHolds( { // if auth is enabled on the issuance and mpt is not authorized, // clear amount - if (sle_ && sle_->isFlag(lsfMPTRequireAuth) && !sleMpt->isFlag(lsfMPTAuthorized)) + if (sle_ && sle_->isFlag(lsfMPTRequireAuth) && !mpt->isFlag(lsfMPTAuthorized)) amount.clear(mptIssue_); } } diff --git a/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp b/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp index 9e1353429bd..91692ed26c1 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 @@ -303,7 +304,7 @@ IOUToken::transferRate() const //------------------------------------------------------------------------------ TER -trustCreate( +WritableRippleState::trustCreate( ApplyView& view, bool const bSrcHigh, AccountID const& uSrcAccountID, @@ -417,7 +418,7 @@ trustCreate( } TER -trustDelete( +WritableRippleState::trustDelete( ApplyView& view, std::shared_ptr const& sleRippleState, AccountID const& uLowAccountID, @@ -553,7 +554,7 @@ issueIOU( state->setFieldAmount(sfBalance, final_balance); if (must_delete) { - return trustDelete( + return WritableRippleState::trustDelete( view, state, bSenderHigh ? account : issue.account, @@ -580,7 +581,7 @@ issueIOU( bool noRipple = (receiverAccount->getFlags() & lsfDefaultRipple) == 0; - return trustCreate( + return WritableRippleState::trustCreate( view, bSenderHigh, issue.account, @@ -646,7 +647,7 @@ redeemIOU( if (must_delete) { - return trustDelete( + return WritableRippleState::trustDelete( applyView, state, bSenderHigh ? issue.account : account, @@ -794,7 +795,7 @@ WritableIOUToken::addEmptyHolding( if (priorBalance < readView_.fees().accountReserve(ownerCount + 1)) return tecNO_LINE_INSUF_RESERVE; - return trustCreate( + return WritableRippleState::trustCreate( applyView_, high, srcId, @@ -867,7 +868,7 @@ WritableIOUToken::removeEmptyHolding(AccountID const& accountID, beast::Journal line->clearFlag(lsfHighReserve); } - return trustDelete( + return WritableRippleState::trustDelete( applyView_, line, line->at(sfLowLimit)->getIssuer(), @@ -908,7 +909,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 547431c79f2..255e2d19375 100644 --- a/src/libxrpl/ledger/helpers/TokenHelpers.cpp +++ b/src/libxrpl/ledger/helpers/TokenHelpers.cpp @@ -3,7 +3,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -385,7 +387,7 @@ rippleCreditIOU( if (bDelete) { - return trustDelete( + return WritableRippleState::trustDelete( view, sleRippleState, bSenderHigh ? uReceiverID : uSenderID, @@ -413,7 +415,7 @@ rippleCreditIOU( bool const noRipple = (wrappedAccount->getFlags() & lsfDefaultRipple) == 0; - return trustCreate( + return WritableRippleState::trustCreate( view, bSenderHigh, uSenderID, @@ -823,15 +825,15 @@ rippleCreditMPT( } else { - auto const mptokenID = keylet::mptoken(mptIssuance.getMptID(), uSenderID); - if (auto sle = view.peek(mptokenID)) + WritableMPToken mpt(mptIssuance, uSenderID); + if (mpt.exists()) { - auto const amt = sle->getFieldU64(sfMPTAmount); + auto const amt = mpt->getFieldU64(sfMPTAmount); auto const pay = saAmount.mpt().value(); if (amt < pay) return tecINSUFFICIENT_FUNDS; - (*sle)[sfMPTAmount] = amt - pay; - view.update(sle); + (*mpt)[sfMPTAmount] = amt - pay; + mpt.update(); } else { @@ -855,11 +857,11 @@ rippleCreditMPT( } else { - auto const mptokenID = keylet::mptoken(mptIssuance.getMptID(), uReceiverID); - if (auto sle = view.peek(mptokenID)) + WritableMPToken mptoken(mptIssuance, uReceiverID); + if (mptoken.exists()) { - (*sle)[sfMPTAmount] += saAmount.mpt().value(); - view.update(sle); + (*mptoken)[sfMPTAmount] += saAmount.mpt().value(); + mptoken.update(); } else { diff --git a/src/libxrpl/tx/transactors/check/CheckCash.cpp b/src/libxrpl/tx/transactors/check/CheckCash.cpp index db63e1a5e8d..e722769c6b4 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 @@ -194,8 +195,7 @@ CheckCash::preclaim(PreclaimContext const& ctx) // However, the trustline from destination to issuer may not // be frozen. - IOUToken wrapped(ctx.view, Issue{currency, issuerId}); - if (wrapped.isFrozen(dstId)) + if (iouToken.isFrozen(dstId)) { JLOG(ctx.j.warn()) << "Cashing a check to a frozen trustline."; return tecFROZEN; @@ -335,7 +335,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/EscrowCreate.cpp b/src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp index 53631dd2094..9a004b64f56 100644 --- a/src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp +++ b/src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp @@ -4,6 +4,10 @@ #include #include #include +#include +#include +#include +#include #include #include #include @@ -159,24 +163,25 @@ escrowCreatePreclaimHelper( AccountID const& dest, STAmount const& amount) { - AccountID issuer = amount.getIssuer(); + IOUToken const token(ctx.view, amount.issue()); + AccountID const& issuer = token.getIssuer(); // If the issuer is the same as the account, return tecNO_PERMISSION if (issuer == account) return tecNO_PERMISSION; // If the lsfAllowTrustLineLocking is not enabled, return tecNO_PERMISSION - AccountRoot const acctIssuer(issuer, ctx.view); - if (!acctIssuer) + if (!token.exists()) return tecNO_ISSUER; - if (!acctIssuer->isFlag(lsfAllowTrustLineLocking)) + if (!token->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::line(account, issuer, amount.getCurrency())); - if (!sleRippleState) + RippleState const accountHolder(token, account); + RippleState const destHolder(token, dest); + if (!accountHolder) return tecNO_LINE; - STAmount const balance = (*sleRippleState)[sfBalance]; + STAmount const balance = (*accountHolder)[sfBalance]; // If balance is positive, issuer must have higher address than account if (balance > beast::zero && issuer < account) @@ -187,24 +192,23 @@ escrowCreatePreclaimHelper( return tecNO_PERMISSION; // LCOV_EXCL_LINE // If the issuer has requireAuth set, check if the account is authorized - auto const token = IOUToken(ctx.view, amount.issue()); - if (auto const ter = token.requireAuth(account); !isTesSuccess(ter)) + if (auto const ter = accountHolder.requireAuth(); !isTesSuccess(ter)) return ter; // If the issuer has requireAuth set, check if the destination is authorized - if (auto const ter = token.requireAuth(dest); !isTesSuccess(ter)) + if (auto const ter = destHolder.requireAuth(); !isTesSuccess(ter)) return ter; // If the issuer has frozen the account, return tecFROZEN - if (token.isFrozen(account)) + if (accountHolder.isFrozen()) return tecFROZEN; // If the issuer has frozen the destination, return tecFROZEN - if (token.isFrozen(dest)) + if (destHolder.isFrozen()) return tecFROZEN; STAmount const spendableAmount = - accountHolds(ctx.view, account, amount.getCurrency(), issuer, fhIGNORE_FREEZE, ctx.j); + accountHolder.accountHolds(fhIGNORE_FREEZE, ahIGNORE_AUTH, ctx.j); // If the balance is less than or equal to 0, return tecINSUFFICIENT_FUNDS if (spendableAmount <= beast::zero) @@ -230,13 +234,13 @@ escrowCreatePreclaimHelper( AccountID const& dest, STAmount const& amount) { - AccountID issuer = amount.getIssuer(); + auto const mptIssuance = MPTokenIssuance(ctx.view, amount.get()); + AccountID const& issuer = mptIssuance.getIssuer(); // If the issuer is the same as the account, return tecNO_PERMISSION if (issuer == account) return tecNO_PERMISSION; // If the mpt does not exist, return tecOBJECT_NOT_FOUND - auto const mptIssuance = MPTokenIssuance(ctx.view, amount.get()); if (!mptIssuance.exists()) return tecOBJECT_NOT_FOUND; @@ -249,34 +253,36 @@ escrowCreatePreclaimHelper( if (mptIssuance->getAccountID(sfIssuer) != issuer) return tecNO_PERMISSION; // LCOV_EXCL_LINE + MPToken const accountHolder(mptIssuance, account); + MPToken const destHolder(mptIssuance, dest); // If the account does not have the mpt, return tecOBJECT_NOT_FOUND - if (!ctx.view.exists(keylet::mptoken(mptIssuance.getMptID(), account))) + if (!accountHolder.exists()) return tecOBJECT_NOT_FOUND; // If the issuer has requireAuth set, check if the account is // authorized - if (auto const ter = mptIssuance.requireAuth(account, AuthType::WeakAuth); !isTesSuccess(ter)) + if (auto const ter = accountHolder.requireAuth(AuthType::WeakAuth); !isTesSuccess(ter)) return ter; // If the issuer has requireAuth set, check if the destination is // authorized - if (auto const ter = mptIssuance.requireAuth(dest, AuthType::WeakAuth); !isTesSuccess(ter)) + if (auto const ter = destHolder.requireAuth(AuthType::WeakAuth); !isTesSuccess(ter)) return ter; // If the issuer has frozen the account, return tecLOCKED - if (mptIssuance.isFrozen(account)) + if (accountHolder.isFrozen()) return tecLOCKED; // If the issuer has frozen the destination, return tecLOCKED - if (mptIssuance.isFrozen(dest)) + if (destHolder.isFrozen()) return tecLOCKED; // If the mpt cannot be transferred, return tecNO_AUTH - if (auto const ter = mptIssuance.canTransfer(account, dest); !isTesSuccess(ter)) + if (auto const ter = accountHolder.canTransfer(dest); !isTesSuccess(ter)) return ter; - STAmount const spendableAmount = accountHolds( - ctx.view, account, amount.get(), fhIGNORE_FREEZE, ahIGNORE_AUTH, ctx.j); + STAmount const spendableAmount = + accountHolder.accountHolds(fhIGNORE_FREEZE, ahIGNORE_AUTH, ctx.j); // If the balance is less than or equal to 0, return tecINSUFFICIENT_FUNDS if (spendableAmount <= beast::zero) diff --git a/src/libxrpl/tx/transactors/escrow/EscrowHelpers.h b/src/libxrpl/tx/transactors/escrow/EscrowHelpers.h index a48b6cf50f5..c51ceb5ce45 100644 --- a/src/libxrpl/tx/transactors/escrow/EscrowHelpers.h +++ b/src/libxrpl/tx/transactors/escrow/EscrowHelpers.h @@ -4,11 +4,12 @@ #include #include #include +#include #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 @@ -181,9 +182,8 @@ escrowUnlockApplyHelper( bool const senderIssuer = issuer == sender; bool const receiverIssuer = issuer == receiver; - auto const mptIssuance = MPTokenIssuance(view, amount.get()); - auto const mptID = mptIssuance.getMptID(); - if (!view.exists(keylet::mptoken(mptID, receiver)) && createAsset && !receiverIssuer) + WritableMPTokenIssuance mptIssuance(view, amount.get()); + if (!mptIssuance.hasHolder(receiver) && createAsset && !receiverIssuer) { // For backwards compatibility: if dest is not WritableAccountRoot, return error if (!std::holds_alternative(dest)) @@ -197,7 +197,7 @@ escrowUnlockApplyHelper( return tecINSUFFICIENT_RESERVE; } - if (auto const ter = MPTokenAuthorize::createMPToken(view, mptID, receiver, 0); + if (auto const ter = WritableMPToken::createMPToken(mptIssuance, receiver, 0); !isTesSuccess(ter)) { return ter; // LCOV_EXCL_LINE @@ -207,7 +207,7 @@ escrowUnlockApplyHelper( wrappedDest.adjustOwnerCount(1, journal); } - if (!view.exists(keylet::mptoken(mptID, receiver)) && !receiverIssuer) + if (!mptIssuance.hasHolder(receiver) && !receiverIssuer) return tecNO_PERMISSION; auto const xferRate = mptIssuance.transferRate(); diff --git a/src/libxrpl/tx/transactors/nft/NFTokenUtils.cpp b/src/libxrpl/tx/transactors/nft/NFTokenUtils.cpp index 75d7d8dd8c2..b7ff3b2c19f 100644 --- a/src/libxrpl/tx/transactors/nft/NFTokenUtils.cpp +++ b/src/libxrpl/tx/transactors/nft/NFTokenUtils.cpp @@ -830,6 +830,7 @@ tokenOfferCreatePreclaim( std::optional const& owner, std::uint32_t txFlags) { + IOUToken token(view, amount.issue()); if (!(nftFlags & nft::flagCreateTrustLines) && !amount.native() && xferFee) { if (!view.exists(keylet::account(nftIssuer))) @@ -843,13 +844,12 @@ tokenOfferCreatePreclaim( !view.read(keylet::line(nftIssuer, amount.issue()))) return tecNO_LINE; } - else if (!view.exists(keylet::line(nftIssuer, amount.issue()))) + else if (!token.hasHolder(nftIssuer)) { return tecNO_LINE; } - IOUToken wrapped(view, amount.issue()); - if (wrapped.isFrozen(nftIssuer)) + if (token.isFrozen(nftIssuer)) return tecFROZEN; } @@ -862,7 +862,7 @@ tokenOfferCreatePreclaim( return tefNFTOKEN_IS_NOT_TRANSFERABLE; } - if (IOUToken(view, amount.issue()).isFrozen(acctID)) + if (token.isFrozen(acctID)) return tecFROZEN; // If this is an offer to buy the token, the account must have the diff --git a/src/libxrpl/tx/transactors/token/Clawback.cpp b/src/libxrpl/tx/transactors/token/Clawback.cpp index 93663c22bb7..4917cffdef0 100644 --- a/src/libxrpl/tx/transactors/token/Clawback.cpp +++ b/src/libxrpl/tx/transactors/token/Clawback.cpp @@ -132,18 +132,17 @@ preclaimHelper( AccountID const& holder, STAmount const& clawAmount) { - auto const issuanceKey = keylet::mptIssuance(clawAmount.get().getMptID()); - auto const sleIssuance = ctx.view.read(issuanceKey); - if (!sleIssuance) + MPTokenIssuance const mptIssuance(ctx.view, clawAmount.get()); + if (!mptIssuance.exists()) return tecOBJECT_NOT_FOUND; - if (!((*sleIssuance)[sfFlags] & lsfMPTCanClawback)) + if (!mptIssuance.canClawback()) return tecNO_PERMISSION; - if (sleIssuance->getAccountID(sfIssuer) != issuer) + if (mptIssuance.getIssuer() != issuer) return tecNO_PERMISSION; - if (!ctx.view.exists(keylet::mptoken(issuanceKey.key, holder))) + if (!mptIssuance.hasHolder(holder)) return tecOBJECT_NOT_FOUND; if (accountHolds( diff --git a/src/libxrpl/tx/transactors/token/MPTokenAuthorize.cpp b/src/libxrpl/tx/transactors/token/MPTokenAuthorize.cpp index 002acf29e06..b32586d320c 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(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; @@ -116,7 +110,7 @@ MPTokenAuthorize::preclaim(PreclaimContext const& ctx) return tecNO_AUTH; // The holder must create the MPT before the issuer can authorize it. - if (!ctx.view.exists(keylet::mptoken(ctx.tx[sfMPTokenIssuanceID], *holderID))) + if (!mptIssuance.hasHolder(*holderID)) return tecOBJECT_NOT_FOUND; // Can't unauthorize the pseudo-accounts because they are implicitly @@ -128,32 +122,6 @@ MPTokenAuthorize::preclaim(PreclaimContext const& ctx) return tesSUCCESS; } -TER -MPTokenAuthorize::createMPToken( - ApplyView& view, - MPTID const& mptIssuanceID, - AccountID const& account, - std::uint32_t const flags) -{ - auto const mptokenKey = keylet::mptoken(mptIssuanceID, account); - - auto const ownerNode = - view.dirInsert(keylet::ownerDir(account), mptokenKey, describeOwnerDir(account)); - - if (!ownerNode) - return tecDIR_FULL; // LCOV_EXCL_LINE - - auto mptoken = std::make_shared(mptokenKey); - (*mptoken)[sfAccount] = account; - (*mptoken)[sfMPTokenIssuanceID] = mptIssuanceID; - (*mptoken)[sfFlags] = flags; - (*mptoken)[sfOwnerNode] = *ownerNode; - - view.insert(mptoken); - - return tesSUCCESS; -} - TER MPTokenAuthorize::doApply() { diff --git a/src/libxrpl/tx/transactors/token/MPTokenIssuanceSet.cpp b/src/libxrpl/tx/transactors/token/MPTokenIssuanceSet.cpp index 8e1532b10bd..e0d1e342150 100644 --- a/src/libxrpl/tx/transactors/token/MPTokenIssuanceSet.cpp +++ b/src/libxrpl/tx/transactors/token/MPTokenIssuanceSet.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -179,7 +180,7 @@ MPTokenIssuanceSet::preclaim(PreclaimContext const& ctx) return tecNO_DST; // the mptoken must exist - if (!ctx.view.exists(keylet::mptoken(ctx.tx[sfMPTokenIssuanceID], *holderID))) + if (!mptIssuance.hasHolder(*holderID)) return tecOBJECT_NOT_FOUND; } @@ -235,28 +236,14 @@ MPTokenIssuanceSet::preclaim(PreclaimContext const& ctx) return tesSUCCESS; } -TER -MPTokenIssuanceSet::doApply() +static TER +updateMPTokenIssuance(WritableMPTokenIssuance& mptIssuance, STTx const& tx) { - auto const mptIssuanceID = ctx_.tx[sfMPTokenIssuanceID]; - auto const txFlags = ctx_.tx.getFlags(); - auto const holderID = ctx_.tx[~sfHolder]; - auto const domainID = ctx_.tx[~sfDomainID]; - std::shared_ptr sle; - - if (holderID) - { - sle = view().peek(keylet::mptoken(mptIssuanceID, *holderID)); - } - else - { - sle = view().peek(keylet::mptIssuance(mptIssuanceID)); - } - - if (!sle) + if (!mptIssuance) return tecINTERNAL; // LCOV_EXCL_LINE - std::uint32_t const flagsIn = sle->getFieldU32(sfFlags); + auto const txFlags = tx.getFlags(); + std::uint32_t const flagsIn = mptIssuance->getFieldU32(sfFlags); std::uint32_t flagsOut = flagsIn; if (txFlags & tfMPTLock) @@ -268,7 +255,7 @@ MPTokenIssuanceSet::doApply() flagsOut &= ~lsfMPTLocked; } - if (auto const mutableFlags = ctx_.tx[~sfMutableFlags].value_or(0)) + if (auto const mutableFlags = tx[~sfMutableFlags].value_or(0)) { for (auto const& f : mptMutabilityFlags) { @@ -286,14 +273,14 @@ MPTokenIssuanceSet::doApply() { // If the lsfMPTCanTransfer flag is being cleared, then also clear // the TransferFee field. - sle->makeFieldAbsent(sfTransferFee); + mptIssuance->makeFieldAbsent(sfTransferFee); } } if (flagsIn != flagsOut) - sle->setFieldU32(sfFlags, flagsOut); + mptIssuance->setFieldU32(sfFlags, flagsOut); - if (auto const transferFee = ctx_.tx[~sfTransferFee]) + if (auto const transferFee = tx[~sfTransferFee]) { // TransferFee uses soeDEFAULT style: // - If the field is absent, it is interpreted as 0. @@ -301,47 +288,86 @@ MPTokenIssuanceSet::doApply() // Therefore, when TransferFee is 0, the field should be removed. if (transferFee == 0) { - sle->makeFieldAbsent(sfTransferFee); + mptIssuance->makeFieldAbsent(sfTransferFee); } else { - sle->setFieldU16(sfTransferFee, *transferFee); + mptIssuance->setFieldU16(sfTransferFee, *transferFee); } } - if (auto const metadata = ctx_.tx[~sfMPTokenMetadata]) + if (auto const metadata = tx[~sfMPTokenMetadata]) { if (metadata->empty()) { - sle->makeFieldAbsent(sfMPTokenMetadata); + mptIssuance->makeFieldAbsent(sfMPTokenMetadata); } else { - sle->setFieldVL(sfMPTokenMetadata, *metadata); + mptIssuance->setFieldVL(sfMPTokenMetadata, *metadata); } } - if (domainID) + if (auto const domainID = tx[~sfDomainID]) { // This is enforced in preflight. XRPL_ASSERT( - sle->getType() == ltMPTOKEN_ISSUANCE, + mptIssuance->getType() == ltMPTOKEN_ISSUANCE, "MPTokenIssuanceSet::doApply : modifying MPTokenIssuance"); if (*domainID != beast::zero) { - sle->setFieldH256(sfDomainID, *domainID); + mptIssuance->setFieldH256(sfDomainID, *domainID); } else { - if (sle->isFieldPresent(sfDomainID)) - sle->makeFieldAbsent(sfDomainID); + if (mptIssuance->isFieldPresent(sfDomainID)) + mptIssuance->makeFieldAbsent(sfDomainID); } } - view().update(sle); + mptIssuance.update(); + + return tesSUCCESS; +} + +static TER +updateMPToken(WritableMPToken& mpt, STTx const& tx) +{ + if (!mpt) + return tecINTERNAL; // LCOV_EXCL_LINE + + auto const txFlags = tx.getFlags(); + std::uint32_t const flagsIn = mpt->getFieldU32(sfFlags); + + if (txFlags & tfMPTLock) + { + mpt->setFlag(flagsIn | lsfMPTLocked); + } + else if (txFlags & tfMPTUnlock) + { + mpt->clearFlag(lsfMPTLocked); + } + + mpt.update(); return tesSUCCESS; } +TER +MPTokenIssuanceSet::doApply() +{ + auto const mptIssuanceID = ctx_.tx[sfMPTokenIssuanceID]; + auto const holderID = ctx_.tx[~sfHolder]; + WritableMPTokenIssuance mptIssuance(view(), mptIssuanceID); + + if (holderID) + { + WritableMPToken mpt(mptIssuance, *holderID); + return updateMPToken(mpt, ctx_.tx); + } + + return updateMPTokenIssuance(mptIssuance, ctx_.tx); +} + } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/token/TrustSet.cpp b/src/libxrpl/tx/transactors/token/TrustSet.cpp index 17ddbd8e330..2447ed54697 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 @@ -580,7 +581,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) @@ -631,7 +633,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/libxrpl/tx/transactors/vault/VaultDelete.cpp b/src/libxrpl/tx/transactors/vault/VaultDelete.cpp index efa27d75dd4..e328af85b63 100644 --- a/src/libxrpl/tx/transactors/vault/VaultDelete.cpp +++ b/src/libxrpl/tx/transactors/vault/VaultDelete.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -115,11 +116,9 @@ 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 (auto const mptoken = MPToken(shareIssuance, vault->at(sfOwner))) { - if (auto const ter = makeWritableTokenBase(view(), MPTIssue(shareMPTID)) - ->removeEmptyHolding(accountID_, j_); - !isTesSuccess(ter)) + if (auto const ter = shareIssuance.removeEmptyHolding(accountID_, j_); !isTesSuccess(ter)) { // LCOV_EXCL_START JLOG(j_.error()) // diff --git a/src/libxrpl/tx/transactors/vault/VaultDeposit.cpp b/src/libxrpl/tx/transactors/vault/VaultDeposit.cpp index 9a7ad2373ed..80bef82176b 100644 --- a/src/libxrpl/tx/transactors/vault/VaultDeposit.cpp +++ b/src/libxrpl/tx/transactors/vault/VaultDeposit.cpp @@ -62,8 +62,8 @@ VaultDeposit::preclaim(PreclaimContext const& ctx) // LCOV_EXCL_STOP } - auto const mptIssuance = MPTokenIssuance(ctx.view, vaultShare); - if (!mptIssuance) + auto const shareIssuance = MPTokenIssuance(ctx.view, vaultShare); + if (!shareIssuance) { // LCOV_EXCL_START JLOG(ctx.j.error()) << "VaultDeposit: missing issuance of vault shares."; @@ -71,7 +71,7 @@ VaultDeposit::preclaim(PreclaimContext const& ctx) // LCOV_EXCL_STOP } - if (mptIssuance->isFlag(lsfMPTLocked)) + if (shareIssuance->isFlag(lsfMPTLocked)) { // LCOV_EXCL_START JLOG(ctx.j.error()) << "VaultDeposit: issuance of vault shares is locked."; @@ -80,16 +80,19 @@ VaultDeposit::preclaim(PreclaimContext const& ctx) } // Cannot deposit inside Vault an Asset frozen for the depositor - if (token->isFrozen(account)) - return vaultAsset.holds() ? tecFROZEN : tecLOCKED; + if (auto const ret = token->checkFrozen(account)) + return ret; + // Cannot deposit if the vault account's asset holding is frozen/locked + if (token->isFrozen(vaultAccount)) + return tecLOCKED; // Cannot deposit if the shares of the vault are frozen - if (MPTokenIssuance(ctx.view, vaultShare).isFrozen(account)) + if (shareIssuance.isFrozen(account)) return tecLOCKED; if (vault->isFlag(lsfVaultPrivate) && account != vault->at(sfOwner)) { - auto const maybeDomainID = mptIssuance->at(~sfDomainID); + auto const maybeDomainID = shareIssuance->at(~sfDomainID); // Since this is a private vault and the account is not its owner, we // perform authorization check based on DomainID read from mptIssuance. // Had the vault shares been a regular MPToken, we would allow @@ -159,7 +162,7 @@ VaultDeposit::doApply() else // !vault->isFlag(lsfVaultPrivate) || accountID_ == vault->at(sfOwner) { // No authorization needed, but must ensure there is MPToken - if (!view().exists(keylet::mptoken(mptoken.getMptID(), accountID_))) + if (!mptoken.hasHolder(accountID_)) { if (auto const err = mptoken.authorizeMPToken(preFeeBalance_, accountID_, ctx_.journal); !isTesSuccess(err)) diff --git a/src/libxrpl/tx/transactors/vault/VaultWithdraw.cpp b/src/libxrpl/tx/transactors/vault/VaultWithdraw.cpp index 9c5cb9fb615..4063f421fb4 100644 --- a/src/libxrpl/tx/transactors/vault/VaultWithdraw.cpp +++ b/src/libxrpl/tx/transactors/vault/VaultWithdraw.cpp @@ -82,6 +82,10 @@ VaultWithdraw::preclaim(PreclaimContext const& ctx) if (auto const ret = vaultAssetToken->checkFrozen(dstAcct)) return ret; + // Cannot withdraw if the vault account's asset holding is frozen/locked + if (vaultAssetToken->isFrozen(vaultAccount)) + return tecLOCKED; + // Cannot return shares to the vault, if the underlying asset was frozen for // the submitter if (auto const ret = MPTokenIssuance(ctx.view, vaultShare).checkFrozen(account)) diff --git a/src/test/app/Invariants_test.cpp b/src/test/app/Invariants_test.cpp index f4de0000048..4f9015a0312 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(),