Search Apps Documentation Source Content File Folder Download Copy Actions Download

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}