protocol_fee.gno
7.01 Kb ยท 237 lines
1package v1
2
3import (
4 "chain"
5 "chain/runtime"
6 "strconv"
7 "strings"
8
9 "gno.land/p/nt/ufmt"
10
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// Returns:
23// - map[string]int64: Token paths to amounts distributed to gov/staker
24//
25// Only callable by admin or gov/staker contract.
26// Note: Default split is 0% devOps, 100% gov/staker.
27func (pf *protocolFeeV1) DistributeProtocolFee() map[string]int64 {
28 halt.AssertIsNotHaltedProtocolFee()
29
30 caller := runtime.PreviousRealm().Address()
31 assertIsAdminOrGovStaker(caller)
32
33 protocolFeeAddr := access.MustGetAddress(prabc.ROLE_PROTOCOL_FEE.String())
34
35 sentToDevOpsForEvent := make([]string, 0)
36 sentToGovStakerForEvent := make([]string, 0)
37 toReturnDistributedToGovStaker := make(map[string]int64)
38
39 for token, amount := range pf.store.GetTokenListWithAmounts() {
40 balance := common.BalanceOf(token, protocolFeeAddr)
41
42 // amount should be less than or equal to balance
43 if amount > balance {
44 panic(makeErrorWithDetail(
45 errInvalidAmount,
46 ufmt.Sprintf("amount: %d should be less than or equal to balance: %d", amount, balance),
47 ))
48 }
49
50 if amount <= 0 {
51 continue
52 }
53
54 // Distribute only the recorded amount, not the entire balance
55 distributeAmount := amount
56 if distributeAmount > balance {
57 // This should not happen due to the check above, but safeguard anyway
58 distributeAmount = balance
59 }
60
61 devOpsPct := pf.getProtocolFeeState().DevOpsPct()
62 toDevOpsAmount := safeMulDiv(distributeAmount, devOpsPct, 10000) // default 0%
63 toGovStakerAmount := safeSubInt64(distributeAmount, toDevOpsAmount) // default 100%
64
65 // Distribute to DevOps
66 if err := pf.getProtocolFeeState().distributeToDevOps(token, toDevOpsAmount); err != nil {
67 panic(err)
68 }
69 if toDevOpsAmount > 0 {
70 sentToDevOpsForEvent = append(sentToDevOpsForEvent, makeEventString(token, toDevOpsAmount))
71 }
72
73 // Distribute to Gov/Staker
74 if err := pf.getProtocolFeeState().distributeToGovStaker(token, toGovStakerAmount); err != nil {
75 panic(err)
76 }
77 if toGovStakerAmount > 0 {
78 sentToGovStakerForEvent = append(sentToGovStakerForEvent, makeEventString(token, toGovStakerAmount))
79 toReturnDistributedToGovStaker[token] = toGovStakerAmount
80 }
81 }
82
83 if err := pf.getProtocolFeeState().clearTokenListWithAmount(); err != nil {
84 panic(err)
85 }
86
87 previousRealm := runtime.PreviousRealm()
88 chain.Emit(
89 "TransferProtocolFee",
90 "prevAddr", previousRealm.Address().String(),
91 "prevRealm", previousRealm.PkgPath(),
92 "toDevOps", strings.Join(sentToDevOpsForEvent, ","),
93 "toGovStaker", strings.Join(sentToGovStakerForEvent, ","),
94 )
95
96 return toReturnDistributedToGovStaker
97}
98
99// SetDevOpsPct sets the devOpsPct.
100//
101// Parameters:
102// - pct: percentage for devOps (0-10000, where 10000 = 100%)
103//
104// Only callable by admin or governance.
105// Note: GovStaker percentage is automatically adjusted to (10000 - devOpsPct).
106func (pf *protocolFeeV1) SetDevOpsPct(pct int64) {
107 halt.AssertIsNotHaltedProtocolFee()
108
109 caller := runtime.PreviousRealm().Address()
110 access.AssertIsAdminOrGovernance(caller)
111
112 assertIsValidPercent(pct)
113
114 prevDevOpsPct := pf.getProtocolFeeState().DevOpsPct()
115 prevGovStakerPct := pf.getProtocolFeeState().GovStakerPct()
116
117 newDevOpsPct, err := pf.getProtocolFeeState().setDevOpsPct(pct)
118 if err != nil {
119 panic(err)
120 }
121 newGovStakerPct := pf.getProtocolFeeState().GovStakerPct()
122
123 previousRealm := runtime.PreviousRealm()
124 chain.Emit(
125 "SetDevOpsPct",
126 "prevAddr", previousRealm.Address().String(),
127 "prevRealm", previousRealm.PkgPath(),
128 "newDevOpsPct", strconv.FormatInt(newDevOpsPct, 10),
129 "prevDevOpsPct", strconv.FormatInt(prevDevOpsPct, 10),
130 "newGovStakerPct", strconv.FormatInt(newGovStakerPct, 10),
131 "prevGovStakerPct", strconv.FormatInt(prevGovStakerPct, 10),
132 )
133}
134
135// SetGovStakerPct sets the stakerPct.
136//
137// Parameters:
138// - pct: percentage for gov/staker (0-10000, where 10000 = 100%)
139//
140// Only callable by admin or governance.
141// Note: DevOps percentage is automatically adjusted to (10000 - govStakerPct).
142func (pf *protocolFeeV1) SetGovStakerPct(pct int64) {
143 halt.AssertIsNotHaltedProtocolFee()
144
145 caller := runtime.PreviousRealm().Address()
146 access.AssertIsAdminOrGovernance(caller)
147
148 assertIsValidPercent(pct)
149
150 prevDevOpsPct := pf.getProtocolFeeState().DevOpsPct()
151 prevGovStakerPct := pf.getProtocolFeeState().GovStakerPct()
152
153 newGovStakerPct, err := pf.getProtocolFeeState().setGovStakerPct(pct)
154 if err != nil {
155 panic(err)
156 }
157 newDevOpsPct := pf.getProtocolFeeState().DevOpsPct()
158
159 previousRealm := runtime.PreviousRealm()
160 chain.Emit(
161 "SetGovStakerPct",
162 "prevAddr", previousRealm.Address().String(),
163 "prevRealm", previousRealm.PkgPath(),
164 "newDevOpsPct", strconv.FormatInt(newDevOpsPct, 10),
165 "prevDevOpsPct", strconv.FormatInt(prevDevOpsPct, 10),
166 "newGovStakerPct", strconv.FormatInt(newGovStakerPct, 10),
167 "prevGovStakerPct", strconv.FormatInt(prevGovStakerPct, 10),
168 )
169}
170
171// AddToProtocolFee adds the amount to the tokenListWithAmount.
172//
173// Parameters:
174// - tokenPath: token contract path
175// - amount: fee amount to add
176//
177// Only callable by pool, router or staker contracts.
178// Note: Accumulated fees are distributed when DistributeProtocolFee is called.
179func (pf *protocolFeeV1) AddToProtocolFee(tokenPath string, amount int64) {
180 halt.AssertIsNotHaltedProtocolFee()
181
182 caller := runtime.PreviousRealm().Address()
183 assertIsPoolOrPositionOrRouterOrStaker(caller)
184
185 if amount < 0 {
186 panic(makeErrorWithDetail(
187 errInvalidAmount,
188 ufmt.Sprintf("amount(%d) should not be negative", amount),
189 ))
190 }
191
192 currentAmount, exists := pf.store.GetTokenListWithAmountItem(tokenPath)
193 if !exists {
194 currentAmount = 0
195 }
196
197 // Check for overflow
198 addedAmount := safeAddInt64(currentAmount, amount)
199
200 pf.store.SetTokenListWithAmountItem(tokenPath, addedAmount)
201}
202
203// ClearTokenListWithAmount clears the tokenListWithAmount.
204//
205// Resets all accumulated token amounts to zero.
206// Only callable by gov/staker contract.
207// Note: Should be called after successful distribution.
208func (pf *protocolFeeV1) ClearTokenListWithAmount() {
209 halt.AssertIsNotHaltedProtocolFee()
210
211 caller := runtime.PreviousRealm().Address()
212 access.AssertIsGovStaker(caller)
213
214 if err := pf.getProtocolFeeState().clearTokenListWithAmount(); err != nil {
215 panic(err)
216 }
217}
218
219// ClearAccuTransferToGovStaker clears the accuToGovStaker.
220//
221// Resets accumulated transfer tracking for gov/staker.
222// This allows gov/staker to track distributions between calls.
223//
224// Only callable by gov/staker contract.
225// Note: Should be called after reading accumulated amounts.
226func (pf *protocolFeeV1) ClearAccuTransferToGovStaker() {
227 halt.AssertIsNotHaltedProtocolFee()
228
229 caller := runtime.PreviousRealm().Address()
230 access.AssertIsGovStaker(caller)
231
232 pf.store.InitializeAccuToGovStaker()
233}
234
235func makeEventString(tokenPath string, amount int64) string {
236 return tokenPath + "*FEE*" + strconv.FormatInt(amount, 10)
237}