Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

mint.gno

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