PathrockNetwork Gno Explorer
HomeBlocksTransactionsTokensRealmsPackagesValidatorsAnalytics

PathrockNetwork Gno Explorer — an independent explorer for Gno.land Mainnet (gnoland-1), operated by PathrockNetwork. Not an official Gno.land service.

gnowebarchive RPC

gno.land/r/gnoswap/staker/v1

Realm
Open in gnoweb ↗

Overview

Kind
Realm (renderable)
Name
v1
Namespace
gnoswap / staker
Files
28 (README)(gnomod.toml)
Exported functions
14
Module
gno.land/r/gnoswap/staker/v1
gno
0.9

Files (28)

  • README.mdmarkdown
  • gnomod.tomltoml
  • assert.gnogno
  • calculate_pool_position_reward.gnogno
  • consts.gnogno
  • doc.gnogno
  • emits.gnogno
  • errors.gnogno
  • event_info.gnogno
  • external_deposit_fee.gnogno
  • external_incentive.gnogno
  • external_token_list.gnogno
  • getter_utils.gnogno
  • getter.gnogno
  • init.gnogno
  • instance.gnogno
  • manage_pool_tier_and_warmup.gnogno
  • protocol_fee_unstaking.gnogno
  • render.gnogno
  • reward_calculation_incentives.gnogno
  • reward_calculation_pool_tier.gnogno
  • reward_calculation_pool.gnogno
  • reward_calculation_tick.gnogno
  • reward_calculation_warmup.gnogno
  • staker.gnogno
  • type.gnogno
  • unstaked_position.gnogno
  • utils.gnogno
  • reward_calculation_tick.gnogno
    1package staker23import (4	"chain"5	"errors"67	"gno.land/p/gnoswap/gnsmath/v1"8

    Functions

    • NewDepositResolver(deposit *gno.land/r/gnoswap/staker.Deposit) *gno.land/r/gnoswap/staker/v1.DepositResolver

    • NewDeposits() *gno.land/r/gnoswap/staker/v1.Deposits

    • NewExternalIncentiveResolver(externalIncentive *gno.land/r/gnoswap/staker.ExternalIncentive) *gno.land/r/gnoswap/staker/v1.ExternalIncentiveResolver

    • NewExternalIncentives() *gno.land/r/gnoswap/staker/v1.ExternalIncentives

    • NewIncentivesResolver(incentives *gno.land/r/gnoswap/staker.Incentives) *gno.land/r/gnoswap/staker/v1.IncentivesResolver

    • NewPoolResolver(pool *gno.land/r/gnoswap/staker.Pool) *gno.land/r/gnoswap/staker/v1.PoolResolver

    • NewPools() *gno.land/r/gnoswap/staker/v1.Pools

    • NewPoolTier(pools *gno.land/r/gnoswap/staker/v1.Pools, currentTime int64, initialPoolPath string, getEmission func() (int64, .uverse.error), getHalvingBlocksInRange func(int64, int64) ([]int64, []int64, .uverse.error)) *gno.land/r/gnoswap/staker/v1.PoolTier

    • NewPoolTierBy(membership *gno.land/p/nt/bptree/v0.BPTree, tierRatio struct{Tier1 uint64; Tier2 uint64; Tier3 uint64}, counts [4]uint64, lastRewardCacheTimestamp int64, currentEmission int64, getEmission func() (int64, .uverse.error), getHalvingBlocksInRange func(int64, int64) ([]int64, []int64, .uverse.error)) *gno.land/r/gnoswap/staker/v1.PoolTier

    • NewStakerV1(stakerStore interface {AddAllowedToken func(int, .uverse.realm, string) .uverse.error; AddDeniedRewardToken func(int, .uverse.realm, string) .uverse.error; GetAllowedTokens func() []string; GetCurrentSwapBatch func() *gno.land/r/gnoswap/staker.SwapBatchProcessor; GetDeniedRewardTokens func() []string; GetDepositGnsAmount func() int64; GetDeposits func() *gno.land/p/nt/bptree/v0.BPTree; GetExternalIncentives func() *gno.land/p/nt/bptree/v0.BPTree; GetIncentiveCounter func() *gno.land/r/gnoswap/staker.Counter; GetMinimumRewardAmount func() int64; GetPendingProtocolFee func(string) int64; GetPendingProtocolFees func() map[string]int64; GetPoolTierCounts func() [4]uint64; GetPoolTierCurrentEmission func() int64; GetPoolTierGetEmission func() func() (int64, .uverse.error); GetPoolTierGetHalvingBlocksInRange func() func(int64, int64) ([]int64, []int64, .uverse.error); GetPoolTierLastRewardCacheTimestamp func() int64; GetPoolTierMemberships func() *gno.land/p/nt/bptree/v0.BPTree; GetPoolTierRatio func() gno.land/r/gnoswap/staker.TierRatio; GetPools func() *gno.land/p/nt/bptree/v0.BPTree; GetTokenSpecificMinimumRewards func() map[string]int64; GetTotalEmissionSent func() int64; GetUncollectedIncentiveCounts func() *gno.land/p/nt/bptree/v0.BPTree; GetUnstakedPositions func() *gno.land/p/nt/bptree/v0.BPTree; GetUnstakingFee func() uint64; GetWarmupTemplate func() []gno.land/r/gnoswap/staker.Warmup; HasAllowedTokensStoreKey func() bool; HasCurrentSwapBatchStoreKey func() bool; HasDeniedRewardTokensStoreKey func() bool; HasDepositGnsAmountStoreKey func() bool; HasDepositsStoreKey func() bool; HasExternalIncentivesStoreKey func() bool; HasIncentiveCounterStoreKey func() bool; HasMinimumRewardAmountStoreKey func() bool; HasPendingProtocolFeesStoreKey func() bool; HasPoolTierCountsStoreKey func() bool; HasPoolTierCurrentEmissionStoreKey func() bool; HasPoolTierGetEmissionStoreKey func() bool; HasPoolTierGetHalvingBlocksInRangeStoreKey func() bool; HasPoolTierLastRewardCacheTimestampStoreKey func() bool; HasPoolTierMembershipsStoreKey func() bool; HasPoolTierRatioStoreKey func() bool; HasPoolsStoreKey func() bool; HasTokenSpecificMinimumRewardsStoreKey func() bool; HasTotalEmissionSentStoreKey func() bool; HasUncollectedIncentiveCountsStoreKey func() bool; HasUnstakedPositionsStoreKey func() bool; HasUnstakingFeeStoreKey func() bool; HasWarmupTemplateStoreKey func() bool; NextIncentiveID func(.uverse.address, int64) string; RemoveAllowedToken func(int, .uverse.realm, string) .uverse.error; RemoveDeniedRewardToken func(int, .uverse.realm, string) .uverse.error; RemovePendingProtocolFee func(int, .uverse.realm, string) .uverse.error; RemoveTokenSpecificMinimumRewardItem func(int, .uverse.realm, string) .uverse.error; SetAllowedTokens func(int, .uverse.realm, []string) .uverse.error; SetCurrentSwapBatch func(int, .uverse.realm, *gno.land/r/gnoswap/staker.SwapBatchProcessor) .uverse.error; SetDepositGnsAmount func(int, .uverse.realm, int64) .uverse.error; SetDeposits func(int, .uverse.realm, *gno.land/p/nt/bptree/v0.BPTree) .uverse.error; SetExternalIncentives func(int, .uverse.realm, *gno.land/p/nt/bptree/v0.BPTree) .uverse.error; SetIncentiveCounter func(int, .uverse.realm, *gno.land/r/gnoswap/staker.Counter) .uverse.error; SetMinimumRewardAmount func(int, .uverse.realm, int64) .uverse.error; SetPendingProtocolFee func(int, .uverse.realm, string, int64) .uverse.error; SetPendingProtocolFees func(int, .uverse.realm, map[string]int64) .uverse.error; SetPoolTierCounts func(int, .uverse.realm, [4]uint64) .uverse.error; SetPoolTierCurrentEmission func(int, .uverse.realm, int64) .uverse.error; SetPoolTierGetEmission func(int, .uverse.realm, func() (int64, .uverse.error)) .uverse.error; SetPoolTierGetHalvingBlocksInRange func(int, .uverse.realm, func(int64, int64) ([]int64, []int64, .uverse.error)) .uverse.error; SetPoolTierLastRewardCacheTimestamp func(int, .uverse.realm, int64) .uverse.error; SetPoolTierMemberships func(int, .uverse.realm, *gno.land/p/nt/bptree/v0.BPTree) .uverse.error; SetPoolTierRatio func(int, .uverse.realm, gno.land/r/gnoswap/staker.TierRatio) .uverse.error; SetPools func(int, .uverse.realm, *gno.land/p/nt/bptree/v0.BPTree) .uverse.error; SetTokenSpecificMinimumRewardItem func(int, .uverse.realm, string, int64) .uverse.error; SetTokenSpecificMinimumRewards func(int, .uverse.realm, map[string]int64) .uverse.error; SetTotalEmissionSent func(int, .uverse.realm, int64) .uverse.error; SetUncollectedIncentiveCounts func(int, .uverse.realm, *gno.land/p/nt/bptree/v0.BPTree) .uverse.error; SetUnstakedPositions func(int, .uverse.realm, *gno.land/p/nt/bptree/v0.BPTree) .uverse.error; SetUnstakingFee func(int, .uverse.realm, uint64) .uverse.error; SetWarmupTemplate func(int, .uverse.realm, []gno.land/r/gnoswap/staker.Warmup) .uverse.error}, poolAccessor interface {ExistsPoolPath func(string) bool; GetSlot0SqrtPriceX96 func(string) string; GetSlot0Tick func(string) int32; SetSwapEndHook func(int, .uverse.realm, func(int, .uverse.realm, string) .uverse.error); SetSwapStartHook func(int, .uverse.realm, func(int, .uverse.realm, string, int64)); SetTickCrossHook func(int, .uverse.realm, func(int, .uverse.realm, string, int32, bool, int64))}, emissionAccessor interface {GetStakerEmissionAmountPerSecond func() (int64, .uverse.error); GetStakerEmissionAmountPerSecondInRange func(int64, int64) ([]int64, []int64, .uverse.error); MintAndDistributeGns func(int, .uverse.realm) (int64, bool); SetOnDistributionPctChangeCallback func(int, .uverse.realm, func(int, .uverse.realm, int64))}, nftAccessor interface {Approve func(int, .uverse.realm, .uverse.address, gno.land/p/nt/grc721/v0.TokenID) .uverse.error; Burn func(int, .uverse.realm, gno.land/p/nt/grc721/v0.TokenID); Exists func(gno.land/p/nt/grc721/v0.TokenID) bool; Mint func(int, .uverse.realm, .uverse.address, gno.land/p/nt/grc721/v0.TokenID) gno.land/p/nt/grc721/v0.TokenID; MustOwnerOf func(gno.land/p/nt/grc721/v0.TokenID) .uverse.address; OwnerOf func(gno.land/p/nt/grc721/v0.TokenID) (.uverse.address, .uverse.error); TotalSupply func() int64; TransferFrom func(int, .uverse.realm, .uverse.address, .uverse.address, gno.land/p/nt/grc721/v0.TokenID) .uverse.error})

    • NewTickCrossEventInfo(tickID int32, stakedLiquidityGross *gno.land/p/gnoswap/uint256/v1.Uint, stakedLiquidityDelta *gno.land/p/gnoswap/int256/v1.Int, outsideAccumulation *gno.land/p/gnoswap/uint256/v1.Uint) *gno.land/r/gnoswap/staker/v1.tickCrossEventInfo

    • NewTickResolver(tick *gno.land/r/gnoswap/staker.Tick) *gno.land/r/gnoswap/staker/v1.TickResolver

    • NewUnstakedPositions() *gno.land/r/gnoswap/staker/v1.UnstakedPositions

    • TierRatioFromCounts(tier1Count uint64, tier2Count uint64, tier3Count uint64) struct{Tier1 uint64; Tier2 uint64; Tier3 uint64}

    Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.

    Rendered

    RenderedRawgnoweb ↗
    This realm does not provide renderable output (vm/qrender → NoRenderDeclError). View on gnoweb ↗

    vm/qrender output, sanitized (docs/render-security.md) and displayed in an empty-sandbox iframe — scripts, forms and popups cannot run. Links stay inert in-preview; right-click to open.

    i256 "gno.land/p/gnoswap/int256/v1"
    9 u256 "gno.land/p/gnoswap/uint256/v1"
    10 "gno.land/p/gnoswap/utils/v1"
    11 sr "gno.land/r/gnoswap/staker"
    12)
    13
    14type TickResolver struct {
    15 *sr.Tick
    16}
    17
    18// CurrentOutsideAccumulation returns the latest outside accumulation for the tick
    19// Parameters:
    20// - timestamp: Timestamp through which the tick's outside-accumulation history is queried.
    21//
    22// Returns:
    23// - *u256.Uint: Most recent decoded outside accumulation at or before timestamp, or zero when none exists.
    24func (self *TickResolver) CurrentOutsideAccumulation(timestamp int64) *u256.Uint {
    25 var acc *u256.Uint
    26 self.OutsideAccumulation().ReverseIterate(0, timestamp, func(key int64, value any) bool {
    27 v, ok := value.(string)
    28 if !ok {
    29 panic("failed to cast value to string")
    30 }
    31 acc = utils.DecodeUint256(v)
    32 return true
    33 })
    34 if acc == nil {
    35 return u256.Zero()
    36 }
    37 return acc
    38}
    39
    40// modifyDepositLower updates the tick's liquidity info by treating the deposit as a lower tick
    41func (self *TickResolver) modifyDepositLower(currentTime int64, liquidity *i256.Int) {
    42 // update staker side tick info
    43 self.SetStakedLiquidityGross(gnsmath.LiquidityMathAddDelta(self.StakedLiquidityGross(), liquidity))
    44 if self.StakedLiquidityGross().Lt(u256.Zero()) {
    45 panic("stakedLiquidityGross is negative")
    46 }
    47 self.SetStakedLiquidityDelta(i256.Zero().Add(self.StakedLiquidityDelta(), liquidity))
    48}
    49
    50// modifyDepositUpper updates the tick's liquidity info by treating the deposit as an upper tick
    51func (self *TickResolver) modifyDepositUpper(currentTime int64, liquidity *i256.Int) {
    52 self.SetStakedLiquidityGross(gnsmath.LiquidityMathAddDelta(self.StakedLiquidityGross(), liquidity))
    53 if self.StakedLiquidityGross().Lt(u256.Zero()) {
    54 panic("stakedLiquidityGross is negative")
    55 }
    56 self.SetStakedLiquidityDelta(i256.Zero().Sub(self.StakedLiquidityDelta(), liquidity))
    57}
    58
    59// updateCurrentOutsideAccumulation updates the tick's outside accumulation
    60// It "flips" the accumulation's inside/outside by subtracting the current outside accumulation from the global accumulation.
    61// An unchanged result does not require a checkpoint because predecessor queries
    62// return the same value for this and all subsequent timestamps.
    63func (self *TickResolver) updateCurrentOutsideAccumulation(timestamp int64, acc *u256.Uint) *u256.Uint {
    64 currentOutsideAccumulation := self.CurrentOutsideAccumulation(timestamp)
    65 newOutsideAccumulation := u256.Zero().Sub(acc, currentOutsideAccumulation)
    66 if newOutsideAccumulation.Eq(currentOutsideAccumulation) {
    67 return currentOutsideAccumulation
    68 }
    69
    70 self.SetOutsideAccumulationAt(timestamp, newOutsideAccumulation)
    71 return newOutsideAccumulation
    72}
    73
    74// NewTickResolver wraps a staker tick to expose reward-accumulation updates.
    75//
    76// Parameters:
    77// - tick: Staker tick whose state is resolved.
    78//
    79// Returns:
    80// - *TickResolver: Resolver backed by tick.
    81func NewTickResolver(tick *sr.Tick) *TickResolver {
    82 return &TickResolver{
    83 Tick: tick,
    84 }
    85}
    86
    87// swapStartHook is called when a swap starts
    88// This hook initializes the batch processor for accumulating tick crosses
    89func (s *stakerV1) swapStartHook(_ int, rlm realm, poolPath string, timestamp int64) {
    90 pool, ok := s.getPools().Get(poolPath)
    91 if !ok {
    92 return
    93 }
    94 if pool.Ticks().Tree().Size() == 0 {
    95 return
    96 }
    97
    98 // Initialize batch processor for this swap
    99 // This will accumulate all tick crosses until swap completion
    100 currentSwapBatch := sr.NewSwapBatchProcessor(poolPath, pool, timestamp)
    101 err := s.store.SetCurrentSwapBatch(0, rlm, currentSwapBatch)
    102 if err != nil {
    103 panic(err)
    104 }
    105}
    106
    107// swapEndHook is called when a swap ends
    108// This hook processes all accumulated tick crosses in a single batch operation
    109// and cleans up the batch processor. The batch processing approach provides:
    110// 1. O(1) pool state updates instead of O(n) where n = number of tick crosses
    111// 2. Reduced computational overhead for reward calculations
    112// 3. Atomic processing ensuring consistency across all tick updates
    113func (s *stakerV1) swapEndHook(_ int, rlm realm, poolPath string) error {
    114 // Validate batch processor state
    115 currentSwapBatch := s.store.GetCurrentSwapBatch()
    116
    117 if currentSwapBatch == nil || !currentSwapBatch.IsActive() || currentSwapBatch.PoolPath() != poolPath {
    118 return nil
    119 }
    120
    121 // Disable further accumulation
    122 currentSwapBatch.SetIsActive(false)
    123
    124 // Process all accumulated tick crosses in a single batch
    125 // This is where the optimization happens - instead of processing
    126 // each tick cross individually, we calculate cumulative effects
    127 err := s.processBatchedTickCrosses(0, rlm)
    128 if err != nil {
    129 return err
    130 }
    131
    132 // Clean up batch processor
    133 err = s.store.SetCurrentSwapBatch(0, rlm, nil)
    134 if err != nil {
    135 return err
    136 }
    137
    138 return nil
    139}
    140
    141// tickCrossHook is called when a tick is crossed.
    142// Active swaps accumulate crosses and process their net liquidity change once at
    143// swap end. Calls without an active batch use the immediate fallback, which
    144// materializes the reward cache for each individual tick cross.
    145func (s *stakerV1) tickCrossHook(_ int, rlm realm, poolPath string, tickId int32, zeroForOne bool, timestamp int64) {
    146 pool, ok := s.getPools().Get(poolPath)
    147 if !ok {
    148 return
    149 }
    150
    151 // Skip ticks without staking state.
    152 tick := pool.Ticks().Get(tickId)
    153 if tick == nil {
    154 return
    155 }
    156
    157 // Skip ticks without staked boundary liquidity (no reward impact)
    158 if tick.StakedLiquidityGross().IsZero() {
    159 return
    160 }
    161
    162 currentSwapBatch := s.store.GetCurrentSwapBatch()
    163 // Batch processing path: accumulate tick crosses during active swap
    164 if currentSwapBatch != nil && currentSwapBatch.IsActive() && currentSwapBatch.PoolPath() == poolPath {
    165 // Pre-calculate liquidity delta with direction consideration
    166 // zeroForOne swap: liquidity delta is negated (liquidity being removed from current tick)
    167 liquidityDelta := tick.StakedLiquidityDelta()
    168 if zeroForOne {
    169 liquidityDelta = i256.Zero().Neg(liquidityDelta)
    170 }
    171
    172 // Accumulate this tick cross for batch processing
    173 currentSwapBatch.AddCross(sr.NewSwapTickCross(tickId, zeroForOne, liquidityDelta))
    174 return
    175 }
    176
    177 // Immediate fallback for tick-cross callers without an active swap batch.
    178 // Current production tick crosses originate from Swap and use batching; this
    179 // path preserves correct accounting for future non-swap callers.
    180 s.processTickCrossImmediate(pool, tick, tickId, zeroForOne, timestamp)
    181}
    182
    183// processTickCrossImmediate processes one unbatched tick cross.
    184// It materializes elapsed halving boundaries while the pre-cross liquidity is
    185// still effective, before modifying the deposit for that individual cross.
    186func (s *stakerV1) processTickCrossImmediate(pool *sr.Pool, tick *sr.Tick, tickId int32, zeroForOne bool, timestamp int64) {
    187 // Calculate the effective tick position after crossing
    188 // For zeroForOne swaps, liquidity becomes effective one tick lower
    189 nextTick := tickId
    190 if zeroForOne {
    191 nextTick-- // Move to the lower tick where liquidity becomes active
    192 }
    193
    194 // Calculate liquidity delta with direction consideration
    195 liquidityDelta := tick.StakedLiquidityDelta()
    196 if zeroForOne {
    197 // Negate delta for zeroForOne direction (liquidity being removed from current range)
    198 liquidityDelta = i256.Zero().Neg(liquidityDelta)
    199 }
    200
    201 // Update pool's cumulative deposit with the liquidity change
    202 poolResolver := NewPoolResolver(pool)
    203 s.getPoolTier().cacheRewardForPool(timestamp, s.getPools(), pool.PoolPath())
    204 newAcc := poolResolver.modifyDeposit(liquidityDelta, timestamp, nextTick)
    205
    206 // Update the tick's outside accumulation for reward calculations
    207 // This ensures proper reward distribution tracking across tick boundaries
    208 tickResolver := NewTickResolver(tick)
    209 tickResolver.updateCurrentOutsideAccumulation(timestamp, newAcc)
    210}
    211
    212// processBatchedTickCrosses processes all accumulated tick crosses at once
    213// This is the core optimization function that processes multiple tick crosses in a single operation.
    214// Instead of updating pool state for each tick cross individually (O(n) operations),
    215// it calculates the cumulative effect and applies it once (O(1) pool updates + O(n) tick updates).
    216func (s *stakerV1) processBatchedTickCrosses(_ int, rlm realm) error {
    217 // Early exit for empty batches
    218 currentSwapBatch := s.store.GetCurrentSwapBatch()
    219 if currentSwapBatch == nil || len(currentSwapBatch.Crosses()) == 0 {
    220 return nil
    221 }
    222
    223 // Validate pool reference
    224 if currentSwapBatch.Pool() == nil {
    225 return errors.New(errPoolNotFound)
    226 }
    227
    228 batch := currentSwapBatch
    229 timestamp := batch.Timestamp()
    230
    231 // Phase 1: Calculate cumulative liquidity delta across all tick crosses
    232 // This replaces multiple individual pool updates with a single cumulative update
    233 cumulativeDelta := i256.Zero()
    234 for _, tickCross := range batch.Crosses() {
    235 newDelta := cumulativeDelta.Add(cumulativeDelta, tickCross.Delta())
    236 cumulativeDelta = newDelta
    237 }
    238
    239 // Phase 2: Determine the effective tick position for pool state update
    240 // Use the last crossed tick as the reference point for cumulative changes
    241 lastCross := batch.LastCross()
    242 if lastCross == nil {
    243 return nil
    244 }
    245
    246 lastTick := lastCross.TickID()
    247 if lastCross.ZeroForOne() {
    248 lastTick-- // Adjust for zeroForOne direction
    249 }
    250
    251 // Phase 3: Apply the batch's cumulative change to pool state once.
    252 // cacheRewardForPool runs once for each non-empty normal swap batch, before
    253 // the net liquidity change. It sees the pre-cross liquidity and splits
    254 // unclaimable intervals at every elapsed halving boundary.
    255 poolResolver := NewPoolResolver(batch.Pool())
    256 s.getPoolTier().cacheRewardForPool(timestamp, s.getPools(), batch.PoolPath())
    257 newAcc := poolResolver.modifyDeposit(cumulativeDelta, timestamp, lastTick)
    258
    259 // Phase 4: Update individual tick outside accumulations for reward tracking
    260 // While we optimize pool updates, each tick still needs its accumulation updated
    261 // for proper reward distribution calculations
    262
    263 for _, tickCross := range batch.Crosses() {
    264 tick := batch.Pool().Ticks().Get(tickCross.TickID())
    265 if tick == nil {
    266 // Pruned after the cross was accumulated, so its staked gross
    267 // liquidity is zero and it carries no reward weight.
    268 continue
    269 }
    270
    271 tickResolver := NewTickResolver(tick)
    272 outsideAccumulation := tickResolver.updateCurrentOutsideAccumulation(timestamp, newAcc)
    273
    274 tickCrossEventInfo := NewTickCrossEventInfo(
    275 tickCross.TickID(),
    276 tick.StakedLiquidityGross(),
    277 tick.StakedLiquidityDelta(),
    278 outsideAccumulation,
    279 )
    280
    281 chain.Emit(
    282 "StakerTickCross",
    283 "poolPath", batch.PoolPath(),
    284 "tick", tickCrossEventInfo.ToString(),
    285 )
    286 }
    287
    288 previousRealm := rlm.Previous()
    289 stakedLiquidity := poolResolver.CurrentStakedLiquidity(timestamp)
    290
    291 // Emit event with staker-side tick cross information.
    292 // lastTick — the effective tick written to HistoricalTick by modifyDeposit above.
    293 // Reward calculation (CalculateRawRewardForPosition) reads HistoricalTick for the feeGrowthInside branch,
    294 // so off-chain indexers must use this same value (NOT the pool's Slot0 tick) to reproduce in-range status.
    295 chain.Emit(
    296 "BatchStakerTickCross",
    297 "prevAddr", previousRealm.Address().String(),
    298 "prevRealm", previousRealm.PkgPath(),
    299 "poolPath", batch.PoolPath(),
    300 "blockTimestamp", utils.FormatInt(timestamp),
    301 "stakedLiquidity", stakedLiquidity.ToString(),
    302 "globalRewardRatioAccX128", newAcc.ToString(),
    303 "lastTick", utils.FormatInt(lastTick),
    304 )
    305
    306 return nil
    307}
    308
    309func (s *stakerV1) setupSwapHooks(_ int, rlm realm) {
    310 // Set tick cross hook for pool contract
    311 s.poolAccessor.SetTickCrossHook(0, rlm, s.tickCrossHook)
    312
    313 // Set swap start/end hooks for batch processing
    314 s.poolAccessor.SetSwapStartHook(0, rlm, s.swapStartHook)
    315
    316 // Set swap end hook for batch processing
    317 s.poolAccessor.SetSwapEndHook(0, rlm, s.swapEndHook)
    318}
    319
    *gno.land/r/gnoswap/staker/v1.stakerV1