From 54a350be79a455fec7b91ccab1d79df0be8bc5fd Mon Sep 17 00:00:00 2001
From: yinyiqian1 <yqian@ripple.com>
Date: Mon, 4 Nov 2024 15:27:57 -0500
Subject: [PATCH] Add AMMClawback Transaction (XLS-0073d) (#5142)

Amendment:
- AMMClawback

New Transactions:
- AMMClawback

Modified Transactions:
- AMMCreate
- AMMDeposit
---
 include/xrpl/protocol/Feature.h               |    2 +-
 include/xrpl/protocol/TxFlags.h               |    4 +
 include/xrpl/protocol/detail/features.macro   |    1 +
 .../xrpl/protocol/detail/transactions.macro   |    8 +
 include/xrpl/protocol/jss.h                   |    1 +
 src/libxrpl/protocol/TER.cpp                  |    2 +-
 src/test/app/AMMClawback_test.cpp             | 1794 +++++++++++++++++
 src/test/app/AMM_test.cpp                     |  351 +++-
 src/test/app/MPToken_test.cpp                 |   11 +
 src/test/jtx/AMM.h                            |    8 +
 src/test/jtx/impl/AMM.cpp                     |   20 +
 src/test/rpc/Status_test.cpp                  |    4 +-
 src/xrpld/app/tx/detail/AMMClawback.cpp       |  290 +++
 src/xrpld/app/tx/detail/AMMClawback.h         |   75 +
 src/xrpld/app/tx/detail/AMMCreate.cpp         |    8 +-
 src/xrpld/app/tx/detail/AMMDeposit.cpp        |   31 +
 src/xrpld/app/tx/detail/AMMWithdraw.cpp       |  271 ++-
 src/xrpld/app/tx/detail/AMMWithdraw.h         |   98 +-
 src/xrpld/app/tx/detail/InvariantCheck.cpp    |    7 +-
 src/xrpld/app/tx/detail/applySteps.cpp        |    1 +
 20 files changed, 2840 insertions(+), 147 deletions(-)
 create mode 100644 src/test/app/AMMClawback_test.cpp
 create mode 100644 src/xrpld/app/tx/detail/AMMClawback.cpp
 create mode 100644 src/xrpld/app/tx/detail/AMMClawback.h

diff --git a/include/xrpl/protocol/Feature.h b/include/xrpl/protocol/Feature.h
index eb975f39ae0..a2510c63000 100644
--- a/include/xrpl/protocol/Feature.h
+++ b/include/xrpl/protocol/Feature.h
@@ -80,7 +80,7 @@ namespace detail {
 // Feature.cpp. Because it's only used to reserve storage, and determine how
 // large to make the FeatureBitset, it MAY be larger. It MUST NOT be less than
 // the actual number of amendments. A LogicError on startup will verify this.
-static constexpr std::size_t numFeatures = 80;
+static constexpr std::size_t numFeatures = 81;
 
 /** Amendments that this server supports and the default voting behavior.
    Whether they are enabled depends on the Rules defined in the validated
diff --git a/include/xrpl/protocol/TxFlags.h b/include/xrpl/protocol/TxFlags.h
index 4894f48a7f9..c293798f7d7 100644
--- a/include/xrpl/protocol/TxFlags.h
+++ b/include/xrpl/protocol/TxFlags.h
@@ -207,6 +207,10 @@ constexpr std::uint32_t tfDepositSubTx =
 constexpr std::uint32_t tfWithdrawMask = ~(tfUniversal | tfWithdrawSubTx);
 constexpr std::uint32_t tfDepositMask = ~(tfUniversal | tfDepositSubTx);
 
+// AMMClawback flags:
+constexpr std::uint32_t tfClawTwoAssets                = 0x00000001;
+constexpr std::uint32_t tfAMMClawbackMask = ~(tfUniversal | tfClawTwoAssets);
+
 // BridgeModify flags:
 constexpr std::uint32_t tfClearAccountCreateAmount     = 0x00010000;
 constexpr std::uint32_t tfBridgeModifyMask = ~(tfUniversal | tfClearAccountCreateAmount);
diff --git a/include/xrpl/protocol/detail/features.macro b/include/xrpl/protocol/detail/features.macro
index 3a8d77e2bab..e5351be11c0 100644
--- a/include/xrpl/protocol/detail/features.macro
+++ b/include/xrpl/protocol/detail/features.macro
@@ -95,6 +95,7 @@ XRPL_FIX    (1513,                       Supported::yes, VoteBehavior::DefaultYe
 XRPL_FEATURE(FlowCross,                  Supported::yes, VoteBehavior::DefaultYes)
 XRPL_FEATURE(Flow,                       Supported::yes, VoteBehavior::DefaultYes)
 XRPL_FEATURE(OwnerPaysFee,               Supported::no,  VoteBehavior::DefaultNo)
+XRPL_FEATURE(AMMClawback,                Supported::yes,  VoteBehavior::DefaultYes)
 
 // The following amendments are obsolete, but must remain supported
 // because they could potentially get enabled.
diff --git a/include/xrpl/protocol/detail/transactions.macro b/include/xrpl/protocol/detail/transactions.macro
index 30e27da4167..a064abbc12b 100644
--- a/include/xrpl/protocol/detail/transactions.macro
+++ b/include/xrpl/protocol/detail/transactions.macro
@@ -227,6 +227,14 @@ TRANSACTION(ttCLAWBACK, 30, Clawback, ({
     {sfHolder, soeOPTIONAL},
 }))
 
+/** This transaction claws back tokens from an AMM pool. */
+TRANSACTION(ttAMM_CLAWBACK, 31, AMMClawback, ({
+    {sfHolder, soeREQUIRED},
+    {sfAsset, soeREQUIRED},
+    {sfAsset2, soeREQUIRED},
+    {sfAmount, soeOPTIONAL},
+}))
+
 /** This transaction type creates an AMM instance */
 TRANSACTION(ttAMM_CREATE, 35, AMMCreate, ({
     {sfAmount, soeREQUIRED},
diff --git a/include/xrpl/protocol/jss.h b/include/xrpl/protocol/jss.h
index bafdde4fbcc..90e5b1c6e47 100644
--- a/include/xrpl/protocol/jss.h
+++ b/include/xrpl/protocol/jss.h
@@ -73,6 +73,7 @@ JSS(Escrow);               // ledger type.
 JSS(Fee);                  // in/out: TransactionSign; field.
 JSS(FeeSettings);          // ledger type.
 JSS(Flags);                // in/out: TransactionSign; field.
+JSS(Holder);               // field.
 JSS(Invalid);              //
 JSS(LastLedgerSequence);   // in: TransactionSign; field
 JSS(LastUpdateTime);       // field.
diff --git a/src/libxrpl/protocol/TER.cpp b/src/libxrpl/protocol/TER.cpp
index 788b3a86152..90809b29981 100644
--- a/src/libxrpl/protocol/TER.cpp
+++ b/src/libxrpl/protocol/TER.cpp
@@ -160,7 +160,7 @@ transResults()
 
         MAKE_ERROR(temMALFORMED,                 "Malformed transaction."),
         MAKE_ERROR(temBAD_AMM_TOKENS,            "Malformed: Invalid LPTokens."),
-        MAKE_ERROR(temBAD_AMOUNT,                "Can only send positive amounts."),
+        MAKE_ERROR(temBAD_AMOUNT,                "Malformed: Bad amount."),
         MAKE_ERROR(temBAD_CURRENCY,              "Malformed: Bad currency."),
         MAKE_ERROR(temBAD_EXPIRATION,            "Malformed: Bad expiration."),
         MAKE_ERROR(temBAD_FEE,                   "Invalid fee, negative or not XRP."),
diff --git a/src/test/app/AMMClawback_test.cpp b/src/test/app/AMMClawback_test.cpp
new file mode 100644
index 00000000000..705a1274073
--- /dev/null
+++ b/src/test/app/AMMClawback_test.cpp
@@ -0,0 +1,1794 @@
+//------------------------------------------------------------------------------
+/*
+  This file is part of rippled: https://github.com/ripple/rippled
+  Copyright (c) 2024 Ripple Labs Inc.
+  Permission to use, copy, modify, and/or distribute this software for any
+  purpose  with  or without fee is hereby granted, provided that the above
+  copyright notice and this permission notice appear in all copies.
+  THE  SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+  WITH  REGARD  TO  THIS  SOFTWARE  INCLUDING  ALL  IMPLIED  WARRANTIES  OF
+  MERCHANTABILITY  AND  FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+  ANY  SPECIAL ,  DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+  WHATSOEVER  RESULTING  FROM  LOSS  OF USE, DATA OR PROFITS, WHETHER IN AN
+  ACTION  OF  CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+//==============================================================================
+#include <test/jtx.h>
+#include <test/jtx/AMM.h>
+#include <test/jtx/AMMTest.h>
+#include <test/jtx/trust.h>
+#include <xrpld/ledger/ApplyViewImpl.h>
+#include <xrpl/basics/random.h>
+#include <xrpl/json/to_string.h>
+#include <xrpl/protocol/Feature.h>
+#include <xrpl/protocol/jss.h>
+#include <initializer_list>
+namespace ripple {
+namespace test {
+class AMMClawback_test : public jtx::AMMTest
+{
+    void
+    testInvalidRequest(FeatureBitset features)
+    {
+        testcase("test invalid request");
+        using namespace jtx;
+
+        // Test if holder does not exist.
+        {
+            Env env(*this, features);
+            Account gw{"gateway"};
+            Account alice{"alice"};
+            env.fund(XRP(100000), gw, alice);
+            env.close();
+
+            // gw sets asfAllowTrustLineClawback.
+            env(fset(gw, asfAllowTrustLineClawback));
+            env.close();
+            env.require(flags(gw, asfAllowTrustLineClawback));
+
+            env.trust(USD(10000), alice);
+            env(pay(gw, alice, gw["USD"](100)));
+
+            AMM amm(env, alice, XRP(100), USD(100));
+            env.close();
+
+            env(amm::ammClawback(
+                    gw, Account("unknown"), USD, XRP, std::nullopt),
+                ter(terNO_ACCOUNT));
+        }
+
+        // Test if asset pair provided does not exist. This should
+        // return terNO_AMM error.
+        {
+            Env env(*this, features);
+            Account gw{"gateway"};
+            Account alice{"alice"};
+            env.fund(XRP(100000), gw, alice);
+            env.close();
+
+            // gw sets asfAllowTrustLineClawback.
+            env(fset(gw, asfAllowTrustLineClawback));
+            env.close();
+            env.require(flags(gw, asfAllowTrustLineClawback));
+
+            // gw issues 100 USD to Alice.
+            auto const USD = gw["USD"];
+            env.trust(USD(10000), alice);
+            env(pay(gw, alice, USD(100)));
+            env.close();
+
+            // Withdraw all the tokens from the AMMAccount.
+            // The AMMAccount will be auto deleted.
+            AMM amm(env, gw, XRP(100), USD(100));
+            amm.withdrawAll(gw);
+            BEAST_EXPECT(!amm.ammExists());
+            env.close();
+
+            // The AMM account does not exist at all now.
+            // It should return terNO_AMM error.
+            env(amm::ammClawback(gw, alice, USD, EUR, std::nullopt),
+                ter(terNO_AMM));
+        }
+
+        // Test if the issuer field and holder field is the same. This should
+        // return temMALFORMED error.
+        {
+            Env env(*this, features);
+            Account gw{"gateway"};
+            Account alice{"alice"};
+            env.fund(XRP(10000), gw, alice);
+            env.close();
+
+            // gw sets asfAllowTrustLineClawback.
+            env(fset(gw, asfAllowTrustLineClawback));
+            env.close();
+            env.require(flags(gw, asfAllowTrustLineClawback));
+
+            // gw issues 100 USD to Alice.
+            auto const USD = gw["USD"];
+            env.trust(USD(1000), alice);
+            env(pay(gw, alice, USD(100)));
+            env.close();
+
+            AMM amm(env, gw, XRP(100), USD(100), ter(tesSUCCESS));
+
+            // Issuer can not clawback from himself.
+            env(amm::ammClawback(gw, gw, USD, XRP, std::nullopt),
+                ter(temMALFORMED));
+
+            // Holder can not clawback from himself.
+            env(amm::ammClawback(alice, alice, USD, XRP, std::nullopt),
+                ter(temMALFORMED));
+        }
+
+        // Test if the Asset field matches the Account field.
+        {
+            Env env(*this, features);
+            Account gw{"gateway"};
+            Account alice{"alice"};
+            env.fund(XRP(10000), gw, alice);
+            env.close();
+
+            // gw sets asfAllowTrustLineClawback.
+            env(fset(gw, asfAllowTrustLineClawback));
+            env.close();
+            env.require(flags(gw, asfAllowTrustLineClawback));
+
+            // gw issues 100 USD to Alice.
+            auto const USD = gw["USD"];
+            env.trust(USD(1000), alice);
+            env(pay(gw, alice, USD(100)));
+            env.close();
+
+            AMM amm(env, gw, XRP(100), USD(100), ter(tesSUCCESS));
+
+            // The Asset's issuer field is alice, while the Account field is gw.
+            // This should return temMALFORMED because they do not match.
+            env(amm::ammClawback(
+                    gw,
+                    alice,
+                    Issue{gw["USD"].currency, alice.id()},
+                    XRP,
+                    std::nullopt),
+                ter(temMALFORMED));
+        }
+
+        // Test if the Amount field matches the Asset field.
+        {
+            Env env(*this, features);
+            Account gw{"gateway"};
+            Account alice{"alice"};
+            env.fund(XRP(10000), gw, alice);
+            env.close();
+
+            // gw sets asfAllowTrustLineClawback.
+            env(fset(gw, asfAllowTrustLineClawback));
+            env.close();
+            env.require(flags(gw, asfAllowTrustLineClawback));
+
+            // gw issues 100 USD to Alice.
+            auto const USD = gw["USD"];
+            env.trust(USD(1000), alice);
+            env(pay(gw, alice, USD(100)));
+            env.close();
+
+            AMM amm(env, gw, XRP(100), USD(100), ter(tesSUCCESS));
+
+            // The Asset's issuer subfield is gw account and Amount's issuer
+            // subfield is alice account. Return temBAD_AMOUNT because
+            // they do not match.
+            env(amm::ammClawback(
+                    gw,
+                    alice,
+                    USD,
+                    XRP,
+                    STAmount{Issue{gw["USD"].currency, alice.id()}, 1}),
+                ter(temBAD_AMOUNT));
+        }
+
+        // Test if the Amount is invalid, which is less than zero.
+        {
+            Env env(*this, features);
+            Account gw{"gateway"};
+            Account alice{"alice"};
+            env.fund(XRP(10000), gw, alice);
+            env.close();
+
+            // gw sets asfAllowTrustLineClawback.
+            env(fset(gw, asfAllowTrustLineClawback));
+            env.close();
+            env.require(flags(gw, asfAllowTrustLineClawback));
+
+            // gw issues 100 USD to Alice.
+            auto const USD = gw["USD"];
+            env.trust(USD(1000), alice);
+            env(pay(gw, alice, USD(100)));
+            env.close();
+
+            AMM amm(env, gw, XRP(100), USD(100), ter(tesSUCCESS));
+
+            // Return temBAD_AMOUNT if the Amount value is less than 0.
+            env(amm::ammClawback(
+                    gw,
+                    alice,
+                    USD,
+                    XRP,
+                    STAmount{Issue{gw["USD"].currency, gw.id()}, -1}),
+                ter(temBAD_AMOUNT));
+
+            // Return temBAD_AMOUNT if the Amount value is 0.
+            env(amm::ammClawback(
+                    gw,
+                    alice,
+                    USD,
+                    XRP,
+                    STAmount{Issue{gw["USD"].currency, gw.id()}, 0}),
+                ter(temBAD_AMOUNT));
+        }
+
+        // Test if the issuer did not set asfAllowTrustLineClawback, AMMClawback
+        // transaction is prohibited.
+        {
+            Env env(*this, features);
+            Account gw{"gateway"};
+            Account alice{"alice"};
+            env.fund(XRP(10000), gw, alice);
+            env.close();
+
+            // gw issues 100 USD to Alice.
+            auto const USD = gw["USD"];
+            env.trust(USD(1000), alice);
+            env(pay(gw, alice, USD(100)));
+            env.close();
+            env.require(balance(alice, gw["USD"](100)));
+            env.require(balance(gw, alice["USD"](-100)));
+
+            // gw creates AMM pool of XRP/USD.
+            AMM amm(env, gw, XRP(100), USD(100), ter(tesSUCCESS));
+
+            // If asfAllowTrustLineClawback is not set, the issuer is not
+            // allowed to send the AMMClawback transaction.
+            env(amm::ammClawback(gw, alice, USD, XRP, std::nullopt),
+                ter(tecNO_PERMISSION));
+        }
+
+        // Test invalid flag.
+        {
+            Env env(*this, features);
+            Account gw{"gateway"};
+            Account alice{"alice"};
+            env.fund(XRP(10000), gw, alice);
+            env.close();
+
+            // gw sets asfAllowTrustLineClawback.
+            env(fset(gw, asfAllowTrustLineClawback));
+            env.close();
+            env.require(flags(gw, asfAllowTrustLineClawback));
+
+            // gw issues 100 USD to Alice.
+            auto const USD = gw["USD"];
+            env.trust(USD(1000), alice);
+            env(pay(gw, alice, USD(100)));
+            env.close();
+
+            AMM amm(env, gw, XRP(100), USD(100), ter(tesSUCCESS));
+
+            // Return temINVALID_FLAG when providing invalid flag.
+            env(amm::ammClawback(gw, alice, USD, XRP, std::nullopt),
+                txflags(tfTwoAssetIfEmpty),
+                ter(temINVALID_FLAG));
+        }
+
+        // Test if tfClawTwoAssets is set when the two assets in the AMM pool
+        // are not issued by the same issuer.
+        {
+            Env env(*this, features);
+            Account gw{"gateway"};
+            Account alice{"alice"};
+            env.fund(XRP(10000), gw, alice);
+            env.close();
+
+            // gw sets asfAllowTrustLineClawback.
+            env(fset(gw, asfAllowTrustLineClawback));
+            env.close();
+            env.require(flags(gw, asfAllowTrustLineClawback));
+
+            // gw issues 100 USD to Alice.
+            auto const USD = gw["USD"];
+            env.trust(USD(1000), alice);
+            env(pay(gw, alice, USD(100)));
+            env.close();
+
+            // gw creates AMM pool of XRP/USD.
+            AMM amm(env, gw, XRP(100), USD(100), ter(tesSUCCESS));
+
+            // Return tecNO_PERMISSION because the issuer set tfClawTwoAssets,
+            // but the issuer only issues USD in the pool. The issuer is not
+            // allowed to set tfClawTwoAssets flag if he did not issue both
+            // assts in the pool.
+            env(amm::ammClawback(gw, alice, USD, XRP, std::nullopt),
+                txflags(tfClawTwoAssets),
+                ter(tecNO_PERMISSION));
+        }
+
+        // Test clawing back XRP is being prohibited.
+        {
+            Env env(*this, features);
+            Account gw{"gateway"};
+            Account alice{"alice"};
+            env.fund(XRP(1000000), gw, alice);
+            env.close();
+
+            // gw sets asfAllowTrustLineClawback.
+            env(fset(gw, asfAllowTrustLineClawback));
+            env.close();
+            env.require(flags(gw, asfAllowTrustLineClawback));
+
+            // gw issues 3000 USD to Alice.
+            auto const USD = gw["USD"];
+            env.trust(USD(100000), alice);
+            env(pay(gw, alice, USD(3000)));
+            env.close();
+
+            // Alice creates AMM pool of XRP/USD.
+            AMM amm(env, alice, XRP(1000), USD(2000), ter(tesSUCCESS));
+            env.close();
+
+            // Clawback XRP is prohibited.
+            env(amm::ammClawback(gw, alice, XRP, USD, std::nullopt),
+                ter(temMALFORMED));
+        }
+    }
+
+    void
+    testFeatureDisabled(FeatureBitset features)
+    {
+        testcase("test featureAMMClawback is not enabled.");
+        using namespace jtx;
+        if (!features[featureAMMClawback])
+        {
+            Env env(*this, features);
+            Account gw{"gateway"};
+            Account alice{"alice"};
+            env.fund(XRP(1000000), gw, alice);
+            env.close();
+
+            // gw sets asfAllowTrustLineClawback.
+            env(fset(gw, asfAllowTrustLineClawback));
+            env.close();
+            env.require(flags(gw, asfAllowTrustLineClawback));
+
+            // gw issues 3000 USD to Alice.
+            auto const USD = gw["USD"];
+            env.trust(USD(100000), alice);
+            env(pay(gw, alice, USD(3000)));
+            env.close();
+
+            // When featureAMMClawback is not enabled, AMMClawback is disabled.
+            // Because when featureAMMClawback is disabled, we can not create
+            // amm account, call amm::ammClawback directly for testing purpose.
+            env(amm::ammClawback(gw, alice, USD, XRP, std::nullopt),
+                ter(temDISABLED));
+        }
+    }
+
+    void
+    testAMMClawbackSpecificAmount(FeatureBitset features)
+    {
+        testcase("test AMMClawback specific amount");
+        using namespace jtx;
+
+        // Test AMMClawback for USD/EUR pool. The assets are issued by different
+        // issuer. Claw back USD, and EUR goes back to the holder.
+        {
+            Env env(*this, features);
+            Account gw{"gateway"};
+            Account gw2{"gateway2"};
+            Account alice{"alice"};
+            env.fund(XRP(1000000), gw, gw2, alice);
+            env.close();
+
+            // gw sets asfAllowTrustLineClawback.
+            env(fset(gw, asfAllowTrustLineClawback));
+            env.close();
+            env.require(flags(gw, asfAllowTrustLineClawback));
+
+            // gw issues 3000 USD to Alice.
+            auto const USD = gw["USD"];
+            env.trust(USD(100000), alice);
+            env(pay(gw, alice, USD(3000)));
+            env.close();
+            env.require(balance(gw, alice["USD"](-3000)));
+            env.require(balance(alice, gw["USD"](3000)));
+
+            // gw2 issues 3000 EUR to Alice.
+            auto const EUR = gw2["EUR"];
+            env.trust(EUR(100000), alice);
+            env(pay(gw2, alice, EUR(3000)));
+            env.close();
+            env.require(balance(gw2, alice["EUR"](-3000)));
+            env.require(balance(alice, gw2["EUR"](3000)));
+
+            // Alice creates AMM pool of EUR/USD.
+            AMM amm(env, alice, EUR(1000), USD(2000), ter(tesSUCCESS));
+            env.close();
+
+            BEAST_EXPECT(amm.expectBalances(
+                USD(2000), EUR(1000), IOUAmount{1414213562373095, -12}));
+
+            // gw clawback 1000 USD from the AMM pool.
+            env(amm::ammClawback(gw, alice, USD, EUR, USD(1000)),
+                ter(tesSUCCESS));
+            env.close();
+
+            // Alice's initial balance for USD is 3000 USD. Alice deposited 2000
+            // USD into the pool, then she has 1000 USD. And 1000 USD was clawed
+            // back from the AMM pool, so she still has 1000 USD.
+            env.require(balance(gw, alice["USD"](-1000)));
+            env.require(balance(alice, gw["USD"](1000)));
+
+            // Alice's initial balance for EUR is 3000 EUR. Alice deposited 1000
+            // EUR into the pool, 500 EUR was withdrawn proportionally. So she
+            // has 2500 EUR now.
+            env.require(balance(gw2, alice["EUR"](-2500)));
+            env.require(balance(alice, gw2["EUR"](2500)));
+
+            // 1000 USD and 500 EUR was withdrawn from the AMM pool, so the
+            // current balance is 1000 USD and 500 EUR.
+            BEAST_EXPECT(amm.expectBalances(
+                USD(1000), EUR(500), IOUAmount{7071067811865475, -13}));
+
+            // Alice has half of its initial lptokens Left.
+            BEAST_EXPECT(
+                amm.expectLPTokens(alice, IOUAmount{7071067811865475, -13}));
+
+            // gw clawback another 1000 USD from the AMM pool. The AMM pool will
+            // be empty and get deleted.
+            env(amm::ammClawback(gw, alice, USD, EUR, USD(1000)),
+                ter(tesSUCCESS));
+            env.close();
+
+            // Alice should still has 1000 USD because gw clawed back from the
+            // AMM pool.
+            env.require(balance(gw, alice["USD"](-1000)));
+            env.require(balance(alice, gw["USD"](1000)));
+
+            // Alice should has 3000 EUR now because another 500 EUR was
+            // withdrawn.
+            env.require(balance(gw2, alice["EUR"](-3000)));
+            env.require(balance(alice, gw2["EUR"](3000)));
+
+            // amm is automatically deleted.
+            BEAST_EXPECT(!amm.ammExists());
+        }
+
+        // Test AMMClawback for USD/XRP pool. Claw back USD, and XRP goes back
+        // to the holder.
+        {
+            Env env(*this, features);
+            Account gw{"gateway"};
+            Account alice{"alice"};
+            env.fund(XRP(1000000), gw, alice);
+            env.close();
+
+            // gw sets asfAllowTrustLineClawback.
+            env(fset(gw, asfAllowTrustLineClawback));
+            env.close();
+            env.require(flags(gw, asfAllowTrustLineClawback));
+
+            // gw issues 3000 USD to Alice.
+            auto const USD = gw["USD"];
+            env.trust(USD(100000), alice);
+            env(pay(gw, alice, USD(3000)));
+            env.close();
+            env.require(balance(gw, alice["USD"](-3000)));
+            env.require(balance(alice, gw["USD"](3000)));
+
+            // Alice creates AMM pool of XRP/USD.
+            AMM amm(env, alice, XRP(1000), USD(2000), ter(tesSUCCESS));
+            env.close();
+
+            BEAST_EXPECT(amm.expectBalances(
+                USD(2000), XRP(1000), IOUAmount{1414213562373095, -9}));
+
+            auto aliceXrpBalance = env.balance(alice, XRP);
+
+            // gw clawback 1000 USD from the AMM pool.
+            env(amm::ammClawback(gw, alice, USD, XRP, USD(1000)),
+                ter(tesSUCCESS));
+            env.close();
+
+            // Alice's initial balance for USD is 3000 USD. Alice deposited 2000
+            // USD into the pool, then she has 1000 USD. And 1000 USD was clawed
+            // back from the AMM pool, so she still has 1000 USD.
+            env.require(balance(gw, alice["USD"](-1000)));
+            env.require(balance(alice, gw["USD"](1000)));
+
+            // Alice will get 500 XRP back.
+            BEAST_EXPECT(
+                expectLedgerEntryRoot(env, alice, aliceXrpBalance + XRP(500)));
+
+            // 1000 USD and 500 XRP was withdrawn from the AMM pool, so the
+            // current balance is 1000 USD and 500 XRP.
+            BEAST_EXPECT(amm.expectBalances(
+                USD(1000), XRP(500), IOUAmount{7071067811865475, -10}));
+
+            // Alice has half of its initial lptokens Left.
+            BEAST_EXPECT(
+                amm.expectLPTokens(alice, IOUAmount{7071067811865475, -10}));
+
+            // gw clawback another 1000 USD from the AMM pool. The AMM pool will
+            // be empty and get deleted.
+            env(amm::ammClawback(gw, alice, USD, XRP, USD(1000)),
+                ter(tesSUCCESS));
+            env.close();
+
+            // Alice should still has 1000 USD because gw clawed back from the
+            // AMM pool.
+            env.require(balance(gw, alice["USD"](-1000)));
+            env.require(balance(alice, gw["USD"](1000)));
+
+            // Alice will get another 1000 XRP back.
+            BEAST_EXPECT(
+                expectLedgerEntryRoot(env, alice, aliceXrpBalance + XRP(1000)));
+
+            // amm is automatically deleted.
+            BEAST_EXPECT(!amm.ammExists());
+        }
+    }
+
+    void
+    testAMMClawbackExceedBalance(FeatureBitset features)
+    {
+        testcase(
+            "test AMMClawback specific amount which exceeds the current "
+            "balance");
+        using namespace jtx;
+
+        // Test AMMClawback for USD/EUR pool. The assets are issued by different
+        // issuer. Claw back USD for multiple times, and EUR goes back to the
+        // holder. The last AMMClawback transaction exceeds the holder's USD
+        // balance in AMM pool.
+        {
+            Env env(*this, features);
+            Account gw{"gateway"};
+            Account gw2{"gateway2"};
+            Account alice{"alice"};
+            env.fund(XRP(1000000), gw, gw2, alice);
+            env.close();
+
+            // gw sets asfAllowTrustLineClawback.
+            env(fset(gw, asfAllowTrustLineClawback));
+            env.close();
+            env.require(flags(gw, asfAllowTrustLineClawback));
+
+            // gw issues 6000 USD to Alice.
+            auto const USD = gw["USD"];
+            env.trust(USD(100000), alice);
+            env(pay(gw, alice, USD(6000)));
+            env.close();
+            env.require(balance(alice, gw["USD"](6000)));
+
+            // gw2 issues 6000 EUR to Alice.
+            auto const EUR = gw2["EUR"];
+            env.trust(EUR(100000), alice);
+            env(pay(gw2, alice, EUR(6000)));
+            env.close();
+            env.require(balance(alice, gw2["EUR"](6000)));
+
+            // Alice creates AMM pool of EUR/USD
+            AMM amm(env, alice, EUR(5000), USD(4000), ter(tesSUCCESS));
+            env.close();
+
+            BEAST_EXPECT(amm.expectBalances(
+                USD(4000), EUR(5000), IOUAmount{4472135954999580, -12}));
+
+            // gw clawback 1000 USD from the AMM pool
+            env(amm::ammClawback(gw, alice, USD, EUR, USD(1000)),
+                ter(tesSUCCESS));
+            env.close();
+
+            // Alice's initial balance for USD is 6000 USD. Alice deposited 4000
+            // USD into the pool, then she has 2000 USD. And 1000 USD was clawed
+            // back from the AMM pool, so she still has 2000 USD.
+            env.require(balance(alice, gw["USD"](2000)));
+
+            // Alice's initial balance for EUR is 6000 EUR. Alice deposited 5000
+            // EUR into the pool, 1250 EUR was withdrawn proportionally. So she
+            // has 2500 EUR now.
+            env.require(balance(alice, gw2["EUR"](2250)));
+
+            // 1000 USD and 1250 EUR was withdrawn from the AMM pool, so the
+            // current balance is 3000 USD and 3750 EUR.
+            BEAST_EXPECT(amm.expectBalances(
+                USD(3000), EUR(3750), IOUAmount{3354101966249685, -12}));
+
+            // Alice has 3/4 of its initial lptokens Left.
+            BEAST_EXPECT(
+                amm.expectLPTokens(alice, IOUAmount{3354101966249685, -12}));
+
+            // gw clawback another 500 USD from the AMM pool.
+            env(amm::ammClawback(gw, alice, USD, EUR, USD(500)),
+                ter(tesSUCCESS));
+            env.close();
+
+            // Alice should still has 2000 USD because gw clawed back from the
+            // AMM pool.
+            env.require(balance(alice, gw["USD"](2000)));
+
+            BEAST_EXPECT(amm.expectBalances(
+                STAmount{USD, UINT64_C(2500000000000001), -12},
+                STAmount{EUR, UINT64_C(3125000000000001), -12},
+                IOUAmount{2795084971874738, -12}));
+
+            BEAST_EXPECT(
+                env.balance(alice, EUR) ==
+                STAmount(EUR, UINT64_C(2874999999999999), -12));
+
+            // gw clawback small amount, 1 USD.
+            env(amm::ammClawback(gw, alice, USD, EUR, USD(1)), ter(tesSUCCESS));
+            env.close();
+
+            // Another 1 USD / 1.25 EUR was withdrawn.
+            env.require(balance(alice, gw["USD"](2000)));
+
+            BEAST_EXPECT(amm.expectBalances(
+                STAmount{USD, UINT64_C(2499000000000002), -12},
+                STAmount{EUR, UINT64_C(3123750000000002), -12},
+                IOUAmount{2793966937885989, -12}));
+
+            BEAST_EXPECT(
+                env.balance(alice, EUR) ==
+                STAmount(EUR, UINT64_C(2876249999999998), -12));
+
+            // gw clawback 4000 USD, exceeding the current balance. We
+            // will clawback all.
+            env(amm::ammClawback(gw, alice, USD, EUR, USD(4000)),
+                ter(tesSUCCESS));
+            env.close();
+
+            env.require(balance(alice, gw["USD"](2000)));
+
+            // All alice's EUR in the pool goes back to alice.
+            BEAST_EXPECT(
+                env.balance(alice, EUR) ==
+                STAmount(EUR, UINT64_C(6000000000000000), -12));
+
+            // amm is automatically deleted.
+            BEAST_EXPECT(!amm.ammExists());
+        }
+
+        // Test AMMClawback for USD/XRP pool. Claw back USD for multiple times,
+        // and XRP goes back to the holder. The last AMMClawback transaction
+        // exceeds the holder's USD balance in AMM pool. In this case, gw
+        // creates the AMM pool USD/XRP, both alice and bob deposit into it. gw2
+        // creates the AMM pool EUR/XRP.
+        {
+            Env env(*this, features);
+            Account gw{"gateway"};
+            Account gw2{"gateway2"};
+            Account alice{"alice"};
+            Account bob{"bob"};
+            env.fund(XRP(1000000), gw, gw2, alice, bob);
+            env.close();
+
+            // gw sets asfAllowTrustLineClawback.
+            env(fset(gw, asfAllowTrustLineClawback));
+            env.close();
+            env.require(flags(gw, asfAllowTrustLineClawback));
+
+            // gw2 sets asfAllowTrustLineClawback.
+            env(fset(gw2, asfAllowTrustLineClawback));
+            env.close();
+            env.require(flags(gw2, asfAllowTrustLineClawback));
+
+            // gw issues 6000 USD to Alice and 5000 USD to Bob.
+            auto const USD = gw["USD"];
+            env.trust(USD(100000), alice);
+            env(pay(gw, alice, USD(6000)));
+            env.trust(USD(100000), bob);
+            env(pay(gw, bob, USD(5000)));
+            env.close();
+
+            // gw2 issues 5000 EUR to Alice and 4000 EUR to Bob.
+            auto const EUR = gw2["EUR"];
+            env.trust(EUR(100000), alice);
+            env(pay(gw2, alice, EUR(5000)));
+            env.trust(EUR(100000), bob);
+            env(pay(gw2, bob, EUR(4000)));
+            env.close();
+
+            // gw creates AMM pool of XRP/USD, alice and bob deposit XRP/USD.
+            AMM amm(env, gw, XRP(2000), USD(1000), ter(tesSUCCESS));
+            BEAST_EXPECT(amm.expectBalances(
+                USD(1000), XRP(2000), IOUAmount{1414213562373095, -9}));
+            amm.deposit(alice, USD(1000), XRP(2000));
+            BEAST_EXPECT(amm.expectBalances(
+                USD(2000), XRP(4000), IOUAmount{2828427124746190, -9}));
+            amm.deposit(bob, USD(1000), XRP(2000));
+            BEAST_EXPECT(amm.expectBalances(
+                USD(3000), XRP(6000), IOUAmount{4242640687119285, -9}));
+            env.close();
+
+            // gw2 creates AMM pool of XRP/EUR, alice and bob deposit XRP/EUR.
+            AMM amm2(env, gw2, XRP(3000), EUR(1000), ter(tesSUCCESS));
+            BEAST_EXPECT(amm2.expectBalances(
+                EUR(1000), XRP(3000), IOUAmount{1732050807568878, -9}));
+            amm2.deposit(alice, EUR(1000), XRP(3000));
+            BEAST_EXPECT(amm2.expectBalances(
+                EUR(2000), XRP(6000), IOUAmount{3464101615137756, -9}));
+            amm2.deposit(bob, EUR(1000), XRP(3000));
+            BEAST_EXPECT(amm2.expectBalances(
+                EUR(3000), XRP(9000), IOUAmount{5196152422706634, -9}));
+            env.close();
+
+            auto aliceXrpBalance = env.balance(alice, XRP);
+            auto bobXrpBalance = env.balance(bob, XRP);
+
+            // gw clawback 500 USD from alice in amm
+            env(amm::ammClawback(gw, alice, USD, XRP, USD(500)),
+                ter(tesSUCCESS));
+            env.close();
+
+            // Alice's initial balance for USD is 6000 USD. Alice deposited 1000
+            // USD into the pool, then she has 5000 USD. And 500 USD was clawed
+            // back from the AMM pool, so she still has 5000 USD.
+            env.require(balance(alice, gw["USD"](5000)));
+
+            // Bob's balance is not changed.
+            env.require(balance(bob, gw["USD"](4000)));
+
+            // Alice gets 1000 XRP back.
+            BEAST_EXPECT(
+                expectLedgerEntryRoot(env, alice, aliceXrpBalance + XRP(1000)));
+
+            BEAST_EXPECT(amm.expectBalances(
+                USD(2500), XRP(5000), IOUAmount{3535533905932738, -9}));
+            BEAST_EXPECT(
+                amm.expectLPTokens(alice, IOUAmount{7071067811865480, -10}));
+            BEAST_EXPECT(
+                amm.expectLPTokens(bob, IOUAmount{1414213562373095, -9}));
+
+            // gw clawback 10 USD from bob in amm.
+            env(amm::ammClawback(gw, bob, USD, XRP, USD(10)), ter(tesSUCCESS));
+            env.close();
+
+            env.require(balance(alice, gw["USD"](5000)));
+            env.require(balance(bob, gw["USD"](4000)));
+
+            // Bob gets 20 XRP back.
+            BEAST_EXPECT(
+                expectLedgerEntryRoot(env, bob, bobXrpBalance + XRP(20)));
+            BEAST_EXPECT(amm.expectBalances(
+                STAmount{USD, UINT64_C(2490000000000001), -12},
+                XRP(4980),
+                IOUAmount{3521391770309008, -9}));
+            BEAST_EXPECT(
+                amm.expectLPTokens(alice, IOUAmount{7071067811865480, -10}));
+            BEAST_EXPECT(
+                amm.expectLPTokens(bob, IOUAmount{1400071426749365, -9}));
+
+            // gw2 clawback 200 EUR from amm2.
+            env(amm::ammClawback(gw2, alice, EUR, XRP, EUR(200)),
+                ter(tesSUCCESS));
+            env.close();
+
+            env.require(balance(alice, gw2["EUR"](4000)));
+            env.require(balance(bob, gw2["EUR"](3000)));
+
+            // Alice gets 600 XRP back.
+            BEAST_EXPECT(expectLedgerEntryRoot(
+                env, alice, aliceXrpBalance + XRP(1000) + XRP(600)));
+            BEAST_EXPECT(amm2.expectBalances(
+                EUR(2800), XRP(8400), IOUAmount{4849742261192859, -9}));
+            BEAST_EXPECT(
+                amm2.expectLPTokens(alice, IOUAmount{1385640646055103, -9}));
+            BEAST_EXPECT(
+                amm2.expectLPTokens(bob, IOUAmount{1732050807568878, -9}));
+
+            // gw claw back 1000 USD from alice in amm, which exceeds alice's
+            // balance. This will clawback all the remaining LP tokens of alice
+            // (corresponding 500 USD / 1000 XRP).
+            env(amm::ammClawback(gw, alice, USD, XRP, USD(1000)),
+                ter(tesSUCCESS));
+            env.close();
+
+            env.require(balance(alice, gw["USD"](5000)));
+            env.require(balance(bob, gw["USD"](4000)));
+
+            // Alice gets 1000 XRP back.
+            BEAST_EXPECT(expectLedgerEntryRoot(
+                env,
+                alice,
+                aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000)));
+            BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0)));
+            BEAST_EXPECT(
+                amm.expectLPTokens(bob, IOUAmount{1400071426749365, -9}));
+            BEAST_EXPECT(amm.expectBalances(
+                STAmount{USD, UINT64_C(1990000000000001), -12},
+                XRP(3980),
+                IOUAmount{2814284989122460, -9}));
+
+            // gw clawback 1000 USD from bob in amm, which also exceeds bob's
+            // balance in amm. All bob's lptoken in amm will be consumed, which
+            // corresponds to 990 USD / 1980 XRP
+            env(amm::ammClawback(gw, bob, USD, XRP, USD(1000)),
+                ter(tesSUCCESS));
+            env.close();
+
+            env.require(balance(alice, gw["USD"](5000)));
+            env.require(balance(bob, gw["USD"](4000)));
+
+            BEAST_EXPECT(expectLedgerEntryRoot(
+                env,
+                alice,
+                aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000)));
+            BEAST_EXPECT(expectLedgerEntryRoot(
+                env, bob, bobXrpBalance + XRP(20) + XRP(1980)));
+
+            // Now neither alice nor bob has any lptoken in amm.
+            BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0)));
+            BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(0)));
+
+            // gw2 claw back 1000 EUR from alice in amm2, which exceeds alice's
+            // balance. All alice's lptokens will be consumed, which corresponds
+            // to 800EUR / 2400 XRP.
+            env(amm::ammClawback(gw2, alice, EUR, XRP, EUR(1000)),
+                ter(tesSUCCESS));
+            env.close();
+
+            env.require(balance(alice, gw2["EUR"](4000)));
+            env.require(balance(bob, gw2["EUR"](3000)));
+
+            // Alice gets another 2400 XRP back, bob's XRP balance remains the
+            // same.
+            BEAST_EXPECT(expectLedgerEntryRoot(
+                env,
+                alice,
+                aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000) +
+                    XRP(2400)));
+            BEAST_EXPECT(expectLedgerEntryRoot(
+                env, bob, bobXrpBalance + XRP(20) + XRP(1980)));
+
+            // Alice now does not have any lptoken in amm2
+            BEAST_EXPECT(amm2.expectLPTokens(alice, IOUAmount(0)));
+
+            BEAST_EXPECT(amm2.expectBalances(
+                EUR(2000), XRP(6000), IOUAmount{3464101615137756, -9}));
+
+            // gw2 claw back 2000 EUR from bib in amm2, which exceeds bob's
+            // balance. All bob's lptokens will be consumed, which corresponds
+            // to 1000EUR / 3000 XRP.
+            env(amm::ammClawback(gw2, bob, EUR, XRP, EUR(2000)),
+                ter(tesSUCCESS));
+            env.close();
+
+            env.require(balance(alice, gw2["EUR"](4000)));
+            env.require(balance(bob, gw2["EUR"](3000)));
+
+            // Bob gets another 3000 XRP back. Alice's XRP balance remains the
+            // same.
+            BEAST_EXPECT(expectLedgerEntryRoot(
+                env,
+                alice,
+                aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000) +
+                    XRP(2400)));
+            BEAST_EXPECT(expectLedgerEntryRoot(
+                env, bob, bobXrpBalance + XRP(20) + XRP(1980) + XRP(3000)));
+
+            // Neither alice nor bob has any lptoken in amm2
+            BEAST_EXPECT(amm2.expectLPTokens(alice, IOUAmount(0)));
+            BEAST_EXPECT(amm2.expectLPTokens(bob, IOUAmount(0)));
+
+            BEAST_EXPECT(amm2.expectBalances(
+                EUR(1000), XRP(3000), IOUAmount{1732050807568878, -9}));
+        }
+    }
+
+    void
+    testAMMClawbackAll(FeatureBitset features)
+    {
+        testcase("test AMMClawback all the tokens in the AMM pool");
+        using namespace jtx;
+
+        // Test AMMClawback for USD/EUR pool. The assets are issued by different
+        // issuer. Claw back all the USD for different users.
+        {
+            Env env(*this, features);
+            Account gw{"gateway"};
+            Account gw2{"gateway2"};
+            Account alice{"alice"};
+            Account bob{"bob"};
+            Account carol{"carol"};
+            env.fund(XRP(1000000), gw, gw2, alice, bob, carol);
+            env.close();
+
+            // gw sets asfAllowTrustLineClawback.
+            env(fset(gw, asfAllowTrustLineClawback));
+            env.close();
+            env.require(flags(gw, asfAllowTrustLineClawback));
+
+            // gw2 sets asfAllowTrustLineClawback.
+            env(fset(gw2, asfAllowTrustLineClawback));
+            env.close();
+            env.require(flags(gw2, asfAllowTrustLineClawback));
+
+            // gw issues 6000 USD to Alice, 5000 USD to Bob, and 4000 USD
+            // to Carol.
+            auto const USD = gw["USD"];
+            env.trust(USD(100000), alice);
+            env(pay(gw, alice, USD(6000)));
+            env.trust(USD(100000), bob);
+            env(pay(gw, bob, USD(5000)));
+            env.trust(USD(100000), carol);
+            env(pay(gw, carol, USD(4000)));
+            env.close();
+
+            // gw2 issues 6000 EUR to Alice and 5000 EUR to Bob and 4000
+            // EUR to Carol.
+            auto const EUR = gw2["EUR"];
+            env.trust(EUR(100000), alice);
+            env(pay(gw2, alice, EUR(6000)));
+            env.trust(EUR(100000), bob);
+            env(pay(gw2, bob, EUR(5000)));
+            env.trust(EUR(100000), carol);
+            env(pay(gw2, carol, EUR(4000)));
+            env.close();
+
+            // Alice creates AMM pool of EUR/USD
+            AMM amm(env, alice, EUR(5000), USD(4000), ter(tesSUCCESS));
+            env.close();
+
+            BEAST_EXPECT(amm.expectBalances(
+                USD(4000), EUR(5000), IOUAmount{4472135954999580, -12}));
+            amm.deposit(bob, USD(2000), EUR(2500));
+            BEAST_EXPECT(amm.expectBalances(
+                USD(6000), EUR(7500), IOUAmount{6708203932499370, -12}));
+            amm.deposit(carol, USD(1000), EUR(1250));
+            BEAST_EXPECT(amm.expectBalances(
+                USD(7000), EUR(8750), IOUAmount{7826237921249265, -12}));
+
+            BEAST_EXPECT(
+                amm.expectLPTokens(alice, IOUAmount{4472135954999580, -12}));
+            BEAST_EXPECT(
+                amm.expectLPTokens(bob, IOUAmount{2236067977499790, -12}));
+            BEAST_EXPECT(
+                amm.expectLPTokens(carol, IOUAmount{1118033988749895, -12}));
+
+            env.require(balance(alice, gw["USD"](2000)));
+            env.require(balance(alice, gw2["EUR"](1000)));
+            env.require(balance(bob, gw["USD"](3000)));
+            env.require(balance(bob, gw2["EUR"](2500)));
+            env.require(balance(carol, gw["USD"](3000)));
+            env.require(balance(carol, gw2["EUR"](2750)));
+
+            // gw clawback all the bob's USD in amm. (2000 USD / 2500 EUR)
+            env(amm::ammClawback(gw, bob, USD, EUR, std::nullopt),
+                ter(tesSUCCESS));
+            env.close();
+
+            BEAST_EXPECT(amm.expectBalances(
+                STAmount{USD, UINT64_C(4999999999999999), -12},
+                STAmount{EUR, UINT64_C(6249999999999999), -12},
+                IOUAmount{5590169943749475, -12}));
+
+            BEAST_EXPECT(
+                amm.expectLPTokens(alice, IOUAmount{4472135954999580, -12}));
+            BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(0)));
+            BEAST_EXPECT(
+                amm.expectLPTokens(carol, IOUAmount{1118033988749895, -12}));
+
+            // Bob will get 2500 EUR back.
+            env.require(balance(alice, gw["USD"](2000)));
+            env.require(balance(alice, gw2["EUR"](1000)));
+            BEAST_EXPECT(
+                env.balance(bob, USD) ==
+                STAmount(USD, UINT64_C(3000000000000000), -12));
+
+            BEAST_EXPECT(
+                env.balance(bob, EUR) ==
+                STAmount(EUR, UINT64_C(5000000000000001), -12));
+            env.require(balance(carol, gw["USD"](3000)));
+            env.require(balance(carol, gw2["EUR"](2750)));
+
+            // gw2 clawback all carol's EUR in amm. (1000 USD / 1250 EUR)
+            env(amm::ammClawback(gw2, carol, EUR, USD, std::nullopt),
+                ter(tesSUCCESS));
+            env.close();
+            BEAST_EXPECT(amm.expectBalances(
+                STAmount{USD, UINT64_C(3999999999999999), -12},
+                STAmount{EUR, UINT64_C(4999999999999999), -12},
+                IOUAmount{4472135954999580, -12}));
+
+            BEAST_EXPECT(
+                amm.expectLPTokens(alice, IOUAmount{4472135954999580, -12}));
+            BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(0)));
+            BEAST_EXPECT(amm.expectLPTokens(carol, IOUAmount(0)));
+
+            // gw2 clawback all alice's EUR in amm. (4000 USD / 5000 EUR)
+            env(amm::ammClawback(gw2, alice, EUR, USD, std::nullopt),
+                ter(tesSUCCESS));
+            env.close();
+
+            env.require(balance(carol, gw2["EUR"](2750)));
+            env.require(balance(carol, gw["USD"](4000)));
+            BEAST_EXPECT(!amm.ammExists());
+        }
+
+        // Test AMMClawback for USD/XRP pool. Claw back all the USD for
+        // different users.
+        {
+            Env env(*this, features);
+            Account gw{"gateway"};
+            Account alice{"alice"};
+            Account bob{"bob"};
+            env.fund(XRP(1000000), gw, alice, bob);
+            env.close();
+
+            // gw sets asfAllowTrustLineClawback
+            env(fset(gw, asfAllowTrustLineClawback));
+            env.close();
+            env.require(flags(gw, asfAllowTrustLineClawback));
+
+            // gw issues 600000 USD to Alice and 500000 USD to Bob.
+            auto const USD = gw["USD"];
+            env.trust(USD(1000000), alice);
+            env(pay(gw, alice, USD(600000)));
+            env.trust(USD(1000000), bob);
+            env(pay(gw, bob, USD(500000)));
+            env.close();
+
+            // gw creates AMM pool of XRP/USD, alice and bob deposit XRP/USD.
+            AMM amm(env, gw, XRP(2000), USD(10000), ter(tesSUCCESS));
+            BEAST_EXPECT(amm.expectBalances(
+                USD(10000), XRP(2000), IOUAmount{4472135954999580, -9}));
+            amm.deposit(alice, USD(1000), XRP(200));
+            BEAST_EXPECT(amm.expectBalances(
+                USD(11000), XRP(2200), IOUAmount{4919349550499538, -9}));
+            amm.deposit(bob, USD(2000), XRP(400));
+            BEAST_EXPECT(amm.expectBalances(
+                USD(13000), XRP(2600), IOUAmount{5813776741499453, -9}));
+            env.close();
+
+            auto aliceXrpBalance = env.balance(alice, XRP);
+            auto bobXrpBalance = env.balance(bob, XRP);
+
+            // gw clawback all alice's USD in amm. (1000 USD / 200 XRP)
+            env(amm::ammClawback(gw, alice, USD, XRP, std::nullopt),
+                ter(tesSUCCESS));
+            env.close();
+            BEAST_EXPECT(amm.expectBalances(
+                USD(12000), XRP(2400), IOUAmount{5366563145999495, -9}));
+            BEAST_EXPECT(
+                expectLedgerEntryRoot(env, alice, aliceXrpBalance + XRP(200)));
+            BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0)));
+
+            // gw clawback all bob's USD in amm. (2000 USD / 400 XRP)
+            env(amm::ammClawback(gw, bob, USD, XRP, std::nullopt),
+                ter(tesSUCCESS));
+            env.close();
+            BEAST_EXPECT(amm.expectBalances(
+                USD(10000), XRP(2000), IOUAmount{4472135954999580, -9}));
+            BEAST_EXPECT(
+                expectLedgerEntryRoot(env, bob, bobXrpBalance + XRP(400)));
+            BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0)));
+            BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(0)));
+        }
+    }
+
+    void
+    testAMMClawbackSameIssuerAssets(FeatureBitset features)
+    {
+        testcase(
+            "test AMMClawback from AMM pool with assets having the same "
+            "issuer");
+        using namespace jtx;
+
+        // Test AMMClawback for USD/EUR pool. The assets are issued by different
+        // issuer. Claw back all the USD for different users.
+        Env env(*this, features);
+        Account gw{"gateway"};
+        Account alice{"alice"};
+        Account bob{"bob"};
+        Account carol{"carol"};
+        env.fund(XRP(1000000), gw, alice, bob, carol);
+        env.close();
+
+        // gw sets asfAllowTrustLineClawback.
+        env(fset(gw, asfAllowTrustLineClawback));
+        env.close();
+        env.require(flags(gw, asfAllowTrustLineClawback));
+
+        auto const USD = gw["USD"];
+        env.trust(USD(100000), alice);
+        env(pay(gw, alice, USD(10000)));
+        env.trust(USD(100000), bob);
+        env(pay(gw, bob, USD(9000)));
+        env.trust(USD(100000), carol);
+        env(pay(gw, carol, USD(8000)));
+        env.close();
+
+        auto const EUR = gw["EUR"];
+        env.trust(EUR(100000), alice);
+        env(pay(gw, alice, EUR(10000)));
+        env.trust(EUR(100000), bob);
+        env(pay(gw, bob, EUR(9000)));
+        env.trust(EUR(100000), carol);
+        env(pay(gw, carol, EUR(8000)));
+        env.close();
+
+        AMM amm(env, alice, EUR(2000), USD(8000), ter(tesSUCCESS));
+        env.close();
+
+        BEAST_EXPECT(amm.expectBalances(USD(8000), EUR(2000), IOUAmount(4000)));
+        amm.deposit(bob, USD(4000), EUR(1000));
+        BEAST_EXPECT(
+            amm.expectBalances(USD(12000), EUR(3000), IOUAmount(6000)));
+        amm.deposit(carol, USD(2000), EUR(500));
+        BEAST_EXPECT(
+            amm.expectBalances(USD(14000), EUR(3500), IOUAmount(7000)));
+
+        // gw clawback 1000 USD from carol.
+        env(amm::ammClawback(gw, carol, USD, EUR, USD(1000)), ter(tesSUCCESS));
+        env.close();
+        BEAST_EXPECT(
+            amm.expectBalances(USD(13000), EUR(3250), IOUAmount(6500)));
+
+        BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(4000)));
+        BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(2000)));
+        BEAST_EXPECT(amm.expectLPTokens(carol, IOUAmount(500)));
+        BEAST_EXPECT(env.balance(alice, USD) == USD(2000));
+        BEAST_EXPECT(env.balance(alice, EUR) == EUR(8000));
+        BEAST_EXPECT(env.balance(bob, USD) == USD(5000));
+        BEAST_EXPECT(env.balance(bob, EUR) == EUR(8000));
+        BEAST_EXPECT(env.balance(carol, USD) == USD(6000));
+        // 250 EUR goes back to carol.
+        BEAST_EXPECT(env.balance(carol, EUR) == EUR(7750));
+
+        // gw clawback 1000 USD from bob with tfClawTwoAssets flag.
+        // then the corresponding EUR will also be clawed back
+        // by gw.
+        env(amm::ammClawback(gw, bob, USD, EUR, USD(1000)),
+            txflags(tfClawTwoAssets),
+            ter(tesSUCCESS));
+        env.close();
+        BEAST_EXPECT(
+            amm.expectBalances(USD(12000), EUR(3000), IOUAmount(6000)));
+
+        BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(4000)));
+        BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(1500)));
+        BEAST_EXPECT(amm.expectLPTokens(carol, IOUAmount(500)));
+        BEAST_EXPECT(env.balance(alice, USD) == USD(2000));
+        BEAST_EXPECT(env.balance(alice, EUR) == EUR(8000));
+        BEAST_EXPECT(env.balance(bob, USD) == USD(5000));
+        // 250 EUR did not go back to bob because tfClawTwoAssets is set.
+        BEAST_EXPECT(env.balance(bob, EUR) == EUR(8000));
+        BEAST_EXPECT(env.balance(carol, USD) == USD(6000));
+        BEAST_EXPECT(env.balance(carol, EUR) == EUR(7750));
+
+        // gw clawback all USD from alice and set tfClawTwoAssets.
+        env(amm::ammClawback(gw, alice, USD, EUR, std::nullopt),
+            txflags(tfClawTwoAssets),
+            ter(tesSUCCESS));
+        env.close();
+        BEAST_EXPECT(amm.expectBalances(USD(4000), EUR(1000), IOUAmount(2000)));
+
+        BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0)));
+        BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(1500)));
+        BEAST_EXPECT(amm.expectLPTokens(carol, IOUAmount(500)));
+        BEAST_EXPECT(env.balance(alice, USD) == USD(2000));
+        BEAST_EXPECT(env.balance(alice, EUR) == EUR(8000));
+        BEAST_EXPECT(env.balance(bob, USD) == USD(5000));
+        BEAST_EXPECT(env.balance(bob, EUR) == EUR(8000));
+        BEAST_EXPECT(env.balance(carol, USD) == USD(6000));
+        BEAST_EXPECT(env.balance(carol, EUR) == EUR(7750));
+    }
+
+    void
+    testAMMClawbackSameCurrency(FeatureBitset features)
+    {
+        testcase(
+            "test AMMClawback from AMM pool with assets having the same "
+            "currency, but from different issuer");
+        using namespace jtx;
+
+        // Test AMMClawback for USD/EUR pool. The assets are issued by different
+        // issuer. Claw back all the USD for different users.
+        Env env(*this, features);
+        Account gw{"gateway"};
+        Account gw2{"gateway2"};
+        Account alice{"alice"};
+        Account bob{"bob"};
+        env.fund(XRP(1000000), gw, gw2, alice, bob);
+        env.close();
+
+        // gw sets asfAllowTrustLineClawback.
+        env(fset(gw, asfAllowTrustLineClawback));
+        env.close();
+        env.require(flags(gw, asfAllowTrustLineClawback));
+
+        // gw2 sets asfAllowTrustLineClawback.
+        env(fset(gw2, asfAllowTrustLineClawback));
+        env.close();
+        env.require(flags(gw2, asfAllowTrustLineClawback));
+
+        env.trust(gw["USD"](100000), alice);
+        env(pay(gw, alice, gw["USD"](8000)));
+        env.trust(gw["USD"](100000), bob);
+        env(pay(gw, bob, gw["USD"](7000)));
+
+        env.trust(gw2["USD"](100000), alice);
+        env(pay(gw2, alice, gw2["USD"](6000)));
+        env.trust(gw2["USD"](100000), bob);
+        env(pay(gw2, bob, gw2["USD"](5000)));
+        env.close();
+
+        AMM amm(env, alice, gw["USD"](1000), gw2["USD"](1500), ter(tesSUCCESS));
+        env.close();
+
+        BEAST_EXPECT(amm.expectBalances(
+            gw["USD"](1000),
+            gw2["USD"](1500),
+            IOUAmount{1224744871391589, -12}));
+        amm.deposit(bob, gw["USD"](2000), gw2["USD"](3000));
+        BEAST_EXPECT(amm.expectBalances(
+            gw["USD"](3000),
+            gw2["USD"](4500),
+            IOUAmount{3674234614174767, -12}));
+
+        // Issuer does not match with asset.
+        env(amm::ammClawback(
+                gw,
+                alice,
+                gw2["USD"],
+                gw["USD"],
+                STAmount{Issue{gw2["USD"].currency, gw2.id()}, 500}),
+            ter(temMALFORMED));
+
+        // gw2 clawback 500 gw2[USD] from alice.
+        env(amm::ammClawback(
+                gw2,
+                alice,
+                gw2["USD"],
+                gw["USD"],
+                STAmount{Issue{gw2["USD"].currency, gw2.id()}, 500}),
+            ter(tesSUCCESS));
+        env.close();
+        BEAST_EXPECT(amm.expectBalances(
+            STAmount{gw["USD"], UINT64_C(2666666666666667), -12},
+            gw2["USD"](4000),
+            IOUAmount{3265986323710904, -12}));
+
+        BEAST_EXPECT(
+            amm.expectLPTokens(alice, IOUAmount{8164965809277260, -13}));
+        BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount{2449489742783178, -12}));
+        BEAST_EXPECT(
+            env.balance(alice, gw["USD"]) ==
+            STAmount(gw["USD"], UINT64_C(7333333333333333), -12));
+        BEAST_EXPECT(env.balance(alice, gw2["USD"]) == gw2["USD"](4500));
+        BEAST_EXPECT(env.balance(bob, gw["USD"]) == gw["USD"](5000));
+        BEAST_EXPECT(env.balance(bob, gw2["USD"]) == gw2["USD"](2000));
+
+        // gw clawback all gw["USD"] from bob.
+        env(amm::ammClawback(gw, bob, gw["USD"], gw2["USD"], std::nullopt),
+            ter(tesSUCCESS));
+        env.close();
+        BEAST_EXPECT(amm.expectBalances(
+            STAmount{gw["USD"], UINT64_C(6666666666666670), -13},
+            gw2["USD"](1000),
+            IOUAmount{8164965809277260, -13}));
+
+        BEAST_EXPECT(
+            amm.expectLPTokens(alice, IOUAmount{8164965809277260, -13}));
+        BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(0)));
+        BEAST_EXPECT(
+            env.balance(alice, gw["USD"]) ==
+            STAmount(gw["USD"], UINT64_C(7333333333333333), -12));
+        BEAST_EXPECT(env.balance(alice, gw2["USD"]) == gw2["USD"](4500));
+        BEAST_EXPECT(env.balance(bob, gw["USD"]) == gw["USD"](5000));
+        // Bob gets 3000 gw2["USD"] back and now his balance is 5000.
+        BEAST_EXPECT(env.balance(bob, gw2["USD"]) == gw2["USD"](5000));
+    }
+
+    void
+    testAMMClawbackIssuesEachOther(FeatureBitset features)
+    {
+        testcase("test AMMClawback when issuing token for each other");
+        using namespace jtx;
+
+        // gw and gw2 issues token for each other. Test AMMClawback from
+        // each other.
+        Env env(*this, features);
+        Account gw{"gateway"};
+        Account gw2{"gateway2"};
+        Account alice{"alice"};
+        env.fund(XRP(1000000), gw, gw2, alice);
+        env.close();
+
+        // gw sets asfAllowTrustLineClawback.
+        env(fset(gw, asfAllowTrustLineClawback));
+        env.close();
+        env.require(flags(gw, asfAllowTrustLineClawback));
+
+        // gw2 sets asfAllowTrustLineClawback.
+        env(fset(gw2, asfAllowTrustLineClawback));
+        env.close();
+        env.require(flags(gw2, asfAllowTrustLineClawback));
+
+        auto const USD = gw["USD"];
+        env.trust(USD(100000), gw2);
+        env(pay(gw, gw2, USD(5000)));
+        env.trust(USD(100000), alice);
+        env(pay(gw, alice, USD(5000)));
+
+        auto const EUR = gw2["EUR"];
+        env.trust(EUR(100000), gw);
+        env(pay(gw2, gw, EUR(6000)));
+        env.trust(EUR(100000), alice);
+        env(pay(gw2, alice, EUR(6000)));
+        env.close();
+
+        AMM amm(env, gw, USD(1000), EUR(2000), ter(tesSUCCESS));
+        env.close();
+        BEAST_EXPECT(amm.expectBalances(
+            USD(1000), EUR(2000), IOUAmount{1414213562373095, -12}));
+
+        amm.deposit(gw2, USD(2000), EUR(4000));
+        BEAST_EXPECT(amm.expectBalances(
+            USD(3000), EUR(6000), IOUAmount{4242640687119285, -12}));
+
+        amm.deposit(alice, USD(3000), EUR(6000));
+        BEAST_EXPECT(amm.expectBalances(
+            USD(6000), EUR(12000), IOUAmount{8485281374238570, -12}));
+
+        BEAST_EXPECT(amm.expectLPTokens(gw, IOUAmount{1414213562373095, -12}));
+        BEAST_EXPECT(amm.expectLPTokens(gw2, IOUAmount{2828427124746190, -12}));
+        BEAST_EXPECT(
+            amm.expectLPTokens(alice, IOUAmount{4242640687119285, -12}));
+
+        // gw claws back 1000 USD from gw2.
+        env(amm::ammClawback(gw, gw2, USD, EUR, USD(1000)), ter(tesSUCCESS));
+        env.close();
+        BEAST_EXPECT(amm.expectBalances(
+            USD(5000), EUR(10000), IOUAmount{7071067811865475, -12}));
+
+        BEAST_EXPECT(amm.expectLPTokens(gw, IOUAmount{1414213562373095, -12}));
+        BEAST_EXPECT(amm.expectLPTokens(gw2, IOUAmount{1414213562373095, -12}));
+        BEAST_EXPECT(
+            amm.expectLPTokens(alice, IOUAmount{4242640687119285, -12}));
+
+        BEAST_EXPECT(env.balance(alice, USD) == USD(2000));
+        BEAST_EXPECT(env.balance(alice, EUR) == EUR(0));
+        BEAST_EXPECT(env.balance(gw, EUR) == EUR(4000));
+        BEAST_EXPECT(env.balance(gw2, USD) == USD(3000));
+
+        // gw2 claws back 1000 EUR from gw.
+        env(amm::ammClawback(gw2, gw, EUR, USD, EUR(1000)), ter(tesSUCCESS));
+        env.close();
+        BEAST_EXPECT(amm.expectBalances(
+            USD(4500),
+            STAmount(EUR, UINT64_C(9000000000000001), -12),
+            IOUAmount{6363961030678928, -12}));
+
+        BEAST_EXPECT(amm.expectLPTokens(gw, IOUAmount{7071067811865480, -13}));
+        BEAST_EXPECT(amm.expectLPTokens(gw2, IOUAmount{1414213562373095, -12}));
+        BEAST_EXPECT(
+            amm.expectLPTokens(alice, IOUAmount{4242640687119285, -12}));
+
+        BEAST_EXPECT(env.balance(alice, USD) == USD(2000));
+        BEAST_EXPECT(env.balance(alice, EUR) == EUR(0));
+        BEAST_EXPECT(env.balance(gw, EUR) == EUR(4000));
+        BEAST_EXPECT(env.balance(gw2, USD) == USD(3000));
+
+        // gw2 claws back 4000 EUR from alice.
+        env(amm::ammClawback(gw2, alice, EUR, USD, EUR(4000)), ter(tesSUCCESS));
+        env.close();
+        BEAST_EXPECT(amm.expectBalances(
+            USD(2500),
+            STAmount(EUR, UINT64_C(5000000000000001), -12),
+            IOUAmount{3535533905932738, -12}));
+
+        BEAST_EXPECT(amm.expectLPTokens(gw, IOUAmount{7071067811865480, -13}));
+        BEAST_EXPECT(amm.expectLPTokens(gw2, IOUAmount{1414213562373095, -12}));
+        BEAST_EXPECT(
+            amm.expectLPTokens(alice, IOUAmount{1414213562373095, -12}));
+
+        BEAST_EXPECT(env.balance(alice, USD) == USD(4000));
+        BEAST_EXPECT(env.balance(alice, EUR) == EUR(0));
+        BEAST_EXPECT(env.balance(gw, EUR) == EUR(4000));
+        BEAST_EXPECT(env.balance(gw2, USD) == USD(3000));
+    }
+
+    void
+    testNotHoldingLptoken(FeatureBitset features)
+    {
+        testcase(
+            "test AMMClawback from account which does not own any lptoken in "
+            "the pool");
+        using namespace jtx;
+
+        Env env(*this, features);
+        Account gw{"gateway"};
+        Account alice{"alice"};
+        env.fund(XRP(1000000), gw, alice);
+        env.close();
+
+        // gw sets asfAllowTrustLineClawback.
+        env(fset(gw, asfAllowTrustLineClawback));
+        env.close();
+        env.require(flags(gw, asfAllowTrustLineClawback));
+
+        auto const USD = gw["USD"];
+        env.trust(USD(100000), alice);
+        env(pay(gw, alice, USD(5000)));
+
+        AMM amm(env, gw, USD(1000), XRP(2000), ter(tesSUCCESS));
+        env.close();
+
+        // Alice did not deposit in the amm pool. So AMMClawback from Alice
+        // will fail.
+        env(amm::ammClawback(gw, alice, USD, XRP, USD(1000)),
+            ter(tecAMM_BALANCE));
+    }
+
+    void
+    testAssetFrozen(FeatureBitset features)
+    {
+        testcase("test assets frozen");
+        using namespace jtx;
+
+        // test individually frozen trustline.
+        {
+            Env env(*this, features);
+            Account gw{"gateway"};
+            Account gw2{"gateway2"};
+            Account alice{"alice"};
+            env.fund(XRP(1000000), gw, gw2, alice);
+            env.close();
+
+            // gw sets asfAllowTrustLineClawback.
+            env(fset(gw, asfAllowTrustLineClawback));
+            env.close();
+            env.require(flags(gw, asfAllowTrustLineClawback));
+
+            // gw issues 3000 USD to Alice.
+            auto const USD = gw["USD"];
+            env.trust(USD(100000), alice);
+            env(pay(gw, alice, USD(3000)));
+            env.close();
+            env.require(balance(alice, gw["USD"](3000)));
+
+            // gw2 issues 3000 EUR to Alice.
+            auto const EUR = gw2["EUR"];
+            env.trust(EUR(100000), alice);
+            env(pay(gw2, alice, EUR(3000)));
+            env.close();
+            env.require(balance(alice, gw2["EUR"](3000)));
+
+            // Alice creates AMM pool of EUR/USD.
+            AMM amm(env, alice, EUR(1000), USD(2000), ter(tesSUCCESS));
+            env.close();
+
+            BEAST_EXPECT(amm.expectBalances(
+                USD(2000), EUR(1000), IOUAmount{1414213562373095, -12}));
+
+            // freeze trustline
+            env(trust(gw, alice["USD"](0), tfSetFreeze));
+            env.close();
+
+            // gw clawback 1000 USD from the AMM pool.
+            env(amm::ammClawback(gw, alice, USD, EUR, USD(1000)),
+                ter(tesSUCCESS));
+            env.close();
+
+            env.require(balance(alice, gw["USD"](1000)));
+            env.require(balance(alice, gw2["EUR"](2500)));
+            BEAST_EXPECT(amm.expectBalances(
+                USD(1000), EUR(500), IOUAmount{7071067811865475, -13}));
+
+            // Alice has half of its initial lptokens Left.
+            BEAST_EXPECT(
+                amm.expectLPTokens(alice, IOUAmount{7071067811865475, -13}));
+
+            // gw clawback another 1000 USD from the AMM pool. The AMM pool will
+            // be empty and get deleted.
+            env(amm::ammClawback(gw, alice, USD, EUR, USD(1000)),
+                ter(tesSUCCESS));
+            env.close();
+
+            // Alice should still has 1000 USD because gw clawed back from the
+            // AMM pool.
+            env.require(balance(alice, gw["USD"](1000)));
+            env.require(balance(alice, gw2["EUR"](3000)));
+
+            // amm is automatically deleted.
+            BEAST_EXPECT(!amm.ammExists());
+        }
+
+        // test individually frozen trustline of both USD and EUR currency.
+        {
+            Env env(*this, features);
+            Account gw{"gateway"};
+            Account gw2{"gateway2"};
+            Account alice{"alice"};
+            env.fund(XRP(1000000), gw, gw2, alice);
+            env.close();
+
+            // gw sets asfAllowTrustLineClawback.
+            env(fset(gw, asfAllowTrustLineClawback));
+            env.close();
+            env.require(flags(gw, asfAllowTrustLineClawback));
+
+            // gw issues 3000 USD to Alice.
+            auto const USD = gw["USD"];
+            env.trust(USD(100000), alice);
+            env(pay(gw, alice, USD(3000)));
+            env.close();
+            env.require(balance(alice, gw["USD"](3000)));
+
+            // gw2 issues 3000 EUR to Alice.
+            auto const EUR = gw2["EUR"];
+            env.trust(EUR(100000), alice);
+            env(pay(gw2, alice, EUR(3000)));
+            env.close();
+            env.require(balance(alice, gw2["EUR"](3000)));
+
+            // Alice creates AMM pool of EUR/USD.
+            AMM amm(env, alice, EUR(1000), USD(2000), ter(tesSUCCESS));
+            env.close();
+
+            BEAST_EXPECT(amm.expectBalances(
+                USD(2000), EUR(1000), IOUAmount{1414213562373095, -12}));
+
+            // freeze trustlines
+            env(trust(gw, alice["USD"](0), tfSetFreeze));
+            env(trust(gw2, alice["EUR"](0), tfSetFreeze));
+            env.close();
+
+            // gw clawback 1000 USD from the AMM pool.
+            env(amm::ammClawback(gw, alice, USD, EUR, USD(1000)),
+                ter(tesSUCCESS));
+            env.close();
+
+            env.require(balance(alice, gw["USD"](1000)));
+            env.require(balance(alice, gw2["EUR"](2500)));
+            BEAST_EXPECT(amm.expectBalances(
+                USD(1000), EUR(500), IOUAmount{7071067811865475, -13}));
+            BEAST_EXPECT(
+                amm.expectLPTokens(alice, IOUAmount{7071067811865475, -13}));
+        }
+
+        // test gw global freeze.
+        {
+            Env env(*this, features);
+            Account gw{"gateway"};
+            Account gw2{"gateway2"};
+            Account alice{"alice"};
+            env.fund(XRP(1000000), gw, gw2, alice);
+            env.close();
+
+            // gw sets asfAllowTrustLineClawback.
+            env(fset(gw, asfAllowTrustLineClawback));
+            env.close();
+            env.require(flags(gw, asfAllowTrustLineClawback));
+
+            // gw issues 3000 USD to Alice.
+            auto const USD = gw["USD"];
+            env.trust(USD(100000), alice);
+            env(pay(gw, alice, USD(3000)));
+            env.close();
+            env.require(balance(alice, gw["USD"](3000)));
+
+            // gw2 issues 3000 EUR to Alice.
+            auto const EUR = gw2["EUR"];
+            env.trust(EUR(100000), alice);
+            env(pay(gw2, alice, EUR(3000)));
+            env.close();
+            env.require(balance(alice, gw2["EUR"](3000)));
+
+            // Alice creates AMM pool of EUR/USD.
+            AMM amm(env, alice, EUR(1000), USD(2000), ter(tesSUCCESS));
+            env.close();
+
+            BEAST_EXPECT(amm.expectBalances(
+                USD(2000), EUR(1000), IOUAmount{1414213562373095, -12}));
+
+            // global freeze
+            env(fset(gw, asfGlobalFreeze));
+            env.close();
+
+            // gw clawback 1000 USD from the AMM pool.
+            env(amm::ammClawback(gw, alice, USD, EUR, USD(1000)),
+                ter(tesSUCCESS));
+            env.close();
+
+            env.require(balance(alice, gw["USD"](1000)));
+            env.require(balance(alice, gw2["EUR"](2500)));
+            BEAST_EXPECT(amm.expectBalances(
+                USD(1000), EUR(500), IOUAmount{7071067811865475, -13}));
+            BEAST_EXPECT(
+                amm.expectLPTokens(alice, IOUAmount{7071067811865475, -13}));
+        }
+
+        // Test both assets are issued by the same issuer. And issuer sets
+        // global freeze.
+        {
+            Env env(*this, features);
+            Account gw{"gateway"};
+            Account alice{"alice"};
+            Account bob{"bob"};
+            Account carol{"carol"};
+            env.fund(XRP(1000000), gw, alice, bob, carol);
+            env.close();
+
+            // gw sets asfAllowTrustLineClawback.
+            env(fset(gw, asfAllowTrustLineClawback));
+            env.close();
+            env.require(flags(gw, asfAllowTrustLineClawback));
+
+            auto const USD = gw["USD"];
+            env.trust(USD(100000), alice);
+            env(pay(gw, alice, USD(10000)));
+            env.trust(USD(100000), bob);
+            env(pay(gw, bob, USD(9000)));
+            env.trust(USD(100000), carol);
+            env(pay(gw, carol, USD(8000)));
+            env.close();
+
+            auto const EUR = gw["EUR"];
+            env.trust(EUR(100000), alice);
+            env(pay(gw, alice, EUR(10000)));
+            env.trust(EUR(100000), bob);
+            env(pay(gw, bob, EUR(9000)));
+            env.trust(EUR(100000), carol);
+            env(pay(gw, carol, EUR(8000)));
+            env.close();
+
+            AMM amm(env, alice, EUR(2000), USD(8000), ter(tesSUCCESS));
+            env.close();
+
+            BEAST_EXPECT(
+                amm.expectBalances(USD(8000), EUR(2000), IOUAmount(4000)));
+            amm.deposit(bob, USD(4000), EUR(1000));
+            BEAST_EXPECT(
+                amm.expectBalances(USD(12000), EUR(3000), IOUAmount(6000)));
+            amm.deposit(carol, USD(2000), EUR(500));
+            BEAST_EXPECT(
+                amm.expectBalances(USD(14000), EUR(3500), IOUAmount(7000)));
+
+            // global freeze
+            env(fset(gw, asfGlobalFreeze));
+            env.close();
+
+            // gw clawback 1000 USD from carol.
+            env(amm::ammClawback(gw, carol, USD, EUR, USD(1000)),
+                ter(tesSUCCESS));
+            env.close();
+            BEAST_EXPECT(
+                amm.expectBalances(USD(13000), EUR(3250), IOUAmount(6500)));
+
+            BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(4000)));
+            BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(2000)));
+            BEAST_EXPECT(amm.expectLPTokens(carol, IOUAmount(500)));
+            BEAST_EXPECT(env.balance(alice, USD) == USD(2000));
+            BEAST_EXPECT(env.balance(alice, EUR) == EUR(8000));
+            BEAST_EXPECT(env.balance(bob, USD) == USD(5000));
+            BEAST_EXPECT(env.balance(bob, EUR) == EUR(8000));
+            BEAST_EXPECT(env.balance(carol, USD) == USD(6000));
+            // 250 EUR goes back to carol.
+            BEAST_EXPECT(env.balance(carol, EUR) == EUR(7750));
+
+            // gw clawback 1000 USD from bob with tfClawTwoAssets flag.
+            // then the corresponding EUR will also be clawed back
+            // by gw.
+            env(amm::ammClawback(gw, bob, USD, EUR, USD(1000)),
+                txflags(tfClawTwoAssets),
+                ter(tesSUCCESS));
+            env.close();
+            BEAST_EXPECT(
+                amm.expectBalances(USD(12000), EUR(3000), IOUAmount(6000)));
+
+            BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(4000)));
+            BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(1500)));
+            BEAST_EXPECT(amm.expectLPTokens(carol, IOUAmount(500)));
+            BEAST_EXPECT(env.balance(alice, USD) == USD(2000));
+            BEAST_EXPECT(env.balance(alice, EUR) == EUR(8000));
+            BEAST_EXPECT(env.balance(bob, USD) == USD(5000));
+            // 250 EUR did not go back to bob because tfClawTwoAssets is set.
+            BEAST_EXPECT(env.balance(bob, EUR) == EUR(8000));
+            BEAST_EXPECT(env.balance(carol, USD) == USD(6000));
+            BEAST_EXPECT(env.balance(carol, EUR) == EUR(7750));
+
+            // gw clawback all USD from alice and set tfClawTwoAssets.
+            env(amm::ammClawback(gw, alice, USD, EUR, std::nullopt),
+                txflags(tfClawTwoAssets),
+                ter(tesSUCCESS));
+            env.close();
+            BEAST_EXPECT(
+                amm.expectBalances(USD(4000), EUR(1000), IOUAmount(2000)));
+
+            BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0)));
+            BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(1500)));
+            BEAST_EXPECT(amm.expectLPTokens(carol, IOUAmount(500)));
+            BEAST_EXPECT(env.balance(alice, USD) == USD(2000));
+            BEAST_EXPECT(env.balance(alice, EUR) == EUR(8000));
+            BEAST_EXPECT(env.balance(bob, USD) == USD(5000));
+            BEAST_EXPECT(env.balance(bob, EUR) == EUR(8000));
+            BEAST_EXPECT(env.balance(carol, USD) == USD(6000));
+            BEAST_EXPECT(env.balance(carol, EUR) == EUR(7750));
+        }
+    }
+
+    void
+    testSingleDepositAndClawback(FeatureBitset features)
+    {
+        testcase("test single depoit and clawback");
+        using namespace jtx;
+
+        // Test AMMClawback for USD/XRP pool. Claw back USD, and XRP goes back
+        // to the holder.
+        Env env(*this, features);
+        Account gw{"gateway"};
+        Account alice{"alice"};
+        env.fund(XRP(1000000000), gw, alice);
+        env.close();
+
+        // gw sets asfAllowTrustLineClawback.
+        env(fset(gw, asfAllowTrustLineClawback));
+        env.close();
+        env.require(flags(gw, asfAllowTrustLineClawback));
+
+        // gw issues 1000 USD to Alice.
+        auto const USD = gw["USD"];
+        env.trust(USD(100000), alice);
+        env(pay(gw, alice, USD(1000)));
+        env.close();
+        env.require(balance(alice, gw["USD"](1000)));
+
+        // gw creates AMM pool of XRP/USD.
+        AMM amm(env, gw, XRP(100), USD(400), ter(tesSUCCESS));
+        env.close();
+
+        BEAST_EXPECT(amm.expectBalances(USD(400), XRP(100), IOUAmount(200000)));
+
+        amm.deposit(alice, USD(400));
+        env.close();
+
+        BEAST_EXPECT(amm.expectBalances(
+            USD(800), XRP(100), IOUAmount{2828427124746190, -10}));
+
+        auto aliceXrpBalance = env.balance(alice, XRP);
+
+        env(amm::ammClawback(gw, alice, USD, XRP, USD(400)), ter(tesSUCCESS));
+        env.close();
+
+        BEAST_EXPECT(amm.expectBalances(
+            STAmount(USD, UINT64_C(5656854249492380), -13),
+            XRP(70.710678),
+            IOUAmount(200000)));
+        BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0)));
+        BEAST_EXPECT(expectLedgerEntryRoot(
+            env, alice, aliceXrpBalance + XRP(29.289322)));
+    }
+
+    void
+    run() override
+    {
+        FeatureBitset const all{jtx::supported_amendments()};
+        testInvalidRequest(all);
+        testFeatureDisabled(all - featureAMMClawback);
+        testAMMClawbackSpecificAmount(all);
+        testAMMClawbackExceedBalance(all);
+        testAMMClawbackAll(all);
+        testAMMClawbackSameIssuerAssets(all);
+        testAMMClawbackSameCurrency(all);
+        testAMMClawbackIssuesEachOther(all);
+        testNotHoldingLptoken(all);
+        testAssetFrozen(all);
+        testSingleDepositAndClawback(all);
+    }
+};
+BEAST_DEFINE_TESTSUITE(AMMClawback, app, ripple);
+}  // namespace test
+}  // namespace ripple
diff --git a/src/test/app/AMM_test.cpp b/src/test/app/AMM_test.cpp
index ceddc019504..8e764390e9a 100644
--- a/src/test/app/AMM_test.cpp
+++ b/src/test/app/AMM_test.cpp
@@ -416,25 +416,10 @@ struct AMM_test : public jtx::AMMTest
             AMM ammAlice1(
                 env, alice, USD(10'000), USD1(10'000), ter(terNO_RIPPLE));
         }
-
-        // Issuer has clawback enabled
-        {
-            Env env(*this);
-            env.fund(XRP(1'000), gw);
-            env(fset(gw, asfAllowTrustLineClawback));
-            fund(env, gw, {alice}, XRP(1'000), {USD(1'000)}, Fund::Acct);
-            env.close();
-            AMM amm(env, gw, XRP(100), USD(100), ter(tecNO_PERMISSION));
-            AMM amm1(env, alice, USD(100), XRP(100), ter(tecNO_PERMISSION));
-            env(fclear(gw, asfAllowTrustLineClawback));
-            env.close();
-            // Can't be cleared
-            AMM amm2(env, gw, XRP(100), USD(100), ter(tecNO_PERMISSION));
-        }
     }
 
     void
-    testInvalidDeposit()
+    testInvalidDeposit(FeatureBitset features)
     {
         testcase("Invalid Deposit");
 
@@ -869,62 +854,112 @@ struct AMM_test : public jtx::AMMTest
         });
 
         // Globally frozen asset
-        testAMM([&](AMM& ammAlice, Env& env) {
-            env(fset(gw, asfGlobalFreeze));
-            // Can deposit non-frozen token
-            ammAlice.deposit(carol, XRP(100));
-            ammAlice.deposit(
-                carol,
-                USD(100),
-                std::nullopt,
-                std::nullopt,
-                std::nullopt,
-                ter(tecFROZEN));
-            ammAlice.deposit(
-                carol, 1'000'000, std::nullopt, std::nullopt, ter(tecFROZEN));
-            ammAlice.deposit(
-                carol,
-                XRP(100),
-                USD(100),
-                std::nullopt,
-                std::nullopt,
-                ter(tecFROZEN));
-        });
+        testAMM(
+            [&](AMM& ammAlice, Env& env) {
+                env(fset(gw, asfGlobalFreeze));
+                if (!features[featureAMMClawback])
+                    // If the issuer set global freeze, the holder still can
+                    // deposit the other non-frozen token when AMMClawback is
+                    // not enabled.
+                    ammAlice.deposit(carol, XRP(100));
+                else
+                    // If the issuer set global freeze, the holder cannot
+                    // deposit the other non-frozen token when AMMClawback is
+                    // enabled.
+                    ammAlice.deposit(
+                        carol,
+                        XRP(100),
+                        std::nullopt,
+                        std::nullopt,
+                        std::nullopt,
+                        ter(tecFROZEN));
+                ammAlice.deposit(
+                    carol,
+                    USD(100),
+                    std::nullopt,
+                    std::nullopt,
+                    std::nullopt,
+                    ter(tecFROZEN));
+                ammAlice.deposit(
+                    carol,
+                    1'000'000,
+                    std::nullopt,
+                    std::nullopt,
+                    ter(tecFROZEN));
+                ammAlice.deposit(
+                    carol,
+                    XRP(100),
+                    USD(100),
+                    std::nullopt,
+                    std::nullopt,
+                    ter(tecFROZEN));
+            },
+            std::nullopt,
+            0,
+            std::nullopt,
+            {features});
 
         // Individually frozen (AMM) account
-        testAMM([&](AMM& ammAlice, Env& env) {
-            env(trust(gw, carol["USD"](0), tfSetFreeze));
-            env.close();
-            // Can deposit non-frozen token
-            ammAlice.deposit(carol, XRP(100));
-            ammAlice.deposit(
-                carol, 1'000'000, std::nullopt, std::nullopt, ter(tecFROZEN));
-            ammAlice.deposit(
-                carol,
-                USD(100),
-                std::nullopt,
-                std::nullopt,
-                std::nullopt,
-                ter(tecFROZEN));
-            env(trust(gw, carol["USD"](0), tfClearFreeze));
-            // Individually frozen AMM
-            env(trust(
-                gw,
-                STAmount{Issue{gw["USD"].currency, ammAlice.ammAccount()}, 0},
-                tfSetFreeze));
-            env.close();
-            // Can deposit non-frozen token
-            ammAlice.deposit(carol, XRP(100));
-            ammAlice.deposit(
-                carol, 1'000'000, std::nullopt, std::nullopt, ter(tecFROZEN));
-            ammAlice.deposit(
-                carol,
-                USD(100),
-                std::nullopt,
-                std::nullopt,
-                std::nullopt,
-                ter(tecFROZEN));
-        });
+        testAMM(
+            [&](AMM& ammAlice, Env& env) {
+                env(trust(gw, carol["USD"](0), tfSetFreeze));
+                env.close();
+                if (!features[featureAMMClawback])
+                    // Can deposit non-frozen token if AMMClawback is not
+                    // enabled
+                    ammAlice.deposit(carol, XRP(100));
+                else
+                    // Cannot deposit non-frozen token if the other token is
+                    // frozen when AMMClawback is enabled
+                    ammAlice.deposit(
+                        carol,
+                        XRP(100),
+                        std::nullopt,
+                        std::nullopt,
+                        std::nullopt,
+                        ter(tecFROZEN));
+
+                ammAlice.deposit(
+                    carol,
+                    1'000'000,
+                    std::nullopt,
+                    std::nullopt,
+                    ter(tecFROZEN));
+                ammAlice.deposit(
+                    carol,
+                    USD(100),
+                    std::nullopt,
+                    std::nullopt,
+                    std::nullopt,
+                    ter(tecFROZEN));
+                env(trust(gw, carol["USD"](0), tfClearFreeze));
+                // Individually frozen AMM
+                env(trust(
+                    gw,
+                    STAmount{
+                        Issue{gw["USD"].currency, ammAlice.ammAccount()}, 0},
+                    tfSetFreeze));
+                env.close();
+                // Can deposit non-frozen token
+                ammAlice.deposit(carol, XRP(100));
+                ammAlice.deposit(
+                    carol,
+                    1'000'000,
+                    std::nullopt,
+                    std::nullopt,
+                    ter(tecFROZEN));
+                ammAlice.deposit(
+                    carol,
+                    USD(100),
+                    std::nullopt,
+                    std::nullopt,
+                    std::nullopt,
+                    ter(tecFROZEN));
+            },
+            std::nullopt,
+            0,
+            std::nullopt,
+            {features});
 
         // Individually frozen (AMM) account with IOU/IOU AMM
         testAMM(
@@ -970,6 +1005,44 @@ struct AMM_test : public jtx::AMMTest
             },
             {{USD(20'000), BTC(0.5)}});
 
+        // Deposit unauthorized token.
+        {
+            Env env(*this, features);
+            env.fund(XRP(1000), gw, alice, bob);
+            env(fset(gw, asfRequireAuth));
+            env.close();
+            env(trust(gw, alice["USD"](100)), txflags(tfSetfAuth));
+            env(trust(alice, gw["USD"](20)));
+            env.close();
+            env(pay(gw, alice, gw["USD"](10)));
+            env.close();
+            env(trust(gw, bob["USD"](100)));
+            env.close();
+
+            AMM amm(env, alice, XRP(10), gw["USD"](10), ter(tesSUCCESS));
+            env.close();
+
+            if (features[featureAMMClawback])
+                // if featureAMMClawback is enabled, bob can not deposit XRP
+                // because he's not authorized to hold the paired token
+                // gw["USD"].
+                amm.deposit(
+                    bob,
+                    XRP(10),
+                    std::nullopt,
+                    std::nullopt,
+                    std::nullopt,
+                    ter(tecNO_AUTH));
+            else
+                amm.deposit(
+                    bob,
+                    XRP(10),
+                    std::nullopt,
+                    std::nullopt,
+                    std::nullopt,
+                    ter(tesSUCCESS));
+        }
+
         // Insufficient XRP balance
         testAMM([&](AMM& ammAlice, Env& env) {
             env.fund(XRP(1'000), bob);
@@ -6862,13 +6935,143 @@ struct AMM_test : public jtx::AMMTest
         }
     }
 
+    void
+    testAMMClawback(FeatureBitset features)
+    {
+        testcase("test clawback from AMM account");
+        using namespace jtx;
+
+        // Issuer has clawback enabled
+        Env env(*this, features);
+        env.fund(XRP(1'000), gw);
+        env(fset(gw, asfAllowTrustLineClawback));
+        fund(env, gw, {alice}, XRP(1'000), {USD(1'000)}, Fund::Acct);
+        env.close();
+
+        // If featureAMMClawback is not enabled, AMMCreate is not allowed for
+        // clawback-enabled issuer
+        if (!features[featureAMMClawback])
+        {
+            AMM amm(env, gw, XRP(100), USD(100), ter(tecNO_PERMISSION));
+            AMM amm1(env, alice, USD(100), XRP(100), ter(tecNO_PERMISSION));
+            env(fclear(gw, asfAllowTrustLineClawback));
+            env.close();
+            // Can't be cleared
+            AMM amm2(env, gw, XRP(100), USD(100), ter(tecNO_PERMISSION));
+        }
+        // If featureAMMClawback is enabled, AMMCreate is allowed for
+        // clawback-enabled issuer. Clawback from the AMM Account is not
+        // allowed, which will return tecAMM_ACCOUNT. We can only use
+        // AMMClawback transaction to claw back from AMM Account.
+        else
+        {
+            AMM amm(env, gw, XRP(100), USD(100), ter(tesSUCCESS));
+            AMM amm1(env, alice, USD(100), XRP(200), ter(tecDUPLICATE));
+
+            // Construct the amount being clawed back using AMM account.
+            // By doing this, we make the clawback transaction's Amount field's
+            // subfield `issuer` to be the AMM account, which means
+            // we are clawing back from an AMM account. This should return an
+            // tecAMM_ACCOUNT error because regular Clawback transaction is not
+            // allowed for clawing back from an AMM account. Please notice the
+            // `issuer` subfield represents the account being clawed back, which
+            // is confusing.
+            Issue usd(USD.issue().currency, amm.ammAccount());
+            auto amount = amountFromString(usd, "10");
+            env(claw(gw, amount), ter(tecAMM_ACCOUNT));
+        }
+    }
+
+    void
+    testAMMDepositWithFrozenAssets(FeatureBitset features)
+    {
+        testcase("test AMMDeposit with frozen assets");
+        using namespace jtx;
+
+        // This lambda function is used to create trustlines
+        // between gw and alice, and create an AMM account.
+        // And also test the callback function.
+        auto testAMMDeposit = [&](Env& env, std::function<void(AMM & amm)> cb) {
+            env.fund(XRP(1'000), gw);
+            fund(env, gw, {alice}, XRP(1'000), {USD(1'000)}, Fund::Acct);
+            env.close();
+            AMM amm(env, alice, XRP(100), USD(100), ter(tesSUCCESS));
+            env(trust(gw, alice["USD"](0), tfSetFreeze));
+            cb(amm);
+        };
+
+        // Deposit two assets, one of which is frozen,
+        // then we should get tecFROZEN error.
+        {
+            Env env(*this, features);
+            testAMMDeposit(env, [&](AMM& amm) {
+                amm.deposit(
+                    alice,
+                    USD(100),
+                    XRP(100),
+                    std::nullopt,
+                    tfTwoAsset,
+                    ter(tecFROZEN));
+            });
+        }
+
+        // Deposit one asset, which is the frozen token,
+        // then we should get tecFROZEN error.
+        {
+            Env env(*this, features);
+            testAMMDeposit(env, [&](AMM& amm) {
+                amm.deposit(
+                    alice,
+                    USD(100),
+                    std::nullopt,
+                    std::nullopt,
+                    tfSingleAsset,
+                    ter(tecFROZEN));
+            });
+        }
+
+        if (features[featureAMMClawback])
+        {
+            // Deposit one asset which is not the frozen token,
+            // but the other asset is frozen. We should get tecFROZEN error
+            // when feature AMMClawback is enabled.
+            Env env(*this, features);
+            testAMMDeposit(env, [&](AMM& amm) {
+                amm.deposit(
+                    alice,
+                    XRP(100),
+                    std::nullopt,
+                    std::nullopt,
+                    tfSingleAsset,
+                    ter(tecFROZEN));
+            });
+        }
+        else
+        {
+            // Deposit one asset which is not the frozen token,
+            // but the other asset is frozen. We will get tecSUCCESS
+            // when feature AMMClawback is not enabled.
+            Env env(*this, features);
+            testAMMDeposit(env, [&](AMM& amm) {
+                amm.deposit(
+                    alice,
+                    XRP(100),
+                    std::nullopt,
+                    std::nullopt,
+                    tfSingleAsset,
+                    ter(tesSUCCESS));
+            });
+        }
+    }
+
     void
     run() override
     {
         FeatureBitset const all{jtx::supported_amendments()};
         testInvalidInstance();
         testInstanceCreate();
-        testInvalidDeposit();
+        testInvalidDeposit(all);
+        testInvalidDeposit(all - featureAMMClawback);
         testDeposit();
         testInvalidWithdraw();
         testWithdraw();
@@ -6908,6 +7111,12 @@ struct AMM_test : public jtx::AMMTest
         testFixAMMOfferBlockedByLOB(all - fixAMMv1_1);
         testLPTokenBalance(all);
         testLPTokenBalance(all - fixAMMv1_1);
+        testAMMClawback(all);
+        testAMMClawback(all - featureAMMClawback);
+        testAMMClawback(all - fixAMMv1_1 - featureAMMClawback);
+        testAMMDepositWithFrozenAssets(all);
+        testAMMDepositWithFrozenAssets(all - featureAMMClawback);
+        testAMMDepositWithFrozenAssets(all - fixAMMv1_1 - featureAMMClawback);
     }
 };
 
diff --git a/src/test/app/MPToken_test.cpp b/src/test/app/MPToken_test.cpp
index fa888faea17..9fdad6a0743 100644
--- a/src/test/app/MPToken_test.cpp
+++ b/src/test/app/MPToken_test.cpp
@@ -1443,6 +1443,17 @@ class MPToken_test : public beast::unit_test::suite
             };
             ammBid(sfBidMin);
             ammBid(sfBidMax);
+            // AMMClawback
+            {
+                Json::Value jv;
+                jv[jss::TransactionType] = jss::AMMClawback;
+                jv[jss::Account] = alice.human();
+                jv[jss::Holder] = carol.human();
+                jv[jss::Asset] = to_json(xrpIssue());
+                jv[jss::Asset2] = to_json(USD.issue());
+                jv[jss::Amount] = mpt.getJson(JsonOptions::none);
+                test(jv, jss::Amount.c_str());
+            }
             // CheckCash
             auto checkCash = [&](SField const& field) {
                 Json::Value jv;
diff --git a/src/test/jtx/AMM.h b/src/test/jtx/AMM.h
index 77b9c8c9ec6..52039f74aea 100644
--- a/src/test/jtx/AMM.h
+++ b/src/test/jtx/AMM.h
@@ -438,6 +438,14 @@ trust(
     std::uint32_t flags = 0);
 Json::Value
 pay(Account const& account, AccountID const& to, STAmount const& amount);
+
+Json::Value
+ammClawback(
+    Account const& issuer,
+    Account const& holder,
+    Issue const& asset,
+    Issue const& asset2,
+    std::optional<STAmount> const& amount);
 }  // namespace amm
 
 }  // namespace jtx
diff --git a/src/test/jtx/impl/AMM.cpp b/src/test/jtx/impl/AMM.cpp
index 4ef4fef7c1e..089d3508d70 100644
--- a/src/test/jtx/impl/AMM.cpp
+++ b/src/test/jtx/impl/AMM.cpp
@@ -823,6 +823,26 @@ pay(Account const& account, AccountID const& to, STAmount const& amount)
     jv[jss::Flags] = tfUniversal;
     return jv;
 }
+
+Json::Value
+ammClawback(
+    Account const& issuer,
+    Account const& holder,
+    Issue const& asset,
+    Issue const& asset2,
+    std::optional<STAmount> const& amount)
+{
+    Json::Value jv;
+    jv[jss::TransactionType] = jss::AMMClawback;
+    jv[jss::Account] = issuer.human();
+    jv[jss::Holder] = holder.human();
+    jv[jss::Asset] = to_json(asset);
+    jv[jss::Asset2] = to_json(asset2);
+    if (amount)
+        jv[jss::Amount] = amount->getJson(JsonOptions::none);
+
+    return jv;
+}
 }  // namespace amm
 }  // namespace jtx
 }  // namespace test
diff --git a/src/test/rpc/Status_test.cpp b/src/test/rpc/Status_test.cpp
index 1ae8b23c66c..c68131e8131 100644
--- a/src/test/rpc/Status_test.cpp
+++ b/src/test/rpc/Status_test.cpp
@@ -76,7 +76,7 @@ class codeString_test : public beast::unit_test::suite
 
         {
             auto s = codeString(temBAD_AMOUNT);
-            expect(s == "temBAD_AMOUNT: Can only send positive amounts.", s);
+            expect(s == "temBAD_AMOUNT: Malformed: Bad amount.", s);
         }
 
         {
@@ -176,7 +176,7 @@ class fillJson_test : public beast::unit_test::suite
             "temBAD_AMOUNT",
             temBAD_AMOUNT,
             {},
-            "temBAD_AMOUNT: Can only send positive amounts.");
+            "temBAD_AMOUNT: Malformed: Bad amount.");
 
         expectFill(
             "rpcBAD_SYNTAX",
diff --git a/src/xrpld/app/tx/detail/AMMClawback.cpp b/src/xrpld/app/tx/detail/AMMClawback.cpp
new file mode 100644
index 00000000000..468a5a4c6a2
--- /dev/null
+++ b/src/xrpld/app/tx/detail/AMMClawback.cpp
@@ -0,0 +1,290 @@
+//------------------------------------------------------------------------------
+/*
+  This file is part of rippled: https://github.com/ripple/rippled
+  Copyright (c) 2024 Ripple Labs Inc.
+
+  Permission to use, copy, modify, and/or distribute this software for any
+  purpose  with  or without fee is hereby granted, provided that the above
+  copyright notice and this permission notice appear in all copies.
+
+  THE  SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+  WITH  REGARD  TO  THIS  SOFTWARE  INCLUDING  ALL  IMPLIED  WARRANTIES  OF
+  MERCHANTABILITY  AND  FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+  ANY  SPECIAL ,  DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+  WHATSOEVER  RESULTING  FROM  LOSS  OF USE, DATA OR PROFITS, WHETHER IN AN
+  ACTION  OF  CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+//==============================================================================
+
+#include <xrpld/app/misc/AMMHelpers.h>
+#include <xrpld/app/misc/AMMUtils.h>
+#include <xrpld/app/tx/detail/AMMClawback.h>
+#include <xrpld/app/tx/detail/AMMWithdraw.h>
+#include <xrpld/ledger/Sandbox.h>
+#include <xrpld/ledger/View.h>
+#include <xrpl/protocol/AMMCore.h>
+#include <xrpl/protocol/Feature.h>
+#include <xrpl/protocol/Indexes.h>
+#include <xrpl/protocol/Protocol.h>
+#include <xrpl/protocol/TxFlags.h>
+#include <xrpl/protocol/st.h>
+#include <tuple>
+
+namespace ripple {
+
+NotTEC
+AMMClawback::preflight(PreflightContext const& ctx)
+{
+    if (!ctx.rules.enabled(featureAMMClawback))
+        return temDISABLED;
+
+    if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
+        return ret;  // LCOV_EXCL_LINE
+
+    if (ctx.tx.getFlags() & tfAMMClawbackMask)
+        return temINVALID_FLAG;
+
+    AccountID const issuer = ctx.tx[sfAccount];
+    AccountID const holder = ctx.tx[sfHolder];
+
+    if (issuer == holder)
+    {
+        JLOG(ctx.j.trace())
+            << "AMMClawback: holder cannot be the same as issuer.";
+        return temMALFORMED;
+    }
+
+    std::optional<STAmount> const clawAmount = ctx.tx[~sfAmount];
+    auto const asset = ctx.tx[sfAsset];
+
+    if (isXRP(asset))
+        return temMALFORMED;
+
+    if (asset.account != issuer)
+    {
+        JLOG(ctx.j.trace()) << "AMMClawback: Asset's account does not "
+                               "match Account field.";
+        return temMALFORMED;
+    }
+
+    if (clawAmount && clawAmount->issue() != asset)
+    {
+        JLOG(ctx.j.trace()) << "AMMClawback: Amount's issuer/currency subfield "
+                               "does not match Asset field";
+        return temBAD_AMOUNT;
+    }
+
+    if (clawAmount && *clawAmount <= beast::zero)
+        return temBAD_AMOUNT;
+
+    return preflight2(ctx);
+}
+
+TER
+AMMClawback::preclaim(PreclaimContext const& ctx)
+{
+    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
+
+    if (!ctx.view.read(keylet::account(ctx.tx[sfHolder])))
+        return terNO_ACCOUNT;
+
+    auto const ammSle = ctx.view.read(keylet::amm(asset, asset2));
+    if (!ammSle)
+    {
+        JLOG(ctx.j.debug()) << "AMM Clawback: Invalid asset pair.";
+        return terNO_AMM;
+    }
+
+    std::uint32_t const issuerFlagsIn = sleIssuer->getFieldU32(sfFlags);
+
+    // If AllowTrustLineClawback is not set or NoFreeze is set, return no
+    // permission
+    if (!(issuerFlagsIn & lsfAllowTrustLineClawback) ||
+        (issuerFlagsIn & lsfNoFreeze))
+        return tecNO_PERMISSION;
+
+    auto const flags = ctx.tx.getFlags();
+    if (flags & tfClawTwoAssets && asset.account != asset2.account)
+    {
+        JLOG(ctx.j.trace())
+            << "AMMClawback: tfClawTwoAssets can only be enabled when two "
+               "assets in the AMM pool are both issued by the issuer";
+        return tecNO_PERMISSION;
+    }
+
+    return tesSUCCESS;
+}
+
+TER
+AMMClawback::doApply()
+{
+    Sandbox sb(&ctx_.view());
+
+    auto const ter = applyGuts(sb);
+    if (ter == tesSUCCESS)
+        sb.apply(ctx_.rawView());
+
+    return ter;
+}
+
+TER
+AMMClawback::applyGuts(Sandbox& sb)
+{
+    std::optional<STAmount> const clawAmount = ctx_.tx[~sfAmount];
+    AccountID const issuer = ctx_.tx[sfAccount];
+    AccountID const holder = ctx_.tx[sfHolder];
+    Issue const asset = ctx_.tx[sfAsset];
+    Issue const asset2 = ctx_.tx[sfAsset2];
+
+    auto ammSle = sb.peek(keylet::amm(asset, asset2));
+    if (!ammSle)
+        return tecINTERNAL;  // LCOV_EXCL_LINE
+
+    auto const ammAccount = (*ammSle)[sfAccount];
+    auto const accountSle = sb.read(keylet::account(ammAccount));
+    if (!accountSle)
+        return tecINTERNAL;  // LCOV_EXCL_LINE
+
+    auto const expected = ammHolds(
+        sb,
+        *ammSle,
+        asset,
+        asset2,
+        FreezeHandling::fhIGNORE_FREEZE,
+        ctx_.journal);
+
+    if (!expected)
+        return expected.error();  // LCOV_EXCL_LINE
+    auto const [amountBalance, amount2Balance, lptAMMBalance] = *expected;
+
+    TER result;
+    STAmount newLPTokenBalance;
+    STAmount amountWithdraw;
+    std::optional<STAmount> amount2Withdraw;
+
+    auto const holdLPtokens = ammLPHolds(sb, *ammSle, holder, j_);
+    if (holdLPtokens == beast::zero)
+        return tecAMM_BALANCE;
+
+    if (!clawAmount)
+        // Because we are doing a two-asset withdrawal,
+        // tfee is actually not used, so pass tfee as 0.
+        std::tie(result, newLPTokenBalance, amountWithdraw, amount2Withdraw) =
+            AMMWithdraw::equalWithdrawTokens(
+                sb,
+                *ammSle,
+                holder,
+                ammAccount,
+                amountBalance,
+                amount2Balance,
+                lptAMMBalance,
+                holdLPtokens,
+                holdLPtokens,
+                0,
+                FreezeHandling::fhIGNORE_FREEZE,
+                WithdrawAll::Yes,
+                ctx_.journal);
+    else
+        std::tie(result, newLPTokenBalance, amountWithdraw, amount2Withdraw) =
+            equalWithdrawMatchingOneAmount(
+                sb,
+                *ammSle,
+                holder,
+                ammAccount,
+                amountBalance,
+                amount2Balance,
+                lptAMMBalance,
+                holdLPtokens,
+                *clawAmount);
+
+    if (result != tesSUCCESS)
+        return result;  // LCOV_EXCL_LINE
+
+    auto const res = AMMWithdraw::deleteAMMAccountIfEmpty(
+        sb, ammSle, newLPTokenBalance, asset, asset2, j_);
+    if (!res.second)
+        return res.first;  // LCOV_EXCL_LINE
+
+    JLOG(ctx_.journal.trace())
+        << "AMM Withdraw during AMMClawback: lptoken new balance: "
+        << to_string(newLPTokenBalance.iou())
+        << " old balance: " << to_string(lptAMMBalance.iou());
+
+    auto const ter = rippleCredit(sb, holder, issuer, amountWithdraw, true, j_);
+    if (ter != tesSUCCESS)
+        return ter;  // LCOV_EXCL_LINE
+
+    // if the issuer issues both assets and sets flag tfClawTwoAssets, we
+    // will claw the paired asset as well. We already checked if
+    // tfClawTwoAssets is enabled, the two assets have to be issued by the
+    // same issuer.
+    if (!amount2Withdraw)
+        return tecINTERNAL;  // LCOV_EXCL_LINE
+
+    auto const flags = ctx_.tx.getFlags();
+    if (flags & tfClawTwoAssets)
+        return rippleCredit(sb, holder, issuer, *amount2Withdraw, true, j_);
+
+    return tesSUCCESS;
+}
+
+std::tuple<TER, STAmount, STAmount, std::optional<STAmount>>
+AMMClawback::equalWithdrawMatchingOneAmount(
+    Sandbox& sb,
+    SLE const& ammSle,
+    AccountID const& holder,
+    AccountID const& ammAccount,
+    STAmount const& amountBalance,
+    STAmount const& amount2Balance,
+    STAmount const& lptAMMBalance,
+    STAmount const& holdLPtokens,
+    STAmount const& amount)
+{
+    auto frac = Number{amount} / amountBalance;
+    auto const amount2Withdraw = amount2Balance * frac;
+
+    auto const lpTokensWithdraw =
+        toSTAmount(lptAMMBalance.issue(), lptAMMBalance * frac);
+    if (lpTokensWithdraw > holdLPtokens)
+        // if lptoken balance less than what the issuer intended to clawback,
+        // clawback all the tokens. Because we are doing a two-asset withdrawal,
+        // tfee is actually not used, so pass tfee as 0.
+        return AMMWithdraw::equalWithdrawTokens(
+            sb,
+            ammSle,
+            holder,
+            ammAccount,
+            amountBalance,
+            amount2Balance,
+            lptAMMBalance,
+            holdLPtokens,
+            holdLPtokens,
+            0,
+            FreezeHandling::fhIGNORE_FREEZE,
+            WithdrawAll::Yes,
+            ctx_.journal);
+
+    // Because we are doing a two-asset withdrawal,
+    // tfee is actually not used, so pass tfee as 0.
+    return AMMWithdraw::withdraw(
+        sb,
+        ammSle,
+        ammAccount,
+        holder,
+        amountBalance,
+        amount,
+        toSTAmount(amount2Balance.issue(), amount2Withdraw),
+        lptAMMBalance,
+        toSTAmount(lptAMMBalance.issue(), lptAMMBalance * frac),
+        0,
+        FreezeHandling::fhIGNORE_FREEZE,
+        WithdrawAll::No,
+        ctx_.journal);
+}
+
+}  // namespace ripple
diff --git a/src/xrpld/app/tx/detail/AMMClawback.h b/src/xrpld/app/tx/detail/AMMClawback.h
new file mode 100644
index 00000000000..fdcfc53e2ca
--- /dev/null
+++ b/src/xrpld/app/tx/detail/AMMClawback.h
@@ -0,0 +1,75 @@
+//------------------------------------------------------------------------------
+/*
+  This file is part of rippled: https://github.com/ripple/rippled
+  Copyright (c) 2024 Ripple Labs Inc.
+
+  Permission to use, copy, modify, and/or distribute this software for any
+  purpose  with  or without fee is hereby granted, provided that the above
+  copyright notice and this permission notice appear in all copies.
+
+  THE  SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+  WITH  REGARD  TO  THIS  SOFTWARE  INCLUDING  ALL  IMPLIED  WARRANTIES  OF
+  MERCHANTABILITY  AND  FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+  ANY  SPECIAL ,  DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+  WHATSOEVER  RESULTING  FROM  LOSS  OF USE, DATA OR PROFITS, WHETHER IN AN
+  ACTION  OF  CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+//==============================================================================
+
+#ifndef RIPPLE_TX_AMMCLAWBACK_H_INCLUDED
+#define RIPPLE_TX_AMMCLAWBACK_H_INCLUDED
+
+#include <xrpld/app/tx/detail/Transactor.h>
+
+namespace ripple {
+class Sandbox;
+class AMMClawback : public Transactor
+{
+public:
+    static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
+
+    explicit AMMClawback(ApplyContext& ctx) : Transactor(ctx)
+    {
+    }
+
+    static NotTEC
+    preflight(PreflightContext const& ctx);
+
+    static TER
+    preclaim(PreclaimContext const& ctx);
+
+    TER
+    doApply() override;
+
+private:
+    TER
+    applyGuts(Sandbox& view);
+
+    /** Withdraw both assets by providing maximum amount of asset1,
+     * asset2's amount will be calculated according to the current proportion.
+     * Since it is two-asset withdrawal, tfee is omitted.
+     * @param view
+     * @param ammAccount current AMM account
+     * @param amountBalance current AMM asset1 balance
+     * @param amount2Balance current AMM asset2 balance
+     * @param lptAMMBalance current AMM LPT balance
+     * @param amount asset1 withdraw amount
+     * @return
+     */
+    std::tuple<TER, STAmount, STAmount, std::optional<STAmount>>
+    equalWithdrawMatchingOneAmount(
+        Sandbox& view,
+        SLE const& ammSle,
+        AccountID const& holder,
+        AccountID const& ammAccount,
+        STAmount const& amountBalance,
+        STAmount const& amount2Balance,
+        STAmount const& lptAMMBalance,
+        STAmount const& holdLPtokens,
+        STAmount const& amount);
+};
+
+}  // namespace ripple
+
+#endif
diff --git a/src/xrpld/app/tx/detail/AMMCreate.cpp b/src/xrpld/app/tx/detail/AMMCreate.cpp
index 237e1afa240..31773166d4a 100644
--- a/src/xrpld/app/tx/detail/AMMCreate.cpp
+++ b/src/xrpld/app/tx/detail/AMMCreate.cpp
@@ -184,7 +184,13 @@ AMMCreate::preclaim(PreclaimContext const& ctx)
         return tecAMM_INVALID_TOKENS;
     }
 
-    // Disallow AMM if the issuer has clawback enabled
+    // If featureAMMClawback is enabled, allow AMMCreate without checking
+    // if the issuer has clawback enabled
+    if (ctx.view.rules().enabled(featureAMMClawback))
+        return tesSUCCESS;
+
+    // Disallow AMM if the issuer has clawback enabled when featureAMMClawback
+    // is not enabled
     auto clawbackDisabled = [&](Issue const& issue) -> TER {
         if (isXRP(issue))
             return tesSUCCESS;
diff --git a/src/xrpld/app/tx/detail/AMMDeposit.cpp b/src/xrpld/app/tx/detail/AMMDeposit.cpp
index 9bbf5b4a60a..3448401eb79 100644
--- a/src/xrpld/app/tx/detail/AMMDeposit.cpp
+++ b/src/xrpld/app/tx/detail/AMMDeposit.cpp
@@ -244,6 +244,37 @@ AMMDeposit::preclaim(PreclaimContext const& ctx)
             : tecUNFUNDED_AMM;
     };
 
+    if (ctx.view.rules().enabled(featureAMMClawback))
+    {
+        // Check if either of the assets is frozen, AMMDeposit is not allowed
+        // if either asset is frozen
+        auto checkAsset = [&](Issue const& asset) -> TER {
+            if (auto const ter = requireAuth(ctx.view, asset, accountID))
+            {
+                JLOG(ctx.j.debug())
+                    << "AMM Deposit: account is not authorized, " << asset;
+                return ter;
+            }
+
+            if (isFrozen(ctx.view, accountID, asset))
+            {
+                JLOG(ctx.j.debug())
+                    << "AMM Deposit: account or currency is frozen, "
+                    << to_string(accountID) << " " << to_string(asset.currency);
+
+                return tecFROZEN;
+            }
+
+            return tesSUCCESS;
+        };
+
+        if (auto const ter = checkAsset(ctx.tx[sfAsset]))
+            return ter;
+
+        if (auto const ter = checkAsset(ctx.tx[sfAsset2]))
+            return ter;
+    }
+
     auto const amount = ctx.tx[~sfAmount];
     auto const amount2 = ctx.tx[~sfAmount2];
     auto const ammAccountID = ammSle->getAccountID(sfAccount);
diff --git a/src/xrpld/app/tx/detail/AMMWithdraw.cpp b/src/xrpld/app/tx/detail/AMMWithdraw.cpp
index 51b512aba0a..0a6f3291b78 100644
--- a/src/xrpld/app/tx/detail/AMMWithdraw.cpp
+++ b/src/xrpld/app/tx/detail/AMMWithdraw.cpp
@@ -22,7 +22,6 @@
 #include <xrpld/app/misc/AMMHelpers.h>
 #include <xrpld/app/misc/AMMUtils.h>
 #include <xrpld/ledger/Sandbox.h>
-#include <xrpld/ledger/View.h>
 #include <xrpl/basics/Number.h>
 #include <xrpl/protocol/AMMCore.h>
 #include <xrpl/protocol/STAccount.h>
@@ -358,6 +357,7 @@ AMMWithdraw::applyGuts(Sandbox& sb)
         if (subTxType & tfTwoAsset)
             return equalWithdrawLimit(
                 sb,
+                *ammSle,
                 ammAccountID,
                 amountBalance,
                 amount2Balance,
@@ -368,6 +368,7 @@ AMMWithdraw::applyGuts(Sandbox& sb)
         if (subTxType & tfOneAssetLPToken || subTxType & tfOneAssetWithdrawAll)
             return singleWithdrawTokens(
                 sb,
+                *ammSle,
                 ammAccountID,
                 amountBalance,
                 lptAMMBalance,
@@ -377,6 +378,7 @@ AMMWithdraw::applyGuts(Sandbox& sb)
         if (subTxType & tfLimitLPToken)
             return singleWithdrawEPrice(
                 sb,
+                *ammSle,
                 ammAccountID,
                 amountBalance,
                 lptAMMBalance,
@@ -385,10 +387,18 @@ AMMWithdraw::applyGuts(Sandbox& sb)
                 tfee);
         if (subTxType & tfSingleAsset)
             return singleWithdraw(
-                sb, ammAccountID, amountBalance, lptAMMBalance, *amount, tfee);
+                sb,
+                *ammSle,
+                ammAccountID,
+                amountBalance,
+                lptAMMBalance,
+                *amount,
+                tfee);
         if (subTxType & tfLPToken || subTxType & tfWithdrawAll)
+        {
             return equalWithdrawTokens(
                 sb,
+                *ammSle,
                 ammAccountID,
                 amountBalance,
                 amount2Balance,
@@ -396,6 +406,7 @@ AMMWithdraw::applyGuts(Sandbox& sb)
                 lpTokens,
                 *lpTokensWithdraw,
                 tfee);
+        }
         // should not happen.
         // LCOV_EXCL_START
         JLOG(j_.error()) << "AMM Withdraw: invalid options.";
@@ -406,22 +417,12 @@ AMMWithdraw::applyGuts(Sandbox& sb)
     if (result != tesSUCCESS)
         return {result, false};
 
-    bool updateBalance = true;
-    if (newLPTokenBalance == beast::zero)
-    {
-        if (auto const ter =
-                deleteAMMAccount(sb, ctx_.tx[sfAsset], ctx_.tx[sfAsset2], j_);
-            ter != tesSUCCESS && ter != tecINCOMPLETE)
-            return {ter, false};
-        else
-            updateBalance = (ter == tecINCOMPLETE);
-    }
-
-    if (updateBalance)
-    {
-        ammSle->setFieldAmount(sfLPTokenBalance, newLPTokenBalance);
-        sb.update(ammSle);
-    }
+    auto const res = deleteAMMAccountIfEmpty(
+        sb, ammSle, newLPTokenBalance, ctx_.tx[sfAsset], ctx_.tx[sfAsset2], j_);
+    // LCOV_EXCL_START
+    if (!res.second)
+        return {res.first, false};
+    // LCOV_EXCL_STOP
 
     JLOG(ctx_.journal.trace())
         << "AMM Withdraw: tokens " << to_string(newLPTokenBalance.iou()) << " "
@@ -447,6 +448,7 @@ AMMWithdraw::doApply()
 std::pair<TER, STAmount>
 AMMWithdraw::withdraw(
     Sandbox& view,
+    SLE const& ammSle,
     AccountID const& ammAccount,
     STAmount const& amountBalance,
     STAmount const& amountWithdraw,
@@ -455,27 +457,60 @@ AMMWithdraw::withdraw(
     STAmount const& lpTokensWithdraw,
     std::uint16_t tfee)
 {
-    auto const ammSle =
-        ctx_.view().read(keylet::amm(ctx_.tx[sfAsset], ctx_.tx[sfAsset2]));
-    if (!ammSle)
-        return {tecINTERNAL, STAmount{}};  // LCOV_EXCL_LINE
-    auto const lpTokens = ammLPHolds(view, *ammSle, account_, ctx_.journal);
+    TER ter;
+    STAmount newLPTokenBalance;
+    std::tie(ter, newLPTokenBalance, std::ignore, std::ignore) = withdraw(
+        view,
+        ammSle,
+        ammAccount,
+        account_,
+        amountBalance,
+        amountWithdraw,
+        amount2Withdraw,
+        lpTokensAMMBalance,
+        lpTokensWithdraw,
+        tfee,
+        FreezeHandling::fhZERO_IF_FROZEN,
+        isWithdrawAll(ctx_.tx),
+        j_);
+    return {ter, newLPTokenBalance};
+}
+
+std::tuple<TER, STAmount, STAmount, std::optional<STAmount>>
+AMMWithdraw::withdraw(
+    Sandbox& view,
+    SLE const& ammSle,
+    AccountID const& ammAccount,
+    AccountID const& account,
+    STAmount const& amountBalance,
+    STAmount const& amountWithdraw,
+    std::optional<STAmount> const& amount2Withdraw,
+    STAmount const& lpTokensAMMBalance,
+    STAmount const& lpTokensWithdraw,
+    std::uint16_t tfee,
+    FreezeHandling freezeHandling,
+    WithdrawAll withdrawAll,
+    beast::Journal const& journal)
+{
+    auto const lpTokens = ammLPHolds(view, ammSle, account, journal);
     auto const expected = ammHolds(
         view,
-        *ammSle,
+        ammSle,
         amountWithdraw.issue(),
         std::nullopt,
-        FreezeHandling::fhZERO_IF_FROZEN,
-        j_);
+        freezeHandling,
+        journal);
+    // LCOV_EXCL_START
     if (!expected)
-        return {expected.error(), STAmount{}};
+        return {expected.error(), STAmount{}, STAmount{}, STAmount{}};
+    // LCOV_EXCL_STOP
     auto const [curBalance, curBalance2, _] = *expected;
     (void)_;
 
     auto const
         [amountWithdrawActual, amount2WithdrawActual, lpTokensWithdrawActual] =
             [&]() -> std::tuple<STAmount, std::optional<STAmount>, STAmount> {
-        if (!(ctx_.tx[sfFlags] & (tfWithdrawAll | tfOneAssetWithdrawAll)))
+        if (withdrawAll == WithdrawAll::No)
             return adjustAmountsByLPTokens(
                 amountBalance,
                 amountWithdraw,
@@ -491,11 +526,11 @@ AMMWithdraw::withdraw(
     if (lpTokensWithdrawActual <= beast::zero ||
         lpTokensWithdrawActual > lpTokens)
     {
-        JLOG(ctx_.journal.debug())
+        JLOG(journal.debug())
             << "AMM Withdraw: failed to withdraw, invalid LP tokens: "
             << lpTokensWithdrawActual << " " << lpTokens << " "
             << lpTokensAMMBalance;
-        return {tecAMM_INVALID_TOKENS, STAmount{}};
+        return {tecAMM_INVALID_TOKENS, STAmount{}, STAmount{}, STAmount{}};
     }
 
     // Should not happen since the only LP on last withdraw
@@ -503,11 +538,13 @@ AMMWithdraw::withdraw(
     if (view.rules().enabled(fixAMMv1_1) &&
         lpTokensWithdrawActual > lpTokensAMMBalance)
     {
-        JLOG(ctx_.journal.debug())
+        // LCOV_EXCL_START
+        JLOG(journal.debug())
             << "AMM Withdraw: failed to withdraw, unexpected LP tokens: "
             << lpTokensWithdrawActual << " " << lpTokens << " "
             << lpTokensAMMBalance;
-        return {tecINTERNAL, STAmount{}};
+        return {tecINTERNAL, STAmount{}, STAmount{}, STAmount{}};
+        // LCOV_EXCL_STOP
     }
 
     // Withdrawing one side of the pool
@@ -516,12 +553,12 @@ AMMWithdraw::withdraw(
         (amount2WithdrawActual == curBalance2 &&
          amountWithdrawActual != curBalance))
     {
-        JLOG(ctx_.journal.debug())
+        JLOG(journal.debug())
             << "AMM Withdraw: failed to withdraw one side of the pool "
             << " curBalance: " << curBalance << " " << amountWithdrawActual
             << " lpTokensBalance: " << lpTokensWithdraw << " lptBalance "
             << lpTokensAMMBalance;
-        return {tecAMM_BALANCE, STAmount{}};
+        return {tecAMM_BALANCE, STAmount{}, STAmount{}, STAmount{}};
     }
 
     // May happen if withdrawing an amount close to one side of the pool
@@ -529,42 +566,44 @@ AMMWithdraw::withdraw(
         (amountWithdrawActual != curBalance ||
          amount2WithdrawActual != curBalance2))
     {
-        JLOG(ctx_.journal.debug())
+        JLOG(journal.debug())
             << "AMM Withdraw: failed to withdraw all tokens "
             << " curBalance: " << curBalance << " " << amountWithdrawActual
             << " curBalance2: " << amount2WithdrawActual.value_or(STAmount{0})
             << " lpTokensBalance: " << lpTokensWithdraw << " lptBalance "
             << lpTokensAMMBalance;
-        return {tecAMM_BALANCE, STAmount{}};
+        return {tecAMM_BALANCE, STAmount{}, STAmount{}, STAmount{}};
     }
 
     // Withdrawing more than the pool's balance
     if (amountWithdrawActual > curBalance ||
         amount2WithdrawActual > curBalance2)
     {
-        JLOG(ctx_.journal.debug())
+        JLOG(journal.debug())
             << "AMM Withdraw: withdrawing more than the pool's balance "
             << " curBalance: " << curBalance << " " << amountWithdrawActual
             << " curBalance2: " << curBalance2 << " "
             << (amount2WithdrawActual ? *amount2WithdrawActual : STAmount{})
             << " lpTokensBalance: " << lpTokensWithdraw << " lptBalance "
             << lpTokensAMMBalance;
-        return {tecAMM_BALANCE, STAmount{}};
+        return {tecAMM_BALANCE, STAmount{}, STAmount{}, STAmount{}};
     }
 
     // Withdraw amountWithdraw
     auto res = accountSend(
         view,
         ammAccount,
-        account_,
+        account,
         amountWithdrawActual,
-        ctx_.journal,
+        journal,
         WaiveTransferFee::Yes);
     if (res != tesSUCCESS)
     {
-        JLOG(ctx_.journal.debug())
+        // LCOV_EXCL_START
+        JLOG(journal.debug())
             << "AMM Withdraw: failed to withdraw " << amountWithdrawActual;
-        return {res, STAmount{}};
+        return {res, STAmount{}, STAmount{}, STAmount{}};
+        // LCOV_EXCL_STOP
     }
 
     // Withdraw amount2Withdraw
@@ -573,40 +612,46 @@ AMMWithdraw::withdraw(
         res = accountSend(
             view,
             ammAccount,
-            account_,
+            account,
             *amount2WithdrawActual,
-            ctx_.journal,
+            journal,
             WaiveTransferFee::Yes);
         if (res != tesSUCCESS)
         {
-            JLOG(ctx_.journal.debug()) << "AMM Withdraw: failed to withdraw "
-                                       << *amount2WithdrawActual;
-            return {res, STAmount{}};
+            // LCOV_EXCL_START
+            JLOG(journal.debug()) << "AMM Withdraw: failed to withdraw "
+                                  << *amount2WithdrawActual;
+            return {res, STAmount{}, STAmount{}, STAmount{}};
+            // LCOV_EXCL_STOP
         }
     }
 
     // Withdraw LP tokens
     res = redeemIOU(
         view,
-        account_,
+        account,
         lpTokensWithdrawActual,
         lpTokensWithdrawActual.issue(),
-        ctx_.journal);
+        journal);
     if (res != tesSUCCESS)
     {
-        JLOG(ctx_.journal.debug())
-            << "AMM Withdraw: failed to withdraw LPTokens";
-        return {res, STAmount{}};
+        // LCOV_EXCL_START
+        JLOG(journal.debug()) << "AMM Withdraw: failed to withdraw LPTokens";
+        return {res, STAmount{}, STAmount{}, STAmount{}};
+        // LCOV_EXCL_STOP
     }
 
-    return {tesSUCCESS, lpTokensAMMBalance - lpTokensWithdrawActual};
+    return std::make_tuple(
+        tesSUCCESS,
+        lpTokensAMMBalance - lpTokensWithdrawActual,
+        amountWithdrawActual,
+        amount2WithdrawActual);
 }
 
-/** Proportional withdrawal of pool assets for the amount of LPTokens.
- */
 std::pair<TER, STAmount>
 AMMWithdraw::equalWithdrawTokens(
     Sandbox& view,
+    SLE const& ammSle,
     AccountID const& ammAccount,
     STAmount const& amountBalance,
     STAmount const& amount2Balance,
@@ -614,20 +659,94 @@ AMMWithdraw::equalWithdrawTokens(
     STAmount const& lpTokens,
     STAmount const& lpTokensWithdraw,
     std::uint16_t tfee)
+{
+    TER ter;
+    STAmount newLPTokenBalance;
+    std::tie(ter, newLPTokenBalance, std::ignore, std::ignore) =
+        equalWithdrawTokens(
+            view,
+            ammSle,
+            account_,
+            ammAccount,
+            amountBalance,
+            amount2Balance,
+            lptAMMBalance,
+            lpTokens,
+            lpTokensWithdraw,
+            tfee,
+            FreezeHandling::fhZERO_IF_FROZEN,
+            isWithdrawAll(ctx_.tx),
+            ctx_.journal);
+    return {ter, newLPTokenBalance};
+}
+
+std::pair<TER, bool>
+AMMWithdraw::deleteAMMAccountIfEmpty(
+    Sandbox& sb,
+    std::shared_ptr<SLE> const ammSle,
+    STAmount const& lpTokenBalance,
+    Issue const& issue1,
+    Issue const& issue2,
+    beast::Journal const& journal)
+{
+    TER ter;
+    bool updateBalance = true;
+    if (lpTokenBalance == beast::zero)
+    {
+        ter = deleteAMMAccount(sb, issue1, issue2, journal);
+        if (ter != tesSUCCESS && ter != tecINCOMPLETE)
+            return {ter, false};  // LCOV_EXCL_LINE
+        else
+            updateBalance = (ter == tecINCOMPLETE);
+    }
+
+    if (updateBalance)
+    {
+        ammSle->setFieldAmount(sfLPTokenBalance, lpTokenBalance);
+        sb.update(ammSle);
+    }
+
+    return {ter, true};
+}
+
+/** Proportional withdrawal of pool assets for the amount of LPTokens.
+ */
+std::tuple<TER, STAmount, STAmount, std::optional<STAmount>>
+AMMWithdraw::equalWithdrawTokens(
+    Sandbox& view,
+    SLE const& ammSle,
+    AccountID const account,
+    AccountID const& ammAccount,
+    STAmount const& amountBalance,
+    STAmount const& amount2Balance,
+    STAmount const& lptAMMBalance,
+    STAmount const& lpTokens,
+    STAmount const& lpTokensWithdraw,
+    std::uint16_t tfee,
+    FreezeHandling freezeHanding,
+    WithdrawAll withdrawAll,
+    beast::Journal const& journal)
 {
     try
     {
         // Withdrawing all tokens in the pool
         if (lpTokensWithdraw == lptAMMBalance)
+        {
             return withdraw(
                 view,
+                ammSle,
                 ammAccount,
+                account,
                 amountBalance,
                 amountBalance,
                 amount2Balance,
                 lptAMMBalance,
                 lpTokensWithdraw,
-                tfee);
+                tfee,
+                freezeHanding,
+                WithdrawAll::Yes,
+                journal);
+        }
 
         auto const frac = divide(lpTokensWithdraw, lptAMMBalance, noIssue());
         auto const withdrawAmount =
@@ -639,25 +758,30 @@ AMMWithdraw::equalWithdrawTokens(
         // withdrawal due to round off. Fail so the user withdraws
         // more tokens.
         if (withdrawAmount == beast::zero || withdraw2Amount == beast::zero)
-            return {tecAMM_FAILED, STAmount{}};
+            return {tecAMM_FAILED, STAmount{}, STAmount{}, STAmount{}};
 
         return withdraw(
             view,
+            ammSle,
             ammAccount,
+            account,
             amountBalance,
             withdrawAmount,
             withdraw2Amount,
             lptAMMBalance,
             lpTokensWithdraw,
-            tfee);
+            tfee,
+            freezeHanding,
+            withdrawAll,
+            journal);
     }
     // LCOV_EXCL_START
     catch (std::exception const& e)
     {
-        JLOG(j_.error()) << "AMMWithdraw::equalWithdrawTokens exception "
-                         << e.what();
+        JLOG(journal.error())
+            << "AMMWithdraw::equalWithdrawTokens exception " << e.what();
     }
-    return {tecINTERNAL, STAmount{}};
+    return {tecINTERNAL, STAmount{}, STAmount{}, STAmount{}};
     // LCOV_EXCL_STOP
 }
 
@@ -689,6 +813,7 @@ AMMWithdraw::equalWithdrawTokens(
 std::pair<TER, STAmount>
 AMMWithdraw::equalWithdrawLimit(
     Sandbox& view,
+    SLE const& ammSle,
     AccountID const& ammAccount,
     STAmount const& amountBalance,
     STAmount const& amount2Balance,
@@ -700,8 +825,10 @@ AMMWithdraw::equalWithdrawLimit(
     auto frac = Number{amount} / amountBalance;
     auto const amount2Withdraw = amount2Balance * frac;
     if (amount2Withdraw <= amount2)
+    {
         return withdraw(
             view,
+            ammSle,
             ammAccount,
             amountBalance,
             amount,
@@ -709,11 +836,14 @@ AMMWithdraw::equalWithdrawLimit(
             lptAMMBalance,
             toSTAmount(lptAMMBalance.issue(), lptAMMBalance * frac),
             tfee);
+    }
+
     frac = Number{amount2} / amount2Balance;
     auto const amountWithdraw = amountBalance * frac;
     assert(amountWithdraw <= amount);
     return withdraw(
         view,
+        ammSle,
         ammAccount,
         amountBalance,
         toSTAmount(amount.issue(), amountWithdraw),
@@ -731,6 +861,7 @@ AMMWithdraw::equalWithdrawLimit(
 std::pair<TER, STAmount>
 AMMWithdraw::singleWithdraw(
     Sandbox& view,
+    SLE const& ammSle,
     AccountID const& ammAccount,
     STAmount const& amountBalance,
     STAmount const& lptAMMBalance,
@@ -740,8 +871,10 @@ AMMWithdraw::singleWithdraw(
     auto const tokens = lpTokensOut(amountBalance, amount, lptAMMBalance, tfee);
     if (tokens == beast::zero)
         return {tecAMM_FAILED, STAmount{}};
+
     return withdraw(
         view,
+        ammSle,
         ammAccount,
         amountBalance,
         amount,
@@ -764,6 +897,7 @@ AMMWithdraw::singleWithdraw(
 std::pair<TER, STAmount>
 AMMWithdraw::singleWithdrawTokens(
     Sandbox& view,
+    SLE const& ammSle,
     AccountID const& ammAccount,
     STAmount const& amountBalance,
     STAmount const& lptAMMBalance,
@@ -774,8 +908,10 @@ AMMWithdraw::singleWithdrawTokens(
     auto const amountWithdraw =
         withdrawByTokens(amountBalance, lptAMMBalance, lpTokensWithdraw, tfee);
     if (amount == beast::zero || amountWithdraw >= amount)
+    {
         return withdraw(
             view,
+            ammSle,
             ammAccount,
             amountBalance,
             amountWithdraw,
@@ -783,6 +919,8 @@ AMMWithdraw::singleWithdrawTokens(
             lptAMMBalance,
             lpTokensWithdraw,
             tfee);
+    }
+
     return {tecAMM_FAILED, STAmount{}};
 }
 
@@ -808,6 +946,7 @@ AMMWithdraw::singleWithdrawTokens(
 std::pair<TER, STAmount>
 AMMWithdraw::singleWithdrawEPrice(
     Sandbox& view,
+    SLE const& ammSle,
     AccountID const& ammAccount,
     STAmount const& amountBalance,
     STAmount const& lptAMMBalance,
@@ -833,8 +972,10 @@ AMMWithdraw::singleWithdrawEPrice(
         return {tecAMM_FAILED, STAmount{}};
     auto const amountWithdraw = toSTAmount(amount.issue(), tokens / ePrice);
     if (amount == beast::zero || amountWithdraw >= amount)
+    {
         return withdraw(
             view,
+            ammSle,
             ammAccount,
             amountBalance,
             amountWithdraw,
@@ -842,8 +983,16 @@ AMMWithdraw::singleWithdrawEPrice(
             lptAMMBalance,
             toSTAmount(lptAMMBalance.issue(), tokens),
             tfee);
+    }
 
     return {tecAMM_FAILED, STAmount{}};
 }
 
+WithdrawAll
+AMMWithdraw::isWithdrawAll(STTx const& tx)
+{
+    if (tx[sfFlags] & (tfWithdrawAll | tfOneAssetWithdrawAll))
+        return WithdrawAll::Yes;
+    return WithdrawAll::No;
+}
 }  // namespace ripple
diff --git a/src/xrpld/app/tx/detail/AMMWithdraw.h b/src/xrpld/app/tx/detail/AMMWithdraw.h
index 9e9920aa5f6..f5b6b52e5ba 100644
--- a/src/xrpld/app/tx/detail/AMMWithdraw.h
+++ b/src/xrpld/app/tx/detail/AMMWithdraw.h
@@ -21,6 +21,7 @@
 #define RIPPLE_TX_AMMWITHDRAW_H_INCLUDED
 
 #include <xrpld/app/tx/detail/Transactor.h>
+#include <xrpld/ledger/View.h>
 
 namespace ripple {
 
@@ -62,6 +63,9 @@ class Sandbox;
  * @see [XLS30d:AMMWithdraw
  * transaction](https://github.com/XRPLF/XRPL-Standards/discussions/78)
  */
+
+enum class WithdrawAll : bool { No = false, Yes };
+
 class AMMWithdraw : public Transactor
 {
 public:
@@ -80,6 +84,76 @@ class AMMWithdraw : public Transactor
     TER
     doApply() override;
 
+    /** Equal-asset withdrawal (LPTokens) of some AMM instance pools
+     * shares represented by the number of LPTokens .
+     * The trading fee is not charged.
+     * @param view
+     * @param ammAccount
+     * @param amountBalance current LP asset1 balance
+     * @param amount2Balance current LP asset2 balance
+     * @param lptAMMBalance current AMM LPT balance
+     * @param lpTokens current LPT balance
+     * @param lpTokensWithdraw amount of tokens to withdraw
+     * @param tfee trading fee in basis points
+     * @param withdrawAll if withdrawing all lptokens
+     * @return
+     */
+    static std::tuple<TER, STAmount, STAmount, std::optional<STAmount>>
+    equalWithdrawTokens(
+        Sandbox& view,
+        SLE const& ammSle,
+        AccountID const account,
+        AccountID const& ammAccount,
+        STAmount const& amountBalance,
+        STAmount const& amount2Balance,
+        STAmount const& lptAMMBalance,
+        STAmount const& lpTokens,
+        STAmount const& lpTokensWithdraw,
+        std::uint16_t tfee,
+        FreezeHandling freezeHanding,
+        WithdrawAll withdrawAll,
+        beast::Journal const& journal);
+
+    /** Withdraw requested assets and token from AMM into LP account.
+     * Return new total LPToken balance and the withdrawn amounts for both
+     * assets.
+     * @param view
+     * @param ammSle AMM ledger entry
+     * @param ammAccount AMM account
+     * @param amountBalance current LP asset1 balance
+     * @param amountWithdraw asset1 withdraw amount
+     * @param amount2Withdraw asset2 withdraw amount
+     * @param lpTokensAMMBalance current AMM LPT balance
+     * @param lpTokensWithdraw amount of lptokens to withdraw
+     * @param tfee trading fee in basis points
+     * @param withdrawAll if withdraw all lptokens
+     * @return
+     */
+    static std::tuple<TER, STAmount, STAmount, std::optional<STAmount>>
+    withdraw(
+        Sandbox& view,
+        SLE const& ammSle,
+        AccountID const& ammAccount,
+        AccountID const& account,
+        STAmount const& amountBalance,
+        STAmount const& amountWithdraw,
+        std::optional<STAmount> const& amount2Withdraw,
+        STAmount const& lpTokensAMMBalance,
+        STAmount const& lpTokensWithdraw,
+        std::uint16_t tfee,
+        FreezeHandling freezeHandling,
+        WithdrawAll withdrawAll,
+        beast::Journal const& journal);
+
+    static std::pair<TER, bool>
+    deleteAMMAccountIfEmpty(
+        Sandbox& sb,
+        std::shared_ptr<SLE> const ammSle,
+        STAmount const& lpTokenBalance,
+        Issue const& issue1,
+        Issue const& issue2,
+        beast::Journal const& journal);
+
 private:
     std::pair<TER, bool>
     applyGuts(Sandbox& view);
@@ -87,21 +161,22 @@ class AMMWithdraw : public Transactor
     /** Withdraw requested assets and token from AMM into LP account.
      * Return new total LPToken balance.
      * @param view
-     * @param ammAccount
-     * @param amountBalance
-     * @param amountWithdraw
-     * @param amount2Withdraw
+     * @param ammSle AMM ledger entry
+     * @param ammAccount AMM account
+     * @param amountBalance current LP asset1 balance
+     * @param amountWithdraw asset1 withdraw amount
+     * @param amount2Withdraw asset2 withdraw amount
      * @param lpTokensAMMBalance current AMM LPT balance
-     * @param lpTokensWithdraw
-     * @param tfee
+     * @param lpTokensWithdraw amount of lptokens to withdraw
      * @return
      */
     std::pair<TER, STAmount>
     withdraw(
         Sandbox& view,
+        SLE const& ammSle,
         AccountID const& ammAccount,
-        STAmount const& amountWithdraw,
         STAmount const& amountBalance,
+        STAmount const& amountWithdraw,
         std::optional<STAmount> const& amount2Withdraw,
         STAmount const& lpTokensAMMBalance,
         STAmount const& lpTokensWithdraw,
@@ -123,6 +198,7 @@ class AMMWithdraw : public Transactor
     std::pair<TER, STAmount>
     equalWithdrawTokens(
         Sandbox& view,
+        SLE const& ammSle,
         AccountID const& ammAccount,
         STAmount const& amountBalance,
         STAmount const& amount2Balance,
@@ -147,6 +223,7 @@ class AMMWithdraw : public Transactor
     std::pair<TER, STAmount>
     equalWithdrawLimit(
         Sandbox& view,
+        SLE const& ammSle,
         AccountID const& ammAccount,
         STAmount const& amountBalance,
         STAmount const& amount2Balance,
@@ -168,6 +245,7 @@ class AMMWithdraw : public Transactor
     std::pair<TER, STAmount>
     singleWithdraw(
         Sandbox& view,
+        SLE const& ammSle,
         AccountID const& ammAccount,
         STAmount const& amountBalance,
         STAmount const& lptAMMBalance,
@@ -188,6 +266,7 @@ class AMMWithdraw : public Transactor
     std::pair<TER, STAmount>
     singleWithdrawTokens(
         Sandbox& view,
+        SLE const& ammSle,
         AccountID const& ammAccount,
         STAmount const& amountBalance,
         STAmount const& lptAMMBalance,
@@ -209,12 +288,17 @@ class AMMWithdraw : public Transactor
     std::pair<TER, STAmount>
     singleWithdrawEPrice(
         Sandbox& view,
+        SLE const& ammSle,
         AccountID const& ammAccount,
         STAmount const& amountBalance,
         STAmount const& lptAMMBalance,
         STAmount const& amount,
         STAmount const& ePrice,
         std::uint16_t tfee);
+
+    /** Check from the flags if it's withdraw all */
+    WithdrawAll
+    isWithdrawAll(STTx const& tx);
 };
 
 }  // namespace ripple
diff --git a/src/xrpld/app/tx/detail/InvariantCheck.cpp b/src/xrpld/app/tx/detail/InvariantCheck.cpp
index e8bbd0283b5..d1eaf86844d 100644
--- a/src/xrpld/app/tx/detail/InvariantCheck.cpp
+++ b/src/xrpld/app/tx/detail/InvariantCheck.cpp
@@ -342,11 +342,12 @@ AccountRootsNotDeleted::finalize(
         return false;
     }
 
-    // A successful AMMWithdraw MAY delete one account root
+    // A successful AMMWithdraw/AMMClawback MAY delete one account root
     // when the total AMM LP Tokens balance goes to 0. Not every AMM withdraw
     // deletes the AMM account, accountsDeleted_ is set if it is deleted.
-    if (tx.getTxnType() == ttAMM_WITHDRAW && result == tesSUCCESS &&
-        accountsDeleted_ == 1)
+    if ((tx.getTxnType() == ttAMM_WITHDRAW ||
+         tx.getTxnType() == ttAMM_CLAWBACK) &&
+        result == tesSUCCESS && accountsDeleted_ == 1)
         return true;
 
     if (accountsDeleted_ == 0)
diff --git a/src/xrpld/app/tx/detail/applySteps.cpp b/src/xrpld/app/tx/detail/applySteps.cpp
index f59cd73378b..44c25cb22ef 100644
--- a/src/xrpld/app/tx/detail/applySteps.cpp
+++ b/src/xrpld/app/tx/detail/applySteps.cpp
@@ -19,6 +19,7 @@
 
 #include <xrpld/app/tx/applySteps.h>
 #include <xrpld/app/tx/detail/AMMBid.h>
+#include <xrpld/app/tx/detail/AMMClawback.h>
 #include <xrpld/app/tx/detail/AMMCreate.h>
 #include <xrpld/app/tx/detail/AMMDelete.h>
 #include <xrpld/app/tx/detail/AMMDeposit.h>