Kind Realm (renderable)
Name v1
Namespace gnoswap / staker
Exported functions 14
Module gno.land/r/gnoswap/staker/v1
gno 0.9 reward_calculation_incentives.gno gno
⧉
1 package staker 2 3 import ( 4 "gno.land/p/gnoswap/gnsmath/v1" 5 u256 "gno.land/p/gnoswap/uint256/v1" 6 bptree "gno.land/p/nt/bptree/v0" 7 8 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.
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.
sr "gno.land/r/gnoswap/staker"
9 )
10
11 type IncentivesResolver struct {
12 *sr.Incentives
13 }
14
15 // NewIncentivesResolver wraps the persisted external-incentive state for lookup and mutation.
16 //
17 // Parameters:
18 // - incentives: External incentive state containing the incentive tree and unclaimable periods.
19 //
20 // Returns:
21 // - resolver: Resolver backed by incentives.
22 func NewIncentivesResolver(incentives *sr.Incentives) *IncentivesResolver {
23 return &IncentivesResolver{
24 Incentives: incentives,
25 }
26 }
27
28 // Get resolves an external incentive by its identifier.
29 //
30 // Parameters:
31 // - incentiveId: External incentive identifier used as the tree key.
32 //
33 // Returns:
34 // - incentive: Stored external incentive pointer when the ID exists; nil when it is absent.
35 // - found: True when incentiveId is present in the tree; false otherwise.
36 func (self *IncentivesResolver) Get(incentiveId string ) (*sr.ExternalIncentive, bool ) {
37 return retrieveIncentive(self.IncentiveTrees(), incentiveId)
38 }
39
40 // GetIncentiveResolver resolves an incentive ID to its field accessor.
41 //
42 // Parameters:
43 // - incentiveId: External incentive identifier to resolve.
44 //
45 // Returns:
46 // - resolver: Resolver for the stored incentive when found; nil when incentiveId is absent.
47 // - found: True when incentiveId resolves to a stored incentive; false otherwise.
48 func (self *IncentivesResolver) GetIncentiveResolver(incentiveId string ) (*ExternalIncentiveResolver, bool ) {
49 if incentive, ok := self.Get(incentiveId); ok {
50 return NewExternalIncentiveResolver(incentive), true
51 }
52 return nil , false
53 }
54
55 func retrieveIncentive(tree *bptree.BPTree, id string ) (*sr.ExternalIncentive, bool ) {
56 value := tree.Get(id)
57 if value == nil {
58 return nil , false
59 }
60 v, ok := value.(*sr.ExternalIncentive)
61 if !ok {
62 panic( "failed to cast value to *sr.ExternalIncentive" )
63 }
64 return v, true
65 }
66
67 // Create a new external incentive
68 // Panics if the incentive already exists.
69 func (self *IncentivesResolver) create(incentive *sr.ExternalIncentive) {
70 self.Incentives.SetIncentive(incentive.IncentiveId(), incentive)
71 self.Incentives.AddIncentiveByStartTime(incentive.StartTimestamp(), incentive.IncentiveId())
72 }
73
74 // update updates an existing incentive with new information
75 func (self *IncentivesResolver) update(incentive *sr.ExternalIncentive) {
76 self.Incentives.SetIncentive(incentive.IncentiveId(), incentive)
77 }
78
79 // remove drops an incentive from both the incentive tree and the start-time
80 // discovery index. It is only valid for an incentive that has not started: no
81 // deposit can reference it yet, so nothing is left pointing at a missing record.
82 func (self *IncentivesResolver) remove(incentive *sr.ExternalIncentive) {
83 self.Incentives.RemoveIncentive(incentive.IncentiveId())
84 self.Incentives.RemoveIncentiveByStartTime(incentive.StartTimestamp(), incentive.IncentiveId())
85 }
86
87 // starts incentive unclaimable period for this pool
88 func (self *IncentivesResolver) startUnclaimablePeriod(startTimestamp int64 ) {
89 self.Incentives.SetUnclaimablePeriod(startTimestamp, int64 ( 0 ))
90 }
91
92 // ends incentive unclaimable period for this pool
93 // ignores if currently not in unclaimable period
94 func (self *IncentivesResolver) endUnclaimablePeriod(endTimestamp int64 ) {
95 startTimestamp := int64 ( 0 )
96 self.UnclaimablePeriods().ReverseIterate( 0 , endTimestamp, func (key int64 , value any ) bool {
97 v, ok := value.( int64 )
98 if !ok {
99 panic( "failed to cast value to int64" )
100 }
101 if v != 0 {
102 // Already ended, no need to update
103 // keeping startTimestamp as 0 to indicate this
104 return true
105 }
106 startTimestamp = key
107 return true
108 })
109
110 if startTimestamp == 0 {
111 // No ongoing unclaimable period found
112 return
113 }
114
115 if startTimestamp == endTimestamp {
116 self.Incentives.RemoveUnclaimablePeriod(startTimestamp)
117 } else {
118 self.Incentives.SetUnclaimablePeriod(startTimestamp, endTimestamp)
119 }
120
121 // Accumulate the just-closed period into every active incentive
122 self.accumulateUnclaimableSeconds(startTimestamp, endTimestamp)
123 }
124
125 // accumulateUnclaimableSeconds adds the unclaimable duration between
126 // startTimestamp and endTimestamp to every non-refunded incentive whose window
127 // overlaps the period. The accumulator is updated in place on the stored
128 // incentive pointers, so no tree write is required here.
129 func (self *IncentivesResolver) accumulateUnclaimableSeconds(startTimestamp, endTimestamp int64 ) {
130 self.Incentives.IterateIncentives( func (_ string , incentive *sr.ExternalIncentive) bool {
131 if incentive.Refunded() {
132 return false
133 }
134
135 duration := calculateUnClaimableDuration(
136 startTimestamp,
137 endTimestamp,
138 incentive.StartTimestamp(),
139 incentive.EndTimestamp(),
140 )
141 if duration > 0 {
142 incentive.SetUnclaimableSeconds(gnsmath.SafeAddInt64(incentive.UnclaimableSeconds(), duration))
143 }
144 return false
145 })
146 }
147
148 // calculate unclaimable reward from the per-incentive unclaimable seconds
149 // accumulator instead of scanning the unbounded unclaimable periods tree.
150 func (self *IncentivesResolver) calculateUnclaimableReward(incentiveId string ) int64 {
151 incentive, ok := self.Get(incentiveId)
152 if !ok {
153 return 0
154 }
155
156 // The accumulator holds the duration of every closed unclaimable period
157 // that overlaps the incentive window.
158 timeDiff := incentive.UnclaimableSeconds()
159
160 // Ongoing unclaimable periods (end == 0) are not yet accumulated because
161 // their end is unknown. They are resolved here by treating them as
162 // extending to the incentive end. Unclaimable periods never overlap and
163 // are recorded in ascending order, so an ongoing period is always the
164 // most recently started one and therefore the highest key in the tree.
165 // ReverseIterate visits the highest key first, so each scan below finds
166 // the only ongoing period that can affect the reward (if any) on its
167 // first visit and stops - the scan is bounded regardless of how many
168 // periods exist in the tree.
169 //
170 // Tail 1: the ongoing period that started before the incentive window
171 // (e.g. the initial pool-creation period). If the highest period starting
172 // before the window is already closed, no ongoing period can overlap the
173 // incentive start and there is nothing to add.
174 self.UnclaimablePeriods().ReverseIterate( 0 , incentive.StartTimestamp()- 1 , func (startTimestamp int64 , value any ) bool {
175 endTimestamp, ok := value.( int64 )
176 if !ok {
177 panic( "failed to cast value to int64" )
178 }
179 if endTimestamp != 0 {
180 // Already closed - the duration is already accumulated in
181 // UnclaimableSeconds(), and being the highest key it is also the
182 // latest period, so no ongoing period can exist below it.
183 return true
184 }
185
186 timeDiff = gnsmath.SafeAddInt64(timeDiff, calculateUnClaimableDuration(
187 startTimestamp,
188 incentive.EndTimestamp(),
189 incentive.StartTimestamp(),
190 incentive.EndTimestamp(),
191 ))
192 return true
193 })
194
195 // Tail 2: the ongoing period that started within the incentive window.
196 // The upper bound is endTime-1 to mirror the Iterate(start, end)
197 // half-open range, which excludes a period starting exactly at endTime
198 // (e.g. the initial pool-creation period that coincides with endTime).
199 self.UnclaimablePeriods().ReverseIterate(incentive.StartTimestamp(), incentive.EndTimestamp()- 1 , func (startTimestamp int64 , value any ) bool {
200 endTimestamp, ok := value.( int64 )
201 if !ok {
202 panic( "failed to cast value to int64" )
203 }
204 if endTimestamp != 0 {
205 // Already closed - the duration is already accumulated in
206 // UnclaimableSeconds(), and being the highest key it is also the
207 // latest period, so no ongoing period can exist below it.
208 return true
209 }
210
211 timeDiff = gnsmath.SafeAddInt64(timeDiff, calculateUnClaimableDuration(
212 startTimestamp,
213 incentive.EndTimestamp(),
214 incentive.StartTimestamp(),
215 incentive.EndTimestamp(),
216 ))
217 return true
218 })
219
220 // rewardPerSecondX128 = rps << 128, so dividing by q128 here recovers the
221 // floor of (timeDiff * rps) without the truncation that an int64 rps would
222 // have introduced at incentive-creation time.
223 unclaimable := u256.MulDiv(
224 u256.NewUintFromInt64(timeDiff),
225 incentive.RewardPerSecondX128(),
226 q128,
227 )
228 return gnsmath.SafeConvertToInt64(unclaimable)
229 }
230
231 // calculateUnClaimableDuration calculates the duration of overlap between an unclaimable period and incentive period
232 func calculateUnClaimableDuration(unclaimableStart, unclaimableEnd, incentiveStartTimestamp, incentiveEndTimestamp int64 ) int64 {
233 // Use later timestamp between unclaimable start and incentive start
234 startTime := unclaimableStart
235 if startTime < incentiveStartTimestamp {
236 startTime = incentiveStartTimestamp
237 }
238
239 // Use earlier timestamp between unclaimable end and incentive end
240 endTime := unclaimableEnd
241 if endTime > incentiveEndTimestamp {
242 endTime = incentiveEndTimestamp
243 }
244
245 // Return 0 if no overlap
246 if endTime < startTime {
247 return 0
248 }
249
250 // Calculate overlap duration
251 return gnsmath.SafeSubInt64(endTime, startTime)
252 }
253
*gno.land/r/gnoswap/staker/v1.stakerV1