mirror of
https://github.com/XRPLF/rippled.git
synced 2026-07-23 15:10:34 +00:00
Fix AMMClawback missing MPToken recreation for MPTs
This commit is contained in:
@@ -171,6 +171,14 @@ createMPToken(
|
|||||||
AccountID const& account,
|
AccountID const& account,
|
||||||
std::uint32_t const flags);
|
std::uint32_t const flags);
|
||||||
|
|
||||||
|
TER
|
||||||
|
checkCreateMPT(
|
||||||
|
xrpl::ApplyView& view,
|
||||||
|
xrpl::MPTIssue const& mptIssue,
|
||||||
|
xrpl::AccountID const& holder,
|
||||||
|
std::uint32_t flags,
|
||||||
|
beast::Journal j);
|
||||||
|
|
||||||
TER
|
TER
|
||||||
checkCreateMPT(
|
checkCreateMPT(
|
||||||
xrpl::ApplyView& view,
|
xrpl::ApplyView& view,
|
||||||
|
|||||||
@@ -812,6 +812,7 @@ checkCreateMPT(
|
|||||||
xrpl::ApplyView& view,
|
xrpl::ApplyView& view,
|
||||||
xrpl::MPTIssue const& mptIssue,
|
xrpl::MPTIssue const& mptIssue,
|
||||||
xrpl::AccountID const& holder,
|
xrpl::AccountID const& holder,
|
||||||
|
std::uint32_t flags,
|
||||||
beast::Journal j)
|
beast::Journal j)
|
||||||
{
|
{
|
||||||
if (mptIssue.getIssuer() == holder)
|
if (mptIssue.getIssuer() == holder)
|
||||||
@@ -821,7 +822,7 @@ checkCreateMPT(
|
|||||||
auto const mptokenID = keylet::mptoken(mptIssuanceID.key, holder);
|
auto const mptokenID = keylet::mptoken(mptIssuanceID.key, holder);
|
||||||
if (!view.exists(mptokenID))
|
if (!view.exists(mptokenID))
|
||||||
{
|
{
|
||||||
if (auto const err = createMPToken(view, mptIssue.getMptID(), holder, 0);
|
if (auto const err = createMPToken(view, mptIssue.getMptID(), holder, flags);
|
||||||
!isTesSuccess(err))
|
!isTesSuccess(err))
|
||||||
{
|
{
|
||||||
return err;
|
return err;
|
||||||
@@ -836,6 +837,16 @@ checkCreateMPT(
|
|||||||
return tesSUCCESS;
|
return tesSUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TER
|
||||||
|
checkCreateMPT(
|
||||||
|
xrpl::ApplyView& view,
|
||||||
|
xrpl::MPTIssue const& mptIssue,
|
||||||
|
xrpl::AccountID const& holder,
|
||||||
|
beast::Journal j)
|
||||||
|
{
|
||||||
|
return checkCreateMPT(view, mptIssue, holder, 0, j);
|
||||||
|
}
|
||||||
|
|
||||||
std::int64_t
|
std::int64_t
|
||||||
maxMPTAmount(SLE const& sleIssuance)
|
maxMPTAmount(SLE const& sleIssuance)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -649,11 +649,21 @@ AMMWithdraw::withdraw(
|
|||||||
if (mptokenKey && account != asset.getIssuer())
|
if (mptokenKey && account != asset.getIssuer())
|
||||||
{
|
{
|
||||||
auto const& mptIssue = asset.get<MPTIssue>();
|
auto const& mptIssue = asset.get<MPTIssue>();
|
||||||
|
std::uint32_t createFlags = 0;
|
||||||
if (auto const err = requireAuth(view, mptIssue, account, AuthType::WeakAuth);
|
if (auto const err = requireAuth(view, mptIssue, account, AuthType::WeakAuth);
|
||||||
!isTesSuccess(err))
|
!isTesSuccess(err))
|
||||||
return err;
|
{
|
||||||
|
if (authHandling != AuthHandling::IgnoreAuth || err != tecNO_AUTH)
|
||||||
|
return err;
|
||||||
|
|
||||||
if (auto const err = checkCreateMPT(view, mptIssue, account, journal);
|
// AMMClawback ignores authorization so the issuer can recover
|
||||||
|
// MPT locked in the pool even if the holder deleted their
|
||||||
|
// MPToken. Recreate that MPToken as authorized for the
|
||||||
|
// clawback withdrawal path only.
|
||||||
|
createFlags = lsfMPTAuthorized;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto const err = checkCreateMPT(view, mptIssue, account, createFlags, journal);
|
||||||
!isTesSuccess(err))
|
!isTesSuccess(err))
|
||||||
{
|
{
|
||||||
return err;
|
return err;
|
||||||
|
|||||||
@@ -1335,6 +1335,60 @@ class AMMClawbackMPT_test : public beast::unit_test::Suite
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
testClawbackCreatesMissingMPToken(FeatureBitset features)
|
||||||
|
{
|
||||||
|
testcase("test AMMClawback creates missing MPToken");
|
||||||
|
using namespace jtx;
|
||||||
|
|
||||||
|
auto test = [&](std::optional<std::uint64_t> const clawAmount) {
|
||||||
|
Env env{*this, features};
|
||||||
|
Account const gw{"gateway"};
|
||||||
|
Account const alice{"alice"};
|
||||||
|
env.fund(XRP(1'000'000), gw, alice);
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
MPTTester token(
|
||||||
|
{.env = env,
|
||||||
|
.issuer = gw,
|
||||||
|
.holders = {alice},
|
||||||
|
.pay = 1'000,
|
||||||
|
.flags = tfMPTCanClawback | tfMPTRequireAuth | kMPT_DEX_FLAGS,
|
||||||
|
.authHolder = true});
|
||||||
|
|
||||||
|
AMM ammAlice(env, alice, token(1'000), XRP(1'000));
|
||||||
|
env.close();
|
||||||
|
BEAST_EXPECT(env.balance(alice, token) == token(0));
|
||||||
|
|
||||||
|
// The holder can delete the zero-balance MPToken while still
|
||||||
|
// holding LP tokens. A regular AMMWithdraw remains subject to
|
||||||
|
// RequireAuth and cannot recreate the missing token.
|
||||||
|
token.authorize({.account = alice, .flags = tfMPTUnauthorize});
|
||||||
|
env.close();
|
||||||
|
BEAST_EXPECT(!env.le(keylet::mptoken(token.issuanceID(), alice.id())));
|
||||||
|
ammAlice.withdrawAll(alice, std::nullopt, Ter(tecNO_AUTH));
|
||||||
|
env.close();
|
||||||
|
BEAST_EXPECT(!env.le(keylet::mptoken(token.issuanceID(), alice.id())));
|
||||||
|
|
||||||
|
// AMMClawback ignores authorization and must be able to recreate
|
||||||
|
// the holder MPToken so the issuer can recover MPT from the pool.
|
||||||
|
std::optional<STAmount> amount;
|
||||||
|
if (clawAmount)
|
||||||
|
amount = token(*clawAmount);
|
||||||
|
env(amm::ammClawback(gw, alice, token, XRP, amount));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
auto const sleMpt = env.le(keylet::mptoken(token.issuanceID(), alice.id()));
|
||||||
|
BEAST_EXPECT(sleMpt && sleMpt->isFlag(lsfMPTAuthorized));
|
||||||
|
env.require(Balance(alice, token(0)));
|
||||||
|
|
||||||
|
BEAST_EXPECT(clawAmount ? ammAlice.ammExists() : !ammAlice.ammExists());
|
||||||
|
};
|
||||||
|
|
||||||
|
test(std::nullopt);
|
||||||
|
test(400);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
testSingleDepositAndClawback(FeatureBitset features)
|
testSingleDepositAndClawback(FeatureBitset features)
|
||||||
{
|
{
|
||||||
@@ -1824,6 +1878,7 @@ class AMMClawbackMPT_test : public beast::unit_test::Suite
|
|||||||
testAMMClawbackAllSameIssuer(all);
|
testAMMClawbackAllSameIssuer(all);
|
||||||
testAMMClawbackIssuesEachOther(all);
|
testAMMClawbackIssuesEachOther(all);
|
||||||
testAssetFrozenOrLocked(all);
|
testAssetFrozenOrLocked(all);
|
||||||
|
testClawbackCreatesMissingMPToken(all);
|
||||||
testSingleDepositAndClawback(all);
|
testSingleDepositAndClawback(all);
|
||||||
testLastHolderLPTokenBalance(all);
|
testLastHolderLPTokenBalance(all);
|
||||||
testLastHolderLPTokenBalance(all - fixAMMv1_3 - fixAMMClawbackRounding);
|
testLastHolderLPTokenBalance(all - fixAMMv1_3 - fixAMMClawbackRounding);
|
||||||
|
|||||||
Reference in New Issue
Block a user