diff --git a/src/ripple/app/paths/AMMLiquidity.h b/src/ripple/app/paths/AMMLiquidity.h index 60155dbf13f..8159a6ab35a 100644 --- a/src/ripple/app/paths/AMMLiquidity.h +++ b/src/ripple/app/paths/AMMLiquidity.h @@ -148,6 +148,9 @@ class AMMLiquidity */ std::optional> maxOffer(TAmounts const& balances, Rules const& rules) const; + + Quality + adjSpotQualityWithFees(TAmounts const& balances) const; }; } // namespace ripple diff --git a/src/ripple/app/paths/impl/AMMLiquidity.cpp b/src/ripple/app/paths/impl/AMMLiquidity.cpp index 9ec23d08a1a..3b8690b9e8d 100644 --- a/src/ripple/app/paths/impl/AMMLiquidity.cpp +++ b/src/ripple/app/paths/impl/AMMLiquidity.cpp @@ -115,6 +115,17 @@ maxOut(T const& out, Issue const& iss) } } // namespace +template +Quality +AMMLiquidity::adjSpotQualityWithFees( + TAmounts const& balances) const +{ + auto const outWithFee = + toAmount(issueOut_, balances.out * feeMult(tradingFee_)); + return Quality{balances}; + return Quality{TAmounts{balances.in, outWithFee}}; +} + template std::optional> AMMLiquidity::maxOffer( @@ -135,11 +146,16 @@ AMMLiquidity::maxOffer( auto const out = maxOut(balances.out, issueOut()); if (out <= TOut{0} || out >= balances.out) return std::nullopt; + auto const spotPriceQ = [&]() { + if (!rules.enabled(fixAMMv1_1)) + return Quality{balances}; + return adjSpotQualityWithFees(balances); + }(); return AMMOffer( *this, {swapAssetOut(balances, out, tradingFee_), out}, balances, - Quality{balances}); + spotPriceQ); } } @@ -176,7 +192,12 @@ AMMLiquidity::getOffer( // to the requested clobQuality but not exactly and potentially SPQ may keep // on approaching clobQuality for many iterations. Checking for the quality // threshold prevents this scenario. - if (auto const spotPriceQ = Quality{balances}; clobQuality && + auto const spotPriceQ = [&]() { + if (!view.rules().enabled(fixAMMv1_1)) + return Quality{balances}; + return adjSpotQualityWithFees(balances); + }(); + if (clobQuality && (spotPriceQ <= clobQuality || withinRelativeDistance(spotPriceQ, *clobQuality, Number(1, -7)))) { diff --git a/src/test/app/AMM_test.cpp b/src/test/app/AMM_test.cpp index b4abd385257..d187dd29b81 100644 --- a/src/test/app/AMM_test.cpp +++ b/src/test/app/AMM_test.cpp @@ -6741,6 +6741,7 @@ struct AMM_test : public jtx::AMMTest void testLPTokenBalance(FeatureBitset features) { + testcase("Fix LPToken Balance"); using namespace jtx; // Last Liquidity Provider is the issuer of one token @@ -6856,6 +6857,106 @@ struct AMM_test : public jtx::AMMTest } } + void + testFixAMMBlockingLOB(FeatureBitset features) + { + testcase("Fix AMM Offer Blocking LOB"); + using namespace jtx; + + { + Env env(*this, features); + auto const XPM = gw["XPM"]; + + fund( + env, + gw, + {alice, carol}, + XRP(1'000'000'000), + {XPM(1'000'000'000)}); + + env(offer(alice, STAmount{XPM, UINT64_C(1), -9}, XRPAmount{1'000})); + env(offer(alice, STAmount{XPM, UINT64_C(1), -8}, XRPAmount{10})); + env(offer(alice, STAmount{XPM, UINT64_C(1), -5}, XRPAmount{1'000})); + env(offer( + alice, + STAmount{XPM, UINT64_C(5'00001605), -8}, + XRPAmount{5'065'000})); + env(offer( + alice, + STAmount{XPM, UINT64_C(196'547002735), -9}, + XRPAmount{196'527'350})); + env(offer( + alice, + STAmount{XPM, UINT64_C(1'05'314740196), -9}, + XRPAmount{57'822'092})); + env(offer( + alice, STAmount{XPM, UINT64_C(100)}, XRPAmount{3'600'879})); + env(offer( + alice, + STAmount{XPM, UINT64_C(500'00555), -5}, + XRPAmount{18'002'000})); + env(offer( + alice, + STAmount{XPM, UINT64_C(360'00288), -5}, + XRPAmount{12'960'000})); + env(offer( + alice, + STAmount{XPM, UINT64_C(2'500'02), -2}, + XRPAmount{90'000'000})); + env(offer( + alice, + STAmount{XPM, UINT64_C(400'009107432), -9}, + XRPAmount{14'001'999})); + env(offer( + alice, + STAmount{XPM, UINT64_C(10'000'0714), -4}, + XRPAmount{350'020'000})); + env(offer( + alice, STAmount{XPM, UINT64_C(142860)}, XRPAmount{5000175003})); + env(offer( + alice, + STAmount{XPM, UINT64_C(28'572), -3}, + XRPAmount{1'000'000})); + env(offer( + alice, + STAmount{XPM, UINT64_C(924'828553344), -9}, + XRPAmount{32'368'352})); + env(offer( + alice, + STAmount{XPM, UINT64_C(136'175266308), -9}, + XRPAmount{4'766'039})); + env.close(); + + AMM amm(env, gw, XPM(1), XRPAmount{35'000}, false, 50); + + env.close(); + + if (!features[fixAMMv1_1]) + { + env(offer(carol, XRPAmount{34902}, XPM(25'714'285)), + txflags(tfSell | tfImmediateOrCancel), + ter(tecKILLED)); + env.close(); + BEAST_EXPECT(amm.expectBalances( + XPM(1), XRPAmount{35'000}, amm.tokens())); + BEAST_EXPECT(expectOffers(env, alice, 16)); + BEAST_EXPECT(expectOffers(env, carol, 0)); + } + else + { + env(offer(carol, XRPAmount{34902}, XPM(25'714'285)), + txflags(tfSell | tfImmediateOrCancel)); + env.close(); + BEAST_EXPECT(amm.expectBalances( + STAmount{XPM, UINT64_C(8'758'662723307628), -12}, + XRPAmount{4}, + amm.tokens())); + BEAST_EXPECT(expectOffers(env, alice, 0)); + BEAST_EXPECT(expectOffers(env, carol, 0)); + } + } + } + void run() override { @@ -6900,6 +7001,8 @@ struct AMM_test : public jtx::AMMTest testFixAMMOfferBlockedByLOB(all - fixAMMv1_1); testLPTokenBalance(all); testLPTokenBalance(all - fixAMMv1_1); + testFixAMMBlockingLOB(all); + testFixAMMBlockingLOB(all - fixAMMv1_1); } }; diff --git a/src/test/jtx/impl/TestHelpers.cpp b/src/test/jtx/impl/TestHelpers.cpp index 41d9db7c7e5..1489afce0b1 100644 --- a/src/test/jtx/impl/TestHelpers.cpp +++ b/src/test/jtx/impl/TestHelpers.cpp @@ -161,7 +161,8 @@ expectOffers( } return true; }); - return size == cnt && matched == toMatch.size(); + return size == cnt && + (toMatch.empty() || (!toMatch.empty() && matched == toMatch.size())); } Json::Value