Search Apps Documentation Source Content File Folder Download Copy Actions Download

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}