reposition.gno
5.43 Kb ยท 173 lines
1package v1
2
3import (
4 "chain"
5 "chain/runtime"
6
7 u256 "gno.land/p/gnoswap/uint256"
8 "gno.land/p/nt/ufmt"
9
10 "gno.land/r/gnoswap/common"
11 "gno.land/r/gnoswap/emission"
12 "gno.land/r/gnoswap/halt"
13 pl "gno.land/r/gnoswap/pool"
14)
15
16// Reposition adjusts the price range and liquidity of an existing position.
17//
18// Parameters:
19// - positionId: NFT token ID to reposition
20// - tickLower, tickUpper: new price range boundaries
21// - amount0DesiredStr, amount1DesiredStr: desired token amounts for new position
22// - amount0MinStr, amount1MinStr: minimum acceptable amounts (slippage protection)
23// - deadline: transaction expiration timestamp
24//
25// Returns positionId, liquidity, tickLower, tickUpper, amount0, amount1.
26func (p *positionV1) Reposition(
27 positionId uint64,
28 tickLower int32,
29 tickUpper int32,
30 amount0DesiredStr string,
31 amount1DesiredStr string,
32 amount0MinStr string,
33 amount1MinStr string,
34 deadline int64,
35) (uint64, string, int32, int32, string, string) {
36 halt.AssertIsNotHaltedPosition()
37 halt.AssertIsNotHaltedWithdraw()
38
39 caller := runtime.PreviousRealm().Address()
40 assertIsOwnerForToken(p, positionId, caller)
41 assertIsNotExpired(deadline)
42
43 emission.MintAndDistributeGns(cross)
44
45 // position should be burned to reposition
46 position := p.mustGetPosition(positionId)
47
48 // assert that the user has sent the correct amount of native coin
49 token0, token1, _ := splitOf(position.PoolKey())
50 assertIsValidUserCoinSendWithWrappedTokenPair(token0, token1, amount0DesiredStr, amount1DesiredStr)
51
52 receivedCoins := common.ExistsUserSendCoins()
53 if receivedCoins {
54 if common.IsGNOTPath(token0) {
55 token0 = common.GNOT_DENOM
56 } else if common.IsGNOTPath(token1) {
57 token1 = common.GNOT_DENOM
58 }
59 }
60
61 oldTickLower := position.TickLower()
62 oldTickUpper := position.TickUpper()
63
64 if !position.IsClear() {
65 panic(newErrorWithDetail(
66 errNotClear,
67 ufmt.Sprintf(
68 "position(%d) isn't clear(liquidity:%s, tokensOwed0:%s, tokensOwed1:%s)",
69 positionId,
70 position.Liquidity().ToString(),
71 position.TokensOwed0().ToString(),
72 position.TokensOwed1().ToString(),
73 ),
74 ))
75 }
76
77 token0, token1, _, _, _, err := p.processTokens(
78 token0,
79 token1,
80 amount0DesiredStr,
81 amount1DesiredStr,
82 caller,
83 )
84 if err != nil {
85 panic(err)
86 }
87
88 poolKey := position.PoolKey()
89
90 liquidity, amount0, amount1 := p.addLiquidity(
91 AddLiquidityParams{
92 poolKey: poolKey,
93 tickLower: tickLower,
94 tickUpper: tickUpper,
95 amount0Desired: u256.MustFromDecimal(amount0DesiredStr),
96 amount1Desired: u256.MustFromDecimal(amount1DesiredStr),
97 amount0Min: u256.MustFromDecimal(amount0MinStr),
98 amount1Min: u256.MustFromDecimal(amount1MinStr),
99 caller: caller,
100 },
101 )
102
103 // before update position, calculate token balances
104 calculatedToken0Balance, calculatedToken1Balance := calculatePositionBalances(position)
105
106 token0Balance, overflow := u256.Zero().AddOverflow(calculatedToken0Balance, amount0)
107 if overflow {
108 panic(newErrorWithDetail(errOverflow, "token0Balance + amount0 overflow"))
109 }
110
111 token1Balance, overflow := u256.Zero().AddOverflow(calculatedToken1Balance, amount1)
112 if overflow {
113 panic(newErrorWithDetail(errOverflow, "token1Balance + amount1 overflow"))
114 }
115
116 // update position tickLower, tickUpper to new value
117 // because getCurrentFeeGrowth() uses tickLower, tickUpper
118 position.SetTickLower(tickLower)
119 position.SetTickUpper(tickUpper)
120
121 currentFeeGrowth, err := p.getCurrentFeeGrowth(position, caller)
122 if err != nil {
123 panic(newErrorWithDetail(err, "failed to get current fee growth"))
124 }
125 position.SetFeeGrowthInside0LastX128(currentFeeGrowth.feeGrowthInside0LastX128)
126 position.SetFeeGrowthInside1LastX128(currentFeeGrowth.feeGrowthInside1LastX128)
127
128 position.SetLiquidity(liquidity)
129 position.SetToken0Balance(token0Balance)
130 position.SetToken1Balance(token1Balance)
131
132 // OBS: do not reset feeGrowthInside1LastX128 and feeGrowthInside1LastX128 to zero
133 // if so, ( decrease 100% -> reposition )
134 // > at this point, that position will have unclaimedFee which isn't intended
135 position.SetTokensOwed0(u256.Zero())
136 position.SetTokensOwed1(u256.Zero())
137 position.SetBurned(false)
138 p.mustUpdatePosition(positionId, *position)
139
140 poolSqrtPriceX96 := pl.GetSlot0SqrtPriceX96(poolKey)
141 poolToken0Balance := pl.GetBalanceToken0(poolKey)
142 poolToken1Balance := pl.GetBalanceToken1(poolKey)
143
144 tickCumulative, liquidityCumulative, secondsPerLiquidityCumulativeX128, observationTimestamp :=
145 pl.GetObservation(poolKey, 0)
146
147 previousRealm := runtime.PreviousRealm()
148 chain.Emit(
149 "Reposition",
150 "prevAddr", previousRealm.Address().String(),
151 "prevRealm", previousRealm.PkgPath(),
152 "lpPositionId", formatUint(positionId),
153 "tickLower", formatInt(tickLower),
154 "tickUpper", formatInt(tickUpper),
155 "liquidityDelta", liquidity.ToString(),
156 "amount0", amount0.ToString(),
157 "amount1", amount1.ToString(),
158 "prevTickLower", formatInt(oldTickLower),
159 "prevTickUpper", formatInt(oldTickUpper),
160 "poolPath", poolKey,
161 "sqrtPriceX96", poolSqrtPriceX96.ToString(),
162 "positionLiquidity", p.GetPositionLiquidity(positionId).ToString(),
163 "poolLiquidity", pl.GetLiquidity(poolKey),
164 "token0Balance", poolToken0Balance,
165 "token1Balance", poolToken1Balance,
166 "tickCumulative", formatInt(tickCumulative),
167 "liquidityCumulative", liquidityCumulative,
168 "secondsPerLiquidityCumulativeX128", secondsPerLiquidityCumulativeX128,
169 "observationTimestamp", formatInt(observationTimestamp),
170 )
171
172 return positionId, liquidity.ToString(), tickLower, tickUpper, amount0.ToString(), amount1.ToString()
173}