Skip to content
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

refactor(CL): rounding edge case in zfo strategy and tests #6379

Closed
wants to merge 12 commits into from

Conversation

p0mvn
Copy link
Member

@p0mvn p0mvn commented Sep 12, 2023

Closes: #XXX

What is the purpose of the change

Introduces a solution to the rounding edge case discovered by the fuzz test suite.

It seems to occur when swapping at a low price level. There were instances caught where our amountInRemainingLessFee is one unit smaller than amountIn necessary to reach the target (next initialized tick). Additionally, the sqrtPriceNext ends up being only 1 BigDec ULP above the target.

The likey reason for such behavior is QuoRoundUp here:

return liquidity.Mul(sqrtPriceCurrent).QuoRoundUp(denominator)

However, if we remove this rounding behavior, we start getting issues elsewhere. Specifically, over-consuming the amount in.
The current direction is correct to round up in pool's favor.

However, it seems that in cases like the one encountered, it sometimes overrounds, breaking the swap progress.

Therefore, we detect this edge case and simply bump the sqrtPriceNext by one ULP to reach target and consume the full amount in necessary to reach the next initialized tick.

Testing and Verifying

This was caught by the fuzz test suite. Upon applying the change, the issue got resolved.

This issue can be reproducing by following the instructions here: https://github.com/osmosis-labs/osmosis/pull/6380/files#r1323366454

Documentation and Release Note

  • Does this pull request introduce a new feature or user-facing behavior changes?
  • Changelog entry added to Unreleased section of CHANGELOG.md?

Where is the change documented?

  • Specification (x/{module}/README.md)
  • Osmosis documentation site
  • Code comments?
  • N/A

@p0mvn p0mvn added the V:state/breaking State machine breaking PR label Sep 12, 2023
Comment on lines 84 to 98
// This edge case deals with an edge case stemming from rounding.
// If the computed sqrtPriceNext is 1 BigDec ULP away from the sqrtPriceTarget AND
// amount zero in needed to reach target is 1 unit greater than the amount zero remaining after spread rewards,
// we deems this as a rounding error and make sqrtPriceNext equal to sqrtPriceTarget
// while consuming all among in actually remaining.
//
// Without this change, we reach an infinite loop swap condition where we try to swap until the target but fail to consume
// any amount in to advance the swap by only one ULP.
//
// Changing the rounding behavior causes issues in other parts of the system where we end up overconsuming the final amount in.
if sqrtPriceNext.Sub(sqrtPriceTarget).Equal(smallestBigDec) && amountZeroIn.Sub(amountZeroInRemainingLessSpreadReward).Equal(oneBigDec) {
sqrtPriceNext = sqrtPriceTarget
amountZeroIn = amountZeroInRemainingLessSpreadReward
}

Copy link
Member Author

@p0mvn p0mvn Sep 12, 2023

Choose a reason for hiding this comment

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

Note to reviewer:

Fuzz tester repro that is failing without this change: https://github.com/osmosis-labs/osmosis/pull/6380/files#r1323366454

Must be ran from the linked branch with this change reverted

@p0mvn p0mvn marked this pull request as ready for review September 12, 2023 18:58
Comment on lines +84 to +98
// The code snippet below deals with an edge case stemming from rounding.
// If the computed sqrtPriceNext is 1 BigDec ULP away from the sqrtPriceTarget AND
// amount zero in needed to reach target is 1 unit greater than the amount zero remaining after spread rewards,
// we deem this as a rounding error and make sqrtPriceNext equal to sqrtPriceTarget
// while consuming all amount in actually remaining.
//
// Without this change, we reach an infinite loop swap condition where we try to swap until the target but fail to consume
// any amount in to advance by only 1 ULP.
//
// Changing the rounding behavior causes issues in other parts of the system where we end up overconsuming the final amount in.
if sqrtPriceNext.Sub(sqrtPriceTarget).Equal(smallestBigDec) && amountZeroIn.Sub(amountZeroInRemainingLessSpreadReward).Equal(oneBigDec) {
sqrtPriceNext = sqrtPriceTarget
amountZeroIn = amountZeroInRemainingLessSpreadReward
}

Copy link
Member

Choose a reason for hiding this comment

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

Is a similar situation possible in the InGivenOut direction?

Copy link
Member Author

@p0mvn p0mvn Sep 14, 2023

Choose a reason for hiding this comment

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

In given out did not experience this in the fuzzer.

Since rounding functions and logic are different between the two, a similar situation might be impossible for in given out.

Since this is more of a UX issue (some swaps failing due to infinite loops) and not a safety issue, I don't think it is worth a deep investigation

@@ -77,6 +81,21 @@ func (s zeroForOneStrategy) ComputeSwapWithinBucketOutGivenIn(sqrtPriceCurrent,
sqrtPriceNext = math.GetNextSqrtPriceFromAmount0InRoundingUp(sqrtPriceCurrent, liquidityBigDec, amountZeroInRemainingLessSpreadReward)
}

// The code snippet below deals with an edge case stemming from rounding.
// If the computed sqrtPriceNext is 1 BigDec ULP away from the sqrtPriceTarget AND
Copy link
Member

Choose a reason for hiding this comment

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

Is it 1 ULP or 1 smallestBigDec? Or are these synonymous?

Copy link
Member Author

Choose a reason for hiding this comment

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

Synonymous in this context. BigDec and Dec ULPs are different.

This is what this stands for: https://en.wikipedia.org/wiki/Unit_in_the_last_place

Comment on lines +199 to +200
sqrtPriceCurrent: types.MinSqrtPriceBigDec,
sqrtPriceTarget: types.MinSqrtPriceV2,
Copy link
Member

Choose a reason for hiding this comment

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

To clarify, this doesn't necessarily need to happen at the minSqrtPrice target right? Just the diff between these two needs to be a smallestBigDec?

Copy link
Member Author

@p0mvn p0mvn Sep 14, 2023

Choose a reason for hiding this comment

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

Should be possible at other targets. However, the fuzzer was hitting this when swapping to min tick only. I don't have other repros

Base automatically changed from roman/price-limit-api to main September 14, 2023 11:17
@p0mvn p0mvn marked this pull request as draft September 14, 2023 18:50
@p0mvn p0mvn closed this Sep 14, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants