Skip to content

fix: Correct hybrid offer deletion on credential expiry#6843

Open
Kassaking7 wants to merge 3 commits intoXRPLF:developfrom
Kassaking7:DEFI-629
Open

fix: Correct hybrid offer deletion on credential expiry#6843
Kassaking7 wants to merge 3 commits intoXRPLF:developfrom
Kassaking7:DEFI-629

Conversation

@Kassaking7
Copy link
Copy Markdown
Collaborator

High Level Overview of Change

When a hybrid offer owner's domain credential expires, the offer is completely deleted from all order books (including the open book) the next time OfferStream encounters it. This happens because OfferStream::step() applies the offerInDomain check to ALL offers with sfDomainID, regardless of whether the current book being walked is a domain book or an open book. Hybrid offers should remain available in the open book even if the owner loses domain access.

Root Cause

When OfferStream::step() iterates through the open (non-domain) order book and encounters a hybrid offer, it runs the offerInDomain check

Context of Change

Add a check in OfferStream::step() to only validate domain membership when walking a domain book

API Impact

  • Public API: New feature (new methods and/or new fields)
  • Public API: Breaking change (in general, breaking changes should only impact the next api_version)
  • libxrpl change (any change that may affect libxrpl or dependents of libxrpl)
  • Peer protocol change (must be backward compatible or bump the peer protocol version)

@Kassaking7 Kassaking7 changed the title fix: correct hybrid offer deletion on credential expiry fix: Correct hybrid offer deletion on credential expiry Apr 9, 2026
@codecov
Copy link
Copy Markdown

codecov bot commented Apr 9, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 81.6%. Comparing base (56c9d1d) to head (02db687).
⚠️ Report is 6 commits behind head on develop.

Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff           @@
##           develop   #6843   +/-   ##
=======================================
  Coverage     81.6%   81.6%           
=======================================
  Files         1010    1010           
  Lines        75982   75974    -8     
  Branches      7637    7603   -34     
=======================================
+ Hits         61984   61988    +4     
+ Misses       13998   13986   -12     
Files with missing lines Coverage Δ
src/libxrpl/tx/paths/OfferStream.cpp 79.0% <100.0%> (+0.2%) ⬆️

... and 25 files with indirect coverage changes

Impacted file tree graph

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@Kassaking7 Kassaking7 requested a review from shawnxie999 April 10, 2026 15:14
Comment thread src/libxrpl/tx/paths/OfferStream.cpp Outdated
Comment on lines +228 to +232
// Only validate domain membership when walking a domain book.
// Hybrid offers carry sfDomainID but also participate in the open
// book; expiry of the owner's domain credential should not evict
// the offer from the open book.
if (book_.domain.has_value() && entry->isFieldPresent(sfDomainID) &&
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This must be amendment gated. You can create an amendment, potentially to be bundled with other fixes

Comment thread src/test/app/PermissionedDEX_test.cpp Outdated
Comment on lines +1122 to +1132
// both offers are in the open book directory
BEAST_EXPECT(checkDirectorySize(env, openDir, 2));

// this normal payment should consume the regular offer and remove the
// unfunded hybrid offer
// a regular payment crosses the hybrid offer first (FIFO, older offer),
// then stops — the regular offer is untouched.
env(pay(alice, carol, USD(5)), path(~USD), sendmax(XRP(5)));
env.close();

BEAST_EXPECT(!offerExists(env, bob, hybridOfferSeq));
BEAST_EXPECT(checkOffer(env, bob, regularOfferSeq, XRP(5), USD(5)));
BEAST_EXPECT(checkDirectorySize(env, openDir, 1));
BEAST_EXPECT(checkOffer(env, bob, hybridOfferSeq, XRP(40), USD(40), lsfHybrid, true));
BEAST_EXPECT(checkOffer(env, bob, regularOfferSeq, XRP(10), USD(10)));
BEAST_EXPECT(checkDirectorySize(env, openDir, 2));
Copy link
Copy Markdown
Collaborator

@shawnxie999 shawnxie999 Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

existing test code should not be removed. You should have an if-statements to switch expected behavior when the amendment is on/off

Comment thread src/test/app/PermissionedDEX_test.cpp Outdated
Comment on lines +1291 to +1299

// Regression: hybrid offer is NOT deleted from the open book when the
// owner's domain credential expires.
//
// OfferStream::step() must only run the offerInDomain eviction check
// when walking a domain book (book_.domain.has_value()). When walking
// the open book the check must be skipped so that a hybrid offer stays
// available regardless of the owner's current domain-credential status.
{
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's move this to a new test function for visibility.

Comment thread src/test/app/PermissionedDEX_test.cpp Outdated
Comment on lines +1356 to +1357
// Offer still exists with 3 USD / 3 XRP remaining.
BEAST_EXPECT(checkOffer(env, devin, hybridOfferSeq, XRP(3), USD(3), lsfHybrid, true));
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it would be good to have to have another a domain payment trying to consume this offer after this (which should remove the offer)

Comment thread src/test/app/PermissionedDEX_test.cpp Outdated
Comment on lines +1525 to +1526
testHybridInvalidOffer(all | fixSecurity3_1_3);
testHybridOpenBookAfterCredentialExpiry(all | fixSecurity3_1_3);
Copy link
Copy Markdown
Collaborator

@shawnxie999 shawnxie999 Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all already includes all the amendments, you need to test all - fixSecurity3_1_3 instead

Comment thread src/test/app/PermissionedDEX_test.cpp Outdated
Comment on lines +1525 to +1526
testHybridInvalidOffer(all | fixSecurity3_1_3);
testHybridOpenBookAfterCredentialExpiry(all | fixSecurity3_1_3);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just curious what's the difference between testHybridInvalidOffer and testHybridOpenBookAfterCredentialExpiry? don't they test similar behaviors?

Comment thread src/libxrpl/tx/paths/OfferStream.cpp Outdated
// Hybrid offers carry sfDomainID but also participate in the open
// book; expiry of the owner's domain credential should not evict
// the offer from the open book.
if ((!view_.rules().enabled(fixSecurity3_1_3) || book_.domain.has_value()) &&
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI, I'm not sure if this change is within the scope of fixSecurity3_1_3, which is going to be released soon. If this isn't included, we should have a separate amendment

@Kassaking7 Kassaking7 requested a review from shawnxie999 April 16, 2026 16:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants