staker_reward.gno
8.71 Kb · 302 lines
1package staker
2
3import (
4 "chain"
5 "errors"
6 "time"
7
8 prbac "gno.land/p/gnoswap/rbac"
9 "gno.land/p/gnoswap/utils"
10 ufmt "gno.land/p/nt/ufmt/v0"
11
12 "gno.land/r/gnoswap/access"
13 "gno.land/r/gnoswap/common"
14 "gno.land/r/gnoswap/gns"
15 "gno.land/r/gnoswap/gov/xgns"
16 "gno.land/r/gnoswap/halt"
17)
18
19// CollectReward collects accumulated rewards based on xGNS holdings.
20//
21// Claims all pending rewards from governance staking.
22// Distributes protocol fees and emission rewards proportionally.
23// Multi-token rewards system based on xGNS share.
24//
25// Reward Types:
26// 1. Emission rewards: GNS from protocol emission
27// 2. Protocol fees: Various tokens from swap/pool fees
28// 3. Withdrawal fees: 1% of liquidity provider rewards
29// 4. Pool creation fees: 100 GNS per pool
30//
31// Distribution Formula:
32//
33// userReward = (userXGNS / totalXGNS) * accumulatedRewards
34//
35// Process:
36// 1. Calculates share based on xGNS balance
37// 2. Claims GNS emission rewards
38// 3. Claims protocol fee rewards (all tokens)
39// 4. Transfers all rewards to caller
40// 5. Resets user's reward tracking
41//
42// No parameters required - automatically determines caller's rewards.
43// Transfers rewards directly to caller.
44func (gs *govStakerV1) CollectReward(_ int, rlm realm) {
45 if !rlm.IsCurrent() {
46 panic(errors.New(errSpoofedRealm))
47 }
48
49 halt.AssertIsNotHaltedWithdraw()
50
51 prev := rlm.Previous()
52 caller := prev.Address()
53 from := rlm.Address()
54 currentTimestamp := time.Now().Unix()
55
56 emissionReward, protocolFeeRewards, err := gs.claimRewards(0, rlm, caller.String(), currentTimestamp)
57 if err != nil {
58 panic(err)
59 }
60
61 // Transfer emission rewards (GNS tokens) if any
62 if emissionReward > 0 {
63 gns.Transfer(cross(rlm), caller, emissionReward)
64
65 chain.Emit(
66 "CollectEmissionReward",
67 "prevAddr", prev.Address().String(),
68 "prevRealm", prev.PkgPath(),
69 "from", from.String(),
70 "to", caller.String(),
71 "emissionRewardAmount", utils.FormatInt(emissionReward),
72 )
73 }
74
75 // Transfer protocol fee rewards for each token type
76 for tokenPath, amount := range protocolFeeRewards {
77 if amount > 0 {
78 err := transferToken(0, rlm, tokenPath, from, caller, amount)
79 if err != nil {
80 panic(err)
81 }
82
83 chain.Emit(
84 "CollectProtocolFeeReward",
85 "prevAddr", prev.Address().String(),
86 "prevRealm", prev.PkgPath(),
87 "tokenPath", tokenPath,
88 "from", from.String(),
89 "to", caller.String(),
90 "collectedAmount", utils.FormatInt(amount),
91 )
92 }
93 }
94}
95
96// CollectEmissionReward collects accumulated GNS emission rewards only.
97func (gs *govStakerV1) CollectEmissionReward(_ int, rlm realm) {
98 assertNotImplementYet()
99}
100
101// CollectProtocolFeeReward collects accumulated protocol fee rewards for the provided token path.
102func (gs *govStakerV1) CollectProtocolFeeReward(_ int, rlm realm, tokenPath string) {
103 assertNotImplementYet()
104}
105
106// CollectRewardFromLaunchPad collects rewards for launchpad project wallets.
107//
108// Parameters:
109// - to: recipient address for rewards
110//
111// Only callable by launchpad contract.
112func (gs *govStakerV1) CollectRewardFromLaunchPad(_ int, rlm realm, to address) {
113 if !rlm.IsCurrent() {
114 panic(errors.New(errSpoofedRealm))
115 }
116
117 halt.AssertIsNotHaltedWithdraw()
118
119 prev := rlm.Previous()
120 caller := prev.Address()
121 access.AssertIsLaunchpad(caller)
122
123 from := rlm.Address()
124 currentTimestamp := time.Now().Unix()
125
126 launchpadRewardID := gs.makeLaunchpadRewardID(to.String())
127 _, exists := gs.getLaunchpadProjectDeposit(launchpadRewardID)
128 if !exists {
129 panic(makeErrorWithDetails(
130 errNoDelegatedAmount,
131 ufmt.Sprintf("%s is not project wallet from launchpad", to.String()),
132 ))
133 }
134
135 emissionReward, protocolFeeRewards, err := gs.claimRewardsFromLaunchpad(0, rlm, to.String(), currentTimestamp)
136 if err != nil {
137 panic(err)
138 }
139
140 // Transfer emission rewards (GNS tokens) to project wallet if any
141 if emissionReward > 0 {
142 gns.Transfer(cross(rlm), to, emissionReward)
143
144 chain.Emit(
145 "CollectEmissionFromLaunchPad",
146 "prevAddr", prev.Address().String(),
147 "prevRealm", prev.PkgPath(),
148 "from", from.String(),
149 "to", to.String(),
150 "emissionRewardAmount", utils.FormatInt(emissionReward),
151 )
152 }
153
154 // Transfer protocol fee rewards to project wallet for each token type
155 for tokenPath, amount := range protocolFeeRewards {
156 if amount > 0 {
157 err := transferToken(0, rlm, tokenPath, from, to, amount)
158 if err != nil {
159 panic(err)
160 }
161
162 chain.Emit(
163 "CollectProtocolFeeFromLaunchPad",
164 "prevAddr", prev.Address().String(),
165 "prevRealm", prev.PkgPath(),
166 "tokenPath", tokenPath,
167 "from", from.String(),
168 "to", to.String(),
169 "collectedAmount", utils.FormatInt(amount),
170 )
171 }
172 }
173}
174
175// CollectEmissionRewardFromLaunchPad collects emission rewards only for launchpad project wallets.
176func (gs *govStakerV1) CollectEmissionRewardFromLaunchPad(_ int, rlm realm, to address) {
177 assertNotImplementYet()
178}
179
180// CollectProtocolFeeRewardFromLaunchPad collects one token path of protocol fee rewards for launchpad project wallets.
181func (gs *govStakerV1) CollectProtocolFeeRewardFromLaunchPad(_ int, rlm realm, to address, tokenPath string) {
182 assertNotImplementYet()
183}
184
185// SetAmountByProjectWallet sets the amount of reward for the project wallet.
186// This function is exclusively callable by the launchpad contract to manage
187// xGNS balances for project wallets that participate in launchpad offerings.
188//
189// The function handles both adding and removing stakes:
190// - When adding: mints xGNS to launchpad address and starts reward accumulation
191// - When removing: burns xGNS from launchpad address and stops reward accumulation
192// Adjusts stake amount for project wallet address.
193// Panics:
194// - if caller is not the launchpad contract
195// - if system is halted for withdrawals
196// - if access control operations fail
197func (gs *govStakerV1) SetAmountByProjectWallet(_ int, rlm realm, addr address, amount int64, add bool) {
198 if !rlm.IsCurrent() {
199 panic(errors.New(errSpoofedRealm))
200 }
201
202 if add {
203 halt.AssertIsNotHaltedGovStaker()
204 } else {
205 halt.AssertIsNotHaltedWithdraw()
206 }
207
208 prev := rlm.Previous()
209 caller := prev.Address()
210 currentTimestamp := time.Now().Unix()
211
212 access.AssertIsLaunchpad(caller)
213
214 launchpadAddr := access.MustGetAddress(prbac.ROLE_LAUNCHPAD.String())
215
216 if add {
217 // Add stake for the project wallet and mint xGNS to launchpad
218 err := gs.addStakeFromLaunchpad(0, rlm, addr.String(), amount, currentTimestamp)
219 if err != nil {
220 panic(err)
221 }
222
223 xgns.Mint(cross(rlm), launchpadAddr, amount)
224 } else {
225 // Remove stake for the project wallet and burn xGNS from launchpad
226 err := gs.removeStakeFromLaunchpad(0, rlm, addr.String(), amount, currentTimestamp)
227 if err != nil {
228 panic(err)
229 }
230
231 xgns.Burn(cross(rlm), launchpadAddr, amount)
232 }
233}
234
235// claimRewards claims both emission and protocol fee rewards.
236// Coordinates claiming process for both reward types.
237func (gs *govStakerV1) claimRewards(_ int, rlm realm, rewardID string, currentTimestamp int64) (int64, map[string]int64, error) {
238 emissionReward, err := gs.claimRewardsEmissionReward(0, rlm, rewardID, currentTimestamp)
239 if err != nil {
240 return 0, nil, err
241 }
242
243 protocolFeeRewards, err := gs.claimRewardsProtocolFeeReward(0, rlm, rewardID, currentTimestamp)
244 if err != nil {
245 return 0, nil, err
246 }
247
248 return emissionReward, protocolFeeRewards, nil
249}
250
251// claimRewardsFromLaunchpad claims rewards for launchpad project wallets.
252// Uses special reward ID format for launchpad integration.
253func (gs *govStakerV1) claimRewardsFromLaunchpad(_ int, rlm realm, address string, currentTimestamp int64) (int64, map[string]int64, error) {
254 launchpadRewardID := gs.makeLaunchpadRewardID(address)
255
256 return gs.claimRewards(0, rlm, launchpadRewardID, currentTimestamp)
257}
258
259// transferToken transfers tokens from the staker contract to a recipient address.
260// transferToken handles token transfers for reward distribution.
261//
262// Non-crossing helper: takes `_ int, rlm realm` so the realm token threaded
263// in by the public reward-collection entry points reaches the underlying
264// GRC-20 transfer without forcing each caller to recompute it.
265func transferToken(
266 _ int,
267 rlm realm,
268 tokenPath string,
269 from, to address,
270 amount int64,
271) error {
272 common.MustRegistered(tokenPath)
273
274 // Validate recipient address
275 if !to.IsValid() {
276 return makeErrorWithDetails(
277 errInvalidAddress,
278 ufmt.Sprintf("invalid address %s to transfer protocol fee", to.String()),
279 )
280 }
281
282 // Validate transfer amount
283 if amount < 0 {
284 return makeErrorWithDetails(
285 errInvalidAmount,
286 ufmt.Sprintf("invalid amount %d to transfer protocol fee", amount),
287 )
288 }
289
290 // Check sufficient balance
291 balance := common.BalanceOf(tokenPath, from)
292 if balance < amount {
293 return makeErrorWithDetails(
294 errNotEnoughBalance,
295 ufmt.Sprintf("not enough %s balance(%d) to collect(%d)", tokenPath, balance, amount),
296 )
297 }
298
299 common.SafeGRC20Transfer(cross(rlm), tokenPath, to, amount)
300
301 return nil
302}