pool.gno
26.26 Kb · 889 lines
1package staker
2
3import (
4 "errors"
5 "strconv"
6 "time"
7
8 i256 "gno.land/p/gnoswap/int256"
9 u256 "gno.land/p/gnoswap/uint256"
10 bptree "gno.land/p/nt/bptree/v0"
11 ufmt "gno.land/p/nt/ufmt/v0"
12)
13
14const AllTierCount = 4 // 0, 1, 2, 3
15
16// q128 is 2^128, used to scale rewardPerSecond so that sub-integer rates
17// (e.g. 10_000_000 / 3600 = 2777.77...) do not lose precision over the
18// lifetime of an incentive. Mirrors the q128 constant used by the v1 reward
19// calculator; kept package-local here because staker depends on neither.
20var q128 = u256.MustFromDecimal("340282366920938463463374607431768211456")
21
22// Pool is a struct for storing an incentivized pool information
23// Each pool stores Incentives and Ticks associated with it.
24//
25// Fields:
26// - poolPath: The path of the pool.
27//
28// - currentStakedLiquidity:
29// The current total staked liquidity of the in-range positions for the pool.
30// Updated when tick cross happens or stake/unstake happens.
31// Used to calculate the global reward ratio accumulation or
32// decide whether to enter/exit unclaimable period.
33//
34// - lastUnclaimableTime:
35// The time at which the unclaimable period started.
36// Set to 0 when the pool is not in an unclaimable period.
37//
38// - unclaimableAcc:
39// The accumulated undisributed unclaimable reward.
40// Reset to 0 when processUnclaimableReward is called and sent to community pool.
41//
42// - rewardCache:
43// The cached per-second reward emitted for this pool.
44// Stores new entry only when the reward is changed.
45// PoolTier.cacheReward() updates this.
46//
47// - incentives: The external incentives associated with the pool.
48//
49// - ticks: The Ticks associated with the pool.
50//
51// - globalRewardRatioAccumulation:
52// Global ratio of Time / TotalStake accumulation(since the pool creation)
53// Stores new entry only when tick cross or stake/unstake happens.
54// It is used to calculate the reward for a staked position at certain time.
55//
56// - historicalTick:
57// The historical tick for the pool at a given time.
58// It does not reflect the exact tick at the timestamp,
59// but it provides correct ordering for the staked position's ticks.
60// Therefore, you should not compare it for equality, only for ordering.
61// Set when tick cross happens or a new position is created.
62type Pool struct {
63 poolPath string
64
65 stakedLiquidity *UintTree // uint64 timestamp -> *u256.Uint(Q128)
66
67 lastUnclaimableTime int64
68 unclaimableAcc int64
69
70 rewardCache *UintTree // uint64 timestamp -> int64 gnsReward
71
72 incentives *Incentives
73
74 ticks Ticks // int32 tickId -> Tick tick
75
76 globalRewardRatioAccumulation *UintTree // uint64 timestamp -> *u256.Uint(Q128) rewardRatioAccumulation
77
78 historicalTick *UintTree // uint64 timestamp -> int32 tickId
79}
80
81// Pool Getter/Setter methods
82
83// PoolPath returns the pool path
84func (p *Pool) PoolPath() string {
85 return p.poolPath
86}
87
88// SetPoolPath sets the pool path
89func (p *Pool) SetPoolPath(poolPath string) {
90 p.poolPath = poolPath
91}
92
93// StakedLiquidity returns the staked liquidity tree
94func (p *Pool) StakedLiquidity() *UintTree {
95 return p.stakedLiquidity
96}
97
98// SetStakedLiquidity sets the staked liquidity tree
99func (p *Pool) SetStakedLiquidity(stakedLiquidity *UintTree) {
100 p.stakedLiquidity = stakedLiquidity
101}
102
103func (p *Pool) SetStakedLiquidityAt(currentTime int64, delta *u256.Uint) {
104 p.StakedLiquidity().Set(currentTime, u256.Zero().Set(delta))
105}
106
107// LastUnclaimableTime returns the last unclaimable time
108func (p *Pool) LastUnclaimableTime() int64 {
109 return p.lastUnclaimableTime
110}
111
112// SetLastUnclaimableTime sets the last unclaimable time
113func (p *Pool) SetLastUnclaimableTime(lastUnclaimableTime int64) {
114 p.lastUnclaimableTime = lastUnclaimableTime
115}
116
117// UnclaimableAcc returns the unclaimable accumulation
118func (p *Pool) UnclaimableAcc() int64 {
119 return p.unclaimableAcc
120}
121
122// SetUnclaimableAcc sets the unclaimable accumulation
123func (p *Pool) SetUnclaimableAcc(unclaimableAcc int64) {
124 p.unclaimableAcc = unclaimableAcc
125}
126
127// RewardCache returns the reward cache tree
128func (p *Pool) RewardCache() *UintTree {
129 return p.rewardCache
130}
131
132// SetRewardCache sets the reward cache tree
133func (p *Pool) SetRewardCache(rewardCache *UintTree) {
134 p.rewardCache = rewardCache
135}
136
137func (p *Pool) SetRewardCacheAt(currentTime int64, reward int64) {
138 p.RewardCache().Set(currentTime, reward)
139}
140
141// Incentives returns the incentives
142func (p *Pool) Incentives() *Incentives {
143 return p.incentives
144}
145
146// SetIncentives sets the incentives
147func (p *Pool) SetIncentives(incentives *Incentives) {
148 p.incentives = incentives
149}
150
151// Ticks returns the ticks
152func (p *Pool) Ticks() *Ticks {
153 return &p.ticks
154}
155
156// SetTicks sets the ticks
157func (p *Pool) SetTicks(ticks Ticks) {
158 p.ticks = ticks
159}
160
161// GlobalRewardRatioAccumulation returns the global reward ratio accumulation tree
162func (p *Pool) GlobalRewardRatioAccumulation() *UintTree {
163 return p.globalRewardRatioAccumulation
164}
165
166// SetGlobalRewardRatioAccumulation sets the global reward ratio accumulation tree
167func (p *Pool) SetGlobalRewardRatioAccumulation(globalRewardRatioAccumulation *UintTree) {
168 p.globalRewardRatioAccumulation = globalRewardRatioAccumulation
169}
170
171func (p *Pool) SetGlobalRewardRatioAccumulationAt(currentTime int64, acc string) {
172 p.GlobalRewardRatioAccumulation().Set(currentTime, acc)
173}
174
175// HistoricalTick returns the historical tick tree
176func (p *Pool) HistoricalTick() *UintTree {
177 return p.historicalTick
178}
179
180// SetHistoricalTick sets the historical tick tree
181func (p *Pool) SetHistoricalTick(historicalTick *UintTree) {
182 p.historicalTick = historicalTick
183}
184
185func (p *Pool) SetHistoricalTickAt(currentTime int64, tick int32) {
186 p.HistoricalTick().Set(currentTime, tick)
187}
188
189// Clone returns a deep copy of the pool.
190func (p *Pool) Clone() *Pool {
191 if p == nil {
192 return nil
193 }
194
195 return &Pool{
196 poolPath: p.poolPath,
197 stakedLiquidity: nil,
198 lastUnclaimableTime: p.lastUnclaimableTime,
199 unclaimableAcc: p.unclaimableAcc,
200 rewardCache: nil,
201 incentives: nil,
202 ticks: NewTicks(),
203 globalRewardRatioAccumulation: nil,
204 historicalTick: nil,
205 }
206}
207
208// NewPool creates a new pool with the given poolPath and currentHeight.
209func NewPool(poolPath string, currentTime int64) *Pool {
210 pool := &Pool{
211 poolPath: poolPath,
212 stakedLiquidity: NewUintTreeN(64),
213 // lastUnclaimableTime is initialized to 0, which means "tracking not started yet".
214 // When the pool receives a tier assignment (or external incentive), `cacheReward` will be called,
215 // which will automatically call `startUnclaimablePeriod` if the pool has zero liquidity.
216 // This ensures proper unclaimable period tracking from the moment rewards start emitting.
217 lastUnclaimableTime: 0,
218 unclaimableAcc: 0,
219 rewardCache: NewUintTreeN(64),
220 incentives: NewIncentives(poolPath),
221 ticks: NewTicks(),
222 globalRewardRatioAccumulation: NewUintTreeN(64),
223 historicalTick: NewUintTreeN(64),
224 }
225
226 pool.SetGlobalRewardRatioAccumulationAt(currentTime, "0")
227
228 // Initialize rewardCache to 0 to ensure `cacheReward` will trigger on first tier assignment
229 pool.SetRewardCacheAt(currentTime, int64(0))
230 pool.SetStakedLiquidityAt(currentTime, u256.Zero())
231
232 return pool
233}
234
235// Incentives represents a collection of external incentives for a specific pool.
236//
237// Fields:
238//
239// - incentives: BPTree storing ExternalIncentive objects indexed by incentiveId
240// The incentiveId serves as the key to efficiently lookup incentive details
241//
242// - targetPoolPath: String identifier for the pool this incentive collection belongs to
243// Used to associate incentives with their corresponding liquidity pool
244//
245// - unclaimablePeriods: Tree storing periods when rewards cannot be claimed
246// Maps start timestamp (key) to end timestamp (value)
247// An end timestamp of 0 indicates an ongoing unclaimable period
248// Used to track intervals when staking rewards are not claimable
249type Incentives struct {
250 incentives *bptree.BPTree // (incentiveId) => ExternalIncentive
251
252 targetPoolPath string // The target pool path for this incentive collection
253
254 unclaimablePeriods *UintTree // blockTimestamp -> any
255}
256
257// Incentives Getter/Setter methods
258
259// Incentives returns the incentives tree
260func (i *Incentives) IncentiveTrees() *bptree.BPTree {
261 return i.incentives
262}
263
264// SetIncentives sets the incentives tree
265func (i *Incentives) SetIncentives(incentives *bptree.BPTree) {
266 i.incentives = incentives
267}
268
269// TargetPoolPath returns the target pool path
270func (i *Incentives) TargetPoolPath() string {
271 return i.targetPoolPath
272}
273
274// SetTargetPoolPath sets the target pool path
275func (i *Incentives) SetTargetPoolPath(targetPoolPath string) {
276 i.targetPoolPath = targetPoolPath
277}
278
279// UnclaimablePeriods returns the unclaimable periods tree
280func (i *Incentives) UnclaimablePeriods() *UintTree {
281 return i.unclaimablePeriods
282}
283
284// SetUnclaimablePeriods sets the unclaimable periods tree
285func (i *Incentives) SetUnclaimablePeriods(unclaimablePeriods *UintTree) {
286 i.unclaimablePeriods = unclaimablePeriods
287}
288
289// Incentive returns an incentive by ID
290func (i *Incentives) Incentive(incentiveId string) (*ExternalIncentive, bool) {
291 value, exists := i.incentives.Get(incentiveId)
292 if !exists {
293 return nil, false
294 }
295 incentive, ok := value.(*ExternalIncentive)
296 return incentive, ok
297}
298
299// SetIncentive sets an incentive by ID
300func (i *Incentives) SetIncentive(incentiveId string, incentive *ExternalIncentive) {
301 i.incentives.Set(incentiveId, incentive)
302}
303
304func (i *Incentives) SetUnclaimablePeriod(startTimestamp int64, endTimestamp int64) {
305 i.unclaimablePeriods.Set(startTimestamp, endTimestamp)
306}
307
308func (i *Incentives) RemoveUnclaimablePeriod(startTimestamp int64) {
309 i.unclaimablePeriods.Remove(startTimestamp)
310}
311
312// IterateIncentives iterates over all incentives
313func (i *Incentives) IterateIncentives(fn func(incentiveId string, incentive *ExternalIncentive) bool) {
314 i.incentives.Iterate("", "", func(key string, value interface{}) bool {
315 if incentive, ok := value.(*ExternalIncentive); ok {
316 return fn(key, incentive)
317 }
318 return false
319 })
320}
321
322func NewIncentives(targetPoolPath string) *Incentives {
323 result := &Incentives{
324 targetPoolPath: targetPoolPath,
325 unclaimablePeriods: NewUintTreeN(64),
326 incentives: bptree.NewBPTreeN(16),
327 }
328
329 // initial unclaimable period starts, as there cannot be any staked positions yet.
330 currentTimestamp := time.Now().Unix()
331 result.SetUnclaimablePeriod(currentTimestamp, int64(0))
332 return result
333}
334
335type ExternalIncentive struct {
336 incentiveId string // incentive id
337 startTimestamp int64 // start time for external reward
338 endTimestamp int64 // end time for external reward
339 createdHeight int64 // block height when the incentive was created
340 createdTimestamp int64 // timestamp when the incentive was created
341 depositGnsAmount int64 // deposited gns amount
342 targetPoolPath string // external reward target pool path
343 rewardToken string // external reward token path
344 totalRewardAmount int64 // total reward amount
345 rewardAmount int64 // to be distributed reward amount
346 rewardPerSecondX128 *u256.Uint // reward per second, scaled by 2^128 to preserve sub-second precision
347 distributedRewardAmount int64 // distributed reward amount, when un-staked and refunded
348 accumulatedPenaltyAmount int64 // accumulated warmup penalty from CollectReward
349 creator address // creator address
350
351 refunded bool // whether incentive has been refunded (includes GNS deposit and unclaimed rewards)
352}
353
354// ExternalIncentive Getter/Setter methods
355
356// IncentiveId returns the incentive ID
357func (e *ExternalIncentive) IncentiveId() string {
358 return e.incentiveId
359}
360
361// SetIncentiveId sets the incentive ID
362func (e *ExternalIncentive) SetIncentiveId(incentiveId string) {
363 e.incentiveId = incentiveId
364}
365
366// StartTimestamp returns the start timestamp
367func (e *ExternalIncentive) StartTimestamp() int64 {
368 return e.startTimestamp
369}
370
371// SetStartTimestamp sets the start timestamp
372func (e *ExternalIncentive) SetStartTimestamp(startTimestamp int64) {
373 e.startTimestamp = startTimestamp
374}
375
376// EndTimestamp returns the end timestamp
377func (e *ExternalIncentive) EndTimestamp() int64 {
378 return e.endTimestamp
379}
380
381// SetEndTimestamp sets the end timestamp
382func (e *ExternalIncentive) SetEndTimestamp(endTimestamp int64) {
383 e.endTimestamp = endTimestamp
384}
385
386// CreatedHeight returns the created height
387func (e *ExternalIncentive) CreatedHeight() int64 {
388 return e.createdHeight
389}
390
391// SetCreatedHeight sets the created height
392func (e *ExternalIncentive) SetCreatedHeight(createdHeight int64) {
393 e.createdHeight = createdHeight
394}
395
396// CreatedTimestamp returns the created timestamp
397func (e *ExternalIncentive) CreatedTimestamp() int64 {
398 return e.createdTimestamp
399}
400
401// SetCreatedTimestamp sets the created timestamp
402func (e *ExternalIncentive) SetCreatedTimestamp(createdTimestamp int64) {
403 e.createdTimestamp = createdTimestamp
404}
405
406// DepositGnsAmount returns the deposit GNS amount
407func (e *ExternalIncentive) DepositGnsAmount() int64 {
408 return e.depositGnsAmount
409}
410
411// SetDepositGnsAmount sets the deposit GNS amount
412func (e *ExternalIncentive) SetDepositGnsAmount(depositGnsAmount int64) {
413 e.depositGnsAmount = depositGnsAmount
414}
415
416// TargetPoolPath returns the target pool path
417func (e *ExternalIncentive) TargetPoolPath() string {
418 return e.targetPoolPath
419}
420
421// SetTargetPoolPath sets the target pool path
422func (e *ExternalIncentive) SetTargetPoolPath(targetPoolPath string) {
423 e.targetPoolPath = targetPoolPath
424}
425
426// RewardToken returns the reward token
427func (e *ExternalIncentive) RewardToken() string {
428 return e.rewardToken
429}
430
431// SetRewardToken sets the reward token
432func (e *ExternalIncentive) SetRewardToken(rewardToken string) {
433 e.rewardToken = rewardToken
434}
435
436// TotalRewardAmount returns the total reward amount
437func (e *ExternalIncentive) TotalRewardAmount() int64 {
438 return e.totalRewardAmount
439}
440
441// SetTotalRewardAmount sets the total reward amount
442func (e *ExternalIncentive) SetTotalRewardAmount(totalRewardAmount int64) {
443 e.totalRewardAmount = totalRewardAmount
444}
445
446// RewardAmount returns the reward amount
447func (e *ExternalIncentive) RewardAmount() int64 {
448 return e.rewardAmount
449}
450
451// SetRewardAmount sets the reward amount
452func (e *ExternalIncentive) SetRewardAmount(rewardAmount int64) {
453 e.rewardAmount = rewardAmount
454}
455
456// RewardPerSecondX128 returns the Q128-scaled reward per second.
457// The underlying value is (rewardAmount << 128) / duration.
458func (e *ExternalIncentive) RewardPerSecondX128() *u256.Uint {
459 return e.rewardPerSecondX128
460}
461
462// SetRewardPerSecondX128 sets the Q128-scaled reward per second.
463func (e *ExternalIncentive) SetRewardPerSecondX128(rewardPerSecondX128 *u256.Uint) {
464 e.rewardPerSecondX128 = u256.Zero().Set(rewardPerSecondX128)
465}
466
467// DistributedRewardAmount returns the distributed reward amount
468func (e *ExternalIncentive) DistributedRewardAmount() int64 {
469 return e.distributedRewardAmount
470}
471
472// SetDistributedRewardAmount sets the distributed reward amount
473func (e *ExternalIncentive) SetDistributedRewardAmount(distributedRewardAmount int64) {
474 e.distributedRewardAmount = distributedRewardAmount
475}
476
477// AccumulatedPenaltyAmount returns the accumulated warmup penalty amount
478func (e *ExternalIncentive) AccumulatedPenaltyAmount() int64 {
479 return e.accumulatedPenaltyAmount
480}
481
482// SetAccumulatedPenaltyAmount sets the accumulated warmup penalty amount
483func (e *ExternalIncentive) SetAccumulatedPenaltyAmount(accumulatedPenaltyAmount int64) {
484 e.accumulatedPenaltyAmount = accumulatedPenaltyAmount
485}
486
487// Creator returns the creator address
488func (e *ExternalIncentive) Creator() address {
489 return e.creator
490}
491
492// SetCreator sets the creator address
493func (e *ExternalIncentive) SetCreator(creator address) {
494 e.creator = creator
495}
496
497// Refunded returns the refunded status
498func (e *ExternalIncentive) Refunded() bool {
499 return e.refunded
500}
501
502// SetRefunded sets the refunded status
503func (e *ExternalIncentive) SetRefunded(refunded bool) {
504 e.refunded = refunded
505}
506
507func (e *ExternalIncentive) Clone() *ExternalIncentive {
508 rewardPerSecondX128 := u256.Zero()
509
510 if e.rewardPerSecondX128 != nil {
511 rewardPerSecondX128 = e.rewardPerSecondX128.Clone()
512 }
513
514 return &ExternalIncentive{
515 incentiveId: e.incentiveId,
516 startTimestamp: e.startTimestamp,
517 endTimestamp: e.endTimestamp,
518 createdHeight: e.createdHeight,
519 createdTimestamp: e.createdTimestamp,
520 depositGnsAmount: e.depositGnsAmount,
521 targetPoolPath: e.targetPoolPath,
522 rewardToken: e.rewardToken,
523 totalRewardAmount: e.totalRewardAmount,
524 rewardAmount: e.rewardAmount,
525 rewardPerSecondX128: rewardPerSecondX128,
526 creator: e.creator,
527 refunded: e.refunded,
528 distributedRewardAmount: e.distributedRewardAmount,
529 accumulatedPenaltyAmount: e.accumulatedPenaltyAmount,
530 }
531}
532
533// NewExternalIncentive creates a new external incentive
534func NewExternalIncentive(
535 incentiveId string,
536 targetPoolPath string,
537 rewardToken string,
538 rewardAmount int64,
539 startTimestamp int64, // timestamp is in unix time(seconds)
540 endTimestamp int64,
541 creator address,
542 depositGnsAmount int64,
543 createdHeight int64,
544 currentTime int64, // current time in unix time(seconds)
545) *ExternalIncentive {
546 incentiveDuration := endTimestamp - startTimestamp
547
548 // Compute reward per second scaled by 2^128 to preserve sub-second precision.
549 // rewardPerSecondX128 = (rewardAmount << 128) / incentiveDuration.
550 // Consumers must divide by 2^128 when materializing back to a plain integer.
551 rewardPerSecondX128 := u256.MulDiv(
552 u256.NewUintFromInt64(rewardAmount),
553 q128,
554 u256.NewUintFromInt64(incentiveDuration),
555 )
556
557 return &ExternalIncentive{
558 incentiveId: incentiveId,
559 targetPoolPath: targetPoolPath,
560 rewardToken: rewardToken,
561 totalRewardAmount: rewardAmount,
562 rewardAmount: rewardAmount,
563 startTimestamp: startTimestamp,
564 endTimestamp: endTimestamp,
565 rewardPerSecondX128: rewardPerSecondX128,
566 distributedRewardAmount: 0,
567 accumulatedPenaltyAmount: 0,
568 creator: creator,
569 createdHeight: createdHeight,
570 createdTimestamp: currentTime,
571 depositGnsAmount: depositGnsAmount,
572 refunded: false,
573 }
574}
575
576// Tick mapping for each pool
577type Ticks struct {
578 tree *bptree.BPTree // int32 tickId -> tick
579}
580
581// Ticks Getter/Setter methods
582
583// Tree returns the ticks tree
584func (t *Ticks) Tree() *bptree.BPTree {
585 return t.tree
586}
587
588// SetTree sets the ticks tree
589func (t *Ticks) SetTree(tree *bptree.BPTree) {
590 t.tree = tree
591}
592
593func (t *Ticks) Get(tickId int32) *Tick {
594 v, ok := t.tree.Get(EncodeInt(tickId))
595 if !ok {
596 tick := &Tick{
597 id: tickId,
598 stakedLiquidityGross: u256.Zero(),
599 stakedLiquidityDelta: i256.Zero(),
600 outsideAccumulation: NewUintTreeN(64),
601 }
602 t.tree.Set(EncodeInt(tickId), tick)
603 return tick
604 }
605
606 tick, ok := v.(*Tick)
607 if !ok {
608 panic("failed to cast value to *Tick")
609 }
610 return tick
611}
612
613func (self *Ticks) Has(tickId int32) bool {
614 return self.tree.Has(EncodeInt(tickId))
615}
616
617// SetTick sets a tick by ID
618func (t *Ticks) SetTick(tickId int32, tick *Tick) {
619 if tick.stakedLiquidityGross.IsZero() {
620 t.tree.Remove(EncodeInt(tickId))
621 return
622 }
623
624 t.tree.Set(EncodeInt(tickId), tick)
625}
626
627// IterateTicks iterates over all ticks
628func (t *Ticks) IterateTicks(fn func(tickId int32, tick *Tick) bool) {
629 t.tree.Iterate("", "", func(key string, value interface{}) bool {
630 tick, ok := value.(*Tick)
631 if !ok {
632 return false
633 }
634
635 // Convert string key back to int32
636 tickId, err := strconv.Atoi(key)
637 if err != nil {
638 return false // skip invalid keys
639 }
640
641 return fn(int32(tickId), tick)
642 })
643}
644
645// Clone returns a deep copy of ticks.
646func (t Ticks) Clone() Ticks {
647 cloned := bptree.NewBPTreeN(16)
648 t.tree.Iterate("", "", func(key string, value any) bool {
649 tick, ok := value.(*Tick)
650 if !ok {
651 panic("failed to cast value to *Tick")
652 }
653 cloned.Set(key, tick.Clone())
654 return false
655 })
656 return Ticks{tree: cloned}
657}
658
659func NewTicks() Ticks {
660 return Ticks{
661 tree: bptree.NewBPTreeN(16),
662 }
663}
664
665// Tick represents the state of a specific tick in a pool.
666//
667// Fields:
668// - id (int32): The ID of the tick.
669// - stakedLiquidityGross (*u256.Uint): Total gross staked liquidity at this tick.
670// - stakedLiquidityDelta (*i256.Int): Net change in staked liquidity at this tick.
671// - outsideAccumulation (*UintTree): RewardRatioAccumulation outside the tick.
672type Tick struct {
673 id int32
674
675 // conceptually equal with Pool.liquidityGross but only for the staked positions
676 stakedLiquidityGross *u256.Uint
677
678 // conceptually equal with Pool.liquidityNet but only for the staked positions
679 stakedLiquidityDelta *i256.Int
680
681 // currentOutsideAccumulation is the accumulation of the time / TotalStake outside the tick.
682 // It is calculated by subtracting the current tick's currentOutsideAccumulation from the global reward ratio accumulation.
683 outsideAccumulation *UintTree // timestamp -> *u256.Uint
684}
685
686// Tick Getter/Setter methods
687
688// Id returns the tick ID
689func (t *Tick) Id() int32 {
690 return t.id
691}
692
693// SetId sets the tick ID
694func (t *Tick) SetId(id int32) {
695 t.id = id
696}
697
698// StakedLiquidityGross returns the staked liquidity gross
699func (t *Tick) StakedLiquidityGross() *u256.Uint {
700 return t.stakedLiquidityGross
701}
702
703// SetStakedLiquidityGross sets the staked liquidity gross
704func (t *Tick) SetStakedLiquidityGross(stakedLiquidityGross *u256.Uint) {
705 t.stakedLiquidityGross = u256.Zero().Set(stakedLiquidityGross)
706}
707
708// StakedLiquidityDelta returns the staked liquidity delta
709func (t *Tick) StakedLiquidityDelta() *i256.Int {
710 return t.stakedLiquidityDelta
711}
712
713// SetStakedLiquidityDelta sets the staked liquidity delta
714func (t *Tick) SetStakedLiquidityDelta(stakedLiquidityDelta *i256.Int) {
715 t.stakedLiquidityDelta = i256.Zero().Set(stakedLiquidityDelta)
716}
717
718// OutsideAccumulation returns the outside accumulation tree
719func (t *Tick) OutsideAccumulation() *UintTree {
720 return t.outsideAccumulation
721}
722
723// SetOutsideAccumulation sets the outside accumulation tree
724func (t *Tick) SetOutsideAccumulation(outsideAccumulation *UintTree) {
725 t.outsideAccumulation = outsideAccumulation
726}
727
728// SetOutsideAccumulationAt sets the outside accumulation at the timestamp.
729func (t *Tick) SetOutsideAccumulationAt(timestamp int64, acc *u256.Uint) {
730 t.outsideAccumulation.Set(timestamp, u256.Zero().Set(acc))
731}
732
733// Clone returns a deep copy of the tick.
734func (t *Tick) Clone() *Tick {
735 if t == nil {
736 return nil
737 }
738
739 return &Tick{
740 id: t.id,
741 stakedLiquidityGross: t.stakedLiquidityGross.Clone(),
742 stakedLiquidityDelta: t.stakedLiquidityDelta.Clone(),
743 outsideAccumulation: t.outsideAccumulation.Clone(),
744 }
745}
746
747func NewTick(tickId int32) *Tick {
748 return &Tick{
749 id: tickId,
750 stakedLiquidityGross: u256.Zero(),
751 stakedLiquidityDelta: i256.Zero(),
752 outsideAccumulation: NewUintTreeN(64),
753 }
754}
755
756// 100%, 0%, 0% if no tier2 and tier3
757// 80%, 0%, 20% if no tier2
758// 70%, 30%, 0% if no tier3
759// 50%, 30%, 20% if has tier2 and tier3
760type TierRatio struct {
761 Tier1 uint64
762 Tier2 uint64
763 Tier3 uint64
764}
765
766func NewTierRatio(tier1, tier2, tier3 uint64) TierRatio {
767 return TierRatio{
768 Tier1: tier1,
769 Tier2: tier2,
770 Tier3: tier3,
771 }
772}
773
774// Get returns the ratio(scaled up by 100) for the given tier.
775func (ratio *TierRatio) Get(tier uint64) (uint64, error) {
776 switch tier {
777 case 1:
778 return ratio.Tier1, nil
779 case 2:
780 return ratio.Tier2, nil
781 case 3:
782 return ratio.Tier3, nil
783 default:
784 return 0, errors.New(ufmt.Sprintf("unsupported tier(%d)", tier))
785 }
786}
787
788// SwapBatchProcessor processes tick crosses in batch for a swap
789// This processor accumulates all tick crosses that occur during a single swap
790// and processes them together at the end, reducing redundant calculations
791// and state updates that would occur with individual tick processing
792type SwapBatchProcessor struct {
793 poolPath string // The pool path identifier for this swap
794 pool *Pool // Reference to the pool being swapped in
795 crosses []*SwapTickCross // Accumulated tick crosses during the swap
796 timestamp int64 // Timestamp when the swap started
797 isActive bool // Flag to prevent accumulation after swap ends
798}
799
800func (s *SwapBatchProcessor) PoolPath() string {
801 return s.poolPath
802}
803
804func (s *SwapBatchProcessor) SetPoolPath(poolPath string) {
805 s.poolPath = poolPath
806}
807
808func (s *SwapBatchProcessor) Pool() *Pool {
809 return s.pool
810}
811
812func (s *SwapBatchProcessor) SetPool(pool *Pool) {
813 s.pool = pool
814}
815
816func (s *SwapBatchProcessor) Crosses() []*SwapTickCross {
817 return s.crosses
818}
819
820func (s *SwapBatchProcessor) SetCrosses(crosses []*SwapTickCross) {
821 s.crosses = crosses
822}
823
824func (s *SwapBatchProcessor) Timestamp() int64 {
825 return s.timestamp
826}
827
828func (s *SwapBatchProcessor) SetTimestamp(timestamp int64) {
829 s.timestamp = timestamp
830}
831
832func (s *SwapBatchProcessor) IsActive() bool {
833 return s.isActive
834}
835
836func (s *SwapBatchProcessor) SetIsActive(isActive bool) {
837 s.isActive = isActive
838}
839
840func (s *SwapBatchProcessor) LastCross() *SwapTickCross {
841 if len(s.crosses) == 0 {
842 return nil
843 }
844
845 return s.crosses[len(s.crosses)-1]
846}
847
848func (s *SwapBatchProcessor) AddCross(tickCross *SwapTickCross) {
849 s.crosses = append(s.crosses, tickCross)
850}
851
852func NewSwapBatchProcessor(poolPath string, pool *Pool, timestamp int64) *SwapBatchProcessor {
853 return &SwapBatchProcessor{
854 poolPath: poolPath,
855 pool: pool,
856 crosses: make([]*SwapTickCross, 0),
857 timestamp: timestamp,
858 isActive: true,
859 }
860}
861
862// SwapTickCross stores information about a tick cross during a swap
863// This struct is used to accumulate tick cross events during a single swap transaction
864// for batch processing to optimize gas usage and computational efficiency
865type SwapTickCross struct {
866 tickID int32 // The tick index that was crossed
867 zeroForOne bool // Direction of the swap (true: token0->token1, false: token1->token0)
868 delta *i256.Int // Pre-calculated liquidity delta for this tick cross
869}
870
871func (s *SwapTickCross) TickID() int32 {
872 return s.tickID
873}
874
875func (s *SwapTickCross) ZeroForOne() bool {
876 return s.zeroForOne
877}
878
879func (s *SwapTickCross) Delta() *i256.Int {
880 return s.delta
881}
882
883func NewSwapTickCross(tickID int32, zeroForOne bool, delta *i256.Int) *SwapTickCross {
884 return &SwapTickCross{
885 tickID: tickID,
886 zeroForOne: zeroForOne,
887 delta: delta,
888 }
889}