package router
import (
"chain"
"errors"
ufmt "gno.land/p/nt/ufmt/v0"
gnsmath "gno.land/p/gnoswap/gnsmath"
prbac "gno.land/p/gnoswap/rbac"
u256 "gno.land/p/gnoswap/uint256"
"gno.land/p/gnoswap/utils"
"gno.land/r/gnoswap/access"
"gno.land/r/gnoswap/common"
"gno.land/r/gnoswap/halt"
pf "gno.land/r/gnoswap/protocol_fee"
_ "gno.land/r/gnoswap/protocol_fee/v1"
)
// GetSwapFee returns the current swap fee rate in basis points.
func (r *routerV1) GetSwapFee() uint64 {
return r.store.GetSwapFee()
}
// SetSwapFee sets the protocol swap fee rate.
//
// Fee is deducted from swap output and sent to protocol fee contract.
// Only callable by admin or governance.
//
// Parameters:
// - fee: Fee rate in basis points (0-1000 = 0%-10%)
//
// Access:
// - Requires: Admin or Governance role
// - Halt check: Router and ProtocolFee must not be halted
//
// Events:
// - SetSwapFee: Emits previous and new fee values
//
// Reverts if:
// - Caller is not admin/governance
// - Fee > 1000 bps (10%)
// - Router or ProtocolFee is halted
func (r *routerV1) SetSwapFee(_ int, rlm realm, fee uint64) {
if !rlm.IsCurrent() {
panic(errors.New(errSpoofedRealm))
}
halt.AssertIsNotHaltedRouter()
halt.AssertIsNotHaltedProtocolFee()
previousRealm := rlm.Previous()
caller := previousRealm.Address()
access.AssertIsAdminOrGovernance(caller)
// max swap fee is 1000 (bps)
if fee > 1000 {
panic(ufmt.Errorf(
"%s: fee must be in range 0 to 1000 (10%). got %d",
errInvalidSwapFee, fee,
))
}
prevSwapFee := r.store.GetSwapFee()
if err := r.store.SetSwapFee(0, rlm, fee); err != nil {
panic(err)
}
chain.Emit(
"SetSwapFee",
"prevAddr", previousRealm.Address().String(),
"prevRealm", previousRealm.PkgPath(),
"newFee", utils.FormatUint(fee),
"prevFee", utils.FormatUint(prevSwapFee),
)
}
// handleSwapFee deducts the protocol fee from the swap amount and transfers it to the protocol fee contract.
func (r *routerV1) handleSwapFee(
_ int,
rlm realm,
outputToken string,
amount int64,
) int64 {
swapFee := r.store.GetSwapFee()
if swapFee <= 0 {
r.addToProtocolFee(0, rlm)
return amount
}
currentTokenPath := outputToken
feeAmountInt64 := calculateRouterFee(amount, swapFee)
r.addPendingProtocolFee(0, rlm, currentTokenPath, feeAmountInt64)
r.addToProtocolFee(0, rlm)
previousRealm := rlm.Previous()
chain.Emit(
"SwapRouteFee",
"prevAddr", previousRealm.Address().String(),
"prevRealm", previousRealm.PkgPath(),
"tokenPath", currentTokenPath,
"amount", utils.FormatInt(feeAmountInt64),
)
return gnsmath.SafeSubInt64(amount, feeAmountInt64)
}
func (r *routerV1) addPendingProtocolFee(_ int, rlm realm, tokenPath string, amount int64) {
if amount <= 0 {
return
}
existingProtocolFees := r.store.GetPendingProtocolFees()
pendingProtocolFees := make(map[string]int64)
for pendingTokenPath, pendingAmount := range existingProtocolFees {
pendingProtocolFees[pendingTokenPath] = pendingAmount
}
pendingProtocolFees[tokenPath] = gnsmath.SafeAddInt64(pendingProtocolFees[tokenPath], amount)
if err := r.store.SetPendingProtocolFees(0, rlm, pendingProtocolFees); err != nil {
panic(err)
}
}
// addToProtocolFee sends pending router protocol fees to the protocol fee realm.
// It is best-effort: when protocol fee collection is halted, it keeps pending
// storage unchanged; otherwise it rebuilds pending storage with only entries
// that could not be added, removing successful and non-positive entries.
func (r *routerV1) addToProtocolFee(_ int, rlm realm) {
pendingProtocolFees := r.store.GetPendingProtocolFees()
if len(pendingProtocolFees) == 0 || halt.IsHaltedProtocolFee() {
return
}
protocolFeeAddr := access.MustGetAddress(prbac.ROLE_PROTOCOL_FEE.String())
remainingProtocolFees := make(map[string]int64)
for tokenPath, amount := range pendingProtocolFees {
if amount <= 0 {
continue
}
common.SafeGRC20Approve(cross(rlm), tokenPath, protocolFeeAddr, amount)
if err := pf.AddToProtocolFee(cross(rlm), tokenPath, amount); err != nil {
remainingProtocolFees[tokenPath] = amount
continue
}
}
if err := r.store.SetPendingProtocolFees(0, rlm, remainingProtocolFees); err != nil {
panic(err)
}
}
func calculateRouterFee(amount int64, swapFee uint64) int64 {
if swapFee <= 0 {
return 0
}
feeAmount := u256.MulDiv(u256.NewUintFromInt64(amount), u256.NewUint(swapFee), u256.NewUint(10000))
return gnsmath.SafeConvertToInt64(feeAmount)
}
// calculate amount to fetch from pool including router fee
// poolAmount = userAmount / (1 - feeRate)
// = userAmount * 10000 / (10000 - swapFeeBPS)
func calculateExactOutWithRouterFee(amount int64, swapFee uint64) int64 {
if amount == 0 {
return amount
}
if swapFee > 0 {
// Use MulDiv to prevent overflow and maintain precision
poolAmount := u256.MulDiv(
u256.NewUintFromInt64(amount),
u256.NewUint(10000),
u256.NewUint(10000-swapFee),
)
return gnsmath.SafeConvertToInt64(poolAmount)
}
return amount
}