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

[CL]: Fix tick rounding bug and implement direct conversion from tick <> sqrt price #5541

Merged
merged 20 commits into from
Jun 19, 2023

Conversation

AlpinYukseloglu
Copy link
Contributor

@AlpinYukseloglu AlpinYukseloglu commented Jun 15, 2023

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

@github-actions github-actions bot added C:docs Improvements or additions to documentation C:x/concentrated-liquidity labels Jun 15, 2023
@AlpinYukseloglu AlpinYukseloglu changed the title [CL]: Implement and test SqrtPriceToTickRoundDownSpacing and replace all uses of price to tick conversion with it [CL]: Implement and test SqrtPriceToTickRoundDownSpacing and use in place of all tick conversions Jun 15, 2023
@AlpinYukseloglu AlpinYukseloglu changed the base branch from main to dev/sqrt_price_to_tick June 15, 2023 18:20
@github-actions github-actions bot removed the C:docs Improvements or additions to documentation label Jun 15, 2023
@AlpinYukseloglu AlpinYukseloglu marked this pull request as ready for review June 16, 2023 01:13
@@ -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() {
Copy link
Contributor Author

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

Copy link
Member

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)
Copy link
Contributor Author

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

@AlpinYukseloglu AlpinYukseloglu changed the base branch from dev/sqrt_price_to_tick to main June 16, 2023 01:17
}

if tickIndexModulus != 0 {
tickIndex = tickIndex - tickIndexModulus
Copy link
Contributor Author

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

@AlpinYukseloglu AlpinYukseloglu changed the title [CL]: Implement and test SqrtPriceToTickRoundDownSpacing and use in place of all tick conversions [CL]: Fix tick rounding bug and implement direct conversion from tick <> sqrt price Jun 16, 2023
@github-actions github-actions bot added the C:docs Improvements or additions to documentation label Jun 16, 2023
Copy link
Member

@czarcas7ic czarcas7ic left a 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

x/concentrated-liquidity/invariant_test.go Outdated Show resolved Hide resolved
x/concentrated-liquidity/math/tick.go Outdated Show resolved Hide resolved
x/concentrated-liquidity/math/tick.go Outdated Show resolved Hide resolved
x/concentrated-liquidity/math/tick.go Outdated Show resolved Hide resolved
x/concentrated-liquidity/math/tick.go Outdated Show resolved Hide resolved
x/concentrated-liquidity/math/tick.go Show resolved Hide resolved
Comment on lines 294 to 296
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)
}
Copy link
Member

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)

Copy link
Member

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

Copy link
Member

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)

Copy link
Contributor Author

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

x/concentrated-liquidity/position_test.go Show resolved Hide resolved
x/concentrated-liquidity/range_test.go Outdated Show resolved Hide resolved
x/concentrated-liquidity/swaps.go Outdated Show resolved Hide resolved
Copy link
Member

@czarcas7ic czarcas7ic left a 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 :)

@p0mvn p0mvn mentioned this pull request Jun 17, 2023
9 tasks
Copy link
Member

@p0mvn p0mvn left a 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

Comment on lines +2524 to +2535
// 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)
Copy link
Member

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

x/concentrated-liquidity/math/tick.go Outdated Show resolved Hide resolved
x/concentrated-liquidity/math/tick.go Outdated Show resolved Hide resolved
x/concentrated-liquidity/math/tick.go Outdated Show resolved Hide resolved
@github-actions github-actions bot added the C:app-wiring Changes to the app folder label Jun 18, 2023
// 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) {
Copy link
Contributor Author

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

@github-actions github-actions bot removed the C:docs Improvements or additions to documentation label Jun 18, 2023
@@ -47,7 +47,7 @@ type RangeTestParams struct {
var (
DefaultRangeTestParams = RangeTestParams{
// Base amounts
baseNumPositions: 1000,
baseNumPositions: 10,
Copy link
Contributor Author

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

Comment on lines +218 to +224
outOfBounds := false
if truncatedTick <= types.MinTick {
truncatedTick = types.MinTick + 1
outOfBounds = true
} else if truncatedTick >= types.MaxTick-1 {
truncatedTick = types.MaxTick - 2
outOfBounds = true
Copy link
Member

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)

Copy link
Contributor Author

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!

Copy link
Member

@ValarDragon ValarDragon left a 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!

Copy link
Member

@p0mvn p0mvn left a comment

Choose a reason for hiding this comment

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

LGTM. Great work

Comment on lines +774 to +801
"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,
},
Copy link
Member

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?

Copy link
Contributor Author

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

@mergify mergify bot merged commit 2853245 into main Jun 19, 2023
@mergify mergify bot deleted the alpo/sqrt_price_to_tick branch June 19, 2023 15:37
mattverse pushed a commit that referenced this pull request Jun 20, 2023
… <> 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
ValarDragon added a commit that referenced this pull request Jun 20, 2023
* 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]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[CL][bug]: Bankers rounding in price to tick conversions allows for LP fund drain
4 participants