protocol_fee_swap.gno
3.32 Kb ยท 138 lines
1package v1
2
3import (
4 "chain"
5 "chain/runtime"
6
7 "gno.land/p/nt/ufmt"
8
9 prbac "gno.land/p/gnoswap/rbac"
10 u256 "gno.land/p/gnoswap/uint256"
11
12 "gno.land/r/gnoswap/access"
13 "gno.land/r/gnoswap/common"
14 "gno.land/r/gnoswap/halt"
15
16 pf "gno.land/r/gnoswap/protocol_fee"
17 _ "gno.land/r/gnoswap/protocol_fee/v1"
18)
19
20// GetSwapFee returns the current swap fee rate in basis points.
21func (r *routerV1) GetSwapFee() uint64 {
22 return r.store.GetSwapFee()
23}
24
25// SetSwapFee sets the protocol swap fee rate.
26//
27// Fee is deducted from swap output and sent to protocol fee contract.
28// Only callable by admin or governance.
29//
30// Parameters:
31// - fee: Fee rate in basis points (0-1000 = 0%-10%)
32//
33// Access:
34// - Requires: Admin or Governance role
35// - Halt check: Router and ProtocolFee must not be halted
36//
37// Events:
38// - SetSwapFee: Emits previous and new fee values
39//
40// Reverts if:
41// - Caller is not admin/governance
42// - Fee > 1000 bps (10%)
43// - Router or ProtocolFee is halted
44func (r *routerV1) SetSwapFee(fee uint64) {
45 halt.AssertIsNotHaltedRouter()
46 halt.AssertIsNotHaltedProtocolFee()
47
48 caller := runtime.PreviousRealm().Address()
49 access.AssertIsAdminOrGovernance(caller)
50
51 // max swap fee is 1000 (bps)
52 if fee > 1000 {
53 panic(ufmt.Errorf(
54 "%s: fee must be in range 0 to 1000 (10%). got %d",
55 errInvalidSwapFee.Error(), fee,
56 ))
57 }
58
59 prevSwapFee := r.store.GetSwapFee()
60 if err := r.store.SetSwapFee(fee); err != nil {
61 panic(err)
62 }
63
64 previousRealm := runtime.PreviousRealm()
65 chain.Emit(
66 "SetSwapFee",
67 "prevAddr", previousRealm.Address().String(),
68 "prevRealm", previousRealm.PkgPath(),
69 "newFee", formatUint(fee),
70 "prevFee", formatUint(prevSwapFee),
71 )
72}
73
74// handleSwapFee deducts the protocol fee from the swap amount and transfers it to the protocol fee contract.
75func (r *routerV1) handleSwapFee(
76 outputToken string,
77 amount int64,
78) int64 {
79 swapFee := r.store.GetSwapFee()
80 if swapFee <= 0 {
81 return amount
82 }
83
84 currentTokenPath := outputToken
85
86 if common.IsGNOTNativePath(outputToken) {
87 currentTokenPath = wugnotPath
88 }
89
90 feeAmountInt64 := calculateRouterFee(amount, swapFee)
91
92 protocolFeeAddr := access.MustGetAddress(prbac.ROLE_PROTOCOL_FEE.String())
93 common.SafeGRC20Transfer(cross, currentTokenPath, protocolFeeAddr, feeAmountInt64)
94
95 pf.AddToProtocolFee(cross, currentTokenPath, feeAmountInt64)
96
97 previousRealm := runtime.PreviousRealm()
98 chain.Emit(
99 "SwapRouteFee",
100 "prevAddr", previousRealm.Address().String(),
101 "prevRealm", previousRealm.PkgPath(),
102 "tokenPath", currentTokenPath,
103 "amount", formatInt64(feeAmountInt64),
104 )
105
106 return safeSubInt64(amount, feeAmountInt64)
107}
108
109func calculateRouterFee(amount int64, swapFee uint64) int64 {
110 if swapFee <= 0 {
111 return 0
112 }
113
114 feeAmount := u256.MulDiv(u256.NewUintFromInt64(amount), u256.NewUint(swapFee), u256.NewUint(10000))
115 return safeConvertToInt64(feeAmount)
116}
117
118// calculate amount to fetch from pool including router fee
119// poolAmount = userAmount / (1 - feeRate)
120// = userAmount * 10000 / (10000 - swapFeeBPS)
121func calculateExactOutWithRouterFee(amount int64, swapFee uint64) int64 {
122 if amount == 0 {
123 return amount
124 }
125
126 if swapFee > 0 {
127 // Use MulDiv to prevent overflow and maintain precision
128 poolAmount := u256.MulDiv(
129 u256.NewUintFromInt64(amount),
130 u256.NewUint(10000),
131 u256.NewUint(10000-swapFee),
132 )
133
134 return safeConvertToInt64(poolAmount)
135 }
136
137 return amount
138}