Search Apps Documentation Source Content File Folder Download Copy Actions Download

pool.gno

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