Search Apps Documentation Source Content File Folder Download Copy Actions Download

burn.gno

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