protocol_fee.gno
6.42 Kb · 234 lines
1package protocol_fee
2
3import (
4 "chain"
5 "errors"
6 "strconv"
7
8 ufmt "gno.land/p/nt/ufmt/v0"
9
10 gnsmath "gno.land/p/gnoswap/gnsmath"
11 prabc "gno.land/p/gnoswap/rbac"
12 "gno.land/r/gnoswap/access"
13 "gno.land/r/gnoswap/common"
14 "gno.land/r/gnoswap/halt"
15)
16
17// DistributeProtocolFee distributes collected protocol fees.
18//
19// Splits fees between devOps and gov/staker based on configured percentages.
20// This function processes all accumulated fees since last distribution.
21//
22// Only callable by admin or gov/staker contract.
23// Note: Default split is 0% devOps, 100% gov/staker.
24func (pf *protocolFeeV1) DistributeProtocolFee(_ int, rlm realm) {
25 if !rlm.IsCurrent() {
26 panic(errors.New(errSpoofedRealm))
27 }
28
29 prev := rlm.Previous()
30 assertIsAdminOrGovStaker(prev.Address())
31
32 if halt.IsHaltedWithdraw() {
33 return
34 }
35
36 protocolFeeAddr := access.MustGetAddress(prabc.ROLE_PROTOCOL_FEE.String())
37 pfs := pf.getProtocolFeeState()
38
39 reservedTokens := pfs.ReservedTokens()
40
41 for _, token := range reservedTokens {
42 toDevOpsAmount := gnsmath.SafeSubInt64(
43 pfs.GetAccuTransferToDevOpsByTokenPath(token),
44 pfs.GetActualDistributedToDevOpsByTokenPath(token),
45 )
46 toGovStakerAmount := gnsmath.SafeSubInt64(
47 pfs.GetAccuTransferToGovStakerByTokenPath(token),
48 pfs.GetActualDistributedToGovStakerByTokenPath(token),
49 )
50
51 amount := gnsmath.SafeAddInt64(toDevOpsAmount, toGovStakerAmount)
52 balance := common.BalanceOf(token, protocolFeeAddr)
53
54 // amount should be less than or equal to balance
55 if amount > balance {
56 panic(makeErrorWithDetail(
57 errInvalidAmount,
58 ufmt.Sprintf("amount: %d should be less than or equal to balance: %d", amount, balance),
59 ))
60 }
61
62 if amount <= 0 {
63 continue
64 }
65
66 // Distribute to DevOps
67 if err := pfs.distributeToDevOps(0, rlm, token, toDevOpsAmount); err != nil {
68 panic(err)
69 }
70
71 // Distribute to Gov/Staker
72 if err := pfs.distributeToGovStaker(0, rlm, token, toGovStakerAmount); err != nil {
73 panic(err)
74 }
75 }
76 if err := pfs.clearReservedTokens(0, rlm); err != nil {
77 panic(err)
78 }
79
80 chain.Emit(
81 "TransferProtocolFee",
82 "prevAddr", prev.Address().String(),
83 "prevRealm", prev.PkgPath(),
84 )
85}
86
87// DistributeProtocolFeeByTokenPath is retained for ABI compatibility while token-path distribution is deferred.
88func (pf *protocolFeeV1) DistributeProtocolFeeByTokenPath(_ int, rlm realm, tokenPath string) {
89 assertNotImplementYet()
90}
91
92// SetDevOpsPct sets the devOpsPct.
93//
94// Parameters:
95// - pct: percentage for devOps (0-10000, where 10000 = 100%)
96//
97// Only callable by admin or governance.
98// Note: GovStaker percentage is automatically adjusted to (10000 - devOpsPct).
99func (pf *protocolFeeV1) SetDevOpsPct(_ int, rlm realm, pct int64) {
100 if !rlm.IsCurrent() {
101 panic(errors.New(errSpoofedRealm))
102 }
103
104 halt.AssertIsNotHaltedProtocolFee()
105
106 prev := rlm.Previous()
107 access.AssertIsAdminOrGovernance(prev.Address())
108
109 assertIsValidPercent(pct)
110
111 prevDevOpsPct := pf.getProtocolFeeState().DevOpsPct()
112 prevGovStakerPct := pf.getProtocolFeeState().GovStakerPct()
113
114 newDevOpsPct, err := pf.getProtocolFeeState().setDevOpsPct(0, rlm, pct)
115 if err != nil {
116 panic(err)
117 }
118 newGovStakerPct := pf.getProtocolFeeState().GovStakerPct()
119
120 chain.Emit(
121 "SetDevOpsPct",
122 "prevAddr", prev.Address().String(),
123 "prevRealm", prev.PkgPath(),
124 "newDevOpsPct", strconv.FormatInt(newDevOpsPct, 10),
125 "prevDevOpsPct", strconv.FormatInt(prevDevOpsPct, 10),
126 "newGovStakerPct", strconv.FormatInt(newGovStakerPct, 10),
127 "prevGovStakerPct", strconv.FormatInt(prevGovStakerPct, 10),
128 )
129}
130
131// SetGovStakerPct sets the stakerPct.
132//
133// Parameters:
134// - pct: percentage for gov/staker (0-10000, where 10000 = 100%)
135//
136// Only callable by admin or governance.
137// Note: DevOps percentage is automatically adjusted to (10000 - govStakerPct).
138func (pf *protocolFeeV1) SetGovStakerPct(_ int, rlm realm, pct int64) {
139 if !rlm.IsCurrent() {
140 panic(errors.New(errSpoofedRealm))
141 }
142
143 halt.AssertIsNotHaltedProtocolFee()
144
145 prev := rlm.Previous()
146 access.AssertIsAdminOrGovernance(prev.Address())
147
148 assertIsValidPercent(pct)
149
150 prevDevOpsPct := pf.getProtocolFeeState().DevOpsPct()
151 prevGovStakerPct := pf.getProtocolFeeState().GovStakerPct()
152
153 newGovStakerPct, err := pf.getProtocolFeeState().setGovStakerPct(0, rlm, pct)
154 if err != nil {
155 panic(err)
156 }
157 newDevOpsPct := pf.getProtocolFeeState().DevOpsPct()
158
159 chain.Emit(
160 "SetGovStakerPct",
161 "prevAddr", prev.Address().String(),
162 "prevRealm", prev.PkgPath(),
163 "newDevOpsPct", strconv.FormatInt(newDevOpsPct, 10),
164 "prevDevOpsPct", strconv.FormatInt(prevDevOpsPct, 10),
165 "newGovStakerPct", strconv.FormatInt(newGovStakerPct, 10),
166 "prevGovStakerPct", strconv.FormatInt(prevGovStakerPct, 10),
167 )
168}
169
170// AddToProtocolFee pulls the approved amount into protocol fee accounting.
171//
172// Parameters:
173// - tokenPath: token contract path
174// - amount: fee amount to add
175//
176// Only callable by pool, router or staker contracts.
177// Caller must approve the protocol fee realm for at least amount before calling.
178// Note: Accumulated fees are distributed when DistributeProtocolFee is called.
179func (pf *protocolFeeV1) AddToProtocolFee(_ int, rlm realm, tokenPath string, amount int64) error {
180 if !rlm.IsCurrent() {
181 return errors.New(errSpoofedRealm)
182 }
183
184 if halt.IsHaltedProtocolFee() {
185 return errors.New(errProtocolFeeHalted)
186 }
187
188 prev := rlm.Previous()
189 caller := prev.Address()
190 assertIsPoolOrPositionOrRouterOrStaker(caller)
191
192 if amount < 0 {
193 panic(makeErrorWithDetail(
194 errInvalidAmount,
195 ufmt.Sprintf("amount(%d) should not be negative", amount),
196 ))
197 }
198
199 if amount == 0 {
200 return nil
201 }
202
203 pf.reserveCollectedProtocolFee(0, rlm, tokenPath, amount)
204 protocolFeeAddr := access.MustGetAddress(prabc.ROLE_PROTOCOL_FEE.String())
205 common.SafeGRC20TransferFrom(cross(rlm), tokenPath, caller, protocolFeeAddr, amount)
206
207 chain.Emit(
208 "AddToProtocolFee",
209 "prevAddr", caller.String(),
210 "prevRealm", prev.PkgPath(),
211 "tokenPath", tokenPath,
212 "amount", strconv.FormatInt(amount, 10),
213 )
214
215 return nil
216}
217
218func (pf *protocolFeeV1) reserveCollectedProtocolFee(_ int, rlm realm, tokenPath string, amount int64) {
219 pfs := pf.getProtocolFeeState()
220
221 toDevOpsAmount := gnsmath.SafeMulDivInt64(amount, pfs.DevOpsPct(), 10000)
222 toGovStakerAmount := gnsmath.SafeSubInt64(amount, toDevOpsAmount)
223
224 if err := pfs.addAccuToDevOps(0, rlm, tokenPath, toDevOpsAmount); err != nil {
225 panic(err)
226 }
227 if err := pfs.addAccuToGovStaker(0, rlm, tokenPath, toGovStakerAmount); err != nil {
228 panic(err)
229 }
230
231 if err := pfs.store.AddReservedToken(0, rlm, tokenPath); err != nil {
232 panic(err)
233 }
234}