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}