diff --git a/include/xrpl/ledger/helpers/MPToken.h b/include/xrpl/ledger/helpers/MPToken.h index 01ece59480..21a5b02385 100644 --- a/include/xrpl/ledger/helpers/MPToken.h +++ b/include/xrpl/ledger/helpers/MPToken.h @@ -28,6 +28,32 @@ public: return issuance_; } + MPTIssue + getMptIssue() const + { + return issuance_.getMptIssue(); + } + + [[nodiscard]] TER + checkSpendable( + STAmount const& amount, + FreezeHandling zeroIfFrozen, + AuthHandling zeroIfUnauthorized, + beast::Journal j) const override + { + STAmount const spendableAmount = accountHolds(zeroIfFrozen, zeroIfUnauthorized, j); + + // If the balance is less than or equal to 0, return tecINSUFFICIENT_FUNDS + if (spendableAmount <= beast::zero) + return tecINSUFFICIENT_FUNDS; + + // If the spendable amount is less than the amount, return tecINSUFFICIENT_FUNDS + if (!canSubtract(spendableAmount, amount)) + return tecINSUFFICIENT_FUNDS; + + return tesSUCCESS; + } + protected: MPTokenIssuance const& issuance_; }; diff --git a/include/xrpl/ledger/helpers/MPTokenHelpers.h b/include/xrpl/ledger/helpers/MPTokenHelpers.h index 538ff618ac..cd95fd5447 100644 --- a/include/xrpl/ledger/helpers/MPTokenHelpers.h +++ b/include/xrpl/ledger/helpers/MPTokenHelpers.h @@ -46,7 +46,7 @@ public: } AccountID const& - getIssuer() const + getIssuer() const override { return mptIssue_.getIssuer(); } @@ -127,6 +127,12 @@ public: [[nodiscard]] bool canClawback() const override; + [[nodiscard]] bool + canEscrow() const override + { + return sle_->isFlag(lsfMPTCanEscrow); + } + [[nodiscard]] bool requiresAuth() const override; @@ -135,6 +141,8 @@ public: { if (!exists()) return tecOBJECT_NOT_FOUND; + if (sle_->getAccountID(sfIssuer) != mptIssue_.getIssuer()) + return tecINTERNAL; return tesSUCCESS; } @@ -144,6 +152,17 @@ public: return readView_.exists(keylet::mptoken(mptID_, holder)); } + [[nodiscard]] TER + checkHolder(AccountID const& holder) const override + { + if (!hasHolder(holder)) + return tecOBJECT_NOT_FOUND; + return tesSUCCESS; + } + + [[nodiscard]] std::unique_ptr + getHolder(AccountID const& holder) const override; + STAmount accountHolds( AccountID const& account, diff --git a/include/xrpl/ledger/helpers/RippleState.h b/include/xrpl/ledger/helpers/RippleState.h index 14e8ff49f9..272a7a0601 100644 --- a/include/xrpl/ledger/helpers/RippleState.h +++ b/include/xrpl/ledger/helpers/RippleState.h @@ -38,6 +38,42 @@ public: return iouToken_; } + [[nodiscard]] TER + checkSpendable( + STAmount const& amount, + FreezeHandling zeroIfFrozen, + AuthHandling zeroIfUnauthorized, + beast::Journal j) const override + { + STAmount const balance = (*sle_)[sfBalance]; + AccountID const& issuer = iouToken_.getIssuer(); + + // If balance is positive, issuer must have higher address than account + if (balance > beast::zero && issuer < holder_) + return tecNO_PERMISSION; // LCOV_EXCL_LINE + + // If balance is negative, issuer must have lower address than account + if (balance < beast::zero && issuer > holder_) + return tecNO_PERMISSION; // LCOV_EXCL_LINE + + STAmount const spendableAmount = accountHolds(zeroIfFrozen, zeroIfUnauthorized, j); + + // If the balance is less than or equal to 0, return tecINSUFFICIENT_FUNDS + if (spendableAmount <= beast::zero) + return tecINSUFFICIENT_FUNDS; + + // If the spendable amount is less than the amount, return + // tecINSUFFICIENT_FUNDS + if (spendableAmount < amount) + return tecINSUFFICIENT_FUNDS; + + // If the amount is not addable to the balance, return tecPRECISION_LOSS + if (!canAdd(spendableAmount, amount)) + return tecPRECISION_LOSS; + + return tesSUCCESS; + } + protected: IOUToken const& iouToken_; }; diff --git a/include/xrpl/ledger/helpers/RippleStateHelpers.h b/include/xrpl/ledger/helpers/RippleStateHelpers.h index e3072bade0..4dc10c7892 100644 --- a/include/xrpl/ledger/helpers/RippleStateHelpers.h +++ b/include/xrpl/ledger/helpers/RippleStateHelpers.h @@ -38,7 +38,7 @@ public: } [[nodiscard]] AccountID const& - getIssuer() const + getIssuer() const override { return issuer_; } @@ -129,6 +129,12 @@ public: [[nodiscard]] bool canClawback() const override; + [[nodiscard]] bool + canEscrow() const override + { + return issuerAccount_->isFlag(lsfAllowTrustLineLocking); + } + [[nodiscard]] bool requiresAuth() const override; @@ -146,6 +152,17 @@ public: return readView_.exists(keylet::line(issuer_, holder, currency_)); } + [[nodiscard]] TER + checkHolder(AccountID const& holder) const override + { + if (!hasHolder(holder)) + return tecNO_LINE; + return tesSUCCESS; + } + + [[nodiscard]] std::unique_ptr + getHolder(AccountID const& holder) const override; + protected: Issue const issue_; AccountID const issuer_; diff --git a/include/xrpl/ledger/helpers/TokenHelpers.h b/include/xrpl/ledger/helpers/TokenHelpers.h index c286214255..2551b8c49a 100644 --- a/include/xrpl/ledger/helpers/TokenHelpers.h +++ b/include/xrpl/ledger/helpers/TokenHelpers.h @@ -44,15 +44,14 @@ enum class WaiveTransferFee : bool { No = false, Yes }; */ enum class AuthType { StrongAuth, WeakAuth, Legacy }; -//------------------------------------------------------------------------------ -// -// Freeze checking (Asset-based dispatchers) -// -//------------------------------------------------------------------------------ +class TokenHolderBase; class TokenBase : public virtual ReadOnlySLE { public: + [[nodiscard]] virtual AccountID const& + getIssuer() const = 0; + [[nodiscard]] virtual bool isGlobalFrozen() const = 0; @@ -153,6 +152,9 @@ public: [[nodiscard]] virtual bool canClawback() const = 0; + [[nodiscard]] virtual bool + canEscrow() const = 0; + /** Check if the token requires authorization for holders. * For IOUs, checks lsfRequireAuth on issuer's AccountRoot. * For MPTs, checks lsfMPTRequireAuth on the issuance. @@ -166,6 +168,12 @@ public: [[nodiscard]] virtual bool hasHolder(AccountID const& holder) const = 0; + [[nodiscard]] virtual TER + checkHolder(AccountID const& holder) const = 0; + + [[nodiscard]] virtual std::unique_ptr + getHolder(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 index b2dc059714..a0d411be80 100644 --- a/include/xrpl/ledger/helpers/TokenHolderBase.h +++ b/include/xrpl/ledger/helpers/TokenHolderBase.h @@ -88,6 +88,13 @@ public: holder_, zeroIfFrozen, zeroIfUnauthorized, j, includeFullBalance); } + [[nodiscard]] virtual TER + checkSpendable( + STAmount const& amount, + FreezeHandling zeroIfFrozen, + AuthHandling zeroIfUnauthorized, + beast::Journal j) const = 0; + [[nodiscard]] TER requireAuth(AuthType authType = AuthType::Legacy, int depth = 0) const { diff --git a/include/xrpl/protocol/STAmount.h b/include/xrpl/protocol/STAmount.h index dadeec096f..9258128265 100644 --- a/include/xrpl/protocol/STAmount.h +++ b/include/xrpl/protocol/STAmount.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -739,6 +740,9 @@ canAdd(STAmount const& amt1, STAmount const& amt2); bool canSubtract(STAmount const& amt1, STAmount const& amt2); +NotTEC +checkAmount(STAmount const& amount); + } // namespace xrpl //------------------------------------------------------------------------------ diff --git a/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp b/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp index c34d7da4ab..200e34f0c9 100644 --- a/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp +++ b/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp @@ -504,15 +504,14 @@ rippleLockEscrowMPT( // 1. Decrease the MPT Holder MPTAmount // 2. Increase the MPT Holder EscrowedAmount { - auto const mptokenID = keylet::mptoken(mptIssuance.getMptID(), sender); - auto sle = view.peek(mptokenID); - if (!sle) + auto mptoken = WritableMPToken(mptIssuance, sender); + if (!mptoken.exists()) { // LCOV_EXCL_START JLOG(j.error()) << "rippleLockEscrowMPT: MPToken not found for " << sender; return tecOBJECT_NOT_FOUND; } // LCOV_EXCL_STOP - auto const amt = sle->getFieldU64(sfMPTAmount); + auto const amt = mptoken->getFieldU64(sfMPTAmount); auto const pay = amount.mpt().value(); // Underflow check for subtraction @@ -523,10 +522,10 @@ rippleLockEscrowMPT( return tecINTERNAL; } // LCOV_EXCL_STOP - (*sle)[sfMPTAmount] = amt - pay; + (*mptoken)[sfMPTAmount] = amt - pay; // Overflow check for addition - uint64_t const locked = (*sle)[~sfLockedAmount].value_or(0); + uint64_t const locked = (*mptoken)[~sfLockedAmount].value_or(0); if (!canAdd(STAmount(mptIssue, locked), STAmount(mptIssue, pay))) { // LCOV_EXCL_START @@ -535,16 +534,16 @@ rippleLockEscrowMPT( return tecINTERNAL; } // LCOV_EXCL_STOP - if (sle->isFieldPresent(sfLockedAmount)) + if (mptoken->isFieldPresent(sfLockedAmount)) { - (*sle)[sfLockedAmount] += pay; + (*mptoken)[sfLockedAmount] += pay; } else { - sle->setFieldU64(sfLockedAmount, pay); + mptoken->setFieldU64(sfLockedAmount, pay); } - view.update(sle); + mptoken.update(); } // 1. Increase the Issuance EscrowedAmount @@ -802,4 +801,10 @@ MPTokenIssuance::accountHolds( return amount; } +std::unique_ptr +MPTokenIssuance::getHolder(AccountID const& holder) const +{ + return std::make_unique(*this, holder); +} + } // namespace xrpl diff --git a/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp b/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp index 3201864587..81fd50bb09 100644 --- a/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp +++ b/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp @@ -924,4 +924,10 @@ deleteAMMTrustLine( return tesSUCCESS; } +std::unique_ptr +IOUToken::getHolder(AccountID const& holder) const +{ + return std::make_unique(*this, holder); +} + } // namespace xrpl diff --git a/src/libxrpl/ledger/helpers/TokenHelpers.cpp b/src/libxrpl/ledger/helpers/TokenHelpers.cpp index 255e2d1937..80ebe242ac 100644 --- a/src/libxrpl/ledger/helpers/TokenHelpers.cpp +++ b/src/libxrpl/ledger/helpers/TokenHelpers.cpp @@ -305,6 +305,7 @@ rippleCreditIOU( { AccountID const& issuer = saAmount.getIssuer(); Currency const& currency = saAmount.getCurrency(); + WritableIOUToken token(view, issuer, currency); // Make sure issuer is involved. XRPL_ASSERT( @@ -316,7 +317,6 @@ rippleCreditIOU( XRPL_ASSERT(uSenderID != uReceiverID, "xrpl::rippleCreditIOU : sender is not receiver"); bool const bSenderHigh = uSenderID > uReceiverID; - auto const index = keylet::line(uSenderID, uReceiverID, currency); XRPL_ASSERT( !isXRP(uSenderID) && uSenderID != noAccount(), "xrpl::rippleCreditIOU : sender is not XRP"); @@ -325,9 +325,10 @@ rippleCreditIOU( "xrpl::rippleCreditIOU : receiver is not XRP"); // If the line exists, modify it accordingly. - if (auto const sleRippleState = view.peek(index)) + auto rippleState = WritableRippleState(view, token, uSenderID); + if (rippleState.exists()) { - STAmount saBalance = sleRippleState->getFieldAmount(sfBalance); + STAmount saBalance = rippleState->getFieldAmount(sfBalance); if (bSenderHigh) saBalance.negate(); // Put balance in sender terms. @@ -343,7 +344,7 @@ rippleCreditIOU( << " amount=" << saAmount.getFullText() << " after=" << saBalance.getFullText(); - std::uint32_t const uFlags(sleRippleState->getFieldU32(sfFlags)); + std::uint32_t const uFlags(rippleState->getFieldU32(sfFlags)); bool bDelete = false; // FIXME This NEEDS to be cleaned up and simplified. It's impossible @@ -357,11 +358,11 @@ rippleCreditIOU( && static_cast(uFlags & (!bSenderHigh ? lsfLowNoRipple : lsfHighNoRipple)) != static_cast(AccountRoot(uSenderID, view)->getFlags() & lsfDefaultRipple) && !(uFlags & (!bSenderHigh ? lsfLowFreeze : lsfHighFreeze)) && - !sleRippleState->getFieldAmount(!bSenderHigh ? sfLowLimit : sfHighLimit) + !rippleState->getFieldAmount(!bSenderHigh ? sfLowLimit : sfHighLimit) // Sender trust limit is 0. - && !sleRippleState->getFieldU32(!bSenderHigh ? sfLowQualityIn : sfHighQualityIn) + && !rippleState->getFieldU32(!bSenderHigh ? sfLowQualityIn : sfHighQualityIn) // Sender quality in is 0. - && !sleRippleState->getFieldU32(!bSenderHigh ? sfLowQualityOut : sfHighQualityOut)) + && !rippleState->getFieldU32(!bSenderHigh ? sfLowQualityOut : sfHighQualityOut)) // Sender quality out is 0. { // Clear the reserve of the sender, possibly delete the line! @@ -369,7 +370,7 @@ rippleCreditIOU( wrappedSender.adjustOwnerCount(-1, j); // Clear reserve flag. - sleRippleState->setFieldU32( + rippleState->setFieldU32( sfFlags, uFlags & (!bSenderHigh ? ~lsfLowReserve : ~lsfHighReserve)); // Balance is zero, receiver reserve is clear. @@ -382,20 +383,20 @@ rippleCreditIOU( saBalance.negate(); // Want to reflect balance to zero even if we are deleting line. - sleRippleState->setFieldAmount(sfBalance, saBalance); + rippleState->setFieldAmount(sfBalance, saBalance); // ONLY: Adjust ripple balance. if (bDelete) { return WritableRippleState::trustDelete( view, - sleRippleState, + rippleState.mutableSle(), bSenderHigh ? uReceiverID : uSenderID, !bSenderHigh ? uReceiverID : uSenderID, j); } - view.update(sleRippleState); + rippleState.update(); return tesSUCCESS; } @@ -415,6 +416,8 @@ rippleCreditIOU( bool const noRipple = (wrappedAccount->getFlags() & lsfDefaultRipple) == 0; + auto const index = keylet::line(uSenderID, uReceiverID, currency); + return WritableRippleState::trustCreate( view, bSenderHigh, @@ -814,7 +817,7 @@ rippleCreditMPT( beast::Journal j) { // Do not check MPT authorization here - it must have been checked earlier - WritableMPTokenIssuance mptIssuance(view, saAmount.get().getMptID()); + WritableMPTokenIssuance mptIssuance(view, saAmount.get()); auto const& issuer = saAmount.getIssuer(); if (!mptIssuance.exists()) return tecOBJECT_NOT_FOUND; @@ -830,8 +833,15 @@ rippleCreditMPT( { auto const amt = mpt->getFieldU64(sfMPTAmount); auto const pay = saAmount.mpt().value(); - if (amt < pay) + if (view.rules().enabled(fixTypeSafetyRefactor)) + { + if (!canSubtract(STAmount(mpt.getMptIssue(), amt), saAmount)) + return tecINSUFFICIENT_FUNDS; + } + else if (amt < pay) + { return tecINSUFFICIENT_FUNDS; + } (*mpt)[sfMPTAmount] = amt - pay; mpt.update(); } diff --git a/src/libxrpl/protocol/STAmount.cpp b/src/libxrpl/protocol/STAmount.cpp index 92ce129825..cf21022e9f 100644 --- a/src/libxrpl/protocol/STAmount.cpp +++ b/src/libxrpl/protocol/STAmount.cpp @@ -1717,4 +1717,28 @@ divRoundStrict(STAmount const& num, STAmount const& den, Asset const& asset, boo return divRoundImpl(num, den, asset, roundUp); } +NotTEC +checkAmount(STAmount const& amount) +{ + if (amount.native()) + { + if (amount <= beast::zero) + return temBAD_AMOUNT; + } + else if (amount.holds()) + { + if (amount.native() || amount <= beast::zero) + return temBAD_AMOUNT; + + if (badCurrency() == amount.getCurrency()) + return temBAD_CURRENCY; + } + else if (amount.holds()) + { + if (amount.native() || amount.mpt() > MPTAmount{maxMPTokenAmount} || amount <= beast::zero) + return temBAD_AMOUNT; + } + return tesSUCCESS; +} + } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp b/src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp index c083c95438..f530d7091b 100644 --- a/src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp +++ b/src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp @@ -60,52 +60,17 @@ EscrowCreate::makeTxConsequences(PreflightContext const& ctx) return TxConsequences{ctx.tx, isXRP(amount) ? amount.xrp() : beast::zero}; } -template -static NotTEC -escrowCreatePreflightHelper(PreflightContext const& ctx); - -template <> -NotTEC -escrowCreatePreflightHelper(PreflightContext const& ctx) -{ - STAmount const amount = ctx.tx[sfAmount]; - if (amount.native() || amount <= beast::zero) - return temBAD_AMOUNT; - - if (badCurrency() == amount.getCurrency()) - return temBAD_CURRENCY; - - return tesSUCCESS; -} - -template <> -NotTEC -escrowCreatePreflightHelper(PreflightContext const& ctx) -{ - if (!ctx.rules.enabled(featureMPTokensV1)) - return temDISABLED; - - auto const amount = ctx.tx[sfAmount]; - if (amount.native() || amount.mpt() > MPTAmount{maxMPTokenAmount} || amount <= beast::zero) - return temBAD_AMOUNT; - - return tesSUCCESS; -} - NotTEC EscrowCreate::preflight(PreflightContext const& ctx) { STAmount const amount{ctx.tx[sfAmount]}; if (!isXRP(amount)) { - if (!ctx.rules.enabled(featureTokenEscrow)) - return temBAD_AMOUNT; + if (amount.holds() && !ctx.rules.enabled(featureMPTokensV1)) + return temDISABLED; - if (auto const ret = std::visit( - [&](T const&) { return escrowCreatePreflightHelper(ctx); }, - amount.asset().value()); - !isTesSuccess(ret)) - return ret; + if (auto const ter = checkAmount(amount); !isTesSuccess(ter)) + return ter; } else { @@ -172,13 +137,13 @@ escrowCreatePreclaimHelper( // If the lsfAllowTrustLineLocking is not enabled, return tecNO_PERMISSION if (!token.exists()) return tecNO_ISSUER; - if (!token->isFlag(lsfAllowTrustLineLocking)) + if (!token.canEscrow()) return tecNO_PERMISSION; // If the account does not have a trustline to the issuer, return tecNO_LINE RippleState const accountHolder(token, account); RippleState const destHolder(token, dest); - if (!accountHolder) + if (!accountHolder.exists()) return tecNO_LINE; STAmount const balance = (*accountHolder)[sfBalance]; @@ -245,7 +210,7 @@ escrowCreatePreclaimHelper( return tecOBJECT_NOT_FOUND; // If the lsfMPTCanEscrow is not enabled, return tecNO_PERMISSION - if (!mptIssuance->isFlag(lsfMPTCanEscrow)) + if (!mptIssuance.canEscrow()) return tecNO_PERMISSION; // If the issuer is not the same as the issuer of the mpt, return @@ -319,13 +284,65 @@ EscrowCreate::preclaim(PreclaimContext const& ctx) if (!ctx.view.rules().enabled(featureTokenEscrow)) return temDISABLED; // LCOV_EXCL_LINE - if (auto const ret = std::visit( + if (ctx.view.rules().enabled(fixTypeSafetyRefactor)) + { + auto const token = makeTokenBase(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 mpt does not exist, return tecOBJECT_NOT_FOUND + if (auto const ter = token->checkExists(); !isTesSuccess(ter)) + return ter; + + // If the lsfMPTCanEscrow is not enabled, return tecNO_PERMISSION + if (!token->canEscrow()) + return tecNO_PERMISSION; + + if (auto const ter = token->checkHolder(account); !isTesSuccess(ter)) + return ter; + + auto const accountHolder = token->getHolder(account); + auto const destHolder = token->getHolder(dest); + + // If the issuer has requireAuth set, check if the account is + // authorized + 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 = destHolder->requireAuth(AuthType::WeakAuth); !isTesSuccess(ter)) + return ter; + + // If the issuer has frozen the account, return tecLOCKED + if (accountHolder->isFrozen()) + return tecLOCKED; + + // If the issuer has frozen the destination, return tecLOCKED + if (destHolder->isFrozen()) + return tecLOCKED; + + // If the mpt cannot be transferred, return tecNO_AUTH + if (auto const ter = accountHolder->canTransfer(dest); !isTesSuccess(ter)) + return ter; + + if (auto const ter = + accountHolder->checkSpendable(amount, fhIGNORE_FREEZE, ahIGNORE_AUTH, ctx.j); + !isTesSuccess(ter)) + return ter; + } + else if ( + auto const ret = std::visit( [&](T const&) { return escrowCreatePreclaimHelper(ctx, account, dest, amount); }, amount.asset().value()); !isTesSuccess(ret)) + { return ret; + } } return tesSUCCESS; } @@ -348,10 +365,6 @@ escrowLockApplyHelper( STAmount const& amount, beast::Journal journal) { - // Defensive: Issuer cannot create an escrow - if (issuer == sender) - return tecINTERNAL; // LCOV_EXCL_LINE - auto const ter = rippleCredit( view, sender, issuer, amount, amount.holds() ? false : true, journal); if (!isTesSuccess(ter)) @@ -368,10 +381,6 @@ escrowLockApplyHelper( STAmount const& amount, beast::Journal journal) { - // Defensive: Issuer cannot create an escrow - if (issuer == sender) - return tecINTERNAL; // LCOV_EXCL_LINE - auto const ter = rippleLockEscrowMPT(view, sender, amount, journal); if (!isTesSuccess(ter)) return ter; // LCOV_EXCL_LINE @@ -389,16 +398,15 @@ EscrowCreate::doApply() if (ctx_.tx[~sfFinishAfter] && after(closeTime, ctx_.tx[sfFinishAfter])) return tecNO_PERMISSION; - WritableAccountRoot wrappedAcct(accountID_, ctx_.view()); - if (!wrappedAcct) + if (!account_) return tefINTERNAL; // LCOV_EXCL_LINE // Check reserve and funds availability STAmount const amount{ctx_.tx[sfAmount]}; - auto const reserve = ctx_.view().fees().accountReserve((*wrappedAcct)[sfOwnerCount] + 1); + auto const reserve = ctx_.view().fees().accountReserve((*account_)[sfOwnerCount] + 1); - auto const balance = wrappedAcct->getFieldAmount(sfBalance).xrp(); + auto const balance = account_->getFieldAmount(sfBalance).xrp(); if (balance < reserve) return tecINSUFFICIENT_RESERVE; @@ -469,7 +477,7 @@ EscrowCreate::doApply() // track the total locked balance. For MPT, this isn't necessary because the // locked balance is already stored directly in the MPTokenIssuance object. AccountID const issuer = amount.getIssuer(); - if (!isXRP(amount) && issuer != accountID_ && issuer != dest && !amount.holds()) + if (amount.holds() && issuer != accountID_ && issuer != dest) { auto page = ctx_.view().dirInsert(keylet::ownerDir(issuer), escrowKeylet, describeOwnerDir(issuer)); @@ -481,11 +489,52 @@ EscrowCreate::doApply() // Deduct owner's balance if (isXRP(amount)) { - (*wrappedAcct)[sfBalance] = (*wrappedAcct)[sfBalance] - amount; + (*account_)[sfBalance] = (*account_)[sfBalance] - amount; } else { - if (auto const ret = std::visit( + // Defensive: Issuer cannot create an escrow + if (issuer == accountID_) + return tecINTERNAL; // LCOV_EXCL_LINE + if (ctx_.view().rules().enabled(fixTypeSafetyRefactor)) + { + auto const ter = rippleCredit( + ctx_.view(), + accountID_, + issuer, + amount, + amount.holds() ? false : true, + j_); + if (!isTesSuccess(ter)) + return ter; // LCOV_EXCL_LINE + if (amount.holds()) + { + auto const mptIssue = amount.get(); + WritableMPTokenIssuance mptIssuance(ctx_.view(), mptIssue); + WritableMPToken mptoken(mptIssuance, accountID_); + auto const pay = amount.mpt().value(); + // Overflow check for addition + uint64_t const locked = (*mptoken)[~sfLockedAmount].value_or(0); + + if (!canAdd(STAmount(mptIssue, locked), STAmount(mptIssue, pay))) + { // LCOV_EXCL_START + JLOG(j_.error()) << "doApply: overflow on locked amount for " + << to_string(accountID_) << ": " << locked << " + " << pay; + return tecINTERNAL; + } // LCOV_EXCL_STOP + + if (mptoken->isFieldPresent(sfLockedAmount)) + { + (*mptoken)[sfLockedAmount] += pay; + } + else + { + mptoken->setFieldU64(sfLockedAmount, pay); + } + } + } + else if ( + auto const ret = std::visit( [&](T const&) { return escrowLockApplyHelper(ctx_.view(), issuer, accountID_, amount, j_); }, @@ -497,8 +546,8 @@ EscrowCreate::doApply() } // increment owner count - wrappedAcct.adjustOwnerCount(1, ctx_.journal); - wrappedAcct.update(); + account_.adjustOwnerCount(1, ctx_.journal); + account_.update(); return tesSUCCESS; }