position.gno
19.84 Kb ยท 616 lines
1package v1
2
3import (
4 "chain"
5 "chain/runtime"
6 "encoding/base64"
7 "strconv"
8
9 prabc "gno.land/p/gnoswap/rbac"
10 u256 "gno.land/p/gnoswap/uint256"
11 "gno.land/p/nt/ufmt"
12
13 "gno.land/r/gnoswap/access"
14 "gno.land/r/gnoswap/common"
15 "gno.land/r/gnoswap/emission"
16 "gno.land/r/gnoswap/halt"
17 pl "gno.land/r/gnoswap/pool"
18 "gno.land/r/gnoswap/position"
19 pf "gno.land/r/gnoswap/protocol_fee"
20 "gno.land/r/gnoswap/referral"
21)
22
23// Mint creates a new liquidity position NFT.
24//
25// Parameters:
26// - token0, token1: token contract paths
27// - fee: pool fee tier
28// - tickLower, tickUpper: price range boundaries
29// - amount0Desired, amount1Desired: desired token amounts
30// - amount0Min, amount1Min: minimum acceptable amounts
31// - deadline: transaction deadline
32// - mintTo: NFT recipient address
33// - caller: transaction initiator
34// - referrer: referral address
35//
36// Returns tokenId, liquidity, amount0, amount1.
37// Only callable by users or staker contract.
38// Note: Slippage protection via amount0Min/amount1Min.
39func (p *positionV1) Mint(
40 token0 string,
41 token1 string,
42 fee uint32,
43 tickLower int32,
44 tickUpper int32,
45 amount0Desired string,
46 amount1Desired string,
47 amount0Min string,
48 amount1Min string,
49 deadline int64,
50 mintTo address,
51 caller address,
52 referrer string,
53) (uint64, string, string, string) {
54 halt.AssertIsNotHaltedPosition()
55
56 previousRealm := runtime.PreviousRealm()
57 prevRealmAddr := previousRealm.Address()
58 if !previousRealm.IsUser() {
59 access.AssertIsStaker(prevRealmAddr)
60 } else {
61 assertEqualsAddress(prevRealmAddr, mintTo)
62 assertEqualsAddress(prevRealmAddr, caller)
63 }
64
65 assertValidNumberString(amount0Desired)
66 assertValidNumberString(amount1Desired)
67 assertValidNumberString(amount0Min)
68 assertValidNumberString(amount1Min)
69
70 // assert that the user has sent the correct amount of native coin
71 assertIsValidUserCoinSendWithTokenPair(token0, token1, amount0Desired, amount1Desired)
72 assertIsNotExpired(deadline)
73
74 success := referral.TryRegister(cross, caller, referrer)
75 actualReferrer := referrer
76 if !success {
77 actualReferrer = referral.GetReferral(caller.String())
78 }
79
80 emission.MintAndDistributeGns(cross)
81
82 mintInput := MintInput{
83 token0: token0,
84 token1: token1,
85 fee: fee,
86 tickLower: tickLower,
87 tickUpper: tickUpper,
88 amount0Desired: amount0Desired,
89 amount1Desired: amount1Desired,
90 amount0Min: amount0Min,
91 amount1Min: amount1Min,
92 deadline: deadline,
93 mintTo: mintTo,
94 caller: caller,
95 }
96 processedInput, err := p.processMintInput(mintInput)
97 if err != nil {
98 panic(newErrorWithDetail(errInvalidInput, err.Error()))
99 }
100
101 // mint liquidity
102 params := newMintParams(processedInput, mintInput)
103 id, liquidity, amount0, amount1 := p.mint(params)
104
105 // refund leftover wrapped tokens
106 if processedInput.tokenPair.token0IsNative && processedInput.tokenPair.wrappedAmount > safeConvertToInt64(amount0) {
107 err = p.unwrap(processedInput.tokenPair.wrappedAmount-safeConvertToInt64(amount0), caller)
108 if err != nil {
109 panic(newErrorWithDetail(errWrapUnwrap, err.Error()))
110 }
111 }
112
113 if processedInput.tokenPair.token1IsNative && processedInput.tokenPair.wrappedAmount > safeConvertToInt64(amount1) {
114 err = p.unwrap(processedInput.tokenPair.wrappedAmount-safeConvertToInt64(amount1), caller)
115 if err != nil {
116 panic(newErrorWithDetail(errWrapUnwrap, err.Error()))
117 }
118 }
119
120 poolSqrtPriceX96 := pl.GetSlot0SqrtPriceX96(processedInput.poolPath)
121
122 tickCumulative, liquidityCumulative, secondsPerLiquidityCumulativeX128, observationTimestamp :=
123 pl.GetObservation(processedInput.poolPath, 0)
124
125 chain.Emit(
126 "Mint",
127 "prevAddr", prevRealmAddr.String(),
128 "prevRealm", previousRealm.PkgPath(),
129 "tickLower", formatInt(processedInput.tickLower),
130 "tickUpper", formatInt(processedInput.tickUpper),
131 "poolPath", processedInput.poolPath,
132 "mintTo", mintTo.String(),
133 "caller", caller.String(),
134 "lpPositionId", formatUint(id),
135 "liquidityDelta", liquidity.ToString(),
136 "amount0", amount0.ToString(),
137 "amount1", amount1.ToString(),
138 "sqrtPriceX96", poolSqrtPriceX96.ToString(),
139 "positionLiquidity", p.GetPositionLiquidity(id).ToString(),
140 "poolLiquidity", pl.GetLiquidity(processedInput.poolPath),
141 "token0Balance", pl.GetBalanceToken0(processedInput.poolPath),
142 "token1Balance", pl.GetBalanceToken1(processedInput.poolPath),
143 "tickCumulative", formatInt(tickCumulative),
144 "liquidityCumulative", liquidityCumulative,
145 "secondsPerLiquidityCumulativeX128", secondsPerLiquidityCumulativeX128,
146 "observationTimestamp", formatInt(observationTimestamp),
147 "referrer", actualReferrer,
148 )
149
150 return id, liquidity.ToString(), amount0.ToString(), amount1.ToString()
151}
152
153// IncreaseLiquidity increases liquidity of an existing position.
154//
155// Adds more liquidity to existing NFT position.
156// Maintains same price range as original position.
157// Calculates optimal token ratio for current price.
158//
159// Parameters:
160// - positionId: NFT token ID to increase
161// - amount0DesiredStr: Desired token0 amount
162// - amount1DesiredStr: Desired token1 amount
163// - amount0MinStr: Minimum token0 (slippage protection)
164// - amount1MinStr: Minimum token1 (slippage protection)
165// - deadline: Transaction expiration timestamp
166//
167// Returns:
168// - positionId: Same NFT ID
169// - liquidity: Liquidity amount added (the delta, not total)
170// - amount0: Token0 actually deposited
171// - amount1: Token1 actually deposited
172// - poolPath: Pool identifier
173//
174// Requirements:
175// - Caller must own the position NFT
176// - Sufficient token balances and approvals
177func (p *positionV1) IncreaseLiquidity(
178 positionId uint64,
179 amount0DesiredStr string,
180 amount1DesiredStr string,
181 amount0MinStr string,
182 amount1MinStr string,
183 deadline int64,
184) (uint64, string, string, string, string) {
185 halt.AssertIsNotHaltedPosition()
186
187 caller := runtime.PreviousRealm().Address()
188 assertIsOwnerForToken(p, positionId, caller)
189
190 assertValidNumberString(amount0DesiredStr)
191 assertValidNumberString(amount1DesiredStr)
192 assertValidNumberString(amount0MinStr)
193 assertValidNumberString(amount1MinStr)
194 assertIsNotExpired(deadline)
195
196 emission.MintAndDistributeGns(cross)
197
198 position := p.mustGetPosition(positionId)
199 token0, token1, _ := splitOf(position.PoolKey())
200
201 // assert that the user has sent the correct amount of native coin
202 assertIsValidUserCoinSendWithWrappedTokenPair(token0, token1, amount0DesiredStr, amount1DesiredStr)
203
204 receivedCoins := common.ExistsUserSendCoins()
205
206 err := validateTokenPath(token0, token1)
207 if err != nil {
208 panic(newErrorWithDetail(err, ufmt.Sprintf("token0(%s), token1(%s)", token0, token1)))
209 }
210
211 wrappedAmount := int64(0)
212
213 if receivedCoins {
214 inputWrappedAmount := int64(0)
215
216 if common.IsGNOTPath(token0) {
217 inputWrappedAmount = mustParseInt64(amount0DesiredStr)
218 } else if common.IsGNOTPath(token1) {
219 inputWrappedAmount = mustParseInt64(amount1DesiredStr)
220 }
221
222 wrappedAmount, err = p.safeWrapNativeToken(inputWrappedAmount, caller)
223 if err != nil {
224 panic(err)
225 }
226 }
227
228 amount0Desired, amount1Desired, amount0Min, amount1Min := parseAmounts(amount0DesiredStr, amount1DesiredStr, amount0MinStr, amount1MinStr)
229 increaseLiquidityParams := IncreaseLiquidityParams{
230 positionId: positionId,
231 amount0Desired: amount0Desired,
232 amount1Desired: amount1Desired,
233 amount0Min: amount0Min,
234 amount1Min: amount1Min,
235 deadline: deadline,
236 caller: caller,
237 }
238
239 _, liquidity, amount0, amount1, poolPath, err := p.increaseLiquidity(increaseLiquidityParams)
240 if err != nil {
241 panic(err)
242 }
243
244 if err := p.unwrapLeftoverWrappedToken(token0, wrappedAmount, safeConvertToInt64(amount0), caller); err != nil {
245 panic(err)
246 }
247 if err := p.unwrapLeftoverWrappedToken(token1, wrappedAmount, safeConvertToInt64(amount1), caller); err != nil {
248 panic(err)
249 }
250
251 tickCumulative, liquidityCumulative, secondsPerLiquidityCumulativeX128, observationTimestamp :=
252 pl.GetObservation(poolPath, 0)
253
254 previousRealm := runtime.PreviousRealm()
255 chain.Emit(
256 "IncreaseLiquidity",
257 "prevAddr", previousRealm.Address().String(),
258 "prevRealm", previousRealm.PkgPath(),
259 "poolPath", poolPath,
260 "caller", caller.String(),
261 "lpPositionId", formatUint(positionId),
262 "liquidityDelta", liquidity.ToString(),
263 "amount0", amount0.ToString(),
264 "amount1", amount1.ToString(),
265 "sqrtPriceX96", pl.GetSlot0SqrtPriceX96(poolPath).ToString(),
266 "positionLiquidity", p.GetPositionLiquidity(positionId).ToString(),
267 "poolLiquidity", pl.GetLiquidity(poolPath),
268 "token0Balance", pl.GetBalanceToken0(poolPath),
269 "token1Balance", pl.GetBalanceToken1(poolPath),
270 "tickCumulative", formatInt(tickCumulative),
271 "liquidityCumulative", liquidityCumulative,
272 "secondsPerLiquidityCumulativeX128", secondsPerLiquidityCumulativeX128,
273 "observationTimestamp", formatInt(observationTimestamp),
274 )
275
276 return positionId, liquidity.ToString(), amount0.ToString(), amount1.ToString(), poolPath
277}
278
279// unwrapLeftoverWrappedToken unwraps leftover wrapped tokens to native tokens.
280func (p *positionV1) unwrapLeftoverWrappedToken(token string, wrapped, amount int64, caller address) error {
281 unwrappable := isWrappedToken(token) && wrapped > amount
282 if !unwrappable {
283 return nil
284 }
285
286 err := p.unwrap(wrapped-amount, caller)
287 if err != nil {
288 return makeErrorWithDetails(errWrapUnwrap, err.Error())
289 }
290
291 return nil
292}
293
294// DecreaseLiquidity decreases liquidity of an existing position.
295//
296// Removes liquidity but keeps NFT ownership.
297// Calculates tokens owed based on current price.
298// Two-step: decrease then collect tokens.
299//
300// Parameters:
301// - positionId: NFT token ID
302// - liquidityStr: Amount of liquidity to remove
303// - amount0MinStr: Min token0 to receive (slippage)
304// - amount1MinStr: Min token1 to receive (slippage)
305// - deadline: Transaction expiration
306// - unwrapResult: Convert WUGNOT to GNOT if true
307//
308// Returns:
309// - positionId: Same NFT ID
310// - liquidity: Amount of liquidity removed (the delta)
311// - fee0, fee1: Fees collected
312// - amount0, amount1: Principal collected
313// - poolPath: Pool identifier
314//
315// Note: Applies withdrawal fee on collected amounts.
316func (p *positionV1) DecreaseLiquidity(
317 positionId uint64,
318 liquidityStr string,
319 amount0MinStr string,
320 amount1MinStr string,
321 deadline int64,
322 unwrapResult bool,
323) (uint64, string, string, string, string, string, string) {
324 halt.AssertIsNotHaltedPosition()
325 halt.AssertIsNotHaltedWithdraw()
326
327 caller := runtime.PreviousRealm().Address()
328 assertIsOwnerForToken(p, positionId, caller)
329 assertIsNotExpired(deadline)
330 assertValidLiquidityAmount(liquidityStr)
331
332 emission.MintAndDistributeGns(cross)
333
334 amount0Min := u256.MustFromDecimal(amount0MinStr)
335 amount1Min := u256.MustFromDecimal(amount1MinStr)
336 decreaseLiquidityParams := DecreaseLiquidityParams{
337 positionId: positionId,
338 liquidity: liquidityStr,
339 amount0Min: amount0Min,
340 amount1Min: amount1Min,
341 deadline: deadline,
342 unwrapResult: unwrapResult,
343 caller: caller,
344 }
345
346 positionId, liquidity, fee0, fee1, amount0, amount1, poolPath, err := p.decreaseLiquidity(decreaseLiquidityParams)
347 if err != nil {
348 panic(err)
349 }
350
351 tickCumulative, liquidityCumulative, secondsPerLiquidityCumulativeX128, observationTimestamp :=
352 pl.GetObservation(poolPath, 0)
353
354 previousRealm := runtime.PreviousRealm()
355 chain.Emit(
356 "DecreaseLiquidity",
357 "prevAddr", previousRealm.Address().String(),
358 "prevRealm", previousRealm.PkgPath(),
359 "lpPositionId", formatUint(positionId),
360 "poolPath", poolPath,
361 "liquidityDelta", liquidity,
362 "feeAmount0", fee0,
363 "feeAmount1", fee1,
364 "amount0", amount0,
365 "amount1", amount1,
366 "unwrapResult", formatBool(unwrapResult),
367 "sqrtPriceX96", pl.GetSlot0SqrtPriceX96(poolPath).ToString(),
368 "positionLiquidity", p.GetPositionLiquidity(positionId).ToString(),
369 "poolLiquidity", pl.GetLiquidity(poolPath),
370 "token0Balance", pl.GetBalanceToken0(poolPath),
371 "token1Balance", pl.GetBalanceToken1(poolPath),
372 "tickCumulative", formatInt(tickCumulative),
373 "liquidityCumulative", liquidityCumulative,
374 "secondsPerLiquidityCumulativeX128", secondsPerLiquidityCumulativeX128,
375 "observationTimestamp", formatInt(observationTimestamp),
376 )
377
378 return positionId, liquidity, fee0, fee1, amount0, amount1, poolPath
379}
380
381// CollectFee collects swap fee from the position.
382//
383// Claims accumulated fees without removing liquidity.
384// Useful for active positions earning ongoing fees.
385// Applies protocol withdrawal fee.
386//
387// Parameters:
388// - positionId: NFT token ID
389// - unwrapResult: Convert WUGNOT to GNOT if true
390//
391// Returns:
392// - positionId: Same NFT ID
393// - tokensCollected0: Token0 amount sent to caller (after withdrawal fee)
394// - tokensCollected1: Token1 amount sent to caller (after withdrawal fee)
395// - poolPath: Pool identifier
396// - totalAmount0: Raw token0 collected (before withdrawal fee)
397// - totalAmount1: Raw token1 collected (before withdrawal fee)
398//
399// Requirements:
400// - Caller must be owner or approved operator
401// - Position must have accumulated fees
402func (p *positionV1) CollectFee(
403 positionId uint64,
404 unwrapResult bool,
405) (uint64, string, string, string, string, string) {
406 halt.AssertIsNotHaltedPosition()
407 halt.AssertIsNotHaltedWithdraw()
408
409 caller := runtime.PreviousRealm().Address()
410 assertIsOwnerOrOperatorForToken(p, positionId, caller)
411
412 emission.MintAndDistributeGns(cross)
413
414 return p.collectFee(positionId, unwrapResult, caller)
415}
416
417// collectFee performs fee collection and withdrawal fee calculation.
418func (p *positionV1) collectFee(
419 positionId uint64,
420 unwrapResult bool,
421 caller address,
422) (uint64, string, string, string, string, string) {
423 // verify position
424 position := p.mustGetPosition(positionId)
425 token0, token1, fee := splitOf(position.PoolKey())
426
427 pl.Burn(
428 cross,
429 token0,
430 token1,
431 fee,
432 position.TickLower(),
433 position.TickUpper(),
434 "0", // burn '0' liquidity to collect fee
435 caller,
436 )
437
438 currentFeeGrowth, err := p.getCurrentFeeGrowth(position, caller)
439 if err != nil {
440 panic(newErrorWithDetail(err, "failed to get current fee growth"))
441 }
442
443 tokensOwed0, tokensOwed1 := p.calculateFees(position, currentFeeGrowth)
444
445 position.SetFeeGrowthInside0LastX128(u256.Zero().Set(currentFeeGrowth.feeGrowthInside0LastX128))
446 position.SetFeeGrowthInside1LastX128(u256.Zero().Set(currentFeeGrowth.feeGrowthInside1LastX128))
447
448 // collect fee
449 amount0, amount1 := pl.Collect(
450 cross,
451 token0, token1, fee,
452 caller,
453 position.TickLower(), position.TickUpper(),
454 tokensOwed0.ToString(), tokensOwed1.ToString(),
455 )
456 amount0Uint256 := u256.MustFromDecimal(amount0)
457 amount1Uint256 := u256.MustFromDecimal(amount1)
458
459 // sometimes there will be a few less uBase amount than expected due to rounding down in core, but we just subtract the full amount expected
460 // instead of the actual amount so we can burn the token
461 tokensOwed0Updated, underflow := u256.Zero().SubOverflow(tokensOwed0, amount0Uint256)
462 if underflow {
463 panic(newErrorWithDetail(errUnderflow, "tokensOwed0 - amount0 underflow"))
464 }
465 position.SetTokensOwed0(tokensOwed0Updated)
466
467 tokensOwed1Updated, underflow := u256.Zero().SubOverflow(tokensOwed1, amount1Uint256)
468 if underflow {
469 panic(newErrorWithDetail(errUnderflow, "tokensOwed1 - amount1 underflow"))
470 }
471 position.SetTokensOwed1(tokensOwed1Updated)
472 p.mustUpdatePosition(positionId, *position)
473
474 withdrawalFeeBps := pl.GetWithdrawalFee()
475 amount0WithoutFee, fee0 := calculateAmountWithWithdrawalFee(amount0Uint256, withdrawalFeeBps)
476 amount1WithoutFee, fee1 := calculateAmountWithWithdrawalFee(amount1Uint256, withdrawalFeeBps)
477
478 poolAddr := access.MustGetAddress(prabc.ROLE_POOL.String())
479
480 if isWrappedToken(token0) && unwrapResult {
481 p.unwrapWithTransferFrom(poolAddr, caller, amount0WithoutFee)
482 } else {
483 common.SafeGRC20TransferFrom(cross, token0, poolAddr, caller, amount0WithoutFee)
484 }
485
486 if isWrappedToken(token1) && unwrapResult {
487 p.unwrapWithTransferFrom(poolAddr, caller, amount1WithoutFee)
488 } else {
489 common.SafeGRC20TransferFrom(cross, token1, poolAddr, caller, amount1WithoutFee)
490 }
491
492 protocolFeeAddr := access.MustGetAddress(prabc.ROLE_PROTOCOL_FEE.String())
493
494 // handle withdrawal protocol fee
495 pf.AddToProtocolFee(cross, token0, fee0)
496 common.SafeGRC20TransferFrom(cross, token0, poolAddr, protocolFeeAddr, fee0)
497
498 pf.AddToProtocolFee(cross, token1, fee1)
499 common.SafeGRC20TransferFrom(cross, token1, poolAddr, protocolFeeAddr, fee1)
500
501 amount0WithoutFeeStr := formatInt(amount0WithoutFee)
502 amount1WithoutFeeStr := formatInt(amount1WithoutFee)
503
504 previousRealm := runtime.PreviousRealm()
505 chain.Emit(
506 "CollectSwapFee",
507 "prevAddr", previousRealm.Address().String(),
508 "prevRealm", previousRealm.PkgPath(),
509 "lpPositionId", formatUint(positionId),
510 "feeAmount0", amount0WithoutFeeStr,
511 "feeAmount1", amount1WithoutFeeStr,
512 "poolPath", position.PoolKey(),
513 "unwrapResult", formatBool(unwrapResult),
514 "feeGrowthInside0LastX128", position.FeeGrowthInside0LastX128().ToString(),
515 "feeGrowthInside1LastX128", position.FeeGrowthInside1LastX128().ToString(),
516 )
517
518 chain.Emit(
519 "WithdrawalFee",
520 "prevAddr", previousRealm.Address().String(),
521 "prevRealm", previousRealm.PkgPath(),
522 "lpTokenId", formatUint(positionId),
523 "poolPath", position.PoolKey(),
524 "feeAmount0", formatInt(fee0),
525 "feeAmount1", formatInt(fee1),
526 "amount0WithoutFee", amount0WithoutFeeStr,
527 "amount1WithoutFee", amount1WithoutFeeStr,
528 )
529
530 return positionId, amount0WithoutFeeStr, amount1WithoutFeeStr, position.PoolKey(), amount0, amount1
531}
532
533// calculateAmountWithWithdrawalFee calculates amount after deducting withdrawal fee.
534func calculateAmountWithWithdrawalFee(amount *u256.Uint, fee uint64) (int64, int64) {
535 if fee == 0 {
536 return safeConvertToInt64(amount), 0
537 }
538
539 feeAmount, overflow := u256.Zero().MulOverflow(amount, u256.NewUint(fee))
540 if overflow {
541 panic(errOverflow)
542 }
543 feeAmount = u256.Zero().Div(feeAmount, u256.NewUint(10000))
544 amountWithoutFee := u256.Zero().Sub(amount, feeAmount)
545
546 return safeConvertToInt64(amountWithoutFee), safeConvertToInt64(feeAmount)
547}
548
549// SetPositionOperator sets an operator for a position.
550// Only staker can call this function.
551func (p *positionV1) SetPositionOperator(
552 id uint64,
553 operator address,
554) {
555 halt.AssertIsNotHaltedPosition()
556
557 access.AssertIsStaker(runtime.PreviousRealm().Address())
558
559 assertValidOperatorAddress(operator)
560
561 position := p.mustGetPosition(id)
562 prevOperator := position.Operator()
563 position.SetOperator(operator)
564
565 p.mustUpdatePosition(id, *position)
566
567 previousRealm := runtime.PreviousRealm()
568 chain.Emit(
569 "SetPositionOperator",
570 "prevAddr", previousRealm.Address().String(),
571 "prevRealm", previousRealm.PkgPath(),
572 "lpPositionId", formatUint(id),
573 "prevOperator", prevOperator.String(),
574 "newOperator", operator.String(),
575 )
576}
577
578// getCurrentFeeGrowth retrieves current fee growth values for a position.
579func (p *positionV1) getCurrentFeeGrowth(position *position.Position, owner address) (FeeGrowthInside, error) {
580 positionKey := computePositionKey(position.TickLower(), position.TickUpper())
581 feeGrowthInside0LastX128, feeGrowthInside1LastX128 := pl.GetPositionFeeGrowthInsideLastX128(position.PoolKey(), positionKey)
582
583 feeGrowthInside := FeeGrowthInside{
584 feeGrowthInside0LastX128: feeGrowthInside0LastX128,
585 feeGrowthInside1LastX128: feeGrowthInside1LastX128,
586 }
587
588 return feeGrowthInside, nil
589}
590
591// computePositionKey generates a unique base64-encoded key for a liquidity position.
592func computePositionKey(
593 tickLower int32,
594 tickUpper int32,
595) string {
596 currentRealmPath := runtime.CurrentRealm().PkgPath()
597 key := currentRealmPath + "__" + strconv.Itoa(int(tickLower)) + "__" + strconv.Itoa(int(tickUpper))
598 encoded := base64.StdEncoding.EncodeToString([]byte(key))
599 return encoded
600}
601
602// calculatePositionBalances computes token balances for a position at current price.
603// Returns calculated token0 and token1 balances based on position liquidity and price range.
604func calculatePositionBalances(position *position.Position) (*u256.Uint, *u256.Uint) {
605 liquidity := position.Liquidity()
606 if liquidity.IsZero() {
607 return u256.Zero(), u256.Zero()
608 }
609
610 return common.GetAmountsForLiquidity(
611 pl.GetSlot0SqrtPriceX96(position.PoolKey()), // currentSqrtPriceX96
612 common.TickMathGetSqrtRatioAtTick(position.TickLower()),
613 common.TickMathGetSqrtRatioAtTick(position.TickUpper()),
614 liquidity,
615 )
616}