diff --git a/include/xrpl/protocol/STEitherAmount.h b/include/xrpl/protocol/STEitherAmount.h index 81a2d6106bd..c18c806f1e5 100644 --- a/include/xrpl/protocol/STEitherAmount.h +++ b/include/xrpl/protocol/STEitherAmount.h @@ -269,6 +269,18 @@ isMPT(T const& amount) return false; } +template + requires( + std::is_same_v || + std::is_same_v>) bool +isMPT(T const& amount) +{ + if constexpr (std::is_same_v) + return amount.isMPT(); + else + return amount && amount->isMPT(); +} + template bool isIssue(T const& amount) diff --git a/include/xrpl/protocol/TER.h b/include/xrpl/protocol/TER.h index d0f9275d69c..6d666a3fb55 100644 --- a/include/xrpl/protocol/TER.h +++ b/include/xrpl/protocol/TER.h @@ -140,7 +140,8 @@ enum TEMcodes : TERUnderlyingType { temARRAY_EMPTY, temARRAY_TOO_LARGE, - temMPT_NOT_SUPPORTED + temMPT_NOT_SUPPORTED, + temMPT_INVALID_USAGE }; //------------------------------------------------------------------------------ diff --git a/src/libxrpl/protocol/STObject.cpp b/src/libxrpl/protocol/STObject.cpp index 8be922ae6fc..c84e5b7bf57 100644 --- a/src/libxrpl/protocol/STObject.cpp +++ b/src/libxrpl/protocol/STObject.cpp @@ -169,7 +169,12 @@ STObject::applyTemplate(const SOTemplate& type) { if (auto const v = dynamic_cast(&iter->get()); v && v->isMPT()) - throwFieldErr(e.sField().fieldName, "doesn't support MPT"); + { + if (auto const& rules = getCurrentTransactionRules(); + rules && rules->enabled(featureMPTokensV1)) + throwFieldErr( + e.sField().fieldName, "doesn't support MPT"); + } } v.emplace_back(std::move(*iter)); v_.erase(iter); diff --git a/src/libxrpl/protocol/STTx.cpp b/src/libxrpl/protocol/STTx.cpp index 63cbfcfdd3b..29ffc25c1d8 100644 --- a/src/libxrpl/protocol/STTx.cpp +++ b/src/libxrpl/protocol/STTx.cpp @@ -543,9 +543,16 @@ isAccountAndMPTFieldOkay(STObject const& st) auto t = dynamic_cast(st.peekAtPIndex(i)); if (t && t->isDefault()) return false; - auto amt = dynamic_cast(st.peekAtPIndex(i)); - if (amt && amt->isMPT() && !isMPTAmountAllowed) - return false; + if (st.peekAtIndex(i).getSType() == STI_AMOUNT) + { + auto amt = dynamic_cast(st.peekAtPIndex(i)); + if (amt && amt->isMPT() && !isMPTAmountAllowed) + { + if (auto const& rules = getCurrentTransactionRules(); + rules && rules->enabled(featureMPTokensV1)) + return false; + } + } } return true; diff --git a/src/libxrpl/protocol/TER.cpp b/src/libxrpl/protocol/TER.cpp index cd0dac413e7..38842c34758 100644 --- a/src/libxrpl/protocol/TER.cpp +++ b/src/libxrpl/protocol/TER.cpp @@ -190,6 +190,7 @@ transResults() MAKE_ERROR(temINVALID, "The transaction is ill-formed."), MAKE_ERROR(temINVALID_FLAG, "The transaction has an invalid flag."), MAKE_ERROR(temMPT_NOT_SUPPORTED, "MPT is not supported."), + MAKE_ERROR(temMPT_INVALID_USAGE, "Invalid MPT usage."), MAKE_ERROR(temREDUNDANT, "The transaction is redundant."), MAKE_ERROR(temRIPPLE_EMPTY, "PathSet with no paths."), MAKE_ERROR(temUNCERTAIN, "In process of determining result. Never returned."), diff --git a/src/test/app/MPToken_test.cpp b/src/test/app/MPToken_test.cpp index 64413d1c3f4..f49833cf1dd 100644 --- a/src/test/app/MPToken_test.cpp +++ b/src/test/app/MPToken_test.cpp @@ -861,7 +861,7 @@ class MPToken_test : public beast::unit_test::suite mptAlice.create(); - env(offer(alice, mptAlice.mpt(100), XRP(100)), ter(temMALFORMED)); + env(offer(alice, mptAlice.mpt(100), XRP(100)), ter(temMPT_NOT_SUPPORTED)); env.close(); BEAST_EXPECT(expectOffers(env, alice, 0)); diff --git a/src/xrpld/app/tx/detail/AMMBid.cpp b/src/xrpld/app/tx/detail/AMMBid.cpp index 3f37da60d7c..308f140791e 100644 --- a/src/xrpld/app/tx/detail/AMMBid.cpp +++ b/src/xrpld/app/tx/detail/AMMBid.cpp @@ -40,6 +40,10 @@ AMMBid::preflight(PreflightContext const& ctx) if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) return ret; + if (ctx.rules.enabled(featureMPTokensV1) && + (isMPT(ctx.tx[~sfBidMin]) || isMPT(ctx.tx[~sfBidMax]))) + return temMPT_INVALID_USAGE; + if (ctx.tx.getFlags() & tfUniversalMask) { JLOG(ctx.j.debug()) << "AMM Bid: invalid flags."; diff --git a/src/xrpld/app/tx/detail/AMMCreate.cpp b/src/xrpld/app/tx/detail/AMMCreate.cpp index 17af2f880e3..46dd9e583ab 100644 --- a/src/xrpld/app/tx/detail/AMMCreate.cpp +++ b/src/xrpld/app/tx/detail/AMMCreate.cpp @@ -41,6 +41,10 @@ AMMCreate::preflight(PreflightContext const& ctx) if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) return ret; + if (ctx.rules.enabled(featureMPTokensV1) && + (isMPT(ctx.tx[sfAmount]) || isMPT(ctx.tx[sfAmount2]))) + return temMPT_NOT_SUPPORTED; + if (ctx.tx.getFlags() & tfUniversalMask) { JLOG(ctx.j.debug()) << "AMM Instance: invalid flags."; diff --git a/src/xrpld/app/tx/detail/AMMDeposit.cpp b/src/xrpld/app/tx/detail/AMMDeposit.cpp index 24320551e8c..7f9b0c093e7 100644 --- a/src/xrpld/app/tx/detail/AMMDeposit.cpp +++ b/src/xrpld/app/tx/detail/AMMDeposit.cpp @@ -41,6 +41,14 @@ AMMDeposit::preflight(PreflightContext const& ctx) if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) return ret; + if (ctx.rules.enabled(featureMPTokensV1)) + { + if (isMPT(ctx.tx[~sfAmount]) || isMPT(ctx.tx[~sfAmount2])) + return temMPT_NOT_SUPPORTED; + if (isMPT(ctx.tx[~sfEPrice]) || isMPT(ctx.tx[~sfLPTokenOut])) + return temMPT_INVALID_USAGE; + } + auto const flags = ctx.tx.getFlags(); if (flags & tfDepositMask) { diff --git a/src/xrpld/app/tx/detail/AMMWithdraw.cpp b/src/xrpld/app/tx/detail/AMMWithdraw.cpp index ff5cf203c12..afc17690219 100644 --- a/src/xrpld/app/tx/detail/AMMWithdraw.cpp +++ b/src/xrpld/app/tx/detail/AMMWithdraw.cpp @@ -41,6 +41,14 @@ AMMWithdraw::preflight(PreflightContext const& ctx) if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) return ret; + if (ctx.rules.enabled(featureMPTokensV1)) + { + if (isMPT(ctx.tx[~sfAmount]) || isMPT(ctx.tx[~sfAmount2])) + return temMPT_NOT_SUPPORTED; + if (isMPT(ctx.tx[~sfEPrice]) || isMPT(ctx.tx[~sfLPTokenIn])) + return temMPT_INVALID_USAGE; + } + auto const flags = ctx.tx.getFlags(); if (flags & tfWithdrawMask) { diff --git a/src/xrpld/app/tx/detail/CashCheck.cpp b/src/xrpld/app/tx/detail/CashCheck.cpp index 7118ae1d1c3..4e537432053 100644 --- a/src/xrpld/app/tx/detail/CashCheck.cpp +++ b/src/xrpld/app/tx/detail/CashCheck.cpp @@ -42,6 +42,10 @@ CashCheck::preflight(PreflightContext const& ctx) if (!isTesSuccess(ret)) return ret; + if (ctx.rules.enabled(featureMPTokensV1) && + (isMPT(ctx.tx[~sfAmount]) || isMPT(ctx.tx[~sfDeliverMin]))) + return temMPT_NOT_SUPPORTED; + if (ctx.tx.getFlags() & tfUniversalMask) { // There are no flags (other than universal) for CashCheck yet. diff --git a/src/xrpld/app/tx/detail/Change.cpp b/src/xrpld/app/tx/detail/Change.cpp index 3c281cc3580..56d005f7429 100644 --- a/src/xrpld/app/tx/detail/Change.cpp +++ b/src/xrpld/app/tx/detail/Change.cpp @@ -38,6 +38,11 @@ Change::preflight(PreflightContext const& ctx) if (!isTesSuccess(ret)) return ret; + if (ctx.rules.enabled(featureMPTokensV1) && + (isMPT(ctx.tx[~sfBaseFeeDrops]) || isMPT(ctx.tx[~sfReserveBaseDrops]) || + isMPT(ctx.tx[~sfReserveIncrementDrops]))) + return temMPT_INVALID_USAGE; + auto account = ctx.tx.getAccountID(sfAccount); if (account != beast::zero) { diff --git a/src/xrpld/app/tx/detail/CreateCheck.cpp b/src/xrpld/app/tx/detail/CreateCheck.cpp index c9557209404..46e38ee2976 100644 --- a/src/xrpld/app/tx/detail/CreateCheck.cpp +++ b/src/xrpld/app/tx/detail/CreateCheck.cpp @@ -38,6 +38,9 @@ CreateCheck::preflight(PreflightContext const& ctx) if (!isTesSuccess(ret)) return ret; + if (ctx.rules.enabled(featureMPTokensV1) && isMPT(ctx.tx[sfSendMax])) + return temMPT_NOT_SUPPORTED; + if (ctx.tx.getFlags() & tfUniversalMask) { // There are no flags (other than universal) for CreateCheck yet. diff --git a/src/xrpld/app/tx/detail/CreateOffer.cpp b/src/xrpld/app/tx/detail/CreateOffer.cpp index ed4c9f44fef..5c5ed56dbf1 100644 --- a/src/xrpld/app/tx/detail/CreateOffer.cpp +++ b/src/xrpld/app/tx/detail/CreateOffer.cpp @@ -45,6 +45,10 @@ CreateOffer::preflight(PreflightContext const& ctx) if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) return ret; + if (ctx.rules.enabled(featureMPTokensV1) && + (isMPT(ctx.tx[sfTakerPays]) || isMPT(ctx.tx[sfTakerGets]))) + return temMPT_NOT_SUPPORTED; + auto& tx = ctx.tx; auto& j = ctx.j; diff --git a/src/xrpld/app/tx/detail/NFTokenAcceptOffer.cpp b/src/xrpld/app/tx/detail/NFTokenAcceptOffer.cpp index 940c3f0df6e..782d8d5a23b 100644 --- a/src/xrpld/app/tx/detail/NFTokenAcceptOffer.cpp +++ b/src/xrpld/app/tx/detail/NFTokenAcceptOffer.cpp @@ -36,6 +36,10 @@ NFTokenAcceptOffer::preflight(PreflightContext const& ctx) if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) return ret; + if (ctx.rules.enabled(featureMPTokensV1) && + isMPT(ctx.tx[~sfNFTokenBrokerFee])) + return temMPT_INVALID_USAGE; + if (ctx.tx.getFlags() & tfNFTokenAcceptOfferMask) return temINVALID_FLAG; diff --git a/src/xrpld/app/tx/detail/NFTokenCreateOffer.cpp b/src/xrpld/app/tx/detail/NFTokenCreateOffer.cpp index c132df1ffda..0f42fadb316 100644 --- a/src/xrpld/app/tx/detail/NFTokenCreateOffer.cpp +++ b/src/xrpld/app/tx/detail/NFTokenCreateOffer.cpp @@ -36,6 +36,9 @@ NFTokenCreateOffer::preflight(PreflightContext const& ctx) if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) return ret; + if (ctx.rules.enabled(featureMPTokensV1) && isMPT(ctx.tx[sfAmount])) + return temMPT_NOT_SUPPORTED; + auto const txFlags = ctx.tx.getFlags(); if (txFlags & tfNFTokenCreateOfferMask) diff --git a/src/xrpld/app/tx/detail/NFTokenMint.cpp b/src/xrpld/app/tx/detail/NFTokenMint.cpp index 06ff1932f3f..30834df70f0 100644 --- a/src/xrpld/app/tx/detail/NFTokenMint.cpp +++ b/src/xrpld/app/tx/detail/NFTokenMint.cpp @@ -53,6 +53,9 @@ NFTokenMint::preflight(PreflightContext const& ctx) if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) return ret; + if (ctx.rules.enabled(featureMPTokensV1) && isMPT(ctx.tx[~sfAmount])) + return temMPT_NOT_SUPPORTED; + // Prior to fixRemoveNFTokenAutoTrustLine, transfer of an NFToken between // accounts allowed a TrustLine to be added to the issuer of that token // without explicit permission from that issuer. This was enabled by diff --git a/src/xrpld/app/tx/detail/SetTrust.cpp b/src/xrpld/app/tx/detail/SetTrust.cpp index dd55e2c125a..173ed35003d 100644 --- a/src/xrpld/app/tx/detail/SetTrust.cpp +++ b/src/xrpld/app/tx/detail/SetTrust.cpp @@ -34,6 +34,9 @@ SetTrust::preflight(PreflightContext const& ctx) if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) return ret; + if (ctx.rules.enabled(featureMPTokensV1) && isMPT(ctx.tx[~sfLimitAmount])) + return temMPT_INVALID_USAGE; + auto& tx = ctx.tx; auto& j = ctx.j; diff --git a/src/xrpld/app/tx/detail/XChainBridge.cpp b/src/xrpld/app/tx/detail/XChainBridge.cpp index 23e9def0716..0e0ab3079aa 100644 --- a/src/xrpld/app/tx/detail/XChainBridge.cpp +++ b/src/xrpld/app/tx/detail/XChainBridge.cpp @@ -1216,6 +1216,10 @@ attestationPreflight(PreflightContext const& ctx) if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) return ret; + if (ctx.rules.enabled(featureMPTokensV1) && + (isMPT(ctx.tx[sfAmount]) || isMPT(ctx.tx[~sfSignatureReward]))) + return temMPT_NOT_SUPPORTED; + if (ctx.tx.getFlags() & tfUniversalMask) return temINVALID_FLAG; @@ -1383,6 +1387,11 @@ XChainCreateBridge::preflight(PreflightContext const& ctx) if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) return ret; + if (ctx.rules.enabled(featureMPTokensV1) && + (isMPT(ctx.tx[sfSignatureReward]) || + isMPT(ctx.tx[~sfMinAccountCreateAmount]))) + return temMPT_NOT_SUPPORTED; + if (ctx.tx.getFlags() & tfUniversalMask) return temINVALID_FLAG; @@ -1564,6 +1573,11 @@ BridgeModify::preflight(PreflightContext const& ctx) if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) return ret; + if (ctx.rules.enabled(featureMPTokensV1) && + (isMPT(ctx.tx[~sfSignatureReward]) || + isMPT(ctx.tx[~sfMinAccountCreateAmount]))) + return temMPT_NOT_SUPPORTED; + if (ctx.tx.getFlags() & tfBridgeModifyMask) return temINVALID_FLAG; @@ -1674,6 +1688,9 @@ XChainClaim::preflight(PreflightContext const& ctx) if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) return ret; + if (ctx.rules.enabled(featureMPTokensV1) && isMPT(ctx.tx[sfAmount])) + return temMPT_NOT_SUPPORTED; + if (ctx.tx.getFlags() & tfUniversalMask) return temINVALID_FLAG; @@ -1912,6 +1929,9 @@ XChainCommit::preflight(PreflightContext const& ctx) if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) return ret; + if (ctx.rules.enabled(featureMPTokensV1) && isMPT(ctx.tx[sfAmount])) + return temMPT_NOT_SUPPORTED; + if (ctx.tx.getFlags() & tfUniversalMask) return temINVALID_FLAG; @@ -2028,6 +2048,10 @@ XChainCreateClaimID::preflight(PreflightContext const& ctx) if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) return ret; + if (ctx.rules.enabled(featureMPTokensV1) && + isMPT(ctx.tx[sfSignatureReward])) + return temMPT_NOT_SUPPORTED; + if (ctx.tx.getFlags() & tfUniversalMask) return temINVALID_FLAG; @@ -2183,6 +2207,10 @@ XChainCreateAccountCommit::preflight(PreflightContext const& ctx) if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) return ret; + if (ctx.rules.enabled(featureMPTokensV1) && + (isMPT(ctx.tx[sfAmount]) || isMPT(ctx.tx[sfSignatureReward]))) + return temMPT_NOT_SUPPORTED; + if (ctx.tx.getFlags() & tfUniversalMask) return temINVALID_FLAG;