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: uninitialize empty ticks #5883

Merged
merged 6 commits into from
Jul 30, 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### State Breaking

* [#5532](https://github.com/osmosis-labs/osmosis/pull/5532) fix: Fix x/tokenfactory genesis import denoms reset x/bank existing denom metadata
* [#5883](https://github.com/osmosis-labs/osmosis/pull/5883) feat: Uninitialize empty ticks
* [#5874](https://github.com/osmosis-labs/osmosis/pull/5874) Remove Partial Migration from superfluid migration to CL

### BugFix
Expand Down
4 changes: 4 additions & 0 deletions x/concentrated-liquidity/client/query_proto_wrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,10 @@ func (q Querier) UserUnbondingPositions(ctx sdk.Context, req clquery.UserUnbondi
}

cfmmPoolId, err := q.Keeper.GetUserUnbondingPositions(ctx, sdkAddr)
if err != nil {
return nil, err
}

Comment on lines +275 to +278
Copy link
Member Author

Choose a reason for hiding this comment

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

drive by change

return &clquery.UserUnbondingPositionsResponse{
PositionsWithPeriodLock: cfmmPoolId,
}, nil
Expand Down
2 changes: 1 addition & 1 deletion x/concentrated-liquidity/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ func (k Keeper) ComputeInAmtGivenOut(
return k.computeInAmtGivenOut(ctx, desiredTokenOut, tokenInDenom, spreadFactor, priceLimit, poolId)
}

func (k Keeper) InitOrUpdateTick(ctx sdk.Context, poolId uint64, currentTick int64, tickIndex int64, liquidityIn sdk.Dec, upper bool) (err error) {
func (k Keeper) InitOrUpdateTick(ctx sdk.Context, poolId uint64, currentTick int64, tickIndex int64, liquidityIn sdk.Dec, upper bool) (tickIsEmpty bool, err error) {
Copy link
Member Author

Choose a reason for hiding this comment

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

This is added as a return value so that we don't have to refetch the tick at the end of the withdraw method. It must be done at the end of the withdraw method, since if we delete the tick early, we will lose the fee/incentive info that is saved there that gets claimed after initOrUpdateTick is called in the WithdrawPosition method.

Copy link
Contributor

Choose a reason for hiding this comment

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

"if we delete the tick early, we will lose the fee/incentive info that is saved there that gets claimed after initOrUpdateTick is called in the WithdrawPosition method."

just curious:
when we withdrawPosition(entire position amount), we auto claim the left over fees/incentives and then delete tick right? if yes, then how could there be chance which we could lose fee/incentive that gets claimed after initOrUpdateTick is called

Copy link
Member Author

Choose a reason for hiding this comment

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

What the comment meant was, it would have been cleaner to delete the tick as soon as we know its empty, but some tick data is still needed when withdrawing to claim those rewards so we need to delete as the last step (as it is in this PR)

return k.initOrUpdateTick(ctx, poolId, currentTick, tickIndex, liquidityIn, upper)
}

Expand Down
2 changes: 1 addition & 1 deletion x/concentrated-liquidity/keeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ func (s *KeeperTestSuite) validateTickUpdates(poolId uint64, lowerTick int64, up
}

func (s *KeeperTestSuite) initializeTick(ctx sdk.Context, currentTick int64, tickIndex int64, initialLiquidity sdk.Dec, spreadRewardGrowthOppositeDirectionOfTraversal sdk.DecCoins, uptimeTrackers []model.UptimeTracker, isLower bool) {
err := s.App.ConcentratedLiquidityKeeper.InitOrUpdateTick(ctx, validPoolId, currentTick, tickIndex, initialLiquidity, isLower)
_, err := s.App.ConcentratedLiquidityKeeper.InitOrUpdateTick(ctx, validPoolId, currentTick, tickIndex, initialLiquidity, isLower)
s.Require().NoError(err)

tickInfo, err := s.App.ConcentratedLiquidityKeeper.GetTickInfo(ctx, validPoolId, tickIndex)
Expand Down
45 changes: 27 additions & 18 deletions x/concentrated-liquidity/lp.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ func (k Keeper) CreatePosition(ctx sdk.Context, poolId uint64, owner sdk.AccAddr
}

// Initialize / update the position in the pool based on the provided tick range and liquidity delta.
actualAmount0, actualAmount1, err = k.UpdatePosition(ctx, poolId, owner, lowerTick, upperTick, liquidityDelta, joinTime, positionId)
actualAmount0, actualAmount1, _, _, err = k.UpdatePosition(ctx, poolId, owner, lowerTick, upperTick, liquidityDelta, joinTime, positionId)
if err != nil {
return 0, sdk.Int{}, sdk.Int{}, sdk.Dec{}, 0, 0, err
}
Expand Down Expand Up @@ -160,9 +160,9 @@ func (k Keeper) CreatePosition(ctx sdk.Context, poolId uint64, owner sdk.AccAddr
// When the last position within a pool is removed, this function calls an AfterLastPoolPosistionRemoved listener
// Currently, it creates twap records. Assumming that pool had all liqudity drained and then re-initialized,
// the whole twap state is completely reset. This is because when there is no liquidity in pool, spot price
// is undefined.
// Additionally, when the last position is removed by calling this method, the current sqrt price and current
// tick of the pool are set to zero.
// is undefined. When the last position is removed by calling this method, the current sqrt price and current
// tick of the pool are set to zero. Lastly, if the tick being withdrawn from is now empty due to the withdrawal,
// it is deleted from state.
// Returns error if
// - the provided owner does not own the position being withdrawn
// - there is no position in the given tick ranges
Expand Down Expand Up @@ -219,7 +219,7 @@ func (k Keeper) WithdrawPosition(ctx sdk.Context, owner sdk.AccAddress, position
liquidityDelta := requestedLiquidityAmountToWithdraw.Neg()

// Update the position in the pool based on the provided tick range and liquidity delta.
actualAmount0, actualAmount1, err := k.UpdatePosition(ctx, position.PoolId, owner, position.LowerTick, position.UpperTick, liquidityDelta, position.JoinTime, positionId)
actualAmount0, actualAmount1, lowerTickIsEmpty, upperTickIsEmpty, err := k.UpdatePosition(ctx, position.PoolId, owner, position.LowerTick, position.UpperTick, liquidityDelta, position.JoinTime, positionId)
if err != nil {
return sdk.Int{}, sdk.Int{}, err
}
Expand Down Expand Up @@ -266,6 +266,14 @@ func (k Keeper) WithdrawPosition(ctx sdk.Context, owner sdk.AccAddress, position
}
}

// If lowertick/uppertick has no liquidity in it, delete it from state.
if lowerTickIsEmpty {
k.RemoveTickInfo(ctx, position.PoolId, position.LowerTick)
}
if upperTickIsEmpty {
k.RemoveTickInfo(ctx, position.PoolId, position.UpperTick)
}

tokensRemoved := sdk.Coins{}
if actualAmount0.IsPositive() {
tokensRemoved = tokensRemoved.Add(sdk.NewCoin(pool.GetToken0(), actualAmount0))
Expand Down Expand Up @@ -394,63 +402,64 @@ func (k Keeper) addToPosition(ctx sdk.Context, owner sdk.AccAddress, positionId
// Updates ticks and pool liquidity. Returns how much of each token is either added or removed.
// Negative returned amounts imply that tokens are removed from the pool.
// Positive returned amounts imply that tokens are added to the pool.
// If the lower and/or upper ticks are being updated to have zero liquidity, a boolean is returned to flag the tick as empty to be deleted at the end of the withdrawPosition method.
// WARNING: this method may mutate the pool, make sure to refetch the pool after calling this method.
func (k Keeper) UpdatePosition(ctx sdk.Context, poolId uint64, owner sdk.AccAddress, lowerTick, upperTick int64, liquidityDelta sdk.Dec, joinTime time.Time, positionId uint64) (sdk.Int, sdk.Int, error) {
func (k Keeper) UpdatePosition(ctx sdk.Context, poolId uint64, owner sdk.AccAddress, lowerTick, upperTick int64, liquidityDelta sdk.Dec, joinTime time.Time, positionId uint64) (sdk.Int, sdk.Int, bool, bool, error) {
Copy link
Member

Choose a reason for hiding this comment

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

Why don't we just have this method deal with deleting the tick? Why pass it up?

Copy link
Member Author

Choose a reason for hiding this comment

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

@ValarDragon It needs to be passed up for the following reason:

We collect spread rewards when withdrawing an entire position

if requestedLiquidityAmountToWithdraw.Equal(position.Liquidity) {
if _, err := k.collectSpreadRewards(ctx, owner, positionId); err != nil {
return sdk.Int{}, sdk.Int{}, err
}

This calls prepareClaimableSpreadRewards

spreadRewardsClaimed, err := k.prepareClaimableSpreadRewards(ctx, positionId)
if err != nil {
return sdk.Coins{}, err
}

This calculates the spreadRewardGrowthOutside via the ticks

// Compute the spread reward growth outside of the range between the position's lower and upper ticks.
spreadRewardGrowthOutside, err := k.getSpreadRewardGrowthOutside(ctx, position.PoolId, position.LowerTick, position.UpperTick)
if err != nil {
return nil, err
}

If we delete tick info earlier, this will output an incorrect amount

Copy link
Member

Choose a reason for hiding this comment

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

I see

if err := k.validatePositionUpdateById(ctx, positionId, owner, lowerTick, upperTick, liquidityDelta, joinTime, poolId); err != nil {
return sdk.Int{}, sdk.Int{}, err
return sdk.Int{}, sdk.Int{}, false, false, err
}

pool, err := k.getPoolById(ctx, poolId)
if err != nil {
return sdk.Int{}, sdk.Int{}, err
return sdk.Int{}, sdk.Int{}, false, false, err
}

currentTick := pool.GetCurrentTick()

// update lower tickInfo state
err = k.initOrUpdateTick(ctx, poolId, currentTick, lowerTick, liquidityDelta, false)
lowerTickIsEmpty, err := k.initOrUpdateTick(ctx, poolId, currentTick, lowerTick, liquidityDelta, false)
if err != nil {
return sdk.Int{}, sdk.Int{}, err
return sdk.Int{}, sdk.Int{}, false, false, err
}

// update upper tickInfo state
err = k.initOrUpdateTick(ctx, poolId, currentTick, upperTick, liquidityDelta, true)
upperTickIsEmpty, err := k.initOrUpdateTick(ctx, poolId, currentTick, upperTick, liquidityDelta, true)
if err != nil {
return sdk.Int{}, sdk.Int{}, err
return sdk.Int{}, sdk.Int{}, false, false, err
}

// update position state
err = k.initOrUpdatePosition(ctx, poolId, owner, lowerTick, upperTick, liquidityDelta, joinTime, positionId)
if err != nil {
return sdk.Int{}, sdk.Int{}, err
return sdk.Int{}, sdk.Int{}, false, false, err
}

// Refetch pool to get the updated pool.
// Note that updateUptimeAccumulatorsToNow may modify the pool state and rewrite it to the store.
pool, err = k.getPoolById(ctx, poolId)
if err != nil {
return sdk.Int{}, sdk.Int{}, err
return sdk.Int{}, sdk.Int{}, false, false, err
}

// calculate the actual amounts of tokens 0 and 1 that were added or removed from the pool.
actualAmount0, actualAmount1, err := pool.CalcActualAmounts(ctx, lowerTick, upperTick, liquidityDelta)
if err != nil {
return sdk.Int{}, sdk.Int{}, err
return sdk.Int{}, sdk.Int{}, false, false, err
}

// the pool's liquidity value is only updated if this position is active
pool.UpdateLiquidityIfActivePosition(ctx, lowerTick, upperTick, liquidityDelta)

if err := k.setPool(ctx, pool); err != nil {
return sdk.Int{}, sdk.Int{}, err
return sdk.Int{}, sdk.Int{}, false, false, err
}

if err := k.initOrUpdatePositionSpreadRewardAccumulator(ctx, poolId, lowerTick, upperTick, positionId, liquidityDelta); err != nil {
return sdk.Int{}, sdk.Int{}, err
return sdk.Int{}, sdk.Int{}, false, false, err
}

// The returned amounts are rounded down to avoid returning more to clients than they actually deposited.
return actualAmount0.TruncateInt(), actualAmount1.TruncateInt(), nil
return actualAmount0.TruncateInt(), actualAmount1.TruncateInt(), lowerTickIsEmpty, upperTickIsEmpty, nil
}

// sendCoinsBetweenPoolAndUser takes the amounts calculated from a join/exit position and executes the send between pool and user
Expand Down
35 changes: 29 additions & 6 deletions x/concentrated-liquidity/lp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,7 @@ func (s *KeeperTestSuite) TestWithdrawPosition() {
timeElapsed time.Duration
createLockState lockState
withdrawWithNonOwner bool
isFullLiquidityWithdraw bool
}{
"base case: withdraw full liquidity amount": {
setupConfig: baseCase,
Expand All @@ -422,7 +423,8 @@ func (s *KeeperTestSuite) TestWithdrawPosition() {
// Note: subtracting one due to truncations in favor of the pool when withdrawing.
amount1Expected: baseCase.amount1Expected.Sub(sdk.OneInt()), // 5000 usdc
},
timeElapsed: defaultTimeElapsed,
timeElapsed: defaultTimeElapsed,
isFullLiquidityWithdraw: true,
},
"withdraw full liquidity amount with underlying lock that has finished unlocking": {
setupConfig: baseCase,
Expand All @@ -445,8 +447,9 @@ func (s *KeeperTestSuite) TestWithdrawPosition() {
liquidityAmount: FullRangeLiquidityAmt,
underlyingLockId: 1,
},
createLockState: unlocked,
timeElapsed: defaultTimeElapsed,
createLockState: unlocked,
timeElapsed: defaultTimeElapsed,
isFullLiquidityWithdraw: true,
},
"error: withdraw full liquidity amount but still locked": {
setupConfig: baseCase,
Expand Down Expand Up @@ -484,7 +487,8 @@ func (s *KeeperTestSuite) TestWithdrawPosition() {
// Note: subtracting one due to truncations in favor of the pool when withdrawing.
amount1Expected: baseCase.amount1Expected.Sub(sdk.OneInt()), // 5000 usdc
},
timeElapsed: 0,
timeElapsed: 0,
isFullLiquidityWithdraw: true,
},
"error: no position created": {
setupConfig: baseCase,
Expand Down Expand Up @@ -698,6 +702,17 @@ func (s *KeeperTestSuite) TestWithdrawPosition() {
s.validatePositionUpdate(s.Ctx, config.positionId, expectedRemainingLiquidity)
}

// Check that ticks were removed if liquidity is fully withdrawn.
lowerTickValue := store.Get(types.KeyTick(defaultPoolId, config.lowerTick))
upperTickValue := store.Get(types.KeyTick(defaultPoolId, config.upperTick))
if tc.isFullLiquidityWithdraw {
s.Require().Nil(lowerTickValue)
s.Require().Nil(upperTickValue)
} else {
s.Require().NotNil(lowerTickValue)
s.Require().NotNil(upperTickValue)
}

// Check tick state.
s.validateTickUpdates(config.poolId, config.lowerTick, config.upperTick, expectedRemainingLiquidity, config.expectedSpreadRewardGrowthOutsideLower, config.expectedSpreadRewardGrowthOutsideUpper)

Expand Down Expand Up @@ -1601,12 +1616,12 @@ func (s *KeeperTestSuite) TestUpdatePosition() {
)
s.Require().NoError(err)

// explicitly make update time different to ensure that the pool is updated with last liqudity update.
// explicitly make update time different to ensure that the pool is updated with last liquidity update.
expectedUpdateTime := tc.joinTime.Add(time.Second)
s.Ctx = s.Ctx.WithBlockTime(expectedUpdateTime)

// system under test
actualAmount0, actualAmount1, err := s.App.ConcentratedLiquidityKeeper.UpdatePosition(
actualAmount0, actualAmount1, lowerTickIsEmpty, upperTickIsEmpty, err := s.App.ConcentratedLiquidityKeeper.UpdatePosition(
s.Ctx,
tc.poolId,
s.TestAccs[tc.ownerIndex],
Expand All @@ -1624,6 +1639,14 @@ func (s *KeeperTestSuite) TestUpdatePosition() {
} else {
s.Require().NoError(err)

if tc.liquidityDelta.Equal(DefaultLiquidityAmt.Neg()) {
s.Require().True(lowerTickIsEmpty)
s.Require().True(upperTickIsEmpty)
} else {
s.Require().False(lowerTickIsEmpty)
s.Require().False(upperTickIsEmpty)
}

var (
expectedAmount0 sdk.Dec
expectedAmount1 sdk.Dec
Expand Down
2 changes: 1 addition & 1 deletion x/concentrated-liquidity/position.go
Original file line number Diff line number Diff line change
Expand Up @@ -572,7 +572,7 @@ func (k Keeper) fungifyChargedPosition(ctx sdk.Context, owner sdk.AccAddress, po

// Create the new position in the pool based on the provided tick range and liquidity delta.
// This also initializes the spread reward accumulator and the uptime accumulators for the new position.
_, _, err = k.UpdatePosition(ctx, poolId, owner, lowerTick, upperTick, combinedLiquidityOfAllPositions, joinTime, newPositionId)
_, _, _, _, err = k.UpdatePosition(ctx, poolId, owner, lowerTick, upperTick, combinedLiquidityOfAllPositions, joinTime, newPositionId)
if err != nil {
return 0, err
}
Expand Down
4 changes: 2 additions & 2 deletions x/concentrated-liquidity/spread_rewards_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1245,10 +1245,10 @@ func (s *KeeperTestSuite) TestInitOrUpdateSpreadRewardAccumulatorPosition_Updati
s.crossTickAndChargeSpreadReward(poolId, DefaultLowerTick)
}

err := s.App.ConcentratedLiquidityKeeper.InitOrUpdateTick(s.Ctx, poolId, pool.GetCurrentTick(), DefaultLowerTick, DefaultLiquidityAmt, false)
_, err := s.App.ConcentratedLiquidityKeeper.InitOrUpdateTick(s.Ctx, poolId, pool.GetCurrentTick(), DefaultLowerTick, DefaultLiquidityAmt, false)
s.Require().NoError(err)

err = s.App.ConcentratedLiquidityKeeper.InitOrUpdateTick(s.Ctx, poolId, pool.GetCurrentTick(), DefaultUpperTick, DefaultLiquidityAmt, true)
_, err = s.App.ConcentratedLiquidityKeeper.InitOrUpdateTick(s.Ctx, poolId, pool.GetCurrentTick(), DefaultUpperTick, DefaultLiquidityAmt, true)
s.Require().NoError(err)

// InitOrUpdateSpreadRewardAccumulatorPosition #1 lower tick to upper tick
Expand Down
22 changes: 17 additions & 5 deletions x/concentrated-liquidity/tick.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,15 @@ import (
// initOrUpdateTick retrieves the tickInfo from the specified tickIndex and updates both the liquidityNet and LiquidityGross.
// The given currentTick value is used to determine the strategy for updating the spread factor accumulator.
// We update the tick's spread reward growth opposite direction of last traversal accumulator to the spread reward growth global when tick index is <= current tick.
// Otherwise, it is set to zero.
// Otherwise, it is set to zero. If the liquidityDelta causes the tick to be empty, a boolean flags that the tick is empty for the withdrawPosition method to handle later (removes the tick from state).
// Note that liquidityDelta can be either positive or negative depending on whether we are adding or removing liquidity.
// if we are initializing or updating an upper tick, we subtract the liquidityIn from the LiquidityNet
// if we are initializing or updating a lower tick, we add the liquidityIn from the LiquidityNet
// WARNING: this method may mutate the pool, make sure to refetch the pool after calling this method.
func (k Keeper) initOrUpdateTick(ctx sdk.Context, poolId uint64, currentTick int64, tickIndex int64, liquidityDelta sdk.Dec, upper bool) (err error) {
func (k Keeper) initOrUpdateTick(ctx sdk.Context, poolId uint64, currentTick int64, tickIndex int64, liquidityDelta sdk.Dec, upper bool) (tickIsEmpty bool, err error) {
tickInfo, err := k.GetTickInfo(ctx, poolId, tickIndex)
if err != nil {
return err
return false, err
}

// If both liquidity fields are zero, we consume the base gas spread factor for initializing a tick.
Expand All @@ -48,7 +48,7 @@ func (k Keeper) initOrUpdateTick(ctx sdk.Context, poolId uint64, currentTick int
if tickIndex <= currentTick {
accum, err := k.GetSpreadRewardAccumulator(ctx, poolId)
if err != nil {
return err
return false, err
}

tickInfo.SpreadRewardGrowthOppositeDirectionOfLastTraversal = accum.GetValue()
Expand All @@ -68,8 +68,13 @@ func (k Keeper) initOrUpdateTick(ctx sdk.Context, poolId uint64, currentTick int
tickInfo.LiquidityNet.AddMut(liquidityDelta)
}

// If liquidity is now zero, this tick is flagged to be un-initialized at the end of the withdrawPosition method.
if tickInfo.LiquidityGross.IsZero() && tickInfo.LiquidityNet.IsZero() {
tickIsEmpty = true
}

k.SetTickInfo(ctx, poolId, tickIndex, &tickInfo)
return nil
return tickIsEmpty, nil
}

// crossTick crosses the given tick. The tick is specified by its index and tick info.
Expand Down Expand Up @@ -160,6 +165,13 @@ func (k Keeper) SetTickInfo(ctx sdk.Context, poolId uint64, tickIndex int64, tic
osmoutils.MustSet(store, key, tickInfo)
}

// RemoveTickInfo removes the tickInfo from state.
func (k Keeper) RemoveTickInfo(ctx sdk.Context, poolId uint64, tickIndex int64) {
store := ctx.KVStore(k.storeKey)
key := types.KeyTick(poolId, tickIndex)
store.Delete(key)
}

func (k Keeper) GetAllInitializedTicksForPool(ctx sdk.Context, poolId uint64) ([]genesis.FullTick, error) {
return osmoutils.GatherValuesFromStorePrefixWithKeyParser(ctx.KVStore(k.storeKey), types.KeyTickPrefixByPoolId(poolId), ParseFullTickFromBytes)
}
Expand Down
15 changes: 11 additions & 4 deletions x/concentrated-liquidity/tick_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,8 @@ func (s *KeeperTestSuite) TestInitOrUpdateTick() {
if test.tickExists {
tickInfoBefore, err := s.App.ConcentratedLiquidityKeeper.GetTickInfo(s.Ctx, 1, test.param.tickIndex)
s.Require().NoError(err)
err = s.App.ConcentratedLiquidityKeeper.InitOrUpdateTick(s.Ctx, test.param.poolId, currentTick, test.param.tickIndex, DefaultLiquidityAmt, test.param.upper)
tickIsEmpty, err := s.App.ConcentratedLiquidityKeeper.InitOrUpdateTick(s.Ctx, test.param.poolId, currentTick, test.param.tickIndex, DefaultLiquidityAmt, test.param.upper)
s.Require().False(tickIsEmpty)
s.Require().NoError(err)
if tickInfoBefore.LiquidityGross.IsZero() && test.param.tickIndex <= pool.GetCurrentTick() {
tickInfoAfter, err := s.App.ConcentratedLiquidityKeeper.GetTickInfo(s.Ctx, 1, test.param.tickIndex)
Expand Down Expand Up @@ -328,7 +329,7 @@ func (s *KeeperTestSuite) TestInitOrUpdateTick() {

// System under test.
// Initialize or update the tick according to the test case
err = s.App.ConcentratedLiquidityKeeper.InitOrUpdateTick(s.Ctx, test.param.poolId, currentTick, test.param.tickIndex, test.param.liquidityIn, test.param.upper)
tickIsEmpty, err := s.App.ConcentratedLiquidityKeeper.InitOrUpdateTick(s.Ctx, test.param.poolId, currentTick, test.param.tickIndex, test.param.liquidityIn, test.param.upper)
if tickInfoAfter.LiquidityGross.IsZero() && test.param.tickIndex <= pool.GetCurrentTick() {
tickInfoAfter, err := s.App.ConcentratedLiquidityKeeper.GetTickInfo(s.Ctx, 1, test.param.tickIndex)
s.Require().NoError(err)
Expand All @@ -340,6 +341,12 @@ func (s *KeeperTestSuite) TestInitOrUpdateTick() {
}
s.Require().NoError(err)

if test.expectedLiquidityGross.IsZero() && test.expectedLiquidityNet.IsZero() {
s.Require().True(tickIsEmpty)
} else {
s.Require().False(tickIsEmpty)
}

// Get the tick info for poolId 1 again
tickInfoAfter, err = s.App.ConcentratedLiquidityKeeper.GetTickInfo(s.Ctx, 1, test.param.tickIndex)
s.Require().NoError(err)
Expand Down Expand Up @@ -454,7 +461,7 @@ func (s *KeeperTestSuite) TestGetTickInfo() {
}

// Set up an initialized tick
err := clKeeper.InitOrUpdateTick(s.Ctx, validPoolId, DefaultCurrTick, preInitializedTickIndex, DefaultLiquidityAmt, true)
_, err := clKeeper.InitOrUpdateTick(s.Ctx, validPoolId, DefaultCurrTick, preInitializedTickIndex, DefaultLiquidityAmt, true)
s.Require().NoError(err)

// Charge spread factor to make sure that the global spread factor accumulator is always updated.
Expand Down Expand Up @@ -622,7 +629,7 @@ func (s *KeeperTestSuite) TestCrossTick() {
}

// Set up an initialized tick
err := s.App.ConcentratedLiquidityKeeper.InitOrUpdateTick(s.Ctx, validPoolId, DefaultCurrTick, test.preInitializedTickIndex, DefaultLiquidityAmt, true)
_, err := s.App.ConcentratedLiquidityKeeper.InitOrUpdateTick(s.Ctx, validPoolId, DefaultCurrTick, test.preInitializedTickIndex, DefaultLiquidityAmt, true)
s.Require().NoError(err)

// Update global uptime accums for edge case testing
Expand Down
Loading