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_pool_tier.gnogno
    1package staker23import (4	"errors"56	"gno.land/p/gnoswap/gnsmath/v1"7	bptree "gno.land/p/nt/bptree/v0"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.

    9 sr "gno.land/r/gnoswap/staker"
    10)
    11
    12const (
    13 AllTierCount = 4 // 0, 1, 2, 3
    14 Tier1 = 1
    15 Tier2 = 2
    16 Tier3 = 3
    17)
    18
    19// TierRatioFromCounts calculates the ratio distribution for each tier based on pool counts.
    20//
    21// Parameters:
    22// - tier1Count (uint64): Number of pools in tier 1.
    23// - tier2Count (uint64): Number of pools in tier 2.
    24// - tier3Count (uint64): Number of pools in tier 3.
    25//
    26// Returns:
    27// - TierRatio: The ratio distribution across tier 1, 2, and 3, scaled up by 100.
    28func TierRatioFromCounts(tier1Count, tier2Count, tier3Count uint64) sr.TierRatio {
    29 // tier1 always exists.
    30 //
    31 // TierRatio is declared in /r/gnoswap/staker; constructing it via a
    32 // composite literal here (/r/gnoswap/staker/v1) trips the construction-time
    33 // check ("cannot allocate ... in realm ..."). Route through the domain
    34 // constructor sr.NewTierRatio so allocation happens in the declaring realm.
    35 if tier2Count == 0 && tier3Count == 0 {
    36 return sr.NewTierRatio(100, 0, 0)
    37 }
    38 if tier2Count == 0 {
    39 return sr.NewTierRatio(80, 0, 20)
    40 }
    41 if tier3Count == 0 {
    42 return sr.NewTierRatio(70, 30, 0)
    43 }
    44 return sr.NewTierRatio(50, 30, 20)
    45}
    46
    47// PoolTier manages pool counts, ratios, and rewards for different tiers.
    48//
    49// Fields:
    50// - membership: Tracks which tier a pool belongs to (poolPath -> blockNumber -> tier).
    51//
    52// Methods:
    53// - CurrentCount: Returns the current count of pools in a tier at a specific timestamp.
    54// - CurrentRatio: Returns the current ratio for a tier at a specific timestamp.
    55// - CurrentTier: Returns the tier of a specific pool at a given timestamp.
    56// - CurrentReward: Retrieves the reward for a tier at a specific timestamp.
    57// - changeTier: Updates the tier of a pool and recalculates ratios.
    58type PoolTier struct {
    59 membership *bptree.BPTree // poolPath -> tier(1, 2, 3)
    60
    61 tierRatio sr.TierRatio
    62
    63 counts [AllTierCount]uint64
    64
    65 lastRewardCacheTimestamp int64
    66
    67 currentEmission int64
    68
    69 // returns current emission.
    70 getEmission func() (int64, error)
    71 // Returns a list of halving timestamps and their emission amounts within the interval [start, end) in ascending order.
    72 // The first return value is a list of timestamps where halving occurs.
    73 // The second return value is a list of emission amounts corresponding to each halving timestamp.
    74 getHalvingBlocksInRange func(start, end int64) ([]int64, []int64, error)
    75}
    76
    77// NewPoolTier creates a new PoolTier instance with single initial 1 tier pool.
    78//
    79// Parameters:
    80// - pools: The pool collection.
    81// - currentTime: The current block time.
    82// - initialPoolPath: The path of the initial pool.
    83// - getEmission: A function that returns the current emission to the staker contract.
    84// - getHalvingBlocksInRange: A function that returns a list of halving blocks within the interval [start, end) in ascending order.
    85//
    86// Returns:
    87// - *PoolTier: The new PoolTier instance.
    88func NewPoolTier(pools *Pools, currentTime int64, initialPoolPath string, getEmission func() (int64, error), getHalvingBlocksInRange func(start, end int64) ([]int64, []int64, error)) *PoolTier {
    89 currentEmission, err := getEmission()
    90 if err != nil {
    91 panic(err)
    92 }
    93
    94 result := &PoolTier{
    95 membership: sr.NewBPTreeN(16),
    96 tierRatio: TierRatioFromCounts(1, 0, 0),
    97 lastRewardCacheTimestamp: gnsmath.SafeAddInt64(currentTime, 1),
    98 getEmission: getEmission,
    99 getHalvingBlocksInRange: getHalvingBlocksInRange,
    100 currentEmission: currentEmission,
    101 }
    102
    103 pools.set(initialPoolPath, sr.NewPool(initialPoolPath, currentTime+1))
    104 result.changeTier(currentTime+1, pools, initialPoolPath, 1)
    105 return result
    106}
    107
    108// NewPoolTierBy reconstructs a PoolTier from persisted membership, ratios, counts, and callbacks.
    109//
    110// Parameters:
    111// - membership: Persisted pool-path-to-tier mapping.
    112// - tierRatio: Persisted reward-share ratio for each tier.
    113// - counts: Persisted number of pools in each tier index.
    114// - lastRewardCacheTimestamp: Timestamp through which reward caches have been materialized.
    115// - currentEmission: Emission rate currently used for reward accrual.
    116// - getEmission: Callback returning the current staker emission rate or an error.
    117// - getHalvingBlocksInRange: Callback returning halving timestamps and corresponding emissions in [start, end).
    118//
    119// Returns:
    120// - *PoolTier: Reconstructed tier manager using the supplied persisted state and callbacks.
    121func NewPoolTierBy(
    122 membership *bptree.BPTree,
    123 tierRatio sr.TierRatio,
    124 counts [AllTierCount]uint64,
    125 lastRewardCacheTimestamp int64,
    126 currentEmission int64,
    127 getEmission func() (int64, error),
    128 getHalvingBlocksInRange func(start, end int64) ([]int64, []int64, error),
    129) *PoolTier {
    130 return &PoolTier{
    131 membership: membership,
    132 tierRatio: tierRatio,
    133 counts: counts,
    134 lastRewardCacheTimestamp: lastRewardCacheTimestamp,
    135 getEmission: getEmission,
    136 getHalvingBlocksInRange: getHalvingBlocksInRange,
    137 currentEmission: currentEmission,
    138 }
    139}
    140
    141// CurrentReward returns the current per-pool reward for the given tier.
    142// Parameters:
    143// - tier: Tier number whose current emission reward is requested.
    144//
    145// Returns:
    146// - int64: Current per-pool reward for the tier, or zero when the calculation cannot produce a reward.
    147// - error: Error from emission lookup, invalid-tier lookup, or reward calculation; nil when the reward calculation succeeds.
    148func (self *PoolTier) CurrentReward(tier uint64) (int64, error) {
    149 currentEmission, err := self.getEmission()
    150 if err != nil {
    151 return 0, err
    152 }
    153 tierRatio, err := self.tierRatio.Get(tier)
    154 if err != nil {
    155 return 0, makeErrorWithDetails(errInvalidPoolTier, err.Error())
    156 }
    157
    158 tierRatioInt64 := int64(tierRatio)
    159 count := int64(self.CurrentCount(tier))
    160
    161 return calculatePoolReward(currentEmission, tierRatioInt64, count)
    162}
    163
    164// CurrentCount returns the current count of pools in the given tier.
    165// Parameters:
    166// - tier: Tier index whose pool count is requested; out-of-range indexes return zero.
    167//
    168// Returns:
    169// - int: Number of pools currently assigned to tier.
    170func (self *PoolTier) CurrentCount(tier uint64) int {
    171 if tier >= AllTierCount {
    172 return 0
    173 }
    174 return int(self.counts[tier])
    175}
    176
    177// CurrentAllTierCounts returns the current count of pools in each tier.
    178// Returns:
    179// - []uint64: Snapshot of pool counts for tier indexes 0 through AllTierCount-1.
    180func (self *PoolTier) CurrentAllTierCounts() []uint64 {
    181 out := make([]uint64, AllTierCount)
    182 copy(out, self.counts[:])
    183 return out // returning snapshot
    184}
    185
    186// CurrentTier returns the tier of the given pool.
    187// Parameters:
    188// - poolPath: Pool path whose current tier membership is requested.
    189//
    190// Returns:
    191// - tier: Current tier number, or zero when the pool is not in the membership tree.
    192func (self *PoolTier) CurrentTier(poolPath string) (tier uint64) {
    193 if tierI := self.membership.Get(poolPath); tierI == nil {
    194 return 0
    195 } else {
    196 var ok bool
    197 tier, ok = tierI.(uint64)
    198 if !ok {
    199 panic("failed to cast tier to uint64")
    200 }
    201 return tier
    202 }
    203}
    204
    205// changeTier updates the tier of a pool, recalculates ratios, and applies
    206// updated per-pool reward to each of the pools.
    207func (self *PoolTier) changeTier(currentTime int64, pools *Pools, poolPath string, nextTier uint64) map[uint64]int64 {
    208 currentTier := self.CurrentTier(poolPath)
    209 if currentTier == nextTier {
    210 // no change, return
    211 return make(map[uint64]int64)
    212 }
    213 assertTier1HasSparePool(currentTier, self.counts[Tier1])
    214
    215 self.cacheReward(currentTime, pools)
    216
    217 // decrement count from current tier if it exists
    218 if currentTier > 0 {
    219 if self.counts[currentTier] == 0 {
    220 panic("counts underflow: removing from empty tier")
    221 }
    222 self.counts[currentTier]--
    223 }
    224
    225 if nextTier == 0 {
    226 // removed from the tier
    227 self.membership.Remove(poolPath)
    228 pool, ok := pools.Get(poolPath)
    229 if !ok {
    230 panic("changeTier: pool not found")
    231 }
    232 poolResolver := NewPoolResolver(pool)
    233 // prevent new rewards from accumulating after tier removal
    234 poolResolver.cacheReward(currentTime, 0)
    235 } else {
    236 // handle all move/add operations
    237 self.membership.Set(poolPath, nextTier)
    238 self.counts[nextTier]++
    239 }
    240
    241 self.tierRatio = TierRatioFromCounts(self.counts[Tier1], self.counts[Tier2], self.counts[Tier3])
    242 currentEmission, err := self.getEmission()
    243 if err != nil {
    244 panic(err)
    245 }
    246 tierRewards := self.computeTierRewards(currentEmission)
    247
    248 // Cache updated reward for each tiered pool
    249 self.membership.Iterate("", "", func(key string, value any) bool {
    250 pool, ok := pools.Get(key)
    251 if !ok {
    252 panic("changeTier: pool not found")
    253 }
    254 tier, ok := value.(uint64)
    255 if !ok {
    256 panic("failed to cast value to uint64")
    257 }
    258
    259 poolReward, ok := tierRewards[tier]
    260 if !ok {
    261 return false // Skip if no pools in tier
    262 }
    263
    264 poolResolver := NewPoolResolver(pool)
    265 poolResolver.cacheReward(currentTime, poolReward)
    266 return false
    267 })
    268
    269 self.currentEmission = currentEmission
    270
    271 return tierRewards
    272}
    273
    274// cacheReward MUST be called before calculating any position reward.
    275// cacheReward updates the reward cache for each pool, accounting for any halving events
    276// that occurred between the last cached timestamp and the current timestamp.
    277// Note: Block height is used only for event tracking purposes.
    278// tierContextOf returns the pool's tier and the ratio/count setting its current emission rate.
    279func (self *PoolTier) tierContextOf(poolPath string) (uint64, uint64, uint64, error) {
    280 tier := self.CurrentTier(poolPath)
    281 if tier == 0 || tier >= AllTierCount {
    282 return tier, 0, 0, nil
    283 }
    284
    285 ratio, err := self.tierRatio.Get(tier)
    286 if err != nil {
    287 return 0, 0, 0, makeErrorWithDetails(errInvalidPoolTier, err.Error())
    288 }
    289
    290 return tier, ratio, self.counts[tier], nil
    291}
    292
    293func (self *PoolTier) cacheReward(currentTimestamp int64, pools *Pools) {
    294 lastTimestamp := self.lastRewardCacheTimestamp
    295
    296 if currentTimestamp <= lastTimestamp {
    297 // no need to check
    298 return
    299 }
    300
    301 // find halving blocks in range
    302 halvingTimestamps, halvingEmissions, err := self.getHalvingBlocksInRange(lastTimestamp, currentTimestamp)
    303 if err != nil {
    304 panic(err)
    305 }
    306
    307 if len(halvingTimestamps) == 0 {
    308 self.applyCacheToAllPools(pools, currentTimestamp, self.currentEmission)
    309 self.lastRewardCacheTimestamp = currentTimestamp
    310 return
    311 }
    312
    313 for i, hvTimestamp := range halvingTimestamps {
    314 emission := halvingEmissions[i]
    315 // caching: [lastTimestamp, hvTimestamp)
    316 self.applyCacheToAllPools(pools, hvTimestamp, emission)
    317
    318 // halve emissions when halvingBlock is reached
    319 self.currentEmission = emission
    320 }
    321
    322 // remaining range [lastTimestamp, currentTimestamp)
    323 self.applyCacheToAllPools(pools, currentTimestamp, self.currentEmission)
    324
    325 self.lastRewardCacheTimestamp = currentTimestamp
    326}
    327
    328// cacheRewardForPool caches internal reward/accumulators for a single pool only.
    329// This avoids iterating all tiered pools on every position reward calculation.
    330func (self *PoolTier) cacheRewardForPool(currentTimestamp int64, pools *Pools, poolPath string) {
    331 pool, ok := pools.Get(poolPath)
    332 if !ok {
    333 return
    334 }
    335
    336 tierNum := self.CurrentTier(poolPath)
    337 // Pool not in the internal-incentive system.
    338 if tierNum == 0 {
    339 return
    340 }
    341
    342 // Find the latest reward cache timestamp for this pool.
    343 lastTimestamp := int64(0)
    344 hasLast := false
    345 pool.RewardCache().ReverseIterate(0, currentTimestamp, func(key int64, _ any) bool {
    346 lastTimestamp = key
    347 hasLast = true
    348 return true
    349 })
    350
    351 if !hasLast {
    352 // Fallback to global tier cache cursor.
    353 lastTimestamp = self.lastRewardCacheTimestamp
    354 }
    355
    356 if currentTimestamp <= lastTimestamp {
    357 return
    358 }
    359
    360 // Determine halving boundaries since the pool's last cached reward timestamp.
    361 halvingTimestamps, halvingEmissions, err := self.getHalvingBlocksInRange(lastTimestamp, currentTimestamp)
    362 if err != nil {
    363 panic(err)
    364 }
    365 poolResolver := NewPoolResolver(pool)
    366
    367 if len(halvingTimestamps) == 0 {
    368 // No emission change within the range => use the live emission.
    369 currentEmission, err := self.getEmission()
    370 if err != nil {
    371 panic(err)
    372 }
    373 self.applyCacheToPool(poolResolver, tierNum, currentTimestamp, currentEmission)
    374 return
    375 }
    376
    377 // Apply caching at every halving boundary.
    378 currentEmission := int64(0)
    379 for i, hvTimestamp := range halvingTimestamps {
    380 currentEmission = halvingEmissions[i]
    381 self.applyCacheToPool(poolResolver, tierNum, hvTimestamp, currentEmission)
    382 }
    383
    384 // Remaining range [lastTimestamp, currentTimestamp).
    385 self.applyCacheToPool(poolResolver, tierNum, currentTimestamp, currentEmission)
    386}
    387
    388// applyCacheToPool applies the cached reward to all tiered pool.
    389func (self *PoolTier) applyCacheToPool(poolResolver *PoolResolver, tierNum uint64, currentTimestamp, emissionInThisInterval int64) {
    390 tierRewards := self.computeTierRewards(emissionInThisInterval)
    391 poolReward, ok := tierRewards[tierNum]
    392 if !ok {
    393 return
    394 }
    395
    396 poolResolver.cacheReward(currentTimestamp, poolReward)
    397}
    398
    399// applyCacheToAllPools applies the cached reward to all tiered pools.
    400func (self *PoolTier) applyCacheToAllPools(pools *Pools, currentTimestamp, emissionInThisInterval int64) {
    401 // calculate denominator and number of pools in each tier
    402 counts := self.CurrentAllTierCounts()
    403 tierRewards := self.computeTierRewards(emissionInThisInterval)
    404
    405 // apply cache to all pools
    406 self.membership.Iterate("", "", func(key string, value any) bool {
    407 pool, ok := pools.Get(key)
    408 if !ok {
    409 return false
    410 }
    411
    412 tierNum, ok := value.(uint64)
    413 if !ok {
    414 panic("failed to cast value to uint64")
    415 }
    416 // Skip pools with tier 0 (removed from tier system)
    417 if tierNum == 0 {
    418 return false
    419 }
    420
    421 if counts[tierNum] == 0 {
    422 return false // Skip if no pools in tier
    423 }
    424
    425 poolReward, ok := tierRewards[tierNum]
    426 if !ok {
    427 return false
    428 }
    429
    430 // accumulate the reward for the interval (startBlock to endBlock) in the Pool
    431 poolResolver := NewPoolResolver(pool)
    432 poolResolver.cacheReward(currentTimestamp, poolReward)
    433 return false
    434 })
    435}
    436
    437// IsInternallyIncentivizedPool returns true if the pool is in a tier.
    438// Parameters:
    439// - poolPath: Pool path whose membership is checked.
    440//
    441// Returns:
    442// - bool: True when poolPath belongs to a nonzero internal-incentive tier; false otherwise.
    443func (self *PoolTier) IsInternallyIncentivizedPool(poolPath string) bool {
    444 return self.CurrentTier(poolPath) > 0
    445}
    446
    447// calculatePoolReward calculates the reward for a pool based on the emission, tier ratio, and tier count.
    448//
    449// Parameters:
    450// - emission: The emission for the pool.
    451// - tierRatio: The tier ratio for the pool.
    452// - tierCount: The tier count for the pool.
    453//
    454// Returns:
    455// - int64: The reward for the pool.
    456// - error: An invalid calculation input.
    457func calculatePoolReward(emission int64, tierRatio int64, tierCount int64) (int64, error) {
    458 if emission < 0 || tierRatio < 0 || tierCount < 0 {
    459 return 0, errors.New(errCalculationError)
    460 }
    461
    462 if emission == 0 || tierRatio == 0 || tierCount == 0 {
    463 return 0, nil
    464 }
    465
    466 tierReward := gnsmath.SafeMulDivInt64(emission, tierRatio, 100)
    467
    468 return tierReward / tierCount, nil
    469}
    470
    471// computeTierRewards caches per-tier pool rewards to avoid recalculating for each pool iteration.
    472func (self *PoolTier) computeTierRewards(emission int64) map[uint64]int64 {
    473 tierRewards := make(map[uint64]int64, AllTierCount-1)
    474
    475 for tierNum := uint64(1); tierNum < AllTierCount; tierNum++ {
    476 tierCount := int64(self.counts[tierNum])
    477 if tierCount == 0 {
    478 continue
    479 }
    480
    481 tierRatio, err := self.tierRatio.Get(tierNum)
    482 if err != nil {
    483 panic(makeErrorWithDetails(errInvalidPoolTier, err.Error()))
    484 }
    485
    486 reward, err := calculatePoolReward(emission, int64(tierRatio), tierCount)
    487 if err != nil {
    488 panic(err)
    489 }
    490 tierRewards[tierNum] = reward
    491 }
    492
    493 return tierRewards
    494}
    495
    *gno.land/r/gnoswap/staker/v1.stakerV1