Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

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}