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