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

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}