mint.gno
7.72 Kb ยท 249 lines
1package v1
2
3import (
4 u256 "gno.land/p/gnoswap/uint256"
5 "gno.land/p/nt/ufmt"
6
7 "gno.land/r/gnoswap/common"
8 pl "gno.land/r/gnoswap/pool"
9 "gno.land/r/gnoswap/position"
10)
11
12// mint creates a new liquidity position by adding liquidity to a pool and minting an NFT.
13// Panics if position ID already exists or adding liquidity fails.
14func (p *positionV1) mint(params MintParams) (uint64, *u256.Uint, *u256.Uint, *u256.Uint) {
15 poolKey := pl.GetPoolPath(params.token0, params.token1, params.fee)
16 liquidity, amount0, amount1 := p.addLiquidity(
17 AddLiquidityParams{
18 poolKey: poolKey,
19 tickLower: params.tickLower,
20 tickUpper: params.tickUpper,
21 amount0Desired: params.amount0Desired,
22 amount1Desired: params.amount1Desired,
23 amount0Min: params.amount0Min,
24 amount1Min: params.amount1Min,
25 caller: params.caller,
26 },
27 )
28 // Ensure liquidity is not zero before minting NFT
29 if liquidity.IsZero() {
30 panic(newErrorWithDetail(
31 errZeroLiquidity,
32 "Liquidity is zero, cannot mint position.",
33 ))
34 }
35
36 id := p.getNextId()
37
38 if p.ExistPosition(id) {
39 panic(newErrorWithDetail(
40 errPositionExist,
41 ufmt.Sprintf("positionId(%d)", id),
42 ))
43 }
44
45 p.nftAccessor.Mint(params.mintTo, positionIdFrom(id))
46
47 positionKey := computePositionKey(params.tickLower, params.tickUpper)
48 feeGrowthInside0LastX128, feeGrowthInside1LastX128 := pl.GetPositionFeeGrowthInsideLastX128(poolKey, positionKey)
49
50 position := position.NewPosition(
51 poolKey,
52 params.tickLower,
53 params.tickUpper,
54 liquidity,
55 feeGrowthInside0LastX128,
56 feeGrowthInside1LastX128,
57 u256.Zero(),
58 u256.Zero(),
59 amount0,
60 amount1,
61 false,
62 zeroAddress,
63 )
64
65 // The position ID should not exist at the time of minting
66 p.mustUpdatePosition(id, *position)
67 p.incrementNextId()
68
69 return id, liquidity, amount0, amount1
70}
71
72// processMintInput processes and validates user input for minting liquidity.
73// It handles token ordering, amount validation, and native token wrapping.
74func (p *positionV1) processMintInput(input MintInput) (ProcessedMintInput, error) {
75 token0, token1, token0IsNative, token1IsNative, wrappedAmount, err := p.processTokens(
76 input.token0,
77 input.token1,
78 input.amount0Desired,
79 input.amount1Desired,
80 input.caller,
81 )
82 if err != nil {
83 return ProcessedMintInput{}, err
84 }
85
86 pair := TokenPair{
87 token0: token0,
88 token1: token1,
89 token0IsNative: token0IsNative,
90 token1IsNative: token1IsNative,
91 wrappedAmount: wrappedAmount,
92 }
93
94 // parse amounts
95 amount0Desired, amount1Desired, amount0Min, amount1Min := parseAmounts(input.amount0Desired, input.amount1Desired, input.amount0Min, input.amount1Min)
96
97 tickLower, tickUpper := input.tickLower, input.tickUpper
98
99 // swap if token1 < token0
100 if token1 < token0 {
101 pair.token0, pair.token1 = pair.token1, pair.token0
102 amount0Desired, amount1Desired = amount1Desired, amount0Desired
103 amount0Min, amount1Min = amount1Min, amount0Min
104 tickLower, tickUpper = -tickUpper, -tickLower
105 pair.token0IsNative, pair.token1IsNative = pair.token1IsNative, pair.token0IsNative
106 }
107
108 return ProcessedMintInput{
109 tokenPair: pair,
110 amount0Desired: amount0Desired,
111 amount1Desired: amount1Desired,
112 amount0Min: amount0Min,
113 amount1Min: amount1Min,
114 tickLower: tickLower,
115 tickUpper: tickUpper,
116 poolPath: pl.GetPoolPath(pair.token0, pair.token1, input.fee),
117 }, nil
118}
119
120// processTokens validates token paths and handles native token wrapping.
121// Panics if validation fails or native token wrapping encounters issues.
122func (p *positionV1) processTokens(
123 token0 string,
124 token1 string,
125 amount0Desired string,
126 amount1Desired string,
127 caller address,
128) (string, string, bool, bool, int64, error) {
129 err := validateTokenPath(token0, token1)
130 if err != nil {
131 panic(newErrorWithDetail(err, ufmt.Sprintf("token0(%s), token1(%s)", token0, token1)))
132 }
133
134 token0IsNative := false
135 token1IsNative := false
136 wrappedAmount := int64(0)
137
138 if isNative(token0) {
139 token0 = WUGNOT_PATH
140 token0IsNative = true
141
142 amount0DesiredInt := mustParseInt64(amount0Desired)
143 wrappedAmount, err = p.safeWrapNativeToken(amount0DesiredInt, caller)
144 if err != nil {
145 return "", "", false, false, 0, err
146 }
147 } else if isNative(token1) {
148 token1 = WUGNOT_PATH
149 token1IsNative = true
150
151 amount1DesiredInt := mustParseInt64(amount1Desired)
152 wrappedAmount, err = p.safeWrapNativeToken(amount1DesiredInt, caller)
153 if err != nil {
154 return "", "", false, false, 0, err
155 }
156 }
157
158 return token0, token1, token0IsNative, token1IsNative, wrappedAmount, nil
159}
160
161// increaseLiquidity increases the liquidity of an existing position.
162func (p *positionV1) increaseLiquidity(params IncreaseLiquidityParams) (uint64, *u256.Uint, *u256.Uint, *u256.Uint, string, error) {
163 caller := params.caller
164 position := p.mustGetPosition(params.positionId)
165
166 liquidity, amount0, amount1 := p.addLiquidity(
167 AddLiquidityParams{
168 poolKey: position.PoolKey(),
169 tickLower: position.TickLower(),
170 tickUpper: position.TickUpper(),
171 amount0Desired: params.amount0Desired,
172 amount1Desired: params.amount1Desired,
173 amount0Min: params.amount0Min,
174 amount1Min: params.amount1Min,
175 caller: caller,
176 },
177 )
178
179 positionKey := computePositionKey(position.TickLower(), position.TickUpper())
180 feeGrowthInside0LastX128, feeGrowthInside1LastX128 := pl.GetPositionFeeGrowthInsideLastX128(position.PoolKey(), positionKey)
181
182 calculatedToken0Balance, calculatedToken1Balance := calculatePositionBalances(position)
183
184 feeGrowth := FeeGrowthInside{
185 feeGrowthInside0LastX128: feeGrowthInside0LastX128,
186 feeGrowthInside1LastX128: feeGrowthInside1LastX128,
187 }
188 tokensOwed0, tokensOwed1 := p.calculateFees(position, feeGrowth)
189
190 liquidityAmount, overflow := u256.Zero().AddOverflow(position.Liquidity(), liquidity)
191 if overflow {
192 return 0, nil, nil, nil, "", errOverflow
193 }
194 token0Balance, overflow := u256.Zero().AddOverflow(calculatedToken0Balance, amount0)
195 if overflow {
196 return 0, nil, nil, nil, "", errOverflow
197 }
198 token1Balance, overflow := u256.Zero().AddOverflow(calculatedToken1Balance, amount1)
199 if overflow {
200 return 0, nil, nil, nil, "", errOverflow
201 }
202
203 position.SetTokensOwed0(tokensOwed0)
204 position.SetTokensOwed1(tokensOwed1)
205
206 position.SetFeeGrowthInside0LastX128(feeGrowthInside0LastX128)
207 position.SetFeeGrowthInside1LastX128(feeGrowthInside1LastX128)
208
209 position.SetLiquidity(liquidityAmount)
210 position.SetToken0Balance(token0Balance)
211 position.SetToken1Balance(token1Balance)
212 position.SetBurned(false)
213
214 err := p.setPosition(params.positionId, *position)
215 if err != nil {
216 return 0, nil, nil, nil, "", makeErrorWithDetails(
217 errPositionDoesNotExist,
218 ufmt.Sprintf("cannot increase liquidity for non-existent position(%d)", params.positionId),
219 )
220 }
221
222 return params.positionId, liquidity, amount0, amount1, position.PoolKey(), nil
223}
224
225// validateTokenPath validates token paths are not identical, not conflicting, and in valid format.
226func validateTokenPath(token0, token1 string) error {
227 if token0 == token1 {
228 return errInvalidTokenPath
229 }
230 if (token0 == common.GNOT_DENOM && token1 == WUGNOT_PATH) ||
231 (token0 == WUGNOT_PATH && token1 == common.GNOT_DENOM) {
232 return errInvalidTokenPath
233 }
234 if (!isNative(token0) && !isValidTokenPath(token0)) ||
235 (!isNative(token1) && !isValidTokenPath(token1)) {
236 return errInvalidTokenPath
237 }
238 return nil
239}
240
241// isValidTokenPath checks if the token path is registered in the system.
242func isValidTokenPath(tokenPath string) bool {
243 return common.IsRegistered(tokenPath) == nil
244}
245
246// parseAmounts converts amount strings to u256.Uint values.
247func parseAmounts(amount0Desired, amount1Desired, amount0Min, amount1Min string) (*u256.Uint, *u256.Uint, *u256.Uint, *u256.Uint) {
248 return u256.MustFromDecimal(amount0Desired), u256.MustFromDecimal(amount1Desired), u256.MustFromDecimal(amount0Min), u256.MustFromDecimal(amount1Min)
249}