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_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}