From 7b6328849702e87b62f4c8de6fe1d83b55ac2c30 Mon Sep 17 00:00:00 2001 From: Vito <5780819+Tapanito@users.noreply.github.com> Date: Thu, 18 Jun 2026 19:41:35 +0200 Subject: [PATCH] fix: Unify AMM Deposit/Withdraw freeze checks with checkDepositFreeze/checkWithdrawFreeze Migrate AMMDeposit and AMMWithdraw to use the unified freeze helpers introduced for Vault and LoanBroker, gated on fixCleanup3_3_0. AMMDeposit: checkDepositFreeze for both pool assets replaces the two-layer checkAsset + checkAmount freeze logic. If either pool asset is frozen (depositor or AMM account), all deposits are blocked. AMMWithdraw: checkWithdrawFreeze per withdrawn amount replaces checkFrozen(AMM) + checkIndividualFrozen(user). Regular IOU freeze no longer blocks self-withdrawal; only deep freeze does. The issuer exemption allows the token issuer to withdraw from a frozen pool; issuerFreezeHandling() ensures doApply uses IgnoreFreeze so pool math does not divide by zero. Rename checkWithdrawFreezes -> checkWithdrawFreeze (singular) and update all callers. Document both helpers with full freeze semantics. --- include/xrpl/ledger/helpers/TokenHelpers.h | 47 ++++-- include/xrpl/tx/transactors/dex/AMMWithdraw.h | 5 + src/libxrpl/ledger/helpers/TokenHelpers.cpp | 2 +- src/libxrpl/tx/transactors/dex/AMMDeposit.cpp | 68 +++++--- .../tx/transactors/dex/AMMWithdraw.cpp | 65 ++++++-- .../lending/LoanBrokerCoverWithdraw.cpp | 2 +- .../tx/transactors/vault/VaultWithdraw.cpp | 2 +- src/test/app/AMM_test.cpp | 152 ++++++++++++------ 8 files changed, 242 insertions(+), 101 deletions(-) diff --git a/include/xrpl/ledger/helpers/TokenHelpers.h b/include/xrpl/ledger/helpers/TokenHelpers.h index d4a42b5d43..ce661bae9c 100644 --- a/include/xrpl/ledger/helpers/TokenHelpers.h +++ b/include/xrpl/ledger/helpers/TokenHelpers.h @@ -132,18 +132,32 @@ checkDeepFrozen(ReadView const& view, AccountID const& account, MPTIssue const& checkDeepFrozen(ReadView const& view, AccountID const& account, Asset const& asset); /** - * Checks freeze compliance for a pseudo-account withdrawal to a non-issuer destination. + * Checks freeze compliance for withdrawing an asset from a pseudo-account + * (e.g. Vault, AMM, LoanBroker) to a destination account. * - * Returns tesSUCCESS immediately when dstAcct is the asset issuer — redemption - * to the issuer bypasses all freeze checks. + * Asserts that sourceAcct is a pseudo-account. * - * Otherwise checks: - * - The source pseudo-account is not frozen for the asset (checkFrozen). - * - The submitting account is not individually frozen for the asset (checkFrozen). - * - The destination is not deep-frozen for the asset (checkDeepFrozen). + * Issuer exemption: returns tesSUCCESS immediately when dstAcct is the asset + * issuer — the issuer can always receive their own token, even when the pool + * is frozen. Callers that need to block withdrawals from a frozen pool even + * for the issuer (e.g. because the pool math cannot handle it) must check + * checkFrozen(sourceAcct, asset) separately before calling this function. + * + * Otherwise checks, in order: + * 1. checkFrozen(sourceAcct, asset) — the pseudo-account's trustline / + * global freeze must not block sending. + * 2. checkFrozen(submitterAcct, asset) — skipped when submitter == dst + * (self-withdrawal); a regular freeze should not prevent recovering + * one's own funds. + * 3. checkDeepFrozen(dstAcct, asset) — the destination must not be + * deep-frozen (cannot receive under any circumstance). + * + * For IOUs a regular individual freeze on the withdrawer does NOT block + * self-withdrawal; only deep freeze does. For MPTs "locked" is equivalent + * to deep-frozen, so locked MPT holders are always blocked. */ [[nodiscard]] TER -checkWithdrawFreezes( +checkWithdrawFreeze( ReadView const& view, AccountID const& sourceAcct, AccountID const& submitterAcct, @@ -151,14 +165,23 @@ checkWithdrawFreezes( Asset const& asset); /** - * Checks freeze compliance for a pseudo-account deposit. - * - The source pseudo-account is not deep frozen for the asset - * - The submitting account is not individually frozen for the asset + * Checks freeze compliance for depositing an asset into a pseudo-account + * (e.g. Vault, AMM, LoanBroker). + * + * Asserts that dstAcct is a pseudo-account. + * + * Checks, in order: + * 1. checkFrozen(srcAcct, asset) — the depositor must not be frozen + * (global or individual) for the asset. + * 2. checkFrozen(dstAcct, asset) — the pseudo-account must not be + * frozen for the asset. Unlike regular accounts, pseudo-accounts + * cannot receive deposits under a regular freeze because the + * deposited funds could not later be withdrawn. */ [[nodiscard]] TER checkDepositFreeze( ReadView const& view, - AccountID const& sourceAcct, + AccountID const& srcAcct, AccountID const& dstAcct, Asset const& asset); diff --git a/include/xrpl/tx/transactors/dex/AMMWithdraw.h b/include/xrpl/tx/transactors/dex/AMMWithdraw.h index 6e88320eae..8b6d39349b 100644 --- a/include/xrpl/tx/transactors/dex/AMMWithdraw.h +++ b/include/xrpl/tx/transactors/dex/AMMWithdraw.h @@ -159,6 +159,11 @@ public: beast::Journal const& journal); private: + /** Returns IgnoreFreeze when the withdrawer is the issuer of a pool + * asset (post-fixCleanup3_3_0), ZeroIfFrozen otherwise. */ + [[nodiscard]] FreezeHandling + issuerFreezeHandling() const; + std::pair applyGuts(Sandbox& view); diff --git a/src/libxrpl/ledger/helpers/TokenHelpers.cpp b/src/libxrpl/ledger/helpers/TokenHelpers.cpp index b3bfc8bc5a..c171df9417 100644 --- a/src/libxrpl/ledger/helpers/TokenHelpers.cpp +++ b/src/libxrpl/ledger/helpers/TokenHelpers.cpp @@ -165,7 +165,7 @@ checkDeepFrozen(ReadView const& view, AccountID const& account, Asset const& ass } TER -checkWithdrawFreezes( +checkWithdrawFreeze( ReadView const& view, AccountID const& srcAcct, AccountID const& submitterAcct, diff --git a/src/libxrpl/tx/transactors/dex/AMMDeposit.cpp b/src/libxrpl/tx/transactors/dex/AMMDeposit.cpp index 91858e3cd7..0ee234fe97 100644 --- a/src/libxrpl/tx/transactors/dex/AMMDeposit.cpp +++ b/src/libxrpl/tx/transactors/dex/AMMDeposit.cpp @@ -251,7 +251,36 @@ AMMDeposit::preclaim(PreclaimContext const& ctx) : tecUNFUNDED_AMM; }; - if (ctx.view.rules().enabled(featureAMMClawback)) + auto const amount = ctx.tx[~sfAmount]; + auto const amount2 = ctx.tx[~sfAmount2]; + auto const ammAccountID = ammSle->getAccountID(sfAccount); + + if (ctx.view.rules().enabled(fixCleanup3_3_0)) + { + // Unified deposit freeze check for both pool assets. + // AMMDeposit is not allowed if either asset is frozen. + auto checkAsset = [&](Asset const& asset) -> TER { + if (auto const ter = requireAuth(ctx.view, asset, accountID, AuthType::WeakAuth)) + { + JLOG(ctx.j.debug()) << "AMM Deposit: account is not authorized, " << asset; + return ter; + } + if (auto const ter = checkDepositFreeze(ctx.view, accountID, ammAccountID, asset)) + { + JLOG(ctx.j.debug()) + << "AMM Deposit: frozen, " << to_string(accountID) << " " << to_string(asset); + return ter; + } + return tesSUCCESS; + }; + + if (auto const ter = checkAsset(ctx.tx[sfAsset])) + return ter; + + if (auto const ter = checkAsset(ctx.tx[sfAsset2])) + return ter; + } + else if (ctx.view.rules().enabled(featureAMMClawback)) { // Check if either of the assets is frozen, AMMDeposit is not allowed // if either asset is frozen @@ -283,10 +312,6 @@ AMMDeposit::preclaim(PreclaimContext const& ctx) return ter; } - auto const amount = ctx.tx[~sfAmount]; - auto const amount2 = ctx.tx[~sfAmount2]; - auto const ammAccountID = ammSle->getAccountID(sfAccount); - auto checkAmount = [&](std::optional const& amount, bool checkBalance) -> TER { if (amount) { @@ -301,21 +326,26 @@ AMMDeposit::preclaim(PreclaimContext const& ctx) return ter; // LCOV_EXCL_STOP } - // AMM account or currency frozen - if (auto const ter = checkFrozen(ctx.view, ammAccountID, amount->asset()); - !isTesSuccess(ter)) + if (!ctx.view.rules().enabled(fixCleanup3_3_0)) { - JLOG(ctx.j.debug()) << "AMM Deposit: AMM account or currency is frozen or locked, " - << to_string(accountID); - return ter; - } - // Account frozen - if (auto const ter = checkIndividualFrozen(ctx.view, accountID, amount->asset()); - !isTesSuccess(ter)) - { - JLOG(ctx.j.debug()) << "AMM Deposit: account is frozen or locked, " - << to_string(accountID) << " " << to_string(amount->asset()); - return ter; + // AMM account or currency frozen + if (auto const ter = checkFrozen(ctx.view, ammAccountID, amount->asset()); + !isTesSuccess(ter)) + { + JLOG(ctx.j.debug()) + << "AMM Deposit: AMM account or currency is frozen or locked, " + << to_string(accountID); + return ter; + } + // Account frozen + if (auto const ter = checkIndividualFrozen(ctx.view, accountID, amount->asset()); + !isTesSuccess(ter)) + { + JLOG(ctx.j.debug()) + << "AMM Deposit: account is frozen or locked, " << to_string(accountID) + << " " << to_string(amount->asset()); + return ter; + } } if (checkBalance) { diff --git a/src/libxrpl/tx/transactors/dex/AMMWithdraw.cpp b/src/libxrpl/tx/transactors/dex/AMMWithdraw.cpp index d3a6c9c74c..9418b40071 100644 --- a/src/libxrpl/tx/transactors/dex/AMMWithdraw.cpp +++ b/src/libxrpl/tx/transactors/dex/AMMWithdraw.cpp @@ -238,21 +238,36 @@ AMMWithdraw::preclaim(PreclaimContext const& ctx) << "AMM Withdraw: account is not authorized, " << amount->asset(); return ter; } - // AMM account or currency frozen - if (auto const ter = checkFrozen(ctx.view, ammAccountID, amount->asset()); - !isTesSuccess(ter)) + if (ctx.view.rules().enabled(fixCleanup3_3_0)) { - JLOG(ctx.j.debug()) << "AMM Withdraw: AMM account or currency is frozen or locked, " - << to_string(accountID); - return ter; + if (auto const ret = checkWithdrawFreeze( + ctx.view, ammAccountID, accountID, accountID, amount->asset())) + { + JLOG(ctx.j.debug()) << "AMM Withdraw: frozen, " << to_string(accountID) << " " + << to_string(amount->asset()); + return ret; + } } - // Account frozen - if (auto const ter = checkIndividualFrozen(ctx.view, accountID, amount->asset()); - !isTesSuccess(ter)) + else { - JLOG(ctx.j.debug()) << "AMM Withdraw: account is frozen or locked, " - << to_string(accountID) << " " << to_string(amount->asset()); - return ter; + // AMM account or currency frozen + if (auto const ter = checkFrozen(ctx.view, ammAccountID, amount->asset()); + !isTesSuccess(ter)) + { + JLOG(ctx.j.debug()) + << "AMM Withdraw: AMM account or currency is frozen or locked, " + << to_string(accountID); + return ter; + } + // Account frozen + if (auto const ter = checkIndividualFrozen(ctx.view, accountID, amount->asset()); + !isTesSuccess(ter)) + { + JLOG(ctx.j.debug()) + << "AMM Withdraw: account is frozen or locked, " << to_string(accountID) + << " " << to_string(amount->asset()); + return ter; + } } } return tesSUCCESS; @@ -302,6 +317,24 @@ AMMWithdraw::preclaim(PreclaimContext const& ctx) return tesSUCCESS; } +FreezeHandling +AMMWithdraw::issuerFreezeHandling() const +{ + // When the withdrawer is the issuer of a pool asset, the issuer can + // always receive their own token — even when the pool is frozen. + // Use IgnoreFreeze so ammHolds returns real balances instead of zero. + if (ctx_.view().rules().enabled(fixCleanup3_3_0)) + { + auto const asset1 = Asset{ctx_.tx[sfAsset]}; + auto const asset2 = Asset{ctx_.tx[sfAsset2]}; + if (!asset1.native() && accountID_ == asset1.getIssuer()) + return FreezeHandling::IgnoreFreeze; + if (!asset2.native() && accountID_ == asset2.getIssuer()) + return FreezeHandling::IgnoreFreeze; + } + return FreezeHandling::ZeroIfFrozen; +} + std::pair AMMWithdraw::applyGuts(Sandbox& sb) { @@ -329,12 +362,14 @@ AMMWithdraw::applyGuts(Sandbox& sb) auto const tfee = getTradingFee(ctx_.view(), *ammSle, accountID_); + auto const freezeHandling = issuerFreezeHandling(); + auto const expected = ammHolds( sb, *ammSle, amount ? amount->asset() : std::optional{}, amount2 ? amount2->asset() : std::optional{}, - FreezeHandling::ZeroIfFrozen, + freezeHandling, AuthHandling::ZeroIfUnauthorized, ctx_.journal); if (!expected) @@ -459,7 +494,7 @@ AMMWithdraw::withdraw( lpTokensAMMBalance, lpTokensWithdraw, tfee, - FreezeHandling::ZeroIfFrozen, + issuerFreezeHandling(), AuthHandling::ZeroIfUnauthorized, isWithdrawAll(ctx_.tx), preFeeBalance_, @@ -746,7 +781,7 @@ AMMWithdraw::equalWithdrawTokens( lpTokens, lpTokensWithdraw, tfee, - FreezeHandling::ZeroIfFrozen, + issuerFreezeHandling(), AuthHandling::ZeroIfUnauthorized, isWithdrawAll(ctx_.tx), preFeeBalance_, diff --git a/src/libxrpl/tx/transactors/lending/LoanBrokerCoverWithdraw.cpp b/src/libxrpl/tx/transactors/lending/LoanBrokerCoverWithdraw.cpp index b53a2e9011..c4d8fbd58c 100644 --- a/src/libxrpl/tx/transactors/lending/LoanBrokerCoverWithdraw.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanBrokerCoverWithdraw.cpp @@ -128,7 +128,7 @@ LoanBrokerCoverWithdraw::preclaim(PreclaimContext const& ctx) if (ctx.view.rules().enabled(fixCleanup3_3_0)) { if (auto const ret = - checkWithdrawFreezes(ctx.view, pseudoAccountID, account, dstAcct, vaultAsset)) + checkWithdrawFreeze(ctx.view, pseudoAccountID, account, dstAcct, vaultAsset)) return ret; } else diff --git a/src/libxrpl/tx/transactors/vault/VaultWithdraw.cpp b/src/libxrpl/tx/transactors/vault/VaultWithdraw.cpp index c3c218cdf6..32e42c0958 100644 --- a/src/libxrpl/tx/transactors/vault/VaultWithdraw.cpp +++ b/src/libxrpl/tx/transactors/vault/VaultWithdraw.cpp @@ -170,7 +170,7 @@ VaultWithdraw::preclaim(PreclaimContext const& ctx) // The only way shares become locked is transitively via the // underlying asset, which checkWithdrawFreezes already covers. if (auto const ret = - checkWithdrawFreezes(ctx.view, vaultAccount, account, dstAcct, vaultAsset)) + checkWithdrawFreeze(ctx.view, vaultAccount, account, dstAcct, vaultAsset)) return ret; } else diff --git a/src/test/app/AMM_test.cpp b/src/test/app/AMM_test.cpp index 1b54c2aab9..a23aa2455b 100644 --- a/src/test/app/AMM_test.cpp +++ b/src/test/app/AMM_test.cpp @@ -85,6 +85,21 @@ private: return jtx::testableAmendments() - featureSingleAssetVault - featureLendingProtocol; } + // All 2^N permutations of testableAmendments() with each subset of the + // given features excluded. + static std::vector + amendmentCombinations(std::initializer_list features) + { + std::vector result{testableAmendments()}; + for (auto const& f : features) + { + auto const n = result.size(); + for (std::size_t i = 0; i < n; ++i) + result.push_back(result[i] - f); + } + return result; + } + void testInstanceCreate() { @@ -746,18 +761,19 @@ private: testAMM( [&](AMM& ammAlice, Env& env) { env(fset(gw_, asfGlobalFreeze)); - if (!features[featureAMMClawback]) + auto const freezeBlocksAll = + features[featureAMMClawback] || features[fixCleanup3_3_0]; + if (!freezeBlocksAll) { // If the issuer set global freeze, the holder still can - // deposit the other non-frozen token when AMMClawback is - // not enabled. + // deposit the other non-frozen token when neither + // AMMClawback nor fixCleanup3_3_0 is 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. + // deposit the other non-frozen token. ammAlice.deposit( carol_, XRP(100), std::nullopt, std::nullopt, std::nullopt, Ter(tecFROZEN)); } @@ -786,16 +802,18 @@ private: [&](AMM& ammAlice, Env& env) { env(trust(gw_, carol_["USD"](0), tfSetFreeze)); env.close(); - if (!features[featureAMMClawback]) + auto const freezeBlocksAll = + features[featureAMMClawback] || features[fixCleanup3_3_0]; + if (!freezeBlocksAll) { - // Can deposit non-frozen token if AMMClawback is not - // enabled + // Can deposit non-frozen token if neither AMMClawback + // nor fixCleanup3_3_0 is enabled ammAlice.deposit(carol_, XRP(100)); } else { // Cannot deposit non-frozen token if the other token is - // frozen when AMMClawback is enabled + // frozen ammAlice.deposit( carol_, XRP(100), std::nullopt, std::nullopt, std::nullopt, Ter(tecFROZEN)); } @@ -810,8 +828,18 @@ private: STAmount{Issue{gw_["USD"].currency, ammAlice.ammAccount()}, 0}, tfSetFreeze)); env.close(); - // Can deposit non-frozen token - ammAlice.deposit(carol_, XRP(100)); + // Post-fixCleanup3_3_0: checkDepositFreeze checks both pool + // assets against the AMM account, so depositing the + // non-frozen token is also blocked. + if (!features[fixCleanup3_3_0]) + { + ammAlice.deposit(carol_, XRP(100)); + } + else + { + 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)); @@ -861,11 +889,10 @@ private: AMM amm(env, alice_, XRP(10), gw_["USD"](10), Ter(tesSUCCESS)); env.close(); - if (features[featureAMMClawback]) + if (features[featureAMMClawback] || features[fixCleanup3_3_0]) { - // if featureAMMClawback is enabled, bob_ can not deposit XRP - // because he's not authorized to hold the paired token - // gw_["USD"]. + // 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)); } @@ -1734,36 +1761,57 @@ private: }); // Globally frozen asset - testAMM([&](AMM& ammAlice, Env& env) { - ammAlice.deposit({.account = gw_, .asset1In = USD(1'000), .asset2In = XRP(1'000)}); - env(fset(gw_, asfGlobalFreeze)); - env.close(); - // Can withdraw non-frozen token - for (auto const& account : {alice_, gw_}) - { - ammAlice.withdraw(account, XRP(100)); - ammAlice.withdraw(account, USD(100), std::nullopt, std::nullopt, Ter(tecFROZEN)); - ammAlice.withdraw(account, 1'000, std::nullopt, std::nullopt, Ter(tecFROZEN)); - } - }); + testAMM( + [&](AMM& ammAlice, Env& env) { + auto const fix330 = env.current()->rules().enabled(fixCleanup3_3_0); + ammAlice.deposit({.account = gw_, .asset1In = USD(1'000), .asset2In = XRP(1'000)}); + env(fset(gw_, asfGlobalFreeze)); + env.close(); + // Can withdraw non-frozen token + for (auto const& account : {alice_, gw_}) + { + ammAlice.withdraw(account, XRP(100)); + // Post-fixCleanup3_3_0 the issuer can withdraw their own + // frozen token from the pool. + auto const frozenErr = + (fix330 && account == gw_) ? Ter(tesSUCCESS) : Ter(tecFROZEN); + ammAlice.withdraw(account, USD(100), std::nullopt, std::nullopt, frozenErr); + ammAlice.withdraw(account, 1'000, std::nullopt, std::nullopt, frozenErr); + } + }, + std::nullopt, + 0, + std::nullopt, + amendmentCombinations({fixCleanup3_3_0})); // Individually frozen (AMM) account - testAMM([&](AMM& ammAlice, Env& env) { - env(trust(gw_, alice_["USD"](0), tfSetFreeze)); - env.close(); - // Can withdraw non-frozen token - ammAlice.withdraw(alice_, XRP(100)); - ammAlice.withdraw(alice_, 1'000, std::nullopt, std::nullopt, Ter(tecFROZEN)); - ammAlice.withdraw(alice_, USD(100), std::nullopt, std::nullopt, Ter(tecFROZEN)); - env(trust(gw_, alice_["USD"](0), tfClearFreeze)); - // Individually frozen AMM - env(trust( - gw_, STAmount{Issue{gw_["USD"].currency, ammAlice.ammAccount()}, 0}, tfSetFreeze)); - // Can withdraw non-frozen token - ammAlice.withdraw(alice_, XRP(100)); - ammAlice.withdraw(alice_, 1'000, std::nullopt, std::nullopt, Ter(tecFROZEN)); - ammAlice.withdraw(alice_, USD(100), std::nullopt, std::nullopt, Ter(tecFROZEN)); - }); + testAMM( + [&](AMM& ammAlice, Env& env) { + auto const fix330 = env.current()->rules().enabled(fixCleanup3_3_0); + env(trust(gw_, alice_["USD"](0), tfSetFreeze)); + env.close(); + // Can withdraw non-frozen token + ammAlice.withdraw(alice_, XRP(100)); + // Post-fixCleanup3_3_0 regular freeze no longer blocks + // self-withdrawal; only deep freeze does. + auto const indivFreezeErr = fix330 ? Ter(tesSUCCESS) : Ter(tecFROZEN); + ammAlice.withdraw(alice_, 1'000, std::nullopt, std::nullopt, indivFreezeErr); + ammAlice.withdraw(alice_, USD(100), std::nullopt, std::nullopt, indivFreezeErr); + env(trust(gw_, alice_["USD"](0), tfClearFreeze)); + // Individually frozen AMM — still blocked regardless of + // fixCleanup3_3_0 because the AMM account itself is frozen. + env(trust( + gw_, + STAmount{Issue{gw_["USD"].currency, ammAlice.ammAccount()}, 0}, + tfSetFreeze)); + ammAlice.withdraw(alice_, XRP(100)); + ammAlice.withdraw(alice_, 1'000, std::nullopt, std::nullopt, Ter(tecFROZEN)); + ammAlice.withdraw(alice_, USD(100), std::nullopt, std::nullopt, Ter(tecFROZEN)); + }, + std::nullopt, + 0, + std::nullopt, + amendmentCombinations({fixCleanup3_3_0})); // Carol withdraws more than she owns testAMM([&](AMM& ammAlice, Env&) { @@ -6649,11 +6697,11 @@ private: }); } - if (features[featureAMMClawback]) + if (features[featureAMMClawback] || features[fixCleanup3_3_0]) { // 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. + // but the other asset is frozen. tecFROZEN when either + // AMMClawback or fixCleanup3_3_0 is enabled. Env env(*this, features); testAMMDeposit(env, [&](AMM& amm) { amm.deposit( @@ -6663,8 +6711,8 @@ private: 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. + // but the other asset is frozen. tesSUCCESS only when + // neither AMMClawback nor fixCleanup3_3_0 is enabled. Env env(*this, features); testAMMDeposit(env, [&](AMM& amm) { amm.deposit( @@ -7187,8 +7235,8 @@ private: FeatureBitset const all{testableAmendments()}; testInvalidInstance(); testInstanceCreate(); - testInvalidDeposit(all); - testInvalidDeposit(all - featureAMMClawback); + for (auto const& f : amendmentCombinations({fixCleanup3_3_0, featureAMMClawback})) + testInvalidDeposit(f); testDeposit(); testInvalidWithdraw(); testWithdraw(); @@ -7238,8 +7286,8 @@ private: testAMMClawback(all - featureAMMClawback - featureSingleAssetVault); testAMMClawback(all - featureAMMClawback); testAMMClawback(all - fixAMMv1_1 - fixAMMv1_3 - featureAMMClawback); - testAMMDepositWithFrozenAssets(all); - testAMMDepositWithFrozenAssets(all - featureAMMClawback); + for (auto const& f : amendmentCombinations({fixCleanup3_3_0, featureAMMClawback})) + testAMMDepositWithFrozenAssets(f); testAMMDepositWithFrozenAssets(all - fixAMMv1_1 - featureAMMClawback); testAMMDepositWithFrozenAssets(all - fixAMMv1_1 - fixAMMv1_3 - featureAMMClawback); testFixReserveCheckOnWithdrawal(all);