diff --git a/include/xrpl/protocol/TxFlags.h b/include/xrpl/protocol/TxFlags.h index 8b5e430ecb7..5b26488ab12 100644 --- a/include/xrpl/protocol/TxFlags.h +++ b/include/xrpl/protocol/TxFlags.h @@ -106,6 +106,7 @@ constexpr std::uint32_t tfPartialPayment = 0x00020000; constexpr std::uint32_t tfLimitQuality = 0x00040000; constexpr std::uint32_t tfPaymentMask = ~(tfUniversal | tfPartialPayment | tfLimitQuality | tfNoRippleDirect); +constexpr std::uint32_t tfMPTPaymentMask = ~(tfUniversal | tfPartialPayment); // TrustSet flags: constexpr std::uint32_t tfSetfAuth = 0x00010000; diff --git a/src/xrpld/app/tx/detail/Payment.cpp b/src/xrpld/app/tx/detail/Payment.cpp index 73a136b8408..19952df3040 100644 --- a/src/xrpld/app/tx/detail/Payment.cpp +++ b/src/xrpld/app/tx/detail/Payment.cpp @@ -44,6 +44,24 @@ Payment::makeTxConsequences(PreflightContext const& ctx) return TxConsequences{ctx.tx, calculateMaxXRPSpend(ctx.tx)}; } +STAmount +getMaxSourceAmount( + AccountID const& account, + STAmount const& dstAmount, + std::optional const& sendMax) +{ + if (sendMax) + return *sendMax; + else if (dstAmount.native() || dstAmount.holds()) + return dstAmount; + else + return STAmount( + Issue{dstAmount.get().currency, account}, + dstAmount.mantissa(), + dstAmount.exponent(), + dstAmount < beast::zero); +} + NotTEC Payment::preflight(PreflightContext const& ctx) { @@ -53,124 +71,113 @@ Payment::preflight(PreflightContext const& ctx) auto& tx = ctx.tx; auto& j = ctx.j; - STAmount const saDstAmount(tx.getFieldAmount(sfAmount)); - bool const bMPTDirect = saDstAmount.holds(); + STAmount const dstAmount(tx.getFieldAmount(sfAmount)); + bool const mptDirect = dstAmount.holds(); - if (bMPTDirect && !ctx.rules.enabled(featureMPTokensV1)) + if (mptDirect && !ctx.rules.enabled(featureMPTokensV1)) return temDISABLED; - std::uint32_t const uTxFlags = tx.getFlags(); + std::uint32_t const txFlags = tx.getFlags(); - std::uint32_t paymentMask = - bMPTDirect ? ~(tfUniversal | tfPartialPayment) : tfPaymentMask; + std::uint32_t paymentMask = mptDirect ? tfMPTPaymentMask : tfPaymentMask; - if (uTxFlags & paymentMask) + if (txFlags & paymentMask) { JLOG(j.trace()) << "Malformed transaction: " << "Invalid flags set."; return temINVALID_FLAG; } - if (bMPTDirect && ctx.tx.isFieldPresent(sfPaths)) + if (mptDirect && ctx.tx.isFieldPresent(sfPaths)) return temMALFORMED; - bool const partialPaymentAllowed = uTxFlags & tfPartialPayment; - bool const limitQuality = uTxFlags & tfLimitQuality; - bool const defaultPathsAllowed = !(uTxFlags & tfNoRippleDirect); - bool const bPaths = tx.isFieldPresent(sfPaths); - bool const bMax = tx.isFieldPresent(sfSendMax); + bool const partialPaymentAllowed = txFlags & tfPartialPayment; + bool const limitQuality = txFlags & tfLimitQuality; + bool const defaultPathsAllowed = !(txFlags & tfNoRippleDirect); + bool const hasPaths = tx.isFieldPresent(sfPaths); + bool const hasMax = tx.isFieldPresent(sfSendMax); auto const deliverMin = tx[~sfDeliverMin]; - STAmount maxSourceAmount; auto const account = tx.getAccountID(sfAccount); + STAmount const maxSourceAmount = + getMaxSourceAmount(account, dstAmount, tx[~sfSendMax]); - if (bMax) - maxSourceAmount = tx.getFieldAmount(sfSendMax); - else if (saDstAmount.native() || bMPTDirect) - maxSourceAmount = saDstAmount; - else - maxSourceAmount = STAmount( - Issue{saDstAmount.get().currency, account}, - saDstAmount.mantissa(), - saDstAmount.exponent(), - saDstAmount < beast::zero); - - if ((bMPTDirect && saDstAmount.asset() != maxSourceAmount.asset()) || - (!bMPTDirect && maxSourceAmount.holds())) + if ((mptDirect && dstAmount.asset() != maxSourceAmount.asset()) || + (!mptDirect && maxSourceAmount.holds())) { JLOG(j.trace()) << "Malformed transaction: " - << "inconsistent issues: " << saDstAmount.getFullText() + << "inconsistent issues: " << dstAmount.getFullText() << " " << maxSourceAmount.getFullText() << " " << deliverMin.value_or(STAmount{}).getFullText(); return temMALFORMED; } - auto const& uSrcAsset = maxSourceAmount.asset(); - auto const& uDstAsset = saDstAmount.asset(); + auto const& srcAsset = maxSourceAmount.asset(); + auto const& dstAsset = dstAmount.asset(); - bool const bXRPDirect = uSrcAsset.native() && uDstAsset.native(); + bool const xrpDirect = srcAsset.native() && dstAsset.native(); - if (!isLegalNet(saDstAmount) || !isLegalNet(maxSourceAmount)) + if (!isLegalNet(dstAmount) || !isLegalNet(maxSourceAmount)) return temBAD_AMOUNT; - auto const uDstAccountID = tx.getAccountID(sfDestination); + auto const dstAccountID = tx.getAccountID(sfDestination); - if (!uDstAccountID) + if (!dstAccountID) { JLOG(j.trace()) << "Malformed transaction: " << "Payment destination account not specified."; return temDST_NEEDED; } - if (bMax && maxSourceAmount <= beast::zero) + if (hasMax && maxSourceAmount <= beast::zero) { JLOG(j.trace()) << "Malformed transaction: " << "bad max amount: " << maxSourceAmount.getFullText(); return temBAD_AMOUNT; } - if (saDstAmount <= beast::zero) + if (dstAmount <= beast::zero) { JLOG(j.trace()) << "Malformed transaction: " - << "bad dst amount: " << saDstAmount.getFullText(); + << "bad dst amount: " << dstAmount.getFullText(); return temBAD_AMOUNT; } - if (badCurrency() == uSrcAsset || badCurrency() == uDstAsset) + if (badCurrency() == srcAsset || badCurrency() == dstAsset) { JLOG(j.trace()) << "Malformed transaction: " << "Bad currency."; return temBAD_CURRENCY; } - if (account == uDstAccountID && uSrcAsset == uDstAsset && !bPaths) + if (account == dstAccountID && srcAsset == dstAsset && !hasPaths) { // You're signing yourself a payment. - // If bPaths is true, you might be trying some arbitrage. + // If hasPaths is true, you might be trying some arbitrage. JLOG(j.trace()) << "Malformed transaction: " << "Redundant payment from " << to_string(account) - << " to self without path for " << to_string(uDstAsset); + << " to self without path for " << to_string(dstAsset); return temREDUNDANT; } - if (bXRPDirect && bMax) + if (xrpDirect && hasMax) { // Consistent but redundant transaction. JLOG(j.trace()) << "Malformed transaction: " << "SendMax specified for XRP to XRP."; return temBAD_SEND_XRP_MAX; } - if ((bXRPDirect || bMPTDirect) && bPaths) + if ((xrpDirect || mptDirect) && hasPaths) { // XRP is sent without paths. JLOG(j.trace()) << "Malformed transaction: " << "Paths specified for XRP to XRP or MPT to MPT."; return temBAD_SEND_XRP_PATHS; } - if (bXRPDirect && partialPaymentAllowed) + if (xrpDirect && partialPaymentAllowed) { // Consistent but redundant transaction. JLOG(j.trace()) << "Malformed transaction: " << "Partial payment specified for XRP to XRP."; return temBAD_SEND_XRP_PARTIAL; } - if ((bXRPDirect || bMPTDirect) && limitQuality) + if ((xrpDirect || mptDirect) && limitQuality) { // Consistent but redundant transaction. JLOG(j.trace()) @@ -178,7 +185,7 @@ Payment::preflight(PreflightContext const& ctx) << "Limit quality specified for XRP to XRP or MPT to MPT."; return temBAD_SEND_XRP_LIMIT; } - if ((bXRPDirect || bMPTDirect) && !defaultPathsAllowed) + if ((xrpDirect || mptDirect) && !defaultPathsAllowed) { // Consistent but redundant transaction. JLOG(j.trace()) @@ -205,7 +212,7 @@ Payment::preflight(PreflightContext const& ctx) << " amount. " << dMin.getFullText(); return temBAD_AMOUNT; } - if (dMin.asset() != saDstAmount.asset()) + if (dMin.asset() != dstAmount.asset()) { JLOG(j.trace()) << "Malformed transaction: Dst issue differs " @@ -213,7 +220,7 @@ Payment::preflight(PreflightContext const& ctx) << jss::DeliverMin.c_str() << ". " << dMin.getFullText(); return temBAD_AMOUNT; } - if (dMin > saDstAmount) + if (dMin > dstAmount) { JLOG(j.trace()) << "Malformed transaction: Dst amount less than " @@ -228,22 +235,22 @@ Payment::preflight(PreflightContext const& ctx) TER Payment::preclaim(PreclaimContext const& ctx) { - // Ripple if source or destination is non-native or if there are paths. - std::uint32_t const uTxFlags = ctx.tx.getFlags(); - bool const partialPaymentAllowed = uTxFlags & tfPartialPayment; - auto const paths = ctx.tx.isFieldPresent(sfPaths); + // Ripple if source or destination is non-native or if there are hasPaths. + std::uint32_t const txFlags = ctx.tx.getFlags(); + bool const partialPaymentAllowed = txFlags & tfPartialPayment; + auto const hasPaths = ctx.tx.isFieldPresent(sfPaths); auto const sendMax = ctx.tx[~sfSendMax]; - AccountID const uDstAccountID(ctx.tx[sfDestination]); - STAmount const saDstAmount(ctx.tx[sfAmount]); + AccountID const dstAccountID(ctx.tx[sfDestination]); + STAmount const dstAmount(ctx.tx[sfAmount]); - auto const k = keylet::account(uDstAccountID); + auto const k = keylet::account(dstAccountID); auto const sleDst = ctx.view.read(k); if (!sleDst) { // Destination account does not exist. - if (!saDstAmount.native()) + if (!dstAmount.native()) { JLOG(ctx.j.trace()) << "Delay transaction: Destination account does not exist."; @@ -263,7 +270,7 @@ Payment::preclaim(PreclaimContext const& ctx) // transaction would succeed. return telNO_DST_PARTIAL; } - else if (saDstAmount < STAmount(ctx.view.fees().accountReserve(0))) + else if (dstAmount < STAmount(ctx.view.fees().accountReserve(0))) { // accountReserve is the minimum amount that an account can have. // Reserve is not scaled by load. @@ -293,7 +300,7 @@ Payment::preclaim(PreclaimContext const& ctx) } // Payment with at least one intermediate step and uses transitive balances. - if ((paths || sendMax || !saDstAmount.native()) && ctx.view.open()) + if ((hasPaths || sendMax || !dstAmount.native()) && ctx.view.open()) { STPathSet const& paths = ctx.tx.getFieldPathSet(sfPaths); @@ -314,34 +321,25 @@ Payment::doApply() { auto const deliverMin = ctx_.tx[~sfDeliverMin]; - // Ripple if source or destination is non-native or if there are paths. - std::uint32_t const uTxFlags = ctx_.tx.getFlags(); - bool const partialPaymentAllowed = uTxFlags & tfPartialPayment; - bool const limitQuality = uTxFlags & tfLimitQuality; - bool const defaultPathsAllowed = !(uTxFlags & tfNoRippleDirect); - auto const paths = ctx_.tx.isFieldPresent(sfPaths); + // Ripple if source or destination is non-native or if there are hasPaths. + std::uint32_t const txFlags = ctx_.tx.getFlags(); + bool const partialPaymentAllowed = txFlags & tfPartialPayment; + bool const limitQuality = txFlags & tfLimitQuality; + bool const defaultPathsAllowed = !(txFlags & tfNoRippleDirect); + auto const hasPaths = ctx_.tx.isFieldPresent(sfPaths); auto const sendMax = ctx_.tx[~sfSendMax]; - AccountID const uDstAccountID(ctx_.tx.getAccountID(sfDestination)); - STAmount const saDstAmount(ctx_.tx.getFieldAmount(sfAmount)); - bool const bMPTDirect = saDstAmount.holds(); - STAmount maxSourceAmount; - if (sendMax) - maxSourceAmount = *sendMax; - else if (saDstAmount.native() || bMPTDirect) - maxSourceAmount = saDstAmount; - else - maxSourceAmount = STAmount( - Issue{saDstAmount.getCurrency(), account_}, - saDstAmount.mantissa(), - saDstAmount.exponent(), - saDstAmount < beast::zero); + AccountID const dstAccountID(ctx_.tx.getAccountID(sfDestination)); + STAmount const dstAmount(ctx_.tx.getFieldAmount(sfAmount)); + bool const mptDirect = dstAmount.holds(); + STAmount const maxSourceAmount = + getMaxSourceAmount(account_, dstAmount, sendMax); JLOG(j_.trace()) << "maxSourceAmount=" << maxSourceAmount.getFullText() - << " saDstAmount=" << saDstAmount.getFullText(); + << " dstAmount=" << dstAmount.getFullText(); // Open a ledger for editing. - auto const k = keylet::account(uDstAccountID); + auto const k = keylet::account(dstAccountID); SLE::pointer sleDst = view().peek(k); if (!sleDst) @@ -352,7 +350,7 @@ Payment::doApply() // Create the account. sleDst = std::make_shared(k); - sleDst->setAccountID(sfAccount, uDstAccountID); + sleDst->setAccountID(sfAccount, dstAccountID); sleDst->setFieldU32(sfSequence, seqno); view().insert(sleDst); @@ -371,15 +369,15 @@ Payment::doApply() bool const depositPreauth = view().rules().enabled(featureDepositPreauth); - bool const bRipple = - (paths || sendMax || !saDstAmount.native()) && !bMPTDirect; + bool const ripple = + (hasPaths || sendMax || !dstAmount.native()) && !mptDirect; // If the destination has lsfDepositAuth set, then only direct XRP // payments (no intermediate steps) are allowed to the destination. - if (!depositPreauth && bRipple && reqDepositAuth) + if (!depositPreauth && ripple && reqDepositAuth) return tecNO_PERMISSION; - if (bRipple) + if (ripple) { // Ripple payment with at least one intermediate step and uses // transitive balances. @@ -390,10 +388,10 @@ Payment::doApply() // authorization has two ways to get an IOU Payment in: // 1. If Account == Destination, or // 2. If Account is deposit preauthorized by destination. - if (uDstAccountID != account_) + if (dstAccountID != account_) { if (!view().exists( - keylet::depositPreauth(uDstAccountID, account_))) + keylet::depositPreauth(dstAccountID, account_))) return tecNO_PERMISSION; } } @@ -412,8 +410,8 @@ Payment::doApply() rc = path::RippleCalc::rippleCalculate( pv, maxSourceAmount, - saDstAmount, - uDstAccountID, + dstAmount, + dstAccountID, account_, ctx_.tx.getFieldPathSet(sfPaths), ctx_.app.logs(), @@ -426,7 +424,7 @@ Payment::doApply() // TODO: is this right? If the amount is the correct amount, was // the delivered amount previously set? - if (rc.result() == tesSUCCESS && rc.actualAmountOut != saDstAmount) + if (rc.result() == tesSUCCESS && rc.actualAmountOut != dstAmount) { if (deliverMin && rc.actualAmountOut < *deliverMin) rc.setResult(tecPATH_PARTIAL); @@ -444,39 +442,37 @@ Payment::doApply() terResult = tecPATH_DRY; return terResult; } - else if (bMPTDirect) + else if (mptDirect) { - JLOG(j_.trace()) << " saDstAmount=" << saDstAmount.getFullText(); + JLOG(j_.trace()) << " dstAmount=" << dstAmount.getFullText(); + auto const& mptIssue = dstAmount.get(); - if (auto const ter = - requireAuth(view(), saDstAmount.get(), account_); + if (auto const ter = requireAuth(view(), mptIssue, account_); ter != tesSUCCESS) return ter; - if (auto const ter = - requireAuth(view(), saDstAmount.get(), uDstAccountID); + if (auto const ter = requireAuth(view(), mptIssue, dstAccountID); ter != tesSUCCESS) return ter; - if (auto const ter = canTransfer( - view(), saDstAmount.get(), account_, uDstAccountID); + if (auto const ter = + canTransfer(view(), mptIssue, account_, dstAccountID); ter != tesSUCCESS) return ter; - auto const& mptIssue = saDstAmount.get(); auto const& issuer = mptIssue.getIssuer(); // Transfer rate Rate rate{QUALITY_ONE}; // Payment between the holders - if (account_ != issuer && uDstAccountID != issuer) + if (account_ != issuer && dstAccountID != issuer) { // If globally/individually locked then // - can't send between holders // - holder can send back to issuer // - issuer can send to holder if (isFrozen(view(), account_, mptIssue) || - isFrozen(view(), uDstAccountID, mptIssue)) + isFrozen(view(), dstAccountID, mptIssue)) return tecLOCKED; // Get the rate for a payment between the holders. @@ -484,10 +480,10 @@ Payment::doApply() } // Amount to deliver. - STAmount amountDeliver = saDstAmount; + STAmount amountDeliver = dstAmount; // Factor in the transfer rate. // No rounding. It'll change once MPT integrated into DEX. - STAmount requiredMaxSourceAmount = multiply(saDstAmount, rate); + STAmount requiredMaxSourceAmount = multiply(dstAmount, rate); // Send more than the account wants to pay or less than // the account wants to deliver (if no SendMax). @@ -505,7 +501,7 @@ Payment::doApply() PaymentSandbox pv(&view()); auto res = accountSendMPT( - pv, account_, uDstAccountID, amountDeliver, ctx_.journal); + pv, account_, dstAccountID, amountDeliver, ctx_.journal); if (res == tesSUCCESS) pv.apply(ctx_.rawView()); else if (res == tecINSUFFICIENT_FUNDS || res == tecPATH_DRY) @@ -514,7 +510,7 @@ Payment::doApply() return res; } - assert(saDstAmount.native()); + assert(dstAmount.native()); // Direct XRP payment. @@ -522,25 +518,25 @@ Payment::doApply() if (!sleSrc) return tefINTERNAL; - // uOwnerCount is the number of entries in this ledger for this + // ownerCount is the number of entries in this ledger for this // account that require a reserve. - auto const uOwnerCount = sleSrc->getFieldU32(sfOwnerCount); + auto const ownerCount = sleSrc->getFieldU32(sfOwnerCount); // This is the total reserve in drops. - auto const reserve = view().fees().accountReserve(uOwnerCount); + auto const reserve = view().fees().accountReserve(ownerCount); // mPriorBalance is the balance on the sending account BEFORE the // fees were charged. We want to make sure we have enough reserve // to send. Allow final spend to use reserve for fee. auto const mmm = std::max(reserve, ctx_.tx.getFieldAmount(sfFee).xrp()); - if (mPriorBalance < saDstAmount.xrp() + mmm) + if (mPriorBalance < dstAmount.xrp() + mmm) { // Vote no. However the transaction might succeed, if applied in // a different order. JLOG(j_.trace()) << "Delay transaction: Insufficient funds: " << " " << to_string(mPriorBalance) << " / " - << to_string(saDstAmount.xrp() + mmm) << " (" + << to_string(dstAmount.xrp() + mmm) << " (" << to_string(reserve) << ")"; return tecUNFUNDED_PAYMENT; @@ -573,14 +569,14 @@ Payment::doApply() // We choose the base reserve as our bound because it is // a small number that seldom changes but is always sufficient // to get the account un-wedged. - if (uDstAccountID != account_) + if (dstAccountID != account_) { - if (!view().exists(keylet::depositPreauth(uDstAccountID, account_))) + if (!view().exists(keylet::depositPreauth(dstAccountID, account_))) { // Get the base reserve. XRPAmount const dstReserve{view().fees().accountReserve(0)}; - if (saDstAmount > dstReserve || + if (dstAmount > dstReserve || sleDst->getFieldAmount(sfBalance) > dstReserve) return tecNO_PERMISSION; } @@ -588,9 +584,9 @@ Payment::doApply() } // Do the arithmetic for the transfer and make the ledger change. - sleSrc->setFieldAmount(sfBalance, mSourceBalance - saDstAmount); + sleSrc->setFieldAmount(sfBalance, mSourceBalance - dstAmount); sleDst->setFieldAmount( - sfBalance, sleDst->getFieldAmount(sfBalance) + saDstAmount); + sfBalance, sleDst->getFieldAmount(sfBalance) + dstAmount); // Re-arm the password change fee if we can and need to. if ((sleDst->getFlags() & lsfPasswordSpent)) diff --git a/src/xrpld/ledger/detail/View.cpp b/src/xrpld/ledger/detail/View.cpp index afae132c1fc..8bbf5059171 100644 --- a/src/xrpld/ledger/detail/View.cpp +++ b/src/xrpld/ledger/detail/View.cpp @@ -248,9 +248,8 @@ isFrozen( AccountID const& account, MPTIssue const& mptIssue) { - if (isGlobalFrozen(view, mptIssue)) - return true; - return isIndividualFrozen(view, account, mptIssue); + return isGlobalFrozen(view, mptIssue) || + isIndividualFrozen(view, account, mptIssue); } STAmount @@ -569,10 +568,9 @@ transferRate(ReadView const& view, AccountID const& issuer) Rate transferRate(ReadView const& view, MPTID const& issuanceID) { - auto const sle = view.read(keylet::mptIssuance(issuanceID)); - // fee is 0-50,000 (0-50%), rate is 1,000,000,000-2,000,000,000 - if (sle && sle->isFieldPresent(sfTransferFee)) + if (auto const sle = view.read(keylet::mptIssuance(issuanceID)); + sle && sle->isFieldPresent(sfTransferFee)) return Rate{1'000'000'000u + 10'000 * sle->getFieldU16(sfTransferFee)}; return parityRate; @@ -1231,7 +1229,7 @@ accountSend( { if (view.rules().enabled(fixAMMv1_1)) { - if (saAmount < beast::zero) + if (saAmount < beast::zero || saAmount.holds()) { return tecINTERNAL; } @@ -1353,18 +1351,17 @@ rippleSendMPT( // Safe to get MPT since rippleSendMPT is only called by accountSendMPT auto const issuer = saAmount.getIssuer(); + auto const sle = + view.read(keylet::mptIssuance(saAmount.get().getMptID())); + if (!sle) + return tecOBJECT_NOT_FOUND; + if (uSenderID == issuer || uReceiverID == issuer) { // if sender is issuer, check that the new OutstandingAmount will not // exceed MaximumAmount if (uSenderID == issuer) { - auto const mptID = - keylet::mptIssuance(saAmount.get().getMptID()); - auto const sle = view.peek(mptID); - if (!sle) - return tecOBJECT_NOT_FOUND; - auto const sendAmount = saAmount.mpt().value(); auto const maximumAmount = sle->at(~sfMaximumAmount).value_or(maxMPTokenAmount); @@ -1374,7 +1371,7 @@ rippleSendMPT( return tecPATH_DRY; } - // Direct send: redeeming IOUs and/or sending own IOUs. + // Direct send: redeeming MPTs and/or sending own MPTs. auto const ter = rippleCreditMPT(view, uSenderID, uReceiverID, saAmount, j); if (ter != tesSUCCESS) @@ -1384,29 +1381,23 @@ rippleSendMPT( } // Sending 3rd party MPTs: transit. - if (auto const sle = - view.read(keylet::mptIssuance(saAmount.get().getMptID()))) - { - saActual = (waiveFee == WaiveTransferFee::Yes) - ? saAmount - : multiply( - saAmount, - transferRate(view, saAmount.get().getMptID())); - - JLOG(j.debug()) << "rippleSend> " << to_string(uSenderID) << " - > " - << to_string(uReceiverID) - << " : deliver=" << saAmount.getFullText() - << " cost=" << saActual.getFullText(); + saActual = (waiveFee == WaiveTransferFee::Yes) + ? saAmount + : multiply( + saAmount, + transferRate(view, saAmount.get().getMptID())); - if (auto const terResult = - rippleCreditMPT(view, issuer, uReceiverID, saAmount, j); - terResult != tesSUCCESS) - return terResult; + JLOG(j.debug()) << "rippleSend> " << to_string(uSenderID) << " - > " + << to_string(uReceiverID) + << " : deliver=" << saAmount.getFullText() + << " cost=" << saActual.getFullText(); - return rippleCreditMPT(view, uSenderID, issuer, saActual, j); - } + if (auto const terResult = + rippleCreditMPT(view, issuer, uReceiverID, saAmount, j); + terResult != tesSUCCESS) + return terResult; - return tecOBJECT_NOT_FOUND; + return rippleCreditMPT(view, uSenderID, issuer, saActual, j); } TER