Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

protocol_fee.gno

6.69 Kb · 230 lines
  1package pool
  2
  3import (
  4	"chain"
  5	"errors"
  6
  7	"gno.land/p/gnoswap/gnsmath/v1"
  8	prabc "gno.land/p/gnoswap/rbac/v1"
  9	u256 "gno.land/p/gnoswap/uint256/v1"
 10	"gno.land/p/gnoswap/utils/v1"
 11	ufmt "gno.land/p/nt/ufmt/v0"
 12
 13	"gno.land/r/gnoswap/access/v1"
 14	"gno.land/r/gnoswap/common"
 15	"gno.land/r/gnoswap/halt/v1"
 16	pf "gno.land/r/gnoswap/protocol_fee"
 17)
 18
 19const (
 20	MaxBpsValue = uint64(1000) // 10%
 21	ZeroBps     = uint64(0)
 22)
 23
 24// deductWithdrawalFee splits amount into the fee withheld for the protocol and
 25// the remainder paid out to the collector.
 26//
 27// Rounding favours the collector: the fee is truncated. The two results always
 28// sum back to amount, so a caller that debits amount and pays out
 29// amountAfterFee never leaves the pool short.
 30func deductWithdrawalFee(amount int64, withdrawalFeeBPS uint64) (feeAmount, amountAfterFee int64) {
 31	if amount <= 0 || withdrawalFeeBPS == ZeroBps {
 32		return 0, amount
 33	}
 34
 35	fee, afterFee := calculateAmountWithFee(
 36		u256.NewUint(uint64(amount)),
 37		u256.NewUint(withdrawalFeeBPS),
 38	)
 39
 40	return gnsmath.SafeConvertToInt64(fee), gnsmath.SafeConvertToInt64(afterFee)
 41}
 42
 43// GetPendingProtocolFees returns the pending protocol fee amount per token path.
 44//
 45// Returns:
 46//   - map[string]int64: Pending protocol fee amounts keyed by token contract
 47//     path, with values in token base units.
 48func (i *poolV1) GetPendingProtocolFees() map[string]int64 {
 49	return i.store.GetPendingProtocolFees()
 50}
 51
 52func (i *poolV1) addPendingProtocolFee(_ int, rlm realm, tokenPath string, amount int64) {
 53	if amount <= 0 {
 54		return
 55	}
 56
 57	pendingAmount := gnsmath.SafeAddInt64(i.store.GetPendingProtocolFee(tokenPath), amount)
 58	if err := i.store.SetPendingProtocolFee(0, rlm, tokenPath, pendingAmount); err != nil {
 59		panic(err)
 60	}
 61}
 62
 63// settleProtocolFee forwards amount, plus anything already pending for tokenPath, to
 64// the protocol fee realm. When collection is halted or the transfer fails, the amount
 65// is recorded as pending instead and settled by a later call for the same token, which
 66// every fee path makes even when the fee is zero.
 67//
 68// Only tokenPath is touched, so an unrelated token can never block settlement. The
 69// pending entry is written only when the fee cannot be forwarded, so the common path
 70// performs no store write at all.
 71func (i *poolV1) settleProtocolFee(_ int, rlm realm, tokenPath string, amount int64) {
 72	if halt.IsHaltedProtocolFee() {
 73		i.addPendingProtocolFee(0, rlm, tokenPath, amount)
 74		return
 75	}
 76
 77	pendingAmount := i.store.GetPendingProtocolFee(tokenPath)
 78	totalAmount := gnsmath.SafeAddInt64(pendingAmount, amount)
 79
 80	if totalAmount > 0 {
 81		protocolFeeAddr := access.MustGetAddress(prabc.ROLE_PROTOCOL_FEE.String())
 82		common.SafeGRC20Approve(0, rlm, tokenPath, protocolFeeAddr, totalAmount)
 83
 84		// AddToProtocolFee only reports an error for a halted realm, which is handled
 85		// above. The branch keeps the amount pending should that ever change, and
 86		// withdraws the approval so no unspent allowance outlives the call.
 87		if err := pf.AddToProtocolFee(cross(rlm), tokenPath, totalAmount); err != nil {
 88			common.SafeGRC20Approve(0, rlm, tokenPath, protocolFeeAddr, 0)
 89			i.addPendingProtocolFee(0, rlm, tokenPath, amount)
 90			return
 91		}
 92	}
 93
 94	if err := i.store.RemovePendingProtocolFee(0, rlm, tokenPath); err != nil {
 95		panic(err)
 96	}
 97}
 98
 99// SetPoolCreationFee sets the poolCreationFee.
100// Only admin or governance can call this function.
101//
102// Parameters:
103//   - _: Leading integer discriminator for the forwarded realm call; callers
104//     pass 0.
105//   - rlm: Propagated realm context validated as current before the fee is
106//     written.
107//   - fee: New non-negative pool creation fee in configured native-token base
108//     units.
109func (i *poolV1) SetPoolCreationFee(_ int, rlm realm, fee int64) {
110	access.AssertIsRlmCurrent(0, rlm)
111
112	i.assertPoolUnlocked()
113	halt.AssertIsNotHaltedPool()
114
115	previousRealm := rlm.Previous()
116	caller := previousRealm.Address()
117	access.AssertIsAdminOrGovernance(caller)
118
119	i.lockPool(0, rlm)
120	defer i.unlockPool(0, rlm)
121
122	prevPoolCreationFee := i.store.GetPoolCreationFee()
123	err := i.setPoolCreationFee(0, rlm, fee)
124	if err != nil {
125		panic(err)
126	}
127
128	chain.Emit(
129		"SetPoolCreationFee",
130		"prevAddr", caller.String(),
131		"prevRealm", previousRealm.PkgPath(),
132		"prevFee", utils.FormatInt(prevPoolCreationFee),
133		"newFee", utils.FormatInt(fee),
134	)
135}
136
137// SetWithdrawalFee sets the withdrawal fee.
138// Only admin or governance can call this function.
139//
140// Parameters:
141//   - _: Leading integer discriminator for the forwarded realm call; callers
142//     pass 0.
143//   - rlm: Propagated realm context validated as current before the fee is
144//     written.
145//   - fee: New withdrawal fee in basis points; the setter accepts 0 through
146//     1,000 (10%) inclusive.
147func (i *poolV1) SetWithdrawalFee(_ int, rlm realm, fee uint64) {
148	access.AssertIsRlmCurrent(0, rlm)
149
150	i.assertPoolUnlocked()
151	halt.AssertIsNotHaltedPool()
152
153	previousRealm := rlm.Previous()
154	caller := previousRealm.Address()
155	access.AssertIsAdminOrGovernance(caller)
156
157	i.lockPool(0, rlm)
158	defer i.unlockPool(0, rlm)
159
160	prevWithdrawalFee := i.store.GetWithdrawalFeeBPS()
161
162	err := i.setWithdrawalFee(0, rlm, fee)
163	if err != nil {
164		panic(err)
165	}
166
167	chain.Emit(
168		"SetWithdrawalFee",
169		"prevAddr", caller.String(),
170		"prevRealm", previousRealm.PkgPath(),
171		"prevFee", utils.FormatUint(prevWithdrawalFee),
172		"newFee", utils.FormatUint(fee),
173	)
174}
175
176// calculateAmountWithFee calculates the fee amount and the amount after the fee
177//
178// Inputs:
179// - amount: the amount before the fee
180// - fee: the fee in BPS
181//
182// Outputs:
183// - the fee amount
184// - the amount after the fee applied
185func calculateAmountWithFee(amount, fee *u256.Uint) (feeAmount, afterAmount *u256.Uint) {
186	feeAmount, overflow := u256.Zero().MulOverflow(amount, fee)
187	if overflow {
188		panic(errors.New(errOverflow))
189	}
190	feeAmount = u256.Zero().Div(feeAmount, u256.NewUint(10_000))
191	afterAmount = u256.Zero().Sub(amount, feeAmount)
192	return feeAmount, afterAmount
193}
194
195// setPoolCreationFee this function is internal function called by SetPoolCreationFee
196// And SetPoolCreationFee
197func (i *poolV1) setPoolCreationFee(_ int, rlm realm, fee int64) error {
198	if fee < 0 {
199		return makeErrorWithDetails(
200			errInvalidInput,
201			"pool creation fee cannot be negative",
202		)
203	}
204
205	// update pool creation fee
206	err := i.store.SetPoolCreationFee(0, rlm, fee)
207	if err != nil {
208		return err
209	}
210
211	return nil
212}
213
214// setWithdrawalFee this function is internal function called by SetWithdrawalFee
215// function and SetWithdrawalFee function
216func (i *poolV1) setWithdrawalFee(_ int, rlm realm, fee uint64) error {
217	if fee > MaxBpsValue {
218		return makeErrorWithDetails(
219			errInvalidWithdrawalFeePct,
220			ufmt.Sprintf("fee(%d) must be in range 0 ~ %d", fee, MaxBpsValue),
221		)
222	}
223
224	err := i.store.SetWithdrawalFeeBPS(0, rlm, fee)
225	if err != nil {
226		return err
227	}
228
229	return nil
230}