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