mirror of
https://github.com/XRPLF/rippled.git
synced 2026-06-03 16:56:48 +00:00
Fix AMMClawback missing MPToken recreation for MPTs
This commit is contained in:
@@ -171,6 +171,14 @@ createMPToken(
|
||||
AccountID const& account,
|
||||
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
|
||||
checkCreateMPT(
|
||||
xrpl::ApplyView& view,
|
||||
|
||||
@@ -812,6 +812,7 @@ checkCreateMPT(
|
||||
xrpl::ApplyView& view,
|
||||
xrpl::MPTIssue const& mptIssue,
|
||||
xrpl::AccountID const& holder,
|
||||
std::uint32_t flags,
|
||||
beast::Journal j)
|
||||
{
|
||||
if (mptIssue.getIssuer() == holder)
|
||||
@@ -821,7 +822,7 @@ checkCreateMPT(
|
||||
auto const mptokenID = keylet::mptoken(mptIssuanceID.key, holder);
|
||||
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))
|
||||
{
|
||||
return err;
|
||||
@@ -836,6 +837,16 @@ checkCreateMPT(
|
||||
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
|
||||
maxMPTAmount(SLE const& sleIssuance)
|
||||
{
|
||||
|
||||
@@ -649,11 +649,21 @@ AMMWithdraw::withdraw(
|
||||
if (mptokenKey && account != asset.getIssuer())
|
||||
{
|
||||
auto const& mptIssue = asset.get<MPTIssue>();
|
||||
std::uint32_t createFlags = 0;
|
||||
if (auto const err = requireAuth(view, mptIssue, account, AuthType::WeakAuth);
|
||||
!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))
|
||||
{
|
||||
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
|
||||
testSingleDepositAndClawback(FeatureBitset features)
|
||||
{
|
||||
@@ -1824,6 +1878,7 @@ class AMMClawbackMPT_test : public beast::unit_test::Suite
|
||||
testAMMClawbackAllSameIssuer(all);
|
||||
testAMMClawbackIssuesEachOther(all);
|
||||
testAssetFrozenOrLocked(all);
|
||||
testClawbackCreatesMissingMPToken(all);
|
||||
testSingleDepositAndClawback(all);
|
||||
testLastHolderLPTokenBalance(all);
|
||||
testLastHolderLPTokenBalance(all - fixAMMv1_3 - fixAMMClawbackRounding);
|
||||
|
||||
Reference in New Issue
Block a user