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

feat: return bucket index of the current tick from LiquidityPerTickRange query (backport #6805) #6806

Merged
merged 1 commit into from
Nov 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

* [#6788](https://github.com/osmosis-labs/osmosis/pull/6788) Improve error message when CL LP fails due to slippage bound hit.

### API Breaks

* [#6805](https://github.com/osmosis-labs/osmosis/pull/6805) return bucket index of the current tick from LiquidityPerTickRange query

## v20.0.0

### Features
Expand Down
2 changes: 2 additions & 0 deletions proto/osmosis/concentrated-liquidity/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,8 @@ message LiquidityPerTickRangeRequest {
message LiquidityPerTickRangeResponse {
repeated LiquidityDepthWithRange liquidity = 1
[ (gogoproto.nullable) = false ];

int64 bucket_index = 2 [ (gogoproto.moretags) = "yaml:\"bucket_index\"" ];
}

// ===================== QueryClaimableSpreadRewards
Expand Down
2 changes: 1 addition & 1 deletion x/concentrated-liquidity/bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ func BenchmarkGetTickLiquidityForFullRange(b *testing.B) {
b.StartTimer()

// System under test
liquidityNet, err := clKeeper.GetTickLiquidityForFullRange(s.Ctx, pool.GetId())
liquidityNet, _, err := clKeeper.GetTickLiquidityForFullRange(s.Ctx, pool.GetId())
b.StopTimer()
noError(b, err)

Expand Down
12 changes: 12 additions & 0 deletions x/concentrated-liquidity/client/cli/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ func GetQueryCmd() *cobra.Command {
osmocli.AddQueryCmd(cmd, queryproto.NewQueryClient, GetTickLiquidityNetInDirection)
osmocli.AddQueryCmd(cmd, queryproto.NewQueryClient, GetPoolAccumulatorRewards)
osmocli.AddQueryCmd(cmd, queryproto.NewQueryClient, GetTickAccumulatorTrackers)
osmocli.AddQueryCmd(cmd, queryproto.NewQueryClient, GetLiquidityPerTickRange)
cmd.AddCommand(
osmocli.GetParams[*queryproto.ParamsRequest](
types.ModuleName, queryproto.NewQueryClient),
Expand Down Expand Up @@ -117,6 +118,17 @@ TODO: What does any of that mean...?`,
}, &queryproto.LiquidityNetInDirectionRequest{}
}

func GetLiquidityPerTickRange() (*osmocli.QueryDescriptor, *queryproto.LiquidityPerTickRangeRequest) {
return &osmocli.QueryDescriptor{
Use: "liquidity-per-tick-range",
Short: "Query liquidity per tick range",
Long: `{{.Short}}{{.ExampleHeader}}
{{.CommandPrefix}} 1

[poolid]`,
}, &queryproto.LiquidityPerTickRangeRequest{}
}

func GetPoolAccumulatorRewards() (*osmocli.QueryDescriptor, *queryproto.PoolAccumulatorRewardsRequest) {
return &osmocli.QueryDescriptor{
Use: "pool-accumulator-rewards",
Expand Down
4 changes: 2 additions & 2 deletions x/concentrated-liquidity/client/query_proto_wrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,15 +113,15 @@ func (q Querier) Params(ctx sdk.Context, req clquery.ParamsRequest) (*clquery.Pa
// LiquidityPerTickRange returns the amount of liquidity per every tick range
// existing within the given pool. The amounts are returned as a slice of ranges with their liquidity depths.
func (q Querier) LiquidityPerTickRange(ctx sdk.Context, req clquery.LiquidityPerTickRangeRequest) (*clquery.LiquidityPerTickRangeResponse, error) {
liquidity, err := q.Keeper.GetTickLiquidityForFullRange(
liquidity, bucketIndex, err := q.Keeper.GetTickLiquidityForFullRange(
ctx,
req.PoolId,
)
if err != nil {
return nil, err
}

return &clquery.LiquidityPerTickRangeResponse{Liquidity: liquidity}, nil
return &clquery.LiquidityPerTickRangeResponse{Liquidity: liquidity, BucketIndex: bucketIndex}, nil
}

// LiquidityNetInDirection returns an array of LiquidityDepthWithRange, which contains the range(lower tick and upper tick), the liquidity amount in the range, and current sqrt price.
Expand Down
327 changes: 182 additions & 145 deletions x/concentrated-liquidity/client/queryproto/query.pb.go

Large diffs are not rendered by default.

54 changes: 41 additions & 13 deletions x/concentrated-liquidity/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,54 +17,77 @@ import (
types "github.com/osmosis-labs/osmosis/v20/x/concentrated-liquidity/types"
)

const invalidTickIndex = int64(-1)

// This file contains query-related helper functions for the Concentrated Liquidity module

// GetTickLiquidityForFullRange returns an array of liquidity depth for all ticks existing from min tick ~ max tick.
func (k Keeper) GetTickLiquidityForFullRange(ctx sdk.Context, poolId uint64) ([]queryproto.LiquidityDepthWithRange, error) {
// GetTickLiquidityForFullRange returns a slice of liquidity buckets for all tick ranges existing from min tick ~ max tick.
// Returns index of the bucket that corresponds to the current tick.
// For cases where there is no liqudity in the bucket but there may be liquidity to the right, the value will be -1.
// For cases where there is no liquidity in the bucket but there may be liquidity to the left, the value will be len(liquidityDepthsForRange).
// Otherwise, the index points to the bucket that corresponds to the current tick.
func (k Keeper) GetTickLiquidityForFullRange(ctx sdk.Context, poolId uint64) ([]queryproto.LiquidityDepthWithRange, int64, error) {
// use false for zeroForOne since we're going from lower tick -> upper tick
zeroForOne := false
swapStrategy := swapstrategy.New(zeroForOne, osmomath.ZeroBigDec(), k.storeKey, osmomath.ZeroDec())

// set current tick to min tick, and find the first initialized tick starting from min tick -1.
// set leftmost tick to min tick, and find the first initialized tick starting from min tick -1.
// we do -1 to make min tick inclusive.
currentTick := types.MinCurrentTick
// Note that MinCurrentTick = MinInitializedTick - 1
leftMostTickIndex := types.MinCurrentTick

nextTickIter := swapStrategy.InitializeNextTickIterator(ctx, poolId, currentTick)
nextTickIter := swapStrategy.InitializeNextTickIterator(ctx, poolId, leftMostTickIndex)
defer nextTickIter.Close()
if !nextTickIter.Valid() {
return []queryproto.LiquidityDepthWithRange{}, types.RanOutOfTicksForPoolError{PoolId: poolId}
return []queryproto.LiquidityDepthWithRange{}, invalidTickIndex, types.RanOutOfTicksForPoolError{PoolId: poolId}
}

nextTick, err := types.TickIndexFromBytes(nextTickIter.Key())
if err != nil {
return []queryproto.LiquidityDepthWithRange{}, err
return []queryproto.LiquidityDepthWithRange{}, invalidTickIndex, err
}

tick, err := k.getTickByTickIndex(ctx, poolId, nextTick)
if err != nil {
return []queryproto.LiquidityDepthWithRange{}, err
return []queryproto.LiquidityDepthWithRange{}, invalidTickIndex, err
}

liquidityDepthsForRange := []queryproto.LiquidityDepthWithRange{}

// use the smallest tick initialized as the starting point for calculating liquidity.
currentLiquidity := tick.LiquidityNet
currentTick = nextTick
leftMostTickIndex = nextTick
totalLiquidityWithinRange := currentLiquidity

previousTickIndex := currentTick
previousTickIndex := leftMostTickIndex

concentratedPool, err := k.getPoolById(ctx, poolId)
if err != nil {
return []queryproto.LiquidityDepthWithRange{}, invalidTickIndex, err
}

var (
currentBucketIndex = invalidTickIndex
currentTick = concentratedPool.GetCurrentTick()
currentTickLiquidity = concentratedPool.GetLiquidity()
)

// start from the next index so that the current tick can become lower tick.
nextTickIter.Next()
for ; nextTickIter.Valid(); nextTickIter.Next() {
tickIndex, err := types.TickIndexFromBytes(nextTickIter.Key())
if err != nil {
return []queryproto.LiquidityDepthWithRange{}, err
return []queryproto.LiquidityDepthWithRange{}, invalidTickIndex, err
}

tickStruct, err := ParseTickFromBz(nextTickIter.Value())
if err != nil {
return []queryproto.LiquidityDepthWithRange{}, err
return []queryproto.LiquidityDepthWithRange{}, invalidTickIndex, err
}

// Found the current bucket, update its index.
if currentBucketIndex == invalidTickIndex && concentratedPool.IsCurrentTickInRange(previousTickIndex, tickIndex) && currentTickLiquidity.Equal(totalLiquidityWithinRange) {
currentBucketIndex = int64(len(liquidityDepthsForRange))
}

liquidityDepthForRange := queryproto.LiquidityDepthWithRange{
Expand All @@ -80,7 +103,12 @@ func (k Keeper) GetTickLiquidityForFullRange(ctx sdk.Context, poolId uint64) ([]
totalLiquidityWithinRange = totalLiquidityWithinRange.Add(currentLiquidity)
}

return liquidityDepthsForRange, nil
// This signifies that currrent tick is above the max initialized tick
if currentTick >= previousTickIndex && currentTickLiquidity.IsZero() {
currentBucketIndex = int64(len(liquidityDepthsForRange))
}

return liquidityDepthsForRange, currentBucketIndex, nil
}

// GetLiquidityNetInDirection is a method that returns an array of TickLiquidityNet objects representing the net liquidity in a specified direction
Expand Down
Loading