burn.gno
6.90 Kb ยท 199 lines
1package v1
2
3import (
4 prabc "gno.land/p/gnoswap/rbac"
5 u256 "gno.land/p/gnoswap/uint256"
6 "gno.land/p/nt/ufmt"
7
8 "gno.land/r/gnoswap/access"
9 "gno.land/r/gnoswap/common"
10 pl "gno.land/r/gnoswap/pool"
11 "gno.land/r/gnoswap/position"
12)
13
14// decreaseLiquidity reduces position liquidity and collects fees.
15// If unwrapResult is true, unwraps WUGNOT to GNOT.
16// Returns positionId, liquidity, fee0, fee1, amount0, amount1, poolPath.
17func (p *positionV1) decreaseLiquidity(params DecreaseLiquidityParams) (uint64, string, string, string, string, string, string, error) {
18 caller := params.caller
19
20 // before decrease liquidity, collect fee first
21 _, fee0Str, fee1Str, _, _, _ := p.collectFee(params.positionId, params.unwrapResult, params.caller)
22
23 position := p.mustGetPosition(params.positionId)
24 if position.Liquidity().IsZero() {
25 return params.positionId,
26 "",
27 fee0Str,
28 fee1Str,
29 "", "",
30 position.PoolKey(),
31 makeErrorWithDetails(
32 errZeroLiquidity,
33 ufmt.Sprintf("position(position ID:%d) has 0 liquidity", params.positionId),
34 )
35 }
36
37 liquidityToRemove := u256.MustFromDecimal(params.liquidity)
38 if liquidityToRemove.Gt(position.Liquidity()) {
39 return params.positionId,
40 liquidityToRemove.ToString(),
41 fee0Str,
42 fee1Str,
43 "", "",
44 position.PoolKey(),
45 makeErrorWithDetails(
46 errInvalidLiquidity,
47 ufmt.Sprintf("Liquidity requested(%s) is greater than liquidity held(%s)", liquidityToRemove.ToString(), position.Liquidity().ToString()),
48 )
49 }
50
51 pToken0, pToken1, pFee := splitOf(position.PoolKey())
52 burn0, burn1 := pl.Burn(cross, pToken0, pToken1, pFee, position.TickLower(), position.TickUpper(), liquidityToRemove.ToString(), caller)
53
54 burnedAmount0 := u256.MustFromDecimal(burn0)
55 burnedAmount1 := u256.MustFromDecimal(burn1)
56
57 positionKey := computePositionKey(position.TickLower(), position.TickUpper())
58 feeGrowthInside0LastX128, feeGrowthInside1LastX128 := pl.GetPositionFeeGrowthInsideLastX128(position.PoolKey(), positionKey)
59
60 calculatedToken0Balance, calculatedToken1Balance := calculatePositionBalances(position)
61
62 // Add only burned amounts to tokensOwed since fees were already collected and processed in collectFee
63 tokensOwed0 := u256.Zero().Add(position.TokensOwed0(), burnedAmount0)
64 tokensOwed1 := u256.Zero().Add(position.TokensOwed1(), burnedAmount1)
65
66 newLiquidity, underflow := u256.Zero().SubOverflow(position.Liquidity(), liquidityToRemove)
67 if underflow {
68 panic(newErrorWithDetail(errUnderflow, "positionLiquidity - liquidityToRemove underflow"))
69 }
70
71 newToken0Balance, underflow := u256.Zero().SubOverflow(calculatedToken0Balance, burnedAmount0)
72 if underflow {
73 panic(newErrorWithDetail(errUnderflow, "calculatedToken0Balance - burnedAmount0 underflow"))
74 }
75
76 newToken1Balance, underflow := u256.Zero().SubOverflow(calculatedToken1Balance, burnedAmount1)
77 if underflow {
78 panic(newErrorWithDetail(errUnderflow, "calculatedToken1Balance - burnedAmount1 underflow"))
79 }
80
81 position.SetTokensOwed0(tokensOwed0)
82 position.SetTokensOwed1(tokensOwed1)
83 position.SetFeeGrowthInside0LastX128(feeGrowthInside0LastX128)
84 position.SetFeeGrowthInside1LastX128(feeGrowthInside1LastX128)
85 position.SetLiquidity(newLiquidity)
86 position.SetToken0Balance(newToken0Balance)
87 position.SetToken1Balance(newToken1Balance)
88
89 p.mustUpdatePosition(params.positionId, *position)
90
91 collect0, collect1 := pl.Collect(
92 cross,
93 pToken0,
94 pToken1,
95 pFee,
96 caller,
97 position.TickLower(),
98 position.TickUpper(),
99 burn0,
100 burn1,
101 )
102
103 collectAmount0 := u256.MustFromDecimal(collect0)
104 collectAmount1 := u256.MustFromDecimal(collect1)
105
106 // Slippage check on actually collected amounts to ensure user receives minimum expected tokens
107 if isSlippageExceeded(collectAmount0, collectAmount1, params.amount0Min, params.amount1Min) {
108 return params.positionId,
109 liquidityToRemove.ToString(),
110 fee0Str,
111 fee1Str,
112 collect0,
113 collect1,
114 position.PoolKey(),
115 makeErrorWithDetails(
116 errSlippage,
117 ufmt.Sprintf("collectAmount0(%s) >= amount0Min(%s) && collectAmount1(%s) >= amount1Min(%s)",
118 collectAmount0.ToString(),
119 params.amount0Min.ToString(),
120 collectAmount1.ToString(),
121 params.amount1Min.ToString(),
122 ),
123 )
124 }
125
126 poolAddr := access.MustGetAddress(prabc.ROLE_POOL.String())
127
128 if isWrappedToken(pToken0) && params.unwrapResult {
129 p.unwrapWithTransferFrom(poolAddr, caller, safeConvertToInt64(collectAmount0))
130 } else {
131 common.SafeGRC20TransferFrom(cross, pToken0, poolAddr, caller, safeConvertToInt64(collectAmount0))
132 }
133
134 if isWrappedToken(pToken1) && params.unwrapResult {
135 p.unwrapWithTransferFrom(poolAddr, caller, safeConvertToInt64(collectAmount1))
136 } else {
137 common.SafeGRC20TransferFrom(cross, pToken1, poolAddr, caller, safeConvertToInt64(collectAmount1))
138 }
139
140 // Check for underflow when subtracting collected amounts from tokens owed
141 newOwed0, underflow0 := u256.Zero().SubOverflow(position.TokensOwed0(), collectAmount0)
142 if underflow0 {
143 panic(ufmt.Sprintf("[POSITION] burn.gno | collect() | tokensOwed0(%s) < collectAmount0(%s)", position.TokensOwed0().ToString(), collectAmount0.ToString()))
144 }
145 position.SetTokensOwed0(newOwed0)
146
147 newOwed1, underflow1 := u256.Zero().SubOverflow(position.TokensOwed1(), collectAmount1)
148 if underflow1 {
149 panic(ufmt.Sprintf("[POSITION] burn.gno | collect() | tokensOwed1(%s) < collectAmount1(%s)", position.TokensOwed1().ToString(), collectAmount1.ToString()))
150 }
151 position.SetTokensOwed1(newOwed1)
152
153 if position.IsClear() {
154 position.SetBurned(true) // just update flag (we don't want to burn actual position)
155 }
156
157 p.mustUpdatePosition(params.positionId, *position)
158
159 return params.positionId, liquidityToRemove.ToString(), fee0Str, fee1Str, collect0, collect1, position.PoolKey(), nil
160}
161
162// calculateFees calculates the fees for the current position.
163func (p *positionV1) calculateFees(position *position.Position, currentFeeGrowth FeeGrowthInside) (*u256.Uint, *u256.Uint) {
164 fee0 := calculateTokensOwed(
165 currentFeeGrowth.feeGrowthInside0LastX128,
166 position.FeeGrowthInside0LastX128(),
167 position.Liquidity(),
168 )
169
170 fee1 := calculateTokensOwed(
171 currentFeeGrowth.feeGrowthInside1LastX128,
172 position.FeeGrowthInside1LastX128(),
173 position.Liquidity(),
174 )
175
176 tokensOwed0, overflow0 := u256.Zero().AddOverflow(u256.Zero().Set(position.TokensOwed0()), fee0)
177 if overflow0 {
178 panic(newErrorWithDetail(errOverflow, "tokensOwed0 + fee0 overflow"))
179 }
180
181 tokensOwed1, overflow1 := u256.Zero().AddOverflow(u256.Zero().Set(position.TokensOwed1()), fee1)
182 if overflow1 {
183 panic(newErrorWithDetail(errOverflow, "tokensOwed1 + fee1 overflow"))
184 }
185
186 return tokensOwed0, tokensOwed1
187}
188
189func calculateTokensOwed(
190 feeGrowthInsideLastX128 *u256.Uint,
191 positionFeeGrowthInsideLastX128 *u256.Uint,
192 positionLiquidity *u256.Uint,
193) *u256.Uint {
194 diff, underflow := u256.Zero().SubOverflow(feeGrowthInsideLastX128, positionFeeGrowthInsideLastX128)
195 if underflow {
196 panic(newErrorWithDetail(errUnderflow, "feeGrowthInsideLastX128 - positionFeeGrowthInsideLastX128 underflow"))
197 }
198 return u256.MulDiv(diff, positionLiquidity, q128)
199}