Search Apps Documentation Source Content File Folder Download Copy Actions Download

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}