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

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}