package v1 import ( "chain" ufmt "gno.land/p/nt/ufmt/v0" prbac "gno.land/p/gnoswap/rbac" u256 "gno.land/p/gnoswap/uint256" "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(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.Error(), 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", formatUint(fee), "prevFee", 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", formatInt64(feeAmountInt64), ) return 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] = 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 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 safeConvertToInt64(poolAmount) } return amount }