Compare commits

...

2 Commits

Author SHA1 Message Date
Valentin Balaschenko
20766dc505 tests 2026-05-29 13:58:08 +01:00
Valentin Balaschenko
532cac0446 fix 2026-05-29 13:44:39 +01:00
4 changed files with 54 additions and 0 deletions

View File

@@ -19,6 +19,14 @@ namespace xrpl::permissioned_dex {
bool
accountInDomain(ReadView const& view, AccountID const& account, Domain const& domainID)
{
// A zero domainID is malformed: it would build a keylet with a zero key
// and violate Ledger::read's invariant. Defense in depth: callers should
// reject this at preflight, but guard here too so any future caller and
// the order-book sweep path (offerInDomain -> here) cannot trip the
// invariant.
if (domainID.isZero())
return false;
auto const sleDomain = view.read(keylet::permissionedDomain(domainID));
if (!sleDomain)
return false;

View File

@@ -94,6 +94,16 @@ OfferCreate::preflight(PreflightContext const& ctx)
if (tx.isFlag(tfHybrid) && !tx.isFieldPresent(sfDomainID))
return temINVALID_FLAG;
// A present but zero DomainID is malformed: it would build a keylet with
// a zero key and violate Ledger::read's invariant. Reject at preflight
// (temMALFORMED) instead of letting it slip to preclaim and be
// misclassified as tecNO_PERMISSION.
if (tx.isFieldPresent(sfDomainID) && tx.getFieldH256(sfDomainID).isZero())
{
JLOG(j.debug()) << "Malformed offer: zero DomainID";
return temMALFORMED;
}
bool const bImmediateOrCancel(tx.isFlag(tfImmediateOrCancel));
bool const bFillOrKill(tx.isFlag(tfFillOrKill));

View File

@@ -125,6 +125,16 @@ Payment::preflight(PreflightContext const& ctx)
if (!mpTokensV2 && isDstMPT && ctx.tx.isFieldPresent(sfPaths))
return temMALFORMED;
// A present but zero DomainID is malformed: it would build a keylet with
// a zero key and violate Ledger::read's invariant. Reject at preflight
// (temMALFORMED) instead of letting it slip to preclaim and be
// misclassified as tecNO_PERMISSION.
if (tx.isFieldPresent(sfDomainID) && tx.getFieldH256(sfDomainID).isZero())
{
JLOG(j.debug()) << "Malformed payment: zero DomainID";
return temMALFORMED;
}
bool const partialPaymentAllowed = tx.isFlag(tfPartialPayment);
bool const limitQuality = tx.isFlag(tfLimitQuality);
bool const defaultPathsAllowed = !tx.isFlag(tfNoRippleDirect);

View File

@@ -278,6 +278,17 @@ class PermissionedDEX_test : public beast::unit_test::Suite
env.close();
}
// preflight - a present but zero DomainID is malformed.
// Regression test for "Ledger::read : zero key" assertion.
{
Env env(*this, features);
auto const& [gw_, domainOwner, alice_, bob_, carol_, USD, domainID, credType] =
PermissionedDEX(env);
env(offer(bob_, XRP(10), USD(10)), Domain(uint256{}), Ter(temMALFORMED));
env.close();
}
// apply - offer can be created even if takergets issuer is not in
// domain
{
@@ -413,6 +424,21 @@ class PermissionedDEX_test : public beast::unit_test::Suite
env.close();
}
// preflight - a present but zero DomainID is malformed.
// Regression test for "Ledger::read : zero key" assertion.
{
Env env(*this, features);
auto const& [gw_, domainOwner, alice_, bob_, carol_, USD, domainID, credType] =
PermissionedDEX(env);
env(pay(bob_, alice_, USD(10)),
Path(~USD),
Sendmax(XRP(10)),
Domain(uint256{}),
Ter(temMALFORMED));
env.close();
}
// preclaim - payment with non-domain destination fails
{
Env env(*this, features);