mirror of
https://github.com/XRPLF/rippled.git
synced 2026-08-21 14:20:56 +00:00
fix: AMM Quality Leak into Domain BookStep for Permissioned DEX (#6853)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -905,6 +905,11 @@ BookStep<TIn, TOut, TDerived>::getAMMOffer(
|
||||
ReadView const& view,
|
||||
std::optional<Quality> const& clobQuality) const
|
||||
{
|
||||
// AMM doesn't support domain books. When fixCleanup3_3_0 is enabled, exclude
|
||||
// AMM liquidity so quality estimation matches actual crossing (tryAMM skips
|
||||
// AMM for domain books).
|
||||
if (book_.domain && view.rules().enabled(fixCleanup3_3_0))
|
||||
return std::nullopt;
|
||||
if (ammLiquidity_)
|
||||
return ammLiquidity_->getOffer(view, clobQuality);
|
||||
return std::nullopt;
|
||||
|
||||
@@ -993,6 +993,83 @@ class PermissionedDEX_test : public beast::unit_test::Suite
|
||||
BEAST_EXPECT(usd == USD(45));
|
||||
}
|
||||
|
||||
void
|
||||
testAmmQualityNotLeaked(FeatureBitset features)
|
||||
{
|
||||
bool const excludesAmmFromDomainQuality = features[fixCleanup3_3_0];
|
||||
|
||||
testcase << "AMM quality not leaked into domain BookStep"
|
||||
<< (excludesAmmFromDomainQuality ? " (Cleanup3_3_0 enabled)"
|
||||
: " (Cleanup3_3_0 disabled)");
|
||||
|
||||
Env env(*this, features);
|
||||
auto const& [gw, domainOwner, alice, bob, carol, USD, domainID, credType] =
|
||||
PermissionedDEX(env);
|
||||
auto const eur = gw["EUR"];
|
||||
|
||||
env.trust(eur(1000), bob, domainOwner);
|
||||
env.close();
|
||||
env(pay(gw, bob, eur(100)));
|
||||
env.close();
|
||||
|
||||
env(pay(gw, alice, USD(500)));
|
||||
env.close();
|
||||
|
||||
// The AMM makes the direct XRP->USD book look much better than it
|
||||
// really is for domain payments. The domain LOB direct path is 1:1,
|
||||
// while the competing XRP->EUR->USD path is 2:1.
|
||||
AMM const amm(env, alice, XRP(10), USD(500));
|
||||
|
||||
auto const directOfferSeq{env.seq(bob)};
|
||||
env(offer(bob, XRP(10), USD(10)), Domain(domainID));
|
||||
env.close();
|
||||
|
||||
auto const xrpEurOfferSeq{env.seq(bob)};
|
||||
env(offer(bob, XRP(10), eur(20)), Domain(domainID));
|
||||
env.close();
|
||||
|
||||
auto const eurUsdOfferSeq{env.seq(domainOwner)};
|
||||
env(offer(domainOwner, eur(20), USD(20)), Domain(domainID));
|
||||
env.close();
|
||||
|
||||
auto const carolBalBefore = env.balance(carol, USD);
|
||||
|
||||
// Both paths compete for the same XRP(10) sendmax. If AMM quality leaks
|
||||
// into the direct domain book, the engine ranks direct XRP->USD first
|
||||
// but crossing can only consume the 1:1 LOB offer. With the fix, the
|
||||
// direct book is ranked by its domain LOB quality, so the 2:1
|
||||
// XRP->EUR->USD path executes first.
|
||||
env(pay(alice, carol, USD(100)),
|
||||
Path(~USD),
|
||||
Path(~eur, ~USD),
|
||||
Sendmax(XRP(10)),
|
||||
Txflags(tfPartialPayment | tfNoRippleDirect),
|
||||
Domain(domainID));
|
||||
env.close();
|
||||
|
||||
auto const delivered = env.balance(carol, USD) - carolBalBefore;
|
||||
if (excludesAmmFromDomainQuality)
|
||||
{
|
||||
BEAST_EXPECT(delivered == USD(20));
|
||||
|
||||
BEAST_EXPECT(checkOffer(env, bob, directOfferSeq, XRP(10), USD(10), 0, true));
|
||||
BEAST_EXPECT(!offerExists(env, bob, xrpEurOfferSeq));
|
||||
BEAST_EXPECT(!offerExists(env, domainOwner, eurUsdOfferSeq));
|
||||
}
|
||||
else
|
||||
{
|
||||
BEAST_EXPECT(delivered == USD(10));
|
||||
|
||||
BEAST_EXPECT(!offerExists(env, bob, directOfferSeq));
|
||||
BEAST_EXPECT(checkOffer(env, bob, xrpEurOfferSeq, XRP(10), eur(20), 0, true));
|
||||
BEAST_EXPECT(checkOffer(env, domainOwner, eurUsdOfferSeq, eur(20), USD(20), 0, true));
|
||||
}
|
||||
|
||||
auto [xrp, usd, lpt] = amm.balances(XRP, USD);
|
||||
BEAST_EXPECT(xrp == XRP(10));
|
||||
BEAST_EXPECT(usd == USD(500));
|
||||
}
|
||||
|
||||
void
|
||||
testHybridOfferCreate(FeatureBitset features)
|
||||
{
|
||||
@@ -1943,6 +2020,8 @@ public:
|
||||
testOfferTokenIssuerInDomain(all);
|
||||
testRemoveUnfundedOffer(all);
|
||||
testAmmNotUsed(all);
|
||||
testAmmQualityNotLeaked(all);
|
||||
testAmmQualityNotLeaked(all - fixCleanup3_3_0);
|
||||
testAutoBridge(all);
|
||||
|
||||
// Test hybrid offers
|
||||
|
||||
Reference in New Issue
Block a user