Transaction

82D1A5ACAF0206…F1686308A061

Block 77,265 · index 0 · indexed

Summary

Hash
82D1A5ACAF020658997205F76600679B74600D6AB09AD0967F42F1686308A061
Block
77,265
Size
197532 bytes
Gas used
245,196,029 / 294,235,175
Fee
294236 ugnot
Status
success

Messages

#1AddPackagegno.land/r/gnoswap/pool/v124 arguments

Arguments · 24

  1. #1pool
  2. #2README.md
  3. #3# Pool Concentrated liquidity AMM pools with tick-based pricing. ## Overview Pool contracts implement Uniswap V3-style concentrated liquidity, allowing LPs to provide liquidity within custom price ranges for maximum capital efficiency. ## Configuration - **Pool Creation Fee**: 100 GNS (default) - **Protocol Fee**: Disabled (0) or a denominator of 4-10, routing 1/4 to 1/10 of swap fees to the protocol - **Withdrawal Fee**: 1% on fee-bearing collection (configurable) - **Fee Tiers**: 0.01%, 0.05%, 0.3%, 1% - **Tick Spacing**: Auto-set by fee tier - **Max Liquidity Per Tick**: Depends on tick spacing; use `GetMaxLiquidityPerTick` rather than `2^128 - 1` ## Core Concepts ### Concentrated Liquidity Liquidity providers concentrate capital within custom price ranges instead of 0-∞. This allows LPs to allocate capital where it's most likely to generate fees - near the current price for volatile pairs, or within tight ranges for stable pairs. Capital efficiency can improve by orders of magnitude depending on range selection and pair volatility. For more details, check out [GnoSwap Docs](https://docs.gnoswap.io/core-concepts/amm/concentrated-liquidity). ### Tick System - Price space divided into discrete ticks (0.01% apart) - Each tick represents ~0.01% price change - Positions defined by upper/lower tick boundaries - Liquidity activated only when price in range ## Key Functions ### `CreatePool` Deploys a new trading pair. - Requires 100 GNS creation fee by default - Valid fee tier required - Accepts either token path order and canonicalizes token0/token1 - If paths are reversed, the initial square-root price is inverted - Initial `sqrtPriceX96` must be in `[MIN_SQRT_RATIO, MAX_SQRT_RATIO)` - Does not compare the initial price with an oracle or external market price ### `Mint` Adds liquidity to position (called by Position contract). - Calculates token amounts from liquidity - Updates tick bitmap - Transfers tokens from owner - Returns actual amounts used ### `Burn` Removes liquidity without collecting tokens. - Pool-level operation: burn first, then collect owed tokens - Calculates owed principal - Updates position state ### `Collect` Pays tokens owed by a pool position without a withdrawal fee. This fee-free path is normally used for principal after `Burn`. - Transfers the requested portion of `tokensOwed` - Updates `tokensOwed` ### `CollectSwapFee` Pays accrued swap fees through the fee-bearing collection path. - Applies the configured withdrawal fee - Returns gross collected amounts and the fee withheld - `Position.DecreaseLiquidity` and `Position.CollectFee` invoke the appropriate pool paths internally ### `Swap` Core swap execution (called by Router). - Iterates through ticks - Updates price and liquidity - Calculates fees - Maintains TWAP oracle #### Swap Callback The `Swap` function uses a callback pattern for token transfers, following the Uniswap V3 flash swap design. **Callback Signature**: ```go func swapCallback(cur realm, amount0Delta, amount1Delta int64, _ *pool.CallbackMarker) error ``` **Delta Convention**: | Delta | Meaning | |-------|---------| | Positive (`> 0`) | Amount the pool must RECEIVE (input token) | | Negative (`< 0`) | Amount the pool has SENT (output token) | **Swap Direction Examples**: For `zeroForOne = true` (token0 → token1): - `amount0Delta > 0`: Pool receives token0 (input) - `amount1Delta < 0`: Pool sends token1 (output) For `zeroForOne = false` (token1 → token0): - `amount0Delta < 0`: Pool sends token0 (output) - `amount1Delta > 0`: Pool receives token1 (input) **Callback Implementation Example**: ```go func swapCallback(cur realm, amount0Delta, amount1Delta int64, _ *pool.CallbackMarker) error { caller := cur.Previous().Address() poolAddr := chain.PackageAddress("gno.land/r/gnoswap/pool") // Security check: ensure this callback is invoked by the legitimate pool if caller != poolAddr { return errors.New("unauthorized caller") } if amount0Delta > 0 { // Transfer token0 to pool common.SafeGRC20Transfer(0, cur, token0Path, poolAddr, amount0Delta) } if amount1Delta > 0 { // Transfer token1 to pool common.SafeGRC20Transfer(0, cur, token1Path, poolAddr, amount1Delta) } return nil } ``` **Important Notes**: - A custom callback should verify that the caller is the legitimate pool. - In the router flow, the supplied closure performs that pool-origin check before calling `router.SwapCallback`; the Router implementation then checks that its caller is Router v1. - The callback MUST transfer at least the positive delta amount to the pool. - Return `nil` on success, or an error to revert the swap. - Pool validates the balance increase after callback execution. ## Technical Details ### Price Math **Q96 Format**: Prices stored as `sqrtPriceX96 = sqrt(price) * 2^96` ``` Price 1:1 → sqrtPriceX96 = 79228162514264337593543950336 Price 1:4 → sqrtPriceX96 = 39614081257132168796771975168 Price 100:1 → sqrtPriceX96 = 792281625142643375935439503360 ``` **Tick to Price**: `price = 1.0001^tick` ``` tick 0 = price 1 tick 6932 = price ~2 tick -6932 = price ~0.5 ``` **Range Liquidity**: Liquidity is calculated from the token required by the current price: - Below the range (`current < lower`): token0 only - In the range (`lower <= current < upper`): both token0 and token1 - Above the range (`current >= upper`): token1 only The integer formulas use the square-root prices and round in the direction required by the mint or burn operation; there is no single `amount` formula that applies to all three cases. **Impermanent Loss**: - Narrow range: Higher fees, higher IL - Wide range: Lower fees, lower IL - Stable pairs: ±0.1% ranges optimal - Volatile pairs: ±10%+ ranges recommended ### Fee Mechanics **Swap Fees**: - Charged on input amount - Accumulates as feeGrowthGlobal - Distributed pro-rata to in-range liquidity **Fee Calculation**: ``` fees = feeGrowthInside * liquidity feeGrowthInside = feeGrowthGlobal - feeGrowthOutside ``` **Protocol fees**: - `0` disables protocol fee collection - `4` through `10` are denominators: `4` routes 25% and `10` routes 10% of swap fees to the protocol - Governance-managed configuration applies to the pool set, not an independent percentage selected on each pool ## Security ### Reentrancy Protection - The live guard is the pool-wide `Unlocked` key in the pool KV store, managed by `pool/v1/lock.gno`. `Slot0.unlocked` is a separate stored field and is not the guard; `GetSlot0Unlocked` reports that field, not the live lock. - The lock is not swap-specific. `CreatePool`, `Mint`, `Burn`, `Collect`, `CollectSwapFee`, `CollectProtocol`, `SetFeeProtocol`, `SetWithdrawalFee`, `SetPoolCreationFee`, `IncreaseObservationCardinalityNext`, `SetSwapStartHook`, `SetSwapEndHook`, `SetTickCrossHook`, `Swap`, and the read-only `DrySwap` all assert that the pool is unlocked before doing any work. - The unlocked assertion is read-only and runs before the access checks, so a call that aborts on authorization leaves no persisted lock behind. - Settlement order is operation-specific rather than uniformly checks-effects-interactions. `Swap` settles optimistically through the callback and verifies the resulting balance increase afterwards, while `Mint` pulls tokens before its final pool save. Review the specific path rather than assuming every write precedes every external call. ### Price Manipulation - TWAP oracle provides time-weighted observations for monitoring; it is not an automatic initial-price guard - Large swaps limited by liquidity - Slippage protection required ### Pool Creation Griefing **Issue**: `CreatePool` validates the fee tier, token canonicalization, and square-root price bounds, but does not compare the initial price with an oracle or external market price. A pool can therefore be created at an economically inappropriate extreme price. **Impact**: - Pool may be temporarily unusable - No rational LP may provide liquidity at a distorted price - Price cannot self-correct without liquidity **Recovery Mechanism**: Recovery requires coordinated liquidity provision and swaps to move the price toward a desired market rate, followed by liquidity removal. The protocol does not perform this correction automatically, and profitability depends on market conditions, fees, and slippage. **Example Recovery Sequence**: This pseudocode assumes the integrating realm function has a current `cur` token. ``` // Illustrative sequence; the caller must compose and execute these operations 1. position.Mint(cross(cur), ..., fullRange, largeAmount, ...) // Add liquidity 2. router.ExactInSwapRoute(cross(cur), ..., targetRoute, ...) // Fix price via arbitrage 3. position.DecreaseLiquidity(cross(cur), positionId, ...) // Remove liquidity and collect principal 4. position.CollectFee(cross(cur), positionId) // Collect any remaining fees ``` **Prevention**: - 100 GNS creation fee provides deterrent - Consider implementing price oracle validation for high-value pairs - Monitor pool creation events for suspicious activity ### Rounding - Integer math rounds directionally for the input/output invariant; not every division rounds down - Minimum liquidity enforced - Full precision for amounts
  4. #4assert.gno
  5. #5package pool import ( ufmt "gno.land/p/nt/ufmt/v0" pl "gno.land/r/gnoswap/pool" ) func assertIsNotUserCall(_ int, rlm realm) { previousRealm := rlm.Previous() if previousRealm.IsUserCall() { panic(newErrorWithDetail( errNotAccessEOA, ufmt.Sprintf("previousRealm(%s) is EOA", previousRealm.Address()), )) } } // assertIsNotEqualsTokens asserts that the token0Path and token1Path are not equal. func assertIsNotEqualsTokens(token0Path, token1Path string) { if token0Path == token1Path { panic(newErrorWithDetail( errDuplicateTokenInPool, ufmt.Sprintf("expected token0Path(%s) != token1Path(%s)", token0Path, token1Path), )) } } // assertIsSupportedFeeTier asserts that the fee is a supported fee tier. func assertIsSupportedFeeTier(fee uint32) { if !isValidFeeTier(fee) { panic(newErrorWithDetail( errUnsupportedFeeTier, ufmt.Sprintf("expected fee(%d) to be one of %d, %d, %d, %d", fee, FeeTier100, FeeTier500, FeeTier3000, FeeTier10000), )) } } // assertIsNotExistsPoolPath asserts that the pool path does not exist. func assertIsNotExistsPoolPath(instance *poolV1, token0Path, token1Path string, fee uint32) { poolPath := pl.GetPoolPath(token0Path, token1Path, fee) pools := instance.store.GetPools() if pools.Has(poolPath) { panic(newErrorWithDetail( errPoolAlreadyExists, ufmt.Sprintf("expected poolPath(%s:%s:%d) not to exist", token0Path, token1Path, fee), )) } } func assertIsValidTokenOrder(token0Path, token1Path string) { if token0Path >= token1Path { panic(newErrorWithDetail( errInvalidInput, ufmt.Sprintf("expected token0Path(%s) < token1Path(%s)", token0Path, token1Path), )) } }
  6. #6doc.gno
  7. #7// package v1 implements GnoSwap's concentrated liquidity pools based on Uniswap V3. // It manages liquidity positions, executes swaps, and maintains pool state including // price, liquidity, and fee calculations. // // The pool contract is the core of the GnoSwap AMM, supporting: // - Concentrated liquidity within custom price ranges // - Multiple fee tiers (0.01%, 0.05%, 0.3%, 1%) // - Single-tick and cross-tick swaps // - Protocol fee collection // - Tick bitmap optimization for gas efficiency package pool
  8. #8errors.gno
  9. #9package pool import ( ufmt "gno.land/p/nt/ufmt/v0" ) // Error definitions for pool operations const ( errUnsupportedFeeTier = "[GNOSWAP-POOL-001] unsupported fee tier" errPoolAlreadyExists = "[GNOSWAP-POOL-002] pool already created" errOutOfRange = "[GNOSWAP-POOL-003] out of range for numeric value" errInvalidInput = "[GNOSWAP-POOL-004] invalid input data" errDataNotFound = "[GNOSWAP-POOL-005] requested data not found" errLiquidityCalculation = "[GNOSWAP-POOL-006] invalid liquidity calculated" errZeroLiquidity = "[GNOSWAP-POOL-007] zero liquidity" errDuplicateTokenInPool = "[GNOSWAP-POOL-008] same token used in single pool" errTickLowerInvalid = "[GNOSWAP-POOL-009] tickLower is invalid" errTickUpperInvalid = "[GNOSWAP-POOL-010] tickUpper is invalid" errInvalidSwapAmount = "[GNOSWAP-POOL-011] invalid swap amount" errInvalidProtocolFeePct = "[GNOSWAP-POOL-012] invalid protocol fee percentage" errInvalidWithdrawalFeePct = "[GNOSWAP-POOL-013] invalid withdrawal fee percentage" errLockedPool = "[GNOSWAP-POOL-014] cannot modify pool while locked" errPriceOutOfRange = "[GNOSWAP-POOL-015] swap price out of range" errTransferFailed = "[GNOSWAP-POOL-016] token transfer failed" errInvalidTickAndTickSpacing = "[GNOSWAP-POOL-017] invalid tick and tick spacing requested" errInvalidTickRange = "[GNOSWAP-POOL-018] tickLower is greater than or equal to tickUpper" errUnderflow = "[GNOSWAP-POOL-019] underflow" errOverflow = "[GNOSWAP-POOL-020] overflow" errBalanceUpdateFailed = "[GNOSWAP-POOL-021] balance update failed" errNotAccessEOA = "[GNOSWAP-POOL-022] not access EOA" errInsufficientPayment = "[GNOSWAP-POOL-023] insufficient payment" errNotInitializedObservation = "[GNOSWAP-POOL-024] not initialized observation" errObservationTooOld = "[GNOSWAP-POOL-025] target timestamp before oldest observation" errObservationBeforeEpoch = "[GNOSWAP-POOL-026] lookback window starts before unix epoch" errInsufficientPoolBalance = "[GNOSWAP-POOL-027] insufficient pool balance for swap" ) // newErrorWithDetail adds detail to an error message. func newErrorWithDetail(message string, detail string) string { finalErr := ufmt.Errorf("%s || %s", message, detail) return finalErr.Error() } // makeErrorWithDetails creates an error with additional context. func makeErrorWithDetails(message string, details string) error { return ufmt.Errorf("%s || %s", message, details) }
  10. #10event_info.gno
  11. #11package pool import ( "gno.land/p/gnoswap/utils/v1" pl "gno.land/r/gnoswap/pool" ) type tickEventInfo struct { tickID int32 tickInfo pl.TickInfo } // ToString serializes tick event information as a compact JSON object. // The object includes the tick index and every persisted TickInfo field using // the short event keys documented below. // // Returns: // - string: JSON object containing tick, liquidity, fee-growth, cumulative-time, and initialization fields. func (t *tickEventInfo) ToString() string { return "{\"t\":" + utils.FormatInt(t.tickID) + ",\"lg\":\"" + t.tickInfo.LiquidityGross() + "\"" + ",\"ln\":\"" + t.tickInfo.LiquidityNet() + "\"" + ",\"fg0\":\"" + t.tickInfo.FeeGrowthOutside0X128() + "\"" + ",\"fg1\":\"" + t.tickInfo.FeeGrowthOutside1X128() + "\"" + ",\"tco\":\"" + utils.FormatInt(t.tickInfo.TickCumulativeOutside()) + "\"" + ",\"spl\":\"" + t.tickInfo.SecondsPerLiquidityOutsideX128() + "\"" + ",\"so\":\"" + utils.FormatUint(t.tickInfo.SecondsOutside()) + "\"" + ",\"i\":\"" + utils.FormatBool(t.tickInfo.Initialized()) + "\"}" } // NewTickEventInfo creates event data for one tick and its persisted state. // // Parameters: // - tickID: tick index represented by the event. // - tickInfo: tick state whose fields are serialized by ToString. // // Returns: // - *tickEventInfo: event information value retaining the supplied tick index and state. func NewTickEventInfo(tickID int32, tickInfo pl.TickInfo) *tickEventInfo { return &tickEventInfo{ tickID: tickID, tickInfo: tickInfo, } }
  12. #12factory_param.gno
  13. #13package pool import ( "strings" "gno.land/p/gnoswap/consts/v1" u256 "gno.land/p/gnoswap/uint256/v1" ufmt "gno.land/p/nt/ufmt/v0" "gno.land/r/gnoswap/pool" ) const ( FeeTier100 uint32 = 100 FeeTier500 uint32 = 500 FeeTier3000 uint32 = 3000 FeeTier10000 uint32 = 10000 ) const ( MIN_SQRT_RATIO string = "4295128739" MAX_SQRT_RATIO string = "1461446703485210103287273052203988822378723970342" ) // poolCreateConfig holds the essential parameters for creating a new pool. type poolCreateConfig struct { token0Path string token1Path string fee uint32 sqrtPriceX96 *u256.Uint tickSpacing int32 slot0FeeProtocol uint8 } // newPoolParams defines the essential parameters for creating a new pool. func newPoolParams( token0Path string, token1Path string, fee uint32, sqrtPriceX96 string, tickSpacing int32, slot0FeeProtocol uint8, ) *poolCreateConfig { price := u256.MustFromDecimal(sqrtPriceX96) return &poolCreateConfig{ token0Path: token0Path, token1Path: token1Path, fee: fee, sqrtPriceX96: price, tickSpacing: tickSpacing, slot0FeeProtocol: slot0FeeProtocol, } } // Returns: // - sqrtPriceX96: configured initial square-root price as a mutable u256 // integer in Q64.96 representation. func (p *poolCreateConfig) SqrtPriceX96() *u256.Uint { return p.sqrtPriceX96 } // Returns: // - tickSpacing: configured spacing between usable initialized ticks. func (p *poolCreateConfig) TickSpacing() int32 { return p.tickSpacing } // Returns: // - token0Path: configured token0 contract path. func (p *poolCreateConfig) Token0Path() string { return p.token0Path } // Returns: // - token1Path: configured token1 contract path. func (p *poolCreateConfig) Token1Path() string { return p.token1Path } // Returns: // - fee: configured pool fee tier as a uint32 fee-unit value. func (p *poolCreateConfig) Fee() uint32 { return p.fee } func (p *poolCreateConfig) update() error { token0Path := p.token0Path token1Path := p.token1Path // Always validate that the price is within valid range if err := validateSqrtPriceX96(p.sqrtPriceX96); err != nil { return err } if !p.isInOrder() { token0Path, token1Path = token1Path, token0Path // newPrice = 2^192 / oldPrice newPrice := u256.Zero().Div(consts.Q192(), p.sqrtPriceX96) // Check if calculated price is within valid range if err := validateSqrtPriceX96(newPrice); err != nil { return err } p.sqrtPriceX96 = newPrice } p.token0Path = token0Path p.token1Path = token1Path return nil } // isInOrder checks if token paths are in lexicographical (or, alphabetical) order func (p *poolCreateConfig) isInOrder() bool { if strings.Compare(p.token0Path, p.token1Path) < 0 { return true } return false } func (p *poolCreateConfig) poolPath() string { return pool.GetPoolPath(p.token0Path, p.token1Path, p.fee) } // validateSqrtPriceX96 validates that the given sqrtPriceX96 is within valid range func validateSqrtPriceX96(sqrtPriceX96 *u256.Uint) error { // Valid range is [minSqrtRatio, maxSqrtRatio) - same as TickMathGetTickAtSqrtRatio if sqrtPriceX96.Lt(consts.MinSqrtRatio()) || sqrtPriceX96.Gte(consts.MaxSqrtRatio()) { return makeErrorWithDetails( errOutOfRange, ufmt.Sprintf("sqrtPriceX96(%s) is out of range", sqrtPriceX96.ToString()), ) } return nil } func isValidFeeTier(feeTier uint32) bool { switch feeTier { case FeeTier100, FeeTier500, FeeTier3000, FeeTier10000: return true } return false }
  14. #14getter.gno
  15. #15package pool import ( "errors" "time" u256 "gno.land/p/gnoswap/uint256/v1" rotree "gno.land/p/nt/bptree/rotree/v0" ufmt "gno.land/p/nt/ufmt/v0" pl "gno.land/r/gnoswap/pool" ) // GetPoolPath generates a unique pool path string based on the token paths and fee tier. // Parameters: // - token0Path: first token contract path; pool paths canonicalize token order. // - token1Path: second token contract path; pool paths canonicalize token order. // - fee: fee tier encoded in the pool identifier. // // Returns: // - poolPath: canonical token0:token1:fee pool identifier. func GetPoolPath(token0Path, token1Path string, fee uint32) string { return pl.GetPoolPath(token0Path, token1Path, fee) } // GetFeeAmountTickSpacing retrieves the tick spacing associated with a given fee amount. // Parameters: // - fee: fee tier whose configured tick spacing is requested. // // Returns: // - spacing: configured signed tick interval for fee. // - err: nil when fee is configured; an unsupported-fee error otherwise. func (i *poolV1) GetFeeAmountTickSpacing(fee uint32) (spacing int32, err error) { feeAmountTickSpacing := i.store.GetFeeAmountTickSpacing() spacing, exist := feeAmountTickSpacing[fee] if !exist { return 0, errors.New(newErrorWithDetail( errUnsupportedFeeTier, ufmt.Sprintf("expected fee(%d) to be one of %d, %d, %d, %d", fee, FeeTier100, FeeTier500, FeeTier3000, FeeTier10000), )) } return spacing, nil } // Parameters: // - poolPath: canonical pool identifier to look up. // // Returns: // - token0Path: canonical token0 contract path stored in the pool. // - err: nil when the pool exists; the pool lookup error otherwise. func (i *poolV1) GetToken0Path(poolPath string) (string, error) { pool, err := i.getPool(poolPath) if err != nil { return "", err } return pool.Token0Path(), nil } // Parameters: // - poolPath: canonical pool identifier to look up. // // Returns: // - token1Path: canonical token1 contract path stored in the pool. // - err: nil when the pool exists; the pool lookup error otherwise. func (i *poolV1) GetToken1Path(poolPath string) (string, error) { pool, err := i.getPool(poolPath) if err != nil { return "", err } return pool.Token1Path(), nil } // Parameters: // - poolPath: canonical pool identifier to look up. // // Returns: // - fee: configured fee tier stored in the pool. // - err: nil when the pool exists; the pool lookup error otherwise. func (i *poolV1) GetFee(poolPath string) (uint32, error) { pool, err := i.getPool(poolPath) if err != nil { return 0, err } return pool.Fee(), nil } // Parameters: // - poolPath: canonical pool identifier to look up. // // Returns: // - balanceToken0: pool-held token0 balance in the token's smallest unit. // - err: nil when the pool exists; the pool lookup error otherwise. func (i *poolV1) GetBalanceToken0(poolPath string) (int64, error) { pool, err := i.getPool(poolPath) if err != nil { return 0, err } return pool.BalanceToken0(), nil } // Parameters: // - poolPath: canonical pool identifier to look up. // // Returns: // - balanceToken1: pool-held token1 balance in the token's smallest unit. // - err: nil when the pool exists; the pool lookup error otherwise. func (i *poolV1) GetBalanceToken1(poolPath string) (int64, error) { pool, err := i.getPool(poolPath) if err != nil { return 0, err } return pool.BalanceToken1(), nil } // Parameters: // - poolPath: canonical pool identifier to look up. // // Returns: // - tickSpacing: configured signed interval between usable initialized ticks. // - err: nil when the pool exists; the pool lookup error otherwise. func (i *poolV1) GetTickSpacing(poolPath string) (int32, error) { pool, err := i.getPool(poolPath) if err != nil { return 0, err } return pool.TickSpacing(), nil } // Parameters: // - poolPath: canonical pool identifier to look up. // // Returns: // - maxLiquidity: decimal representation of the maximum liquidity permitted // at one tick for the pool's tick spacing. // - err: nil when the pool exists; the pool lookup error otherwise. func (i *poolV1) GetMaxLiquidityPerTick(poolPath string) (string, error) { pool, err := i.getPool(poolPath) if err != nil { return "", err } return calculateMaxLiquidityPerTick(pool.TickSpacing()).ToString(), nil } // Parameters: // - poolPath: canonical pool identifier to look up. // // Returns: // - feeProtocol: packed token0/token1 protocol-fee denominator configuration // stored in slot0. // - err: nil when the pool exists; the pool lookup error otherwise. func (i *poolV1) GetSlot0FeeProtocol(poolPath string) (uint8, error) { pool, err := i.getPool(poolPath) if err != nil { return 0, err } return pool.Slot0FeeProtocol(), nil } // Parameters: // - poolPath: canonical pool identifier to look up. // // Returns: // - unlocked: true when the pool's slot0 reentrancy lock is open. // - err: nil when the pool exists; the pool lookup error otherwise. func (i *poolV1) GetSlot0Unlocked(poolPath string) (bool, error) { pool, err := i.getPool(poolPath) if err != nil { return false, err } return pool.Slot0Unlocked(), nil } // Parameters: // - poolPath: canonical pool identifier to look up. // // Returns: // - slot0: current pool price, tick, observation, and lock metadata; panics // when poolPath does not identify an existing pool. func (i *poolV1) GetSlot0(poolPath string) pl.Slot0 { return i.mustGetPool(poolPath).Slot0() } // Parameters: // - poolPath: canonical pool identifier containing the observation ring. // - index: observation slot index; in-range unwritten slots return a default // observation, while indices at or above maxObservationCardinality error. // // Returns: // - observation: observation stored at index, or the default zero observation // for an in-range slot that has never been written. // - err: nil for a valid pool and index; a pool, observation, or range error // otherwise. func (i *poolV1) GetObservationAt(poolPath string, index uint16) (pl.Observation, error) { if _, err := i.getPool(poolPath); err != nil { return pl.DefaultObservation(), err } // Uniswap's observations(uint256) getter is backed by a fixed-size array: // an in-range but never-written slot returns the zero observation, while an // out-of-range index reverts. if index >= maxObservationCardinality { return pl.DefaultObservation(), makeErrorWithDetails(errDataNotFound, ufmt.Sprintf("observation index %d out of range", index)) } observations, err := i.getObservations(poolPath) if err != nil { return pl.DefaultObservation(), err } observation, ok := observations.Get(index) if !ok { return pl.DefaultObservation(), nil } return observation, nil } // Observe returns the tick and seconds-per-liquidity cumulatives for each // requested lookback. It reads the current block timestamp once and never // writes an observation, matching Uniswap V3's view-only observe call. // Parameters: // - poolPath: canonical pool identifier whose observation history is queried. // - secondsAgos: lookback durations in seconds; one cumulative pair is // produced for each entry, with zero meaning the current cumulative value. // // Returns: // - tickCumulatives: cumulative tick values aligned by index with secondsAgos. // - secondsPerLiquidityCumulativeX128s: cumulative seconds-per-liquidity // values aligned with secondsAgos and scaled by 2^128 as decimal strings. // - err: nil when every requested lookback can be resolved; an observation // history, epoch, cardinality, or interpolation error otherwise. func (i *poolV1) Observe(poolPath string, secondsAgos []uint32) ([]int64, []string, error) { pool, err := i.getPool(poolPath) if err != nil { return nil, nil, err } observations, err := i.getObservations(poolPath) if err != nil { return nil, nil, err } slot0 := pool.Slot0() return observe( observations, time.Now().Unix(), secondsAgos, slot0.Tick(), slot0.ObservationIndex(), pool.Liquidity(), slot0.ObservationCardinality(), ) } // SnapshotCumulativesInside returns the oracle accumulators that accrued while // the pool price was inside [tickLower, tickUpper). // Parameters: // - poolPath: canonical pool identifier whose oracle accumulators are queried. // - tickLower: inclusive lower tick boundary of the range. // - tickUpper: exclusive upper tick boundary of the range. // // Returns: // - tickCumulativeInside: cumulative tick value accrued while price was // inside [tickLower, tickUpper). // - secondsPerLiquidityInsideX128: seconds-per-liquidity accumulator accrued // inside the range, scaled by 2^128. // - secondsInside: number of seconds during which price was inside the range. // - err: nil when bounds, boundary ticks, and observations are valid; the // corresponding validation or observation error otherwise. func (i *poolV1) SnapshotCumulativesInside( poolPath string, tickLower int32, tickUpper int32, ) (int64, *u256.Uint, uint32, error) { pool, err := i.getPool(poolPath) if err != nil { return 0, nil, 0, err } observations, err := i.getObservations(poolPath) if err != nil { return 0, nil, 0, err } return snapshotCumulativesInside(pool, observations, tickLower, tickUpper) } // Parameters: // - poolPath: canonical pool identifier to look up. // // Returns: // - feeGrowthGlobal0X128: global token0 fee-growth accumulator scaled by 2^128. // - err: nil when the pool exists; the pool lookup error otherwise. func (i *poolV1) GetFeeGrowthGlobal0X128(poolPath string) (*u256.Uint, error) { pool, err := i.getPool(poolPath) if err != nil { return nil, err } return pool.FeeGrowthGlobal0X128(), nil } // Parameters: // - poolPath: canonical pool identifier to look up. // // Returns: // - feeGrowthGlobal1X128: global token1 fee-growth accumulator scaled by 2^128. // - err: nil when the pool exists; the pool lookup error otherwise. func (i *poolV1) GetFeeGrowthGlobal1X128(poolPath string) (*u256.Uint, error) { pool, err := i.getPool(poolPath) if err != nil { return nil, err } return pool.FeeGrowthGlobal1X128(), nil } // Parameters: // - poolPath: canonical pool identifier to look up. // // Returns: // - protocolFeesToken0: protocol fees accrued in token0's smallest unit. // - err: nil when the pool exists; the pool lookup error otherwise. func (i *poolV1) GetProtocolFeesToken0(poolPath string) (int64, error) { pool, err := i.getPool(poolPath) if err != nil { return 0, err } return pool.ProtocolFeesToken0(), nil } // Parameters: // - poolPath: canonical pool identifier to look up. // // Returns: // - protocolFeesToken1: protocol fees accrued in token1's smallest unit. // - err: nil when the pool exists; the pool lookup error otherwise. func (i *poolV1) GetProtocolFeesToken1(poolPath string) (int64, error) { pool, err := i.getPool(poolPath) if err != nil { return 0, err } return pool.ProtocolFeesToken1(), nil } // Parameters: // - poolPath: canonical pool identifier to look up. // // Returns: // - liquidity: current active pool liquidity as a u256 integer. // - err: nil when the pool exists; the pool lookup error otherwise. func (i *poolV1) GetLiquidity(poolPath string) (*u256.Uint, error) { pool, err := i.getPool(poolPath) if err != nil { return nil, err } return pool.Liquidity(), nil } // Parameters: // - poolPath: canonical pool identifier containing the position. // - key: position key identifying its lower and upper tick range. // // Returns: // - position: pointer to a copy of the stored position information; panics // when the pool or position is absent or has an unexpected type. func (i *poolV1) MustGetPosition(poolPath, key string) *pl.PositionInfo { pool := i.mustGetPool(poolPath) positions := pool.Positions() result := positions.Get(key) if result == nil { panic(newErrorWithDetail( errDataNotFound, ufmt.Sprintf("expected position(%s) to exist", key), )) } position, ok := result.(pl.PositionInfo) if !ok { panic("failed to cast position to PositionInfo") } return &position } // Parameters: // - poolPath: canonical pool identifier containing the position. // - key: position key identifying its lower and upper tick range. // // Returns: // - feeGrowthInside0LastX128: token0 fee-growth-inside value last accounted // for the position, encoded as a decimal integer scaled by 2^128. // - err: nil when pool and position exist; the lookup error otherwise. func (i *poolV1) GetPositionFeeGrowthInside0LastX128(poolPath, key string) (string, error) { pool, err := i.getPool(poolPath) if err != nil { return "", err } position, err := pool.GetPosition(key) if err != nil { return "", err } return position.FeeGrowthInside0LastX128(), nil } // Parameters: // - poolPath: canonical pool identifier containing the position. // - key: position key identifying its lower and upper tick range. // // Returns: // - feeGrowthInside1LastX128: token1 fee-growth-inside value last accounted // for the position, encoded as a decimal integer scaled by 2^128. // - err: nil when pool and position exist; the lookup error otherwise. func (i *poolV1) GetPositionFeeGrowthInside1LastX128(poolPath, key string) (string, error) { pool, err := i.getPool(poolPath) if err != nil { return "", err } position, err := pool.GetPosition(key) if err != nil { return "", err } return position.FeeGrowthInside1LastX128(), nil } // Parameters: // - poolPath: canonical pool identifier containing the position. // - key: position key identifying its lower and upper tick range. // // Returns: // - tokensOwed0: token0 fees owed to the position in token0's smallest unit. // - err: nil when pool and position exist; the lookup error otherwise. func (i *poolV1) GetPositionTokensOwed0(poolPath, key string) (int64, error) { pool, err := i.getPool(poolPath) if err != nil { return 0, err } position, err := pool.GetPosition(key) if err != nil { return 0, err } return position.TokensOwed0(), nil } // Parameters: // - poolPath: canonical pool identifier containing the position. // - key: position key identifying its lower and upper tick range. // // Returns: // - tokensOwed1: token1 fees owed to the position in token1's smallest unit. // - err: nil when pool and position exist; the lookup error otherwise. func (i *poolV1) GetPositionTokensOwed1(poolPath, key string) (int64, error) { pool, err := i.getPool(poolPath) if err != nil { return 0, err } position, err := pool.GetPosition(key) if err != nil { return 0, err } return position.TokensOwed1(), nil } // Parameters: // - poolPath: canonical pool identifier containing the tick. // - tick: signed tick index whose gross liquidity is requested. // // Returns: // - liquidityGross: total liquidity referencing the tick, encoded as a decimal // integer string. // - err: nil when pool and tick exist; the lookup error otherwise. func (i *poolV1) GetTickLiquidityGross(poolPath string, tick int32) (string, error) { pool, err := i.getPool(poolPath) if err != nil { return "", err } tickInfo, err := pool.GetTick(tick) if err != nil { return "", err } return tickInfo.LiquidityGross(), nil } // Parameters: // - poolPath: canonical pool identifier containing the tick. // - tick: signed tick index whose net liquidity change is requested. // // Returns: // - liquidityNet: signed net liquidity change applied when crossing the tick, // encoded as a decimal integer string. // - err: nil when pool and tick exist; the lookup error otherwise. func (i *poolV1) GetTickLiquidityNet(poolPath string, tick int32) (string, error) { pool, err := i.getPool(poolPath) if err != nil { return "", err } tickInfo, err := pool.GetTick(tick) if err != nil { return "", err } return tickInfo.LiquidityNet(), nil } // Parameters: // - poolPath: canonical pool identifier containing the tick. // - tick: signed tick index whose token0 fee-growth-outside value is requested. // // Returns: // - feeGrowthOutside0X128: token0 fee-growth-outside accumulator scaled by // 2^128, encoded as a decimal string. // - err: nil when pool and tick exist; the lookup error otherwise. func (i *poolV1) GetTickFeeGrowthOutside0X128(poolPath string, tick int32) (string, error) { pool, err := i.getPool(poolPath) if err != nil { return "", err } tickInfo, err := pool.GetTick(tick) if err != nil { return "", err } return tickInfo.FeeGrowthOutside0X128(), nil } // Parameters: // - poolPath: canonical pool identifier containing the tick. // - tick: signed tick index whose token1 fee-growth-outside value is requested. // // Returns: // - feeGrowthOutside1X128: token1 fee-growth-outside accumulator scaled by // 2^128, encoded as a decimal string. // - err: nil when pool and tick exist; the lookup error otherwise. func (i *poolV1) GetTickFeeGrowthOutside1X128(poolPath string, tick int32) (string, error) { pool, err := i.getPool(poolPath) if err != nil { return "", err } tickInfo, err := pool.GetTick(tick) if err != nil { return "", err } return tickInfo.FeeGrowthOutside1X128(), nil } // Parameters: // - poolPath: canonical pool identifier containing the tick. // - tick: signed tick index whose token fee-growth-outside values are requested. // // Returns: // - feeGrowthOutside0X128: token0 fee-growth-outside accumulator scaled by 2^128, // encoded as a decimal string. // - feeGrowthOutside1X128: token1 fee-growth-outside accumulator scaled by 2^128, // encoded as a decimal string. // - err: nil when pool and tick exist; the lookup error otherwise. func (i *poolV1) GetTickFeeGrowthOutsideX128(poolPath string, tick int32) (string, string, error) { pool, err := i.getPool(poolPath) if err != nil { return "", "", err } tickInfo, err := pool.GetTick(tick) if err != nil { return "", "", err } return tickInfo.FeeGrowthOutside0X128(), tickInfo.FeeGrowthOutside1X128(), nil } // Parameters: // - poolPath: canonical pool identifier containing the tick. // - tick: signed tick index whose cumulative tick-outside value is requested. // // Returns: // - tickCumulativeOutside: cumulative tick value recorded outside the tick's // active range. // - err: nil when pool and tick exist; the lookup error otherwise. func (i *poolV1) GetTickCumulativeOutside(poolPath string, tick int32) (int64, error) { pool, err := i.getPool(poolPath) if err != nil { return 0, err } tickInfo, err := pool.GetTick(tick) if err != nil { return 0, err } return tickInfo.TickCumulativeOutside(), nil } // Parameters: // - poolPath: canonical pool identifier containing the tick. // - tick: signed tick index whose seconds-per-liquidity accumulator is // requested. // // Returns: // - secondsPerLiquidityOutsideX128: seconds-per-liquidity-outside accumulator // encoded as a decimal integer scaled by 2^128. // - err: nil when pool and tick exist; the lookup error otherwise. func (i *poolV1) GetTickSecondsPerLiquidityOutsideX128(poolPath string, tick int32) (string, error) { pool, err := i.getPool(poolPath) if err != nil { return "", err } tickInfo, err := pool.GetTick(tick) if err != nil { return "", err } return tickInfo.SecondsPerLiquidityOutsideX128(), nil } // Parameters: // - poolPath: canonical pool identifier containing the tick. // - tick: signed tick index whose seconds-outside accumulator is requested. // // Returns: // - secondsOutside: seconds elapsed outside the tick's side of the active // range, as recorded by the pool. // - err: nil when pool and tick exist; the lookup error otherwise. func (i *poolV1) GetTickSecondsOutside(poolPath string, tick int32) (uint32, error) { pool, err := i.getPool(poolPath) if err != nil { return 0, err } tickInfo, err := pool.GetTick(tick) if err != nil { return 0, err } return tickInfo.SecondsOutside(), nil } // Parameters: // - poolPath: canonical pool identifier containing the tick. // - tick: signed tick index whose initialization flag is requested. // // Returns: // - initialized: true when the tick has been initialized in the pool. // - err: nil when pool and tick can be read; the lookup error otherwise. func (i *poolV1) GetTickInitialized(poolPath string, tick int32) (bool, error) { pool, err := i.getPool(poolPath) if err != nil { return false, err } tickInfo, err := pool.GetTick(tick) if err != nil { return false, err } return tickInfo.Initialized(), nil } // Parameters: // - poolPath: canonical pool identifier to look up. // // Returns: // - tick: current signed slot0 tick. // - err: nil when the pool exists; the pool lookup error otherwise. func (i *poolV1) GetSlot0Tick(poolPath string) (int32, error) { pool, err := i.getPool(poolPath) if err != nil { return 0, err } return pool.Slot0Tick(), nil } // Parameters: // - poolPath: canonical pool identifier to look up. // // Returns: // - sqrtPriceX96: current square-root price as a u256 Q64.96 integer. // - err: nil when the pool exists; the pool lookup error otherwise. func (i *poolV1) GetSlot0SqrtPriceX96(poolPath string) (*u256.Uint, error) { pool, err := i.getPool(poolPath) if err != nil { return nil, err } return pool.Slot0SqrtPriceX96(), nil } // Parameters: // - poolPath: canonical pool identifier to look up. // // Returns: // - feeGrowthGlobal0X128: global token0 fee-growth accumulator scaled by 2^128. // - feeGrowthGlobal1X128: global token1 fee-growth accumulator scaled by 2^128. // - err: nil when the pool exists; the pool lookup error otherwise. func (i *poolV1) GetFeeGrowthGlobalX128(poolPath string) (*u256.Uint, *u256.Uint, error) { pool, err := i.getPool(poolPath) if err != nil { return nil, nil, err } return pool.FeeGrowthGlobal0X128(), pool.FeeGrowthGlobal1X128(), nil } // Parameters: // - poolPath: canonical pool identifier containing the position. // - key: position key identifying its lower and upper tick range. // // Returns: // - feeGrowthInside0LastX128: token0 fee-growth-inside value last accounted // for the position, encoded as a decimal integer scaled by 2^128. // - feeGrowthInside1LastX128: token1 fee-growth-inside value last accounted // for the position, encoded as a decimal integer scaled by 2^128. // - err: nil when pool and position exist; the lookup error otherwise. func (i *poolV1) GetPositionFeeGrowthInsideLastX128(poolPath, key string) (string, string, error) { pool, err := i.getPool(poolPath) if err != nil { return "", "", err } position, err := pool.GetPosition(key) if err != nil { return "", "", err } return position.FeeGrowthInside0LastX128(), position.FeeGrowthInside1LastX128(), nil } // Parameters: // - poolPath: canonical pool identifier containing the position. // - key: position key identifying its lower and upper tick range. // // Returns: // - liquidity: position liquidity encoded as a decimal integer string. // - err: nil when pool and position exist; the lookup error otherwise. func (i *poolV1) GetPositionLiquidity(poolPath, key string) (string, error) { pool, err := i.getPool(poolPath) if err != nil { return "", err } position, err := pool.GetPosition(key) if err != nil { return "", err } return position.Liquidity(), nil } // Parameters: // - poolPath: canonical pool identifier whose existence is checked. // // Returns: // - exists: true when the pool tree contains poolPath. func (i *poolV1) ExistsPoolPath(poolPath string) bool { pools := i.store.GetPools() return pools.Has(poolPath) } // Returns: // - poolCreationFee: configured pool-creation charge in the chain's smallest // currency unit. func (i *poolV1) GetPoolCreationFee() int64 { return i.store.GetPoolCreationFee() } // Returns: // - withdrawalFeeBPS: configured withdrawal fee in basis points, where 100 // basis points equals one percent. func (i *poolV1) GetWithdrawalFee() uint64 { return i.store.GetWithdrawalFeeBPS() } // OracleConsult returns the time-weighted average price for a pool over a specified period. // // Parameters: // - poolPath: canonical pool identifier whose observations are consulted. // - secondsAgo: lookback duration in seconds for the time-weighted query. // // Returns: // - arithmeticMeanTick: arithmetic mean tick over the requested lookback. // - harmonicMeanLiquidity: harmonic mean active liquidity as a u256 integer. // - err: nil when the pool history covers secondsAgo; an observation or // lookback error otherwise. func (i *poolV1) OracleConsult(poolPath string, secondsAgo uint32) (int32, *u256.Uint, error) { pool, err := i.getPool(poolPath) if err != nil { return 0, nil, err } observations, err := i.getObservations(poolPath) if err != nil { return 0, nil, err } tick, liquidity, err := oracleConsult(pool, observations, secondsAgo) if err != nil { return 0, nil, err } return tick, liquidity, nil } // GetPools returns a read-only view of every pool, keyed by pool path. // Callers paginate it themselves through IterateByOffset. // Returns: // - pools: read-only tree keyed by canonical pool path; entries are exposed // through safe read-only wrappers and callers paginate the tree themselves. func (i *poolV1) GetPools() *rotree.ReadOnlyTree { return rotree.Wrap(i.store.GetPools(), clonePoolEntry) } // GetFeeAmountTickSpacings returns all fee tier to tick spacing mappings. // Returns: // - feeAmountTickSpacings: copy of the configured fee-tier to tick-spacing // mapping. func (i *poolV1) GetFeeAmountTickSpacings() map[uint32]int32 { return i.store.GetFeeAmountTickSpacing() } // GetPoolPositions returns a read-only view of a pool's positions, keyed by // position key. Callers paginate it themselves through IterateByOffset. // nil is returned when the pool does not exist. // Parameters: // - poolPath: canonical pool identifier whose positions are exposed. // // Returns: // - positions: read-only tree keyed by position key, or nil when poolPath // does not identify an existing pool. func (i *poolV1) GetPoolPositions(poolPath string) *rotree.ReadOnlyTree { pool, err := i.getPool(poolPath) if err != nil { return nil } // makeEntrySafeFn is a function that makes an entry safe to read. // But PositionInfo is immutable, so we can just return the entry as is. return rotree.Wrap(pool.Positions(), nil) } // Tick enumeration // GetInitializedTicksInRange returns initialized ticks within the given range. // Parameters: // - poolPath: canonical pool identifier whose initialized ticks are listed. // - tickLower: inclusive lower tick bound for enumeration. // - tickUpper: inclusive upper tick bound for enumeration. // // Returns: // - ticks: initialized tick indices in the requested inclusive range, in // encoded tree order; empty when no ticks are initialized there. // - err: nil when the pool exists; the pool lookup error otherwise. func (i *poolV1) GetInitializedTicksInRange(poolPath string, tickLower, tickUpper int32) ([]int32, error) { pool, err := i.getPool(poolPath) if err != nil { return nil, err } ticks := make([]int32, 0) pool.IterateTicks(tickLower, tickUpper, func(tick int32, _ pl.TickInfo) bool { ticks = append(ticks, tick) return false }) return ticks, nil } // Structure getters // GetTickInfo returns the tick info for a given tick. // Parameters: // - poolPath: canonical pool identifier containing the tick. // - tick: signed tick index whose full state is requested. // // Returns: // - tickInfo: initialized state and outside accumulators for tick. // - err: nil when the pool and tick can be read; the corresponding lookup // error otherwise. func (i *poolV1) GetTickInfo(poolPath string, tick int32) (pl.TickInfo, error) { pool, err := i.getPool(poolPath) if err != nil { return pl.NewTickInfo(), err } return pool.GetTick(tick) } // GetTickBitmaps returns the tick bitmap for a given word position. // Parameters: // - poolPath: canonical pool identifier whose bitmap map is queried. // - wordPos: signed bitmap-word position to retrieve. // // Returns: // - bitmap: decimal string encoding the initialized-tick bitmap word at // wordPos. // - err: nil when the pool and bitmap word exist; the pool lookup or missing // bitmap error otherwise. func (i *poolV1) GetTickBitmaps(poolPath string, wordPos int16) (string, error) { pool, err := i.getPool(poolPath) if err != nil { return "", err } tickBitmap, ok := pool.TickBitmaps()[wordPos] if !ok { return "", ufmt.Errorf("tick bitmap %d not found", wordPos) } return tickBitmap, nil }
  16. #16getter_utils.gno
  17. #17package pool import ( pl "gno.land/r/gnoswap/pool" ) // The functions below are makeEntrySafeFn transformations handed to // rotree.Wrap. They run on every read through a read-only view, so each one // returns a value the caller may hold without reaching realm-owned state. // clonePoolEntry is the transformation for the pool tree. Pool is mutable and // its setters are declared in /r/gnoswap/pool, so an external holder of the // stored pointer could write realm state through it. // // Pool.Clone leaves the pool's collections nil; see it for which lookups read // them instead. func clonePoolEntry(value any) any { pool, ok := value.(*pl.Pool) if !ok { return nil } return pool.Clone() }
  18. #18gnomod.toml
  19. #19module = "gno.land/r/gnoswap/pool/v1" gno = "0.9"
  20. #20init.gno
  21. #21package pool import ( ufmt "gno.land/p/nt/ufmt/v0" "gno.land/r/gnoswap/access/v1" "gno.land/r/gnoswap/emission" "gno.land/r/gnoswap/pool" ) const ( // slot0FeeProtocol is the protocol-fee denominator (0 or 4-10). // Zero disables protocol fees; 4 routes 1/4 of swap fees to the protocol. // This parameter can be modified through governance. defaultSlot0FeeProtocol = uint8(0) // poolCreationFee is the fee that is charged when a user creates a pool. // The fee is denominated in GNS tokens. // This parameter can be modified through governance. defaultPoolCreationFee = int64(100_000_000) // 100_GNS // withdrawalFeeBPS is the fee that is charged when a user withdraws their collected fees // The fee is denominated in BPS (Basis Points) // Example: 100 BPS = 1% // This parameter can be modified through governance. defaultWithdrawalFeeBPS = uint64(100) defaultUnlocked = true ) func init(cur realm) { registerPoolV1(cur) } func registerPoolV1(cur realm) { pool.RegisterInitializer(cross(cur), func(_ int, rlm realm, poolStore pool.IPoolStore) pool.IPool { access.AssertIsRlmCurrent(0, rlm) err := initStoreData(0, rlm, poolStore) if err != nil { panic(err) } emission.SetDefaultInitialPoolChecker(cross(rlm), func(poolPath string) bool { return pool.ExistsPoolPath(poolPath) }) return NewPoolV1(poolStore) }) } func initStoreData(_ int, rlm realm, poolStore pool.IPoolStore) error { if !poolStore.HasPools() { err := poolStore.SetPools(0, rlm, pool.NewPoolsTree()) if err != nil { return err } } if !poolStore.HasObservations() { err := poolStore.SetObservations(0, rlm, pool.NewObservationsTree()) if err != nil { return err } } if err := validatePoolObservations(poolStore); err != nil { return err } if !poolStore.HasFeeAmountTickSpacing() { err := poolStore.SetFeeAmountTickSpacing(0, rlm, pool.NewDefaultFeeAmountTickSpacing()) if err != nil { return err } } if !poolStore.HasPoolCreationFee() { err := poolStore.SetPoolCreationFee(0, rlm, defaultPoolCreationFee) if err != nil { return err } } pendingProtocolFees := make(map[string]int64) if poolStore.HasPendingProtocolFees() { pendingProtocolFees = poolStore.GetPendingProtocolFees() } if err := poolStore.SetPendingProtocolFees(0, rlm, pendingProtocolFees); err != nil { return err } if !poolStore.HasSlot0FeeProtocol() { err := poolStore.SetSlot0FeeProtocol(0, rlm, defaultSlot0FeeProtocol) if err != nil { return err } } if !poolStore.HasWithdrawalFeeBPS() { err := poolStore.SetWithdrawalFeeBPS(0, rlm, defaultWithdrawalFeeBPS) if err != nil { return err } } if !poolStore.HasUnlocked() { err := poolStore.SetUnlocked(0, rlm, defaultUnlocked) if err != nil { return err } } // Initialize swap start hook with no-op function if !poolStore.HasSwapStartHook() { noopSwapStart := func(cur realm, poolPath string, timestamp int64) {} err := poolStore.SetSwapStartHook(0, rlm, noopSwapStart) if err != nil { return err } } // Initialize swap end hook with no-op function if !poolStore.HasSwapEndHook() { noopSwapEnd := func(cur realm, poolPath string) error { return nil } err := poolStore.SetSwapEndHook(0, rlm, noopSwapEnd) if err != nil { return err } } // Initialize tick cross hook with no-op function if !poolStore.HasTickCrossHook() { noopTickCross := func(cur realm, poolPath string, tickId int32, zeroForOne bool, timestamp int64) {} err := poolStore.SetTickCrossHook(0, rlm, noopTickCross) if err != nil { return err } } return nil } // validatePoolObservations ensures every persisted pool owns an observation tree. // CreatePool initializes the pair in one transaction; this check catches an // implementation or upgrade that would otherwise leave a pool unusable later. func validatePoolObservations(poolStore pool.IPoolStore) error { pools := poolStore.GetPools() observations := poolStore.GetObservations() var validationErr error pools.Iterate("", "", func(poolPath string, _ any) bool { if !observations.Has(poolPath) { validationErr = ufmt.Errorf("expected observations for poolPath(%s) to exist", poolPath) return true } return false }) return validationErr }
  22. #22instance.gno
  23. #23package pool import ( "gno.land/r/gnoswap/pool" ) type poolV1 struct { store pool.IPoolStore } // NewPoolV1 constructs the version-one pool implementation around shared // persistent storage. // // Parameters: // - store: pool-domain storage implementation used by the returned API. // // Returns: // - implementation: IPool backed by store. func NewPoolV1(store pool.IPoolStore) pool.IPool { return &poolV1{ store: store, } }
  24. #24lock.gno

Result log

msg:0,success:true,log:,events:[]