Kind Realm (renderable)
Name v1
Namespace gnoswap / pool
Exported functions 3
Module gno.land/r/gnoswap/pool/v1
gno 0.9 oracle.gno gno
⧉
1 package pool 2 3 import ( 4 "errors" 5 "time" 6 7 "gno.land/p/gnoswap/consts/v1" 8 GetPoolPath (token0Path string, token1Path string, fee uint32) string
NewPoolV1 (store interface {GetFeeAmountTickSpacing func() map[uint32]int32; GetObservations func() *gno.land/p/nt/bptree/v0.BPTree; GetPendingProtocolFee func(string) int64; GetPendingProtocolFees func() map[string]int64; GetPoolCreationFee func() int64; GetPools func() *gno.land/p/nt/bptree/v0.BPTree; GetSlot0FeeProtocol func() uint8; GetSwapEndHook func() func(.uverse.realm, string) .uverse.error; GetSwapStartHook func() func(.uverse.realm, string, int64); GetTickCrossHook func() func(.uverse.realm, string, int32, bool, int64); GetUnlocked func() bool; GetWithdrawalFeeBPS func() uint64; HasFeeAmountTickSpacing func() bool; HasObservations func() bool; HasPendingProtocolFees func() bool; HasPoolCreationFee func() bool; HasPools func() bool; HasSlot0FeeProtocol func() bool; HasSwapEndHook func() bool; HasSwapStartHook func() bool; HasTickCrossHook func() bool; HasUnlocked func() bool; HasWithdrawalFeeBPS func() bool; RemovePendingProtocolFee func(int, .uverse.realm, string) .uverse.error; SetFeeAmountTickSpacing func(int, .uverse.realm, map[uint32]int32) .uverse.error; SetObservations func(int, .uverse.realm, *gno.land/p/nt/bptree/v0.BPTree) .uverse.error; SetPendingProtocolFee func(int, .uverse.realm, string, int64) .uverse.error; SetPendingProtocolFees func(int, .uverse.realm, map[string]int64) .uverse.error; SetPoolCreationFee func(int, .uverse.realm, int64) .uverse.error; SetPools func(int, .uverse.realm, *gno.land/p/nt/bptree/v0.BPTree) .uverse.error; SetSlot0FeeProtocol func(int, .uverse.realm, uint8) .uverse.error; SetSwapEndHook func(int, .uverse.realm, func(.uverse.realm, string) .uverse.error) .uverse.error; SetSwapStartHook func(int, .uverse.realm, func(.uverse.realm, string, int64)) .uverse.error; SetTickCrossHook func(int, .uverse.realm, func(.uverse.realm, string, int32, bool, int64)) .uverse.error; SetUnlocked func(int, .uverse.realm, bool) .uverse.error; SetWithdrawalFeeBPS func(int, .uverse.realm, uint64) .uverse.error}) interface {Burn func(int, .uverse.realm, string, string, uint32, int32, int32, string, .uverse.address) (string, string); Collect func(int, .uverse.realm, string, string, uint32, .uverse.address, int32, int32, string, string) (string, string); CollectProtocol func(int, .uverse.realm, string, string, uint32, .uverse.address, string, string) (string, string); CollectSwapFee func(int, .uverse.realm, string, string, uint32, .uverse.address, int32, int32, string, string) (string, string, string, string); CreatePool func(int, .uverse.realm, string, string, uint32, string); DrySwap func(string, string, uint32, bool, string, string) (string, string, .uverse.error); ExistsPoolPath func(string) bool; GetBalanceToken0 func(string) (int64, .uverse.error); GetBalanceToken1 func(string) (int64, .uverse.error); GetFee func(string) (uint32, .uverse.error); GetFeeAmountTickSpacing func(uint32) (int32, .uverse.error); GetFeeAmountTickSpacings func() map[uint32]int32; GetFeeGrowthGlobal0X128 func(string) (*gno.land/p/gnoswap/uint256/v1.Uint, .uverse.error); GetFeeGrowthGlobal1X128 func(string) (*gno.land/p/gnoswap/uint256/v1.Uint, .uverse.error); GetFeeGrowthGlobalX128 func(string) (*gno.land/p/gnoswap/uint256/v1.Uint, *gno.land/p/gnoswap/uint256/v1.Uint, .uverse.error); GetInitializedTicksInRange func(string, int32, int32) ([]int32, .uverse.error); GetLiquidity func(string) (*gno.land/p/gnoswap/uint256/v1.Uint, .uverse.error); GetObservationAt func(string, uint16) (gno.land/r/gnoswap/pool.Observation, .uverse.error); GetPendingProtocolFees func() map[string]int64; GetPoolCreationFee func() int64; GetPoolPositions func(string) *gno.land/p/nt/bptree/rotree/v0.ReadOnlyTree; GetPools func() *gno.land/p/nt/bptree/rotree/v0.ReadOnlyTree; GetPositionFeeGrowthInside0LastX128 func(string, string) (string, .uverse.error); GetPositionFeeGrowthInside1LastX128 func(string, string) (string, .uverse.error); GetPositionFeeGrowthInsideLastX128 func(string, string) (string, string, .uverse.error); GetPositionLiquidity func(string, string) (string, .uverse.error); GetPositionTokensOwed0 func(string, string) (int64, .uverse.error); GetPositionTokensOwed1 func(string, string) (int64, .uverse.error); GetProtocolFeesToken0 func(string) (int64, .uverse.error); GetProtocolFeesToken1 func(string) (int64, .uverse.error); GetSlot0 func(string) gno.land/r/gnoswap/pool.Slot0; GetSlot0FeeProtocol func(string) (uint8, .uverse.error); GetSlot0SqrtPriceX96 func(string) (*gno.land/p/gnoswap/uint256/v1.Uint, .uverse.error); GetSlot0Tick func(string) (int32, .uverse.error); GetSlot0Unlocked func(string) (bool, .uverse.error); GetTickBitmaps func(string, int16) (string, .uverse.error); GetTickCumulativeOutside func(string, int32) (int64, .uverse.error); GetTickFeeGrowthOutside0X128 func(string, int32) (string, .uverse.error); GetTickFeeGrowthOutside1X128 func(string, int32) (string, .uverse.error); GetTickFeeGrowthOutsideX128 func(string, int32) (string, string, .uverse.error); GetTickInfo func(string, int32) (gno.land/r/gnoswap/pool.TickInfo, .uverse.error); GetTickInitialized func(string, int32) (bool, .uverse.error); GetTickLiquidityGross func(string, int32) (string, .uverse.error); GetTickLiquidityNet func(string, int32) (string, .uverse.error); GetTickSecondsOutside func(string, int32) (uint32, .uverse.error); GetTickSecondsPerLiquidityOutsideX128 func(string, int32) (string, .uverse.error); GetTickSpacing func(string) (int32, .uverse.error); GetToken0Path func(string) (string, .uverse.error); GetToken1Path func(string) (string, .uverse.error); GetWithdrawalFee func() uint64; IncreaseObservationCardinalityNext func(int, .uverse.realm, string, string, uint32, uint16); Mint func(int, .uverse.realm, string, string, uint32, int32, int32, string, .uverse.address) (string, string); Observe func(string, []uint32) ([]int64, []string, .uverse.error); OracleConsult func(string, uint32) (int32, *gno.land/p/gnoswap/uint256/v1.Uint, .uverse.error); Render func(string) string; SetFeeProtocol func(int, .uverse.realm, uint8, uint8); SetPoolCreationFee func(int, .uverse.realm, int64); SetSwapEndHook func(int, .uverse.realm, func(.uverse.realm, string) .uverse.error); SetSwapStartHook func(int, .uverse.realm, func(.uverse.realm, string, int64)); SetTickCrossHook func(int, .uverse.realm, func(.uverse.realm, string, int32, bool, int64)); SetWithdrawalFee func(int, .uverse.realm, uint64); SnapshotCumulativesInside func(string, int32, int32) (int64, *gno.land/p/gnoswap/uint256/v1.Uint, uint32, .uverse.error); Swap func(int, .uverse.realm, string, string, uint32, .uverse.address, bool, string, string, func(.uverse.realm, int64, int64, *gno.land/r/gnoswap/pool.CallbackMarker) .uverse.error) (string, string)}
NewTickEventInfo (tickID int32, tickInfo struct{liquidityGross string; liquidityNet string; feeGrowthOutside0X128 string; feeGrowthOutside1X128 string; tickCumulativeOutside int64; secondsPerLiquidityOutsideX128 string; secondsOutside uint32; initialized bool}) *gno.land/r/gnoswap/pool/v1.tickEventInfo
Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.
This realm does not provide renderable output (vm/qrender → NoRenderDeclError).
View on gnoweb ↗ vm/qrender output, sanitized (docs/render-security.md) and displayed in an empty-sandbox iframe — scripts, forms and popups cannot run. Links stay inert in-preview; right-click to open.
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
13 const 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.
17 func 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.
70 func 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
137 func 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
174 func 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
201 func 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
245 func 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
258 func 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).
344 func 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
393 func 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.
440 func 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