From 331b8f47eb1ebb9e30a1493506fa309ed87f9b52 Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Fri, 21 Oct 2022 20:25:26 -0400 Subject: [PATCH] feat(CL): Support Next Tick (#3079) * bez: updates * bez: updates * bez: updates * bez: updates * bez: updates * bez: updates * bez: updates * bez: updates * bez: updates * bez: updates * bez: updates * bez: updates * Update x/concentrated-liquidity/tick_test.go Co-authored-by: Matt, Park <45252226+mattverse@users.noreply.github.com> * Update x/concentrated-liquidity/tick.go Co-authored-by: Matt, Park <45252226+mattverse@users.noreply.github.com> * bez: updates * bez: updates * bez: updates * bez: updates * bez: updates * bez: updates * bez: updates * Fix lint Co-authored-by: Matt, Park <45252226+mattverse@users.noreply.github.com> Co-authored-by: mattverse --- concentratedPool.pb.go | 105 ++++++++++------------------------ lp.go | 18 ++++-- lp_test.go | 4 +- position.go | 21 ++++--- tick.go | 69 +++++++++++++++++++--- tick_test.go | 127 +++++++++++++++++++++++++++++++++++++++++ types/constants.go | 8 +-- types/keys.go | 67 +++++++++++++++++----- 8 files changed, 301 insertions(+), 118 deletions(-) create mode 100644 tick_test.go diff --git a/concentratedPool.pb.go b/concentratedPool.pb.go index a7dd065121d..0e6b3017ed0 100644 --- a/concentratedPool.pb.go +++ b/concentratedPool.pb.go @@ -74,9 +74,7 @@ func (m *Pool) XXX_DiscardUnknown() { var xxx_messageInfo_Pool proto.InternalMessageInfo type TickInfo struct { - Initialized bool `protobuf:"varint,1,opt,name=initialized,proto3" json:"initialized,omitempty"` - // sum of all non-normalized pool weights - Liquidity github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,2,opt,name=liquidity,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"liquidity" yaml:"liquidity"` + Liquidity github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,1,opt,name=liquidity,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"liquidity" yaml:"liquidity"` } func (m *TickInfo) Reset() { *m = TickInfo{} } @@ -112,13 +110,6 @@ func (m *TickInfo) XXX_DiscardUnknown() { var xxx_messageInfo_TickInfo proto.InternalMessageInfo -func (m *TickInfo) GetInitialized() bool { - if m != nil { - return m.Initialized - } - return false -} - type Position struct { Liquidity github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,1,opt,name=liquidity,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"liquidity" yaml:"liquidity"` } @@ -167,36 +158,35 @@ func init() { } var fileDescriptor_b144264ce94bcf63 = []byte{ - // 460 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x93, 0x31, 0x6f, 0xd3, 0x40, - 0x14, 0xc7, 0xed, 0x34, 0x4d, 0xd3, 0x2b, 0xaa, 0xda, 0x03, 0x21, 0xd3, 0xc1, 0x8e, 0x2c, 0x81, - 0x3a, 0x10, 0x1b, 0x83, 0x58, 0x2a, 0xa6, 0x20, 0x86, 0x6c, 0x91, 0x41, 0x0c, 0x2c, 0xc1, 0xb9, - 0x3b, 0xd2, 0xa7, 0x38, 0x77, 0xc9, 0xdd, 0xa5, 0x22, 0x7c, 0x02, 0x26, 0xc4, 0xc8, 0xd8, 0x0f, - 0xc1, 0x67, 0x40, 0x15, 0x53, 0x47, 0xc4, 0x10, 0xa1, 0xe4, 0x1b, 0xf4, 0x13, 0x20, 0xdb, 0x17, - 0xe3, 0x0c, 0x0c, 0x1d, 0x32, 0xd9, 0xef, 0xff, 0xee, 0xfd, 0x7f, 0x7f, 0xe9, 0xe9, 0xa1, 0xe7, - 0x42, 0x8d, 0x85, 0x02, 0x15, 0x12, 0xc1, 0x09, 0xe3, 0x5a, 0x26, 0x9a, 0xd1, 0x76, 0x0a, 0xd3, - 0x19, 0x50, 0xd0, 0xf3, 0x0d, 0xb9, 0x27, 0x44, 0x1a, 0x4c, 0xa4, 0xd0, 0x02, 0x3f, 0x34, 0x63, - 0x41, 0xb5, 0x5f, 0x4e, 0x05, 0x17, 0xd1, 0x80, 0xe9, 0x24, 0x3a, 0x79, 0x40, 0xf2, 0x77, 0xfd, - 0x7c, 0x28, 0x2c, 0x8a, 0xc2, 0xe1, 0xe4, 0xde, 0x50, 0x0c, 0x45, 0xa1, 0x67, 0x7f, 0x85, 0xea, - 0xff, 0xd8, 0x41, 0xf5, 0x0c, 0x83, 0x1f, 0xa3, 0xbd, 0x84, 0x52, 0xc9, 0x94, 0x72, 0xec, 0x96, - 0x7d, 0xba, 0xdf, 0xc1, 0x37, 0x0b, 0xef, 0x70, 0x9e, 0x8c, 0xd3, 0x33, 0xdf, 0x34, 0xfc, 0x78, - 0xfd, 0x04, 0x1f, 0xa2, 0x1a, 0x50, 0xa7, 0xd6, 0xb2, 0x4f, 0xeb, 0x71, 0x0d, 0x28, 0x7e, 0x8f, - 0xf6, 0xcb, 0x30, 0xce, 0x4e, 0x3e, 0xdf, 0xb9, 0x5a, 0x78, 0xd6, 0xef, 0x85, 0xf7, 0x68, 0x08, - 0xfa, 0x7c, 0x36, 0x08, 0x88, 0x18, 0x9b, 0x40, 0xe6, 0xd3, 0x56, 0x74, 0x14, 0xea, 0xf9, 0x84, - 0xa9, 0xa0, 0xcb, 0xf5, 0xcd, 0xc2, 0x3b, 0x2a, 0x68, 0xa5, 0x91, 0x1f, 0xff, 0x33, 0xc5, 0xf7, - 0x51, 0x43, 0x8b, 0x11, 0xe3, 0x4f, 0x9c, 0x7a, 0x66, 0x1f, 0x9b, 0xaa, 0xd4, 0x23, 0x67, 0xb7, - 0xa2, 0x47, 0x78, 0x8a, 0x30, 0x99, 0x49, 0xc9, 0xb8, 0xee, 0xab, 0xa9, 0xd4, 0xfd, 0x89, 0x04, - 0xc2, 0x9c, 0x46, 0x1e, 0xed, 0xe5, 0xad, 0xa3, 0x1d, 0x17, 0xd1, 0xd4, 0x44, 0x18, 0x27, 0x3f, - 0x3e, 0x32, 0xf6, 0xaf, 0xa7, 0x52, 0xf7, 0x32, 0x09, 0x9f, 0xa3, 0x3b, 0x6b, 0xa4, 0x06, 0x32, - 0x72, 0xf6, 0x72, 0xd8, 0xab, 0x5b, 0xc3, 0xee, 0x16, 0xb0, 0xaa, 0x97, 0x1f, 0x1f, 0x98, 0xf2, - 0x0d, 0x90, 0xd1, 0xd9, 0xf1, 0xe7, 0x4b, 0xcf, 0xfa, 0x76, 0xe9, 0x59, 0x3f, 0xbf, 0xb7, 0x77, - 0xb3, 0xf5, 0x75, 0xfd, 0x2f, 0x36, 0x6a, 0x66, 0xbd, 0x2e, 0xff, 0x20, 0x70, 0x0b, 0x1d, 0x00, - 0x07, 0x0d, 0x49, 0x0a, 0x9f, 0x18, 0xcd, 0x17, 0xda, 0x8c, 0xab, 0xd2, 0xe6, 0xc2, 0x6a, 0x5b, - 0x58, 0x98, 0x9f, 0xa2, 0x66, 0x4f, 0x28, 0xd0, 0x20, 0xf8, 0x26, 0xcd, 0xde, 0x02, 0xad, 0xf3, - 0xf6, 0x6a, 0xe9, 0xda, 0xd7, 0x4b, 0xd7, 0xfe, 0xb3, 0x74, 0xed, 0xaf, 0x2b, 0xd7, 0xba, 0x5e, - 0xb9, 0xd6, 0xaf, 0x95, 0x6b, 0xbd, 0x7b, 0x51, 0x01, 0x98, 0x23, 0x6a, 0xa7, 0xc9, 0x40, 0xad, - 0x8b, 0xf0, 0x22, 0x7a, 0x1a, 0x7e, 0xfc, 0xcf, 0x39, 0x0e, 0x1a, 0xf9, 0x99, 0x3c, 0xfb, 0x1b, - 0x00, 0x00, 0xff, 0xff, 0x1d, 0x93, 0x31, 0x76, 0xb7, 0x03, 0x00, 0x00, + // 436 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x93, 0x31, 0x8e, 0xd3, 0x40, + 0x14, 0x86, 0xed, 0x6c, 0x36, 0xcb, 0x0e, 0x68, 0xb5, 0x3b, 0x20, 0x64, 0xb6, 0xb0, 0x57, 0x96, + 0x40, 0x5b, 0x60, 0x1b, 0x83, 0x68, 0x22, 0xaa, 0x20, 0x8a, 0x74, 0x91, 0x41, 0x14, 0x34, 0xc1, + 0x1e, 0x0f, 0xc9, 0xc8, 0x8e, 0xc7, 0x9e, 0x99, 0x44, 0xe4, 0x06, 0x94, 0x94, 0x94, 0x39, 0x04, + 0x67, 0x40, 0x11, 0x55, 0x4a, 0x44, 0x61, 0xa1, 0xe4, 0x06, 0x39, 0x01, 0xb2, 0x3d, 0x31, 0x4e, + 0x41, 0x91, 0x22, 0x95, 0xfd, 0xfe, 0x37, 0xef, 0xff, 0x7e, 0xe9, 0xe9, 0x81, 0x97, 0x94, 0x4f, + 0x28, 0x27, 0xdc, 0x41, 0x34, 0x41, 0x38, 0x11, 0xcc, 0x17, 0x38, 0xb4, 0x62, 0x92, 0x4d, 0x49, + 0x48, 0xc4, 0x7c, 0x4f, 0x1e, 0x50, 0x1a, 0xdb, 0x29, 0xa3, 0x82, 0xc2, 0xc7, 0x72, 0xcc, 0x6e, + 0xf6, 0xeb, 0x29, 0x7b, 0xe6, 0x06, 0x58, 0xf8, 0xee, 0xf5, 0x23, 0x54, 0xbe, 0x1b, 0x96, 0x43, + 0x4e, 0x55, 0x54, 0x0e, 0xd7, 0x0f, 0x46, 0x74, 0x44, 0x2b, 0xbd, 0xf8, 0xab, 0x54, 0xf3, 0xc7, + 0x09, 0x68, 0x17, 0x18, 0xf8, 0x14, 0x9c, 0xf9, 0x61, 0xc8, 0x30, 0xe7, 0x9a, 0x7a, 0xa3, 0xde, + 0x9e, 0xf7, 0xe0, 0x36, 0x37, 0x2e, 0xe6, 0xfe, 0x24, 0xee, 0x9a, 0xb2, 0x61, 0x7a, 0xbb, 0x27, + 0xf0, 0x02, 0xb4, 0x48, 0xa8, 0xb5, 0x6e, 0xd4, 0xdb, 0xb6, 0xd7, 0x22, 0x21, 0xfc, 0x08, 0xce, + 0xeb, 0x30, 0xda, 0x49, 0x39, 0xdf, 0x5b, 0xe6, 0x86, 0xf2, 0x3b, 0x37, 0x9e, 0x8c, 0x88, 0x18, + 0x4f, 0x03, 0x1b, 0xd1, 0x89, 0x0c, 0x24, 0x3f, 0x16, 0x0f, 0x23, 0x47, 0xcc, 0x53, 0xcc, 0xed, + 0x7e, 0x22, 0xb6, 0xb9, 0x71, 0x59, 0xd1, 0x6a, 0x23, 0xd3, 0xfb, 0x67, 0x0a, 0x1f, 0x82, 0x8e, + 0xa0, 0x11, 0x4e, 0x9e, 0x69, 0xed, 0xc2, 0xde, 0x93, 0x55, 0xad, 0xbb, 0xda, 0x69, 0x43, 0x77, + 0x61, 0x06, 0x20, 0x9a, 0x32, 0x86, 0x13, 0x31, 0xe4, 0x19, 0x13, 0xc3, 0x94, 0x11, 0x84, 0xb5, + 0x4e, 0x19, 0xed, 0xf5, 0xc1, 0xd1, 0xae, 0xaa, 0x68, 0x3c, 0xa5, 0xd2, 0xc9, 0xf4, 0x2e, 0xa5, + 0xfd, 0xdb, 0x8c, 0x89, 0x41, 0x21, 0xc1, 0x31, 0xb8, 0xb7, 0x43, 0x0a, 0x82, 0x22, 0xed, 0xac, + 0x84, 0xbd, 0x39, 0x18, 0x76, 0xbf, 0x82, 0x35, 0xbd, 0x4c, 0xef, 0xae, 0x2c, 0xdf, 0x11, 0x14, + 0x75, 0xaf, 0xbe, 0x2c, 0x0c, 0xe5, 0xdb, 0xc2, 0x50, 0x7e, 0x7e, 0xb7, 0x4e, 0x8b, 0xf5, 0xf5, + 0xcd, 0x18, 0xdc, 0x29, 0x5a, 0xfd, 0xe4, 0x13, 0xdd, 0xdf, 0x86, 0x7a, 0x84, 0x6d, 0x14, 0xb4, + 0x01, 0xe5, 0x44, 0x10, 0x9a, 0x1c, 0x9f, 0xd6, 0x7b, 0xbf, 0x5c, 0xeb, 0xea, 0x6a, 0xad, 0xab, + 0x7f, 0xd6, 0xba, 0xfa, 0x75, 0xa3, 0x2b, 0xab, 0x8d, 0xae, 0xfc, 0xda, 0xe8, 0xca, 0x87, 0x57, + 0x0d, 0x80, 0xbc, 0x10, 0x2b, 0xf6, 0x03, 0xbe, 0x2b, 0x9c, 0x99, 0xfb, 0xdc, 0xf9, 0xfc, 0x9f, + 0x5b, 0x0b, 0x3a, 0xe5, 0x0d, 0xbc, 0xf8, 0x1b, 0x00, 0x00, 0xff, 0xff, 0xaa, 0xa3, 0x98, 0x3b, + 0x94, 0x03, 0x00, 0x00, } func (m *Pool) Marshal() (dAtA []byte, err error) { @@ -307,17 +297,7 @@ func (m *TickInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintConcentratedPool(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x12 - if m.Initialized { - i-- - if m.Initialized { - dAtA[i] = 1 - } else { - dAtA[i] = 0 - } - i-- - dAtA[i] = 0x8 - } + dAtA[i] = 0xa return len(dAtA) - i, nil } @@ -401,9 +381,6 @@ func (m *TickInfo) Size() (n int) { } var l int _ = l - if m.Initialized { - n += 2 - } l = m.Liquidity.Size() n += 1 + l + sovConcentratedPool(uint64(l)) return n @@ -723,26 +700,6 @@ func (m *TickInfo) Unmarshal(dAtA []byte) error { } switch fieldNum { case 1: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Initialized", wireType) - } - var v int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowConcentratedPool - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - v |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - m.Initialized = bool(v != 0) - case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Liquidity", wireType) } diff --git a/lp.go b/lp.go index 1d1559d2adf..038af7ca2a9 100644 --- a/lp.go +++ b/lp.go @@ -8,10 +8,17 @@ import ( types "github.com/osmosis-labs/osmosis/v12/x/concentrated-liquidity/types" ) -func (k Keeper) Mint(ctx sdk.Context, poolId uint64, owner sdk.AccAddress, liquidityIn sdk.Int, lowerTick sdk.Int, upperTick sdk.Int) (amtDenom0, amtDenom1 sdk.Int, err error) { - // ensure that lower tick is always smaller than upper tick - if lowerTick.GTE(types.MaxTick) || lowerTick.LT(types.MinTick) || upperTick.GT(types.MaxTick) { - return sdk.Int{}, sdk.Int{}, fmt.Errorf("validation fail") +func (k Keeper) Mint(ctx sdk.Context, poolId uint64, owner sdk.AccAddress, liquidityIn sdk.Int, lowerTick, upperTick int64) (amtDenom0, amtDenom1 sdk.Int, err error) { + // ensure types.MinTick <= lowerTick < types.MaxTick + // TODO (bez): Add unit tests. + if lowerTick < types.MinTick || lowerTick >= types.MaxTick { + return sdk.Int{}, sdk.Int{}, fmt.Errorf("invalid lower tick: %d", lowerTick) + } + + // ensure types.MaxTick < upperTick <= types.MinTick + // TODO (bez): Add unit tests. + if upperTick > types.MaxTick || upperTick <= types.MinTick { + return sdk.Int{}, sdk.Int{}, fmt.Errorf("invalid upper tick: %d", upperTick) } if liquidityIn.IsZero() { @@ -44,12 +51,15 @@ func (k Keeper) Mint(ctx sdk.Context, poolId uint64, owner sdk.AccAddress, liqui func (k Keeper) JoinPoolNoSwap(ctx sdk.Context, tokensIn sdk.Coins, swapFee sdk.Dec) (numShares sdk.Int, err error) { return sdk.Int{}, nil } + func (k Keeper) CalcJoinPoolShares(ctx sdk.Context, tokensIn sdk.Coins, swapFee sdk.Dec) (numShares sdk.Int, newLiquidity sdk.Coins, err error) { return sdk.Int{}, sdk.Coins{}, nil } + func (k Keeper) CalcJoinPoolNoSwapShares(ctx sdk.Context, tokensIn sdk.Coins, swapFee sdk.Dec) (numShares sdk.Int, newLiquidity sdk.Coins, err error) { return sdk.Int{}, sdk.Coins{}, nil } + func (k Keeper) ExitPool(ctx sdk.Context, numShares sdk.Int, exitFee sdk.Dec) (exitedCoins sdk.Coins, err error) { return sdk.Coins{}, nil } diff --git a/lp_test.go b/lp_test.go index ff4406c38e9..e8c5c4c00b0 100644 --- a/lp_test.go +++ b/lp_test.go @@ -15,8 +15,8 @@ func (s *KeeperTestSuite) TestMint() { // denom1: usdc poolId := uint64(1) currentTick := sdk.NewInt(85176) - lowerTick := sdk.NewInt(84222) - upperTick := sdk.NewInt(86129) + lowerTick := int64(84222) + upperTick := int64(86129) liquidity, ok := sdk.NewIntFromString("1517882343751509868544") s.Require().True(ok) currentSqrtP, ok := sdk.NewIntFromString("5602277097478614198912276234240") diff --git a/position.go b/position.go index 02ef6c70ca9..1ed4bb5f18e 100644 --- a/position.go +++ b/position.go @@ -10,9 +10,10 @@ import ( // nolint: unused func (k Keeper) updatePositionWithLiquidity(ctx sdk.Context, poolId uint64, - owner string, - lowerTick, upperTick sdk.Int, - liquidityDelta sdk.Int) { + owner sdk.AccAddress, + lowerTick, upperTick int64, + liquidityDelta sdk.Int, +) { position := k.getPosition(ctx, poolId, owner, lowerTick, upperTick) liquidityBefore := position.Liquidity @@ -23,22 +24,24 @@ func (k Keeper) updatePositionWithLiquidity(ctx sdk.Context, } // nolint: unused -func (k Keeper) getPosition(ctx sdk.Context, poolId uint64, owner string, lowerTick, upperTick sdk.Int) Position { +func (k Keeper) getPosition(ctx sdk.Context, poolId uint64, owner sdk.AccAddress, lowerTick, upperTick int64) Position { store := ctx.KVStore(k.storeKey) - position := Position{} + + var position Position key := types.KeyPosition(poolId, owner, lowerTick, upperTick) osmoutils.MustGet(store, key, &position) + return position } // nolint: unused func (k Keeper) setPosition(ctx sdk.Context, poolId uint64, - owner string, - lowerTick, upperTick sdk.Int, - position Position) { + owner sdk.AccAddress, + lowerTick, upperTick int64, + position Position, +) { store := ctx.KVStore(k.storeKey) - key := types.KeyPosition(poolId, owner, lowerTick, upperTick) osmoutils.MustSet(store, key, &position) } diff --git a/tick.go b/tick.go index 29e6fdabbe7..65d5366786b 100644 --- a/tick.go +++ b/tick.go @@ -1,14 +1,19 @@ package concentrated_liquidity import ( + fmt "fmt" + + "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" + db "github.com/tendermint/tm-db" + "github.com/osmosis-labs/osmosis/v12/osmomath" "github.com/osmosis-labs/osmosis/v12/osmoutils" types "github.com/osmosis-labs/osmosis/v12/x/concentrated-liquidity/types" ) -func (k Keeper) getSqrtRatioAtTick(tickIndex sdk.Int) (sdk.Dec, error) { - sqrtRatio, err := sdk.NewDecWithPrec(10001, 4).Power(tickIndex.Uint64()).ApproxSqrt() +func (k Keeper) getSqrtRatioAtTick(tickIndex int64) (sdk.Dec, error) { + sqrtRatio, err := sdk.NewDecWithPrec(10001, 4).Power(uint64(tickIndex)).ApproxSqrt() if err != nil { return sdk.Dec{}, nil } @@ -21,22 +26,68 @@ func (k Keeper) getSqrtRatioAtTick(tickIndex sdk.Int) (sdk.Dec, error) { // return sdk.Int{} // } -func (k Keeper) UpdateTickWithNewLiquidity(ctx sdk.Context, poolId uint64, tickIndex sdk.Int, liquidityDelta sdk.Int) { +func (k Keeper) UpdateTickWithNewLiquidity(ctx sdk.Context, poolId uint64, tickIndex int64, liquidityDelta sdk.Int) { tickInfo := k.getTickInfo(ctx, poolId, tickIndex) liquidityBefore := tickInfo.Liquidity liquidityAfter := liquidityBefore.Add(liquidityDelta) - tickInfo.Liquidity = liquidityAfter - if liquidityBefore == sdk.ZeroInt() { - tickInfo.Initialized = true + k.setTickInfo(ctx, poolId, tickIndex, tickInfo) +} + +// NextInitializedTick returns the next initialized tick index based on the +// current or provided tick index. If no initialized tick exists, <0, false> +// will be returned. The lte argument indicates if we need to find the next +// initialized tick to the left or right of the current tick index, where true +// indicates searching to the left. +func (k Keeper) NextInitializedTick(ctx sdk.Context, poolId uint64, tickIndex int64, lte bool) (next int64, initialized bool) { + store := ctx.KVStore(k.storeKey) + + // Construct a prefix store with a prefix of , allowing + // us to retrieve the next initialized tick without having to scan all ticks. + prefixBz := types.KeyTickPrefix(poolId) + prefixStore := prefix.NewStore(store, prefixBz) + + var startKey []byte + if lte { + // When looking to the left of the current tick, we need to evaluate the + // current tick as well. The end cursor for reverse iteration is non-inclusive + // so must add one and handle overflow. + startKey = types.TickIndexToBytes(osmomath.Max(tickIndex, tickIndex+1)) + } else { + startKey = types.TickIndexToBytes(tickIndex) } - k.setTickInfo(ctx, poolId, tickIndex, tickInfo) + var iter db.Iterator + if lte { + iter = prefixStore.ReverseIterator(nil, startKey) + } else { + iter = prefixStore.Iterator(startKey, nil) + } + + defer iter.Close() + + for ; iter.Valid(); iter.Next() { + // Since, we constructed our prefix store with , the + // key is the encoding of a tick index. + tick, err := types.TickIndexFromBytes(iter.Key()) + if err != nil { + panic(fmt.Errorf("invalid tick index (%s): %v", string(iter.Key()), err)) + } + + if !lte && tick > tickIndex { + return tick, true + } + if lte && tick <= tickIndex { + return tick, true + } + } + + return 0, false } -func (k Keeper) getTickInfo(ctx sdk.Context, poolId uint64, tickIndex sdk.Int) TickInfo { +func (k Keeper) getTickInfo(ctx sdk.Context, poolId uint64, tickIndex int64) TickInfo { store := ctx.KVStore(k.storeKey) tickInfo := TickInfo{} key := types.KeyTick(poolId, tickIndex) @@ -44,7 +95,7 @@ func (k Keeper) getTickInfo(ctx sdk.Context, poolId uint64, tickIndex sdk.Int) T return tickInfo } -func (k Keeper) setTickInfo(ctx sdk.Context, poolId uint64, tickIndex sdk.Int, tickInfo TickInfo) { +func (k Keeper) setTickInfo(ctx sdk.Context, poolId uint64, tickIndex int64, tickInfo TickInfo) { store := ctx.KVStore(k.storeKey) key := types.KeyTick(poolId, tickIndex) osmoutils.MustSet(store, key, &tickInfo) diff --git a/tick_test.go b/tick_test.go new file mode 100644 index 00000000000..4c15ca77738 --- /dev/null +++ b/tick_test.go @@ -0,0 +1,127 @@ +package concentrated_liquidity + +import ( + "testing" + + "github.com/cosmos/cosmos-sdk/store/prefix" + "github.com/cosmos/cosmos-sdk/testutil" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + + types "github.com/osmosis-labs/osmosis/v12/x/concentrated-liquidity/types" +) + +func TestKeeper_TickOrdering(t *testing.T) { + storeKey := sdk.NewKVStoreKey("concentrated_liquidity") + tKey := sdk.NewTransientStoreKey("transient_test") + ctx := testutil.DefaultContext(storeKey, tKey) + + k := Keeper{storeKey: storeKey} + + liquidityTicks := []int64{-200, -55, -4, 70, 78, 84, 139, 240, 535} + for _, t := range liquidityTicks { + k.setTickInfo(ctx, 1, t, TickInfo{}) + } + + store := ctx.KVStore(k.storeKey) + prefixBz := types.KeyTickPrefix(1) + prefixStore := prefix.NewStore(store, prefixBz) + + // Pick a value and ensure ordering is correct for lte=false, i.e. increasing + // ticks. + startKey := types.TickIndexToBytes(-4) + iter := prefixStore.Iterator(startKey, nil) + defer iter.Close() + + var vals []int64 + for ; iter.Valid(); iter.Next() { + tick, err := types.TickIndexFromBytes(iter.Key()) + require.NoError(t, err) + + vals = append(vals, tick) + } + + require.Equal(t, []int64{-4, 70, 78, 84, 139, 240, 535}, vals) + + // Pick a value and ensure ordering is correct for lte=true, i.e. decreasing + // ticks. + startKey = types.TickIndexToBytes(84) + revIter := prefixStore.ReverseIterator(nil, startKey) + defer revIter.Close() + + vals = nil + for ; revIter.Valid(); revIter.Next() { + tick, err := types.TickIndexFromBytes(revIter.Key()) + require.NoError(t, err) + + vals = append(vals, tick) + } + + require.Equal(t, []int64{78, 70, -4, -55, -200}, vals) +} + +func TestKeeper_NextInitializedTick(t *testing.T) { + storeKey := sdk.NewKVStoreKey("concentrated_liquidity") + tKey := sdk.NewTransientStoreKey("transient_test") + ctx := testutil.DefaultContext(storeKey, tKey) + + k := Keeper{storeKey: storeKey} + + liquidityTicks := []int64{-200, -55, -4, 70, 78, 84, 139, 240, 535} + for _, t := range liquidityTicks { + k.setTickInfo(ctx, 1, t, TickInfo{}) + } + + t.Run("lte=false", func(t *testing.T) { + t.Run("returns tick to right if at initialized tick", func(t *testing.T) { + n, initd := k.NextInitializedTick(ctx, 1, 78, false) + require.Equal(t, int64(84), n) + require.True(t, initd) + }) + t.Run("returns tick to right if at initialized tick", func(t *testing.T) { + n, initd := k.NextInitializedTick(ctx, 1, -55, false) + require.Equal(t, int64(-4), n) + require.True(t, initd) + }) + t.Run("returns the tick directly to the right", func(t *testing.T) { + n, initd := k.NextInitializedTick(ctx, 1, 77, false) + require.Equal(t, int64(78), n) + require.True(t, initd) + }) + t.Run("returns the tick directly to the right", func(t *testing.T) { + n, initd := k.NextInitializedTick(ctx, 1, -56, false) + require.Equal(t, int64(-55), n) + require.True(t, initd) + }) + t.Run("returns the next words initialized tick if on the right boundary", func(t *testing.T) { + n, initd := k.NextInitializedTick(ctx, 1, -257, false) + require.Equal(t, int64(-200), n) + require.True(t, initd) + }) + t.Run("returns the next initialized tick from the next word", func(t *testing.T) { + k.setTickInfo(ctx, 1, 340, TickInfo{}) + + n, initd := k.NextInitializedTick(ctx, 1, 328, false) + require.Equal(t, int64(340), n) + require.True(t, initd) + }) + }) + + t.Run("lte=true", func(t *testing.T) { + t.Run("returns tick directly to the left of input tick if not initialized", func(t *testing.T) { + n, initd := k.NextInitializedTick(ctx, 1, 79, true) + require.Equal(t, int64(78), n) + require.True(t, initd) + }) + t.Run("returns same tick if initialized", func(t *testing.T) { + n, initd := k.NextInitializedTick(ctx, 1, 78, true) + require.Equal(t, int64(78), n) + require.True(t, initd) + }) + t.Run("returns next initialized tick far away", func(t *testing.T) { + n, initd := k.NextInitializedTick(ctx, 1, 100, true) + require.Equal(t, int64(84), n) + require.True(t, initd) + }) + }) +} diff --git a/types/constants.go b/types/constants.go index dc076c217e1..d4df3be60cb 100644 --- a/types/constants.go +++ b/types/constants.go @@ -1,11 +1,7 @@ package types -import ( - sdk "github.com/cosmos/cosmos-sdk/types" -) - // TODO: decide on the values for Max tick and Min tick var ( - MaxTick = sdk.NewIntFromUint64(887272) - MinTick = MaxTick.Neg() + MinTick int64 = -887272 + MaxTick int64 = 887272 ) diff --git a/types/keys.go b/types/keys.go index 8aca854efcb..c02a3169179 100644 --- a/types/keys.go +++ b/types/keys.go @@ -4,33 +4,72 @@ import ( "fmt" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/address" ) const ( ModuleName = "concentratedliquidity" + RouterKey = ModuleName - StoreKey = ModuleName - RouterKey = ModuleName - - QuerierRoute = ModuleName - // Contract: Coin denoms cannot contain this character - KeySeparator = "|" + StoreKey = ModuleName ) +// Key prefixes var ( - TickPrefix = "tick_prefix" + KeySeparator - PositionPrefix = "position_prefix" + KeySeparator - PoolPrefix = "pool_prefix" + KeySeparator + TickPrefix = []byte{0x01} + PositionPrefix = []byte{0x02} + PoolPrefix = []byte{0x03} ) -// KeyTick uses pool Id and tick index -func KeyTick(poolId uint64, tickIndex sdk.Int) []byte { - return []byte(fmt.Sprintf("%s%d%s", TickPrefix, poolId, tickIndex.String())) +// TickIndexToBytes converts a tick index to a byte slice. Negative tick indexes +// are prefixed with 0x00 a byte and positive tick indexes are prefixed with a +// 0x01 byte. We do this because big endian byte encoding does not give us in +// order iteration in state due to the tick index values being signed integers. +func TickIndexToBytes(tickIndex int64) []byte { + key := make([]byte, 9) + if tickIndex < 0 { + copy(key[1:], sdk.Uint64ToBigEndian(uint64(tickIndex))) + } else { + copy(key[:1], []byte{0x01}) + copy(key[1:], sdk.Uint64ToBigEndian(uint64(tickIndex))) + } + + return key +} + +// TickIndexFromBytes converts an encoded tick index to an int64 value. It returns +// an error if the encoded tick has invalid length. +func TickIndexFromBytes(bz []byte) (int64, error) { + if len(bz) != 9 { + return 0, fmt.Errorf("invalid encoded tick index length; expected: 9, got: %d", len(bz)) + } + + return int64(sdk.BigEndianToUint64(bz[1:])), nil +} + +// KeyTick returns a key for storing a TickInfo object. +func KeyTick(poolId uint64, tickIndex int64) []byte { + key := KeyTickPrefix(poolId) + key = append(key, TickIndexToBytes(tickIndex)...) + return key +} + +// KeyTickPrefix constructs a key prefix for storing a TickInfo object. +func KeyTickPrefix(poolId uint64) []byte { + var key []byte + key = append(key, TickPrefix...) + key = append(key, sdk.Uint64ToBigEndian(poolId)...) + return key } // KeyPosition uses pool Id, owner, lower tick and upper tick for keys -func KeyPosition(poolId uint64, address string, lowerTick, upperTick sdk.Int) []byte { - return []byte(fmt.Sprintf("%s%d%s%s%s", PositionPrefix, poolId, address, lowerTick.String(), upperTick.String())) +func KeyPosition(poolId uint64, addr sdk.AccAddress, lowerTick, upperTick int64) []byte { + var key []byte + key = append(key, PositionPrefix...) + key = append(key, address.MustLengthPrefix(addr)...) + key = append(key, sdk.Uint64ToBigEndian(uint64(lowerTick))...) + key = append(key, sdk.Uint64ToBigEndian(uint64(upperTick))...) + return key } func KeyPool(poolId uint64) []byte {