Search Apps Documentation Source Content File Folder Download Copy Actions Download

mint.gno

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