Kind Realm (renderable)
Name v1
Namespace gnoswap / pool
Exported functions 3
Module gno.land/r/gnoswap/pool/v1
gno 0.9 position.gno gno
⧉
1 package pool 2 3 import ( 4 "errors" 5 "time" 6 7 "gno.land/p/gnoswap/gnsmath/v1" 8 GetPoolPath (token0Path string, token1Path string, fee uint32) string
NewPoolV1 (store interface {GetFeeAmountTickSpacing func() map[uint32]int32; GetObservations func() *gno.land/p/nt/bptree/v0.BPTree; GetPendingProtocolFee func(string) int64; GetPendingProtocolFees func() map[string]int64; GetPoolCreationFee func() int64; GetPools func() *gno.land/p/nt/bptree/v0.BPTree; GetSlot0FeeProtocol func() uint8; GetSwapEndHook func() func(.uverse.realm, string) .uverse.error; GetSwapStartHook func() func(.uverse.realm, string, int64); GetTickCrossHook func() func(.uverse.realm, string, int32, bool, int64); GetUnlocked func() bool; GetWithdrawalFeeBPS func() uint64; HasFeeAmountTickSpacing func() bool; HasObservations func() bool; HasPendingProtocolFees func() bool; HasPoolCreationFee func() bool; HasPools func() bool; HasSlot0FeeProtocol func() bool; HasSwapEndHook func() bool; HasSwapStartHook func() bool; HasTickCrossHook func() bool; HasUnlocked func() bool; HasWithdrawalFeeBPS func() bool; RemovePendingProtocolFee func(int, .uverse.realm, string) .uverse.error; SetFeeAmountTickSpacing func(int, .uverse.realm, map[uint32]int32) .uverse.error; SetObservations func(int, .uverse.realm, *gno.land/p/nt/bptree/v0.BPTree) .uverse.error; SetPendingProtocolFee func(int, .uverse.realm, string, int64) .uverse.error; SetPendingProtocolFees func(int, .uverse.realm, map[string]int64) .uverse.error; SetPoolCreationFee func(int, .uverse.realm, int64) .uverse.error; SetPools func(int, .uverse.realm, *gno.land/p/nt/bptree/v0.BPTree) .uverse.error; SetSlot0FeeProtocol func(int, .uverse.realm, uint8) .uverse.error; SetSwapEndHook func(int, .uverse.realm, func(.uverse.realm, string) .uverse.error) .uverse.error; SetSwapStartHook func(int, .uverse.realm, func(.uverse.realm, string, int64)) .uverse.error; SetTickCrossHook func(int, .uverse.realm, func(.uverse.realm, string, int32, bool, int64)) .uverse.error; SetUnlocked func(int, .uverse.realm, bool) .uverse.error; SetWithdrawalFeeBPS func(int, .uverse.realm, uint64) .uverse.error}) interface {Burn func(int, .uverse.realm, string, string, uint32, int32, int32, string, .uverse.address) (string, string); Collect func(int, .uverse.realm, string, string, uint32, .uverse.address, int32, int32, string, string) (string, string); CollectProtocol func(int, .uverse.realm, string, string, uint32, .uverse.address, string, string) (string, string); CollectSwapFee func(int, .uverse.realm, string, string, uint32, .uverse.address, int32, int32, string, string) (string, string, string, string); CreatePool func(int, .uverse.realm, string, string, uint32, string); DrySwap func(string, string, uint32, bool, string, string) (string, string, .uverse.error); ExistsPoolPath func(string) bool; GetBalanceToken0 func(string) (int64, .uverse.error); GetBalanceToken1 func(string) (int64, .uverse.error); GetFee func(string) (uint32, .uverse.error); GetFeeAmountTickSpacing func(uint32) (int32, .uverse.error); GetFeeAmountTickSpacings func() map[uint32]int32; GetFeeGrowthGlobal0X128 func(string) (*gno.land/p/gnoswap/uint256/v1.Uint, .uverse.error); GetFeeGrowthGlobal1X128 func(string) (*gno.land/p/gnoswap/uint256/v1.Uint, .uverse.error); GetFeeGrowthGlobalX128 func(string) (*gno.land/p/gnoswap/uint256/v1.Uint, *gno.land/p/gnoswap/uint256/v1.Uint, .uverse.error); GetInitializedTicksInRange func(string, int32, int32) ([]int32, .uverse.error); GetLiquidity func(string) (*gno.land/p/gnoswap/uint256/v1.Uint, .uverse.error); GetObservationAt func(string, uint16) (gno.land/r/gnoswap/pool.Observation, .uverse.error); GetPendingProtocolFees func() map[string]int64; GetPoolCreationFee func() int64; GetPoolPositions func(string) *gno.land/p/nt/bptree/rotree/v0.ReadOnlyTree; GetPools func() *gno.land/p/nt/bptree/rotree/v0.ReadOnlyTree; GetPositionFeeGrowthInside0LastX128 func(string, string) (string, .uverse.error); GetPositionFeeGrowthInside1LastX128 func(string, string) (string, .uverse.error); GetPositionFeeGrowthInsideLastX128 func(string, string) (string, string, .uverse.error); GetPositionLiquidity func(string, string) (string, .uverse.error); GetPositionTokensOwed0 func(string, string) (int64, .uverse.error); GetPositionTokensOwed1 func(string, string) (int64, .uverse.error); GetProtocolFeesToken0 func(string) (int64, .uverse.error); GetProtocolFeesToken1 func(string) (int64, .uverse.error); GetSlot0 func(string) gno.land/r/gnoswap/pool.Slot0; GetSlot0FeeProtocol func(string) (uint8, .uverse.error); GetSlot0SqrtPriceX96 func(string) (*gno.land/p/gnoswap/uint256/v1.Uint, .uverse.error); GetSlot0Tick func(string) (int32, .uverse.error); GetSlot0Unlocked func(string) (bool, .uverse.error); GetTickBitmaps func(string, int16) (string, .uverse.error); GetTickCumulativeOutside func(string, int32) (int64, .uverse.error); GetTickFeeGrowthOutside0X128 func(string, int32) (string, .uverse.error); GetTickFeeGrowthOutside1X128 func(string, int32) (string, .uverse.error); GetTickFeeGrowthOutsideX128 func(string, int32) (string, string, .uverse.error); GetTickInfo func(string, int32) (gno.land/r/gnoswap/pool.TickInfo, .uverse.error); GetTickInitialized func(string, int32) (bool, .uverse.error); GetTickLiquidityGross func(string, int32) (string, .uverse.error); GetTickLiquidityNet func(string, int32) (string, .uverse.error); GetTickSecondsOutside func(string, int32) (uint32, .uverse.error); GetTickSecondsPerLiquidityOutsideX128 func(string, int32) (string, .uverse.error); GetTickSpacing func(string) (int32, .uverse.error); GetToken0Path func(string) (string, .uverse.error); GetToken1Path func(string) (string, .uverse.error); GetWithdrawalFee func() uint64; IncreaseObservationCardinalityNext func(int, .uverse.realm, string, string, uint32, uint16); Mint func(int, .uverse.realm, string, string, uint32, int32, int32, string, .uverse.address) (string, string); Observe func(string, []uint32) ([]int64, []string, .uverse.error); OracleConsult func(string, uint32) (int32, *gno.land/p/gnoswap/uint256/v1.Uint, .uverse.error); Render func(string) string; SetFeeProtocol func(int, .uverse.realm, uint8, uint8); SetPoolCreationFee func(int, .uverse.realm, int64); SetSwapEndHook func(int, .uverse.realm, func(.uverse.realm, string) .uverse.error); SetSwapStartHook func(int, .uverse.realm, func(.uverse.realm, string, int64)); SetTickCrossHook func(int, .uverse.realm, func(.uverse.realm, string, int32, bool, int64)); SetWithdrawalFee func(int, .uverse.realm, uint64); SnapshotCumulativesInside func(string, int32, int32) (int64, *gno.land/p/gnoswap/uint256/v1.Uint, uint32, .uverse.error); Swap func(int, .uverse.realm, string, string, uint32, .uverse.address, bool, string, string, func(.uverse.realm, int64, int64, *gno.land/r/gnoswap/pool.CallbackMarker) .uverse.error) (string, string)}
NewTickEventInfo (tickID int32, tickInfo struct{liquidityGross string; liquidityNet string; feeGrowthOutside0X128 string; feeGrowthOutside1X128 string; tickCumulativeOutside int64; secondsPerLiquidityOutsideX128 string; secondsOutside uint32; initialized bool}) *gno.land/r/gnoswap/pool/v1.tickEventInfo
Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.
This realm does not provide renderable output (vm/qrender → NoRenderDeclError).
View on gnoweb ↗ vm/qrender output, sanitized (docs/render-security.md) and displayed in an empty-sandbox iframe — scripts, forms and popups cannot run. Links stay inert in-preview; right-click to open.
i256 "gno.land/p/gnoswap/int256/v1"
9 u256 "gno.land/p/gnoswap/uint256/v1"
10 ufmt "gno.land/p/nt/ufmt/v0"
11
12 pl "gno.land/r/gnoswap/pool"
13 )
14
15 // getPositionKey generates a compact deterministic key for a liquidity position.
16 //
17 // Creates deterministic identifier for position tracking.
18 // Ensures unique positions per price range while preserving lexical ordering.
19 //
20 // Used internally for position state management.
21 //
22 // Parameters:
23 // - tickLower: Lower boundary tick of position range
24 // - tickUpper: Upper boundary tick of position range
25 //
26 // Key Format: EncodePositionKey(tickLower, tickUpper)
27 func getPositionKey(
28 tickLower int32 ,
29 tickUpper int32 ,
30 ) string {
31 return pl.EncodePositionKey(tickLower, tickUpper)
32 }
33
34 // positionUpdate updates a position's liquidity and calculates fees owed.
35 // Returns the updated position information and any error.
36 func positionUpdate(
37 position pl.PositionInfo,
38 liquidityDelta *i256.Int,
39 feeGrowthInside0X128 *u256.Uint,
40 feeGrowthInside1X128 *u256.Uint,
41 ) (pl.PositionInfo, error ) {
42 isZeroLiquidityDelta := liquidityDelta.IsZero()
43
44 posLiquidity := u256.MustFromDecimal(position.Liquidity())
45 if posLiquidity.IsZero() && isZeroLiquidityDelta {
46 return pl.NewDefaultPositionInfo(), makeErrorWithDetails(
47 errZeroLiquidity,
48 "both liquidityDelta and current position's liquidity are zero" ,
49 )
50 }
51
52 if liquidityDelta.IsNeg() {
53 absDelta := i256.Zero().Set(liquidityDelta).Abs()
54 if absDelta.Gt(posLiquidity) {
55 return pl.NewDefaultPositionInfo(), makeErrorWithDetails(
56 errZeroLiquidity,
57 ufmt.Sprintf( "liquidity delta(%s) is greater than current liquidity(%s)" ,
58 liquidityDelta.ToString(), posLiquidity.ToString()),
59 )
60 }
61 }
62
63 var liquidityNext *u256.Uint
64 if isZeroLiquidityDelta {
65 liquidityNext = posLiquidity
66 } else {
67 liquidityNext = gnsmath.LiquidityMathAddDelta(posLiquidity, liquidityDelta)
68 }
69
70 feeGrowthLast0 := u256.MustFromDecimal(position.FeeGrowthInside0LastX128())
71 feeGrowthLast1 := u256.MustFromDecimal(position.FeeGrowthInside1LastX128())
72
73 diff0 := u256.Zero().Sub(feeGrowthInside0X128, feeGrowthLast0)
74 diff1 := u256.Zero().Sub(feeGrowthInside1X128, feeGrowthLast1)
75
76 tokensOwed0 := u256.Zero()
77 if !diff0.IsZero() {
78 tokensOwed0 = u256.MulDiv(diff0, posLiquidity, q128FromDecimal)
79 }
80
81 tokensOwed1 := u256.Zero()
82 if !diff1.IsZero() {
83 tokensOwed1 = u256.MulDiv(diff1, posLiquidity, q128FromDecimal)
84 }
85
86 if !isZeroLiquidityDelta {
87 position.SetLiquidity(liquidityNext.ToString())
88 }
89
90 position.SetFeeGrowthInside0LastX128(feeGrowthInside0X128.ToString())
91 position.SetFeeGrowthInside1LastX128(feeGrowthInside1X128.ToString())
92
93 if tokensOwed0.Gt(zero) || tokensOwed1.Gt(zero) {
94 owed0 := gnsmath.SafeAddInt64(position.TokensOwed0(), gnsmath.SafeConvertToInt64(tokensOwed0))
95 owed1 := gnsmath.SafeAddInt64(position.TokensOwed1(), gnsmath.SafeConvertToInt64(tokensOwed1))
96
97 position.SetTokensOwed0(owed0)
98 position.SetTokensOwed1(owed1)
99 }
100
101 return position, nil
102 }
103
104 // calculateToken0Amount calculates the amount of token0 based on price range and liquidity delta.
105 func calculateToken0Amount(sqrtPriceLower, sqrtPriceUpper *u256.Uint, liquidityDelta *i256.Int) *i256.Int {
106 return gnsmath.GetAmount0Delta(sqrtPriceLower, sqrtPriceUpper, liquidityDelta)
107 }
108
109 // calculateToken1Amount calculates the amount of token1 based on price range and liquidity delta.
110 func calculateToken1Amount(sqrtPriceLower, sqrtPriceUpper *u256.Uint, liquidityDelta *i256.Int) *i256.Int {
111 return gnsmath.GetAmount1Delta(sqrtPriceLower, sqrtPriceUpper, liquidityDelta)
112 }
113
114 // positionUpdateWithKey updates a position in the pool and returns the updated position.
115 func positionUpdateWithKey(
116 p *pl.Pool,
117 positionKey string ,
118 liquidityDelta *i256.Int,
119 feeGrowthInside0X128, feeGrowthInside1X128 *u256.Uint,
120 ) (pl.PositionInfo, error ) {
121 // if position does not exist, create a new position
122 //
123 // Note: The positionUpdate function is designed to handle both new positions and existing positions,
124 // so there's no need to check for existence in GetPosition.
125 positionToUpdate, err := p.GetPosition(positionKey)
126 if err != nil {
127 positionToUpdate = pl.NewDefaultPositionInfo()
128 }
129
130 positionAfterUpdate, err := positionUpdate(positionToUpdate, liquidityDelta, feeGrowthInside0X128, feeGrowthInside1X128)
131 if err != nil {
132 return pl.NewDefaultPositionInfo(), err
133 }
134
135 setPosition(p, positionKey, positionAfterUpdate)
136
137 return positionAfterUpdate, nil
138 }
139
140 // setPosition sets the position info for a given key.
141 func setPosition(p *pl.Pool, posKey string , positionInfo pl.PositionInfo) {
142 p.SetPosition(posKey, positionInfo)
143 }
144
145 // modifyPosition updates a position in the pool and calculates the amount of tokens
146 // needed (for minting) or returned (for burning). The calculation depends on the current
147 // price (tick) relative to the position's price range.
148 //
149 // The function handles three cases:
150 // 1. Current price below range (tick < tickLower): only token0 is used/returned
151 // 2. Current price in range (tickLower <= tick < tickUpper): both tokens are used/returned
152 // 3. Current price above range (tick >= tickUpper): only token1 is used/returned
153 //
154 // Parameters:
155 // - params: ModifyPositionParams containing owner, tickLower, tickUpper, and liquidityDelta
156 //
157 // Returns:
158 // - PositionInfo: updated position information
159 // - *u256.Uint: amount of token0 needed/returned
160 // - *u256.Uint: amount of token1 needed/returned
161 func modifyPosition(p *pl.Pool, observations *pl.ObservationTree, params ModifyPositionParams) (pl.PositionInfo, *u256.Uint, *u256.Uint, error ) {
162 if err := validateTicks(params.tickLower, params.tickUpper); err != nil {
163 return pl.NewDefaultPositionInfo(), zero, zero, err
164 }
165
166 // get current state and price bounds
167 tick := p.Slot0Tick()
168 // update position state
169 position, err := updatePosition(p, observations, params, tick)
170 if err != nil {
171 return pl.NewDefaultPositionInfo(), zero, zero, err
172 }
173
174 liqDelta := params.liquidityDelta
175 if liqDelta.IsZero() {
176 return position, zero, zero, nil
177 }
178
179 amount0, amount1 := i256.Zero(), i256.Zero()
180
181 // covert ticks to sqrt price to use in amount calculations
182 // price = 1.0001^tick, but we use sqrtPriceX96
183 sqrtRatioLower := gnsmath.TickMathGetSqrtRatioAtTick(params.tickLower)
184 sqrtRatioUpper := gnsmath.TickMathGetSqrtRatioAtTick(params.tickUpper)
185 sqrtPriceX96 := p.Slot0SqrtPriceX96()
186
187 // calculate token amounts based on current price position relative to range
188 switch {
189 case tick < params.tickLower:
190 // case 1
191 // full range between lower and upper tick is used for token0
192 // current tick is below the passed range; liquidity can only become in range by crossing from left to
193 // right, when we'll need _more_ token0 (it's becoming more valuable) so user must provide it
194 amount0 = calculateToken0Amount(sqrtRatioLower, sqrtRatioUpper, liqDelta)
195
196 case tick < params.tickUpper:
197 // case 2: Current price is within the position range
198 liquidityBefore := p.Liquidity()
199 currentTime := time.Now().Unix()
200 // Update oracle BEFORE liquidity changes
201 if observations == nil {
202 return pl.NewDefaultPositionInfo(), zero, zero, errors.New( "observations not initialized" )
203 }
204 slot0 := p.Slot0()
205 observationIndex, observationCardinality, err := writeObservation(
206 observations,
207 slot0.ObservationIndex(),
208 currentTime,
209 tick,
210 liquidityBefore,
211 slot0.ObservationCardinality(),
212 slot0.ObservationCardinalityNext(),
213 )
214 if err != nil {
215 return pl.NewDefaultPositionInfo(), zero, zero, err
216 }
217 slot0.SetObservationIndex(observationIndex)
218 slot0.SetObservationCardinality(observationCardinality)
219 p.SetSlot0(slot0)
220
221 // token0 used from current price to upper tick
222 amount0 = calculateToken0Amount(sqrtPriceX96, sqrtRatioUpper, liqDelta)
223 // token1 used from lower tick to current price
224 amount1 = calculateToken1Amount(sqrtRatioLower, sqrtPriceX96, liqDelta)
225 // update pool's active liquidity since price is in range
226 p.SetLiquidity(gnsmath.LiquidityMathAddDelta(liquidityBefore, liqDelta))
227
228 default :
229 // case 3
230 // full range between lower and upper tick is used for token1
231 // current tick is above the passed range; liquidity can only become in range by crossing from right to
232 // left, when we'll need _more_ token1 (it's becoming more valuable) so user must provide it
233 amount1 = calculateToken1Amount(sqrtRatioLower, sqrtRatioUpper, liqDelta)
234 }
235
236 return position, amount0.Abs(), amount1.Abs(), nil
237 }
238
239 // updatePosition modifies the position's liquidity and updates the corresponding tick states.
240 //
241 // This function updates the position data based on the specified liquidity delta and tick range.
242 // It also manages the fee growth, tick state flipping, and cleanup of unused tick data.
243 //
244 // Parameters:
245 // - positionParams: ModifyPositionParams, the parameters for the position modification, which include:
246 // - owner: The address of the position owner.
247 // - tickLower: The lower tick boundary of the position.
248 // - tickUpper: The upper tick boundary of the position.
249 // - liquidityDelta: The change in liquidity (positive or negative).
250 // - tick: int32, the current tick position.
251 //
252 // Returns:
253 // - PositionInfo: The updated position information.
254 //
255 // Workflow:
256 // 1. Clone the global fee growth values (token 0 and token 1).
257 // 2. If the liquidity delta is non-zero:
258 // - Update the lower and upper ticks using `tickUpdate`, flipping their states if necessary.
259 // - If a tick's state was flipped, update the tick bitmap to reflect the new state.
260 // 3. Calculate the fee growth inside the tick range using `getFeeGrowthInside`.
261 // 4. Generate a unique position key and update the position data using `positionUpdateWithKey`.
262 // 5. If liquidity is being removed (negative delta), clean up unused tick data by deleting the tick entries.
263 // 6. Return the updated position.
264 //
265 // Notes:
266 // - The function flips the tick states and cleans up unused tick data when liquidity is removed.
267 // - It ensures fee growth and position data remain accurate after the update.
268 //
269 // Example Usage:
270 //
271 // ```gno
272 //
273 // updatedPosition := pool.updatePosition(positionParams, currentTick)
274 // println("Updated Position Info:", updatedPosition)
275 //
276 // ```
277 func updatePosition(p *pl.Pool, observations *pl.ObservationTree, positionParams ModifyPositionParams, tick int32 ) (pl.PositionInfo, error ) {
278 feeGrowthGlobal0X128 := p.FeeGrowthGlobal0X128().Clone()
279 feeGrowthGlobal1X128 := p.FeeGrowthGlobal1X128().Clone()
280 liquidityDelta := positionParams.liquidityDelta
281
282 var flippedLower, flippedUpper bool
283 if !liquidityDelta.IsZero() {
284 blockTimestamp := time.Now().Unix()
285 if observations == nil {
286 return pl.NewDefaultPositionInfo(), errors.New( "observations not initialized" )
287 }
288 slot0 := p.Slot0()
289 tickCumulative, secondsPerLiquidityStr, err := observeSingle(
290 observations,
291 blockTimestamp,
292 0 ,
293 slot0.Tick(),
294 slot0.ObservationIndex(),
295 p.Liquidity(),
296 slot0.ObservationCardinality(),
297 )
298 if err != nil {
299 return pl.NewDefaultPositionInfo(), err
300 }
301
302 secondsPerLiquidityCumulativeX128 := u256.MustFromDecimal(secondsPerLiquidityStr)
303
304 flippedLower = tickUpdate(
305 p,
306 positionParams.tickLower,
307 tick,
308 liquidityDelta,
309 feeGrowthGlobal0X128,
310 feeGrowthGlobal1X128,
311 secondsPerLiquidityCumulativeX128,
312 tickCumulative,
313 blockTimestamp,
314 false ,
315 calculateMaxLiquidityPerTick(p.TickSpacing()),
316 )
317
318 flippedUpper = tickUpdate(
319 p,
320 positionParams.tickUpper,
321 tick,
322 liquidityDelta,
323 feeGrowthGlobal0X128,
324 feeGrowthGlobal1X128,
325 secondsPerLiquidityCumulativeX128,
326 tickCumulative,
327 blockTimestamp,
328 true ,
329 calculateMaxLiquidityPerTick(p.TickSpacing()),
330 )
331
332 if flippedLower {
333 tickBitmapFlipTick(p, positionParams.tickLower, p.TickSpacing())
334 }
335
336 if flippedUpper {
337 tickBitmapFlipTick(p, positionParams.tickUpper, p.TickSpacing())
338 }
339 }
340
341 feeGrowthInside0X128, feeGrowthInside1X128 := getFeeGrowthInside(
342 p,
343 positionParams.tickLower,
344 positionParams.tickUpper,
345 tick,
346 feeGrowthGlobal0X128,
347 feeGrowthGlobal1X128,
348 )
349
350 positionKey := getPositionKey(positionParams.tickLower, positionParams.tickUpper)
351
352 position, err := positionUpdateWithKey(
353 p,
354 positionKey,
355 liquidityDelta,
356 feeGrowthInside0X128.Clone(),
357 feeGrowthInside1X128.Clone(),
358 )
359 if err != nil {
360 return pl.NewDefaultPositionInfo(), err
361 }
362
363 // clear any tick data that is no longer needed
364 if liquidityDelta.IsNeg() {
365 if flippedLower {
366 deleteTick(p, positionParams.tickLower)
367 }
368 if flippedUpper {
369 deleteTick(p, positionParams.tickUpper)
370 }
371 }
372
373 return position, nil
374 }
375