protocol_fee.gno
7.53 Kb · 283 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 pf.distributeProtocolFeeForToken(
43 0,
44 rlm,
45 pfs,
46 protocolFeeAddr,
47 token,
48 )
49 }
50}
51
52// DistributeProtocolFeeByTokenPath distributes collected protocol fees for one token path.
53func (pf *protocolFeeV1) DistributeProtocolFeeByTokenPath(_ int, rlm realm, tokenPath string) {
54 if !rlm.IsCurrent() {
55 panic(errors.New(errSpoofedRealm))
56 }
57
58 prev := rlm.Previous()
59 assertIsAdminOrGovStaker(prev.Address())
60
61 if halt.IsHaltedWithdraw() {
62 return
63 }
64
65 protocolFeeAddr := access.MustGetAddress(prabc.ROLE_PROTOCOL_FEE.String())
66 pfs := pf.getProtocolFeeState()
67
68 if !containsString(pfs.ReservedTokens(), tokenPath) {
69 return
70 }
71
72 pf.distributeProtocolFeeForToken(
73 0,
74 rlm,
75 pfs,
76 protocolFeeAddr,
77 tokenPath,
78 )
79}
80
81// distributeProtocolFeeForToken validates and distributes one reserved token's pending fees.
82func (pf *protocolFeeV1) distributeProtocolFeeForToken(_ int, rlm realm, pfs *protocolFeeState, protocolFeeAddr address, tokenPath string) {
83 toDevOpsAmount := gnsmath.SafeSubInt64(
84 pfs.GetAccuTransferToDevOpsByTokenPath(tokenPath),
85 pfs.GetActualDistributedToDevOpsByTokenPath(tokenPath),
86 )
87 toGovStakerAmount := gnsmath.SafeSubInt64(
88 pfs.GetAccuTransferToGovStakerByTokenPath(tokenPath),
89 pfs.GetActualDistributedToGovStakerByTokenPath(tokenPath),
90 )
91
92 amount := gnsmath.SafeAddInt64(toDevOpsAmount, toGovStakerAmount)
93 balance := common.BalanceOf(tokenPath, protocolFeeAddr)
94
95 // amount should be less than or equal to balance
96 if amount > balance {
97 panic(makeErrorWithDetail(
98 errInvalidAmount,
99 ufmt.Sprintf("amount: %d should be less than or equal to balance: %d", amount, balance),
100 ))
101 }
102
103 if err := pfs.removeReservedToken(0, rlm, tokenPath); err != nil {
104 panic(err)
105 }
106
107 if amount <= 0 {
108 return
109 }
110
111 // distributeToDevOps and distributeToGovStaker record history before transferring.
112 if err := pfs.distributeToDevOps(0, rlm, tokenPath, toDevOpsAmount); err != nil {
113 panic(err)
114 }
115 if err := pfs.distributeToGovStaker(0, rlm, tokenPath, toGovStakerAmount); err != nil {
116 panic(err)
117 }
118
119 prev := rlm.Previous()
120
121 chain.Emit(
122 "TransferProtocolFee",
123 "prevAddr", prev.Address().String(),
124 "prevRealm", prev.PkgPath(),
125 "tokenPath", tokenPath,
126 "toDevOpsAmount", strconv.FormatInt(toDevOpsAmount, 10),
127 "toGovStakerAmount", strconv.FormatInt(toGovStakerAmount, 10),
128 "amount", strconv.FormatInt(amount, 10),
129 )
130}
131
132func containsString(values []string, target string) bool {
133 for _, value := range values {
134 if value == target {
135 return true
136 }
137 }
138 return false
139}
140
141// SetDevOpsPct sets the devOpsPct.
142//
143// Parameters:
144// - pct: percentage for devOps (0-10000, where 10000 = 100%)
145//
146// Only callable by admin or governance.
147// Note: GovStaker percentage is automatically adjusted to (10000 - devOpsPct).
148func (pf *protocolFeeV1) SetDevOpsPct(_ int, rlm realm, pct int64) {
149 if !rlm.IsCurrent() {
150 panic(errors.New(errSpoofedRealm))
151 }
152
153 halt.AssertIsNotHaltedProtocolFee()
154
155 prev := rlm.Previous()
156 access.AssertIsAdminOrGovernance(prev.Address())
157
158 assertIsValidPercent(pct)
159
160 prevDevOpsPct := pf.getProtocolFeeState().DevOpsPct()
161 prevGovStakerPct := pf.getProtocolFeeState().GovStakerPct()
162
163 newDevOpsPct, err := pf.getProtocolFeeState().setDevOpsPct(0, rlm, pct)
164 if err != nil {
165 panic(err)
166 }
167 newGovStakerPct := pf.getProtocolFeeState().GovStakerPct()
168
169 chain.Emit(
170 "SetDevOpsPct",
171 "prevAddr", prev.Address().String(),
172 "prevRealm", prev.PkgPath(),
173 "newDevOpsPct", strconv.FormatInt(newDevOpsPct, 10),
174 "prevDevOpsPct", strconv.FormatInt(prevDevOpsPct, 10),
175 "newGovStakerPct", strconv.FormatInt(newGovStakerPct, 10),
176 "prevGovStakerPct", strconv.FormatInt(prevGovStakerPct, 10),
177 )
178}
179
180// SetGovStakerPct sets the stakerPct.
181//
182// Parameters:
183// - pct: percentage for gov/staker (0-10000, where 10000 = 100%)
184//
185// Only callable by admin or governance.
186// Note: DevOps percentage is automatically adjusted to (10000 - govStakerPct).
187func (pf *protocolFeeV1) SetGovStakerPct(_ int, rlm realm, pct int64) {
188 if !rlm.IsCurrent() {
189 panic(errors.New(errSpoofedRealm))
190 }
191
192 halt.AssertIsNotHaltedProtocolFee()
193
194 prev := rlm.Previous()
195 access.AssertIsAdminOrGovernance(prev.Address())
196
197 assertIsValidPercent(pct)
198
199 prevDevOpsPct := pf.getProtocolFeeState().DevOpsPct()
200 prevGovStakerPct := pf.getProtocolFeeState().GovStakerPct()
201
202 newGovStakerPct, err := pf.getProtocolFeeState().setGovStakerPct(0, rlm, pct)
203 if err != nil {
204 panic(err)
205 }
206 newDevOpsPct := pf.getProtocolFeeState().DevOpsPct()
207
208 chain.Emit(
209 "SetGovStakerPct",
210 "prevAddr", prev.Address().String(),
211 "prevRealm", prev.PkgPath(),
212 "newDevOpsPct", strconv.FormatInt(newDevOpsPct, 10),
213 "prevDevOpsPct", strconv.FormatInt(prevDevOpsPct, 10),
214 "newGovStakerPct", strconv.FormatInt(newGovStakerPct, 10),
215 "prevGovStakerPct", strconv.FormatInt(prevGovStakerPct, 10),
216 )
217}
218
219// AddToProtocolFee pulls the approved amount into protocol fee accounting.
220//
221// Parameters:
222// - tokenPath: token contract path
223// - amount: fee amount to add
224//
225// Only callable by pool, router or staker contracts.
226// Caller must approve the protocol fee realm for at least amount before calling.
227// Note: Accumulated fees are distributed when DistributeProtocolFee is called.
228func (pf *protocolFeeV1) AddToProtocolFee(_ int, rlm realm, tokenPath string, amount int64) error {
229 if !rlm.IsCurrent() {
230 return errors.New(errSpoofedRealm)
231 }
232
233 if halt.IsHaltedProtocolFee() {
234 return errors.New(errProtocolFeeHalted)
235 }
236
237 prev := rlm.Previous()
238 caller := prev.Address()
239 assertIsPoolOrPositionOrRouterOrStaker(caller)
240
241 if amount < 0 {
242 panic(makeErrorWithDetail(
243 errInvalidAmount,
244 ufmt.Sprintf("amount(%d) should not be negative", amount),
245 ))
246 }
247
248 if amount == 0 {
249 return nil
250 }
251
252 pf.reserveCollectedProtocolFee(0, rlm, tokenPath, amount)
253 protocolFeeAddr := access.MustGetAddress(prabc.ROLE_PROTOCOL_FEE.String())
254 common.SafeGRC20TransferFrom(cross(rlm), tokenPath, caller, protocolFeeAddr, amount)
255
256 chain.Emit(
257 "AddToProtocolFee",
258 "prevAddr", caller.String(),
259 "prevRealm", prev.PkgPath(),
260 "tokenPath", tokenPath,
261 "amount", strconv.FormatInt(amount, 10),
262 )
263
264 return nil
265}
266
267func (pf *protocolFeeV1) reserveCollectedProtocolFee(_ int, rlm realm, tokenPath string, amount int64) {
268 pfs := pf.getProtocolFeeState()
269
270 toDevOpsAmount := gnsmath.SafeMulDivInt64(amount, pfs.DevOpsPct(), 10000)
271 toGovStakerAmount := gnsmath.SafeSubInt64(amount, toDevOpsAmount)
272
273 if err := pfs.addAccuToDevOps(0, rlm, tokenPath, toDevOpsAmount); err != nil {
274 panic(err)
275 }
276 if err := pfs.addAccuToGovStaker(0, rlm, tokenPath, toGovStakerAmount); err != nil {
277 panic(err)
278 }
279
280 if err := pfs.store.AddReservedToken(0, rlm, tokenPath); err != nil {
281 panic(err)
282 }
283}