Search Apps Documentation Source Content File Folder Download Copy Actions Download

staker_reward.gno

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