// SPDX-License-Identifier: Apache-2.0 pragma solidity 0.8.28; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol"; /// @title FeeSwitch /// @notice Aggregator fee, switchable by the owner multisig. /// @dev MAX_FEE_BPS is a bytecode constant: a compromised owner cannot raise it. /// Ownership is two-step: a successor must call acceptOwnership(), so a typo in the new owner /// address cannot leave the contract without a reachable owner. contract FeeSwitch is Ownable2Step { /// @notice Hard ceiling, 0.30%. Not raisable by anyone, including the owner. uint16 public constant MAX_FEE_BPS = 30; uint16 public constant BPS_DENOMINATOR = 10_000; /// @notice Current fee in basis points. Starts at 0: the switch ships off. uint16 public feeBps; address public feeCollector; error FeeTooHigh(uint16 requested, uint16 max); error ZeroCollector(); event FeeBpsSet(uint16 oldBps, uint16 newBps); event FeeCollectorSet(address oldCollector, address newCollector); constructor(address owner_, address collector_) Ownable(owner_) { if (collector_ == address(0)) revert ZeroCollector(); feeCollector = collector_; emit FeeCollectorSet(address(0), collector_); } function setFeeBps(uint16 newBps) external onlyOwner { if (newBps > MAX_FEE_BPS) revert FeeTooHigh(newBps, MAX_FEE_BPS); emit FeeBpsSet(feeBps, newBps); feeBps = newBps; } function setFeeCollector(address newCollector) external onlyOwner { if (newCollector == address(0)) revert ZeroCollector(); emit FeeCollectorSet(feeCollector, newCollector); feeCollector = newCollector; } }