-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fixFillOrKill
: fix OfferCreate with tfFillOrKill if offer is better than open offer rate (Amendment)
#4694
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really like both the fix and the unit test that demonstrates the problem. I left a few comments on the unit test, but they are all at your discretion. If you would like to pick up all of the unit test suggestions then you can, if you would like, cherry-pick them from the top-most commit here: https://github.com/scottschurr/rippled/commits/gregt-fix-fillorkill
Very nicely done. Thanks for fixing my bug! 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This fix is elegant! Thanks for figuring out the problem and fixing it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for reviewing and the suggestions. I added/pushed your commit.
@@ -5081,6 +5081,157 @@ class Offer_test : public beast::unit_test::suite | |||
pass(); | |||
} | |||
|
|||
void | |||
testFillOrKill(FeatureBitset features) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks very much for adding this test. I especially like the way it demonstrates identical behavior before flowCross and after flowCross but with your fix. Nicely done!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still 👍!
@seelabs : will you review this? |
note: seelabs plans to review (eventually :) |
if constexpr (std::is_same_v<TOutAmt, XRPAmount>) | ||
{ | ||
static auto max = XRPAmount{STAmount::cMaxNative}; | ||
return outReq == max; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can talk about this, but I don't love this solution. It's using a special value of output request to change behavior. Any payment that uses this value will get this special behavior, even payments that aren't offers and don't have the flag set. It also means if the implementation changes so we no long set these max amounts this code breaks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't argue that this is a great solution, but that's a problem with the FlowCross implementation, not with this fix. @gregtatcam is using the only indication that FlowCross sends down through the payment engine that the tfSell
flag is set:
https://github.com/XRPLF/rippled/blob/develop/src/ripple/app/tx/impl/CreateOffer.cpp#L739-L755
But, yes, there is also an offerCrossing
flag that is passed as well. We could add that to the sell
detection. Maybe like this...
// FlowCross handles tfSell by setting the deliver amount to max
auto const sell = [&]() -> bool {
if (offerCrossing)
{
if constexpr (std::is_same_v<TOutAmt, XRPAmount>)
{
static const auto max = XRPAmount{STAmount::cMaxNative};
return outReq == max;
}
else if constexpr (std::is_same_v<TOutAmt, IOUAmount>)
{
static const auto max =
IOUAmount{STAmount::cMaxValue / 2, STAmount::cMaxOffset};
return outReq >= max;
}
}
return false;
}();
That's still not great, but it makes sure that the sell
flag is only set when offer crossing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @scottschurr, it is better but as you said not great. I wanted to minimize the payment engine impact and not change the arguments to flow()
. If it's ok to change the args then the offerCrossing
can be changed from bool
to optional<uint32>
, which if seated contains the offer crossing flags and if not seated then it's not offer crossing. The semantics of the offerCrossing
flag remains the same but it does provide more information. The only thing I don't like is that partialPayment
flag carries a duplicate info (it's set to !(txFlags & tfFillOrKill)
) but it's probably ok. @seelabs does this work?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added enum OfferCrossing {No = 0, Yes, Sell}
and pass it as the offerCrossing
flag. This eliminates the check for the special value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good. Thanks for making that change, I appreciate it.
src/ripple/protocol/Feature.h
Outdated
@@ -348,6 +348,7 @@ extern uint256 const fixNonFungibleTokensV1_2; | |||
extern uint256 const fixNFTokenRemint; | |||
extern uint256 const fixReducedOffersV1; | |||
extern uint256 const featureClawback; | |||
extern uint256 const fixFillOrKillOnFlowCross; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I probably wouldn't mention "FlowCross". Just fixOfferFillOrKill
. There's only one offer crossing implementation, there's no need to mention it by name.
Given there are requested changes awaiting review by @gregtatcam , this PR is currently targeting a 2024 release. If there is more urgency to it, please comment here to make the case. Thanks! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First, your solution of changing the bool
to an enum
is terrific! It clarifies a bunch of code by changing a nondescript bool
into something that has useful descriptive names. Great choice!
I did leave two comments. You need to fix the name of a local in Offer_test::run()
. You can also consider fixing the names of your new enumerators so they match our code base conventions.
But other than those two things I really like this approach. Thanks!
src/test/app/Offer_test.cpp
Outdated
@@ -5153,12 +5344,17 @@ class Offer_test : public beast::unit_test::suite | |||
FeatureBitset const takerDryOffer{fixTakerDryOfferRemoval}; | |||
FeatureBitset const rmSmallIncreasedQOffers{fixRmSmallIncreasedQOffers}; | |||
FeatureBitset const immediateOfferKilled{featureImmediateOfferKilled}; | |||
FeatureBitset const fixFillOrKill{fixFillOrKill}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a naming problem here. You have two distinct values with the same name. Just pick a different name for the local variable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, it's fixed in this commit 3859760.
src/ripple/app/paths/impl/Steps.h
Outdated
@@ -39,6 +39,7 @@ class AMMContext; | |||
enum class DebtDirection { issues, redeems }; | |||
enum class QualityDirection { in, out }; | |||
enum class StrandDirection { forward, reverse }; | |||
enum OfferCrossing { No = 0, Yes = 1, Sell = 2 }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this code base the enumerators typically start with a lower case letter. See the three preceding enums.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
@gregtatcam This branch has conflicts that must be resolved The conflicts look simple but I'll let you take care of them, preferably by adding a merge commit. (As we've been doing, this branch will be squashed to a single commit when it lands in |
@gregtatcam - I am not sure you merged with latest at your convenience, please resolve |
Updated. |
@seelabs or @scottschurr - could you have a quick re-review to confirm that the merge looks OK? |
When I attempt to build the latest version of this branch the build fails. But that failure stems from the APIv2: remove tx_history and ledger header commit (1eac4d2) The failure is in src/test/jtx/TestHelpers.h:38...
The compiler says
I suspect that the library for the version of clang I'm running does not yet have support for My inability to compile just happened with the introduction of this commit. There are newer versions of Xcode I could upgrade to. I'm running 13.4.1, release June 22, 2022 -- about a year and a half old. There is a version 15.0.1 available, released Oct 18, 2023. Is it time to upgrade? |
Builds for me. I have Apple clang version 15.0.0 (clang-1500.0.40.1). |
According to this website, ranges support requires Xcode 14.3 or later: https://developer.apple.com/xcode/cpp/ |
Xcode 14.3 was released March 30, 2023 according to Wikipedia. |
In the mean time, we'd better have @seelabs review the merge commit, since the code is not building for me at the moment. |
note: #4788 is expected to fix the error scottschurr mentioned above |
Now that #4788 has been merged, this PR can be merged once CI passes. Meanwhile, @gregtatcam - can you confirm this looks good to merge? (If so, we can put the |
It's good to merge. |
fixFillOrKill
: fix OfferCreate with tfFillOrKill if offer is better than open offer rate (Amendment)
Introduce the `fixFillOrKill` amendment. Fix an edge case occurring when an offer with `tfFillOrKill` set (but without `tfSell` set) fails to cross an offer with a better rate. If `tfFillOrKill` is set, then the owner must receive the full TakerPays. Without this amendment, an offer fails if the entire `TakerGets` is not spent. With this amendment, when `tfSell` is not set, the entire `TakerGets` does not have to be spent. For details about OfferCreate, see: https://xrpl.org/offercreate.html Fix XRPLF#4684 --------- Co-authored-by: Scott Schurr <scott@ripple.com>
High Level Overview of Change
This PR addresses #4684 issue. This is the payment engine breaking change and requires an amendment.
Context of Change
flowCross amendment introduces an issue where the offer crossing with tfFillOrKill flag set and tfSell not set fails to cross an offer with a better rate. For instance, given two offers:
(takerPays/takerGets)
XRP(100)/USD(100)
USD(100)/XRP(101) has tfFillOrKill set
The second offer fails to cross but it should succeed because the taker can buy USD(100) for XRP(100) instead of XRP(101). XRPL reference for the offer create with tfFillOrKill states:
Type of Change
Before / After
The amendment code added in StrandFlow.cpp:flow() handles tfFillOrKill and tfFillOrKill+tfSell as two separate cases, whereas the original code handled both as one case.
Test Plan
Added testFillOrKill() to Offer_test.