Kind Realm (renderable)
Name v1
Namespace gnoswap / pool
Exported functions 3
Module gno.land/r/gnoswap/pool/v1
gno 0.9 manager.gno gno
⧉
1 package pool 2 3 import ( 4 "chain" 5 "errors" 6 "time" 7 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.
prabc "gno.land/p/gnoswap/rbac/v1"
9 "gno.land/p/gnoswap/utils/v1"
10 ufmt "gno.land/p/nt/ufmt/v0"
11
12 "gno.land/r/gnoswap/access/v1"
13 "gno.land/r/gnoswap/common"
14 gns "gno.land/r/gnoswap/gns"
15 "gno.land/r/gnoswap/halt/v1"
16 pl "gno.land/r/gnoswap/pool"
17
18 en "gno.land/r/gnoswap/emission"
19 )
20
21 const GNS_TOKEN_KEY string = "gno.land/r/gnoswap/gns.GNS"
22
23 // CreatePool creates a new concentrated liquidity pool.
24 //
25 // Deploys a new AMM pool for a token pair with the specified fee tier.
26 // Charges the configured pool-creation fee (100 GNS by default).
27 // Accepts either token path order, canonicalizes token0/token1, and sets the
28 // initial price and tick spacing for that canonical order.
29 //
30 // Parameters:
31 // - _: Noncrossing implementation-call discriminator; pass 0.
32 // - rlm: Current realm context forwarded unchanged by the pool proxy.
33 // - token0Path: first token contract path; either order is accepted and the
34 // stored pair is canonicalized.
35 // - token1Path: second token contract path; either order is accepted and the
36 // stored pair is canonicalized.
37 // - fee: supported pool fee tier (100=0.01%, 500=0.05%, 3000=0.3%, 10000=1%).
38 // - sqrtPriceX96: initial square-root price in Q64.96 format, constrained to
39 // [MIN_SQRT_RATIO, MAX_SQRT_RATIO).
40 //
41 // Tick spacing by fee tier:
42 // - 0.01%: 1 tick
43 // - 0.05%: 10 ticks
44 // - 0.30%: 60 ticks
45 // - 1.00%: 200 ticks
46 //
47 // Requirements:
48 // - Tokens must be different after canonicalization/wrapping
49 // - Fee tier must be supported
50 // - Pool must not already exist
51 // - Caller must have the configured creation fee
52 //
53 // Price initialization:
54 // - The initial sqrt price must be in [MIN_SQRT_RATIO, MAX_SQRT_RATIO).
55 // - No oracle or external market-price sanity check is performed.
56 // - If paths are supplied in reverse order, the stored sqrt price is
57 // inverted to match the canonical token0/token1 order.
58 //
59 // Recovery from an economically inappropriate price:
60 // 1. Add wide-range liquidity at the current price.
61 // 2. Execute a swap to move the price toward the desired market rate.
62 // 3. Remove the liquidity.
63 //
64 // The executor's LP losses offset arbitrage gains, minus fees and slippage.
65 func (i *poolV1) CreatePool(
66 _ int ,
67 rlm realm,
68 token0Path string ,
69 token1Path string ,
70 fee uint32 ,
71 sqrtPriceX96 string ,
72 ) {
73 access.AssertIsRlmCurrent( 0 , rlm)
74
75 i.assertPoolUnlocked()
76 halt.AssertIsNotHaltedPool()
77
78 assertIsSupportedFeeTier(fee)
79 assertIsNotExistsPoolPath(i, token0Path, token1Path, fee)
80
81 i.lockPool( 0 , rlm)
82 defer i.unlockPool( 0 , rlm)
83
84 en.MintAndDistributeGns(cross(rlm))
85
86 slot0FeeProtocol := i.store.GetSlot0FeeProtocol()
87
88 tickSpacing, err := i.GetFeeAmountTickSpacing(fee)
89 if err != nil {
90 panic(err)
91 }
92
93 poolInfo := newPoolParams(
94 token0Path,
95 token1Path,
96 fee,
97 sqrtPriceX96,
98 tickSpacing,
99 slot0FeeProtocol,
100 )
101
102 err = poolInfo.update()
103 if err != nil {
104 panic(err)
105 }
106
107 // validate token paths are not the same after wrapping
108 assertIsNotEqualsTokens(poolInfo.token0Path, poolInfo.token1Path)
109
110 // check if wrapped token paths are registered
111 common.MustRegistered(poolInfo.token0Path, poolInfo.token1Path)
112
113 pool := newPool(poolInfo)
114 poolPath := poolInfo.poolPath()
115
116 pools := i.store.GetPools()
117 pools.Set(poolPath, pool)
118
119 err = i.store.SetPools( 0 , rlm, pools)
120 if err != nil {
121 panic(err)
122 }
123
124 observations := i.store.GetObservations()
125 observations.Set(poolPath, pl.NewPoolObservationsTree(time.Now().Unix()))
126 if err := i.store.SetObservations( 0 , rlm, observations); err != nil {
127 panic(err)
128 }
129
130 poolCreationFee := i.store.GetPoolCreationFee()
131
132 if poolCreationFee > 0 {
133 previousRealm := rlm.Previous()
134 previousRealmAddr := previousRealm.Address()
135 poolAddr := access.MustGetAddress(prabc.ROLE_POOL.String())
136 gns.TransferFrom(cross(rlm), previousRealmAddr, poolAddr, poolCreationFee)
137
138 chain.Emit(
139 "PoolCreationFee" ,
140 "prevAddr" , previousRealmAddr.String(),
141 "prevRealm" , previousRealm.PkgPath(),
142 "poolPath" , poolPath,
143 "feeTokenPath" , GNS_TOKEN_KEY,
144 "feeAmount" , utils.FormatInt(poolCreationFee),
145 )
146 }
147
148 i.settleProtocolFee( 0 , rlm, GNS_TOKEN_KEY, poolCreationFee)
149
150 previousRealm := rlm.Previous()
151 chain.Emit(
152 "CreatePool" ,
153 "prevAddr" , previousRealm.Address().String(),
154 "prevRealm" , previousRealm.PkgPath(),
155 "token0Path" , poolInfo.Token0Path(),
156 "token1Path" , poolInfo.Token1Path(),
157 "fee" , utils.FormatUint(fee),
158 "sqrtPriceX96" , poolInfo.SqrtPriceX96().ToString(),
159 "poolPath" , poolPath,
160 "tick" , utils.FormatInt(pool.Slot0Tick()),
161 "tickSpacing" , utils.FormatInt(poolInfo.TickSpacing()),
162 )
163 }
164
165 // SetFeeProtocol sets the protocol fee denominators for all managed pools.
166 //
167 // Parameters:
168 // - _: Noncrossing implementation-call discriminator; pass 0.
169 // - rlm: Current realm context forwarded unchanged by the pool proxy.
170 // - feeProtocol0: token0 protocol-fee denominator; must be 0 or 4-10.
171 // Zero disables protocol fees; 4 routes one quarter of swap fees and 10
172 // routes one tenth to the protocol.
173 // - feeProtocol1: token1 protocol-fee denominator with the same 0 or 4-10
174 // constraint and fee share semantics as feeProtocol0.
175 //
176 // Only callable by admin or governance.
177 func (i *poolV1) SetFeeProtocol(_ int , rlm realm, feeProtocol0, feeProtocol1 uint8 ) {
178 access.AssertIsRlmCurrent( 0 , rlm)
179
180 i.assertPoolUnlocked()
181 halt.AssertIsNotHaltedPool()
182
183 caller := rlm.Previous().Address()
184 access.AssertIsAdminOrGovernance(caller)
185
186 i.lockPool( 0 , rlm)
187 defer i.unlockPool( 0 , rlm)
188
189 err := i.setFeeProtocolInternal( 0 , rlm, feeProtocol0, feeProtocol1)
190 if err != nil {
191 panic(err)
192 }
193 }
194
195 // setFeeAmountTickSpacing associates a tick spacing value with a fee amount.
196 func (i *poolV1) setFeeAmountTickSpacing(_ int , rlm realm, fee uint32 , tickSpacing int32 ) error {
197 feeAmountTickSpacing := i.store.GetFeeAmountTickSpacing()
198 feeAmountTickSpacing[fee] = tickSpacing
199
200 return i.store.SetFeeAmountTickSpacing( 0 , rlm, feeAmountTickSpacing)
201 }
202
203 func (i *poolV1) getPool(poolPath string ) (*pl.Pool, error ) {
204 pools := i.store.GetPools()
205
206 iPool := pools.Get(poolPath)
207 if iPool == nil {
208 return nil , ufmt.Errorf( "expected poolPath(%s) to exist" , poolPath)
209 }
210
211 p, ok := iPool.(*pl.Pool)
212 if !ok {
213 return nil , ufmt.Errorf( "failed to cast pool to *Pool: %T" , iPool)
214 }
215
216 return p, nil
217 }
218
219 // mustGetPool retrieves a pool instance by its path and ensures it exists.
220 func (i *poolV1) mustGetPool(poolPath string ) (pool *pl.Pool) {
221 p, err := i.getPool(poolPath)
222 if err != nil {
223 panic(makeErrorWithDetails(errDataNotFound, err.Error()))
224 }
225
226 return p
227 }
228
229 func (i *poolV1) mustGetPoolBy(token0Path, token1Path string , fee uint32 ) *pl.Pool {
230 poolPath := GetPoolPath(token0Path, token1Path, fee)
231 return i.mustGetPool(poolPath)
232 }
233
234 func (i *poolV1) getObservations(poolPath string ) (*pl.ObservationTree, error ) {
235 observations := i.store.GetObservations()
236 value := observations.Get(poolPath)
237 if value == nil {
238 return nil , ufmt.Errorf( "expected observations for poolPath(%s) to exist" , poolPath)
239 }
240
241 tree, ok := value.(*pl.ObservationTree)
242 if !ok {
243 return nil , ufmt.Errorf( "failed to cast observations for poolPath(%s): %T" , poolPath, value)
244 }
245
246 return tree, nil
247 }
248
249 func (i *poolV1) mustGetObservations(poolPath string ) *pl.ObservationTree {
250 tree, err := i.getObservations(poolPath)
251 if err != nil {
252 panic(makeErrorWithDetails(errDataNotFound, err.Error()))
253 }
254 return tree
255 }
256
257 // savePool updates a pool in the pools map and persists to storage.
258 // This ensures that modifications to pool objects are properly saved.
259 func (i *poolV1) savePool(_ int , rlm realm, pool *pl.Pool) error {
260 pools := i.store.GetPools()
261 pools.Set(pool.PoolPath(), pool)
262
263 return i.store.SetPools( 0 , rlm, pools)
264 }
265
266 // setFeeProtocolInternal updates the protocol fee for all pools and emits an event.
267 func (i *poolV1) setFeeProtocolInternal(_ int , rlm realm, feeProtocol0, feeProtocol1 uint8 ) error {
268 oldFee := i.store.GetSlot0FeeProtocol()
269 newFee, err := i.setFeeProtocol( 0 , rlm, feeProtocol0, feeProtocol1)
270 if err != nil {
271 return err
272 }
273
274 feeProtocol0Old := oldFee % 16
275 feeProtocol1Old := oldFee >> 4
276
277 previousRealm := rlm.Previous()
278 chain.Emit(
279 "SetFeeProtocol" ,
280 "prevAddr" , previousRealm.Address().String(),
281 "prevRealm" , previousRealm.PkgPath(),
282 "prevFeeProtocol0" , utils.FormatUint(feeProtocol0Old),
283 "prevFeeProtocol1" , utils.FormatUint(feeProtocol1Old),
284 "feeProtocol0" , utils.FormatUint(feeProtocol0),
285 "feeProtocol1" , utils.FormatUint(feeProtocol1),
286 "newFee" , utils.FormatUint(newFee),
287 )
288
289 return nil
290 }
291
292 // setFeeProtocol updates the protocol fee configuration for all managed pools.
293 //
294 // This function combines the protocol fee values for token0 and token1 into a single `uint8` value,
295 // where:
296 // - Lower 4 bits store feeProtocol0 (for token0).
297 // - Upper 4 bits store feeProtocol1 (for token1).
298 //
299 // The updated fee protocol is applied uniformly to all pools managed by the system.
300 //
301 // Parameters:
302 // - feeProtocol0: protocol fee for token0 (must be 0 or between 4 and 10 inclusive).
303 // - feeProtocol1: protocol fee for token1 (must be 0 or between 4 and 10 inclusive).
304 //
305 // Returns:
306 // - newFee (uint8): the combined fee protocol value.
307 //
308 // Example:
309 // If feeProtocol0 = 4 and feeProtocol1 = 5:
310 //
311 // newFee = 4 + (5 << 4)
312 // // Results in: 0x54 (84 in decimal)
313 // // Binary: 0101 0100
314 // // ^^^^ ^^^^
315 // // fee1=5 fee0=4
316 //
317 // Notes:
318 // - This function ensures that all pools under management are updated to use the same fee protocol.
319 // - Caller restrictions (e.g., admin or governance) are not enforced in this function.
320 // - Ensure the system is not halted before updating fees.
321 func (i *poolV1) setFeeProtocol(_ int , rlm realm, feeProtocol0, feeProtocol1 uint8 ) ( uint8 , error ) {
322 if err := validateFeeProtocol(feeProtocol0, feeProtocol1); err != nil {
323 return 0 , err
324 }
325
326 // combine both protocol fee into a single byte:
327 // - feePrtocol0 occupies the lower 4 bits
328 // - feeProtocol1 is shifted the lower 4 positions to occupy the upper 4 bits
329 newFee := feeProtocol0 + (feeProtocol1 << 4 ) // ( << 4 ) = ( * 16 )
330
331 pools := i.store.GetPools()
332
333 // Update slot0 for each pool
334 pools.Iterate( "" , "" , func (poolPath string , poolI any ) bool {
335 pool, ok := poolI.(*pl.Pool)
336 if !ok {
337 panic(ufmt.Errorf( "failed to cast pool to *Pool: %T" , pool))
338 }
339
340 slot0 := pool.Slot0()
341 slot0.SetFeeProtocol(newFee)
342
343 pool.SetSlot0(slot0)
344 return false
345 })
346
347 // update slot0
348 err := i.store.SetSlot0FeeProtocol( 0 , rlm, newFee)
349 if err != nil {
350 return 0 , err
351 }
352
353 return newFee, nil
354 }
355
356 // validateFeeProtocol validates the fee protocol values for token0 and token1.
357 //
358 // This function checks whether the provided fee protocol values (`feeProtocol0` and `feeProtocol1`)
359 // are valid using the `isValidFeeProtocolValue` function. If either value is invalid, it returns
360 // an error indicating that the protocol fee percentage is invalid.
361 //
362 // Parameters:
363 // - feeProtocol0: uint8, the fee protocol value for token0.
364 // - feeProtocol1: uint8, the fee protocol value for token1.
365 //
366 // Returns:
367 // - error: Returns `errInvalidProtocolFeePct` if either `feeProtocol0` or `feeProtocol1` is invalid.
368 // Returns `nil` if both values are valid.
369 func validateFeeProtocol(feeProtocol0, feeProtocol1 uint8 ) error {
370 if !isValidFeeProtocolValue(feeProtocol0) || !isValidFeeProtocolValue(feeProtocol1) {
371 return errors.New(errInvalidProtocolFeePct)
372 }
373 return nil
374 }
375
376 // isValidFeeProtocolValue checks if a fee protocol value is within acceptable range.
377 // Valid values are either 0 (disabled) or between 4 and 10 inclusive.
378 //
379 // The value is used as a denominator: protocolFee = swapFee / feeProtocol
380 // (e.g., feeProtocol=4 means 1/4 = 25% of swap fees go to protocol)
381 func isValidFeeProtocolValue(value uint8 ) bool {
382 return value == 0 || (value >= 4 && value <= 10 )
383 }
384