burn.gno
5.82 Kb · 170 lines
1package position
2
3import (
4 "errors"
5
6 "gno.land/p/gnoswap/consts/v1"
7 "gno.land/p/gnoswap/gnsmath/v1"
8 u256 "gno.land/p/gnoswap/uint256/v1"
9 "gno.land/p/gnoswap/utils/v1"
10 ufmt "gno.land/p/nt/ufmt/v0"
11
12 pl "gno.land/r/gnoswap/pool"
13 "gno.land/r/gnoswap/position"
14)
15
16// decreaseLiquidity reduces position liquidity and collects fees.
17// Returns positionId, liquidity, fee0, fee1, amount0, amount1, poolPath.
18func (p *positionV1) decreaseLiquidity(_ int, rlm realm, params DecreaseLiquidityParams) (uint64, string, string, string, string, string, string, error) {
19 caller := params.caller
20
21 // before decrease liquidity, collect fee first
22 _, fee0Str, fee1Str, _, _, _ := p.collectFee(0, rlm, params.positionId, params.caller)
23
24 position := p.mustGetPosition(params.positionId)
25 positionLiquidity := u256.MustFromDecimal(position.Liquidity())
26 if positionLiquidity.IsZero() {
27 return params.positionId,
28 "",
29 fee0Str,
30 fee1Str,
31 "", "",
32 position.PoolKey(),
33 makeErrorWithDetails(
34 errZeroLiquidity,
35 ufmt.Sprintf("position(position ID:%d) has 0 liquidity", params.positionId),
36 )
37 }
38
39 liquidityToRemove := u256.MustFromDecimal(params.liquidity)
40
41 if liquidityToRemove.Gt(positionLiquidity) {
42 return params.positionId,
43 liquidityToRemove.ToString(),
44 fee0Str,
45 fee1Str,
46 "", "",
47 position.PoolKey(),
48 makeErrorWithDetails(
49 errInvalidLiquidity,
50 ufmt.Sprintf("Liquidity requested(%s) is greater than liquidity held(%s)", liquidityToRemove.ToString(), positionLiquidity.ToString()),
51 )
52 }
53
54 pToken0, pToken1, pFee := splitOf(position.PoolKey())
55 burn0, burn1 := pl.Burn(cross(rlm), pToken0, pToken1, pFee, position.TickLower(), position.TickUpper(), liquidityToRemove.ToString(), caller)
56
57 burnedAmount0 := utils.SafeParseInt64(burn0)
58 burnedAmount1 := utils.SafeParseInt64(burn1)
59
60 if burnedAmount0 < 0 || burnedAmount1 < 0 {
61 panic(errors.New(errUnderflow))
62 }
63
64 positionKey := computePositionKey(position.TickLower(), position.TickUpper())
65 feeGrowthInside0LastX128Str, feeGrowthInside1LastX128Str, err := pl.GetPositionFeeGrowthInsideLastX128(position.PoolKey(), positionKey)
66 if err != nil {
67 return 0, "", "", "", "", "", position.PoolKey(), err
68 }
69
70 // Add only burned amounts to tokensOwed since fees were already collected and processed in collectFee
71 tokensOwed0 := gnsmath.SafeAddInt64(position.TokensOwed0(), burnedAmount0)
72 tokensOwed1 := gnsmath.SafeAddInt64(position.TokensOwed1(), burnedAmount1)
73
74 newLiquidity, underflow := u256.Zero().SubOverflow(positionLiquidity, liquidityToRemove)
75 if underflow {
76 panic(newErrorWithDetail(errUnderflow, "positionLiquidity - liquidityToRemove underflow"))
77 }
78
79 position.SetTokensOwed0(tokensOwed0)
80 position.SetTokensOwed1(tokensOwed1)
81 position.SetFeeGrowthInside0LastX128(feeGrowthInside0LastX128Str)
82 position.SetFeeGrowthInside1LastX128(feeGrowthInside1LastX128Str)
83 position.SetLiquidity(newLiquidity.ToString())
84
85 p.mustUpdatePosition(0, rlm, params.positionId, *position)
86
87 collect0, collect1 := pl.Collect(
88 cross(rlm),
89 pToken0,
90 pToken1,
91 pFee,
92 caller,
93 position.TickLower(),
94 position.TickUpper(),
95 burn0,
96 burn1,
97 )
98
99 collectAmount0 := u256.MustFromDecimal(collect0)
100 collectAmount1 := u256.MustFromDecimal(collect1)
101
102 // Slippage check on actually collected amounts to ensure user receives minimum expected tokens
103 if isSlippageExceeded(collectAmount0, collectAmount1, params.amount0Min, params.amount1Min) {
104 return params.positionId,
105 liquidityToRemove.ToString(),
106 fee0Str,
107 fee1Str,
108 collect0,
109 collect1,
110 position.PoolKey(),
111 makeErrorWithDetails(
112 errSlippage,
113 ufmt.Sprintf("collectAmount0(%s) >= amount0Min(%s) && collectAmount1(%s) >= amount1Min(%s)",
114 collectAmount0.ToString(),
115 params.amount0Min.ToString(),
116 collectAmount1.ToString(),
117 params.amount1Min.ToString(),
118 ),
119 )
120 }
121
122 // Check for underflow when subtracting collected amounts from tokens owed
123 collectAmount0Int64 := gnsmath.SafeConvertToInt64(collectAmount0)
124 collectAmount1Int64 := gnsmath.SafeConvertToInt64(collectAmount1)
125
126 if position.TokensOwed0() < collectAmount0Int64 {
127 panic(ufmt.Sprintf("[POSITION] burn.gno | collect() | tokensOwed0(%d) < collectAmount0(%d)", position.TokensOwed0(), collectAmount0Int64))
128 }
129 position.SetTokensOwed0(gnsmath.SafeSubInt64(position.TokensOwed0(), collectAmount0Int64))
130
131 if position.TokensOwed1() < collectAmount1Int64 {
132 panic(ufmt.Sprintf("[POSITION] burn.gno | collect() | tokensOwed1(%d) < collectAmount1(%d)", position.TokensOwed1(), collectAmount1Int64))
133 }
134 position.SetTokensOwed1(gnsmath.SafeSubInt64(position.TokensOwed1(), collectAmount1Int64))
135
136 if position.IsClear() {
137 position.SetBurned(true) // just update flag (we don't want to burn actual position)
138 }
139
140 p.mustUpdatePosition(0, rlm, params.positionId, *position)
141
142 return params.positionId, liquidityToRemove.ToString(), fee0Str, fee1Str, collect0, collect1, position.PoolKey(), nil
143}
144
145// calculateFees calculates the fees for the current position.
146func (p *positionV1) calculateFees(position *position.Position, currentFeeGrowth FeeGrowthInside) (int64, int64) {
147 posLiquidity := u256.MustFromDecimal(position.Liquidity())
148 fee0 := calculateTokensOwed(
149 currentFeeGrowth.feeGrowthInside0LastX128,
150 u256.MustFromDecimal(position.FeeGrowthInside0LastX128()),
151 posLiquidity,
152 )
153
154 fee1 := calculateTokensOwed(
155 currentFeeGrowth.feeGrowthInside1LastX128,
156 u256.MustFromDecimal(position.FeeGrowthInside1LastX128()),
157 posLiquidity,
158 )
159
160 return gnsmath.SafeAddInt64(position.TokensOwed0(), gnsmath.SafeConvertToInt64(fee0)), gnsmath.SafeAddInt64(position.TokensOwed1(), gnsmath.SafeConvertToInt64(fee1))
161}
162
163func calculateTokensOwed(
164 feeGrowthInsideLastX128 *u256.Uint,
165 positionFeeGrowthInsideLastX128 *u256.Uint,
166 positionLiquidity *u256.Uint,
167) *u256.Uint {
168 diff := u256.Zero().Sub(feeGrowthInsideLastX128, positionFeeGrowthInsideLastX128)
169 return u256.MulDiv(diff, positionLiquidity, consts.Q128())
170}