-
Notifications
You must be signed in to change notification settings - Fork 607
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
[CL]: Fix tick rounding bug and implement direct conversion from tick <> sqrt price #5541
Conversation
SqrtPriceToTickRoundDownSpacing
and replace all uses of price to tick conversion with itSqrtPriceToTickRoundDownSpacing
and use in place of all tick conversions
@@ -2509,3 +2509,32 @@ func (s *KeeperTestSuite) TestCreateFullRangePositionLocked() { | |||
}) | |||
} | |||
} | |||
|
|||
// TestTickRoundingEdgeCase tests an edge case where incorrect tick rounding would cause LP funds to be drained. | |||
func (s *KeeperTestSuite) TestTickRoundingEdgeCase() { |
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.
Note to reviewer: this is a test case derived from the original attack vector, demonstrating that the change fixes the issue
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.
Lets link the PR number that showcased it
DefaultExponentConsecutivePositionLowerTick, _ = math.PriceToTickRoundDownSpacing(sdk.NewDec(5500), DefaultTickSpacing) | ||
DefaultExponentConsecutivePositionUpperTick, _ = math.PriceToTickRoundDownSpacing(sdk.NewDec(6250), DefaultTickSpacing) | ||
DefaultExponentOverlappingPositionLowerTick, _ = math.PriceToTickRoundDownSpacing(sdk.NewDec(4000), DefaultTickSpacing) | ||
DefaultExponentOverlappingPositionUpperTick, _ = math.PriceToTickRoundDownSpacing(sdk.NewDec(4999), DefaultTickSpacing) |
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.
Driveby rename as the original name could be misinterpreted to refer to rounding behavior
} | ||
|
||
if tickIndexModulus != 0 { | ||
tickIndex = tickIndex - tickIndexModulus |
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.
Seems like diff got cluttered when rebasing to main. This section might be easier to review in split view
SqrtPriceToTickRoundDownSpacing
and use in place of all tick conversionsThere 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.
Well done on this, will approve after some discourse in some of these comments
if (!outOfBounds && sqrtPrice.GTE(sqrtPriceTplus2)) || sqrtPrice.LT(sqrtPriceTmin1) || sqrtPrice.GT(sqrtPriceTplus2) { | ||
return 0, fmt.Errorf("sqrt price to tick could not find a satisfying tick index. Hit bounds: %v", outOfBounds) | ||
} |
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.
Not doubting this is correct, but its a bit hard to reason about. Consider breaking each of the three cases further in the comments, but not blocking (especially if its just me who is having trouble groking 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.
I am confused about the !outOfBounds
conditional as tied to this
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'd expect it to be !outofbounds && (many or clauses)
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 is unfortunately a slightly hacky way of special casing bound checks for MinTick
/MaxTick
. The first conditional is intended to cover all ticks/sqrt prices that are not boundary ticks. Please see my recent commit for a hopefully more readable bound check: 7c6f256
…verrounding in pool's favor
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.
Spoke offline IRT that unanswered questions. Approving but looking for a second approval before merge. Nice work again :)
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.
Great work! I would like to do another pass tomorrow and run tests more. Leaving nits for now
// Execute a swap that brings the price close enough to the edge of a tick to trigger bankers rounding | ||
swapAddr := testAccs[2] | ||
desiredTokenOut := sdk.NewCoin(USDC, sdk.NewInt(10000)) | ||
s.FundAcc(swapAddr, sdk.NewCoins(sdk.NewCoin(ETH, sdk.NewInt(1000000000000000000)))) | ||
_, _, _, _, _, err := s.clk.SwapInAmtGivenOut(s.Ctx, swapAddr, pool, desiredTokenOut, ETH, sdk.ZeroDec(), sdk.ZeroDec()) | ||
s.Require().NoError(err) | ||
|
||
// Both positions should be able to withdraw successfully | ||
_, _, err = s.clk.WithdrawPosition(s.Ctx, firstPositionAddr, firstPosId, firstPosLiq) | ||
s.Require().NoError(err) | ||
_, _, err = s.clk.WithdrawPosition(s.Ctx, secondPositionAddr, secondPosId, secondPosLiq) | ||
s.Require().NoError(err) |
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.
Commenting for visibility, I expect this to solve this issue in #5526 :
https://github.com/osmosis-labs/osmosis/pull/5526/files#r1233117388
// CONTRACT: tickSpacing must be smaller or equal to the max of 1 << 63 - 1. | ||
// This is not a concern because we have authorized tick spacings that are smaller than this max, | ||
// and we don't expect to ever require it to be this large. | ||
func (s *KeeperTestHelper) PriceToTickRoundDownSpacing(price sdk.Dec, tickSpacing uint64) (int64, error) { |
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.
Note to reviewer: these functions were moved to be high level test helpers to get them out of the state machine without breaking the many tests that rely on them to (correctly) generate expected values
@@ -47,7 +47,7 @@ type RangeTestParams struct { | |||
var ( | |||
DefaultRangeTestParams = RangeTestParams{ | |||
// Base amounts | |||
baseNumPositions: 1000, | |||
baseNumPositions: 10, |
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.
Note to reviewer: the changes in this file are mainly cleanup/test speed related changes to ensure tests run in a reasonable amount of time
outOfBounds := false | ||
if truncatedTick <= types.MinTick { | ||
truncatedTick = types.MinTick + 1 | ||
outOfBounds = true | ||
} else if truncatedTick >= types.MaxTick-1 { | ||
truncatedTick = types.MaxTick - 2 | ||
outOfBounds = true |
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 wonder if its simpler to just deal with these by checking the boundary cases in a separate function, on the sqrt price directly. (pre-squaring)
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.
Possibly, although my previous attempts at this led to constraining the "real" min tick and max tick by 1-2 ticks (which might be fine but leads to more cases that need to be checked/handled)
If you see a way to abstract this logic without triggering this, would be great for us to do a follow up PR to clean this up!
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 LGTM.
I think I'd like to revisit the edge case detections, and add some more randomized TickToPrice / boundary checks, but those can all be done in followup!
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.
LGTM. Great work
"sqrt price exactly equal to min sqrt price": { | ||
sqrtPrice: types.MinSqrtPrice, | ||
tickSpacing: defaultTickSpacing, | ||
tickExpected: types.MinTick, | ||
}, | ||
"sqrt price equal to max sqrt price minus one ULP": { | ||
sqrtPrice: types.MaxSqrtPrice.Sub(sdk.SmallestDec()), | ||
tickSpacing: defaultTickSpacing, | ||
tickExpected: types.MaxTick - defaultTickSpacing, | ||
}, | ||
"sqrt price corresponds exactly to max tick - 1 (tick spacing 1)": { | ||
// Calculated using TickToSqrtPrice(types.MaxTick - 1) | ||
sqrtPrice: sdk.MustNewDecFromStr("9999999499999987499.999374999960937497"), | ||
tickSpacing: 1, | ||
tickExpected: types.MaxTick - 1, | ||
}, | ||
"sqrt price one ULP below max tick - 1 (tick spacing 1)": { | ||
// Calculated using TickToSqrtPrice(types.MaxTick - 1) - 1 ULP | ||
sqrtPrice: sdk.MustNewDecFromStr("9999999499999987499.999374999960937496"), | ||
tickSpacing: 1, | ||
tickExpected: types.MaxTick - 2, | ||
}, | ||
"sqrt price one ULP below max tick - 1 (tick spacing 100)": { | ||
// Calculated using TickToSqrtPrice(types.MaxTick - 1) - 1 ULP | ||
sqrtPrice: sdk.MustNewDecFromStr("9999999499999987499.999374999960937496"), | ||
tickSpacing: defaultTickSpacing, | ||
tickExpected: types.MaxTick - defaultTickSpacing, | ||
}, |
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.
Should we also be having test cases around min tick bounds similar to how this is done for max tick?
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.
Will add these cases in the min tick change PR as the expected values are thrown off due to canonical tick rounding
… <> sqrt price (#5541) Closes: #5516 > **Note to reviewer:** the only real state machine change is in `tick.go` and heavily mirrors @ValarDragon's PR here: #5522 > The rest of the changes are related to function renames/test refactors. ## What is the purpose of the change This PR expands on #5522 and updates all price to tick conversions to use SqrtPriceToTick instead. ## Testing and Verifying The new function is tested in `math/tick_test.go` The original attack vector is also converted into an edge case test in `position_test.go` ## 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
* Some optimizations * Update x/concentrated-liquidity/math/math.go Co-authored-by: Dev Ojha <[email protected]> * [CL]: Fix incorrect bound check/chain halt vector (#5557) * repro panic trigger and fix bound check * fix comments * docs: precision issues around price; short and long term solution (#5552) Closes: #XXX ## What is the purpose of the change Related to: #5550 Documenting latest decisions around tick and price conversions ## Testing and Verifying This change is a trivial rework / code cleanup without any test coverage. ## 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 * Make go tests only run if relevant (#5569) * CL: migration functional test (#5560) Closes: #XXX ## What is the purpose of the change The following PR introduces a functional test that: - Creates every type of balancer position that can be created - Bonded superfluid - Unbonded superfluid (locked) - Unbonded superfluid (unlocking) - Vanilla lock (locked) - Vanilla lock (unlocking) - No lock - It then migrates each position, asserting invariants after each position is migrated - Finally, an overall invariant is run after all positions have been migrated, asserting that all funds are accounted for in some way The next PR subsequent to this will be adding randomization to the inputs in order to make the test non deterministic. ## Testing and Verifying Functional test above added ## 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 * [CL]: Fix tick rounding bug and implement direct conversion from tick <> sqrt price (#5541) Closes: #5516 > **Note to reviewer:** the only real state machine change is in `tick.go` and heavily mirrors @ValarDragon's PR here: #5522 > The rest of the changes are related to function renames/test refactors. ## What is the purpose of the change This PR expands on #5522 and updates all price to tick conversions to use SqrtPriceToTick instead. ## Testing and Verifying The new function is tested in `math/tick_test.go` The original attack vector is also converted into an edge case test in `position_test.go` ## 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 * Still fixing merge conlfict * Revert lp.go change as commented --------- Co-authored-by: Dev Ojha <[email protected]> Co-authored-by: alpo <[email protected]> Co-authored-by: Roman <[email protected]> Co-authored-by: Adam Tucker <[email protected]>
Closes: #5516
What is the purpose of the change
This PR expands on #5522 and updates all price to tick conversions to use SqrtPriceToTick instead.
Testing and Verifying
The new function is tested in
math/tick_test.go
The original attack vector is also converted into an edge case test in
position_test.go
Documentation and Release Note
Unreleased
section ofCHANGELOG.md
?Where is the change documented?
x/{module}/README.md
)