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

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}