Kind Realm (renderable)
Name v1
Namespace gnoswap / gov / staker
Exported functions 12
Module gno.land/r/gnoswap/gov/staker/v1
gno 0.9 protocol_fee_reward_state.gno gno
⧉
1 package staker 2 3 import ( 4 "errors" 5 6 "gno.land/p/gnoswap/consts/v1" 7 "gno.land/p/gnoswap/gnsmath/v1" 8 NewDelegation (id int64, delegateFrom string, delegateTo string, delegateAmount int64, createdHeight int64, createdAt int64) *gno.land/r/gnoswap/gov/staker.Delegation
NewDelegationManagerResolver (delegationManager *gno.land/r/gnoswap/gov/staker.DelegationManager) *gno.land/r/gnoswap/gov/staker/v1.DelegationManagerResolver
NewDelegationResolver (delegation *gno.land/r/gnoswap/gov/staker.Delegation) *gno.land/r/gnoswap/gov/staker/v1.DelegationResolver
NewDelegationWithdraw (delegationID int64, unDelegateAmount int64, createdHeight int64, createdAt int64, unDelegationLockupPeriod int64) struct{delegationID int64; unDelegateAmount int64; unDelegatedHeight int64; unDelegatedAt int64; collectedAmount int64; collectableTime int64; collectedAt int64; collected bool}
NewDelegationWithdrawResolver (withdraw *gno.land/r/gnoswap/gov/staker.DelegationWithdraw) *gno.land/r/gnoswap/gov/staker/v1.DelegationWithdrawResolver
NewDelegationWithdrawWithoutLockup (delegationID int64, unDelegateAmount int64, createdHeight int64, createdAt int64) struct{delegationID int64; unDelegateAmount int64; unDelegatedHeight int64; unDelegatedAt int64; collectedAmount int64; collectableTime int64; collectedAt int64; collected bool}
NewEmissionRewardManagerResolver (emissionRewardManager *gno.land/r/gnoswap/gov/staker.EmissionRewardManager) *gno.land/r/gnoswap/gov/staker/v1.EmissionRewardManagerResolver
NewEmissionRewardStateResolver (emissionRewardState *gno.land/r/gnoswap/gov/staker.EmissionRewardState) *gno.land/r/gnoswap/gov/staker/v1.EmissionRewardStateResolver
NewGovStakerV1 (store interface {GetAllDelegations func() *gno.land/p/nt/bptree/v0.BPTree; GetDelegation func(int64) (*gno.land/r/gnoswap/gov/staker.Delegation, bool); GetDelegationCounter func() *gno.land/r/gnoswap/gov/staker.Counter; GetDelegationManager func() *gno.land/r/gnoswap/gov/staker.DelegationManager; GetEmissionRewardManager func() *gno.land/r/gnoswap/gov/staker.EmissionRewardManager; GetLaunchpadProjectDeposits func() *gno.land/r/gnoswap/gov/staker.LaunchpadProjectDeposits; GetProtocolFeeRewardManager func() *gno.land/r/gnoswap/gov/staker.ProtocolFeeRewardManager; GetTotalDelegatedAmount func() int64; GetTotalDelegationHistory func() *gno.land/r/gnoswap/gov/staker.UintTree; GetTotalLockedAmount func() int64; GetUnDelegationLockupPeriod func() int64; GetUserDelegationHistory func() *gno.land/p/nt/bptree/v0.BPTree; HasDelegation func(int64) bool; HasDelegationCounterStoreKey func() bool; HasDelegationManagerStoreKey func() bool; HasDelegationsStoreKey func() bool; HasEmissionRewardManagerStoreKey func() bool; HasLaunchpadProjectDepositsStoreKey func() bool; HasProtocolFeeRewardManagerStoreKey func() bool; HasTotalDelegatedAmountStoreKey func() bool; HasTotalDelegationHistoryStoreKey func() bool; HasTotalLockedAmountStoreKey func() bool; HasUnDelegationLockupPeriodStoreKey func() bool; HasUserDelegationHistoryStoreKey func() bool; RemoveDelegation func(int, .uverse.realm, int64) .uverse.error; SetDelegation func(int, .uverse.realm, int64, *gno.land/r/gnoswap/gov/staker.Delegation) .uverse.error; SetDelegationCounter func(int, .uverse.realm, *gno.land/r/gnoswap/gov/staker.Counter) .uverse.error; SetDelegationManager func(int, .uverse.realm, *gno.land/r/gnoswap/gov/staker.DelegationManager) .uverse.error; SetDelegations func(int, .uverse.realm, *gno.land/p/nt/bptree/v0.BPTree) .uverse.error; SetEmissionRewardManager func(int, .uverse.realm, *gno.land/r/gnoswap/gov/staker.EmissionRewardManager) .uverse.error; SetLaunchpadProjectDeposits func(int, .uverse.realm, *gno.land/r/gnoswap/gov/staker.LaunchpadProjectDeposits) .uverse.error; SetProtocolFeeRewardManager func(int, .uverse.realm, *gno.land/r/gnoswap/gov/staker.ProtocolFeeRewardManager) .uverse.error; SetTotalDelegatedAmount func(int, .uverse.realm, int64) .uverse.error; SetTotalDelegationHistory func(int, .uverse.realm, *gno.land/r/gnoswap/gov/staker.UintTree) .uverse.error; SetTotalLockedAmount func(int, .uverse.realm, int64) .uverse.error; SetUnDelegationLockupPeriod func(int, .uverse.realm, int64) .uverse.error; SetUserDelegationHistory func(int, .uverse.realm, *gno.land/p/nt/bptree/v0.BPTree) .uverse.error}) interface {CleanStakerDelegationSnapshotByAdmin func(int, .uverse.realm, int64, .uverse.address); CollectEmissionReward func(int, .uverse.realm); CollectEmissionRewardFromLaunchPad func(int, .uverse.realm, .uverse.address); CollectProtocolFeeReward func(int, .uverse.realm, string); CollectProtocolFeeRewardFromLaunchPad func(int, .uverse.realm, .uverse.address, string); CollectReward func(int, .uverse.realm); CollectRewardFromLaunchPad func(int, .uverse.realm, .uverse.address); CollectUndelegatedGns func(int, .uverse.realm) int64; Delegate func(int, .uverse.realm, .uverse.address, int64, string) int64; ExistsDelegation func(int64) bool; GetClaimableRewardByAddress func(.uverse.address) (int64, map[string]int64, .uverse.error); GetClaimableRewardByLaunchpad func(.uverse.address) (int64, map[string]int64, .uverse.error); GetClaimableRewardByRewardID func(string) (int64, map[string]int64, .uverse.error); GetCollectableWithdrawAmount func(int64) int64; GetDelegationWithdrawCount func(int64) int; GetDelegationWithdraws func(int64, int, int) ([]gno.land/r/gnoswap/gov/staker.DelegationWithdraw, .uverse.error); GetDelegations func() *gno.land/p/nt/bptree/rotree/v0.ReadOnlyTree; GetDelegatorDelegations func(.uverse.address) *gno.land/p/nt/bptree/rotree/v0.ReadOnlyTree; GetEmissionAccumulatedTimestamp func() int64; GetEmissionAccumulatedX128PerStake func() *gno.land/p/gnoswap/uint256/v1.Uint; GetEmissionDistributedAmount func() int64; GetLaunchpadProjectDeposit func(string) (int64, bool); GetProtocolFeeAccumulatedTimestamp func() int64; GetProtocolFeeAccumulatedX128PerStake func(string) *gno.land/p/gnoswap/uint256/v1.Uint; GetProtocolFeeAmount func(string) int64; GetTotalDelegated func() int64; GetTotalDelegationAmountAtSnapshot func(int64) (int64, bool); GetTotalLockedAmount func() int64; GetTotalxGnsSupply func() int64; GetUnDelegationLockupPeriod func() int64; GetUserDelegationAmountAtSnapshot func(.uverse.address, int64) (int64, bool); GetUserDelegationIDs func(.uverse.address, .uverse.address) []int64; HasDelegationSnapshotsKey func() bool; Redelegate func(int, .uverse.realm, .uverse.address, .uverse.address, int64) int64; Render func(string) string; SetAmountByProjectWallet func(int, .uverse.realm, .uverse.address, int64, bool); SetUnDelegationLockupPeriodByAdmin func(int, .uverse.realm, int64); Undelegate func(int, .uverse.realm, .uverse.address, int64) int64}
NewLaunchpadProjectDepositsResolver (launchpadProjectDeposits *gno.land/r/gnoswap/gov/staker.LaunchpadProjectDeposits) *gno.land/r/gnoswap/gov/staker/v1.LaunchpadProjectDepositsResolver
NewProtocolFeeRewardManagerResolver (manager *gno.land/r/gnoswap/gov/staker.ProtocolFeeRewardManager) *gno.land/r/gnoswap/gov/staker/v1.ProtocolFeeRewardManagerResolver
NewProtocolFeeRewardStateResolver (protocolFeeRewardState *gno.land/r/gnoswap/gov/staker.ProtocolFeeRewardState) *gno.land/r/gnoswap/gov/staker/v1.ProtocolFeeRewardStateResolver
Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.
Render unavailable
rate limit exceeded — the render query is served by the Gnoland archive RPC.
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.
u256 "gno.land/p/gnoswap/uint256/v1"
9
10 "gno.land/r/gnoswap/gov/staker"
11 )
12
13 // ProtocolFeeRewardStateResolver settles one staker's protocol fee rewards.
14 //
15 // A staker's reward for a token is the sum, over the segments between their stake
16 // events, of the stake held during the segment times the growth of the token's
17 // accumulated fee per stake across it. Segments are folded in event order, so the work
18 // of settling a token is proportional to the staker's own stake changes since the token
19 // was last settled, never to the number of tokens.
20 type ProtocolFeeRewardStateResolver struct {
21 *staker.ProtocolFeeRewardState
22 }
23
24 // NewProtocolFeeRewardStateResolver wraps one staker's protocol fee reward state
25 // with settlement operations.
26 //
27 // Parameters:
28 // - protocolFeeRewardState: staker reward state to resolve and mutate.
29 //
30 // Returns:
31 // - *ProtocolFeeRewardStateResolver: resolver backed by protocolFeeRewardState.
32 func NewProtocolFeeRewardStateResolver(protocolFeeRewardState *staker.ProtocolFeeRewardState) *ProtocolFeeRewardStateResolver {
33 return &ProtocolFeeRewardStateResolver{
34 ProtocolFeeRewardState: protocolFeeRewardState,
35 }
36 }
37
38 // protocolFeeAccumulatorView reads a token accumulator as it stands, optionally
39 // extended in memory with buckets that are still pending on the protocol fee realm.
40 // The extension lets a query report what a collect would pay without folding anything.
41 type protocolFeeAccumulatorView struct {
42 accumulator *staker.ProtocolFeeTokenAccumulator // nil when the token was never folded
43 // extraEpochs and extraValues hold projected history entries, in ascending epoch order
44 extraEpochs [] int64
45 extraValues []*u256.Uint
46 // current is the latest accumulated value, projection included
47 current *u256.Uint
48 // firstEpoch is the first epoch a fee was folded for, projection included, or -1
49 firstEpoch int64
50 // foldedEpoch is the last epoch whose fees are known to be fully folded, or -1
51 foldedEpoch int64
52 // currentEpoch is the accrual epoch in force on the manager
53 currentEpoch int64
54 }
55
56 // at returns the accumulated value after the fees of the latest known epoch at or
57 // before epoch.
58 func (v *protocolFeeAccumulatorView) at(epoch int64 ) *u256.Uint {
59 for i := len(v.extraEpochs) - 1 ; i >= 0 ; i-- {
60 if v.extraEpochs[i] <= epoch {
61 return v.extraValues[i]
62 }
63 }
64
65 if v.accumulator == nil {
66 return u256.Zero()
67 }
68
69 return v.accumulator.GetAccumulatedX128PerStakeAt(epoch)
70 }
71
72 // protocolFeeTokenSettlement is the in-memory working copy of a token's settlement
73 // state. It is written back only on a collect, so queries never touch storage.
74 type protocolFeeTokenSettlement struct {
75 eventCursor int64
76 segmentStakedAmount int64
77 segmentStartX128 *u256.Uint
78 earnedX128 *u256.Uint
79 }
80
81 // loadTokenSettlement returns the settlement state of tokenPath, initialising it when
82 // the token was never settled for this staker.
83 //
84 // A fresh state skips every stake event at or before the accumulator's first epoch:
85 // the accumulator was zero throughout, so those segments earned nothing and the open
86 // segment can start at the last of them. When no fee was folded at all, the skip
87 // covers every event, but only once every closed epoch is known to be folded: a
88 // bucket still pending from a closed epoch could later credit one of those events,
89 // so until then they are settled one by one through settleEvents, which is gated by
90 // foldedEpoch and yields nothing while the accumulator stays at zero.
91 func (p *ProtocolFeeRewardStateResolver) loadTokenSettlement(tokenPath string , view *protocolFeeAccumulatorView) *protocolFeeTokenSettlement {
92 if tokenState, ok := p.GetTokenState(tokenPath); ok {
93 return &protocolFeeTokenSettlement{
94 eventCursor: tokenState.GetEventCursor(),
95 segmentStakedAmount: tokenState.GetSegmentStakedAmount(),
96 segmentStartX128: tokenState.GetSegmentStartX128().Clone(),
97 earnedX128: tokenState.GetEarnedX128().Clone(),
98 }
99 }
100
101 settlement := &protocolFeeTokenSettlement{
102 eventCursor: 0 ,
103 segmentStakedAmount: 0 ,
104 segmentStartX128: u256.Zero(),
105 earnedX128: u256.Zero(),
106 }
107
108 eventCount := p.GetStakeEventCount()
109 if view.firstEpoch < 0 {
110 if view.foldedEpoch >= view.currentEpoch- 1 {
111 // No fee was ever folded and nothing from a closed epoch is pending, so
112 // every recorded event earned nothing.
113 settlement.eventCursor = eventCount
114 settlement.segmentStakedAmount = p.GetStakedAmount()
115 }
116 return settlement
117 }
118
119 // Stake events are appended in epoch order, so the last event at or before
120 // firstEpoch is found by binary search: the lookup costs O(log n) reads no matter
121 // how many stake changes the staker made since the token's first fee.
122 lo, hi := int64 ( 0 ), eventCount
123 for lo < hi {
124 mid := lo + (hi-lo)/ 2
125 event, ok := p.GetStakeEvent(mid)
126 if !ok {
127 break
128 }
129 if event.GetEpoch() <= view.firstEpoch {
130 lo = mid + 1
131 } else {
132 hi = mid
133 }
134 }
135
136 if lo > 0 {
137 if event, ok := p.GetStakeEvent(lo - 1 ); ok {
138 settlement.eventCursor = lo
139 settlement.segmentStakedAmount = event.GetStakedAmount()
140 }
141 }
142
143 return settlement
144 }
145
146 // settleEvents folds up to maxEvents pending stake events into the settlement. An
147 // event can only be folded once the epoch before it is fully folded on the
148 // accumulator, because the segment it closes ends at that epoch's value. A maxEvents
149 // of zero or less folds every event it can. Returns true when no event is left pending.
150 func (p *ProtocolFeeRewardStateResolver) settleEvents(settlement *protocolFeeTokenSettlement, view *protocolFeeAccumulatorView, maxEvents int64 ) bool {
151 eventCount := p.GetStakeEventCount()
152 processed := int64 ( 0 )
153
154 for settlement.eventCursor < eventCount {
155 if maxEvents > 0 && processed >= maxEvents {
156 return false
157 }
158
159 event, ok := p.GetStakeEvent(settlement.eventCursor)
160 if !ok {
161 return false
162 }
163 if event.GetEpoch()- 1 > view.foldedEpoch {
164 return false
165 }
166
167 segmentEndX128 := view.at(event.GetEpoch() - 1 )
168 settlement.earnedX128 = u256.Zero().Add(
169 settlement.earnedX128,
170 segmentRewardX128(settlement.segmentStakedAmount, settlement.segmentStartX128, segmentEndX128),
171 )
172 settlement.segmentStartX128 = segmentEndX128.Clone()
173 settlement.segmentStakedAmount = event.GetStakedAmount()
174 settlement.eventCursor++
175 processed++
176 }
177
178 return true
179 }
180
181 // closeOpenSegment folds the open segment up to the accumulator's latest value. It may
182 // only be called once every stake event is settled, so that the open segment really
183 // ends at the present.
184 func closeOpenSegment(settlement *protocolFeeTokenSettlement, view *protocolFeeAccumulatorView) {
185 settlement.earnedX128 = u256.Zero().Add(
186 settlement.earnedX128,
187 segmentRewardX128(settlement.segmentStakedAmount, settlement.segmentStartX128, view.current),
188 )
189 settlement.segmentStartX128 = view.current.Clone()
190 }
191
192 // takeReward removes the whole-unit part of the earned reward from the settlement and
193 // returns it. The sub-unit remainder stays earned so that no fee share is lost.
194 func takeReward(settlement *protocolFeeTokenSettlement) int64 {
195 rewardX128 := u256.Zero().Rsh(settlement.earnedX128, 128 )
196 reward := gnsmath.SafeConvertToInt64(rewardX128)
197 settlement.earnedX128 = u256.Zero().Sub(settlement.earnedX128, u256.Zero().Lsh(rewardX128, 128 ))
198
199 return reward
200 }
201
202 // segmentRewardX128 returns stakedAmount times the accumulator growth across a segment.
203 func segmentRewardX128(stakedAmount int64 , startX128, endX128 *u256.Uint) *u256.Uint {
204 if stakedAmount <= 0 || !endX128.Gt(startX128) {
205 return u256.Zero()
206 }
207
208 growthX128 := u256.Zero().Sub(endX128, startX128)
209
210 return u256.Zero().Mul(growthX128, u256.NewUintFromInt64(stakedAmount))
211 }
212
213 // storeTokenSettlement writes the settlement back to the staker's state.
214 func (p *ProtocolFeeRewardStateResolver) storeTokenSettlement(tokenPath string , settlement *protocolFeeTokenSettlement) {
215 tokenState, ok := p.GetTokenState(tokenPath)
216 if !ok {
217 tokenState = staker.NewProtocolFeeTokenRewardState(settlement.eventCursor, settlement.segmentStakedAmount, settlement.segmentStartX128)
218 tokenState.SetEarnedX128(settlement.earnedX128)
219 p.SetTokenState(tokenPath, tokenState)
220 return
221 }
222
223 tokenState.SetSegment(settlement.eventCursor, settlement.segmentStakedAmount, settlement.segmentStartX128)
224 tokenState.SetEarnedX128(settlement.earnedX128)
225 }
226
227 // claimTokenReward settles tokenPath and returns the reward collected.
228 //
229 // Up to maxEvents stake events are folded; whatever is settled by then is paid, and
230 // the rest stays pending for a later collect. A maxEvents of zero or less folds
231 // everything.
232 func (p *ProtocolFeeRewardStateResolver) claimTokenReward(tokenPath string , view *protocolFeeAccumulatorView, maxEvents int64 ) ( int64 , error ) {
233 // A token that never collected a fee has nothing to settle; skipping it keeps the
234 // staker's state free of an entry per token merely queried.
235 if _, ok := p.GetTokenState(tokenPath); !ok && view.firstEpoch < 0 {
236 return 0 , nil
237 }
238
239 settlement := p.loadTokenSettlement(tokenPath, view)
240
241 if p.settleEvents(settlement, view, maxEvents) {
242 closeOpenSegment(settlement, view)
243 }
244
245 reward := takeReward(settlement)
246 p.storeTokenSettlement(tokenPath, settlement)
247
248 if reward > 0 {
249 if tokenState, ok := p.GetTokenState(tokenPath); ok {
250 tokenState.SetClaimedReward(gnsmath.SafeAddInt64(tokenState.GetClaimedReward(), reward))
251 }
252 }
253
254 return reward, nil
255 }
256
257 // getClaimableReward returns what a collect of tokenPath would pay right now, without
258 // changing any state.
259 func (p *ProtocolFeeRewardStateResolver) getClaimableReward(tokenPath string , view *protocolFeeAccumulatorView) int64 {
260 settlement := p.loadTokenSettlement(tokenPath, view)
261
262 if p.settleEvents(settlement, view, 0 ) {
263 closeOpenSegment(settlement, view)
264 }
265
266 return gnsmath.SafeConvertToInt64(u256.Zero().Div(settlement.earnedX128, consts.Q128()))
267 }
268
269 // addStake increases the staked amount and records the change from epoch on.
270 func (p *ProtocolFeeRewardStateResolver) addStake(amount int64 , epoch int64 ) error {
271 if amount <= 0 {
272 return errors.New(errAmountMustBePositive)
273 }
274
275 stakedAmount := gnsmath.SafeAddInt64(p.GetStakedAmount(), amount)
276 p.SetStakedAmount(stakedAmount)
277 p.AppendStakeEvent(epoch, stakedAmount)
278
279 return nil
280 }
281
282 // removeStake decreases the staked amount and records the change from epoch on.
283 func (p *ProtocolFeeRewardStateResolver) removeStake(amount int64 , epoch int64 ) error {
284 if amount < 0 {
285 return errors.New(errAmountMustBeNonNegative)
286 }
287 if amount > p.GetStakedAmount() {
288 return errors.New(errRemoveAmountExceedsStaked)
289 }
290
291 stakedAmount := gnsmath.SafeSubInt64(p.GetStakedAmount(), amount)
292 p.SetStakedAmount(stakedAmount)
293 p.AppendStakeEvent(epoch, stakedAmount)
294
295 return nil
296 }
297
298 // IsClaimable reports whether every token may be collected at once at currentTimestamp.
299 //
300 // Parameters:
301 // - currentTimestamp: current Unix timestamp used to compare the last collection.
302 //
303 // Returns:
304 // - bool: true when currentTimestamp is later than the state's claimed timestamp.
305 func (p *ProtocolFeeRewardStateResolver) IsClaimable(currentTimestamp int64 ) bool {
306 return p.GetClaimedTimestamp() < currentTimestamp
307 }
308