fix: Update PermissionedDEX invariant domain tracking for valid offer replacement (#7387)

Co-authored-by: Bart <bthomee@users.noreply.github.com>
This commit is contained in:
Kassaking7
2026-07-23 17:40:21 -04:00
committed by GitHub
parent 7908aec2ec
commit 9afa1cf4d1
3 changed files with 70 additions and 4 deletions

View File

@@ -17,7 +17,8 @@ class ValidPermissionedDEX
bool regularOffers_ = false; // post-fixCleanup3_2_0: excludes deleted offers
bool badHybridsOld_ = false; // pre-fixCleanup3_1_3: missing field/domain or size > 1
bool badHybrids_ = false; // post-fixCleanup3_1_3: also catches size == 0 (size != 1)
hash_set<uint256> domains_;
hash_set<uint256> domainsOld_; // pre-fixCleanup3_4_0: also flags deleted domains
hash_set<uint256> domains_; // post-fixCleanup3_4_0: excludes deleted domains
public:
void

View File

@@ -1,6 +1,7 @@
#include <xrpl/tx/invariants/PermissionedDEXInvariant.h>
#include <xrpl/basics/Log.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/Feature.h>
@@ -19,17 +20,23 @@ namespace xrpl {
void
ValidPermissionedDEX::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after)
{
auto trackDomain = [this, isDelete](uint256 const& domain) {
domainsOld_.insert(domain);
if (!isDelete)
domains_.insert(domain);
};
if (after && after->getType() == ltDIR_NODE)
{
if (after->isFieldPresent(sfDomainID))
domains_.insert(after->getFieldH256(sfDomainID));
trackDomain(after->getFieldH256(sfDomainID));
}
if (after && after->getType() == ltOFFER)
{
if (after->isFieldPresent(sfDomainID))
{
domains_.insert(after->getFieldH256(sfDomainID));
trackDomain(after->getFieldH256(sfDomainID));
}
else
{
@@ -87,7 +94,8 @@ ValidPermissionedDEX::finalize(
// for both payment and offercreate, there shouldn't be another domain
// that's different from the domain specified
for (auto const& d : domains_)
auto const& domains = view.rules().enabled(fixCleanup3_4_0) ? domains_ : domainsOld_;
for (auto const& d : domains)
{
if (d != domain)
{

View File

@@ -2001,6 +2001,61 @@ class PermissionedDEX_test : public beast::unit_test::Suite
}
}
void
testReplaceDomainOfferWithOtherDomainOffer(FeatureBitset features)
{
bool const fixEnabled = features[fixCleanup3_4_0];
testcase << "Replace domain offer via OfferCreate"
<< (fixEnabled ? " (fixCleanup3_4_0 enabled)" : " (fixCleanup3_4_0 disabled)");
Env env(*this, features);
auto const& [gw, domainOwner, alice, bob, carol, USD, domainA, credType] =
PermissionedDEX(env);
Account const domainOwnerB("permdex-domainOwnerB");
auto const domainB =
setupDomain(env, {alice, bob, carol, gw}, domainOwnerB, "permdex-other-domain");
BEAST_EXPECT(domainA != domainB);
auto const oldSeq = env.seq(alice);
env(offer(alice, USD(100), XRP(1)), Domain(domainA));
env.close();
BEAST_EXPECT(checkOffer(env, alice, oldSeq, USD(100), XRP(1), 0, true));
auto const oldOffer = env.le(keylet::offer(alice.id(), oldSeq));
if (!BEAST_EXPECT(oldOffer))
return;
BEAST_EXPECT(oldOffer->getFieldH256(sfDomainID) == domainA);
auto const newSeq = env.seq(alice);
// The invariant should reject mixing active Permissioned DEX domains,
// not a domain that is only touched because its offer is being deleted.
if (fixEnabled)
{
env(offer(alice, USD(100), XRP(2)), Domain(domainB), Json(jss::OfferSequence, oldSeq));
env.close();
BEAST_EXPECT(!offerExists(env, alice, oldSeq));
BEAST_EXPECT(checkOffer(env, alice, newSeq, USD(100), XRP(2), 0, true));
auto const newOffer = env.le(keylet::offer(alice.id(), newSeq));
if (!BEAST_EXPECT(newOffer))
return;
BEAST_EXPECT(newOffer->getFieldH256(sfDomainID) == domainB);
}
else
{
env(offer(alice, USD(100), XRP(2)),
Domain(domainB),
Json(jss::OfferSequence, oldSeq),
Ter(tecINVARIANT_FAILED));
env.close();
BEAST_EXPECT(checkOffer(env, alice, oldSeq, USD(100), XRP(1), 0, true));
BEAST_EXPECT(!offerExists(env, alice, newSeq));
}
}
public:
void
run() override
@@ -2038,6 +2093,8 @@ public:
// only after fixCleanup3_2_0.
testCancelRegularOfferWithDomainCreate(all);
testCancelRegularOfferWithDomainCreate(all - fixCleanup3_2_0);
testReplaceDomainOfferWithOtherDomainOffer(all);
testReplaceDomainOfferWithOtherDomainOffer(all - fixCleanup3_4_0);
}
};