Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

staker_reward.gno

13.53 Kb · 450 lines
  1package staker
  2
  3import (
  4	"chain"
  5	"time"
  6
  7	prbac "gno.land/p/gnoswap/rbac/v1"
  8	"gno.land/p/gnoswap/utils/v1"
  9	ufmt "gno.land/p/nt/ufmt/v0"
 10
 11	"gno.land/r/gnoswap/access/v1"
 12	"gno.land/r/gnoswap/common"
 13	"gno.land/r/gnoswap/gns"
 14	"gno.land/r/gnoswap/gov/xgns"
 15	"gno.land/r/gnoswap/halt/v1"
 16)
 17
 18// CollectReward claims all rewards for the caller.
 19//
 20// It settles both reward streams:
 21//  1. GNS emission rewards from the emission accumulator.
 22//  2. Protocol-fee rewards for every known token path.
 23//
 24// Protocol-fee rewards are divided by the total stake in force for each
 25// accrual epoch and settled across the caller's stake-event segments. They
 26// are not calculated from one current xGNS/total-xGNS ratio. Q128 fractional
 27// remainders remain in state for later collection.
 28//
 29// The all-token path intentionally grows with the number of known fee tokens.
 30// Use the token-specific entry point when bounded work is required.
 31//
 32// No business parameters are required; the caller's reward ID is used.
 33// Rewards are transferred directly to the caller.
 34//
 35// Parameters:
 36//   - _: Noncrossing implementation-call discriminator; pass 0.
 37//   - rlm: Current realm context forwarded by the governance-staker proxy; validated before settlement and transfers.
 38func (gs *govStakerV1) CollectReward(_ int, rlm realm) {
 39	access.AssertIsRlmCurrent(0, rlm)
 40
 41	halt.AssertIsNotHaltedWithdraw()
 42
 43	prev := rlm.Previous()
 44	caller := prev.Address()
 45	from := rlm.Address()
 46	currentTimestamp := time.Now().Unix()
 47
 48	emissionReward, protocolFeeRewards, err := gs.claimRewards(0, rlm, caller.String(), currentTimestamp)
 49	if err != nil {
 50		panic(err)
 51	}
 52
 53	// Transfer emission rewards (GNS tokens) if any
 54	if emissionReward > 0 {
 55		gns.Transfer(cross(rlm), caller, emissionReward)
 56
 57		chain.Emit(
 58			"CollectEmissionReward",
 59			"prevAddr", prev.Address().String(),
 60			"prevRealm", prev.PkgPath(),
 61			"from", from.String(),
 62			"to", caller.String(),
 63			"emissionRewardAmount", utils.FormatInt(emissionReward),
 64		)
 65	}
 66
 67	// Transfer protocol fee rewards for each token type
 68	for tokenPath, amount := range protocolFeeRewards {
 69		if amount > 0 {
 70			err := transferToken(0, rlm, tokenPath, from, caller, amount)
 71			if err != nil {
 72				panic(err)
 73			}
 74
 75			chain.Emit(
 76				"CollectProtocolFeeReward",
 77				"prevAddr", prev.Address().String(),
 78				"prevRealm", prev.PkgPath(),
 79				"tokenPath", tokenPath,
 80				"from", from.String(),
 81				"to", caller.String(),
 82				"collectedAmount", utils.FormatInt(amount),
 83			)
 84		}
 85	}
 86}
 87
 88// CollectEmissionReward collects accumulated GNS emission rewards only.
 89//
 90// Parameters:
 91//   - _: Noncrossing implementation-call discriminator; pass 0.
 92//   - rlm: Current realm context forwarded by the governance-staker proxy; validated before settlement and transfer.
 93func (gs *govStakerV1) CollectEmissionReward(_ int, rlm realm) {
 94	access.AssertIsRlmCurrent(0, rlm)
 95
 96	halt.AssertIsNotHaltedWithdraw()
 97
 98	prev := rlm.Previous()
 99	caller := prev.Address()
100	from := rlm.Address()
101	currentTimestamp := time.Now().Unix()
102
103	emissionReward, err := gs.claimRewardsEmissionReward(0, rlm, caller.String(), currentTimestamp)
104	if err != nil {
105		panic(err)
106	}
107
108	if emissionReward > 0 {
109		gns.Transfer(cross(rlm), caller, emissionReward)
110
111		chain.Emit(
112			"CollectEmissionReward",
113			"prevAddr", prev.Address().String(),
114			"prevRealm", prev.PkgPath(),
115			"from", from.String(),
116			"to", caller.String(),
117			"emissionRewardAmount", utils.FormatInt(emissionReward),
118		)
119	}
120}
121
122// CollectProtocolFeeReward collects accumulated protocol fee rewards for the provided token path.
123//
124// Parameters:
125//   - _: Noncrossing implementation-call discriminator; pass 0.
126//   - rlm: Current realm context forwarded by the governance-staker proxy; validated before settlement and transfer.
127//   - tokenPath: registered fee-token path whose reward should be collected
128func (gs *govStakerV1) CollectProtocolFeeReward(_ int, rlm realm, tokenPath string) {
129	access.AssertIsRlmCurrent(0, rlm)
130
131	halt.AssertIsNotHaltedWithdraw()
132
133	prev := rlm.Previous()
134	caller := prev.Address()
135	from := rlm.Address()
136	currentTimestamp := time.Now().Unix()
137
138	amount, err := gs.claimRewardProtocolFeeRewardByTokenPath(0, rlm, caller.String(), tokenPath, currentTimestamp)
139	if err != nil {
140		panic(err)
141	}
142
143	if amount > 0 {
144		err := transferToken(0, rlm, tokenPath, from, caller, amount)
145		if err != nil {
146			panic(err)
147		}
148
149		chain.Emit(
150			"CollectProtocolFeeReward",
151			"prevAddr", prev.Address().String(),
152			"prevRealm", prev.PkgPath(),
153			"tokenPath", tokenPath,
154			"from", from.String(),
155			"to", caller.String(),
156			"collectedAmount", utils.FormatInt(amount),
157		)
158	}
159}
160
161// CollectRewardFromLaunchPad collects rewards for launchpad project wallets.
162//
163// Parameters:
164//   - _: Noncrossing implementation-call discriminator; pass 0.
165//   - rlm: Current realm context forwarded by the governance-staker proxy; validated before launchpad reward settlement.
166//   - to: project wallet address receiving its emission and protocol-fee rewards
167//
168// Only callable by launchpad contract.
169func (gs *govStakerV1) CollectRewardFromLaunchPad(_ int, rlm realm, to address) {
170	access.AssertIsRlmCurrent(0, rlm)
171
172	halt.AssertIsNotHaltedWithdraw()
173
174	prev := rlm.Previous()
175	caller := prev.Address()
176	access.AssertIsLaunchpad(caller)
177
178	from := rlm.Address()
179	currentTimestamp := time.Now().Unix()
180
181	launchpadRewardID := gs.makeLaunchpadRewardID(to.String())
182	_, exists := gs.getLaunchpadProjectDeposit(launchpadRewardID)
183	if !exists {
184		panic(makeErrorWithDetails(
185			errNoDelegatedAmount,
186			ufmt.Sprintf("%s is not project wallet from launchpad", to.String()),
187		))
188	}
189
190	emissionReward, protocolFeeRewards, err := gs.claimRewardsFromLaunchpad(0, rlm, to.String(), currentTimestamp)
191	if err != nil {
192		panic(err)
193	}
194
195	// Transfer emission rewards (GNS tokens) to project wallet if any
196	if emissionReward > 0 {
197		gns.Transfer(cross(rlm), to, emissionReward)
198
199		chain.Emit(
200			"CollectEmissionFromLaunchPad",
201			"prevAddr", prev.Address().String(),
202			"prevRealm", prev.PkgPath(),
203			"from", from.String(),
204			"to", to.String(),
205			"emissionRewardAmount", utils.FormatInt(emissionReward),
206		)
207	}
208
209	// Transfer protocol fee rewards to project wallet for each token type
210	for tokenPath, amount := range protocolFeeRewards {
211		if amount > 0 {
212			err := transferToken(0, rlm, tokenPath, from, to, amount)
213			if err != nil {
214				panic(err)
215			}
216
217			chain.Emit(
218				"CollectProtocolFeeFromLaunchPad",
219				"prevAddr", prev.Address().String(),
220				"prevRealm", prev.PkgPath(),
221				"tokenPath", tokenPath,
222				"from", from.String(),
223				"to", to.String(),
224				"collectedAmount", utils.FormatInt(amount),
225			)
226		}
227	}
228}
229
230// CollectEmissionRewardFromLaunchPad collects emission rewards only for launchpad project wallets.
231//
232// Parameters:
233//   - _: Noncrossing implementation-call discriminator; pass 0.
234//   - rlm: Current realm context forwarded by the governance-staker proxy; validated before launchpad reward settlement.
235//   - to: project wallet address receiving its emission reward
236func (gs *govStakerV1) CollectEmissionRewardFromLaunchPad(_ int, rlm realm, to address) {
237	access.AssertIsRlmCurrent(0, rlm)
238
239	halt.AssertIsNotHaltedWithdraw()
240
241	prev := rlm.Previous()
242	caller := prev.Address()
243	access.AssertIsLaunchpad(caller)
244
245	from := rlm.Address()
246	currentTimestamp := time.Now().Unix()
247
248	launchpadRewardID := gs.makeLaunchpadRewardID(to.String())
249	_, exists := gs.getLaunchpadProjectDeposit(launchpadRewardID)
250	if !exists {
251		panic(makeErrorWithDetails(
252			errNoDelegatedAmount,
253			ufmt.Sprintf("%s is not project wallet from launchpad", to.String()),
254		))
255	}
256
257	emissionReward, err := gs.claimRewardsEmissionReward(0, rlm, launchpadRewardID, currentTimestamp)
258	if err != nil {
259		panic(err)
260	}
261
262	if emissionReward > 0 {
263		gns.Transfer(cross(rlm), to, emissionReward)
264
265		chain.Emit(
266			"CollectEmissionFromLaunchPad",
267			"prevAddr", prev.Address().String(),
268			"prevRealm", prev.PkgPath(),
269			"from", from.String(),
270			"to", to.String(),
271			"emissionRewardAmount", utils.FormatInt(emissionReward),
272		)
273	}
274}
275
276// CollectProtocolFeeRewardFromLaunchPad collects one token path of protocol fee rewards for launchpad project wallets.
277//
278// Parameters:
279//   - _: Noncrossing implementation-call discriminator; pass 0.
280//   - rlm: Current realm context forwarded by the governance-staker proxy; validated before launchpad reward settlement.
281//   - to: project wallet address receiving the protocol-fee reward
282//   - tokenPath: registered fee-token path whose reward should be collected
283func (gs *govStakerV1) CollectProtocolFeeRewardFromLaunchPad(_ int, rlm realm, to address, tokenPath string) {
284	access.AssertIsRlmCurrent(0, rlm)
285
286	halt.AssertIsNotHaltedWithdraw()
287
288	prev := rlm.Previous()
289	caller := prev.Address()
290	access.AssertIsLaunchpad(caller)
291
292	from := rlm.Address()
293	currentTimestamp := time.Now().Unix()
294
295	launchpadRewardID := gs.makeLaunchpadRewardID(to.String())
296	_, exists := gs.getLaunchpadProjectDeposit(launchpadRewardID)
297	if !exists {
298		panic(makeErrorWithDetails(
299			errNoDelegatedAmount,
300			ufmt.Sprintf("%s is not project wallet from launchpad", to.String()),
301		))
302	}
303
304	amount, err := gs.claimRewardProtocolFeeRewardByTokenPath(0, rlm, launchpadRewardID, tokenPath, currentTimestamp)
305	if err != nil {
306		panic(err)
307	}
308
309	if amount > 0 {
310		err := transferToken(0, rlm, tokenPath, from, to, amount)
311		if err != nil {
312			panic(err)
313		}
314
315		chain.Emit(
316			"CollectProtocolFeeFromLaunchPad",
317			"prevAddr", prev.Address().String(),
318			"prevRealm", prev.PkgPath(),
319			"tokenPath", tokenPath,
320			"from", from.String(),
321			"to", to.String(),
322			"collectedAmount", utils.FormatInt(amount),
323		)
324	}
325}
326
327// SetAmountByProjectWallet sets the amount of reward stake for the project wallet.
328// This function is exclusively callable by the launchpad contract to manage
329// xGNS balances for project wallets that participate in launchpad offerings.
330//
331// The function handles both adding and removing stakes:
332// - When adding: mints xGNS to launchpad address and starts reward accumulation
333// - When removing: burns xGNS from launchpad address and stops reward accumulation
334// Adjusts stake amount for project wallet address.
335//
336// Parameters:
337//   - _: Noncrossing implementation-call discriminator; pass 0.
338//   - rlm: Current realm context forwarded by the governance-staker proxy; validated before launchpad authorization and staking changes.
339//   - addr: project wallet address whose launchpad-backed stake should be adjusted
340//   - amount: amount of stake to add or remove
341//   - add: true to add stake and mint xGNS to launchpad, false to remove stake and burn xGNS from launchpad
342//
343// Panics:
344//   - if caller is not the launchpad contract
345//   - if system is halted for withdrawals
346//   - if access control operations fail
347func (gs *govStakerV1) SetAmountByProjectWallet(_ int, rlm realm, addr address, amount int64, add bool) {
348	access.AssertIsRlmCurrent(0, rlm)
349
350	if add {
351		halt.AssertIsNotHaltedGovStaker()
352	} else {
353		halt.AssertIsNotHaltedWithdraw()
354	}
355
356	prev := rlm.Previous()
357	caller := prev.Address()
358	currentTimestamp := time.Now().Unix()
359
360	access.AssertIsLaunchpad(caller)
361
362	launchpadAddr := access.MustGetAddress(prbac.ROLE_LAUNCHPAD.String())
363
364	if add {
365		// Add stake for the project wallet and mint xGNS to launchpad
366		err := gs.addStakeFromLaunchpad(0, rlm, addr.String(), amount, currentTimestamp)
367		if err != nil {
368			panic(err)
369		}
370
371		xgns.Mint(cross(rlm), launchpadAddr, amount)
372	} else {
373		// Remove stake for the project wallet and burn xGNS from launchpad
374		err := gs.removeStakeFromLaunchpad(0, rlm, addr.String(), amount, currentTimestamp)
375		if err != nil {
376			panic(err)
377		}
378
379		xgns.Burn(cross(rlm), launchpadAddr, amount)
380	}
381}
382
383// claimRewards claims both emission and protocol fee rewards.
384// Coordinates claiming process for both reward types.
385func (gs *govStakerV1) claimRewards(_ int, rlm realm, rewardID string, currentTimestamp int64) (int64, map[string]int64, error) {
386	emissionReward, err := gs.claimRewardsEmissionReward(0, rlm, rewardID, currentTimestamp)
387	if err != nil {
388		return 0, nil, err
389	}
390
391	protocolFeeRewards, err := gs.claimRewardsProtocolFeeReward(0, rlm, rewardID, currentTimestamp)
392	if err != nil {
393		return 0, nil, err
394	}
395
396	return emissionReward, protocolFeeRewards, nil
397}
398
399// claimRewardsFromLaunchpad claims rewards for launchpad project wallets.
400// Uses special reward ID format for launchpad integration.
401func (gs *govStakerV1) claimRewardsFromLaunchpad(_ int, rlm realm, address string, currentTimestamp int64) (int64, map[string]int64, error) {
402	launchpadRewardID := gs.makeLaunchpadRewardID(address)
403
404	return gs.claimRewards(0, rlm, launchpadRewardID, currentTimestamp)
405}
406
407// transferToken transfers tokens from the staker contract to a recipient address.
408// transferToken handles token transfers for reward distribution.
409//
410// Non-crossing helper: takes `_ int, rlm realm` so the realm token threaded
411// in by the public reward-collection entry points reaches the underlying
412// GRC-20 transfer without forcing each caller to recompute it.
413func transferToken(
414	_ int,
415	rlm realm,
416	tokenPath string,
417	from, to address,
418	amount int64,
419) error {
420	common.MustRegistered(tokenPath)
421
422	// Validate recipient address
423	if !to.IsValid() {
424		return makeErrorWithDetails(
425			errInvalidAddress,
426			ufmt.Sprintf("invalid address %s to transfer protocol fee", to.String()),
427		)
428	}
429
430	// Validate transfer amount
431	if amount < 0 {
432		return makeErrorWithDetails(
433			errInvalidAmount,
434			ufmt.Sprintf("invalid amount %d to transfer protocol fee", amount),
435		)
436	}
437
438	// Check sufficient balance
439	balance := common.BalanceOf(tokenPath, from)
440	if balance < amount {
441		return makeErrorWithDetails(
442			errNotEnoughBalance,
443			ufmt.Sprintf("not enough %s balance(%d) to collect(%d)", tokenPath, balance, amount),
444		)
445	}
446
447	common.SafeGRC20Transfer(0, rlm, tokenPath, to, amount)
448
449	return nil
450}