Skip to content

Commit

Permalink
add underlying lock ID func + helpers
Browse files Browse the repository at this point in the history
  • Loading branch information
czarcas7ic committed Apr 10, 2023
1 parent 32e03f1 commit 0edfe2b
Show file tree
Hide file tree
Showing 11 changed files with 220 additions and 38 deletions.
20 changes: 12 additions & 8 deletions x/concentrated-liquidity/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,16 +74,16 @@ func (k *Keeper) SwapInAmtGivenOut(ctx sdk.Context, sender sdk.AccAddress, pool
return k.swapInAmtGivenOut(ctx, sender, pool, desiredTokenOut, tokenInDenom, swapFee, priceLimit)
}

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) {
return k.updatePosition(ctx, poolId, owner, lowerTick, upperTick, liquidityDelta, joinTime, positionId)
func (k Keeper) UpdatePosition(ctx sdk.Context, poolId uint64, owner sdk.AccAddress, lowerTick, upperTick int64, liquidityDelta sdk.Dec, joinTime time.Time, positionId, underlyingLockId uint64) (sdk.Int, sdk.Int, error) {
return k.updatePosition(ctx, poolId, owner, lowerTick, upperTick, liquidityDelta, joinTime, positionId, underlyingLockId)
}

func (k Keeper) InitOrUpdateTick(ctx sdk.Context, poolId uint64, currentTick int64, tickIndex int64, liquidityIn sdk.Dec, upper bool) (err error) {
return k.initOrUpdateTick(ctx, poolId, currentTick, tickIndex, liquidityIn, upper)
}

func (k Keeper) InitOrUpdatePosition(ctx sdk.Context, poolId uint64, owner sdk.AccAddress, lowerTick, upperTick int64, liquidityDelta sdk.Dec, joinTime time.Time, positionId uint64) (err error) {
return k.initOrUpdatePosition(ctx, poolId, owner, lowerTick, upperTick, liquidityDelta, joinTime, positionId)
func (k Keeper) InitOrUpdatePosition(ctx sdk.Context, poolId uint64, owner sdk.AccAddress, lowerTick, upperTick int64, liquidityDelta sdk.Dec, joinTime time.Time, positionId, underlyingLockId uint64) (err error) {
return k.initOrUpdatePosition(ctx, poolId, owner, lowerTick, upperTick, liquidityDelta, joinTime, positionId, underlyingLockId)
}

func (k Keeper) PoolExists(ctx sdk.Context, poolId uint64) bool {
Expand All @@ -110,10 +110,6 @@ func ConvertPoolInterfaceToConcentrated(poolI poolmanagertypes.PoolI) (types.Con
return convertPoolInterfaceToConcentrated(poolI)
}

func (k Keeper) SetPosition(ctx sdk.Context, poolId uint64, owner sdk.AccAddress, lowerTick, upperTick int64, joinTime time.Time, liquidity sdk.Dec, positionId uint64) {
k.setPosition(ctx, poolId, owner, lowerTick, upperTick, joinTime, liquidity, positionId)
}

func (k Keeper) ValidateSwapFee(ctx sdk.Context, params types.Params, swapFee sdk.Dec) bool {
return k.validateSwapFee(ctx, params, swapFee)
}
Expand All @@ -126,6 +122,14 @@ func (k Keeper) ValidatePositionsAndGetTotalLiquidity(ctx sdk.Context, owner sdk
return k.validatePositionsAndGetTotalLiquidity(ctx, owner, positionIds)
}

func (k Keeper) IsLockMature(ctx sdk.Context, underlyingLockId uint64) (bool, error) {
return k.isLockMature(ctx, underlyingLockId)
}

func (k Keeper) DoesPositionHaveUnderlyingLockInState(ctx sdk.Context, positionId uint64) bool {
return k.doesPositionHaveUnderlyingLockInState(ctx, positionId)
}

// fees methods
func (k Keeper) CreateFeeAccumulator(ctx sdk.Context, poolId uint64) error {
return k.createFeeAccumulator(ctx, poolId)
Expand Down
6 changes: 3 additions & 3 deletions x/concentrated-liquidity/fees_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -778,7 +778,7 @@ func (s *KeeperTestSuite) TestQueryAndCollectFees() {
ctx := s.Ctx

// Set the position in store, otherwise querying via position id will fail.
clKeeper.SetPosition(ctx, validPoolId, tc.owner, tc.lowerTick, tc.upperTick, time.Now().UTC(), tc.initialLiquidity, DefaultPositionId)
clKeeper.SetPosition(ctx, validPoolId, tc.owner, tc.lowerTick, tc.upperTick, time.Now().UTC(), tc.initialLiquidity, DefaultPositionId, DefaultUnderlyingLockId)

s.initializeFeeAccumulatorPositionWithLiquidity(ctx, validPoolId, tc.lowerTick, tc.upperTick, DefaultPositionId, tc.initialLiquidity)

Expand Down Expand Up @@ -890,14 +890,14 @@ func (s *KeeperTestSuite) TestUpdateFeeAccumulatorPosition() {
poolOne := s.PrepareConcentratedPool()

// Setup test case position
s.App.ConcentratedLiquidityKeeper.SetPosition(s.Ctx, poolOne.GetId(), tc.owner, tc.lowerTick, tc.upperTick, time.Now().UTC(), tc.liquidity, tc.positionIdSetup)
s.App.ConcentratedLiquidityKeeper.SetPosition(s.Ctx, poolOne.GetId(), tc.owner, tc.lowerTick, tc.upperTick, time.Now().UTC(), tc.liquidity, tc.positionIdSetup, DefaultUnderlyingLockId)
err := s.App.ConcentratedLiquidityKeeper.InitializeFeeAccumulatorPosition(s.Ctx, poolOne.GetId(), tc.lowerTick, tc.upperTick, tc.positionIdSetup)
s.Require().NoError(err)

// Setup static position
// Note: setting the position manually here is a hack.
// When we call InitializeFeeAccumulatorPosition, the liquidity gets set to zero.
s.App.ConcentratedLiquidityKeeper.SetPosition(s.Ctx, poolOne.GetId(), tc.owner, tc.lowerTick, tc.upperTick, time.Now().UTC(), tc.liquidity, tc.positionIdSetup+1)
s.App.ConcentratedLiquidityKeeper.SetPosition(s.Ctx, poolOne.GetId(), tc.owner, tc.lowerTick, tc.upperTick, time.Now().UTC(), tc.liquidity, tc.positionIdSetup+1, DefaultUnderlyingLockId)
err = s.App.ConcentratedLiquidityKeeper.InitializeFeeAccumulatorPosition(s.Ctx, poolOne.GetId(), tc.lowerTick, tc.upperTick, tc.positionIdSetup+1)
s.Require().NoError(err)

Expand Down
4 changes: 3 additions & 1 deletion x/concentrated-liquidity/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,9 @@ func (k Keeper) InitGenesis(ctx sdk.Context, genState genesis.GenesisState) {
if _, ok := seenPoolIds[position.PoolId]; !ok {
panic(fmt.Sprintf("found position with pool id (%d) but there is no pool with such id that exists", position.PoolId))
}
k.setPosition(ctx, position.PoolId, sdk.MustAccAddressFromBech32(position.Address), position.LowerTick, position.UpperTick, position.JoinTime, position.Liquidity, position.PositionId)

// We hardcode the underlying lock id to 0, because genesisState should already hold the positionId to lockId connections
k.SetPosition(ctx, position.PoolId, sdk.MustAccAddressFromBech32(position.Address), position.LowerTick, position.UpperTick, position.JoinTime, position.Liquidity, position.PositionId, 0)
}
}

Expand Down
7 changes: 3 additions & 4 deletions x/concentrated-liquidity/incentives_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1807,7 +1807,7 @@ func (s *KeeperTestSuite) TestInitPositionUptime() {
if test.existingPosition {
err := s.App.ConcentratedLiquidityKeeper.InitOrUpdatePositionUptime(s.Ctx, clPool.GetId(), test.positionLiquidity, s.TestAccs[0], test.lowerTick.tickIndex, test.upperTick.tickIndex, test.positionLiquidity, DefaultJoinTime, DefaultPositionId)
s.Require().NoError(err)
s.App.ConcentratedLiquidityKeeper.SetPosition(s.Ctx, clPool.GetId(), s.TestAccs[0], test.lowerTick.tickIndex, test.upperTick.tickIndex, DefaultJoinTime, test.positionLiquidity, DefaultPositionId)
s.App.ConcentratedLiquidityKeeper.SetPosition(s.Ctx, clPool.GetId(), s.TestAccs[0], test.lowerTick.tickIndex, test.upperTick.tickIndex, DefaultJoinTime, test.positionLiquidity, DefaultPositionId, DefaultUnderlyingLockId)

s.initializeTick(s.Ctx, test.currentTickIndex.Int64(), test.newLowerTick.tickIndex, sdk.ZeroDec(), cl.EmptyCoins, test.newLowerTick.uptimeTrackers, true)
s.initializeTick(s.Ctx, test.currentTickIndex.Int64(), test.newUpperTick.tickIndex, sdk.ZeroDec(), cl.EmptyCoins, test.newUpperTick.uptimeTrackers, false)
Expand Down Expand Up @@ -2643,7 +2643,7 @@ func (s *KeeperTestSuite) TestCollectIncentives() {

// Initialize all positions
for i := 0; i < tc.numPositions; i++ {
err := clKeeper.InitOrUpdatePosition(ctx, validPoolId, ownerWithValidPosition, tc.positionParams.lowerTick, tc.positionParams.upperTick, tc.positionParams.liquidity, tc.positionParams.joinTime, uint64(i+1))
err := clKeeper.InitOrUpdatePosition(ctx, validPoolId, ownerWithValidPosition, tc.positionParams.lowerTick, tc.positionParams.upperTick, tc.positionParams.liquidity, tc.positionParams.joinTime, uint64(i+1), DefaultUnderlyingLockId)
s.Require().NoError(err)
}
ctx = ctx.WithBlockTime(ctx.BlockTime().Add(tc.timeInPosition))
Expand Down Expand Up @@ -3111,7 +3111,6 @@ func (s *KeeperTestSuite) TestClaimAllIncentives() {
growthOutside: uptimeHelper.twoHundredTokensMultiDenom,

expectedError: cltypes.NegativeDurationError{Duration: time.Hour * 504 * -1},

},
}
for _, tc := range tests {
Expand All @@ -3129,7 +3128,7 @@ func (s *KeeperTestSuite) TestClaimAllIncentives() {
}

// Initialize position
err := clKeeper.InitOrUpdatePosition(s.Ctx, validPoolId, defaultSender, DefaultLowerTick, DefaultUpperTick, sdk.OneDec(), joinTime, tc.positionIdCreate)
err := clKeeper.InitOrUpdatePosition(s.Ctx, validPoolId, defaultSender, DefaultLowerTick, DefaultUpperTick, sdk.OneDec(), joinTime, tc.positionIdCreate, DefaultUnderlyingLockId)
s.Require().NoError(err)

clPool.SetCurrentTick(DefaultCurrTick)
Expand Down
74 changes: 62 additions & 12 deletions x/concentrated-liquidity/lp.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ import (

sdk "github.com/cosmos/cosmos-sdk/types"

"github.com/osmosis-labs/osmosis/osmomath"
"github.com/osmosis-labs/osmosis/v15/x/concentrated-liquidity/internal/math"
types "github.com/osmosis-labs/osmosis/v15/x/concentrated-liquidity/types"
lockuptypes "github.com/osmosis-labs/osmosis/v15/x/lockup/types"
)

const noUnderlyingLockId = uint64(0)

// createPosition creates a concentrated liquidity position in range between lowerTick and upperTick
// in a given `PoolId with the desired amount of each token. Since LPs are only allowed to provide
// liquidity proportional to the existing reserves, the actual amount of tokens used might differ from requested.
Expand Down Expand Up @@ -81,7 +83,7 @@ func (k Keeper) createPosition(ctx sdk.Context, poolId uint64, owner sdk.AccAddr
}

// Update the position in the pool based on the provided tick range and liquidity delta.
actualAmount0, actualAmount1, err := k.updatePosition(cacheCtx, poolId, owner, lowerTick, upperTick, liquidityDelta, joinTime, positionId)
actualAmount0, actualAmount1, err := k.updatePosition(cacheCtx, poolId, owner, lowerTick, upperTick, liquidityDelta, joinTime, positionId, noUnderlyingLockId)
if err != nil {
return 0, sdk.Int{}, sdk.Int{}, sdk.Dec{}, time.Time{}, err
}
Expand Down Expand Up @@ -126,6 +128,7 @@ func (k Keeper) createPosition(ctx sdk.Context, poolId uint64, owner sdk.AccAddr
// tick are set to zero.
// Returns error if
// - there is no position in the given tick ranges
// - if the position's underlying lock is not mature
// - if tick ranges are invalid
// - if attempts to withdraw an amount higher than originally provided in createPosition for a given range.
func (k Keeper) withdrawPosition(ctx sdk.Context, owner sdk.AccAddress, positionId uint64, requestedLiquidityAmountToWithdraw sdk.Dec) (amtDenom0, amtDenom1 sdk.Int, err error) {
Expand All @@ -134,6 +137,28 @@ func (k Keeper) withdrawPosition(ctx sdk.Context, owner sdk.AccAddress, position
return sdk.Int{}, sdk.Int{}, err
}

// If underlying lock exists in state, validate unlocked conditions are met before withdrawing liquidity.
// If unlocked conditions are met, remove the link between the position and the underlying lock.
if k.doesPositionHaveUnderlyingLockInState(ctx, position.PositionId) {
lockId, err := k.GetPositionIdToLock(ctx, positionId)
if err != nil {
return sdk.Int{}, sdk.Int{}, err
}

// Check if the underlying lock is mature.
lockIsMature, err := k.isLockMature(ctx, lockId)
if err != nil {
return sdk.Int{}, sdk.Int{}, err
}
if lockIsMature {
// Remove the link between the position and the underlying lock since the lock is mature.
k.RemovePositionIdToLock(ctx, positionId)
} else {
// Lock is not mature, return error.
return sdk.Int{}, sdk.Int{}, types.LockNotMatureError{LockId: lockId}
}
}

// Retrieve the pool associated with the given pool ID.
pool, err := k.getPoolById(ctx, position.PoolId)
if err != nil {
Expand Down Expand Up @@ -167,7 +192,8 @@ 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)
// We hardcode the underlying lock ID to 0 since we validated above that if an underlying lock exists, it is mature.
actualAmount0, actualAmount1, err := k.updatePosition(ctx, position.PoolId, owner, position.LowerTick, position.UpperTick, liquidityDelta, position.JoinTime, positionId, 0)
if err != nil {
return sdk.Int{}, sdk.Int{}, err
}
Expand Down Expand Up @@ -225,7 +251,7 @@ func (k Keeper) withdrawPosition(ctx sdk.Context, owner sdk.AccAddress, position
// 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.
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, underlyingLockId uint64) (sdk.Int, sdk.Int, error) {
// now calculate amount for token0 and token1
pool, err := k.getPoolById(ctx, poolId)
if err != nil {
Expand All @@ -249,7 +275,7 @@ func (k Keeper) updatePosition(ctx sdk.Context, poolId uint64, owner sdk.AccAddr

// update position state
// TODO: come back to sdk.Int vs sdk.Dec state & truncation
err = k.initOrUpdatePosition(ctx, poolId, owner, lowerTick, upperTick, liquidityDelta, joinTime, positionId)
err = k.initOrUpdatePosition(ctx, poolId, owner, lowerTick, upperTick, liquidityDelta, joinTime, positionId, underlyingLockId)
if err != nil {
return sdk.Int{}, sdk.Int{}, err
}
Expand Down Expand Up @@ -306,17 +332,15 @@ func (k Keeper) initializeInitialPositionForPool(ctx sdk.Context, pool types.Con
return types.InitialLiquidityZeroError{Amount0: amount0Desired, Amount1: amount1Desired}
}

initialSpotPrice := osmomath.BigDecFromSDKDec(amount1Desired.ToDec()).Quo(osmomath.BigDecFromSDKDec((amount0Desired.ToDec())))

// Calculate the initial tick from the initial spot price
initialTick, err := math.PriceToTick(initialSpotPrice.SDKDec(), pool.GetExponentAtPriceOne())
// Calculate the spot price and sqrt price from the amount provided
initialSpotPrice := amount1Desired.ToDec().Quo(amount0Desired.ToDec())
initialSqrtPrice, err := initialSpotPrice.ApproxSqrt()
if err != nil {
return err
}

// Re-Calculate the spot price from initial tick so that it is aligned with our internal
// tick to price conversion.
initialSqrtPrice, err := math.TickToSqrtPrice(initialTick, pool.GetExponentAtPriceOne())
// Calculate the initial tick from the initial spot price
initialTick, err := math.PriceToTick(initialSpotPrice, pool.GetExponentAtPriceOne())
if err != nil {
return err
}
Expand Down Expand Up @@ -384,3 +408,29 @@ func emitLiquidityChangeEvent(ctx sdk.Context, eventType string, positionId uint
sdk.NewAttribute(types.AttributeAmount1, actualAmount1.String()),
))
}

// isLockMature checks if the underlying lock has expired.
// If the lock doesn't exist, it returns true.
// If the lock exists, it checks if the lock has expired.
// If the lock has expired, it returns true.
// If the lock is still active, it returns false.
func (k Keeper) isLockMature(ctx sdk.Context, underlyingLockId uint64) (bool, error) {
// Query the underlying lock
underlyingLock, err := k.lockupKeeper.GetLockByID(ctx, underlyingLockId)
if err != nil && errors.Is(err, lockuptypes.ErrLockupNotFound) {
// Lock doesn't exist, so we can withdraw from this position
return true, nil
} else if err != nil {
// Unexpected error, return false to prevent any further action and return the error
return false, err
}

// Check if the lock has expired
if underlyingLock.EndTime.After(ctx.BlockTime()) {
// Lock is still active, so we cannot withdraw from this position
return false, nil
}

// Lock has expired, so we can withdraw from this position
return true, nil
}
1 change: 1 addition & 0 deletions x/concentrated-liquidity/lp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -829,6 +829,7 @@ func (s *KeeperTestSuite) TestUpdatePosition() {
tc.liquidityDelta,
tc.joinTime,
tc.positionId,
DefaultUnderlyingLockId,
)

if tc.expectedError {
Expand Down
Loading

0 comments on commit 0edfe2b

Please sign in to comment.