diff --git a/src/test/app/MPToken_test.cpp b/src/test/app/MPToken_test.cpp index 0310974aaa9..376020f7628 100644 --- a/src/test/app/MPToken_test.cpp +++ b/src/test/app/MPToken_test.cpp @@ -3127,14 +3127,26 @@ 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 = 10'000}); amm::ammClawback( 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/AMMDeposit.cpp b/src/xrpld/app/tx/detail/AMMDeposit.cpp index 886144ff402..4e543396d66 100644 --- a/src/xrpld/app/tx/detail/AMMDeposit.cpp +++ b/src/xrpld/app/tx/detail/AMMDeposit.cpp @@ -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; diff --git a/src/xrpld/app/tx/detail/AMMWithdraw.cpp b/src/xrpld/app/tx/detail/AMMWithdraw.cpp index f280c29b437..25c58825748 100644 --- a/src/xrpld/app/tx/detail/AMMWithdraw.cpp +++ b/src/xrpld/app/tx/detail/AMMWithdraw.cpp @@ -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, " @@ -639,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, @@ -663,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;