Search Apps Documentation Source Content File Folder Download Copy Actions Download

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}