init.gno
7.03 Kb · 258 lines
1package staker
2
3import (
4 "time"
5
6 "gno.land/r/gnoswap/access/v1"
7
8 "gno.land/r/gnoswap/emission"
9
10 sr "gno.land/r/gnoswap/staker"
11)
12
13const (
14 defaultDepositGnsAmount = int64(100_000_000_000)
15 defaultMinimumRewardAmount = int64(1_000_000_000)
16
17 // unstakingFee is the fee charged when unstaking positions.
18 // This parameter can be modified through governance.
19 defaultUnstakingFee = uint64(100) // 1%
20)
21
22func init(cur realm) {
23 registerStakerV1(cur)
24}
25
26func registerStakerV1(cur realm) {
27 sr.RegisterInitializer(cross(cur), func(_ int, rlm realm, stakerStore sr.IStakerStore, poolAccessor sr.PoolAccessor, emissionAccessor sr.EmissionAccessor, nftAccessor sr.NFTAccessor) sr.IStaker {
28 access.AssertIsRlmCurrent(0, rlm)
29
30 err := initStoreData(0, rlm, stakerStore, emissionAccessor)
31 if err != nil {
32 panic(err)
33 }
34
35 instance := NewStakerV1(stakerStore, poolAccessor, emissionAccessor, nftAccessor)
36 instance.setupSwapHooks(0, rlm)
37 emissionAccessor.SetOnDistributionPctChangeCallback(0, rlm, instance.emissionCacheUpdateHook)
38
39 return instance
40 })
41}
42
43func initStoreData(_ int, rlm realm, stakerStore sr.IStakerStore, emissionAccessor sr.EmissionAccessor) error {
44 if !stakerStore.HasDepositGnsAmountStoreKey() {
45 err := stakerStore.SetDepositGnsAmount(0, rlm, defaultDepositGnsAmount)
46 if err != nil {
47 return err
48 }
49 }
50
51 if !stakerStore.HasMinimumRewardAmountStoreKey() {
52 err := stakerStore.SetMinimumRewardAmount(0, rlm, defaultMinimumRewardAmount)
53 if err != nil {
54 return err
55 }
56 }
57
58 if !stakerStore.HasDepositsStoreKey() {
59 err := stakerStore.SetDeposits(0, rlm, sr.NewBPTreeN(16))
60 if err != nil {
61 return err
62 }
63 }
64
65 if !stakerStore.HasExternalIncentivesStoreKey() {
66 err := stakerStore.SetExternalIncentives(0, rlm, sr.NewBPTreeN(16))
67 if err != nil {
68 return err
69 }
70 }
71
72 if !stakerStore.HasTotalEmissionSentStoreKey() {
73 err := stakerStore.SetTotalEmissionSent(0, rlm, 0)
74 if err != nil {
75 return err
76 }
77 }
78
79 if !stakerStore.HasAllowedTokensStoreKey() {
80 // Copy the package-level default slice so each store owns its own
81 // allowed-tokens slice. Storing sr.DefaultAllowedTokens directly would
82 // alias the shared (sr-owned) slice across every store, leaking
83 // mutations and tripping the readonly-taint gate on later writes.
84 err := stakerStore.SetAllowedTokens(0, rlm, sr.DefaultAllowedTokens())
85 if err != nil {
86 return err
87 }
88 }
89
90 if !stakerStore.HasIncentiveCounterStoreKey() {
91 err := stakerStore.SetIncentiveCounter(0, rlm, sr.NewCounter())
92 if err != nil {
93 return err
94 }
95 }
96
97 if !stakerStore.HasTokenSpecificMinimumRewardsStoreKey() {
98 err := stakerStore.SetTokenSpecificMinimumRewards(0, rlm, make(map[string]int64))
99 if err != nil {
100 return err
101 }
102 }
103
104 if !stakerStore.HasUnstakingFeeStoreKey() {
105 err := stakerStore.SetUnstakingFee(0, rlm, defaultUnstakingFee)
106 if err != nil {
107 return err
108 }
109 }
110
111 pendingProtocolFees := make(map[string]int64)
112 if stakerStore.HasPendingProtocolFeesStoreKey() {
113 pendingProtocolFees = stakerStore.GetPendingProtocolFees()
114 }
115 if err := stakerStore.SetPendingProtocolFees(0, rlm, pendingProtocolFees); err != nil {
116 return err
117 }
118
119 if !stakerStore.HasUnstakedPositionsStoreKey() {
120 err := stakerStore.SetUnstakedPositions(0, rlm, sr.NewBPTreeN(16))
121 if err != nil {
122 return err
123 }
124 }
125
126 if !stakerStore.HasUncollectedIncentiveCountsStoreKey() {
127 err := stakerStore.SetUncollectedIncentiveCounts(0, rlm, sr.NewBPTreeN(16))
128 if err != nil {
129 return err
130 }
131 }
132
133 if !stakerStore.HasWarmupTemplateStoreKey() {
134 err := stakerStore.SetWarmupTemplate(0, rlm, sr.DefaultWarmupTemplate())
135 if err != nil {
136 return err
137 }
138 }
139
140 initializedPoolTier, initializedPools, initialPoolTierTime, initialTierRewards := initializePoolTier(stakerStore)
141
142 previousRealm := rlm.Previous()
143 emitSetPoolTier(
144 previousRealm.Address().String(),
145 previousRealm.PkgPath(),
146 emission.DefaultInitialPoolTierPath,
147 uint64(Tier1),
148 initializedPoolTier.currentEmission,
149 initialTierRewards,
150 initializedPoolTier.counts,
151 initialPoolTierTime,
152 )
153
154 if !stakerStore.HasPoolsStoreKey() {
155 err := stakerStore.SetPools(0, rlm, initializedPools.tree)
156 if err != nil {
157 return err
158 }
159 }
160
161 if !stakerStore.HasPoolTierMembershipsStoreKey() {
162 err := stakerStore.SetPoolTierMemberships(0, rlm, initializedPoolTier.membership)
163 if err != nil {
164 return err
165 }
166 }
167
168 if !stakerStore.HasPoolTierRatioStoreKey() {
169 err := stakerStore.SetPoolTierRatio(0, rlm, initializedPoolTier.tierRatio)
170 if err != nil {
171 return err
172 }
173 }
174
175 if !stakerStore.HasPoolTierCountsStoreKey() {
176 err := stakerStore.SetPoolTierCounts(0, rlm, initializedPoolTier.counts)
177 if err != nil {
178 return err
179 }
180 }
181
182 if !stakerStore.HasPoolTierLastRewardCacheTimestampStoreKey() {
183 err := stakerStore.SetPoolTierLastRewardCacheTimestamp(0, rlm, initializedPoolTier.lastRewardCacheTimestamp)
184 if err != nil {
185 return err
186 }
187 }
188
189 if !stakerStore.HasPoolTierCurrentEmissionStoreKey() {
190 err := stakerStore.SetPoolTierCurrentEmission(0, rlm, initializedPoolTier.currentEmission)
191 if err != nil {
192 return err
193 }
194 }
195
196 if !stakerStore.HasPoolTierGetEmissionStoreKey() {
197 getEmissionFn := func() (int64, error) {
198 return emissionAccessor.GetStakerEmissionAmountPerSecond()
199 }
200
201 err := stakerStore.SetPoolTierGetEmission(0, rlm, getEmissionFn)
202 if err != nil {
203 return err
204 }
205 }
206
207 if !stakerStore.HasPoolTierGetHalvingBlocksInRangeStoreKey() {
208 getHalvingBlocksInRangeFn := func(start, end int64) ([]int64, []int64, error) {
209 return emissionAccessor.GetStakerEmissionAmountPerSecondInRange(start, end)
210 }
211
212 err := stakerStore.SetPoolTierGetHalvingBlocksInRange(0, rlm, getHalvingBlocksInRangeFn)
213 if err != nil {
214 return err
215 }
216 }
217
218 if !stakerStore.HasCurrentSwapBatchStoreKey() {
219 err := stakerStore.SetCurrentSwapBatch(0, rlm, nil)
220 if err != nil {
221 return err
222 }
223 }
224
225 return nil
226}
227
228// initializePoolTier seeds the staker's pool tiering state on first init.
229//
230// The default pool is the canonical WUGNOT:GNS:3000 pool, sourced from the
231// emission realm's DefaultInitialPoolTierPath constant so that the staker's
232// tier-1 base pool is guaranteed to match the pool emission distributes to.
233//
234// When SetDistributionStartTime executes, emission distributes to this default
235// pool and the staker uses the same path as the base tier-1 pool for reward
236// accrual. Keeping both sides on emission's single constant structurally
237// enforces that invariant and removes any risk of the two realms drifting
238// apart on the canonical default pool identity.
239func initializePoolTier(stakerStore sr.IStakerStore) (*PoolTier, *Pools, int64, map[uint64]int64) {
240 pools := NewPools()
241 currentTime := time.Now().Unix()
242
243 pl := NewPools().GetPoolOrNil(emission.DefaultInitialPoolTierPath)
244 if pl == nil {
245 pl = sr.NewPool(emission.DefaultInitialPoolTierPath, currentTime)
246 pools.set(emission.DefaultInitialPoolTierPath, pl)
247 }
248
249 poolTier := NewPoolTier(
250 pools,
251 currentTime,
252 emission.DefaultInitialPoolTierPath,
253 emission.GetStakerEmissionAmountPerSecond,
254 emission.GetStakerEmissionAmountPerSecondInRange,
255 )
256
257 return poolTier, pools, currentTime, poolTier.computeTierRewards(poolTier.currentEmission)
258}