Kind Realm (renderable)
Name v1
Namespace gnoswap / pool
Exported functions 3
Module gno.land/r/gnoswap/pool/v1
gno 0.9 pool.gno gno
⧉
1 package pool 2 3 import ( 4 "chain" 5 "errors" 6 7 "gno.land/r/gnoswap/common" 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.
"gno.land/r/gnoswap/halt/v1"
9 pl "gno.land/r/gnoswap/pool"
10
11 "gno.land/p/gnoswap/gnsmath/v1"
12 i256 "gno.land/p/gnoswap/int256/v1"
13 u256 "gno.land/p/gnoswap/uint256/v1"
14 "gno.land/p/gnoswap/utils/v1"
15 ufmt "gno.land/p/nt/ufmt/v0"
16
17 prabc "gno.land/p/gnoswap/rbac/v1"
18 _ "gno.land/r/gnoswap/rbac/v1"
19
20 "gno.land/r/gnoswap/access/v1"
21 )
22
23 // Mint adds liquidity to a pool position.
24 //
25 // Increases liquidity for a position within specified tick range.
26 // Calculates required token amounts based on current pool price.
27 // Updates tick state and transfers tokens atomically.
28 //
29 // Parameters:
30 // - _: Noncrossing implementation-call discriminator; pass 0.
31 // - rlm: Current realm context forwarded unchanged by the pool proxy.
32 // - token0Path: Registered token contract path for token0.
33 // - token1Path: Registered token contract path for token1.
34 // - fee: Fee tier; supported tiers are 100, 500, 3000, and 10000 (0.01%, 0.05%, 0.3%, and 1%).
35 // - tickLower: Inclusive lower price-range tick; must be aligned to the pool tick spacing.
36 // - tickUpper: Exclusive upper price-range tick; must be aligned to the pool tick spacing.
37 // - liquidityAmount: Positive decimal string specifying liquidity to add; it is converted to uint256 and then int128.
38 // - positionCaller: Nonzero position-contract address that supplies tokens for this mint.
39 //
40 // Returns:
41 // - amount0: Token0 amount consumed, returned as a decimal string.
42 // - amount1: Token1 amount consumed, returned as a decimal string.
43 //
44 // Requirements:
45 // - Pool must exist for token pair and fee
46 // - Liquidity amount must be positive
47 // - Ticks must be valid and aligned to spacing
48 //
49 // Only callable by position contract.
50 func (i *poolV1) Mint(
51 _ int ,
52 rlm realm,
53 token0Path string ,
54 token1Path string ,
55 fee uint32 ,
56 tickLower int32 ,
57 tickUpper int32 ,
58 liquidityAmount string ,
59 positionCaller address,
60 ) ( string , string ) {
61 access.AssertIsRlmCurrent( 0 , rlm)
62
63 i.assertPoolUnlocked()
64 halt.AssertIsNotHaltedPool()
65
66 caller := rlm.Previous().Address()
67 access.AssertIsPosition(caller)
68 access.AssertIsValidAddress(positionCaller)
69
70 i.lockPool( 0 , rlm)
71 defer i.unlockPool( 0 , rlm)
72
73 liquidity := u256.MustFromDecimal(liquidityAmount)
74 if liquidity.IsZero() {
75 panic(errors.New(errZeroLiquidity))
76 }
77
78 pool := i.mustGetPoolBy(token0Path, token1Path, fee)
79
80 tickSpacing := pool.TickSpacing()
81 checkTickSpacing(tickLower, tickSpacing)
82 checkTickSpacing(tickUpper, tickSpacing)
83
84 liquidityDelta := gnsmath.SafeConvertToInt128(liquidity)
85 positionParam := newModifyPositionParams(positionCaller, tickLower, tickUpper, liquidityDelta)
86 observations := i.mustGetObservations(pool.PoolPath())
87 _, amount0, amount1, err := modifyPosition(pool, observations, positionParam)
88 if err != nil {
89 panic(err)
90 }
91
92 poolAddr := access.MustGetAddress(prabc.ROLE_POOL.String())
93
94 if amount0.Gt(u256.Zero()) {
95 i.safeTransferFrom( 0 , rlm, pool, positionCaller, poolAddr, pool.Token0Path(), amount0, true )
96 }
97
98 if amount1.Gt(u256.Zero()) {
99 i.safeTransferFrom( 0 , rlm, pool, positionCaller, poolAddr, pool.Token1Path(), amount1, false )
100 }
101
102 // Save pool state after modifyPosition may have updated liquidity
103 err = i.savePool( 0 , rlm, pool)
104 if err != nil {
105 panic(err)
106 }
107
108 return amount0.ToString(), amount1.ToString()
109 }
110
111 // Burn removes liquidity from a pool position and credits principal as tokens
112 // owed to that pool-level position entry.
113 //
114 // The pool operation itself does not transfer tokens; a subsequent Collect call
115 // pays the credited principal. Position.DecreaseLiquidity wraps Burn and the
116 // fee-free Collect call within one atomic public operation.
117 //
118 // Parameters:
119 // - _: Noncrossing implementation-call discriminator; pass 0.
120 // - rlm: Current realm context forwarded unchanged by the pool proxy.
121 // - token0Path: Registered token contract path for token0.
122 // - token1Path: Registered token contract path for token1.
123 // - fee: Fee tier identifying the pool.
124 // - tickLower: Lower tick of the position's price range; must match pool spacing.
125 // - tickUpper: Upper tick of the position's price range; must match pool spacing.
126 // - liquidityAmount: Non-negative decimal liquidity amount, at most 2^127-1 after conversion to int128.
127 // - positionCaller: Nonzero position-contract address associated with the pool position.
128 //
129 // Returns:
130 // - amount0: Token0 principal credited to the pool position, as a decimal string.
131 // - amount1: Token1 principal credited to the pool position, as a decimal string.
132 //
133 // Note: Tokens remain in pool until Collect is called.
134 // Only callable by position contract.
135 func (i *poolV1) Burn(
136 _ int ,
137 rlm realm,
138 token0Path string ,
139 token1Path string ,
140 fee uint32 ,
141 tickLower int32 ,
142 tickUpper int32 ,
143 liquidityAmount string , // parsed as uint256, then checked against int128
144 positionCaller address,
145 ) ( string , string ) {
146 access.AssertIsRlmCurrent( 0 , rlm)
147
148 i.assertPoolUnlocked()
149 halt.AssertIsNotHaltedWithdraw()
150
151 caller := rlm.Previous().Address()
152 access.AssertIsPosition(caller)
153 access.AssertIsValidAddress(positionCaller)
154
155 i.lockPool( 0 , rlm)
156 defer i.unlockPool( 0 , rlm)
157
158 liqAmount := u256.MustFromDecimal(liquidityAmount)
159 liqAmountInt256 := gnsmath.SafeConvertToInt128(liqAmount)
160 liqDelta := i256.Zero().Neg(liqAmountInt256)
161
162 posParams := newModifyPositionParams(positionCaller, tickLower, tickUpper, liqDelta)
163 pool := i.mustGetPoolBy(token0Path, token1Path, fee)
164 observations := i.mustGetObservations(pool.PoolPath())
165 position, amount0, amount1, err := modifyPosition(pool, observations, posParams)
166 if err != nil {
167 panic(err)
168 }
169
170 if amount0.Gt(u256.Zero()) || amount1.Gt(u256.Zero()) {
171 amount0 = toUint128(amount0)
172 amount1 = toUint128(amount1)
173
174 position.SetTokensOwed0(gnsmath.SafeAddInt64(position.TokensOwed0(), gnsmath.SafeConvertToInt64(amount0)))
175 position.SetTokensOwed1(gnsmath.SafeAddInt64(position.TokensOwed1(), gnsmath.SafeConvertToInt64(amount1)))
176 }
177
178 positionKey := getPositionKey(tickLower, tickUpper)
179
180 setPosition(pool, positionKey, position)
181
182 err = i.savePool( 0 , rlm, pool)
183 if err != nil {
184 panic(err)
185 }
186
187 // actual token transfer happens in Collect()
188 return amount0.ToString(), amount1.ToString()
189 }
190
191 // CollectSwapFee pays out accrued swap fees for a position.
192 //
193 // The withdrawal fee is deducted from the collected amount and settled to the
194 // protocol fee realm; the remainder is transferred to recipient. Only accrued
195 // swap fees belong on this path - principal returned by Burn is not fee
196 // bearing and must go through Collect.
197 //
198 // Parameters:
199 // - _: Noncrossing implementation-call discriminator; pass 0.
200 // - rlm: Current realm context forwarded unchanged by the pool proxy.
201 // - token0Path: Registered token contract path for token0.
202 // - token1Path: Registered token contract path for token1.
203 // - fee: Fee tier identifying the pool.
204 // - recipient: Nonzero address receiving the post-fee token amounts.
205 // - tickLower: Lower tick of the position's price range.
206 // - tickUpper: Upper tick of the position's price range.
207 // - amount0Requested: Non-negative decimal int64 amount of token0 requested; the int64 maximum requests all owed token0.
208 // - amount1Requested: Non-negative decimal int64 amount of token1 requested; the int64 maximum requests all owed token1.
209 //
210 // Returns:
211 // - amount0: Token0 amount collected before withdrawal-fee deduction, as a decimal string.
212 // - amount1: Token1 amount collected before withdrawal-fee deduction, as a decimal string.
213 // - fee0: Withdrawal fee withheld from token0, as a decimal string.
214 // - fee1: Withdrawal fee withheld from token1, as a decimal string.
215 //
216 // Only callable by position contract.
217 func (i *poolV1) CollectSwapFee(
218 _ int ,
219 rlm realm,
220 token0Path string ,
221 token1Path string ,
222 fee uint32 ,
223 recipient address,
224 tickLower int32 ,
225 tickUpper int32 ,
226 amount0Requested string ,
227 amount1Requested string ,
228 ) (amount0, amount1, fee0, fee1 string ) {
229 collected0, collected1, withheld0, withheld1 := i.collect(
230 0 , rlm,
231 token0Path, token1Path, fee,
232 recipient,
233 tickLower, tickUpper,
234 amount0Requested, amount1Requested,
235 true ,
236 )
237
238 return utils.FormatInt(collected0), utils.FormatInt(collected1),
239 utils.FormatInt(withheld0), utils.FormatInt(withheld1)
240 }
241
242 // Collect pays out tokens owed to a position, transferring the full amount to
243 // recipient.
244 //
245 // No withdrawal fee is charged: this path carries principal credited by Burn,
246 // which the protocol does not tax. Accrued swap fees belong on CollectSwapFee.
247 //
248 // Parameters:
249 // - _: Noncrossing implementation-call discriminator; pass 0.
250 // - rlm: Current realm context forwarded unchanged by the pool proxy.
251 // - token0Path: Registered token contract path for token0.
252 // - token1Path: Registered token contract path for token1.
253 // - fee: Fee tier identifying the pool.
254 // - recipient: Nonzero address receiving the owed token amounts.
255 // - tickLower: Lower tick of the position's price range.
256 // - tickUpper: Upper tick of the position's price range.
257 // - amount0Requested: Non-negative decimal int64 amount of token0 requested; the int64 maximum requests all owed token0.
258 // - amount1Requested: Non-negative decimal int64 amount of token1 requested; the int64 maximum requests all owed token1.
259 //
260 // Returns:
261 // - amount0: Token0 principal transferred to recipient, as a decimal string.
262 // - amount1: Token1 principal transferred to recipient, as a decimal string.
263 //
264 // Only callable by position contract.
265 func (i *poolV1) Collect(
266 _ int ,
267 rlm realm,
268 token0Path string ,
269 token1Path string ,
270 fee uint32 ,
271 recipient address,
272 tickLower int32 ,
273 tickUpper int32 ,
274 amount0Requested string ,
275 amount1Requested string ,
276 ) (amount0, amount1 string ) {
277 collected0, collected1, _, _ := i.collect(
278 0 , rlm,
279 token0Path, token1Path, fee,
280 recipient,
281 tickLower, tickUpper,
282 amount0Requested, amount1Requested,
283 false ,
284 )
285
286 return utils.FormatInt(collected0), utils.FormatInt(collected1)
287 }
288
289 // collect settles the pool ledger for a position and pays the recipient.
290 //
291 // The payout is capped by the position's tokensOwed, never by a caller
292 // supplied number, and the ledger is written before any token leaves the
293 // realm. Both happen under a single pool lock, so the pool's accounting and
294 // its token balance are never observably out of step.
295 //
296 // The collected amount is NOT capped by the pool's internal balance: a balance
297 // short of the owed amount means the internal ledger has drifted, so the call
298 // reverts rather than silently paying out less.
299 //
300 // applyWithdrawalFee selects the fee policy; pass false for a fee-free payout.
301 // It is the entry point, not the configured rate, that decides whether the
302 // protocol fee is settled: a rate of zero must still flush a backlog left
303 // pending by an earlier halted settlement.
304 func (i *poolV1) collect(
305 _ int ,
306 rlm realm,
307 token0Path string ,
308 token1Path string ,
309 fee uint32 ,
310 recipient address,
311 tickLower int32 ,
312 tickUpper int32 ,
313 amount0Requested string ,
314 amount1Requested string ,
315 applyWithdrawalFee bool ,
316 ) (amount0, amount1, fee0, fee1 int64 ) {
317 access.AssertIsRlmCurrent( 0 , rlm)
318
319 i.assertPoolUnlocked()
320 halt.AssertIsNotHaltedWithdraw()
321
322 caller := rlm.Previous().Address()
323 access.AssertIsPosition(caller)
324 access.AssertIsValidAddress(recipient)
325
326 i.lockPool( 0 , rlm)
327 defer i.unlockPool( 0 , rlm)
328
329 amount0Req := utils.SafeParseInt64(amount0Requested)
330 amount1Req := utils.SafeParseInt64(amount1Requested)
331
332 if amount0Req < 0 || amount1Req < 0 {
333 panic(errors.New(errInvalidInput))
334 }
335
336 pool := i.mustGetPoolBy(token0Path, token1Path, fee)
337 // The pool position key encodes only the lower and upper ticks. It is scoped
338 // to this pool, so NFTs sharing a range use the same aggregate pool entry.
339 positionKey := getPositionKey(tickLower, tickUpper)
340 position, err := pool.GetPosition(positionKey)
341 if err != nil {
342 panic(newErrorWithDetail(
343 errDataNotFound,
344 ufmt.Sprintf( "positionKey(%s) does not exist" , positionKey),
345 ))
346 }
347
348 amount0 = minRequestedAmount(amount0Req, position.TokensOwed0())
349 amount1 = minRequestedAmount(amount1Req, position.TokensOwed1())
350
351 if amount0 > 0 {
352 tokenOwed0 := gnsmath.SafeSubInt64(position.TokensOwed0(), amount0)
353 token0Balance, err := updatePoolBalance(pool.BalanceToken0(), pool.BalanceToken1(), amount0, true )
354 if err != nil {
355 panic(err)
356 }
357
358 position.SetTokensOwed0(tokenOwed0)
359 pool.SetBalanceToken0(token0Balance)
360 }
361 if amount1 > 0 {
362 tokenOwed1 := gnsmath.SafeSubInt64(position.TokensOwed1(), amount1)
363 token1Balance, err := updatePoolBalance(pool.BalanceToken0(), pool.BalanceToken1(), amount1, false )
364 if err != nil {
365 panic(err)
366 }
367
368 position.SetTokensOwed1(tokenOwed1)
369 pool.SetBalanceToken1(token1Balance)
370 }
371
372 setPosition(pool, positionKey, position)
373
374 if err := i.savePool( 0 , rlm, pool); err != nil {
375 panic(err)
376 }
377
378 withdrawalFeeBPS := ZeroBps
379 if applyWithdrawalFee {
380 withdrawalFeeBPS = i.store.GetWithdrawalFeeBPS()
381 }
382
383 // Effects are persisted above; everything below leaves the realm.
384 fee0, amount0AfterFee := deductWithdrawalFee(amount0, withdrawalFeeBPS)
385 fee1, amount1AfterFee := deductWithdrawalFee(amount1, withdrawalFeeBPS)
386
387 if applyWithdrawalFee {
388 // Called even for a zero fee, and for a zero withdrawal fee rate, so that
389 // anything left pending for these tokens by an earlier halted settlement
390 // is flushed.
391 i.settleProtocolFee( 0 , rlm, token0Path, fee0)
392 i.settleProtocolFee( 0 , rlm, token1Path, fee1)
393 }
394
395 if amount0AfterFee > 0 {
396 common.SafeGRC20Transfer( 0 , rlm, token0Path, recipient, amount0AfterFee)
397 }
398 if amount1AfterFee > 0 {
399 common.SafeGRC20Transfer( 0 , rlm, token1Path, recipient, amount1AfterFee)
400 }
401
402 return amount0, amount1, fee0, fee1
403 }
404
405 // CollectProtocol collects accumulated protocol fees from swap operations.
406 // Only callable by admin or governance.
407 //
408 // Parameters:
409 // - _: Noncrossing implementation-call discriminator; pass 0.
410 // - rlm: Current realm context forwarded unchanged by the pool proxy.
411 // - token0Path: Registered token contract path for token0.
412 // - token1Path: Registered token contract path for token1.
413 // - fee: Fee tier identifying the pool.
414 // - recipient: Nonzero address receiving the collected protocol fees.
415 // - amount0Requested: Non-negative decimal amount of token0 protocol fees requested; capped at the available balance.
416 // - amount1Requested: Non-negative decimal amount of token1 protocol fees requested; capped at the available balance.
417 //
418 // Returns:
419 // - amount0: Token0 protocol fees transferred to recipient, as a decimal string.
420 // - amount1: Token1 protocol fees transferred to recipient, as a decimal string.
421 func (i *poolV1) CollectProtocol(
422 _ int ,
423 rlm realm,
424 token0Path string ,
425 token1Path string ,
426 fee uint32 ,
427 recipient address,
428 amount0Requested string , // uint128
429 amount1Requested string , // uint128
430 ) ( string , string ) {
431 access.AssertIsRlmCurrent( 0 , rlm)
432
433 i.assertPoolUnlocked()
434 halt.AssertIsNotHaltedWithdraw()
435
436 previousRealm := rlm.Previous()
437 caller := previousRealm.Address()
438 access.AssertIsAdminOrGovernance(caller)
439
440 common.MustRegistered(token0Path, token1Path)
441
442 i.lockPool( 0 , rlm)
443 defer i.unlockPool( 0 , rlm)
444
445 amount0, amount1 := i.collectProtocol(
446 0 ,
447 rlm,
448 token0Path,
449 token1Path,
450 fee,
451 recipient,
452 amount0Requested,
453 amount1Requested,
454 )
455
456 chain.Emit(
457 "CollectProtocol" ,
458 "prevAddr" , caller.String(),
459 "prevRealm" , previousRealm.PkgPath(),
460 "token0Path" , token0Path,
461 "token1Path" , token1Path,
462 "fee" , utils.FormatUint(fee),
463 "recipient" , recipient.String(),
464 "internal_amount0" , amount0,
465 "internal_amount1" , amount1,
466 )
467
468 return amount0, amount1
469 }
470
471 // collectProtocol performs the actual protocol fee collection.
472 // It ensures requested amounts don't exceed available protocol fees.
473 // Returns amount0, amount1 as strings representing collected fees.
474 func (i *poolV1) collectProtocol(
475 _ int ,
476 rlm realm,
477 token0Path string ,
478 token1Path string ,
479 fee uint32 ,
480 recipient address,
481 amount0Requested string ,
482 amount1Requested string ,
483 ) ( string , string ) {
484 pool := i.mustGetPoolBy(token0Path, token1Path, fee)
485
486 amount0Req := utils.SafeParseInt64(amount0Requested)
487 amount1Req := utils.SafeParseInt64(amount1Requested)
488
489 if amount0Req < 0 || amount1Req < 0 {
490 panic(errors.New(errInvalidInput))
491 }
492
493 amount0 := minRequestedAmount(amount0Req, pool.ProtocolFeesToken0())
494 amount1 := minRequestedAmount(amount1Req, pool.ProtocolFeesToken1())
495
496 amount0, amount1 = i.saveProtocolFees(pool, amount0, amount1)
497
498 newBalanceToken0, err := updatePoolBalance(pool.BalanceToken0(), pool.BalanceToken1(), amount0, true )
499 if err != nil {
500 panic(err)
501 }
502 pool.SetBalanceToken0(newBalanceToken0)
503
504 newBalanceToken1, err := updatePoolBalance(pool.BalanceToken0(), pool.BalanceToken1(), amount1, false )
505 if err != nil {
506 panic(err)
507 }
508 pool.SetBalanceToken1(newBalanceToken1)
509
510 err = i.savePool( 0 , rlm, pool)
511 if err != nil {
512 panic(err)
513 }
514
515 common.SafeGRC20Transfer( 0 , rlm, pool.Token0Path(), recipient, amount0)
516 common.SafeGRC20Transfer( 0 , rlm, pool.Token1Path(), recipient, amount1)
517
518 return utils.FormatInt(amount0), utils.FormatInt(amount1)
519 }
520
521 // saveProtocolFees updates the protocol fee balances after collection.
522 // Returns amount0, amount1 representing the fees deducted from protocol reserves.
523 func (i *poolV1) saveProtocolFees(pool *pl.Pool, amount0, amount1 int64 ) ( int64 , int64 ) {
524 if pool.ProtocolFeesToken0() < amount0 {
525 panic(errors.New(errUnderflow))
526 }
527 pool.SetProtocolFeesToken0(gnsmath.SafeSubInt64(pool.ProtocolFeesToken0(), amount0))
528
529 if pool.ProtocolFeesToken1() < amount1 {
530 panic(errors.New(errUnderflow))
531 }
532 pool.SetProtocolFeesToken1(gnsmath.SafeSubInt64(pool.ProtocolFeesToken1(), amount1))
533
534 return amount0, amount1
535 }
536
537 func minRequestedAmount(request, available int64 ) int64 {
538 if request > available {
539 return available
540 }
541
542 return request
543 }
544
545 // IncreaseObservationCardinalityNext schedules growth of a pool's circular observation buffer.
546 //
547 // Parameters:
548 // - _: Noncrossing implementation-call discriminator; pass 0.
549 // - rlm: Current realm context forwarded unchanged by the pool proxy.
550 // - token0Path: Registered token contract path for token0.
551 // - token1Path: Registered token contract path for token1.
552 // - fee: Fee tier identifying the pool.
553 // - cardinalityNext: Requested observation-buffer capacity; must not exceed the configured maximum.
554 func (i *poolV1) IncreaseObservationCardinalityNext(
555 _ int ,
556 rlm realm,
557 token0Path string ,
558 token1Path string ,
559 fee uint32 ,
560 cardinalityNext uint16 ,
561 ) {
562 access.AssertIsRlmCurrent( 0 , rlm)
563
564 i.assertPoolUnlocked()
565 halt.AssertIsNotHaltedPool()
566
567 pool := i.mustGetPoolBy(token0Path, token1Path, fee)
568 slot0Before := pool.Slot0()
569 observationCardinalityNextOld := slot0Before.ObservationCardinalityNext()
570
571 i.lockPool( 0 , rlm)
572 defer i.unlockPool( 0 , rlm)
573
574 observations := i.mustGetObservations(pool.PoolPath())
575 if cardinalityNext > maxObservationCardinality {
576 panic( "observation cardinality next exceeds maximum" )
577 }
578 observationCardinalityNextNew, err := grow(
579 observations,
580 slot0Before.ObservationCardinalityNext(),
581 cardinalityNext,
582 )
583 if err != nil {
584 panic(err)
585 }
586 slot0After := pool.Slot0()
587 slot0After.SetObservationCardinalityNext(observationCardinalityNextNew)
588 pool.SetSlot0(slot0After)
589
590 if observationCardinalityNextOld != observationCardinalityNextNew {
591 previousRealm := rlm.Previous()
592 chain.Emit(
593 "IncreaseObservationCardinalityNext" ,
594 "prevAddr" , previousRealm.Address().String(),
595 "prevRealm" , previousRealm.PkgPath(),
596 "poolPath" , pool.PoolPath(),
597 "cardinalityNext" , utils.FormatUint(observationCardinalityNextNew),
598 )
599 }
600 }
601