PathrockNetwork Gno Explorer
HomeBlocksTransactionsTokensRealmsPackagesValidatorsAnalytics

PathrockNetwork Gno Explorer — an independent explorer for Gno.land Mainnet (gnoland-1), operated by PathrockNetwork. Not an official Gno.land service.

gnowebarchive RPC

gno.land/r/gnoswap/pool/v1

Realm
Open in gnoweb ↗

Overview

Kind
Realm (renderable)
Name
v1
Namespace
gnoswap / pool
Files
24 (README)(gnomod.toml)
Exported functions
3
Module
gno.land/r/gnoswap/pool/v1
gno
0.9

Files (24)

  • README.mdmarkdown
  • gnomod.tomltoml
  • assert.gnogno
  • doc.gnogno
  • errors.gnogno
  • event_info.gnogno
  • factory_param.gnogno
  • getter_utils.gnogno
  • getter.gnogno
  • init.gnogno
  • instance.gnogno
  • lock.gnogno
  • manager.gnogno
  • oracle.gnogno
  • pool.gnogno
  • position.gnogno
  • protocol_fee.gnogno
  • render.gnogno
  • swap.gnogno
  • tick_bitmap.gnogno
  • tick.gnogno
  • transfer.gnogno
  • type.gnogno
  • utils.gnogno
  • swap.gnogno
    1package pool23import (4	"chain"5	"errors"6	"strconv"7	"time"8

    Functions

    • 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.

    Rendered

    RenderedRawgnoweb ↗

    Render unavailable

    rate limit exceeded — the render query is served by the Gnoland archive RPC.

    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.

    9 ufmt "gno.land/p/nt/ufmt/v0"
    10 "gno.land/r/gnoswap/access/v1"
    11 "gno.land/r/gnoswap/halt/v1"
    12
    13 "gno.land/p/gnoswap/consts/v1"
    14 "gno.land/p/gnoswap/gnsmath/v1"
    15 i256 "gno.land/p/gnoswap/int256/v1"
    16 u256 "gno.land/p/gnoswap/uint256/v1"
    17 "gno.land/p/gnoswap/utils/v1"
    18
    19 pl "gno.land/r/gnoswap/pool"
    20)
    21
    22// Hook functions allow external contracts to be notified of swap events.
    23var (
    24 // MUST BE IMMUTABLE.
    25 // DO NOT USE THIS VALUE IN ANY ARITHMETIC OPERATIONS' INITIALIZATION
    26 zero = u256.Zero()
    27 zeroI256 = i256.Zero() /* readonly */
    28 fixedPointQ128 = u256.MustFromDecimal(Q128)
    29
    30 maxInt256 = u256.MustFromDecimal(MAX_INT256)
    31 maxInt64 = i256.Zero().SetInt64(INT64_MAX)
    32 minInt64 = i256.Zero().SetInt64(INT64_MIN)
    33)
    34
    35// SetTickCrossHook sets the hook function called when a tick is crossed during swaps.
    36//
    37// Allows staker to monitor liquidity changes at price levels.
    38// Used for reward calculation when positions enter/exit range.
    39//
    40// Only callable by staker contract.
    41// Parameters:
    42// - _: Leading integer discriminator for the forwarded realm call; callers
    43// pass 0.
    44// - rlm: Propagated realm context validated as current before the hook is
    45// stored.
    46// - hook: Callback invoked after an initialized tick is crossed with the
    47// current pool realm, pool path, tick index, swap direction, and timestamp.
    48func (i *poolV1) SetTickCrossHook(_ int, rlm realm, hook func(cur realm, poolPath string, tickId int32, zeroForOne bool, timestamp int64)) {
    49 access.AssertIsRlmCurrent(0, rlm)
    50
    51 i.assertPoolUnlocked()
    52
    53 caller := rlm.Previous().Address()
    54 access.AssertIsStaker(caller)
    55
    56 i.lockPool(0, rlm)
    57 defer i.unlockPool(0, rlm)
    58
    59 err := i.store.SetTickCrossHook(0, rlm, hook)
    60 if err != nil {
    61 panic(err)
    62 }
    63}
    64
    65// SetSwapStartHook sets the hook function called at the beginning of a swap.
    66//
    67// Enables pre-swap state tracking for reward distribution.
    68// Captures timestamp for time-weighted calculations.
    69//
    70// Only callable by staker contract.
    71//
    72// Parameters:
    73// - _: Leading integer discriminator for the forwarded realm call; callers
    74// pass 0.
    75// - rlm: Propagated realm context validated as current before the hook is
    76// stored.
    77// - hook: Callback invoked before swap computation with the current pool
    78// realm, pool path, and block timestamp.
    79func (i *poolV1) SetSwapStartHook(_ int, rlm realm, hook func(cur realm, poolPath string, timestamp int64)) {
    80 access.AssertIsRlmCurrent(0, rlm)
    81
    82 i.assertPoolUnlocked()
    83
    84 caller := rlm.Previous().Address()
    85 access.AssertIsStaker(caller)
    86
    87 i.lockPool(0, rlm)
    88 defer i.unlockPool(0, rlm)
    89
    90 err := i.store.SetSwapStartHook(0, rlm, hook)
    91 if err != nil {
    92 panic(err)
    93 }
    94}
    95
    96// SetSwapEndHook sets the hook function called at the end of a swap.
    97//
    98// Finalizes reward calculations after swap completion.
    99// Allows error propagation to revert invalid swaps.
    100//
    101// Only callable by staker contract.
    102//
    103// Parameters:
    104// - _: Leading integer discriminator for the forwarded realm call; callers
    105// pass 0.
    106// - rlm: Propagated realm context validated as current before the hook is
    107// stored.
    108// - hook: Callback invoked after swap settlement with the current pool realm
    109// and pool path; a non-nil error aborts the surrounding swap.
    110func (i *poolV1) SetSwapEndHook(_ int, rlm realm, hook func(cur realm, poolPath string) error) {
    111 access.AssertIsRlmCurrent(0, rlm)
    112
    113 i.assertPoolUnlocked()
    114
    115 caller := rlm.Previous().Address()
    116 access.AssertIsStaker(caller)
    117
    118 i.lockPool(0, rlm)
    119 defer i.unlockPool(0, rlm)
    120
    121 err := i.store.SetSwapEndHook(0, rlm, hook)
    122 if err != nil {
    123 panic(err)
    124 }
    125}
    126
    127// SwapResult encapsulates all state changes from a swap.
    128// It ensures atomic state transitions that can be applied at once.
    129type SwapResult struct {
    130 Amount0 *i256.Int
    131 Amount1 *i256.Int
    132 NewSqrtPrice *u256.Uint
    133 NewTick int32
    134 NewLiquidity *u256.Uint
    135 NewProtocolFeeToken0 int64
    136 NewProtocolFeeToken1 int64
    137 FeeGrowthGlobal0X128 *u256.Uint
    138 FeeGrowthGlobal1X128 *u256.Uint
    139}
    140
    141// SwapComputation encapsulates the pure computation logic for swaps.
    142type SwapComputation struct {
    143 AmountSpecified *i256.Int
    144 SqrtPriceLimitX96 *u256.Uint
    145 ZeroForOne bool
    146 ExactInput bool
    147 InitialState SwapState
    148 Cache *SwapCache
    149}
    150
    151// Swap executes a swap with callback pattern for optimistic transfers.
    152// This allows flash swaps where tokens are sent before payment is received.
    153//
    154// The flow is:
    155// 1. Pool sends output tokens to recipient
    156// 2. Pool calls callback on msg.sender
    157// 3. Callback must ensure pool receives input tokens
    158// 4. Pool validates its balance increased correctly
    159//
    160// Parameters:
    161// - _: Leading integer discriminator for the forwarded realm call; callers
    162// pass 0.
    163// - rlm: Current propagated realm context; it is validated as current and
    164// used for token transfers and callbacks.
    165// - token0Path: Registered path of token0; it must be the first token in the
    166// pool's canonical token ordering.
    167// - token1Path: Registered path of token1; it must be the second token in
    168// the pool's canonical token ordering.
    169// - fee: Fee tier identifying the registered pool to swap against.
    170// - recipient: Address that receives the output token transfer.
    171// - zeroForOne: Swap direction; true sells token0 for token1, false sells
    172// token1 for token0.
    173// - amountSpecified: Signed decimal amount; positive values request exact
    174// input, negative values request exact output, and zero is invalid.
    175// - sqrtPriceLimitX96: Decimal Q96 square-root-price limit that bounds the
    176// swap in its direction; an invalid bound causes the call to panic.
    177// - swapCallback: Callback that must settle the input delta after output is
    178// sent; it receives signed int64 token deltas and a callback marker, and a
    179// non-nil error aborts the swap.
    180//
    181// Returns:
    182// - string: Signed decimal token0 delta from the pool's perspective; a
    183// positive value is owed to the pool and a negative value is sent out.
    184// - string: Signed decimal token1 delta from the pool's perspective; a
    185// positive value is owed to the pool and a negative value is sent out.
    186func (i *poolV1) Swap(
    187 _ int,
    188 rlm realm,
    189 token0Path string,
    190 token1Path string,
    191 fee uint32,
    192 recipient address,
    193 zeroForOne bool,
    194 amountSpecified string,
    195 sqrtPriceLimitX96 string,
    196 swapCallback func(cur realm, amount0Delta, amount1Delta int64, _ *pl.CallbackMarker) error,
    197) (string, string) {
    198 access.AssertIsRlmCurrent(0, rlm)
    199
    200 i.assertPoolUnlocked()
    201 halt.AssertIsNotHaltedPool()
    202
    203 assertIsNotUserCall(0, rlm)
    204 assertIsValidTokenOrder(token0Path, token1Path)
    205
    206 amounts := i256.MustFromDecimal(amountSpecified)
    207 if amounts.IsZero() {
    208 panic(newErrorWithDetail(
    209 errInvalidSwapAmount,
    210 "amountSpecified == 0",
    211 ))
    212 }
    213
    214 pool := i.mustGetPoolBy(token0Path, token1Path, fee)
    215 observations := i.mustGetObservations(pool.PoolPath())
    216
    217 slot0Start := pool.Slot0()
    218 i.lockPool(0, rlm)
    219 defer i.unlockPool(0, rlm)
    220
    221 blockTimestamp := time.Now().Unix()
    222
    223 // Call swap start hook if set
    224 if i.store.HasSwapStartHook() {
    225 swapStartHook := i.store.GetSwapStartHook()
    226
    227 if swapStartHook != nil {
    228 swapStartHook(cross(rlm), pool.PoolPath(), blockTimestamp)
    229 }
    230 }
    231
    232 sqrtPriceLimit := u256.MustFromDecimal(sqrtPriceLimitX96)
    233 validatePriceLimits(slot0Start, zeroForOne, sqrtPriceLimit)
    234
    235 feeGrowthGlobalX128 := getFeeGrowthGlobal(pool, zeroForOne)
    236 feeProtocol := getFeeProtocol(slot0Start, zeroForOne)
    237 cache := newSwapCache(feeProtocol, pool.Liquidity().Clone(), blockTimestamp, slot0Start)
    238 state := newSwapState(amounts, feeGrowthGlobalX128, cache.liquidityStart.Clone(), slot0Start)
    239
    240 comp := SwapComputation{
    241 AmountSpecified: amounts,
    242 SqrtPriceLimitX96: sqrtPriceLimit,
    243 ZeroForOne: zeroForOne,
    244 ExactInput: amounts.Gt(zeroI256),
    245 InitialState: state,
    246 Cache: cache,
    247 }
    248
    249 var hook func(cur realm, poolPath string, tickId int32, zeroForOne bool, timestamp int64)
    250 if i.store.HasTickCrossHook() {
    251 hook = i.store.GetTickCrossHook()
    252 }
    253 onTickCross := func(pool *pl.Pool, tickId int32, zeroForOne bool, timestamp int64) {
    254 chain.Emit(
    255 "PoolTickCross",
    256 "poolPath", pool.PoolPath(),
    257 "tick", NewTickEventInfo(tickId, *mustGetTick(pool, tickId)).ToString(),
    258 )
    259 if hook != nil {
    260 hook(cross(rlm), pool.PoolPath(), tickId, zeroForOne, timestamp)
    261 }
    262 }
    263
    264 result, err := i.computeSwap(pool, observations, comp, onTickCross)
    265 if err != nil {
    266 panic(err)
    267 }
    268
    269 // Update oracle BEFORE applying swap result (using pre-swap state)
    270 if result.NewTick != slot0Start.Tick() {
    271 observationIndex, observationCardinality, err := writeObservation(
    272 observations,
    273 slot0Start.ObservationIndex(),
    274 cache.blockTimestamp,
    275 slot0Start.Tick(),
    276 cache.liquidityStart,
    277 slot0Start.ObservationCardinality(),
    278 slot0Start.ObservationCardinalityNext(),
    279 )
    280 if err != nil {
    281 panic(err)
    282 }
    283 slot0 := pool.Slot0()
    284 slot0.SetObservationIndex(observationIndex)
    285 slot0.SetObservationCardinality(observationCardinality)
    286 pool.SetSlot0(slot0)
    287 }
    288
    289 applySwapResult(pool, result)
    290
    291 if i.store.HasSwapEndHook() {
    292 swapEndHook := i.store.GetSwapEndHook()
    293 if swapEndHook != nil {
    294 err := swapEndHook(cross(rlm), pool.PoolPath())
    295 if err != nil {
    296 panic(err)
    297 }
    298 }
    299 }
    300
    301 // transfer swap result to recipient then receive input tokens from swap callback
    302 if zeroForOne {
    303 // receive token0 from swap callback
    304 // send token1 to recipient (output)
    305 if result.Amount1.IsNeg() {
    306 i.safeTransfer(0, rlm, pool, recipient, token1Path, result.Amount1.Abs(), false)
    307 }
    308 i.safeSwapCallback(0, rlm, pool, token0Path, result.Amount0, result.Amount1, zeroForOne, swapCallback)
    309 } else {
    310 // receive token1 from swap callback
    311 // send token0 to recipient (output)
    312 if result.Amount0.IsNeg() {
    313 i.safeTransfer(0, rlm, pool, recipient, token0Path, result.Amount0.Abs(), true)
    314 }
    315 i.safeSwapCallback(0, rlm, pool, token1Path, result.Amount1, result.Amount0, zeroForOne, swapCallback)
    316 }
    317
    318 slot0 := pool.Slot0()
    319 lastObservation, err := observationAt(observations, slot0.ObservationIndex())
    320 if err != nil {
    321 panic(err)
    322 }
    323
    324 token0Amount := result.Amount0.ToString()
    325 token1Amount := result.Amount1.ToString()
    326
    327 previousRealm := rlm.Previous()
    328
    329 chain.Emit(
    330 "Swap",
    331 "prevAddr", previousRealm.Address().String(),
    332 "prevRealm", previousRealm.PkgPath(),
    333 "poolPath", pool.PoolPath(),
    334 "zeroForOne", utils.FormatBool(zeroForOne),
    335 "requestAmount", amountSpecified,
    336 "sqrtPriceLimitX96", sqrtPriceLimitX96,
    337 "recipient", recipient.String(),
    338 "token0Amount", token0Amount,
    339 "token1Amount", token1Amount,
    340 "protocolFee0", utils.FormatInt(pool.ProtocolFeesToken0()),
    341 "protocolFee1", utils.FormatInt(pool.ProtocolFeesToken1()),
    342 "sqrtPriceX96", pool.Slot0SqrtPriceX96().ToString(),
    343 "exactIn", strconv.FormatBool(comp.ExactInput),
    344 "currentTick", strconv.FormatInt(int64(pool.Slot0Tick()), 10),
    345 "liquidity", pool.Liquidity().ToString(),
    346 "feeGrowthGlobal0X128", pool.FeeGrowthGlobal0X128().ToString(),
    347 "feeGrowthGlobal1X128", pool.FeeGrowthGlobal1X128().ToString(),
    348 "balanceToken0", utils.FormatInt(pool.BalanceToken0()),
    349 "balanceToken1", utils.FormatInt(pool.BalanceToken1()),
    350 "tickCumulative", utils.FormatInt(lastObservation.TickCumulative()),
    351 "secondsPerLiquidityCumulativeX128", lastObservation.SecondsPerLiquidityCumulativeX128(),
    352 "observationTimestamp", utils.FormatInt(lastObservation.BlockTimestamp()),
    353 )
    354
    355 return token0Amount, token1Amount
    356}
    357
    358// DrySwap simulates a swap against an isolated pool and observation snapshot;
    359// it never writes stored pool or oracle state.
    360//
    361// Parameters:
    362// - token0Path: Registered path of token0; it must be the first token in the
    363// pool's canonical token ordering.
    364// - token1Path: Registered path of token1; it must be the second token in
    365// the pool's canonical token ordering.
    366// - fee: Fee tier identifying the registered pool to simulate.
    367// - zeroForOne: Swap direction; true sells token0 for token1, false sells
    368// token1 for token0.
    369// - amountSpecified: Signed decimal amount; positive values request exact
    370// input, negative values request exact output, and zero is invalid.
    371// - sqrtPriceLimitX96: Decimal Q96 square-root-price limit that bounds the
    372// simulation in its direction.
    373//
    374// Returns:
    375// - amount0: Signed decimal token0 delta from the pool's perspective, or
    376// "0" when simulation fails.
    377// - amount1: Signed decimal token1 delta from the pool's perspective, or
    378// "0" when simulation fails.
    379// - err: Nil when the simulation succeeds; non-nil for invalid inputs,
    380// missing pool state, invalid limits, computation failures, or insufficient
    381// pool output balance.
    382func (i *poolV1) DrySwap(
    383 token0Path string,
    384 token1Path string,
    385 fee uint32,
    386 zeroForOne bool,
    387 amountSpecified string,
    388 sqrtPriceLimitX96 string,
    389) (amount0, amount1 string, err error) {
    390 defer func() {
    391 if r := recover(); r != nil {
    392 amount0 = "0"
    393 amount1 = "0"
    394 if recoveredErr, ok := r.(error); ok {
    395 err = recoveredErr
    396 } else {
    397 err = ufmt.Errorf("%v", r)
    398 }
    399 }
    400 }()
    401
    402 i.assertPoolUnlocked()
    403
    404 amounts := i256.MustFromDecimal(amountSpecified)
    405 if amounts.IsZero() {
    406 return "0", "0", errors.New(errInvalidSwapAmount)
    407 }
    408
    409 pool := i.mustGetPoolBy(token0Path, token1Path, fee)
    410 blockTimestamp := time.Now().Unix()
    411
    412 slot0Start := pool.Slot0()
    413 sqrtPriceLimit := u256.MustFromDecimal(sqrtPriceLimitX96)
    414 validatePriceLimits(slot0Start, zeroForOne, sqrtPriceLimit)
    415
    416 feeGrowthGlobalX128 := getFeeGrowthGlobal(pool, zeroForOne)
    417 feeProtocol := getFeeProtocol(slot0Start, zeroForOne)
    418 cache := newSwapCache(feeProtocol, pool.Liquidity().Clone(), blockTimestamp, slot0Start)
    419 cache.readOnly = true
    420 state := newSwapState(amounts, feeGrowthGlobalX128, cache.liquidityStart, slot0Start)
    421
    422 comp := SwapComputation{
    423 AmountSpecified: amounts,
    424 SqrtPriceLimitX96: sqrtPriceLimit,
    425 ZeroForOne: zeroForOne,
    426 ExactInput: amounts.Gt(zeroI256),
    427 InitialState: state,
    428 Cache: cache,
    429 }
    430
    431 result, err := i.computeSwap(pool, nil, comp, nil)
    432 if err != nil {
    433 return "0", "0", err
    434 }
    435
    436 if zeroForOne {
    437 if pool.BalanceToken1() < gnsmath.SafeConvertToInt64(result.Amount1.Abs()) {
    438 return "0", "0", errors.New(errInsufficientPoolBalance)
    439 }
    440 } else {
    441 if pool.BalanceToken0() < gnsmath.SafeConvertToInt64(result.Amount0.Abs()) {
    442 return "0", "0", errors.New(errInsufficientPoolBalance)
    443 }
    444 }
    445
    446 return result.Amount0.ToString(), result.Amount1.ToString(), nil
    447}
    448
    449// tickCrossHookFn is invoked after an initialized tick is crossed. Swap uses it
    450// for externally visible side effects; DrySwap passes nil.
    451type tickCrossHookFn func(pool *pl.Pool, tickId int32, zeroForOne bool, timestamp int64)
    452
    453// computeSwap calculates swap amounts; tick accounting is written unless Cache.readOnly is set.
    454// The computation continues until either:
    455// - The entire amount is consumed (amountSpecifiedRemaining = 0)
    456// - The price limit is reached (sqrtPriceX96 = sqrtPriceLimitX96)
    457//
    458// Important: This function is critical for AMM price discovery. It iterates through
    459// tick ranges, calculating swap amounts and fees for each liquidity segment.
    460// Returns an error if the computation fails at any step.
    461//
    462// The optional `onTickCross` callback is invoked when an initialized tick is
    463// crossed; Swap supplies a hook that performs a cross-realm call into the
    464// configured tick-cross hook, while DrySwap passes nil.
    465func (i *poolV1) computeSwap(pool *pl.Pool, observations *pl.ObservationTree, comp SwapComputation, onTickCross tickCrossHookFn) (*SwapResult, error) {
    466 state := comp.InitialState
    467 var err error
    468
    469 // Compute swap steps until completion
    470 for shouldContinueSwap(state, comp.SqrtPriceLimitX96) {
    471 state, err = i.computeSwapStep(state, pool, observations, comp.ZeroForOne, comp.SqrtPriceLimitX96, comp.ExactInput, comp.Cache, onTickCross)
    472 if err != nil {
    473 return nil, err
    474 }
    475 }
    476
    477 // Calculate final amounts
    478 amount0 := state.amountCalculated
    479 amount1 := i256.Zero().Sub(comp.AmountSpecified, state.amountSpecifiedRemaining)
    480 if comp.ZeroForOne == comp.ExactInput {
    481 amount0, amount1 = amount1, amount0
    482 }
    483
    484 // Prepare result
    485 result := &SwapResult{
    486 Amount0: amount0,
    487 Amount1: amount1,
    488 NewSqrtPrice: state.sqrtPriceX96,
    489 NewTick: state.tick,
    490 NewLiquidity: state.liquidity,
    491 NewProtocolFeeToken0: pool.ProtocolFeesToken0(),
    492 NewProtocolFeeToken1: pool.ProtocolFeesToken1(),
    493 FeeGrowthGlobal0X128: pool.FeeGrowthGlobal0X128(),
    494 FeeGrowthGlobal1X128: pool.FeeGrowthGlobal1X128(),
    495 }
    496
    497 // Update protocol fees if necessary
    498 if comp.ZeroForOne {
    499 if state.protocolFee.Gt(zero) {
    500 result.NewProtocolFeeToken0 = gnsmath.SafeAddInt64(result.NewProtocolFeeToken0, gnsmath.SafeConvertToInt64(state.protocolFee))
    501 }
    502 result.FeeGrowthGlobal0X128 = state.feeGrowthGlobalX128.Clone()
    503 } else {
    504 if state.protocolFee.Gt(zero) {
    505 result.NewProtocolFeeToken1 = gnsmath.SafeAddInt64(result.NewProtocolFeeToken1, gnsmath.SafeConvertToInt64(state.protocolFee))
    506 }
    507 result.FeeGrowthGlobal1X128 = state.feeGrowthGlobalX128.Clone()
    508 }
    509
    510 return result, nil
    511}
    512
    513// applySwapResult updates pool state with computed results.
    514// All state changes are applied at once to maintain consistency
    515func applySwapResult(pool *pl.Pool, result *SwapResult) {
    516 slot0 := pool.Slot0()
    517 slot0.SetSqrtPriceX96(result.NewSqrtPrice)
    518 slot0.SetTick(result.NewTick)
    519 pool.SetSlot0(slot0)
    520
    521 pool.SetLiquidity(result.NewLiquidity)
    522 pool.SetProtocolFeesToken0(result.NewProtocolFeeToken0)
    523 pool.SetProtocolFeesToken1(result.NewProtocolFeeToken1)
    524 pool.SetFeeGrowthGlobal0X128(result.FeeGrowthGlobal0X128)
    525 pool.SetFeeGrowthGlobal1X128(result.FeeGrowthGlobal1X128)
    526}
    527
    528// validatePriceLimits ensures the provided price limit is valid for the swap direction
    529// The function enforces that:
    530// For zeroForOne (selling token0):
    531// - Price limit must be below current price
    532// - Price limit must be above MIN_SQRT_RATIO
    533//
    534// For !zeroForOne (selling token1):
    535// - Price limit must be above current price
    536// - Price limit must be below MAX_SQRT_RATIO
    537func validatePriceLimits(slot0 pl.Slot0, zeroForOne bool, sqrtPriceLimitX96 *u256.Uint) {
    538 if zeroForOne {
    539 cond1 := sqrtPriceLimitX96.Lt(slot0.SqrtPriceX96())
    540 cond2 := sqrtPriceLimitX96.Gt(consts.MinSqrtRatio())
    541 if !(cond1 && cond2) {
    542 panic(newErrorWithDetail(
    543 errPriceOutOfRange,
    544 ufmt.Sprintf("sqrtPriceLimitX96(%s) < slot0Start.sqrtPriceX96(%s) && sqrtPriceLimitX96(%s) > MIN_SQRT_RATIO(%s)",
    545 sqrtPriceLimitX96.ToString(),
    546 slot0.SqrtPriceX96().ToString(),
    547 sqrtPriceLimitX96.ToString(),
    548 MIN_SQRT_RATIO),
    549 ))
    550 }
    551 } else {
    552 cond1 := sqrtPriceLimitX96.Gt(slot0.SqrtPriceX96())
    553 cond2 := sqrtPriceLimitX96.Lt(consts.MaxSqrtRatio())
    554 if !(cond1 && cond2) {
    555 panic(newErrorWithDetail(
    556 errPriceOutOfRange,
    557 ufmt.Sprintf("sqrtPriceLimitX96(%s) > slot0Start.sqrtPriceX96(%s) && sqrtPriceLimitX96(%s) < MAX_SQRT_RATIO(%s)",
    558 sqrtPriceLimitX96.ToString(),
    559 slot0.SqrtPriceX96().ToString(),
    560 sqrtPriceLimitX96.ToString(),
    561 MAX_SQRT_RATIO),
    562 ))
    563 }
    564 }
    565}
    566
    567// getFeeProtocol returns the appropriate fee protocol based on zero for one.
    568// When zeroForOne is true, we want the lower 4 bits (% 16).
    569// Otherwise, we want the upper 4 bits (/ 16).
    570func getFeeProtocol(slot0 pl.Slot0, zeroForOne bool) uint8 {
    571 shift := uint8(0)
    572 if !zeroForOne {
    573 shift = 4
    574 }
    575 return (slot0.FeeProtocol() >> shift) & uint8(0xF)
    576}
    577
    578// getFeeGrowthGlobal returns the appropriate fee growth global based on zero for one.
    579func getFeeGrowthGlobal(pool *pl.Pool, zeroForOne bool) *u256.Uint {
    580 if zeroForOne {
    581 return pool.FeeGrowthGlobal0X128().Clone()
    582 }
    583 return pool.FeeGrowthGlobal1X128().Clone()
    584}
    585
    586// shouldContinueSwap checks if swap should continue based on remaining amount and price limit.
    587func shouldContinueSwap(state SwapState, sqrtPriceLimitX96 *u256.Uint) bool {
    588 return !state.amountSpecifiedRemaining.IsZero() && !state.sqrtPriceX96.Eq(sqrtPriceLimitX96)
    589}
    590
    591// computeSwapStep executes a single step of swap and returns new state
    592func (i *poolV1) computeSwapStep(
    593 state SwapState,
    594 pool *pl.Pool,
    595 observations *pl.ObservationTree,
    596 zeroForOne bool,
    597 sqrtPriceLimitX96 *u256.Uint,
    598 exactInput bool,
    599 cache *SwapCache,
    600 onTickCross tickCrossHookFn,
    601) (SwapState, error) {
    602 step := computeSwapStepInit(state, pool, zeroForOne)
    603
    604 // determining the price target for this step
    605 sqrtRatioTargetX96 := computeTargetSqrtRatio(step, sqrtPriceLimitX96, zeroForOne).Clone()
    606
    607 // computing the amounts to be swapped at this step
    608 var (
    609 newState SwapState
    610 err error
    611 )
    612
    613 newState, step = computeAmounts(state, sqrtRatioTargetX96, pool, step)
    614 newState, err = updateAmounts(step, newState, exactInput)
    615 if err != nil {
    616 return state, err
    617 }
    618
    619 // if the protocol fee is on, calculate how much is owed,
    620 // decrement fee amount, and increment protocol fee
    621 if cache.feeProtocol > 0 {
    622 newState, step, err = updateFeeProtocol(step, cache.feeProtocol, newState)
    623 if err != nil {
    624 return state, err
    625 }
    626 }
    627
    628 // update global fee tracker
    629 if newState.liquidity.Gt(u256.Zero()) {
    630 update := u256.MulDiv(step.feeAmount, fixedPointQ128, newState.liquidity)
    631 feeGrowthGlobalX128 := u256.Zero().Add(newState.feeGrowthGlobalX128, update)
    632 newState.setFeeGrowthGlobalX128(feeGrowthGlobalX128)
    633 }
    634
    635 // handling tick transitions
    636 if newState.sqrtPriceX96.Eq(step.sqrtPriceNextX96) {
    637 newState, err = i.tickTransition(step, zeroForOne, newState, pool, observations, cache, onTickCross)
    638 if err != nil {
    639 return state, err
    640 }
    641 } else if newState.sqrtPriceX96.Neq(step.sqrtPriceStartX96) {
    642 newState.setTick(gnsmath.TickMathGetTickAtSqrtRatio(newState.sqrtPriceX96))
    643 }
    644
    645 return newState, nil
    646}
    647
    648// updateFeeProtocol calculates and updates protocol fees for the current step.
    649func updateFeeProtocol(step StepComputations, feeProtocol uint8, state SwapState) (SwapState, StepComputations, error) {
    650 delta := u256.Zero().Div(step.feeAmount, u256.NewUint(uint64(feeProtocol)))
    651
    652 newFeeAmount, overflow := u256.Zero().SubOverflow(step.feeAmount, delta)
    653 if overflow {
    654 return state, step, errors.New(errUnderflow)
    655 }
    656
    657 step.feeAmount = newFeeAmount
    658
    659 newProtocolFee, overflow := u256.Zero().AddOverflow(state.protocolFee, delta)
    660 if overflow {
    661 return state, step, errors.New(errOverflow)
    662 }
    663 state.protocolFee = newProtocolFee
    664
    665 return state, step, nil
    666}
    667
    668// computeSwapStepInit initializes the computation for a single swap step.
    669func computeSwapStepInit(state SwapState, pool *pl.Pool, zeroForOne bool) StepComputations {
    670 var step StepComputations
    671 step.sqrtPriceStartX96 = state.sqrtPriceX96
    672 tickNext, initialized := tickBitmapNextInitializedTickWithInOneWord(
    673 pool,
    674 state.tick,
    675 pool.TickSpacing(),
    676 zeroForOne,
    677 )
    678
    679 step.tickNext = tickNext
    680 step.initialized = initialized
    681
    682 // prevent overshoot the min/max tick
    683 step.clampTickNext()
    684 // get the price for the next tick
    685 step.sqrtPriceNextX96 = gnsmath.TickMathGetSqrtRatioAtTick(step.tickNext)
    686 return step
    687}
    688
    689// computeTargetSqrtRatio determines the target sqrt price for the current swap step.
    690func computeTargetSqrtRatio(step StepComputations, sqrtPriceLimitX96 *u256.Uint, zeroForOne bool) *u256.Uint {
    691 if shouldUsePriceLimit(step.sqrtPriceNextX96, sqrtPriceLimitX96, zeroForOne) {
    692 return sqrtPriceLimitX96
    693 }
    694 return step.sqrtPriceNextX96
    695}
    696
    697// shouldUsePriceLimit returns true if the price limit should be used instead of the next tick price
    698func shouldUsePriceLimit(sqrtPriceNext, sqrtPriceLimit *u256.Uint, zeroForOne bool) bool {
    699 if zeroForOne {
    700 return sqrtPriceNext.Lt(sqrtPriceLimit)
    701 }
    702 return sqrtPriceNext.Gt(sqrtPriceLimit)
    703}
    704
    705// computeAmounts calculates the input and output amounts for the current swap step.
    706func computeAmounts(state SwapState, sqrtRatioTargetX96 *u256.Uint, pool *pl.Pool, step StepComputations) (SwapState, StepComputations) {
    707 sqrtPriceX96, amountIn, amountOut, feeAmount := gnsmath.SwapMathComputeSwapStep(
    708 state.sqrtPriceX96,
    709 sqrtRatioTargetX96,
    710 state.liquidity,
    711 state.amountSpecifiedRemaining,
    712 uint64(pool.Fee()),
    713 )
    714
    715 step.amountIn = amountIn
    716 step.amountOut = amountOut
    717 step.feeAmount = feeAmount
    718
    719 state.setSqrtPriceX96(sqrtPriceX96)
    720
    721 return state, step
    722}
    723
    724// updateAmounts calculates new remaining and calculated amounts based on the swap step.
    725// For exact input swaps:
    726// - Decrements remaining input amount by (amountIn + feeAmount)
    727// - Decrements calculated amount by amountOut
    728//
    729// For exact output swaps:
    730// - Increments remaining output amount by amountOut
    731// - Increments calculated amount by (amountIn + feeAmount)
    732func updateAmounts(step StepComputations, state SwapState, exactInput bool) (SwapState, error) {
    733 amountInWithFeeU256 := u256.Zero().Add(step.amountIn, step.feeAmount)
    734 if amountInWithFeeU256.Gt(maxInt256) {
    735 return state, errors.New(errOverflow)
    736 }
    737
    738 amountInWithFee := i256.FromUint256(amountInWithFeeU256)
    739 if step.amountOut.Gt(maxInt256) {
    740 return state, errors.New(errOverflow)
    741 }
    742
    743 var (
    744 amountSpecifiedRemaining *i256.Int
    745 amountCalculated *i256.Int
    746 overflow bool
    747 )
    748
    749 if exactInput {
    750 amountSpecifiedRemaining, overflow = i256.Zero().SubOverflow(state.amountSpecifiedRemaining, amountInWithFee)
    751 if overflow {
    752 return state, errors.New(errUnderflow)
    753 }
    754 amountCalculated, overflow = i256.Zero().SubOverflow(state.amountCalculated, i256.FromUint256(step.amountOut))
    755 if overflow {
    756 return state, errors.New(errUnderflow)
    757 }
    758 } else {
    759 amountSpecifiedRemaining, overflow = i256.Zero().AddOverflow(state.amountSpecifiedRemaining, i256.FromUint256(step.amountOut))
    760 if overflow {
    761 return state, errors.New(errOverflow)
    762 }
    763 amountCalculated, overflow = i256.Zero().AddOverflow(state.amountCalculated, amountInWithFee)
    764 if overflow {
    765 return state, errors.New(errOverflow)
    766 }
    767 }
    768
    769 // If an overflowed value is stored in state, it may cause problems in the next step
    770 if amountCalculated.Gt(maxInt64) || amountSpecifiedRemaining.Gt(maxInt64) {
    771 return state, errors.New(errOverflow)
    772 }
    773
    774 // If an underflowed value is stored in state, it may cause problems in the next step
    775 if amountCalculated.Lt(minInt64) || amountSpecifiedRemaining.Lt(minInt64) {
    776 return state, errors.New(errUnderflow)
    777 }
    778
    779 state.amountSpecifiedRemaining = amountSpecifiedRemaining
    780 state.amountCalculated = amountCalculated
    781
    782 return state, nil
    783}
    784
    785// tickTransition handles the transition between price ticks during a swap
    786func (i *poolV1) tickTransition(step StepComputations, zeroForOne bool, state SwapState, pool *pl.Pool, observations *pl.ObservationTree, cache *SwapCache, onTickCross tickCrossHookFn) (SwapState, error) {
    787 // ensure existing state to keep immutability
    788 newState := state
    789
    790 if step.initialized {
    791 var liquidityNet *i256.Int
    792 if cache.readOnly {
    793 // Quotes need only the liquidity delta. Outside fee/oracle accumulators
    794 // do not affect pricing, and the monotonic path never revisits a tick.
    795 tick := getTick(pool, step.tickNext)
    796 liquidityNet = i256.MustFromDecimal(tick.LiquidityNet())
    797 } else {
    798 // Compute oracle values on first initialized tick cross.
    799 if !cache.computedLatestObservation {
    800 tickCumulative, secondsPerLiquidityStr, err := observeSingle(
    801 observations,
    802 cache.blockTimestamp,
    803 0,
    804 cache.slot0Start.Tick(),
    805 cache.slot0Start.ObservationIndex(),
    806 cache.liquidityStart,
    807 cache.slot0Start.ObservationCardinality(),
    808 )
    809 if err != nil {
    810 return newState, err
    811 }
    812
    813 cache.tickCumulative = tickCumulative
    814 cache.secondsPerLiquidityCumulativeX128 = u256.MustFromDecimal(secondsPerLiquidityStr)
    815 cache.computedLatestObservation = true
    816 }
    817
    818 fee0, fee1 := u256.Zero(), u256.Zero()
    819 if zeroForOne {
    820 fee0 = state.feeGrowthGlobalX128
    821 fee1 = pool.FeeGrowthGlobal1X128()
    822 } else {
    823 fee0 = pool.FeeGrowthGlobal0X128()
    824 fee1 = state.feeGrowthGlobalX128
    825 }
    826
    827 liquidityNet = tickCross(
    828 pool,
    829 step.tickNext,
    830 fee0,
    831 fee1,
    832 cache.secondsPerLiquidityCumulativeX128,
    833 cache.tickCumulative,
    834 cache.blockTimestamp,
    835 )
    836 }
    837
    838 if zeroForOne {
    839 liquidityNet = i256.Zero().Neg(liquidityNet)
    840 }
    841
    842 newState.liquidity = gnsmath.LiquidityMathAddDelta(state.liquidity, liquidityNet)
    843
    844 if !cache.readOnly && onTickCross != nil {
    845 onTickCross(pool, step.tickNext, zeroForOne, cache.blockTimestamp)
    846 }
    847 }
    848
    849 newState.tick = step.tickNext
    850 if zeroForOne {
    851 newState.tick = step.tickNext - 1
    852 }
    853
    854 return newState, nil
    855}
    856