PathrockNetwork Gno Explorer
HomeBlocksTransactionsRealmsPackagesValidatorsAnalytics

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
  • oracle.gnogno
    1package pool23import (4	"errors"5	"time"67	"gno.land/p/gnoswap/consts/v1"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 ↗
    This realm does not provide renderable output (vm/qrender → NoRenderDeclError). View on gnoweb ↗

    vm/qrender output, sanitized (docs/render-security.md) and displayed in an empty-sandbox iframe — scripts, forms and popups cannot run. Links stay inert in-preview; right-click to open.

    u256 "gno.land/p/gnoswap/uint256/v1"
    9 pl "gno.land/r/gnoswap/pool"
    10)
    11
    12// maxObservationCardinality defines the maximum number of observations to store
    13const maxObservationCardinality uint16 = 65535
    14
    15// oracleConsult calculates the time-weighted average price between two points in time.
    16// It returns the arithmetic mean tick and harmonic mean liquidity over the time period.
    17func oracleConsult(p *pl.Pool, observations *pl.ObservationTree, secondsAgo uint32) (int32, *u256.Uint, error) {
    18 if secondsAgo == 0 {
    19 return 0, nil, errors.New("secondsAgo must be greater than 0")
    20 }
    21
    22 if observations == nil {
    23 return 0, nil, errors.New("observations not initialized")
    24 }
    25 slot0 := p.Slot0()
    26
    27 // Get observations for current time and secondsAgo
    28 secondsAgos := []uint32{secondsAgo, 0}
    29 blockTimestamp := time.Now().Unix()
    30
    31 tickCumulatives, secondsPerLiquidityCumulativeX128s, err := observe(
    32 observations,
    33 blockTimestamp,
    34 secondsAgos,
    35 slot0.Tick(),
    36 slot0.ObservationIndex(),
    37 p.Liquidity(),
    38 slot0.ObservationCardinality(),
    39 )
    40 if err != nil {
    41 return 0, nil, err
    42 }
    43
    44 tickCumulativesDelta := tickCumulatives[1] - tickCumulatives[0]
    45 secondsPerLiquidityDelta := u256.Zero().Sub(
    46 u256.MustFromDecimal(secondsPerLiquidityCumulativeX128s[1]),
    47 u256.MustFromDecimal(secondsPerLiquidityCumulativeX128s[0]),
    48 )
    49
    50 arithmeticMeanTick := int32(tickCumulativesDelta / int64(secondsAgo))
    51 if tickCumulativesDelta < 0 && (tickCumulativesDelta%int64(secondsAgo) != 0) {
    52 arithmeticMeanTick--
    53 }
    54
    55 if secondsPerLiquidityDelta.IsZero() {
    56 return arithmeticMeanTick, u256.Zero(), nil
    57 }
    58
    59 // Calculate harmonic mean liquidity
    60 secondsAgoX160 := u256.Zero().Mul(u256.NewUint(uint64(secondsAgo)), consts.Max160())
    61 denominator := u256.Zero().Lsh(secondsPerLiquidityDelta, 32)
    62 harmonicMeanLiquidity := u256.Zero().Div(secondsAgoX160, denominator)
    63
    64 return arithmeticMeanTick, harmonicMeanLiquidity, nil
    65}
    66
    67// snapshotCumulativesInside returns the accumulators that accrued while the
    68// current price was inside [tickLower, tickUpper), following Uniswap V3's
    69// snapshotCumulativesInside flow.
    70func snapshotCumulativesInside(
    71 p *pl.Pool,
    72 observations *pl.ObservationTree,
    73 tickLower int32,
    74 tickUpper int32,
    75) (int64, *u256.Uint, uint32, error) {
    76 if err := validateTicks(tickLower, tickUpper); err != nil {
    77 return 0, nil, 0, err
    78 }
    79
    80 lower := getTick(p, tickLower)
    81 if !lower.Initialized() {
    82 return 0, nil, 0, makeErrorWithDetails(errDataNotFound, "lower tick is not initialized")
    83 }
    84
    85 upper := getTick(p, tickUpper)
    86 if !upper.Initialized() {
    87 return 0, nil, 0, makeErrorWithDetails(errDataNotFound, "upper tick is not initialized")
    88 }
    89
    90 lowerSecondsPerLiquidityOutsideX128 := u256.MustFromDecimal(lower.SecondsPerLiquidityOutsideX128())
    91 upperSecondsPerLiquidityOutsideX128 := u256.MustFromDecimal(upper.SecondsPerLiquidityOutsideX128())
    92 slot0 := p.Slot0()
    93
    94 if slot0.Tick() < tickLower {
    95 return lower.TickCumulativeOutside() - upper.TickCumulativeOutside(),
    96 u256.Zero().Sub(lowerSecondsPerLiquidityOutsideX128, upperSecondsPerLiquidityOutsideX128),
    97 lower.SecondsOutside() - upper.SecondsOutside(), nil
    98 }
    99
    100 if slot0.Tick() < tickUpper {
    101 if observations == nil {
    102 return 0, nil, 0, errors.New("observations not initialized")
    103 }
    104
    105 currentTime := time.Now().Unix()
    106 tickCumulative, secondsPerLiquidityCumulativeX128, err := observeSingle(
    107 observations,
    108 currentTime,
    109 0,
    110 slot0.Tick(),
    111 slot0.ObservationIndex(),
    112 p.Liquidity(),
    113 slot0.ObservationCardinality(),
    114 )
    115 if err != nil {
    116 return 0, nil, 0, err
    117 }
    118
    119 secondsPerLiquidityInsideX128 := u256.Zero().Sub(
    120 u256.Zero().Sub(
    121 u256.MustFromDecimal(secondsPerLiquidityCumulativeX128),
    122 lowerSecondsPerLiquidityOutsideX128,
    123 ),
    124 upperSecondsPerLiquidityOutsideX128,
    125 )
    126
    127 return tickCumulative - lower.TickCumulativeOutside() - upper.TickCumulativeOutside(),
    128 secondsPerLiquidityInsideX128,
    129 uint32(currentTime) - lower.SecondsOutside() - upper.SecondsOutside(), nil
    130 }
    131
    132 return upper.TickCumulativeOutside() - lower.TickCumulativeOutside(),
    133 u256.Zero().Sub(upperSecondsPerLiquidityOutsideX128, lowerSecondsPerLiquidityOutsideX128),
    134 upper.SecondsOutside() - lower.SecondsOutside(), nil
    135}
    136
    137func transform(last pl.Observation, blockTimestamp int64, tick int32, liquidity *u256.Uint) (pl.Observation, error) {
    138 timeDelta := blockTimestamp - last.BlockTimestamp()
    139 if timeDelta < 0 {
    140 return pl.DefaultObservation(), errors.New("time delta must be greater than 0")
    141 }
    142
    143 // calculate cumulative values
    144 tickCumulative := last.TickCumulative() + int64(tick)*timeDelta
    145
    146 // calculate seconds per liquidity
    147 liquidityForCalc := liquidity
    148 if liquidity.IsZero() {
    149 liquidityForCalc = u256.One()
    150 }
    151
    152 // secondsPerLiquidity += timeDelta * 2^128 / max(1, liquidity)
    153 secondsPerLiquidityDelta := u256.MulDiv(
    154 u256.NewUintFromInt64(timeDelta),
    155 consts.Q128(),
    156 liquidityForCalc,
    157 )
    158
    159 prevSecPerLiq := u256.MustFromDecimal(last.SecondsPerLiquidityCumulativeX128())
    160 secondsPerLiquidityCumulativeX128 := u256.Zero().Add(
    161 prevSecPerLiq,
    162 secondsPerLiquidityDelta,
    163 )
    164
    165 observation := pl.MakeObservation(
    166 blockTimestamp,
    167 tickCumulative,
    168 secondsPerLiquidityCumulativeX128.ToString(),
    169 true,
    170 )
    171 return observation, nil
    172}
    173
    174func grow(observations *pl.ObservationTree, current, next uint16) (uint16, error) {
    175 if observations == nil {
    176 return current, errors.New("observations not initialized")
    177 }
    178
    179 if current <= 0 {
    180 return current, errors.New("current must be greater than 0")
    181 }
    182
    183 if next <= current {
    184 return current, nil
    185 }
    186
    187 if next > maxObservationCardinality {
    188 return current, errors.New("next exceeds maximum")
    189 }
    190
    191 // Reserve every new slot now so the caller that increases capacity pays for
    192 // its storage. This matches Uniswap's Oracle.grow behavior and prevents an
    193 // unrestricted reservation from shifting allocation cost to later swappers.
    194 for i := current; i < next; i++ {
    195 observations.Set(i, pl.MakeObservation(1, 0, "0", false))
    196 }
    197
    198 return next, nil
    199}
    200
    201func writeObservation(
    202 observations *pl.ObservationTree,
    203 index uint16,
    204 blockTimestamp int64,
    205 tick int32,
    206 liquidity *u256.Uint,
    207 cardinality uint16,
    208 cardinalityNext uint16,
    209) (indexUpdated uint16, cardinalityUpdated uint16, err error) {
    210 if observations == nil {
    211 return 0, 0, errors.New("observations not initialized")
    212 }
    213 if cardinality == 0 {
    214 return 0, 0, errors.New("observation cardinality must be greater than 0")
    215 }
    216
    217 last, err := observationAt(observations, index)
    218 if err != nil {
    219 return 0, 0, err
    220 }
    221
    222 if last.BlockTimestamp() == blockTimestamp {
    223 return index, cardinality, nil
    224 }
    225
    226 // Check if we need to grow the cardinality
    227 if cardinalityNext > cardinality && index == cardinality-1 {
    228 cardinalityUpdated = cardinalityNext
    229 } else {
    230 cardinalityUpdated = cardinality
    231 }
    232
    233 indexUpdated = (index + 1) % cardinalityUpdated
    234 observation, err := transform(last, blockTimestamp, tick, liquidity)
    235 if err != nil {
    236 return 0, 0, err
    237 }
    238
    239 observations.Set(indexUpdated, observation)
    240 return indexUpdated, cardinalityUpdated, nil
    241}
    242
    243// observationAt returns the observation at a specific index
    244// Returns error if the observation doesn't exist
    245func observationAt(observations *pl.ObservationTree, index uint16) (pl.Observation, error) {
    246 if observations == nil {
    247 return pl.DefaultObservation(), errors.New("observations not initialized")
    248 }
    249 obs, ok := observations.Get(index)
    250 if !ok {
    251 return pl.DefaultObservation(), errors.New(errNotInitializedObservation)
    252 }
    253
    254 return obs, nil
    255}
    256
    257// observeSingle returns the data for a single observation at a specific time ago
    258func observeSingle(
    259 observations *pl.ObservationTree,
    260 time int64,
    261 secondsAgo uint32,
    262 tick int32,
    263 index uint16,
    264 liquidity *u256.Uint,
    265 cardinality uint16,
    266) (int64, string, error) {
    267 if secondsAgo == 0 {
    268 // if secondsAgo is 0, return current values
    269 last, err := observationAt(observations, index)
    270 if err != nil {
    271 return 0, "", err
    272 }
    273
    274 if last.BlockTimestamp() != time {
    275 // need to create virtual observation for current time
    276 transformed, err := transform(last, time, tick, liquidity)
    277 if err != nil {
    278 return 0, "", err
    279 }
    280
    281 return transformed.TickCumulative(), transformed.SecondsPerLiquidityCumulativeX128(), nil
    282 }
    283
    284 return last.TickCumulative(), last.SecondsPerLiquidityCumulativeX128(), nil
    285 }
    286
    287 // A lookback longer than the chain's own age would place the target before unix epoch.
    288 if int64(secondsAgo) > time {
    289 return 0, "", errors.New(errObservationBeforeEpoch)
    290 }
    291
    292 target := time - int64(secondsAgo)
    293
    294 // find the observations before and after the target
    295 beforeOrAt, atOrAfter, err := getSurroundingObservations(
    296 observations,
    297 target,
    298 tick,
    299 index,
    300 liquidity,
    301 cardinality,
    302 )
    303 if err != nil {
    304 return 0, "", err
    305 }
    306
    307 if target == beforeOrAt.BlockTimestamp() {
    308 return beforeOrAt.TickCumulative(), beforeOrAt.SecondsPerLiquidityCumulativeX128(), nil
    309 }
    310
    311 if target == atOrAfter.BlockTimestamp() {
    312 return atOrAfter.TickCumulative(), atOrAfter.SecondsPerLiquidityCumulativeX128(), nil
    313 }
    314
    315 // interpolate between the two observations
    316 observationTimeDelta := atOrAfter.BlockTimestamp() - beforeOrAt.BlockTimestamp()
    317 targetDelta := target - beforeOrAt.BlockTimestamp()
    318
    319 // tickCumulative += (tickCumulativeAfter - tickCumulativeBefore) / observationTimeDelta * targetDelta
    320 tickCumulative := beforeOrAt.TickCumulative() +
    321 ((atOrAfter.TickCumulative()-beforeOrAt.TickCumulative())/observationTimeDelta)*targetDelta
    322
    323 beforeSecPerLiq := u256.MustFromDecimal(beforeOrAt.SecondsPerLiquidityCumulativeX128())
    324 afterSecPerLiq := u256.MustFromDecimal(atOrAfter.SecondsPerLiquidityCumulativeX128())
    325
    326 // for secondsPerLiquidity, need to interpolate carefully
    327 secondsPerLiquidityDelta := u256.Zero().Sub(afterSecPerLiq, beforeSecPerLiq)
    328
    329 secondsPerLiquidity := u256.Zero().Add(
    330 beforeSecPerLiq,
    331 u256.MulDiv(
    332 secondsPerLiquidityDelta,
    333 u256.NewUintFromInt64(targetDelta),
    334 u256.NewUintFromInt64(observationTimeDelta),
    335 ),
    336 )
    337
    338 return tickCumulative, secondsPerLiquidity.ToString(), nil
    339}
    340
    341// getSurroundingObservations finds the observations immediately before and after the target timestamp.
    342// It uses binary search over the logical time-ordered view of the circular buffer.
    343// Logical order starts at (index+1) % cardinality (oldest) and ends at index (latest).
    344func getSurroundingObservations(
    345 observations *pl.ObservationTree,
    346 target int64,
    347 tick int32,
    348 index uint16,
    349 liquidity *u256.Uint,
    350 cardinality uint16,
    351) (pl.Observation, pl.Observation, error) {
    352 // Optimistically set before to the newest observation
    353 beforeOrAt, err := observationAt(observations, index)
    354 if err != nil {
    355 return pl.DefaultObservation(), pl.DefaultObservation(), err
    356 }
    357
    358 // Timestamps are int64, so natural ordering applies. Uniswap V3 needs a
    359 // wraparound-aware comparison here only because it stores them as uint32.
    360 // If the target is chronologically at or after the newest observation, we can early return
    361 if beforeOrAt.BlockTimestamp() <= target {
    362 if beforeOrAt.BlockTimestamp() == target {
    363 // If newest observation equals target, we're in the same block, so we can ignore atOrAfter
    364 return beforeOrAt, pl.DefaultObservation(), nil
    365 }
    366 // Otherwise, we need to transform
    367 atOrAfter, err := transform(beforeOrAt, target, tick, liquidity)
    368 if err != nil {
    369 return pl.DefaultObservation(), pl.DefaultObservation(), err
    370 }
    371 return beforeOrAt, atOrAfter, nil
    372 }
    373
    374 // Now, set before to the oldest observation
    375 start := (index + 1) % cardinality
    376 beforeOrAt, err = observationAt(observations, start)
    377 if err != nil || !beforeOrAt.Initialized() {
    378 beforeOrAt, err = observationAt(observations, 0)
    379 if err != nil {
    380 return pl.DefaultObservation(), pl.DefaultObservation(), err
    381 }
    382 }
    383
    384 // Ensure that the target is chronologically at or after the oldest observation
    385 if beforeOrAt.BlockTimestamp() > target {
    386 return pl.DefaultObservation(), pl.DefaultObservation(), errors.New(errObservationTooOld)
    387 }
    388
    389 // If we've reached this point, we have to binary search
    390 return binarySearch(observations, target, index, cardinality)
    391}
    392
    393func binarySearch(
    394 observations *pl.ObservationTree,
    395 target int64,
    396 index uint16,
    397 cardinality uint16,
    398) (pl.Observation, pl.Observation, error) {
    399 l := uint64((index + 1) % cardinality) // oldest observation
    400 r := l + uint64(cardinality) - 1 // newest observation
    401 var i uint64
    402 var beforeOrAt, atOrAfter pl.Observation
    403 var err error
    404
    405 for {
    406 i = (l + r) / 2
    407
    408 beforeIndex := uint16(i % uint64(cardinality))
    409 beforeOrAt, err = observationAt(observations, beforeIndex)
    410 if err != nil || !beforeOrAt.Initialized() {
    411 // we've landed on an uninitialized tick, keep searching higher (more recently)
    412 l = i + 1
    413 continue
    414 }
    415
    416 afterIndex := uint16((i + 1) % uint64(cardinality))
    417 atOrAfter, err = observationAt(observations, afterIndex)
    418 if err != nil {
    419 return pl.DefaultObservation(), pl.DefaultObservation(), err
    420 }
    421
    422 targetAtOrAfter := beforeOrAt.BlockTimestamp() <= target
    423
    424 // check if we've found the answer!
    425 if targetAtOrAfter && target <= atOrAfter.BlockTimestamp() {
    426 break
    427 }
    428
    429 if !targetAtOrAfter {
    430 r = i - 1
    431 } else {
    432 l = i + 1
    433 }
    434 }
    435
    436 return beforeOrAt, atOrAfter, nil
    437}
    438
    439// observe returns the cumulative tick and liquidity as of each timestamp secondsAgo from the current time.
    440func observe(
    441 observations *pl.ObservationTree,
    442 time int64,
    443 secondsAgos []uint32,
    444 tick int32,
    445 index uint16,
    446 liquidity *u256.Uint,
    447 cardinality uint16,
    448) ([]int64, []string, error) {
    449 if cardinality <= 0 {
    450 return nil, nil, errors.New("observation cardinality must be greater than 0")
    451 }
    452
    453 historyCount := len(secondsAgos)
    454 tickCumulatives := make([]int64, historyCount)
    455 secondsPerLiquidityCumulativeX128s := make([]string, historyCount)
    456
    457 for i, secondsAgo := range secondsAgos {
    458 tickCumulative, secondsPerLiquidity, err := observeSingle(
    459 observations,
    460 time,
    461 secondsAgo,
    462 tick,
    463 index,
    464 liquidity,
    465 cardinality,
    466 )
    467 if err != nil {
    468 return nil, nil, err
    469 }
    470
    471 tickCumulatives[i] = tickCumulative
    472 secondsPerLiquidityCumulativeX128s[i] = secondsPerLiquidity
    473 }
    474
    475 return tickCumulatives, secondsPerLiquidityCumulativeX128s, nil
    476}
    477