protocol_fee_swap.gno
4.88 Kb · 185 lines
1package router
2
3import (
4 "chain"
5 "errors"
6
7 ufmt "gno.land/p/nt/ufmt/v0"
8
9 gnsmath "gno.land/p/gnoswap/gnsmath"
10 prbac "gno.land/p/gnoswap/rbac"
11 u256 "gno.land/p/gnoswap/uint256"
12 "gno.land/p/gnoswap/utils"
13
14 "gno.land/r/gnoswap/access"
15 "gno.land/r/gnoswap/common"
16 "gno.land/r/gnoswap/halt"
17
18 pf "gno.land/r/gnoswap/protocol_fee"
19 _ "gno.land/r/gnoswap/protocol_fee/v1"
20)
21
22// GetSwapFee returns the current swap fee rate in basis points.
23func (r *routerV1) GetSwapFee() uint64 {
24 return r.store.GetSwapFee()
25}
26
27// SetSwapFee sets the protocol swap fee rate.
28//
29// Fee is deducted from swap output and sent to protocol fee contract.
30// Only callable by admin or governance.
31//
32// Parameters:
33// - fee: Fee rate in basis points (0-1000 = 0%-10%)
34//
35// Access:
36// - Requires: Admin or Governance role
37// - Halt check: Router and ProtocolFee must not be halted
38//
39// Events:
40// - SetSwapFee: Emits previous and new fee values
41//
42// Reverts if:
43// - Caller is not admin/governance
44// - Fee > 1000 bps (10%)
45// - Router or ProtocolFee is halted
46func (r *routerV1) SetSwapFee(_ int, rlm realm, fee uint64) {
47 if !rlm.IsCurrent() {
48 panic(errors.New(errSpoofedRealm))
49 }
50
51 halt.AssertIsNotHaltedRouter()
52 halt.AssertIsNotHaltedProtocolFee()
53
54 previousRealm := rlm.Previous()
55 caller := previousRealm.Address()
56 access.AssertIsAdminOrGovernance(caller)
57
58 // max swap fee is 1000 (bps)
59 if fee > 1000 {
60 panic(ufmt.Errorf(
61 "%s: fee must be in range 0 to 1000 (10%). got %d",
62 errInvalidSwapFee, fee,
63 ))
64 }
65
66 prevSwapFee := r.store.GetSwapFee()
67 if err := r.store.SetSwapFee(0, rlm, fee); err != nil {
68 panic(err)
69 }
70
71 chain.Emit(
72 "SetSwapFee",
73 "prevAddr", previousRealm.Address().String(),
74 "prevRealm", previousRealm.PkgPath(),
75 "newFee", utils.FormatUint(fee),
76 "prevFee", utils.FormatUint(prevSwapFee),
77 )
78}
79
80// handleSwapFee deducts the protocol fee from the swap amount and transfers it to the protocol fee contract.
81func (r *routerV1) handleSwapFee(
82 _ int,
83 rlm realm,
84 outputToken string,
85 amount int64,
86) int64 {
87 swapFee := r.store.GetSwapFee()
88 if swapFee <= 0 {
89 r.addToProtocolFee(0, rlm)
90 return amount
91 }
92
93 currentTokenPath := outputToken
94
95 feeAmountInt64 := calculateRouterFee(amount, swapFee)
96 r.addPendingProtocolFee(0, rlm, currentTokenPath, feeAmountInt64)
97 r.addToProtocolFee(0, rlm)
98
99 previousRealm := rlm.Previous()
100 chain.Emit(
101 "SwapRouteFee",
102 "prevAddr", previousRealm.Address().String(),
103 "prevRealm", previousRealm.PkgPath(),
104 "tokenPath", currentTokenPath,
105 "amount", utils.FormatInt(feeAmountInt64),
106 )
107
108 return gnsmath.SafeSubInt64(amount, feeAmountInt64)
109}
110
111func (r *routerV1) addPendingProtocolFee(_ int, rlm realm, tokenPath string, amount int64) {
112 if amount <= 0 {
113 return
114 }
115
116 existingProtocolFees := r.store.GetPendingProtocolFees()
117 pendingProtocolFees := make(map[string]int64)
118 for pendingTokenPath, pendingAmount := range existingProtocolFees {
119 pendingProtocolFees[pendingTokenPath] = pendingAmount
120 }
121 pendingProtocolFees[tokenPath] = gnsmath.SafeAddInt64(pendingProtocolFees[tokenPath], amount)
122 if err := r.store.SetPendingProtocolFees(0, rlm, pendingProtocolFees); err != nil {
123 panic(err)
124 }
125}
126
127// addToProtocolFee sends pending router protocol fees to the protocol fee realm.
128// It is best-effort: when protocol fee collection is halted, it keeps pending
129// storage unchanged; otherwise it rebuilds pending storage with only entries
130// that could not be added, removing successful and non-positive entries.
131func (r *routerV1) addToProtocolFee(_ int, rlm realm) {
132 pendingProtocolFees := r.store.GetPendingProtocolFees()
133 if len(pendingProtocolFees) == 0 || halt.IsHaltedProtocolFee() {
134 return
135 }
136
137 protocolFeeAddr := access.MustGetAddress(prbac.ROLE_PROTOCOL_FEE.String())
138 remainingProtocolFees := make(map[string]int64)
139 for tokenPath, amount := range pendingProtocolFees {
140 if amount <= 0 {
141 continue
142 }
143
144 common.SafeGRC20Approve(cross(rlm), tokenPath, protocolFeeAddr, amount)
145 if err := pf.AddToProtocolFee(cross(rlm), tokenPath, amount); err != nil {
146 remainingProtocolFees[tokenPath] = amount
147 continue
148 }
149 }
150
151 if err := r.store.SetPendingProtocolFees(0, rlm, remainingProtocolFees); err != nil {
152 panic(err)
153 }
154}
155
156func calculateRouterFee(amount int64, swapFee uint64) int64 {
157 if swapFee <= 0 {
158 return 0
159 }
160
161 feeAmount := u256.MulDiv(u256.NewUintFromInt64(amount), u256.NewUint(swapFee), u256.NewUint(10000))
162 return gnsmath.SafeConvertToInt64(feeAmount)
163}
164
165// calculate amount to fetch from pool including router fee
166// poolAmount = userAmount / (1 - feeRate)
167// = userAmount * 10000 / (10000 - swapFeeBPS)
168func calculateExactOutWithRouterFee(amount int64, swapFee uint64) int64 {
169 if amount == 0 {
170 return amount
171 }
172
173 if swapFee > 0 {
174 // Use MulDiv to prevent overflow and maintain precision
175 poolAmount := u256.MulDiv(
176 u256.NewUintFromInt64(amount),
177 u256.NewUint(10000),
178 u256.NewUint(10000-swapFee),
179 )
180
181 return gnsmath.SafeConvertToInt64(poolAmount)
182 }
183
184 return amount
185}