oracle.gno
12.85 Kb · 472 lines
1package v1
2
3import (
4 "errors"
5 "time"
6
7 u256 "gno.land/p/gnoswap/uint256"
8 pl "gno.land/r/gnoswap/pool"
9)
10
11var max160 = u256.Zero().Sub(u256.Zero().Lsh(u256.One(), 160), u256.One()) // 2^160 - 1
12
13// maxObservationCardinality defines the maximum number of observations to store
14const maxObservationCardinality uint16 = 65535
15
16// GetTWAP calculates the time-weighted average price between two points in time
17// Returns the arithmetic mean tick and harmonic mean liquidity over the time period
18func getTWAP(p *pl.Pool, secondsAgo uint32) (int32, *u256.Uint, error) {
19 if secondsAgo == 0 {
20 return 0, nil, errors.New("secondsAgo must be greater than 0")
21 }
22
23 if p.ObservationState() == nil {
24 return 0, nil, errors.New("observation state not initialized")
25 }
26
27 // Get observations for current time and secondsAgo
28 secondsAgos := []uint32{secondsAgo, 0}
29 currentTime := time.Now().Unix()
30
31 tickCumulatives, secondsPerLiquidityCumulativeX128s, err := observe(
32 p.ObservationState(),
33 currentTime,
34 secondsAgos,
35 p.Slot0Tick(),
36 p.ObservationState().Index(),
37 p.Liquidity(),
38 p.ObservationState().Cardinality(),
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)), max160)
61 denominator := u256.Zero().Lsh(secondsPerLiquidityDelta, 32)
62 harmonicMeanLiquidity := u256.Zero().Div(secondsAgoX160, denominator)
63
64 return arithmeticMeanTick, harmonicMeanLiquidity, nil
65}
66
67func writeObservationByPool(
68 p *pl.Pool,
69 currentTime int64,
70 tick int32,
71 liquidity *u256.Uint,
72) error {
73 if p.ObservationState() == nil {
74 p.SetObservationState(pl.NewObservationState(currentTime))
75 }
76
77 err := writeObservation(p.ObservationState(), currentTime, tick, liquidity)
78 if err != nil {
79 return err
80 }
81
82 return nil
83}
84
85func increaseObservationCardinalityNextByPool(p *pl.Pool, observationCardinalityNext uint16) error {
86 observationState := p.ObservationState()
87
88 if observationState == nil {
89 return errors.New("observation state not initialized")
90 }
91
92 if observationCardinalityNext > maxObservationCardinality {
93 return errors.New("observation cardinality next exceeds maximum")
94 }
95
96 if observationCardinalityNext <= observationState.CardinalityNext() {
97 return errors.New("observation cardinality next must be greater than current")
98 }
99
100 observationCardinalityNextNew, err := grow(observationState, observationState.Cardinality(), observationCardinalityNext)
101 if err != nil {
102 return err
103 }
104
105 observationState.SetCardinalityNext(observationCardinalityNextNew)
106
107 return nil
108}
109
110func transform(lastObservation *pl.Observation, currentTime int64, tick int32, liquidity *u256.Uint) (*pl.Observation, error) {
111 timeDelta := currentTime - lastObservation.BlockTimestamp()
112 if timeDelta < 0 {
113 return nil, errors.New("time delta must be greater than 0")
114 }
115
116 // calculate cumulative values
117 tickCumulative := lastObservation.TickCumulative() + int64(tick)*timeDelta
118
119 // calculate liquidity cumulative
120 liquidityDelta, overflow := u256.Zero().MulOverflow(liquidity, u256.NewUintFromInt64(timeDelta))
121 if overflow {
122 panic(errOverflow)
123 }
124
125 prevLiqCum := u256.MustFromDecimal(lastObservation.LiquidityCumulative())
126 liquidityCumulative := u256.Zero().Add(prevLiqCum, liquidityDelta)
127
128 // calculate seconds per liquidity
129 liquidityForCalc := liquidity
130 if liquidity.IsZero() {
131 liquidityForCalc = u256.One()
132 }
133
134 // secondsPerLiquidity += timeDelta * 2^128 / max(1, liquidity)
135 secondsPerLiquidityDelta := u256.MulDiv(
136 u256.NewUintFromInt64(timeDelta),
137 q128FromDecimal,
138 liquidityForCalc,
139 )
140
141 prevSecPerLiq := u256.MustFromDecimal(lastObservation.SecondsPerLiquidityCumulativeX128())
142 secondsPerLiquidityCumulativeX128 := u256.Zero().Add(
143 prevSecPerLiq,
144 secondsPerLiquidityDelta,
145 )
146
147 return pl.NewObservation(
148 currentTime,
149 tickCumulative,
150 liquidityCumulative.ToString(),
151 secondsPerLiquidityCumulativeX128.ToString(),
152 true,
153 ), nil
154}
155
156func grow(os *pl.ObservationState, currentCardinality, nextCardinality uint16) (uint16, error) {
157 if currentCardinality <= 0 {
158 return currentCardinality, errors.New("currentCardinality must be greater than 0")
159 }
160
161 if nextCardinality <= currentCardinality {
162 return currentCardinality, nil
163 }
164
165 if nextCardinality > maxObservationCardinality {
166 return currentCardinality, errors.New("nextCardinality exceeds maximum")
167 }
168
169 // This is more efficient than checking all slots from 0
170 for i := currentCardinality; i < nextCardinality; i++ {
171 observation := pl.NewDefaultObservation()
172 observation.SetBlockTimestamp(1)
173 os.SetObservation(i, observation)
174 }
175
176 return nextCardinality, nil
177}
178
179func lte(time, a, b int64) bool {
180 if (a <= time) && (time <= b) {
181 return a <= b
182 }
183
184 aAdjusted := u256.NewUintFromInt64(a)
185 bAdjusted := u256.NewUintFromInt64(b)
186 timeUint256 := u256.NewUintFromInt64(time)
187
188 if aAdjusted.Gt(timeUint256) {
189 aAdjusted = aAdjusted.Add(aAdjusted, u256.NewUintFromInt64(1<<32))
190 }
191
192 if bAdjusted.Gt(timeUint256) {
193 bAdjusted = bAdjusted.Add(bAdjusted, u256.NewUintFromInt64(1<<32))
194 }
195
196 return aAdjusted.Lte(bAdjusted)
197}
198
199func writeObservation(
200 os *pl.ObservationState,
201 currentTime int64,
202 tick int32,
203 liquidity *u256.Uint,
204) error {
205 lastObservation, err := lastObservation(os)
206 if err != nil {
207 return err
208 }
209
210 if lastObservation.BlockTimestamp() == currentTime {
211 return nil
212 }
213
214 // Check if we need to grow the cardinality
215 if os.CardinalityNext() > os.Cardinality() && os.Index() == os.Cardinality()-1 {
216 os.SetCardinality(os.CardinalityNext())
217 }
218
219 nextIndex := (os.Index() + 1) % os.Cardinality()
220
221 // Ensure the slot exists before writing
222 if _, ok := os.Observations()[nextIndex]; !ok {
223 os.SetObservation(nextIndex, pl.NewDefaultObservation())
224 }
225
226 observation, err := transform(lastObservation, currentTime, tick, liquidity)
227 if err != nil {
228 return err
229 }
230
231 os.SetObservation(nextIndex, observation)
232 os.SetIndex(nextIndex)
233
234 return nil
235}
236
237func lastObservation(os *pl.ObservationState) (*pl.Observation, error) {
238 observation, ok := os.Observations()[os.Index()]
239 if !ok || observation == nil {
240 return nil, errNotInitializedObservation
241 }
242
243 return observation, nil
244}
245
246// observationAt returns the observation at a specific index
247// Returns error if the observation doesn't exist
248func observationAt(os *pl.ObservationState, index uint16) (*pl.Observation, error) {
249 obs, ok := os.Observations()[index]
250 if !ok || obs == nil {
251 return nil, 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 os *pl.ObservationState,
260 currentTime 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(os, index)
270 if err != nil {
271 return 0, "", err
272 }
273
274 if last.BlockTimestamp() != currentTime {
275 // need to create virtual observation for current time
276 transformed, err := transform(last, currentTime, 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 target := currentTime - int64(secondsAgo)
288
289 // find the observations before and after the target
290 beforeOrAt, atOrAfter, err := getSurroundingObservations(
291 os,
292 target,
293 currentTime,
294 tick,
295 index,
296 liquidity,
297 cardinality,
298 )
299 if err != nil {
300 return 0, "", err
301 }
302
303 if target == beforeOrAt.BlockTimestamp() {
304 return beforeOrAt.TickCumulative(), beforeOrAt.SecondsPerLiquidityCumulativeX128(), nil
305 }
306
307 if target == atOrAfter.BlockTimestamp() {
308 return atOrAfter.TickCumulative(), atOrAfter.SecondsPerLiquidityCumulativeX128(), nil
309 }
310
311 // interpolate between the two observations
312 observationTimeDelta := atOrAfter.BlockTimestamp() - beforeOrAt.BlockTimestamp()
313 targetDelta := target - beforeOrAt.BlockTimestamp()
314
315 // tickCumulative += (tickCumulativeAfter - tickCumulativeBefore) / observationTimeDelta * targetDelta
316 tickCumulative := beforeOrAt.TickCumulative() +
317 ((atOrAfter.TickCumulative()-beforeOrAt.TickCumulative())/observationTimeDelta)*targetDelta
318
319 beforeSecPerLiq := u256.MustFromDecimal(beforeOrAt.SecondsPerLiquidityCumulativeX128())
320 afterSecPerLiq := u256.MustFromDecimal(atOrAfter.SecondsPerLiquidityCumulativeX128())
321
322 // for secondsPerLiquidity, need to interpolate carefully
323 secondsPerLiquidityDelta := u256.Zero().Sub(afterSecPerLiq, beforeSecPerLiq)
324
325 secondsPerLiquidity := u256.Zero().Add(
326 beforeSecPerLiq,
327 u256.MulDiv(
328 secondsPerLiquidityDelta,
329 u256.NewUintFromInt64(targetDelta),
330 u256.NewUintFromInt64(observationTimeDelta),
331 ),
332 )
333
334 return tickCumulative, secondsPerLiquidity.ToString(), nil
335}
336
337// getSurroundingObservations finds the observations immediately before and after the target timestamp.
338// It uses binary search over the logical time-ordered view of the circular buffer.
339// Logical order starts at (index+1) % cardinality (oldest) and ends at index (latest).
340func getSurroundingObservations(
341 os *pl.ObservationState,
342 target int64,
343 currentTime int64,
344 tick int32,
345 index uint16,
346 liquidity *u256.Uint,
347 cardinality uint16,
348) (*pl.Observation, *pl.Observation, error) {
349 // Optimistically set before to the newest observation
350 beforeOrAt, err := observationAt(os, index)
351 if err != nil {
352 return nil, nil, err
353 }
354
355 // If the target is chronologically at or after the newest observation, we can early return
356 if lte(currentTime, beforeOrAt.BlockTimestamp(), target) {
357 if beforeOrAt.BlockTimestamp() == target {
358 // If newest observation equals target, we're in the same block, so we can ignore atOrAfter
359 return beforeOrAt, nil, nil
360 }
361 // Otherwise, we need to transform
362 atOrAfter, err := transform(beforeOrAt, target, tick, liquidity)
363 if err != nil {
364 return nil, nil, err
365 }
366 return beforeOrAt, atOrAfter, nil
367 }
368
369 // Now, set before to the oldest observation
370 start := (index + 1) % cardinality
371 beforeOrAt, err = observationAt(os, start)
372 if err != nil || !beforeOrAt.Initialized() {
373 beforeOrAt, err = observationAt(os, 0)
374 if err != nil {
375 return nil, nil, err
376 }
377 }
378
379 // Ensure that the target is chronologically at or after the oldest observation
380 if !lte(currentTime, beforeOrAt.BlockTimestamp(), target) {
381 return nil, nil, errObservationTooOld
382 }
383
384 // If we've reached this point, we have to binary search
385 return binarySearch(os, currentTime, target, index, cardinality)
386}
387
388func binarySearch(
389 os *pl.ObservationState,
390 currentTime int64,
391 target int64,
392 index uint16,
393 cardinality uint16,
394) (*pl.Observation, *pl.Observation, error) {
395 l := uint64((index + 1) % cardinality) // oldest observation
396 r := l + uint64(cardinality) - 1 // newest observation
397 var i uint64
398 var beforeOrAt, atOrAfter *pl.Observation
399 var err error
400
401 for {
402 i = (l + r) / 2
403
404 beforeIndex := uint16(i % uint64(cardinality))
405 beforeOrAt, err = observationAt(os, beforeIndex)
406 if err != nil || !beforeOrAt.Initialized() {
407 // we've landed on an uninitialized tick, keep searching higher (more recently)
408 l = i + 1
409 continue
410 }
411
412 afterIndex := uint16((i + 1) % uint64(cardinality))
413 atOrAfter, err = observationAt(os, afterIndex)
414 if err != nil {
415 return nil, nil, err
416 }
417
418 targetAtOrAfter := lte(currentTime, beforeOrAt.BlockTimestamp(), target)
419
420 // check if we've found the answer!
421 if targetAtOrAfter && lte(currentTime, target, atOrAfter.BlockTimestamp()) {
422 break
423 }
424
425 if !targetAtOrAfter {
426 r = i - 1
427 } else {
428 l = i + 1
429 }
430 }
431
432 return beforeOrAt, atOrAfter, nil
433}
434
435// observe returns the cumulative tick and liquidity as of each timestamp secondsAgo from the current time.
436func observe(
437 os *pl.ObservationState,
438 currentTime int64,
439 secondsAgos []uint32,
440 tick int32,
441 index uint16,
442 liquidity *u256.Uint,
443 cardinality uint16,
444) ([]int64, []string, error) {
445 if cardinality <= 0 {
446 return nil, nil, errors.New("observation cardinality must be greater than 0")
447 }
448
449 historyCount := len(secondsAgos)
450 tickCumulatives := make([]int64, historyCount)
451 secondsPerLiquidityCumulativeX128s := make([]string, historyCount)
452
453 for i, secondsAgo := range secondsAgos {
454 tickCumulative, secondsPerLiquidity, err := observeSingle(
455 os,
456 currentTime,
457 secondsAgo,
458 tick,
459 index,
460 liquidity,
461 cardinality,
462 )
463 if err != nil {
464 return nil, nil, err
465 }
466
467 tickCumulatives[i] = tickCumulative
468 secondsPerLiquidityCumulativeX128s[i] = secondsPerLiquidity
469 }
470
471 return tickCumulatives, secondsPerLiquidityCumulativeX128s, nil
472}