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}