/
tick.go
190 lines (155 loc) · 7.11 KB
/
tick.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
package concentrated_liquidity
import (
"github.com/cosmos/cosmos-sdk/store/prefix"
storetypes "github.com/cosmos/cosmos-sdk/store/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/merlinslair/merlin/osmoutils"
"github.com/merlinslair/merlin/x/concentrated-liquidity/internal/math"
"github.com/merlinslair/merlin/x/concentrated-liquidity/model"
types "github.com/merlinslair/merlin/x/concentrated-liquidity/types"
)
// initOrUpdateTick retrieves the tickInfo from the specified tickIndex and updates both the liquidityNet and LiquidityGross.
// if we are initializing or updating an upper tick, we subtract the liquidityIn from the LiquidityNet
// if we are initializing or updating an lower tick, we add the liquidityIn from the LiquidityNet
func (k Keeper) initOrUpdateTick(ctx sdk.Context, poolId uint64, tickIndex int64, liquidityIn sdk.Dec, upper bool) (err error) {
pool, err := k.getPoolById(ctx, poolId)
if err != nil {
return err
}
currentTick := pool.GetCurrentTick().Int64()
tickInfo, err := k.getTickInfo(ctx, poolId, tickIndex)
if err != nil {
return err
}
// calculate liquidityGross, which does not care about whether liquidityIn is positive or negative
liquidityBefore := tickInfo.LiquidityGross
// note that liquidityIn can be either positive or negative.
// If negative, this would work as a subtraction from liquidityBefore
liquidityAfter := math.AddLiquidity(liquidityBefore, liquidityIn)
tickInfo.LiquidityGross = liquidityAfter
// calculate liquidityNet, which we take into account and track depending on whether liquidityIn is positive or negative
if upper {
tickInfo.LiquidityNet = tickInfo.LiquidityNet.Sub(liquidityIn)
} else {
tickInfo.LiquidityNet = tickInfo.LiquidityNet.Add(liquidityIn)
}
// if given tickIndex is LTE to current tick, tick's fee growth outside is set as fee accumulator's value
if tickIndex <= currentTick {
accum, err := k.getFeeAccumulator(ctx, poolId)
if err != nil {
return err
}
tickInfo.FeeGrowthOutside = accum.GetValue()
}
k.SetTickInfo(ctx, poolId, tickIndex, tickInfo)
return nil
}
func (k Keeper) crossTick(ctx sdk.Context, poolId uint64, tickIndex int64) (liquidityDelta sdk.Dec, err error) {
tickInfo, err := k.getTickInfo(ctx, poolId, tickIndex)
if err != nil {
return sdk.Dec{}, err
}
accum, err := k.getFeeAccumulator(ctx, poolId)
if err != nil {
return sdk.Dec{}, err
}
// subtract tick's fee growth outside from current fee accumulator
tickInfo.FeeGrowthOutside = accum.GetValue().Sub(tickInfo.FeeGrowthOutside)
k.SetTickInfo(ctx, poolId, tickIndex, tickInfo)
return tickInfo.LiquidityNet, nil
}
// getTickInfo gets tickInfo given poolId and tickIndex. Returns a boolean field that returns true if value is found for given key.
func (k Keeper) getTickInfo(ctx sdk.Context, poolId uint64, tickIndex int64) (tickInfo model.TickInfo, err error) {
store := ctx.KVStore(k.storeKey)
tickStruct := model.TickInfo{}
key := types.KeyTick(poolId, tickIndex)
if !k.poolExists(ctx, poolId) {
return model.TickInfo{}, types.PoolNotFoundError{PoolId: poolId}
}
found, err := osmoutils.Get(store, key, &tickStruct)
// return 0 values if key has not been initialized
if !found {
// If tick has not yet been initialized, we create a new one and initialize
// the fee growth outside.
initialFeeGrowthOutside, err := k.getInitialFeeGrowthOutsideForTick(ctx, poolId, tickIndex)
if err != nil {
return tickStruct, err
}
return model.TickInfo{LiquidityGross: sdk.ZeroDec(), LiquidityNet: sdk.ZeroDec(), FeeGrowthOutside: initialFeeGrowthOutside}, nil
}
if err != nil {
return tickStruct, err
}
return tickStruct, nil
}
func (k Keeper) SetTickInfo(ctx sdk.Context, poolId uint64, tickIndex int64, tickInfo model.TickInfo) {
store := ctx.KVStore(k.storeKey)
key := types.KeyTick(poolId, tickIndex)
osmoutils.MustSet(store, key, &tickInfo)
}
// validateTickInRangeIsValid validates that given ticks are valid.
// That is, both lower and upper ticks are within MinTick and MaxTick range for the given exponentAtPriceOne.
// Also, lower tick must be less than upper tick.
// Returns error if validation fails. Otherwise, nil.
// TODO: test
func validateTickRangeIsValid(tickSpacing uint64, exponentAtPriceOne sdk.Int, lowerTick int64, upperTick int64) error {
minTick, maxTick := GetMinAndMaxTicksFromExponentAtPriceOne(exponentAtPriceOne)
// Check if the lower and upper tick values are divisible by the tick spacing.
if lowerTick%int64(tickSpacing) != 0 || upperTick%int64(tickSpacing) != 0 {
return types.TickSpacingError{LowerTick: lowerTick, UpperTick: upperTick, TickSpacing: tickSpacing}
}
// Check if the lower tick value is within the valid range of MinTick to MaxTick.
if lowerTick < minTick || lowerTick >= maxTick {
return types.InvalidTickError{Tick: lowerTick, IsLower: true, MinTick: minTick, MaxTick: maxTick}
}
// Check if the upper tick value is within the valid range of MinTick to MaxTick.
if upperTick > maxTick || upperTick <= minTick {
return types.InvalidTickError{Tick: upperTick, IsLower: false, MinTick: minTick, MaxTick: maxTick}
}
// Check if the lower tick value is greater than or equal to the upper tick value.
if lowerTick >= upperTick {
return types.InvalidLowerUpperTickError{LowerTick: lowerTick, UpperTick: upperTick}
}
return nil
}
// GetMinAndMaxTicksFromExponentAtPriceOne determines min and max ticks allowed for a given exponentAtPriceOne value
// This allows for a min spot price of 0.000000000000000001 and a max spot price of 100000000000000000000000000000000000000 for every exponentAtPriceOne value
func GetMinAndMaxTicksFromExponentAtPriceOne(exponentAtPriceOne sdk.Int) (minTick, maxTick int64) {
return math.GetMinAndMaxTicksFromExponentAtPriceOneInternal(exponentAtPriceOne)
}
// GetPerTickLiquidityDepthFromRange uses the given lower tick and upper tick, iterates over ticks, creates and returns LiquidityDepth array.
// LiquidityNet from the tick is used to indicate liquidity depths.
func (k Keeper) GetPerTickLiquidityDepthFromRange(ctx sdk.Context, poolId uint64, lowerTick, upperTick int64) ([]types.LiquidityDepth, error) {
if !k.poolExists(ctx, poolId) {
return []types.LiquidityDepth{}, types.PoolNotFoundError{PoolId: poolId}
}
store := ctx.KVStore(k.storeKey)
prefixBz := types.KeyTickPrefix(poolId)
prefixStore := prefix.NewStore(store, prefixBz)
lowerKey := types.TickIndexToBytes(lowerTick)
upperKey := types.TickIndexToBytes(upperTick)
iterator := prefixStore.Iterator(lowerKey, storetypes.InclusiveEndBytes(upperKey))
liquidityDepths := []types.LiquidityDepth{}
defer iterator.Close()
for ; iterator.Valid(); iterator.Next() {
tickIndex, err := types.TickIndexFromBytes(iterator.Key())
if err != nil {
return []types.LiquidityDepth{}, err
}
keyTick := types.KeyTick(poolId, tickIndex)
tickStruct := model.TickInfo{}
found, err := osmoutils.Get(store, keyTick, &tickStruct)
if err != nil {
return []types.LiquidityDepth{}, err
}
if !found {
continue
}
liquidityDepth := types.LiquidityDepth{
TickIndex: sdk.NewInt(tickIndex),
LiquidityNet: tickStruct.LiquidityNet,
}
liquidityDepths = append(liquidityDepths, liquidityDepth)
}
return liquidityDepths, nil
}