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