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

protocol_fee_unstaking.gno

4.53 Kb · 149 lines
  1package staker
  2
  3import (
  4	"chain"
  5	"errors"
  6
  7	"gno.land/p/gnoswap/gnsmath/v1"
  8	prbac "gno.land/p/gnoswap/rbac/v1"
  9	"gno.land/p/gnoswap/utils/v1"
 10	"gno.land/r/gnoswap/access/v1"
 11	"gno.land/r/gnoswap/common"
 12	"gno.land/r/gnoswap/halt/v1"
 13
 14	pf "gno.land/r/gnoswap/protocol_fee"
 15)
 16
 17// GetUnstakingFee returns the current unstaking fee rate in basis points.
 18//
 19// Returns:
 20//   - uint64: Current fee rate in basis points; 100 basis points equals 1%.
 21func (s *stakerV1) GetUnstakingFee() uint64 { return s.store.GetUnstakingFee() }
 22
 23// handleStakingRewardFee calculates and applies the unstaking fee.
 24func (s *stakerV1) handleStakingRewardFee(
 25	_ int,
 26	rlm realm,
 27	tokenPath string,
 28	amount int64,
 29	internal bool,
 30	unstakingFee uint64,
 31) (int64, int64, error) {
 32	if internal {
 33		tokenPath = GNS_TOKEN_KEY
 34	}
 35
 36	if unstakingFee == 0 {
 37		s.settleProtocolFee(0, rlm, tokenPath, 0)
 38		return amount, 0, nil
 39	}
 40
 41	// Do not change the order of the operation.
 42	feeAmount := gnsmath.SafeMulDivInt64(amount, gnsmath.SafeUint64ToInt64(unstakingFee), 10000)
 43	if feeAmount < 0 {
 44		return 0, 0, errors.New("fee amount cannot be negative")
 45	}
 46
 47	if feeAmount == 0 {
 48		s.settleProtocolFee(0, rlm, tokenPath, 0)
 49		return amount, 0, nil
 50	}
 51
 52	s.settleProtocolFee(0, rlm, tokenPath, feeAmount)
 53
 54	return gnsmath.SafeSubInt64(amount, feeAmount), feeAmount, nil
 55}
 56
 57// GetPendingProtocolFees returns pending protocol fee amounts keyed by reward-token path.
 58//
 59// Returns:
 60//   - map[string]int64: Pending fee amount per token path awaiting a later settlement.
 61func (s *stakerV1) GetPendingProtocolFees() map[string]int64 {
 62	return s.store.GetPendingProtocolFees()
 63}
 64
 65func (s *stakerV1) addPendingProtocolFee(_ int, rlm realm, tokenPath string, amount int64) {
 66	if amount <= 0 {
 67		return
 68	}
 69
 70	pendingAmount := gnsmath.SafeAddInt64(s.store.GetPendingProtocolFee(tokenPath), amount)
 71	if err := s.store.SetPendingProtocolFee(0, rlm, tokenPath, pendingAmount); err != nil {
 72		panic(err)
 73	}
 74}
 75
 76// settleProtocolFee forwards amount, plus anything already pending for tokenPath, to
 77// the protocol fee realm. When collection is halted or the transfer fails, the amount
 78// is recorded as pending instead and settled by a later call for the same token, which
 79// every fee path makes even when the fee is zero.
 80//
 81// Only tokenPath is touched, so an unrelated token can never block settlement. The
 82// pending entry is written only when the fee cannot be forwarded, so the common path
 83// performs no store write at all.
 84func (s *stakerV1) settleProtocolFee(_ int, rlm realm, tokenPath string, amount int64) {
 85	if halt.IsHaltedProtocolFee() {
 86		s.addPendingProtocolFee(0, rlm, tokenPath, amount)
 87		return
 88	}
 89
 90	pendingAmount := s.store.GetPendingProtocolFee(tokenPath)
 91	totalAmount := gnsmath.SafeAddInt64(pendingAmount, amount)
 92
 93	if totalAmount > 0 {
 94		protocolFeeAddr := access.MustGetAddress(prbac.ROLE_PROTOCOL_FEE.String())
 95		common.SafeGRC20Approve(0, rlm, tokenPath, protocolFeeAddr, totalAmount)
 96
 97		// AddToProtocolFee only reports an error for a halted realm, which is handled
 98		// above. The branch keeps the amount pending should that ever change, and
 99		// withdraws the approval so no unspent allowance outlives the call.
100		if err := pf.AddToProtocolFee(cross(rlm), tokenPath, totalAmount); err != nil {
101			common.SafeGRC20Approve(0, rlm, tokenPath, protocolFeeAddr, 0)
102			s.addPendingProtocolFee(0, rlm, tokenPath, amount)
103			return
104		}
105	}
106
107	if err := s.store.RemovePendingProtocolFee(0, rlm, tokenPath); err != nil {
108		panic(err)
109	}
110}
111
112// SetUnStakingFee sets the unstaking fee rate in basis points.
113// Only admin or governance can call this function.
114//
115// Parameters:
116//   - _: Internal call discriminator; callers pass 0.
117//   - rlm: Propagated realm context; it must be current before changing the stored fee.
118//   - fee: New unstaking fee rate in basis points, validated against the configured maximum.
119func (s *stakerV1) SetUnStakingFee(_ int, rlm realm, fee uint64) {
120	access.AssertIsRlmCurrent(0, rlm)
121
122	halt.AssertIsNotHaltedStaker()
123
124	previousRealm := rlm.Previous()
125	caller := previousRealm.Address()
126	access.AssertIsAdminOrGovernance(caller)
127
128	assertIsValidFeeRate(fee)
129
130	prevUnStakingFee := s.GetUnstakingFee()
131
132	err := s.setUnStakingFee(0, rlm, fee)
133	if err != nil {
134		panic(err)
135	}
136
137	chain.Emit(
138		"SetUnStakingFee",
139		"prevAddr", caller.String(),
140		"prevRealm", previousRealm.PkgPath(),
141		"prevFee", utils.FormatUint(prevUnStakingFee),
142		"newFee", utils.FormatUint(fee),
143	)
144}
145
146// setUnStakingFee internally updates the unstaking fee.
147func (s *stakerV1) setUnStakingFee(_ int, rlm realm, fee uint64) error {
148	return s.store.SetUnstakingFee(0, rlm, fee)
149}