diff --git a/src/test/app/AMMClawback_test.cpp b/src/test/app/AMMClawback_test.cpp index 812193932a5..3a78cb099bc 100644 --- a/src/test/app/AMMClawback_test.cpp +++ b/src/test/app/AMMClawback_test.cpp @@ -1428,7 +1428,293 @@ class AMMClawback_test : public jtx::AMMTest ter(tecINTERNAL)); } -public: + void + testAssetFrozen(FeatureBitset features) + { + testcase("test assets frozen"); + using namespace jtx; + + // test Individually frozen trustline of USD 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 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), std::nullopt), + 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), std::nullopt), + 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), std::nullopt), + 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 global freeze gw USD + { + 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), std::nullopt), + 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), std::nullopt), + 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), 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, 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 run() override { @@ -1442,6 +1728,7 @@ class AMMClawback_test : public jtx::AMMTest testAMMClawbackSameCurrency(all); testAMMClawbackIssuesEachOther(all); testNotHoldingLptoken(all); + testAssetFrozen(all); } }; BEAST_DEFINE_TESTSUITE(AMMClawback, app, ripple); diff --git a/src/test/app/AMM_test.cpp b/src/test/app/AMM_test.cpp index feee7eae603..1745751a259 100644 --- a/src/test/app/AMM_test.cpp +++ b/src/test/app/AMM_test.cpp @@ -6957,9 +6957,9 @@ struct AMM_test : public jtx::AMMTest env.fund(XRP(1'000), gw); fund(env, gw, {alice}, XRP(1'000), {USD(1'000)}, Fund::Acct); env.close(); - AMM* amm = new AMM(env, alice, XRP(100), USD(100), ter(tesSUCCESS)); + AMM amm(env, alice, XRP(100), USD(100), ter(tesSUCCESS)); env(trust(gw, alice["USD"](0), tfSetFreeze)); - cb(*amm); + cb(amm); }; // Deposit two assets, one of which is frozen, diff --git a/src/xrpld/app/misc/detail/AMMUtils.cpp b/src/xrpld/app/misc/detail/AMMUtils.cpp index d5af0ee02ef..efc80cf17b6 100644 --- a/src/xrpld/app/misc/detail/AMMUtils.cpp +++ b/src/xrpld/app/misc/detail/AMMUtils.cpp @@ -16,14 +16,12 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ //============================================================================== -#include #include #include #include #include #include #include -#include namespace ripple { diff --git a/src/xrpld/app/tx/detail/AMMClawback.cpp b/src/xrpld/app/tx/detail/AMMClawback.cpp index a003932f61a..660a52d5c2e 100644 --- a/src/xrpld/app/tx/detail/AMMClawback.cpp +++ b/src/xrpld/app/tx/detail/AMMClawback.cpp @@ -152,7 +152,7 @@ AMMClawback::applyGuts(Sandbox& sb) *ammSle, asset, asset2, - FreezeHandling::fhZERO_IF_FROZEN, + FreezeHandling::fhIGNORE_FREEZE, ctx_.journal); if (!expected) @@ -184,6 +184,7 @@ AMMClawback::applyGuts(Sandbox& sb) holdLPtokens, holdLPtokens, 0, + FreezeHandling::fhIGNORE_FREEZE, WithdrawAll::Yes, ctx_.journal); } @@ -222,7 +223,6 @@ AMMClawback::applyGuts(Sandbox& sb) // same issuer. auto const flags = ctx_.tx.getFlags(); if (flags & tfClawTwoAssets) - return rippleCredit(sb, holder, issuer, *amount2Withdraw, true, j_); return tesSUCCESS; @@ -260,6 +260,7 @@ AMMClawback::equalWithdrawMatchingOneAmount( holdLPtokens, holdLPtokens, 0, + FreezeHandling::fhIGNORE_FREEZE, WithdrawAll::Yes, ctx_.journal); @@ -276,6 +277,7 @@ AMMClawback::equalWithdrawMatchingOneAmount( lptAMMBalance, toSTAmount(lptAMMBalance.issue(), lptAMMBalance * frac), 0, + FreezeHandling::fhIGNORE_FREEZE, WithdrawAll::No, ctx_.journal); } diff --git a/src/xrpld/app/tx/detail/AMMWithdraw.cpp b/src/xrpld/app/tx/detail/AMMWithdraw.cpp index 843384d3778..d3c8c41a03c 100644 --- a/src/xrpld/app/tx/detail/AMMWithdraw.cpp +++ b/src/xrpld/app/tx/detail/AMMWithdraw.cpp @@ -22,7 +22,7 @@ #include #include #include -#include + #include #include #include @@ -397,23 +397,16 @@ AMMWithdraw::applyGuts(Sandbox& sb) tfee); if (subTxType & tfLPToken || subTxType & tfWithdrawAll) { - TER result; - STAmount newLPTokenBalance; - std::tie(result, newLPTokenBalance, std::ignore, std::ignore) = - equalWithdrawTokens( - sb, - *ammSle, - account_, - ammAccountID, - amountBalance, - amount2Balance, - lptAMMBalance, - lpTokens, - *lpTokensWithdraw, - tfee, - isWithdrawAll(ctx_.tx), - ctx_.journal); - return {result, newLPTokenBalance}; + return equalWithdrawTokens( + sb, + *ammSle, + ammAccountID, + amountBalance, + amount2Balance, + lptAMMBalance, + lpTokens, + *lpTokensWithdraw, + tfee); } // should not happen. // LCOV_EXCL_START @@ -457,7 +450,6 @@ std::pair AMMWithdraw::withdraw( Sandbox& view, AccountID const& ammAccount, - AccountID const& account, SLE const& ammSle, STAmount const& amountBalance, STAmount const& amountWithdraw, @@ -471,7 +463,7 @@ AMMWithdraw::withdraw( std::tie(ter, newLPTokenBalance, std::ignore, std::ignore) = withdraw( view, ammAccount, - account, + account_, ammSle, amountBalance, amountWithdraw, @@ -479,6 +471,7 @@ AMMWithdraw::withdraw( lpTokensAMMBalance, lpTokensWithdraw, tfee, + FreezeHandling::fhZERO_IF_FROZEN, isWithdrawAll(ctx_.tx), j_); return {ter, newLPTokenBalance}; @@ -496,6 +489,7 @@ AMMWithdraw::withdraw( STAmount const& lpTokensAMMBalance, STAmount const& lpTokensWithdraw, std::uint16_t tfee, + FreezeHandling freezeHandling, WithdrawAll withdrawAll, beast::Journal const& journal) { @@ -505,7 +499,7 @@ AMMWithdraw::withdraw( ammSle, amountWithdraw.issue(), std::nullopt, - FreezeHandling::fhZERO_IF_FROZEN, + freezeHandling, journal); // LCOV_EXCL_START if (!expected) @@ -655,6 +649,38 @@ AMMWithdraw::withdraw( amount2WithdrawActual); } +std::pair +AMMWithdraw::equalWithdrawTokens( + Sandbox& view, + SLE const& ammSle, + AccountID const& ammAccount, + STAmount const& amountBalance, + STAmount const& amount2Balance, + STAmount const& lptAMMBalance, + 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 AMMWithdraw::deleteAMMAccountIfEmpty( Sandbox& sb, @@ -698,6 +724,7 @@ AMMWithdraw::equalWithdrawTokens( STAmount const& lpTokens, STAmount const& lpTokensWithdraw, std::uint16_t tfee, + FreezeHandling freezeHanding, WithdrawAll withdrawAll, beast::Journal const& journal) { @@ -717,6 +744,7 @@ AMMWithdraw::equalWithdrawTokens( lptAMMBalance, lpTokensWithdraw, tfee, + freezeHanding, WithdrawAll::Yes, journal); } @@ -744,6 +772,7 @@ AMMWithdraw::equalWithdrawTokens( lptAMMBalance, lpTokensWithdraw, tfee, + freezeHanding, withdrawAll, journal); } @@ -801,7 +830,6 @@ AMMWithdraw::equalWithdrawLimit( return withdraw( view, ammAccount, - account_, ammSle, amountBalance, amount, @@ -817,7 +845,6 @@ AMMWithdraw::equalWithdrawLimit( return withdraw( view, ammAccount, - account_, ammSle, amountBalance, toSTAmount(amount.issue(), amountWithdraw), @@ -849,7 +876,6 @@ AMMWithdraw::singleWithdraw( return withdraw( view, ammAccount, - account_, ammSle, amountBalance, amount, @@ -887,7 +913,6 @@ AMMWithdraw::singleWithdrawTokens( return withdraw( view, ammAccount, - account_, ammSle, amountBalance, amountWithdraw, @@ -952,7 +977,6 @@ AMMWithdraw::singleWithdrawEPrice( return withdraw( view, ammAccount, - account_, ammSle, amountBalance, amountWithdraw, diff --git a/src/xrpld/app/tx/detail/AMMWithdraw.h b/src/xrpld/app/tx/detail/AMMWithdraw.h index ddbf8f550ae..6694910e1a0 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 +#include namespace ripple { @@ -109,6 +110,7 @@ class AMMWithdraw : public Transactor STAmount const& lpTokens, STAmount const& lpTokensWithdraw, std::uint16_t tfee, + FreezeHandling freezeHanding, WithdrawAll withdrawAll, beast::Journal const& journal); @@ -138,6 +140,7 @@ class AMMWithdraw : public Transactor STAmount const& lpTokensAMMBalance, STAmount const& lpTokensWithdraw, std::uint16_t tfee, + FreezeHandling freezeHandling, WithdrawAll withdrawAll, beast::Journal const& journal); @@ -169,7 +172,6 @@ class AMMWithdraw : public Transactor withdraw( Sandbox& view, AccountID const& ammAccount, - AccountID const& account, SLE const& ammSle, STAmount const& amountBalance, STAmount const& amountWithdraw, @@ -178,6 +180,31 @@ class AMMWithdraw : public Transactor STAmount const& lpTokensWithdraw, std::uint16_t tfee); + /** 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 + * @return + */ + std::pair + equalWithdrawTokens( + Sandbox& view, + SLE const& ammSle, + AccountID const& ammAccount, + STAmount const& amountBalance, + STAmount const& amount2Balance, + STAmount const& lptAMMBalance, + STAmount const& lpTokens, + STAmount const& lpTokensWithdraw, + std::uint16_t tfee); + /** Withdraw both assets (Asset1Out, Asset2Out) with the constraints * on the maximum amount of each asset that the trader is willing * to withdraw. The trading fee is not charged.