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

burn.gno

5.82 Kb · 170 lines
  1package position
  2
  3import (
  4	"errors"
  5
  6	"gno.land/p/gnoswap/consts/v1"
  7	"gno.land/p/gnoswap/gnsmath/v1"
  8	u256 "gno.land/p/gnoswap/uint256/v1"
  9	"gno.land/p/gnoswap/utils/v1"
 10	ufmt "gno.land/p/nt/ufmt/v0"
 11
 12	pl "gno.land/r/gnoswap/pool"
 13	"gno.land/r/gnoswap/position"
 14)
 15
 16// decreaseLiquidity reduces position liquidity and collects fees.
 17// Returns positionId, liquidity, fee0, fee1, amount0, amount1, poolPath.
 18func (p *positionV1) decreaseLiquidity(_ int, rlm realm, params DecreaseLiquidityParams) (uint64, string, string, string, string, string, string, error) {
 19	caller := params.caller
 20
 21	// before decrease liquidity, collect fee first
 22	_, fee0Str, fee1Str, _, _, _ := p.collectFee(0, rlm, params.positionId, params.caller)
 23
 24	position := p.mustGetPosition(params.positionId)
 25	positionLiquidity := u256.MustFromDecimal(position.Liquidity())
 26	if positionLiquidity.IsZero() {
 27		return params.positionId,
 28			"",
 29			fee0Str,
 30			fee1Str,
 31			"", "",
 32			position.PoolKey(),
 33			makeErrorWithDetails(
 34				errZeroLiquidity,
 35				ufmt.Sprintf("position(position ID:%d) has 0 liquidity", params.positionId),
 36			)
 37	}
 38
 39	liquidityToRemove := u256.MustFromDecimal(params.liquidity)
 40
 41	if liquidityToRemove.Gt(positionLiquidity) {
 42		return params.positionId,
 43			liquidityToRemove.ToString(),
 44			fee0Str,
 45			fee1Str,
 46			"", "",
 47			position.PoolKey(),
 48			makeErrorWithDetails(
 49				errInvalidLiquidity,
 50				ufmt.Sprintf("Liquidity requested(%s) is greater than liquidity held(%s)", liquidityToRemove.ToString(), positionLiquidity.ToString()),
 51			)
 52	}
 53
 54	pToken0, pToken1, pFee := splitOf(position.PoolKey())
 55	burn0, burn1 := pl.Burn(cross(rlm), pToken0, pToken1, pFee, position.TickLower(), position.TickUpper(), liquidityToRemove.ToString(), caller)
 56
 57	burnedAmount0 := utils.SafeParseInt64(burn0)
 58	burnedAmount1 := utils.SafeParseInt64(burn1)
 59
 60	if burnedAmount0 < 0 || burnedAmount1 < 0 {
 61		panic(errors.New(errUnderflow))
 62	}
 63
 64	positionKey := computePositionKey(position.TickLower(), position.TickUpper())
 65	feeGrowthInside0LastX128Str, feeGrowthInside1LastX128Str, err := pl.GetPositionFeeGrowthInsideLastX128(position.PoolKey(), positionKey)
 66	if err != nil {
 67		return 0, "", "", "", "", "", position.PoolKey(), err
 68	}
 69
 70	// Add only burned amounts to tokensOwed since fees were already collected and processed in collectFee
 71	tokensOwed0 := gnsmath.SafeAddInt64(position.TokensOwed0(), burnedAmount0)
 72	tokensOwed1 := gnsmath.SafeAddInt64(position.TokensOwed1(), burnedAmount1)
 73
 74	newLiquidity, underflow := u256.Zero().SubOverflow(positionLiquidity, liquidityToRemove)
 75	if underflow {
 76		panic(newErrorWithDetail(errUnderflow, "positionLiquidity - liquidityToRemove underflow"))
 77	}
 78
 79	position.SetTokensOwed0(tokensOwed0)
 80	position.SetTokensOwed1(tokensOwed1)
 81	position.SetFeeGrowthInside0LastX128(feeGrowthInside0LastX128Str)
 82	position.SetFeeGrowthInside1LastX128(feeGrowthInside1LastX128Str)
 83	position.SetLiquidity(newLiquidity.ToString())
 84
 85	p.mustUpdatePosition(0, rlm, params.positionId, *position)
 86
 87	collect0, collect1 := pl.Collect(
 88		cross(rlm),
 89		pToken0,
 90		pToken1,
 91		pFee,
 92		caller,
 93		position.TickLower(),
 94		position.TickUpper(),
 95		burn0,
 96		burn1,
 97	)
 98
 99	collectAmount0 := u256.MustFromDecimal(collect0)
100	collectAmount1 := u256.MustFromDecimal(collect1)
101
102	// Slippage check on actually collected amounts to ensure user receives minimum expected tokens
103	if isSlippageExceeded(collectAmount0, collectAmount1, params.amount0Min, params.amount1Min) {
104		return params.positionId,
105			liquidityToRemove.ToString(),
106			fee0Str,
107			fee1Str,
108			collect0,
109			collect1,
110			position.PoolKey(),
111			makeErrorWithDetails(
112				errSlippage,
113				ufmt.Sprintf("collectAmount0(%s) >= amount0Min(%s) && collectAmount1(%s) >= amount1Min(%s)",
114					collectAmount0.ToString(),
115					params.amount0Min.ToString(),
116					collectAmount1.ToString(),
117					params.amount1Min.ToString(),
118				),
119			)
120	}
121
122	// Check for underflow when subtracting collected amounts from tokens owed
123	collectAmount0Int64 := gnsmath.SafeConvertToInt64(collectAmount0)
124	collectAmount1Int64 := gnsmath.SafeConvertToInt64(collectAmount1)
125
126	if position.TokensOwed0() < collectAmount0Int64 {
127		panic(ufmt.Sprintf("[POSITION] burn.gno | collect() | tokensOwed0(%d) < collectAmount0(%d)", position.TokensOwed0(), collectAmount0Int64))
128	}
129	position.SetTokensOwed0(gnsmath.SafeSubInt64(position.TokensOwed0(), collectAmount0Int64))
130
131	if position.TokensOwed1() < collectAmount1Int64 {
132		panic(ufmt.Sprintf("[POSITION] burn.gno | collect() | tokensOwed1(%d) < collectAmount1(%d)", position.TokensOwed1(), collectAmount1Int64))
133	}
134	position.SetTokensOwed1(gnsmath.SafeSubInt64(position.TokensOwed1(), collectAmount1Int64))
135
136	if position.IsClear() {
137		position.SetBurned(true) // just update flag (we don't want to burn actual position)
138	}
139
140	p.mustUpdatePosition(0, rlm, params.positionId, *position)
141
142	return params.positionId, liquidityToRemove.ToString(), fee0Str, fee1Str, collect0, collect1, position.PoolKey(), nil
143}
144
145// calculateFees calculates the fees for the current position.
146func (p *positionV1) calculateFees(position *position.Position, currentFeeGrowth FeeGrowthInside) (int64, int64) {
147	posLiquidity := u256.MustFromDecimal(position.Liquidity())
148	fee0 := calculateTokensOwed(
149		currentFeeGrowth.feeGrowthInside0LastX128,
150		u256.MustFromDecimal(position.FeeGrowthInside0LastX128()),
151		posLiquidity,
152	)
153
154	fee1 := calculateTokensOwed(
155		currentFeeGrowth.feeGrowthInside1LastX128,
156		u256.MustFromDecimal(position.FeeGrowthInside1LastX128()),
157		posLiquidity,
158	)
159
160	return gnsmath.SafeAddInt64(position.TokensOwed0(), gnsmath.SafeConvertToInt64(fee0)), gnsmath.SafeAddInt64(position.TokensOwed1(), gnsmath.SafeConvertToInt64(fee1))
161}
162
163func calculateTokensOwed(
164	feeGrowthInsideLastX128 *u256.Uint,
165	positionFeeGrowthInsideLastX128 *u256.Uint,
166	positionLiquidity *u256.Uint,
167) *u256.Uint {
168	diff := u256.Zero().Sub(feeGrowthInsideLastX128, positionFeeGrowthInsideLastX128)
169	return u256.MulDiv(diff, positionLiquidity, consts.Q128())
170}