Search Apps Documentation Source Content File Folder Download Copy Actions Download

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}