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

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