diff --git a/include/xrpl/protocol/Asset.h b/include/xrpl/protocol/Asset.h index 173b4e9f75c..cf4007205ba 100644 --- a/include/xrpl/protocol/Asset.h +++ b/include/xrpl/protocol/Asset.h @@ -27,6 +27,21 @@ namespace ripple { +struct XRPAmountType +{ + using amount_type = XRPAmount; +}; + +struct IOUAmountType +{ + using amount_type = IOUAmount; +}; + +struct MPTAmountType +{ + using amount_type = MPTAmount; +}; + /* Asset is an abstraction of three different issue types: XRP, IOU, MPT. * For historical reasons, two issue types XRP and IOU are wrapped in Issue * type. Many functions and classes there were first written for Issue @@ -92,14 +107,11 @@ class Asset return holds() && get().native(); } - friend constexpr bool - operator==(Asset const& lhs, Asset const& rhs); - - friend constexpr bool - operator!=(Asset const& lhs, Asset const& rhs); + std::variant + getAmountType() const; friend constexpr bool - operator<(Asset const& lhs, Asset const& rhs); + operator==(Asset const& lhs, Asset const& rhs); friend constexpr std::weak_ordering operator<=>(Asset const& lhs, Asset const& rhs); @@ -160,27 +172,6 @@ operator==(Asset const& lhs, Asset const& rhs) rhs.issue_); } -constexpr bool -operator!=(Asset const& lhs, Asset const& rhs) -{ - return !(lhs == rhs); -} - -constexpr bool -operator<(Asset const& lhs, Asset const& rhs) -{ - return std::visit( - [&]( - TLhs const& issLhs, TRhs const& issRhs) { - if constexpr (std::is_same_v) - return issLhs < issRhs; - else - return false; - }, - lhs.issue_, - rhs.issue_); -} - constexpr bool operator==(Currency const& lhs, Asset const& rhs) { diff --git a/include/xrpl/protocol/MPTIssue.h b/include/xrpl/protocol/MPTIssue.h index 6b20d6d586b..13f03283398 100644 --- a/include/xrpl/protocol/MPTIssue.h +++ b/include/xrpl/protocol/MPTIssue.h @@ -53,7 +53,7 @@ class MPTIssue void setJson(Json::Value& jv) const; - auto + constexpr std::weak_ordering operator<=>(MPTIssue const&) const = default; bool @@ -61,31 +61,8 @@ class MPTIssue { return false; } - - friend constexpr std::weak_ordering - operator<=>(MPTIssue const& lhs, MPTIssue const& rhs); }; -constexpr bool -operator==(MPTIssue const& lhs, MPTIssue const& rhs) -{ - return lhs.mptID_ == rhs.mptID_; -} - -constexpr bool -operator!=(MPTIssue const& lhs, MPTIssue const& rhs) -{ - return !(lhs == rhs); -} - -constexpr std::weak_ordering -operator<=>(MPTIssue const& lhs, MPTIssue const& rhs) -{ - if (auto const c{lhs.mptID_ <=> rhs.mptID_}; c != 0) - return c; - return lhs.mptID_ <=> rhs.mptID_; -} - /** MPT is a non-native token. */ inline bool diff --git a/include/xrpl/protocol/detail/transactions.macro b/include/xrpl/protocol/detail/transactions.macro index aa1100f8f01..9bf9ac683f5 100644 --- a/include/xrpl/protocol/detail/transactions.macro +++ b/include/xrpl/protocol/detail/transactions.macro @@ -236,8 +236,8 @@ TRANSACTION(ttCLAWBACK, 30, Clawback, ({ /** This transaction claws back tokens from an AMM pool. */ TRANSACTION(ttAMM_CLAWBACK, 31, AMMClawback, ({ {sfHolder, soeREQUIRED}, - {sfAsset, soeREQUIRED}, - {sfAsset2, soeREQUIRED}, + {sfAsset, soeREQUIRED, soeMPTSupported}, + {sfAsset2, soeREQUIRED, soeMPTSupported}, {sfAmount, soeOPTIONAL, soeMPTSupported}, })) @@ -250,8 +250,8 @@ TRANSACTION(ttAMM_CREATE, 35, AMMCreate, ({ /** This transaction type deposits into an AMM instance */ TRANSACTION(ttAMM_DEPOSIT, 36, AMMDeposit, ({ - {sfAsset, soeREQUIRED}, - {sfAsset2, soeREQUIRED}, + {sfAsset, soeREQUIRED, soeMPTSupported}, + {sfAsset2, soeREQUIRED, soeMPTSupported}, {sfAmount, soeOPTIONAL, soeMPTSupported}, {sfAmount2, soeOPTIONAL, soeMPTSupported}, {sfEPrice, soeOPTIONAL}, @@ -261,8 +261,8 @@ TRANSACTION(ttAMM_DEPOSIT, 36, AMMDeposit, ({ /** This transaction type withdraws from an AMM instance */ TRANSACTION(ttAMM_WITHDRAW, 37, AMMWithdraw, ({ - {sfAsset, soeREQUIRED}, - {sfAsset2, soeREQUIRED}, + {sfAsset, soeREQUIRED, soeMPTSupported}, + {sfAsset2, soeREQUIRED, soeMPTSupported}, {sfAmount, soeOPTIONAL, soeMPTSupported}, {sfAmount2, soeOPTIONAL, soeMPTSupported}, {sfEPrice, soeOPTIONAL}, @@ -271,15 +271,15 @@ TRANSACTION(ttAMM_WITHDRAW, 37, AMMWithdraw, ({ /** This transaction type votes for the trading fee */ TRANSACTION(ttAMM_VOTE, 38, AMMVote, ({ - {sfAsset, soeREQUIRED}, - {sfAsset2, soeREQUIRED}, + {sfAsset, soeREQUIRED, soeMPTSupported}, + {sfAsset2, soeREQUIRED, soeMPTSupported}, {sfTradingFee, soeREQUIRED}, })) /** This transaction type bids for the auction slot */ TRANSACTION(ttAMM_BID, 39, AMMBid, ({ - {sfAsset, soeREQUIRED}, - {sfAsset2, soeREQUIRED}, + {sfAsset, soeREQUIRED, soeMPTSupported}, + {sfAsset2, soeREQUIRED, soeMPTSupported}, {sfBidMin, soeOPTIONAL}, {sfBidMax, soeOPTIONAL}, {sfAuthAccounts, soeOPTIONAL}, @@ -287,8 +287,8 @@ TRANSACTION(ttAMM_BID, 39, AMMBid, ({ /** This transaction type deletes AMM in the empty state */ TRANSACTION(ttAMM_DELETE, 40, AMMDelete, ({ - {sfAsset, soeREQUIRED}, - {sfAsset2, soeREQUIRED}, + {sfAsset, soeREQUIRED, soeMPTSupported}, + {sfAsset2, soeREQUIRED, soeMPTSupported}, })) /** This transactions creates a crosschain sequence number */ diff --git a/src/libxrpl/protocol/Asset.cpp b/src/libxrpl/protocol/Asset.cpp index 4162eb70c26..58b1ae59bf5 100644 --- a/src/libxrpl/protocol/Asset.cpp +++ b/src/libxrpl/protocol/Asset.cpp @@ -43,6 +43,19 @@ Asset::setJson(Json::Value& jv) const std::visit([&](auto&& issue) { issue.setJson(jv); }, issue_); } +std::variant +Asset::getAmountType() const +{ + static XRPAmount* xrp = nullptr; + static IOUAmount* iou = nullptr; + static MPTAmount* mpt = nullptr; + if (holds()) + return mpt; + if (native()) + return xrp; + return iou; +} + std::string to_string(Asset const& asset) { diff --git a/src/libxrpl/protocol/MPTIssue.cpp b/src/libxrpl/protocol/MPTIssue.cpp index 14f2f5fe796..d1a7a09e533 100644 --- a/src/libxrpl/protocol/MPTIssue.cpp +++ b/src/libxrpl/protocol/MPTIssue.cpp @@ -18,6 +18,7 @@ //============================================================================== #include +#include #include #include diff --git a/src/test/app/MPToken_test.cpp b/src/test/app/MPToken_test.cpp index c004c0097f2..376020f7628 100644 --- a/src/test/app/MPToken_test.cpp +++ b/src/test/app/MPToken_test.cpp @@ -1544,9 +1544,6 @@ class MPToken_test : public beast::unit_test::suite jrr = env.rpc("json", "sign", to_string(jv1)); BEAST_EXPECT(jrr[jss::result][jss::error] == "invalidParams"); }; - auto toSFieldRef = [](SField const& field) { - return std::ref(field); - }; auto setMPTFields = [&](SField const& field, Json::Value& jv, bool withAmount = true) { @@ -3130,16 +3127,28 @@ class MPToken_test : public beast::unit_test::suite auto const USD = gw["USD"]; Env env(*this, features); fund(env, gw, {alice}, XRP(1'000), {USD(1'000)}); - MPTTester mpt(env, gw, {.holders = {alice}, .fund = false}); - mpt.create( - {.ownerCount = 1, - .holderCount = 0, - .flags = tfMPTCanTransfer | tfMPTCanTrade}); + MPTTester mpt(env, gw, {.fund = false}); + mpt.create({.flags = tfMPTCanTransfer | tfMPTCanTrade}); auto const MPT = mpt["MPT"]; + AMM amm(env, gw, MPT(100), XRP(100)); + amm.deposit(DepositArg{.account = alice, .asset1In = XRP(10)}); + amm::ammClawback( + gw, alice, MPTIssue(mpt.issuanceID()), xrpIssue(), MPT(10)); + } + + { + Account const gw{"gw"}; + Account const alice{"alice"}; + auto const USD = gw["USD"]; + Env env(*this, features); + fund(env, gw, {alice}, XRP(1'000), {USD(1'000)}); + MPTTester mpt(env, gw, {.fund = false}); + mpt.create({.flags = tfMPTCanTransfer | tfMPTCanTrade}); mpt.authorize({.account = alice}); mpt.pay(gw, alice, 1'000); + auto const MPT = mpt["MPT"]; AMM amm(env, gw, MPT(100), XRP(100)); - amm.deposit(DepositArg{.account = alice, .tokens = 100}); + amm.deposit(DepositArg{.account = alice, .tokens = 10'000}); amm::ammClawback( gw, alice, MPTIssue(mpt.issuanceID()), xrpIssue(), MPT(10)); } diff --git a/src/xrpld/app/paths/Flow.cpp b/src/xrpld/app/paths/Flow.cpp index 5cae4ab76eb..d328cd613b8 100644 --- a/src/xrpld/app/paths/Flow.cpp +++ b/src/xrpld/app/paths/Flow.cpp @@ -130,19 +130,6 @@ flow( } } - using Var = - std::variant; - auto getTypedAmt = [&](Asset const& iss) -> Var { - static auto xrp = XRPAmount{}; - static auto mpt = MPTAmount{}; - static auto iou = IOUAmount{}; - if (isXRP(iss)) - return &xrp; - if (iss.holds()) - return &mpt; - return &iou; - }; - // The src account may send either xrp,iou,mpt. The dst account may receive // either xrp,iou,mpt. Since XRP, IOU, and MPT amounts are represented by // different types, use templates to tell `flow` about the amount types. @@ -165,8 +152,8 @@ flow( ammContext, flowDebugInfo)); }, - getTypedAmt(srcAsset), - getTypedAmt(dstAsset)); + srcAsset.getAmountType(), + dstAsset.getAmountType()); } } // namespace ripple diff --git a/src/xrpld/app/paths/detail/BookStep.cpp b/src/xrpld/app/paths/detail/BookStep.cpp index 0e77c94acb9..054c2b4b4d9 100644 --- a/src/xrpld/app/paths/detail/BookStep.cpp +++ b/src/xrpld/app/paths/detail/BookStep.cpp @@ -1419,21 +1419,6 @@ equalHelper(Step const& step, ripple::Book const& book) return false; } -static std::variant -getTypedAmt(Asset const& asset) -{ - static auto xrp = XRPAmount{}; - static auto mpt = MPTAmount{}; - static auto iou = IOUAmount{}; - if (asset.holds()) - { - if (isXRP(asset.get().currency)) - return &xrp; - return &iou; - } - return &mpt; -} - bool bookStepEqual(Step const& step, ripple::Book const& book) { @@ -1442,15 +1427,13 @@ bookStepEqual(Step const& step, ripple::Book const& book) UNREACHABLE("ripple::test::bookStepEqual : no XRP to XRP book step"); return false; // no such thing as xrp/xrp book step } - bool ret = false; - std::visit( + return std::visit( [&](TIn*&&, TOut*&&) { - ret = - equalHelper>(step, book); + return equalHelper>( + step, book); }, - getTypedAmt(book.in), - getTypedAmt(book.out)); - return ret; + book.in.getAmountType(), + book.out.getAmountType()); } } // namespace test diff --git a/src/xrpld/app/paths/detail/MPTEndpointStep.cpp b/src/xrpld/app/paths/detail/MPTEndpointStep.cpp index 7b6b1fad1aa..178560bb1d0 100644 --- a/src/xrpld/app/paths/detail/MPTEndpointStep.cpp +++ b/src/xrpld/app/paths/detail/MPTEndpointStep.cpp @@ -22,8 +22,8 @@ #include #include #include -#include #include +#include #include #include diff --git a/src/xrpld/app/tx/detail/AMMBid.cpp b/src/xrpld/app/tx/detail/AMMBid.cpp index bbec5d5ffac..856b2dbb4c5 100644 --- a/src/xrpld/app/tx/detail/AMMBid.cpp +++ b/src/xrpld/app/tx/detail/AMMBid.cpp @@ -51,8 +51,7 @@ AMMBid::preflight(PreflightContext const& ctx) return temINVALID_FLAG; } - if (auto const res = invalidAMMAssetPair( - ctx.tx[sfAsset].get(), ctx.tx[sfAsset2].get())) + if (auto const res = invalidAMMAssetPair(ctx.tx[sfAsset], ctx.tx[sfAsset2])) { JLOG(ctx.j.debug()) << "AMM Bid: Invalid asset pair."; return res; diff --git a/src/xrpld/app/tx/detail/AMMClawback.cpp b/src/xrpld/app/tx/detail/AMMClawback.cpp index 2fede9ff85c..55417e9ae75 100644 --- a/src/xrpld/app/tx/detail/AMMClawback.cpp +++ b/src/xrpld/app/tx/detail/AMMClawback.cpp @@ -61,8 +61,8 @@ AMMClawback::preflight(PreflightContext const& ctx) return temMALFORMED; } - auto const asset = ctx.tx[sfAsset].get(); - auto const asset2 = ctx.tx[sfAsset2].get(); + auto const asset = ctx.tx[sfAsset]; + auto const asset2 = ctx.tx[sfAsset2]; if (isXRP(asset)) return temMALFORMED; @@ -82,7 +82,7 @@ AMMClawback::preflight(PreflightContext const& ctx) return temMALFORMED; } - if (clawAmount && clawAmount->get() != asset) + if (clawAmount && clawAmount->issue() != asset) { JLOG(ctx.j.trace()) << "AMMClawback: Amount's issuer/currency subfield " "does not match Asset field"; @@ -98,8 +98,8 @@ AMMClawback::preflight(PreflightContext const& ctx) TER AMMClawback::preclaim(PreclaimContext const& ctx) { - auto const asset = ctx.tx[sfAsset].get(); - auto const asset2 = ctx.tx[sfAsset2].get(); + auto const asset = ctx.tx[sfAsset]; + auto const asset2 = ctx.tx[sfAsset2]; auto const sleIssuer = ctx.view.read(keylet::account(ctx.tx[sfAccount])); if (!sleIssuer) return terNO_ACCOUNT; // LCOV_EXCL_LINE diff --git a/src/xrpld/app/tx/detail/AMMCreate.cpp b/src/xrpld/app/tx/detail/AMMCreate.cpp index 0fc167e70cd..1b37836cc7d 100644 --- a/src/xrpld/app/tx/detail/AMMCreate.cpp +++ b/src/xrpld/app/tx/detail/AMMCreate.cpp @@ -345,8 +345,14 @@ applyCreate( // Authorize MPT if (amount.holds()) { - auto const mptokenKey = - keylet::mptoken(amount.get().getMptID(), *ammAccount); + auto const& mptIssue = amount.get(); + if (auto const err = requireAuth( + ctx_.view(), mptIssue, account_, MPTAuthType::WeakAuth); + err != tesSUCCESS) + return err; + + auto const& mptID = mptIssue.getMptID(); + auto const mptokenKey = keylet::mptoken(mptID, *ammAccount); auto const ownerNode = sb.dirInsert( keylet::ownerDir(*ammAccount), @@ -358,7 +364,7 @@ applyCreate( auto mptoken = std::make_shared(mptokenKey); (*mptoken)[sfAccount] = *ammAccount; - (*mptoken)[sfMPTokenIssuanceID] = amount.get().getMptID(); + (*mptoken)[sfMPTokenIssuanceID] = mptID; (*mptoken)[sfFlags] = 0; (*mptoken)[sfOwnerNode] = *ownerNode; sb.insert(mptoken); diff --git a/src/xrpld/app/tx/detail/AMMDelete.cpp b/src/xrpld/app/tx/detail/AMMDelete.cpp index e25cfcc494d..4592bbedf2c 100644 --- a/src/xrpld/app/tx/detail/AMMDelete.cpp +++ b/src/xrpld/app/tx/detail/AMMDelete.cpp @@ -77,8 +77,8 @@ AMMDelete::doApply() // as we go on processing transactions. Sandbox sb(&ctx_.view()); - auto const ter = deleteAMMAccount( - sb, ctx_.tx[sfAsset].get(), ctx_.tx[sfAsset2].get(), j_); + auto const ter = + deleteAMMAccount(sb, ctx_.tx[sfAsset], ctx_.tx[sfAsset2], j_); if (ter == tesSUCCESS || ter == tecINCOMPLETE) sb.apply(ctx_.rawView()); diff --git a/src/xrpld/app/tx/detail/AMMDeposit.cpp b/src/xrpld/app/tx/detail/AMMDeposit.cpp index 752680301c8..4e543396d66 100644 --- a/src/xrpld/app/tx/detail/AMMDeposit.cpp +++ b/src/xrpld/app/tx/detail/AMMDeposit.cpp @@ -108,8 +108,8 @@ AMMDeposit::preflight(PreflightContext const& ctx) return temMALFORMED; } - auto const asset = ctx.tx[sfAsset].get(); - auto const asset2 = ctx.tx[sfAsset2].get(); + auto const asset = ctx.tx[sfAsset]; + auto const asset2 = ctx.tx[sfAsset2]; if (auto const res = invalidAMMAssetPair(asset, asset2)) { JLOG(ctx.j.debug()) << "AMM Deposit: invalid asset pair."; @@ -259,7 +259,8 @@ AMMDeposit::preclaim(PreclaimContext const& ctx) // Check if either of the assets is frozen, AMMDeposit is not allowed // if either asset is frozen auto checkAsset = [&](Asset const& asset) -> TER { - if (auto const ter = requireAuth(ctx.view, asset, accountID)) + if (auto const ter = requireAuth( + ctx.view, asset, accountID, MPTAuthType::WeakAuth)) { JLOG(ctx.j.debug()) << "AMM Deposit: account is not authorized, " << asset; @@ -278,10 +279,10 @@ AMMDeposit::preclaim(PreclaimContext const& ctx) return tesSUCCESS; }; - if (auto const ter = checkAsset(ctx.tx[sfAsset].get())) + if (auto const ter = checkAsset(ctx.tx[sfAsset])) return ter; - if (auto const ter = checkAsset(ctx.tx[sfAsset2].get())) + if (auto const ter = checkAsset(ctx.tx[sfAsset2])) return ter; } diff --git a/src/xrpld/app/tx/detail/AMMVote.cpp b/src/xrpld/app/tx/detail/AMMVote.cpp index e20fc10620c..e9c8b15be43 100644 --- a/src/xrpld/app/tx/detail/AMMVote.cpp +++ b/src/xrpld/app/tx/detail/AMMVote.cpp @@ -43,8 +43,7 @@ AMMVote::preflight(PreflightContext const& ctx) if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) return ret; - if (auto const res = invalidAMMAssetPair( - ctx.tx[sfAsset].get(), ctx.tx[sfAsset2].get())) + if (auto const res = invalidAMMAssetPair(ctx.tx[sfAsset], ctx.tx[sfAsset2])) { JLOG(ctx.j.debug()) << "AMM Vote: invalid asset pair."; return res; diff --git a/src/xrpld/app/tx/detail/AMMWithdraw.cpp b/src/xrpld/app/tx/detail/AMMWithdraw.cpp index 03bbcaa44c5..25c58825748 100644 --- a/src/xrpld/app/tx/detail/AMMWithdraw.cpp +++ b/src/xrpld/app/tx/detail/AMMWithdraw.cpp @@ -109,8 +109,8 @@ AMMWithdraw::preflight(PreflightContext const& ctx) return temMALFORMED; } - auto const asset = ctx.tx[sfAsset].get(); - auto const asset2 = ctx.tx[sfAsset2].get(); + auto const asset = ctx.tx[sfAsset]; + auto const asset2 = ctx.tx[sfAsset2]; if (auto const res = invalidAMMAssetPair(asset, asset2)) { JLOG(ctx.j.debug()) << "AMM Withdraw: Invalid asset pair."; @@ -226,8 +226,11 @@ AMMWithdraw::preclaim(PreclaimContext const& ctx) << *amount; return tecAMM_BALANCE; } - if (auto const ter = - requireAuth(ctx.view, amount->asset(), accountID)) + if (auto const ter = requireAuth( + ctx.view, + amount->asset(), + accountID, + MPTAuthType::WeakAuth)) { JLOG(ctx.j.debug()) << "AMM Withdraw: account is not authorized, " @@ -438,12 +441,7 @@ AMMWithdraw::applyGuts(Sandbox& sb) return {result, false}; auto const res = deleteAMMAccountIfEmpty( - sb, - ammSle, - newLPTokenBalance, - ctx_.tx[sfAsset].get(), - ctx_.tx[sfAsset2].get(), - j_); + sb, ammSle, newLPTokenBalance, ctx_.tx[sfAsset], ctx_.tx[sfAsset2], j_); // LCOV_EXCL_START if (!res.second) return {res.first, false}; @@ -644,6 +642,61 @@ AMMWithdraw::withdraw( if (auto const err = sufficientReserve(amountWithdrawActual.issue())) return {err, STAmount{}, STAmount{}, STAmount{}}; + // Create MPToken if doesn't exist + // TODO make a library, AMMCreate, AMMAuthorize use almost identical code + auto createMPToken = [&](Asset const& asset) -> TER { + if (asset.holds()) + { + auto const& mptIssue = asset.get(); + auto const issuanceKey = keylet::mptIssuance(mptIssue.getMptID()); + auto const mptokenKey = keylet::mptoken(issuanceKey.key, account); + if (!view.exists(mptokenKey)) + { + if (auto err = requireAuth( + view, mptIssue, account, MPTAuthType::WeakAuth); + err != tesSUCCESS) + return err; + } + + auto const sleAcct = view.peek(keylet::account(account)); + if (!sleAcct) + return tefINTERNAL; + + std::uint32_t const uOwnerCount = + sleAcct->getFieldU32(sfOwnerCount); + XRPAmount const reserveCreate( + (uOwnerCount < 2) + ? XRPAmount(beast::zero) + : view.fees().accountReserve(uOwnerCount + 1)); + + if (priorBalance < reserveCreate) + return tecINSUFFICIENT_RESERVE; + + auto const ownerNode = view.dirInsert( + keylet::ownerDir(account), + mptokenKey, + describeOwnerDir(account)); + + if (!ownerNode) + return tecDIR_FULL; + + auto mptoken = std::make_shared(mptokenKey); + (*mptoken)[sfAccount] = account; + (*mptoken)[sfMPTokenIssuanceID] = mptIssue.getMptID(); + (*mptoken)[sfFlags] = 0; + (*mptoken)[sfOwnerNode] = *ownerNode; + view.insert(mptoken); + + // Update owner count. + adjustOwnerCount(view, sleAcct, 1, journal); + } + return tesSUCCESS; + }; + + if (auto const res = createMPToken(amountWithdrawActual.asset()); + res != tesSUCCESS) + return {res, STAmount{}, STAmount{}, STAmount{}}; + // Withdraw amountWithdraw auto res = accountSend( view, @@ -668,6 +721,10 @@ AMMWithdraw::withdraw( err != tesSUCCESS) return {err, STAmount{}, STAmount{}, STAmount{}}; + if (auto const res = createMPToken(amountWithdrawActual.asset()); + res != tesSUCCESS) + return {res, STAmount{}, STAmount{}, STAmount{}}; + res = accountSend( view, ammAccount, diff --git a/src/xrpld/ledger/View.h b/src/xrpld/ledger/View.h index dc453397ace..4d0fc7798b5 100644 --- a/src/xrpld/ledger/View.h +++ b/src/xrpld/ledger/View.h @@ -542,25 +542,41 @@ transferXRP( STAmount const& amount, beast::Journal j); +/* Check if MPToken exists: + * - StrongAuth - before checking lsfMPTRequireAuth is set + * - WeakAuth - after checking if lsfMPTRequireAuth is set + */ +enum class MPTAuthType : bool { StrongAuth = true, WeakAuth = false }; + /** Check if the account lacks required authorization. * Return tecNO_AUTH or tecNO_LINE if it does * and tesSUCCESS otherwise. */ [[nodiscard]] TER requireAuth(ReadView const& view, Issue const& issue, AccountID const& account); +/* If StrongAuth then return tecNO_AUTH if MPToken doesn't exist or + * lsfMPTRequireAuth is set and MPToken is not authorized. If WeakAuth then + * return tecNO_AUTH if lsfMPTRequireAuth is set and MPToken doesn't exist or is + * not authorized. + */ [[nodiscard]] TER requireAuth( ReadView const& view, MPTIssue const& mptIssue, - AccountID const& account); + AccountID const& account, + MPTAuthType authType = MPTAuthType::StrongAuth); [[nodiscard]] TER inline requireAuth( ReadView const& view, Asset const& asset, - AccountID const& account) + AccountID const& account, + MPTAuthType authType = MPTAuthType::StrongAuth) { return std::visit( [&](TIss const& issue_) { - return requireAuth(view, issue_, account); + if constexpr (std::is_same_v) + return requireAuth(view, issue_, account); + else + return requireAuth(view, issue_, account, authType); }, asset.value()); } diff --git a/src/xrpld/ledger/detail/View.cpp b/src/xrpld/ledger/detail/View.cpp index e224ffbe380..e968540e209 100644 --- a/src/xrpld/ledger/detail/View.cpp +++ b/src/xrpld/ledger/detail/View.cpp @@ -1867,7 +1867,8 @@ TER requireAuth( ReadView const& view, MPTIssue const& mptIssue, - AccountID const& account) + AccountID const& account, + MPTAuthType authType) { auto const mptID = keylet::mptIssuance(mptIssue.getMptID()); auto const sleIssuance = view.read(mptID); @@ -1885,12 +1886,12 @@ requireAuth( auto const sleToken = view.read(mptokenID); // if account has no MPToken, fail - if (!sleToken) + if (!sleToken && authType == MPTAuthType::StrongAuth) return tecNO_AUTH; // mptoken must be authorized if issuance enabled requireAuth if (sleIssuance->getFieldU32(sfFlags) & lsfMPTRequireAuth && - !(sleToken->getFlags() & lsfMPTAuthorized)) + (!sleToken || (!(sleToken->getFlags() & lsfMPTAuthorized)))) return tecNO_AUTH; return tesSUCCESS;