mint.gno
6.03 Kb · 192 lines
1package position
2
3import (
4 "errors"
5
6 u256 "gno.land/p/gnoswap/uint256/v1"
7 ufmt "gno.land/p/nt/ufmt/v0"
8
9 "gno.land/r/gnoswap/common"
10 pl "gno.land/r/gnoswap/pool"
11 "gno.land/r/gnoswap/position"
12)
13
14// mint creates a new liquidity position by adding liquidity to a pool and minting an NFT.
15// Panics if position ID already exists or adding liquidity fails.
16func (p *positionV1) mint(_ int, rlm realm, params MintParams) (uint64, *u256.Uint, *u256.Uint, *u256.Uint) {
17 poolKey := pl.GetPoolPath(params.token0, params.token1, params.fee)
18 liquidity, amount0, amount1 := p.addLiquidity(
19 0,
20 rlm,
21 AddLiquidityParams{
22 poolKey: poolKey,
23 tickLower: params.tickLower,
24 tickUpper: params.tickUpper,
25 amount0Desired: params.amount0Desired,
26 amount1Desired: params.amount1Desired,
27 amount0Min: params.amount0Min,
28 amount1Min: params.amount1Min,
29 caller: params.caller,
30 },
31 )
32 // Ensure liquidity is not zero before minting NFT
33 if liquidity.IsZero() {
34 panic(newErrorWithDetail(
35 errZeroLiquidity,
36 "Liquidity is zero, cannot mint position.",
37 ))
38 }
39
40 id := p.getNextId()
41
42 if p.ExistPosition(id) {
43 panic(newErrorWithDetail(
44 errPositionExist,
45 ufmt.Sprintf("positionId(%d)", id),
46 ))
47 }
48
49 p.nftAccessor.Mint(0, rlm, params.mintTo, positionIdFrom(id))
50
51 positionKey := computePositionKey(params.tickLower, params.tickUpper)
52 feeGrowthInside0LastX128, feeGrowthInside1LastX128, err := pl.GetPositionFeeGrowthInsideLastX128(poolKey, positionKey)
53 if err != nil {
54 panic(err)
55 }
56
57 position := position.NewPosition(
58 poolKey,
59 params.tickLower,
60 params.tickUpper,
61 liquidity.ToString(),
62 feeGrowthInside0LastX128,
63 feeGrowthInside1LastX128,
64 0,
65 0,
66 false,
67 zeroAddress,
68 )
69
70 // The position ID should not exist at the time of minting
71 p.mustUpdatePosition(0, rlm, id, *position)
72 p.incrementNextId(0, rlm)
73
74 return id, liquidity, amount0, amount1
75}
76
77// processMintInput processes and validates user input for minting liquidity.
78// It handles token ordering and amount validation.
79func (p *positionV1) processMintInput(input MintInput) (ProcessedMintInput, error) {
80 token0, token1 := input.token0, input.token1
81 if err := validateTokenPath(token0, token1); err != nil {
82 return ProcessedMintInput{}, makeErrorWithDetails(err.Error(), ufmt.Sprintf("token0(%s), token1(%s)", token0, token1))
83 }
84
85 pair := TokenPair{
86 token0: token0,
87 token1: token1,
88 }
89
90 // parse amounts
91 amount0Desired, amount1Desired, amount0Min, amount1Min := parseAmounts(input.amount0Desired, input.amount1Desired, input.amount0Min, input.amount1Min)
92
93 tickLower, tickUpper := input.tickLower, input.tickUpper
94
95 // swap if token1 < token0
96 if token1 < token0 {
97 pair.token0, pair.token1 = pair.token1, pair.token0
98 amount0Desired, amount1Desired = amount1Desired, amount0Desired
99 amount0Min, amount1Min = amount1Min, amount0Min
100 tickLower, tickUpper = -tickUpper, -tickLower
101 }
102
103 return ProcessedMintInput{
104 tokenPair: pair,
105 amount0Desired: amount0Desired,
106 amount1Desired: amount1Desired,
107 amount0Min: amount0Min,
108 amount1Min: amount1Min,
109 tickLower: tickLower,
110 tickUpper: tickUpper,
111 poolPath: pl.GetPoolPath(pair.token0, pair.token1, input.fee),
112 }, nil
113}
114
115// increaseLiquidity increases the liquidity of an existing position.
116func (p *positionV1) increaseLiquidity(_ int, rlm realm, params IncreaseLiquidityParams) (uint64, *u256.Uint, *u256.Uint, *u256.Uint, string, error) {
117 caller := params.caller
118 position := p.mustGetPosition(params.positionId)
119
120 liquidity, amount0, amount1 := p.addLiquidity(
121 0,
122 rlm,
123 AddLiquidityParams{
124 poolKey: position.PoolKey(),
125 tickLower: position.TickLower(),
126 tickUpper: position.TickUpper(),
127 amount0Desired: params.amount0Desired,
128 amount1Desired: params.amount1Desired,
129 amount0Min: params.amount0Min,
130 amount1Min: params.amount1Min,
131 caller: caller,
132 },
133 )
134
135 positionKey := computePositionKey(position.TickLower(), position.TickUpper())
136 feeGrowthInside0LastX128Str, feeGrowthInside1LastX128Str, err := pl.GetPositionFeeGrowthInsideLastX128(position.PoolKey(), positionKey)
137 if err != nil {
138 return 0, nil, nil, nil, "", err
139 }
140
141 feeGrowth := FeeGrowthInside{
142 feeGrowthInside0LastX128: u256.MustFromDecimal(feeGrowthInside0LastX128Str),
143 feeGrowthInside1LastX128: u256.MustFromDecimal(feeGrowthInside1LastX128Str),
144 }
145 tokensOwed0, tokensOwed1 := p.calculateFees(position, feeGrowth)
146
147 posLiquidity := u256.MustFromDecimal(position.Liquidity())
148 liquidityAmount, overflow := u256.Zero().AddOverflow(posLiquidity, liquidity)
149 if overflow {
150 return 0, nil, nil, nil, "", errors.New(errOverflow)
151 }
152
153 position.SetTokensOwed0(tokensOwed0)
154 position.SetTokensOwed1(tokensOwed1)
155
156 position.SetFeeGrowthInside0LastX128(feeGrowthInside0LastX128Str)
157 position.SetFeeGrowthInside1LastX128(feeGrowthInside1LastX128Str)
158
159 position.SetLiquidity(liquidityAmount.ToString())
160 position.SetBurned(false)
161
162 err = p.setPosition(0, rlm, params.positionId, *position)
163 if err != nil {
164 return 0, nil, nil, nil, "", makeErrorWithDetails(
165 errPositionDoesNotExist,
166 ufmt.Sprintf("cannot increase liquidity for non-existent position(%d)", params.positionId),
167 )
168 }
169
170 return params.positionId, liquidity, amount0, amount1, position.PoolKey(), nil
171}
172
173// validateTokenPath validates token paths are not identical, not conflicting, and in valid format.
174func validateTokenPath(token0, token1 string) error {
175 if token0 == token1 {
176 return errors.New(errInvalidTokenPath)
177 }
178 if !isValidTokenPath(token0) || !isValidTokenPath(token1) {
179 return errors.New(errInvalidTokenPath)
180 }
181 return nil
182}
183
184// isValidTokenPath checks if the token path is registered in the system.
185func isValidTokenPath(tokenPath string) bool {
186 return common.IsRegistered(tokenPath) == nil
187}
188
189// parseAmounts converts amount strings to u256.Uint values.
190func parseAmounts(amount0Desired, amount1Desired, amount0Min, amount1Min string) (*u256.Uint, *u256.Uint, *u256.Uint, *u256.Uint) {
191 return u256.MustFromDecimal(amount0Desired), u256.MustFromDecimal(amount1Desired), u256.MustFromDecimal(amount0Min), u256.MustFromDecimal(amount1Min)
192}