reposition.gno
5.38 Kb · 160 lines
1package position
2
3import (
4 "chain"
5
6 "gno.land/r/gnoswap/access/v1"
7
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 "gno.land/r/gnoswap/common"
13 "gno.land/r/gnoswap/emission"
14 "gno.land/r/gnoswap/halt/v1"
15)
16
17// Reposition adjusts the price range and liquidity of an existing position.
18//
19// Parameters:
20// - _: leading integer discriminator; callers pass 0
21// - rlm: current realm context forwarded from the public wrapper; the implementation validates it as current
22// - positionId: NFT token ID to reposition
23// - tickLower: new lower tick boundary of the position's price range
24// - tickUpper: new upper tick boundary of the position's price range
25// - amount0DesiredStr: desired token0 amount for the new position, represented as a decimal string
26// - amount1DesiredStr: desired token1 amount for the new position, represented as a decimal string
27// - amount0MinStr: minimum acceptable token0 amount for slippage protection
28// - amount1MinStr: minimum acceptable token1 amount for slippage protection
29// - deadline: transaction expiration timestamp
30//
31// Returns:
32// - positionId: NFT token ID that was repositioned
33// - liquidity: new liquidity amount, represented as a decimal string
34// - tickLower: lower tick boundary applied to the position
35// - tickUpper: upper tick boundary applied to the position
36// - amount0: actual token0 amount added to the new range, represented as a decimal string
37// - amount1: actual token1 amount added to the new range, represented as a decimal string
38func (p *positionV1) Reposition(
39 _ int,
40 rlm realm,
41 positionId uint64,
42 tickLower int32,
43 tickUpper int32,
44 amount0DesiredStr string,
45 amount1DesiredStr string,
46 amount0MinStr string,
47 amount1MinStr string,
48 deadline int64,
49) (uint64, string, int32, int32, string, string) {
50 access.AssertIsRlmCurrent(0, rlm)
51
52 halt.AssertIsNotHaltedPosition()
53
54 previousRealm := rlm.Previous()
55 caller := previousRealm.Address()
56 assertIsOwnerForToken(p, positionId, caller)
57 assertIsNotExpired(deadline)
58
59 emission.MintAndDistributeGns(cross(rlm))
60
61 // The position must be clear (zero liquidity and zero tokens owed) before
62 // it can be repositioned.
63 position := p.mustGetPosition(positionId)
64
65 token0, token1, _ := splitOf(position.PoolKey())
66 common.AssertIsNotHandleNativeCoin()
67
68 oldTickLower := position.TickLower()
69 oldTickUpper := position.TickUpper()
70
71 if !position.IsClear() {
72 panic(newErrorWithDetail(
73 errNotClear,
74 ufmt.Sprintf(
75 "position(%d) isn't clear(liquidity:%s, tokensOwed0:%d, tokensOwed1:%d)",
76 positionId,
77 position.Liquidity(),
78 position.TokensOwed0(),
79 position.TokensOwed1(),
80 ),
81 ))
82 }
83
84 if err := validateTokenPath(token0, token1); err != nil {
85 panic(newErrorWithDetail(err.Error(), ufmt.Sprintf("token0(%s), token1(%s)", token0, token1)))
86 }
87
88 poolKey := position.PoolKey()
89
90 liquidity, amount0, amount1 := p.addLiquidity(
91 0,
92 rlm,
93 AddLiquidityParams{
94 poolKey: poolKey,
95 tickLower: tickLower,
96 tickUpper: tickUpper,
97 amount0Desired: u256.MustFromDecimal(amount0DesiredStr),
98 amount1Desired: u256.MustFromDecimal(amount1DesiredStr),
99 amount0Min: u256.MustFromDecimal(amount0MinStr),
100 amount1Min: u256.MustFromDecimal(amount1MinStr),
101 caller: caller,
102 },
103 )
104
105 // update position tickLower, tickUpper to new value
106 // because getCurrentFeeGrowth() uses tickLower, tickUpper
107 position.SetTickLower(tickLower)
108 position.SetTickUpper(tickUpper)
109
110 currentFeeGrowth, err := p.getCurrentFeeGrowth(position, caller)
111 if err != nil {
112 panic(newErrorWithDetail(err.Error(), "failed to get current fee growth"))
113 }
114 position.SetFeeGrowthInside0LastX128(currentFeeGrowth.feeGrowthInside0LastX128.ToString())
115 position.SetFeeGrowthInside1LastX128(currentFeeGrowth.feeGrowthInside1LastX128.ToString())
116
117 position.SetLiquidity(liquidity.ToString())
118
119 // Do not reset the fee-growth checkpoints: a full decrease followed by
120 // repositioning must not create an unintended unclaimed-fee balance.
121 position.SetTokensOwed0(0)
122 position.SetTokensOwed1(0)
123 position.SetBurned(false)
124 p.mustUpdatePosition(0, rlm, positionId, *position)
125
126 pool := mustGetPool(poolKey)
127 positionLiquidity, err := p.GetPositionLiquidity(positionId)
128 if err != nil {
129 panic(err)
130 }
131 tickCumulative, secondsPerLiquidityCumulativeX128, observationTimestamp, err := currentPoolObservation(poolKey)
132 if err != nil {
133 panic(err)
134 }
135
136 chain.Emit(
137 "Reposition",
138 "prevAddr", previousRealm.Address().String(),
139 "prevRealm", previousRealm.PkgPath(),
140 "lpPositionId", utils.FormatUint(positionId),
141 "tickLower", utils.FormatInt(tickLower),
142 "tickUpper", utils.FormatInt(tickUpper),
143 "liquidityDelta", liquidity.ToString(),
144 "amount0", amount0.ToString(),
145 "amount1", amount1.ToString(),
146 "prevTickLower", utils.FormatInt(oldTickLower),
147 "prevTickUpper", utils.FormatInt(oldTickUpper),
148 "poolPath", poolKey,
149 "sqrtPriceX96", pool.Slot0SqrtPriceX96().ToString(),
150 "positionLiquidity", positionLiquidity,
151 "poolLiquidity", pool.Liquidity().ToString(),
152 "token0Balance", utils.FormatInt(pool.BalanceToken0()),
153 "token1Balance", utils.FormatInt(pool.BalanceToken1()),
154 "tickCumulative", utils.FormatInt(tickCumulative),
155 "secondsPerLiquidityCumulativeX128", secondsPerLiquidityCumulativeX128,
156 "observationTimestamp", utils.FormatInt(observationTimestamp),
157 )
158
159 return positionId, liquidity.ToString(), tickLower, tickUpper, amount0.ToString(), amount1.ToString()
160}