diff --git a/src/test/app/MPToken_test.cpp b/src/test/app/MPToken_test.cpp index 9c1f8876d2d..b40230c759d 100644 --- a/src/test/app/MPToken_test.cpp +++ b/src/test/app/MPToken_test.cpp @@ -774,6 +774,7 @@ class MPToken_test : public beast::unit_test::suite mptAlice.pay(carol, bob, 2, tecMPT_LOCKED); // Issuer can send mptAlice.pay(alice, bob, 3); + // Holder can send back to issuer mptAlice.pay(bob, alice, 4); @@ -970,10 +971,10 @@ class MPToken_test : public beast::unit_test::suite auto const MPT = mptAlice["MPT"]; env(pay(alice, carol, MPT(100)), sendmax(XRP(100)), - ter(temMALFORMED)); + ter(temDISABLED)); env(pay(alice, carol, MPT(100)), delivermin(XRP(100)), - ter(temMALFORMED)); + ter(temDISABLED)); } // build_path is invalid if MPT @@ -1610,11 +1611,51 @@ class MPToken_test : public beast::unit_test::suite Account const carol = Account("carol"); auto const USD = gw["USD"]; + // Blocking flags + for (auto flags : + {tfMPTCanLock | + tfMPTCanTransfer, // locked, issuer and holder fails + tfMPTRequireAuth | + tfMPTCanTransfer, // not authorized, holder fails + tfMPTCanTrade, // can't transfer, holder fails + tfMPTCanLock}) // lock mptoken, holder fails + { + Env env{*this, features}; + + MPTTester mpt(env, gw, {.holders = {&alice}}); + + auto const lockMPToken = + (flags & (tfMPTCanLock | tfMPTCanTransfer)) == tfMPTCanLock; + auto const lockMPTIssue = + (flags & (tfMPTCanLock | tfMPTCanTransfer)) == + (tfMPTCanLock | tfMPTCanTransfer); + flags = lockMPToken ? (flags | tfMPTCanTransfer) : flags; + + mpt.create({.ownerCount = 1, .holderCount = 0, .flags = flags}); + auto const MPT = mpt["MPT"]; + + if ((flags & tfMPTRequireAuth) == 0) + { + mpt.authorize({.account = &alice}); + mpt.pay(gw, alice, 200); + } + if (lockMPToken) + mpt.set({.holder = &alice, .flags = tfMPTLock}); + else if (lockMPTIssue) + mpt.set({.flags = tfMPTLock}); + + auto const err = + flags & tfMPTRequireAuth ? tecUNFUNDED_OFFER : tecNO_PERMISSION; + + env(offer(alice, XRP(100), MPT(101)), ter(err)); + env.close(); + } + // MPTokenV2 is disabled { Env env{*this, features - featureMPTokensV2}; - MPTTester mpt(env, gw, {.holders = {&alice, &carol}}); + MPTTester mpt(env, gw, {.holders = {&alice}}); mpt.create( {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer}); @@ -1622,9 +1663,6 @@ class MPToken_test : public beast::unit_test::suite mpt.authorize({.account = &alice}); mpt.pay(gw, alice, 200); - mpt.authorize({.account = &carol}); - mpt.pay(gw, carol, 200); - env(offer(alice, XRP(100), mpt.mpt(101)), ter(temDISABLED)); env.close(); } @@ -1636,7 +1674,9 @@ class MPToken_test : public beast::unit_test::suite MPTTester mpt(env, gw, {.holders = {&alice, &carol}}); mpt.create( - {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer}); + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanTransfer | tfMPTCanTrade}); auto const MPT = mpt["MPT"]; mpt.authorize({.account = &alice}); @@ -1666,7 +1706,9 @@ class MPToken_test : public beast::unit_test::suite MPTTester mpt(env, gw, {.holders = {&alice, &carol}}); mpt.create( - {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer}); + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanTransfer | tfMPTCanTrade}); auto const MPT = mpt["MPT"]; env(trust(alice, USD(2'000))); @@ -1706,13 +1748,17 @@ class MPToken_test : public beast::unit_test::suite MPTTester mpt1(env, gw, {.holders = {&alice, &carol}}); mpt1.create( - {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer}); + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanTransfer | tfMPTCanTrade}); auto const MPT1 = mpt1["MPT1"]; MPTTester mpt2( env, gw, {.holders = {&alice, &carol}, .fund = false}); mpt2.create( - {.ownerCount = 2, .holderCount = 0, .flags = tfMPTCanTransfer}); + {.ownerCount = 2, + .holderCount = 0, + .flags = tfMPTCanTransfer | tfMPTCanTrade}); auto const MPT2 = mpt2["MPT2"]; mpt1.authorize({.account = &alice}); @@ -1755,13 +1801,79 @@ class MPToken_test : public beast::unit_test::suite Account const bob = Account("bob"); auto const USD = gw["USD"]; + // Blocking flags + for (auto flags : + {tfMPTCanLock | + tfMPTCanTransfer, // locked, issuer and holder fails + tfMPTRequireAuth | + tfMPTCanTransfer, // not authorized, holder fails + tfMPTCanTrade, // can't transfer, holder fails + tfMPTCanLock}) // lock mptoken, holder fails + { + Env env{*this, features}; + + MPTTester mpt(env, gw, {.holders = {&alice}}); + + auto const lockMPToken = + (flags & (tfMPTCanLock | tfMPTCanTransfer)) == tfMPTCanLock; + auto const lockMPTIssue = + (flags & (tfMPTCanLock | tfMPTCanTransfer)) == + (tfMPTCanLock | tfMPTCanTransfer); + flags = lockMPToken ? (flags | tfMPTCanTransfer) : flags; + + mpt.create({.ownerCount = 1, .holderCount = 0, .flags = flags}); + auto const MPT = mpt["MPT"]; + + if ((flags & tfMPTRequireAuth) == 0) + { + mpt.authorize({.account = &alice}); + mpt.pay(gw, alice, 200); + } + if (lockMPToken) + mpt.set({.holder = &alice, .flags = tfMPTLock}); + else if (lockMPTIssue) + mpt.set({.flags = tfMPTLock}); + + auto const err = + flags & tfMPTRequireAuth ? tecUNFUNDED_OFFER : tecNO_PERMISSION; + + env(offer(alice, XRP(100), MPT(101)), ter(err)); + env.close(); + } + + // Loop + {} + + // MPTokenV2 is disabled + { + Env env{*this, features - featureMPTokensV2}; + + MPTTester mpt(env, gw, {.holders = {&alice}}); + + mpt.create( + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanTransfer | tfMPTCanTrade}); + auto const MPT = mpt["MPT"]; + + mpt.authorize({.account = &alice}); + + env(pay(gw, alice, MPT(101)), + test::jtx::path(~MPT), + sendmax(XRP(100)), + txflags(tfPartialPayment), + ter(temDISABLED)); + } + // MPT/XRP { Env env{*this, features}; MPTTester mpt(env, gw, {.holders = {&alice, &carol, &bob}}); mpt.create( - {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer}); + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanTransfer | tfMPTCanTrade}); auto const MPT = mpt["MPT"]; mpt.authorize({.account = &alice}); @@ -1796,7 +1908,9 @@ class MPToken_test : public beast::unit_test::suite MPTTester mpt(env, gw, {.holders = {&alice, &carol, &bob}}); mpt.create( - {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer}); + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanTransfer | tfMPTCanTrade}); auto const MPT = mpt["MPT"]; env(trust(alice, USD(2'000))); @@ -1840,7 +1954,9 @@ class MPToken_test : public beast::unit_test::suite MPTTester mpt(env, gw, {.holders = {&alice, &carol, &bob}}); mpt.create( - {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer}); + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanTransfer | tfMPTCanTrade}); auto const MPT = mpt["MPT"]; env(trust(alice, USD(2'000)), txflags(tfClearNoRipple)); @@ -1879,13 +1995,17 @@ class MPToken_test : public beast::unit_test::suite MPTTester mpt1(env, gw, {.holders = {&alice, &carol, &bob}}); mpt1.create( - {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer}); + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanTransfer | tfMPTCanTrade}); auto const MPT1 = mpt1["MPT1"]; MPTTester mpt2( env, gw, {.holders = {&alice, &carol, &bob}, .fund = false}); mpt2.create( - {.ownerCount = 2, .holderCount = 0, .flags = tfMPTCanTransfer}); + {.ownerCount = 2, + .holderCount = 0, + .flags = tfMPTCanTransfer | tfMPTCanTrade}); auto const MPT2 = mpt2["MPT2"]; mpt1.authorize({.account = &alice}); @@ -1924,7 +2044,9 @@ class MPToken_test : public beast::unit_test::suite MPTTester mpt(env, gw, {.fund = false}); mpt.create( - {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer}); + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanTransfer | tfMPTCanTrade}); auto const MPT = mpt["MPT"]; mpt.authorize({.account = &alice}); @@ -1953,7 +2075,9 @@ class MPToken_test : public beast::unit_test::suite MPTTester mpt(env, gw, {.fund = false}); mpt.create( - {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer}); + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanTransfer | tfMPTCanTrade}); auto const MPT = mpt["MPT"]; mpt.authorize({.account = &alice}); @@ -1980,14 +2104,14 @@ class MPToken_test : public beast::unit_test::suite env.close(); MPTTester mpt1(env, gw, {.fund = false}); - mpt1.create({.flags = tfMPTCanTransfer}); + mpt1.create({.flags = tfMPTCanTransfer | tfMPTCanTrade}); auto const MPT1 = mpt1["MPT1"]; mpt1.authorize({.account = &alice}); mpt1.authorize({.account = &bob}); mpt1.pay(gw, alice, 10'100); MPTTester mpt2(env, gw, {.fund = false}); - mpt2.create({.flags = tfMPTCanTransfer}); + mpt2.create({.flags = tfMPTCanTransfer | tfMPTCanTrade}); auto const MPT2 = mpt2["MPT1"]; mpt2.authorize({.account = &alice}); mpt2.authorize({.account = &bob}); @@ -2026,7 +2150,7 @@ class MPToken_test : public beast::unit_test::suite auto createMPT = [&]() -> std::pair { MPTTester mpt(env, gw, {.fund = false}); - mpt.create({.flags = tfMPTCanTransfer}); + mpt.create({.flags = tfMPTCanTransfer | tfMPTCanTrade}); mpt.authorize({.account = &alice}); mpt.pay(gw, alice, 2'000); return {mpt, mpt["MPT"]}; @@ -2079,7 +2203,9 @@ class MPToken_test : public beast::unit_test::suite MPTTester mpt(env, gw, {.holders = {&dan, &carol}}); mpt.create( - {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer}); + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanTransfer | tfMPTCanTrade}); auto const MPT = mpt["MPT"]; mpt.authorize({.account = &dan}); mpt.authorize({.account = &carol}); @@ -2102,7 +2228,9 @@ class MPToken_test : public beast::unit_test::suite MPTTester mpt(env, gw, {.holders = {&alice, &dan}}); mpt.create( - {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer}); + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanTransfer | tfMPTCanTrade}); auto const MPT = mpt["MPT"]; mpt.authorize({.account = &alice}); @@ -2130,7 +2258,9 @@ class MPToken_test : public beast::unit_test::suite MPTTester mpt(env, gw1, {.holders = {&alice, &dan}}); mpt.create( - {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer}); + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanTransfer | tfMPTCanTrade}); auto const MPT = mpt["MPT"]; mpt.authorize({.account = &alice}); @@ -2160,10 +2290,14 @@ class MPToken_test : public beast::unit_test::suite MPTTester mpt1(env, gw1, {.holders = {&carol}}); mpt.create( - {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer}); + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanTransfer | tfMPTCanTrade}); auto const MPT = mpt["MPT"]; mpt1.create( - {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer}); + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanTransfer | tfMPTCanTrade}); auto const MPT1 = mpt1["MPT1"]; mpt.authorize({.account = &alice}); @@ -2196,7 +2330,9 @@ class MPToken_test : public beast::unit_test::suite MPTTester mpt(env, gw, {.holders = {&alice, &bob}}); mpt.create( - {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer}); + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanTransfer | tfMPTCanTrade}); auto const MPT = mpt["MPT"]; mpt.authorize({.account = &alice}); @@ -2232,7 +2368,9 @@ class MPToken_test : public beast::unit_test::suite MPTTester mpt(env, gw, {.holders = {&alice, &carol}}); mpt.create( - {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer}); + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanTransfer | tfMPTCanTrade}); auto const MPT = mpt["MPT"]; mpt.authorize({.account = &alice}); mpt.authorize({.account = &carol}); @@ -2240,7 +2378,9 @@ class MPToken_test : public beast::unit_test::suite MPTTester mpt1(env, gw1, {.holders = {&bob, &dan}}); mpt1.create( - {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer}); + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanTransfer | tfMPTCanTrade}); auto const MPT1 = mpt1["MPT1"]; mpt1.authorize({.account = &bob}); mpt1.pay(gw1, bob, 200); diff --git a/src/xrpld/app/misc/MPTUtils.h b/src/xrpld/app/misc/MPTUtils.h new file mode 100644 index 00000000000..b9fa33da38b --- /dev/null +++ b/src/xrpld/app/misc/MPTUtils.h @@ -0,0 +1,52 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_APP_MISC_MPTUTILS_H_INLCUDED +#define RIPPLE_APP_MISC_MPTUTILS_H_INLCUDED + +#include +#include +#include + +namespace ripple { + +class Asset; +class ReadView; + +/* Return true if a transaction is allowed for the specified MPT/account. The + * function checks MPTokenIssuance and MPToken objects flags to determine if the + * transaction is allowed. + */ +TER +isMPTTxAllowed( + ReadView const& v, + TxType tx, + Asset const& asset, + AccountID const& accountID); + +TER +isMPTDEXAllowed( + ReadView const& view, + Asset const& issuanceID, + AccountID const& srcAccount, + AccountID const& destAccount); + +} // namespace ripple + +#endif // RIPPLE_APP_MISC_MPTUTILS_H_INLCUDED diff --git a/src/xrpld/app/misc/detail/AMMUtils.cpp b/src/xrpld/app/misc/detail/AMMUtils.cpp index 087ee389d18..7c001e00e63 100644 --- a/src/xrpld/app/misc/detail/AMMUtils.cpp +++ b/src/xrpld/app/misc/detail/AMMUtils.cpp @@ -17,6 +17,7 @@ */ //============================================================================== #include +// #include #include #include #include diff --git a/src/xrpld/app/misc/detail/MPTUtils.cpp b/src/xrpld/app/misc/detail/MPTUtils.cpp new file mode 100644 index 00000000000..12c3dc02425 --- /dev/null +++ b/src/xrpld/app/misc/detail/MPTUtils.cpp @@ -0,0 +1,105 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include +#include + +namespace ripple { + +static TER +isMPTAllowed( + ReadView const& view, + TxType txType, + Asset const& asset, + AccountID const& accountID, + std::optional const& destAccount) +{ + if (!asset.holds()) + return tesSUCCESS; + + auto const& issuanceID = asset.get().getMptID(); + auto const isDEX = txType == ttPAYMENT && destAccount; + auto const ammTx = txType == ttAMM_CREATE || txType == ttAMM_DEPOSIT || + txType == ttAMM_WITHDRAW; + auto const allMPTTx = ammTx || txType == ttOFFER_CREATE || + txType == ttCHECK_CREATE || txType == ttCHECK_CASH || + txType == ttPAYMENT; + assert(allMPTTx || isDEX); + + auto const issuanceKey = keylet::mptIssuance(issuanceID); + auto const issuanceSle = view.read(issuanceKey); + if (!issuanceSle) + return tecMPT_ISSUANCE_NOT_FOUND; + + auto const& issuer = asset.getIssuer(); + auto const flags = issuanceSle->getFlags(); + + if (flags & lsfMPTLocked) + return tecNO_PERMISSION; + // Offer crossing and Payment + if ((flags & lsfMPTCanTrade) == 0 && isDEX) + return tecNO_PERMISSION; + if ((flags & lsfMPTCanClawback) && txType == ttAMM_CREATE) + return tecNO_PERMISSION; + + if (accountID != issuer) + { + if ((flags & lsfMPTCanTransfer) == 0) + return tecNO_PERMISSION; + + auto const mptSle = + view.read(keylet::mptoken(issuanceKey.key, accountID)); + // Allow to succeed since some tx create MPToken if it doesn't exist. + // Tx's have their own check for missing MPToken. + if (!mptSle) + return tesSUCCESS; + + if ((mptSle->getFlags() & lsfMPTLocked) && destAccount != issuer) + return tecNO_PERMISSION; + } + + return tesSUCCESS; +} + +TER +isMPTTxAllowed( + ReadView const& view, + TxType txType, + Asset const& asset, + AccountID const& accountID) +{ + // use isDEXAllowed for payment/offer crossing + assert(txType != ttPAYMENT); + return isMPTAllowed(view, txType, asset, accountID, std::nullopt); +} + +TER +isMPTDEXAllowed( + ReadView const& view, + Asset const& asset, + AccountID const& accountID, + AccountID const& dest) +{ + // use ttPAYMENT for both offer crossing and payment + return isMPTAllowed(view, ttPAYMENT, asset, accountID, dest); +} + +} // namespace ripple diff --git a/src/xrpld/app/paths/detail/BookStep.cpp b/src/xrpld/app/paths/detail/BookStep.cpp index e267051f8a7..6dffb8f5eab 100644 --- a/src/xrpld/app/paths/detail/BookStep.cpp +++ b/src/xrpld/app/paths/detail/BookStep.cpp @@ -18,6 +18,7 @@ //============================================================================== #include +#include #include #include #include @@ -1384,6 +1385,14 @@ BookStep::check(StrandContext const& ctx) const keylet::mptIssuance(book_.in.get().getMptID()); if (auto const sle = view.read(mptID); !sle) return terNO_MPT; + + if (auto const ter = isMPTDEXAllowed( + view, + book_.in, + book_.in.getIssuer(), + book_.in.getIssuer()); + ter != tesSUCCESS) + return ter; } } } diff --git a/src/xrpld/app/paths/detail/MPTEndpointStep.cpp b/src/xrpld/app/paths/detail/MPTEndpointStep.cpp index 57172660296..10dc23755a5 100644 --- a/src/xrpld/app/paths/detail/MPTEndpointStep.cpp +++ b/src/xrpld/app/paths/detail/MPTEndpointStep.cpp @@ -103,10 +103,11 @@ class MPTEndpointStep , prevStep_(ctx.prevStep) , isLast_(ctx.isLast) , isDirectBetweenHolders_( - ctx.prevStep && !ctx.prevStep->bookStepBook() && ctx.isLast && - ctx.strandDeliver.holds() && - mptIssue_ == ctx.strandDeliver.get() && - dst_ == ctx.strandDst) + mptIssue_ == ctx.strandDeliver && + ((ctx.isFirst && src != mptIssue_.getIssuer() && + dst_ != mptIssue_.getIssuer()) || + (ctx.prevStep && !ctx.prevStep->bookStepBook() && ctx.isLast && + dst_ == ctx.strandDst))) , j_(ctx.j) { } @@ -371,14 +372,17 @@ DirectMPTPaymentStep::check( StrandContext const& ctx, std::shared_ptr const& sleSrc) const { - // Since this is a payment a MPToken must be present. Perform all + auto const& mptID = mptIssue_.getMptID(); + // Since this is a payment, MPToken must be present. Perform all // MPToken related checks. - if (!ctx.view.exists(keylet::mptIssuance(mptIssue_.getMptID()))) + if (!ctx.view.exists(keylet::mptIssuance(mptID))) return tecMPT_ISSUANCE_NOT_FOUND; - if (src_ != mptIssue_.getIssuer()) + + auto const& issuer = mptIssue_.getIssuer(); + if (src_ != issuer) { - auto const mptokenID = keylet::mptoken(mptIssue_.getMptID(), src_); - if (!ctx.view.exists(mptokenID)) + auto const key = keylet::mptoken(mptID, src_); + if (!ctx.view.exists(key)) return tecNO_AUTH; if (auto const ter = requireAuth(ctx.view, mptIssue_, src_); @@ -386,10 +390,10 @@ DirectMPTPaymentStep::check( return ter; } - if (dst_ != mptIssue_.getIssuer()) + if (dst_ != issuer) { - auto const mptokenID = keylet::mptoken(mptIssue_.getMptID(), dst_); - if (!ctx.view.exists(mptokenID)) + auto const key = keylet::mptoken(mptID, dst_); + if (!ctx.view.exists(key)) return tecNO_AUTH; if (auto const ter = requireAuth(ctx.view, mptIssue_, dst_); @@ -397,16 +401,30 @@ DirectMPTPaymentStep::check( return ter; } - // Direct MPT payment between holders - if (ctx.isFirst && ctx.strandDeliver.holds() && - mptIssue_ == ctx.strandDeliver.get() && dst_ != ctx.strandDst) + // Direct MPT payment + if (mptIssue_ == ctx.strandDeliver && + (ctx.isFirst || (ctx.prevStep && !ctx.prevStep->bookStepBook()))) { - if (isFrozen(ctx.view, src_, mptIssue_) || - isFrozen(ctx.view, ctx.strandDst, mptIssue_)) - return tecMPT_LOCKED; - - if (auto const ter = - canTransfer(ctx.view, mptIssue_, src_, ctx.strandDst); + // Between holders + if (isDirectBetweenHolders_) + { + auto const& holder = ctx.isFirst ? src_ : dst_; + // Payment between the holders + if (isFrozen(ctx.view, holder, mptIssue_)) + return tecMPT_LOCKED; + + if (auto const ter = + canTransfer(ctx.view, mptIssue_, holder, ctx.strandDst); + ter != tesSUCCESS) + return ter; + } + } + // Cross-token MPT payment via DEX + else + { + auto const account = ctx.isFirst ? src_ : dst_; + if (auto const ter = isMPTDEXAllowed( + ctx.view, mptIssue_, account, mptIssue_.getIssuer()); ter != tesSUCCESS) return ter; } @@ -416,9 +434,18 @@ DirectMPTPaymentStep::check( TER DirectMPTOfferCrossingStep::check( - StrandContext const&, + StrandContext const& ctx, std::shared_ptr const&) const { + auto const& holder = ctx.isFirst ? src_ : dst_; + auto const& issuer = mptIssue_.getIssuer(); + if (holder != issuer) + { + if (auto const ter = + isMPTDEXAllowed(ctx.view, mptIssue_, holder, issuer); + ter != tesSUCCESS) + return ter; + } return tesSUCCESS; } @@ -873,7 +900,8 @@ MPTEndpointStep::check(StrandContext const& ctx) const return terNO_ACCOUNT; } - // pure issue/redeem can't be frozen + // pure issue/redeem can't be frozen - can this happen? can only be an + // endpoint if (!(ctx.isLast && ctx.isFirst)) { if (isFrozen(ctx.view, src_, mptIssue_) || @@ -887,34 +915,7 @@ MPTEndpointStep::check(StrandContext const& ctx) const JLOG(j_.warn()) << "MPTEndpointStep: MPT can only be an endpoint"; return terNO_RIPPLE; } -#if 0 - { // TODO MPT is this needed? this step can only be an endpoint. - // it can outout mpt or it can have mpt as an input - // two MPT endpoint steps can also be connected - if (ctx.seenBookOuts.count(mptIssue_)) { - if (!ctx.prevStep) { - assert(0); // prev seen book without a prev step!?! - return temBAD_PATH_LOOP; - } - // This is OK if the previous step is a book step that outputs this - // issue - if (auto book = ctx.prevStep->bookStepBook()) { - if (book->out.holds() && - book->out.get() != mptIssue_) - return temBAD_PATH_LOOP; - } - } - - if (!ctx.seenDirectAssets[0].insert(mptIssue_).second || - !ctx.seenDirectAssets[1].insert(mptIssue_).second) { - JLOG(j_.debug()) - << "DirectStepI: loop detected: Index: " << ctx.strandSize - << ' ' << *this; - return temBAD_PATH_LOOP; - } - } -#endif return static_cast(this)->check(ctx, sleSrc); } diff --git a/src/xrpld/app/tx/detail/AMMCreate.cpp b/src/xrpld/app/tx/detail/AMMCreate.cpp index 370d3373dff..62291c33546 100644 --- a/src/xrpld/app/tx/detail/AMMCreate.cpp +++ b/src/xrpld/app/tx/detail/AMMCreate.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -220,14 +221,14 @@ AMMCreate::preclaim(PreclaimContext const& ctx) if (auto const ter = clawbackDisabled(amount2.asset()); ter != tesSUCCESS) return ter; - auto checkMPT = [&](Asset const& asset) { - if (asset.holds()) - return ctx.view.read(keylet::mptIssuance( - asset.get().getMptID())) != nullptr; - return true; - }; - if (!checkMPT(amount.asset()) || !checkMPT(amount2.asset())) - return tecOBJECT_NOT_FOUND; + if (auto const ter = + isMPTTxAllowed(ctx.view, ttAMM_CREATE, amount.asset(), accountID); + ter != tesSUCCESS) + return ter; + if (auto const ter = + isMPTTxAllowed(ctx.view, ttAMM_CREATE, amount2.asset(), accountID); + ter != tesSUCCESS) + return ter; return tesSUCCESS; } diff --git a/src/xrpld/app/tx/detail/AMMDeposit.cpp b/src/xrpld/app/tx/detail/AMMDeposit.cpp index 9d692381869..f301399c8ad 100644 --- a/src/xrpld/app/tx/detail/AMMDeposit.cpp +++ b/src/xrpld/app/tx/detail/AMMDeposit.cpp @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -348,6 +349,15 @@ AMMDeposit::preclaim(PreclaimContext const& ctx) } } + if (auto const ter = + isMPTTxAllowed(ctx.view, ttAMM_DEPOSIT, ctx.tx[sfAsset], accountID); + ter != tesSUCCESS) + return ter; + if (auto const ter = isMPTTxAllowed( + ctx.view, ttAMM_DEPOSIT, ctx.tx[sfAsset2], accountID); + ter != tesSUCCESS) + return ter; + return tesSUCCESS; } diff --git a/src/xrpld/app/tx/detail/AMMWithdraw.cpp b/src/xrpld/app/tx/detail/AMMWithdraw.cpp index d977153f57d..854f77fe3fc 100644 --- a/src/xrpld/app/tx/detail/AMMWithdraw.cpp +++ b/src/xrpld/app/tx/detail/AMMWithdraw.cpp @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -308,6 +309,15 @@ AMMWithdraw::preclaim(PreclaimContext const& ctx) return ter; } + if (auto const ter = isMPTTxAllowed( + ctx.view, ttAMM_WITHDRAW, ctx.tx[sfAsset], accountID); + ter != tesSUCCESS) + return ter; + if (auto const ter = isMPTTxAllowed( + ctx.view, ttAMM_WITHDRAW, ctx.tx[sfAsset2], accountID); + ter != tesSUCCESS) + return ter; + return tesSUCCESS; } diff --git a/src/xrpld/app/tx/detail/CashCheck.cpp b/src/xrpld/app/tx/detail/CashCheck.cpp index 4289cec1144..c5734240d09 100644 --- a/src/xrpld/app/tx/detail/CashCheck.cpp +++ b/src/xrpld/app/tx/detail/CashCheck.cpp @@ -389,22 +389,19 @@ CashCheck::doApply() bool const checkCashMakesTrustLine = psb.rules().enabled(featureCheckCashMakesTrustLine); - std::optional> trustlineCb; - auto fixupCb = [&]() { - if (trustlineCb) - (*trustlineCb)(); - }; + std::optional trustLineKey; + STAmount savedLimit; + bool destLow = false; if (flowDeliver.holds()) { // If a trust line does not exist yet create one. Issue const& trustLineIssue = flowDeliver.get(); AccountID const issuer = flowDeliver.getIssuer(); AccountID const truster = issuer == account_ ? srcId : account_; - Keylet const trustLineKey = - keylet::line(truster, trustLineIssue); - bool const destLow = issuer > account_; + trustLineKey = keylet::line(truster, trustLineIssue); + destLow = issuer > account_; - if (checkCashMakesTrustLine && !psb.exists(trustLineKey)) + if (checkCashMakesTrustLine && !psb.exists(*trustLineKey)) { // 1. Can the check casher meet the reserve for the trust // line? @@ -440,7 +437,7 @@ CashCheck::doApply() destLow, // is dest low? issuer, // source account_, // destination - trustLineKey.key, // ledger index + trustLineKey->key, // ledger index sleDst, // Account to add to false, // authorize account (sleDst->getFlags() & lsfDefaultRipple) == 0, @@ -467,21 +464,13 @@ CashCheck::doApply() // limit on their trust line. So we tweak the trust line limits // before calling flow and then restore the trust line limits // afterwards. - auto const sleTrustLine = psb.peek(trustLineKey); + auto const sleTrustLine = psb.peek(*trustLineKey); if (!sleTrustLine) return tecNO_LINE; SF_AMOUNT const& tweakedLimit = destLow ? sfLowLimit : sfHighLimit; - STAmount const savedLimit = sleTrustLine->at(tweakedLimit); - - // Make sure the tweaked limits are restored when we leave - // scope. - trustlineCb = - [&psb, &trustLineKey, &tweakedLimit, &savedLimit]() { - if (auto const sleTrustLine = psb.peek(trustLineKey)) - sleTrustLine->at(tweakedLimit) = savedLimit; - }; + savedLimit = sleTrustLine->at(tweakedLimit); if (checkCashMakesTrustLine) { @@ -518,7 +507,17 @@ CashCheck::doApply() psb.insert(mptoken); } } - scope_exit fixup(fixupCb); + // Make sure the tweaked limits are restored when we leave + // scope. + scope_exit fixup([&psb, &trustLineKey, destLow, &savedLimit]() { + if (trustLineKey) + { + SF_AMOUNT const& tweakedLimit = + destLow ? sfLowLimit : sfHighLimit; + if (auto const sleTrustLine = psb.peek(*trustLineKey)) + sleTrustLine->at(tweakedLimit) = savedLimit; + } + }); // Let flow() do the heavy lifting on a check for an IOU. auto const result = flow( diff --git a/src/xrpld/app/tx/detail/CreateCheck.cpp b/src/xrpld/app/tx/detail/CreateCheck.cpp index 936dbefd59b..4cb7c138f49 100644 --- a/src/xrpld/app/tx/detail/CreateCheck.cpp +++ b/src/xrpld/app/tx/detail/CreateCheck.cpp @@ -18,6 +18,7 @@ //============================================================================== #include +#include #include #include #include @@ -180,6 +181,15 @@ CreateCheck::preclaim(PreclaimContext const& ctx) JLOG(ctx.j.warn()) << "Creating a check that has already expired."; return tecEXPIRED; } + + if (auto const ter = isMPTTxAllowed( + ctx.view, + ttCHECK_CREATE, + ctx.tx[sfSendMax].asset(), + ctx.tx[sfAccount]); + ter != tesSUCCESS) + return ter; + return tesSUCCESS; } diff --git a/src/xrpld/app/tx/detail/CreateOffer.cpp b/src/xrpld/app/tx/detail/CreateOffer.cpp index 32cbdcd0e26..b7a91ef0cfe 100644 --- a/src/xrpld/app/tx/detail/CreateOffer.cpp +++ b/src/xrpld/app/tx/detail/CreateOffer.cpp @@ -18,6 +18,7 @@ //============================================================================== #include +#include #include #include #include @@ -161,6 +162,15 @@ CreateOffer::preclaim(PreclaimContext const& ctx) return tecFROZEN; } + if (auto const ter = + isMPTTxAllowed(ctx.view, ttOFFER_CREATE, saTakerPays.asset(), id); + ter != tesSUCCESS) + return ter; + if (auto const ter = + isMPTTxAllowed(ctx.view, ttOFFER_CREATE, saTakerGets.asset(), id); + ter != tesSUCCESS) + return ter; + if (accountFunds( ctx.view, id, diff --git a/src/xrpld/app/tx/detail/Payment.cpp b/src/xrpld/app/tx/detail/Payment.cpp index c112454f9ea..53214d19a62 100644 --- a/src/xrpld/app/tx/detail/Payment.cpp +++ b/src/xrpld/app/tx/detail/Payment.cpp @@ -56,7 +56,7 @@ Payment::preflight(PreflightContext const& ctx) ctx.tx[sfAmount].holds() && (ctx.tx.isFieldPresent(sfSendMax) || ctx.tx.isFieldPresent(sfDeliverMin) || ctx.tx.isFieldPresent(sfPaths))) - return temMALFORMED; + return temDISABLED; if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) return ret;