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