SnuggleVaultAdminSatellite
0xf1f413ab4ef210f2aee5cdb998362b743a9e4fa2
Verification
Verified
v0.8.33+commit.64118f21
Type
Contract
8,696 bytes
ABI entries
56
14 read · 19 write
License
none
Contract information
- Address
- 0xf1f413ab4ef210f2aee5cdb998362b743a9e4fa2
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.33+commit.64118f21
- Optimization
- Enabled
- Creator
- 0x6aC51A7065…80858e25FF
- Creation tx
- 0x0285d91f15…8c6d397b8f
Token
Not a token
This contract does not expose ERC-20 metadata.
Read contract (14)
FEE_CHANGE_COOLDOWN() → uint256
MAX_FEE_CHANGE_BPS() → uint256
MAX_PROTOCOL_FEE_BPS() → uint256
MAX_RANGE_WIDTH_BPS() → uint24
MAX_REFERRAL_FEE_BPS() → uint256
MAX_SLIPPAGE_BPS() → uint256
MIN_TWAP_INTERVAL() → uint32
TIMELOCK_DELAY() → uint256
lastFeeChangeTime() → uint256
pendingStakingManager() → address
pendingStakingManagerTimestamp() → uint256
pendingTreasury() → address
pendingTreasuryTimestamp() → uint256
vault() → address
Events (5)
PoolAddedStakingManagerChangeCancelledStakingManagerChangeProposedTreasuryChangeCancelledTreasuryChangeProposed
ABI
[
{
"inputs": [
{
"internalType": "address",
"name": "_vault",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "FeeChangeCooldown",
"type": "error"
},
{
"inputs": [],
"name": "FeeChangeTooLarge",
"type": "error"
},
{
"inputs": [],
"name": "FeeTooHigh",
"type": "error"
},
{
"inputs": [],
"name": "InvalidAddress",
"type": "error"
},
{
"inputs": [],
"name": "InvalidMaxTotalPositions",
"type": "error"
},
{
"inputs": [],
"name": "InvalidRangeWidth",
"type": "error"
},
{
"inputs": [],
"name": "InvalidSlippage",
"type": "error"
},
{
"inputs": [],
"name": "InvalidTWAPInterval",
"type": "error"
},
{
"inputs": [],
"name": "NoPendingChange",
"type": "error"
},
{
"inputs": [],
"name": "NotVaultOwner",
"type": "error"
},
{
"inputs": [],
"name": "PoolDoesNotExist",
"type": "error"
},
{
"inputs": [],
"name": "PositionIsActive",
"type": "error"
},
{
"inputs": [],
"name": "ReferralFeeTooHigh",
"type": "error"
},
{
"inputs": [],
"name": "RewardAdapterValidationFailed",
"type": "error"
},
{
"inputs": [],
"name": "StakingManagerAlreadySet",
"type": "error"
},
{
"inputs": [],
"name": "TimelockNotReady",
"type": "error"
},
{
"inputs": [],
"name": "TreasuryNotSet",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "poolId",
"type": "bytes32"
},
{
"indexed": false,
"internalType": "address",
"name": "pool",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "token0",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "token1",
"type": "address"
},
{
"indexed": false,
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"indexed": false,
"internalType": "address",
"name": "positionAdapter",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "rewardAdapter",
"type": "address"
}
],
"name": "PoolAdded",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "cancelledManager",
"type": "address"
}
],
"name": "StakingManagerChangeCancelled",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "newManager",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "timelockExpiry",
"type": "uint256"
}
],
"name": "StakingManagerChangeProposed",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "cancelledTreasury",
"type": "address"
}
],
"name": "TreasuryChangeCancelled",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "newTreasury",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "timelockExpiry",
"type": "uint256"
}
],
"name": "TreasuryChangeProposed",
"type": "event"
},
{
"inputs": [],
"name": "FEE_CHANGE_COOLDOWN",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "MAX_FEE_CHANGE_BPS",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "MAX_PROTOCOL_FEE_BPS",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "MAX_RANGE_WIDTH_BPS",
"outputs": [
{
"internalType": "uint24",
"name": "",
"type": "uint24"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "MAX_REFERRAL_FEE_BPS",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "MAX_SLIPPAGE_BPS",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "MIN_TWAP_INTERVAL",
"outputs": [
{
"internalType": "uint32",
"name": "",
"type": "uint32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "TIMELOCK_DELAY",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token0",
"type": "address"
},
{
"internalType": "address",
"name": "token1",
"type": "address"
},
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"internalType": "address",
"name": "positionAdapter",
"type": "address"
},
{
"internalType": "address",
"name": "rewardAdapter",
"type": "address"
}
],
"name": "addPool",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "cancelStakingManager",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "cancelTreasury",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "user",
"type": "address"
}
],
"name": "clearReferrer",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "address",
"name": "spender",
"type": "address"
}
],
"name": "emergencyRevokeApproval",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "nftManager",
"type": "address"
},
{
"internalType": "address",
"name": "operator",
"type": "address"
}
],
"name": "emergencyRevokeNFTApproval",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "executeStakingManager",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "executeTreasury",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "lastFeeChangeTime",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "pendingStakingManager",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "pendingStakingManagerTimestamp",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "pendingTreasury",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "pendingTreasuryTimestamp",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newManager",
"type": "address"
}
],
"name": "proposeStakingManager",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newTreasury",
"type": "address"
}
],
"name": "proposeTreasury",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "poolId",
"type": "bytes32"
}
],
"name": "removePool",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "nftContract",
"type": "address"
},
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
},
{
"internalType": "address",
"name": "to",
"type": "address"
}
],
"name": "rescueERC721",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint24",
"name": "rangeMin",
"type": "uint24"
},
{
"internalType": "uint24",
"name": "rangeMax",
"type": "uint24"
},
{
"internalType": "uint256",
"name": "newReferralFeeBps",
"type": "uint256"
},
{
"internalType": "uint32",
"name": "newTwapInterval",
"type": "uint32"
},
{
"internalType": "uint256",
"name": "newSlippageBps",
"type": "uint256"
}
],
"name": "setConfig",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "user",
"type": "address"
},
{
"internalType": "bool",
"name": "exempt",
"type": "bool"
}
],
"name": "setFeeExempt",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newManager",
"type": "address"
}
],
"name": "setInitialStakingManager",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "keeper",
"type": "address"
},
{
"internalType": "bool",
"name": "authorized",
"type": "bool"
}
],
"name": "setKeeper",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "newMax",
"type": "uint256"
}
],
"name": "setMaxTotalPositions",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "newFeeBps",
"type": "uint256"
}
],
"name": "setPerformanceFee",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "poolId",
"type": "bytes32"
},
{
"internalType": "address",
"name": "newRewardAdapter",
"type": "address"
}
],
"name": "updatePoolRewardAdapter",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "vault",
"outputs": [
{
"internalType": "contract ISnuggleVaultPoolManagement",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
}
]Source code
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@uniswap/v3-periphery/contracts/interfaces/INonfungiblePositionManager.sol";
import "./interfaces/IPositionAdapter.sol";
import "./interfaces/IRewardAdapter.sol";
/// @title ISnuggleVaultPoolManagement
/// @notice Interface for vault functions called by satellite
interface ISnuggleVaultPoolManagement {
function owner() external view returns (address);
function addPoolDirect(
bytes32 poolId, address pool, address token0, address token1,
uint24 fee, int24 tickSpacing, address positionAdapter,
address rewardAdapter, address nftManager
) external;
function updatePoolRewardAdapter(bytes32 poolId, address newRewardAdapter) external;
function removePool(bytes32 poolId) external;
function setPerformanceFee(uint256 newFeeBps) external;
function setConfig(uint24 rangeMin, uint24 rangeMax, uint256 newReferralFeeBps, uint32 newTwapInterval, uint256 newSlippageBps) external;
function setMaxTotalPositions(uint256 newMax) external;
function setKeeper(address keeper, bool authorized) external;
function setFeeExempt(address user, bool exempt) external;
function clearReferrer(address user) external;
function emergencyRevokeApproval(address token, address spender) external;
function emergencyRevokeNFTApproval(address nftManager, address operator) external;
function rescueERC721(address nftContract, uint256 tokenId, address to) external;
function setTreasury(address newTreasury) external;
function setStakingManager(address newManager) external;
function performanceFeeBps() external view returns (uint256);
function referralFeeBps() external view returns (uint256);
function treasury() external view returns (address);
function stakingManager() external view returns (address);
function positions(uint256 tokenId) external view returns (uint256,bytes32,address,uint24,int24,int24,bool,bool,uint64,uint64,uint32,uint32,uint64,uint128,uint128,uint128,uint128);
function allPositionIds(uint256 index) external view returns (uint256);
function totalActivePositions() external view returns (uint256);
}
/// @title SnuggleVaultAdminSatellite
/// @notice Handles complex admin operations for SnuggleVaultUpgradeable
/// @dev Extracted to reduce main vault contract size below EVM bytecode limit
contract SnuggleVaultAdminSatellite {
ISnuggleVaultPoolManagement public immutable vault;
// ═══════════════════════════════════════════════════════════════════════════
// TIMELOCK STATE
// ═══════════════════════════════════════════════════════════════════════════
uint256 public constant TIMELOCK_DELAY = 24 hours;
address public pendingTreasury;
uint256 public pendingTreasuryTimestamp;
address public pendingStakingManager;
uint256 public pendingStakingManagerTimestamp;
// ═══════════════════════════════════════════════════════════════════════════
// EVENTS
// ═══════════════════════════════════════════════════════════════════════════
event PoolAdded(
bytes32 indexed poolId, address pool, address token0, address token1,
uint24 fee, address positionAdapter, address rewardAdapter
);
event TreasuryChangeProposed(address indexed newTreasury, uint256 timelockExpiry);
event TreasuryChangeCancelled(address indexed cancelledTreasury);
event StakingManagerChangeProposed(address indexed newManager, uint256 timelockExpiry);
event StakingManagerChangeCancelled(address indexed cancelledManager);
// ═══════════════════════════════════════════════════════════════════════════
// ERRORS
// ═══════════════════════════════════════════════════════════════════════════
error NotVaultOwner();
error InvalidAddress();
error PoolDoesNotExist();
error FeeTooHigh();
error FeeChangeTooLarge();
error ReferralFeeTooHigh();
error InvalidRangeWidth();
error InvalidTWAPInterval();
error InvalidMaxTotalPositions();
error PositionIsActive();
error NoPendingChange();
error TimelockNotReady();
error TreasuryNotSet();
error InvalidSlippage();
error StakingManagerAlreadySet();
error FeeChangeCooldown();
error RewardAdapterValidationFailed();
// ═══════════════════════════════════════════════════════════════════════════
// MODIFIERS
// ═══════════════════════════════════════════════════════════════════════════
modifier onlyVaultOwner() {
if (msg.sender != vault.owner()) revert NotVaultOwner();
_;
}
constructor(address _vault) {
vault = ISnuggleVaultPoolManagement(_vault);
// V25-L-03: Initialize cooldown so first fee change also respects cooldown period
lastFeeChangeTime = block.timestamp;
}
// ═══════════════════════════════════════════════════════════════════════════
// POOL MANAGEMENT
// ═══════════════════════════════════════════════════════════════════════════
function addPool(
address token0, address token1, uint24 fee,
address positionAdapter, address rewardAdapter
) external onlyVaultOwner {
if (positionAdapter == address(0)) revert InvalidAddress();
IPositionAdapter adapter = IPositionAdapter(positionAdapter);
if (token0 > token1) (token0, token1) = (token1, token0);
address pool = adapter.getPool(token0, token1, fee);
if (pool == address(0)) revert PoolDoesNotExist();
// V28: Validate reward adapter can actually stake in this pool (gauge exists, MasterChef registered, etc.)
// Catches configuration errors at registration time instead of silently failing at staking time
if (rewardAdapter != address(0)) {
try IRewardAdapter(rewardAdapter).validatePool(pool) {}
catch { revert RewardAdapterValidationFailed(); }
}
bytes32 poolId = keccak256(abi.encodePacked(token0, token1, fee, positionAdapter));
int24 tickSpacing = adapter.getTickSpacing(pool);
address nftManager = adapter.positionManager();
vault.addPoolDirect(poolId, pool, token0, token1, fee, tickSpacing, positionAdapter, rewardAdapter, nftManager);
emit PoolAdded(poolId, pool, token0, token1, fee, positionAdapter, rewardAdapter);
}
function updatePoolRewardAdapter(bytes32 poolId, address newRewardAdapter) external onlyVaultOwner {
vault.updatePoolRewardAdapter(poolId, newRewardAdapter);
}
/// @notice Remove a pool from the approved list
/// @dev V20-L-01: Verifies no active positions exist for this pool before removal.
/// Use emergencyRevokeApproval/emergencyRevokeNFTApproval after removal if needed.
/// @dev V24-M-01: WARNING — This function iterates ALL active positions O(n) to verify none
/// belong to the target pool. Gas cost scales linearly with totalActivePositions.
/// For pools with many positions, prefer deactivating via updatePoolRewardAdapter(poolId, address(0))
/// and letting positions withdraw naturally, rather than calling removePool().
/// A per-pool position counter would fix this but exceeds the vault's EIP-170 bytecode limit.
function removePool(bytes32 poolId) external onlyVaultOwner {
// M-04 (Valves): removed the O(n) active-position scan that previously guarded this.
// It was defensive theater — vault.removePool only sets approvedPools[poolId].active =
// false, and the withdraw path is NOT gated on `active`, so existing positions can always
// be withdrawn after a pool is deactivated. The scan added no safety and let any user
// permanently block pool removal by holding a single dust position in the pool.
vault.removePool(poolId);
}
// ═══════════════════════════════════════════════════════════════════════════
// TIMELOCK OPERATIONS
// ═══════════════════════════════════════════════════════════════════════════
function proposeTreasury(address newTreasury) external onlyVaultOwner {
if (newTreasury == address(0)) revert InvalidAddress();
pendingTreasury = newTreasury;
pendingTreasuryTimestamp = block.timestamp + TIMELOCK_DELAY;
emit TreasuryChangeProposed(newTreasury, pendingTreasuryTimestamp);
}
function executeTreasury() external onlyVaultOwner {
if (pendingTreasury == address(0)) revert NoPendingChange();
if (block.timestamp < pendingTreasuryTimestamp) revert TimelockNotReady();
vault.setTreasury(pendingTreasury);
pendingTreasury = address(0);
pendingTreasuryTimestamp = 0;
}
function cancelTreasury() external onlyVaultOwner {
if (pendingTreasury == address(0)) revert NoPendingChange();
emit TreasuryChangeCancelled(pendingTreasury);
pendingTreasury = address(0);
pendingTreasuryTimestamp = 0;
}
/// @notice Set staking manager — initial setup only (no timelock, reverts if already set)
function setInitialStakingManager(address newManager) external onlyVaultOwner {
if (newManager == address(0)) revert InvalidAddress();
if (vault.treasury() == address(0)) revert TreasuryNotSet();
if (vault.stakingManager() != address(0)) revert StakingManagerAlreadySet();
vault.setStakingManager(newManager);
}
function proposeStakingManager(address newManager) external onlyVaultOwner {
if (vault.treasury() == address(0)) revert TreasuryNotSet();
if (newManager == address(0)) revert InvalidAddress();
pendingStakingManager = newManager;
pendingStakingManagerTimestamp = block.timestamp + TIMELOCK_DELAY;
emit StakingManagerChangeProposed(newManager, pendingStakingManagerTimestamp);
}
function executeStakingManager() external onlyVaultOwner {
if (pendingStakingManager == address(0) && pendingStakingManagerTimestamp == 0) revert NoPendingChange();
if (block.timestamp < pendingStakingManagerTimestamp) revert TimelockNotReady();
vault.setStakingManager(pendingStakingManager);
pendingStakingManager = address(0);
pendingStakingManagerTimestamp = 0;
}
function cancelStakingManager() external onlyVaultOwner {
if (pendingStakingManager == address(0) && pendingStakingManagerTimestamp == 0) revert NoPendingChange();
emit StakingManagerChangeCancelled(pendingStakingManager);
pendingStakingManager = address(0);
pendingStakingManagerTimestamp = 0;
}
// ═══════════════════════════════════════════════════════════════════════════
// ADMIN CONFIG (with validation — vault functions are thin setters)
// ═══════════════════════════════════════════════════════════════════════════
uint256 public constant MAX_PROTOCOL_FEE_BPS = 5000;
uint256 public constant MAX_FEE_CHANGE_BPS = 500;
uint256 public constant MAX_REFERRAL_FEE_BPS = 1000;
uint256 public constant MAX_SLIPPAGE_BPS = 1000;
uint32 public constant MIN_TWAP_INTERVAL = 5 minutes;
/// @dev INT-11 (Valves internal V31): absolute ceiling on maxRangeWidthBps. rangeWidthBps is a tick
/// count; the full usable span is MAX_TICK - MIN_TICK = 1,774,544 (a full-range position).
/// Bounding rangeMax here prevents an owner from setting the cap into the int24 sign-wrap zone
/// (> ~8.39M), where TickMath._roundWidth would silently collapse deposits to a 1-tick range.
uint24 public constant MAX_RANGE_WIDTH_BPS = 1_774_544;
uint256 public constant FEE_CHANGE_COOLDOWN = 6 hours;
/// @notice V17-M-03: Last time performance fee was changed (cooldown enforcement)
uint256 public lastFeeChangeTime;
function setPerformanceFee(uint256 newFeeBps) external onlyVaultOwner {
if (newFeeBps > MAX_PROTOCOL_FEE_BPS) revert FeeTooHigh();
if (lastFeeChangeTime > 0 && block.timestamp < lastFeeChangeTime + FEE_CHANGE_COOLDOWN) revert FeeChangeCooldown();
uint256 current = vault.performanceFeeBps();
uint256 delta = newFeeBps > current ? newFeeBps - current : current - newFeeBps;
if (delta > MAX_FEE_CHANGE_BPS) revert FeeChangeTooLarge();
lastFeeChangeTime = block.timestamp;
vault.setPerformanceFee(newFeeBps);
// V22-M-01: Cap referral fee if it would exceed new performance fee
if (vault.referralFeeBps() > newFeeBps) {
vault.setConfig(0, 0, newFeeBps, 0, type(uint256).max);
}
}
function setConfig(
uint24 rangeMin, uint24 rangeMax, uint256 newReferralFeeBps,
uint32 newTwapInterval, uint256 newSlippageBps
) external onlyVaultOwner {
if (rangeMin > 0 && rangeMax > 0 && rangeMin > rangeMax) revert InvalidRangeWidth();
// INT-11: bound the upper cap so it can never enter the int24 sign-wrap zone (see _roundWidth).
if (rangeMax > MAX_RANGE_WIDTH_BPS) revert InvalidRangeWidth();
if (newReferralFeeBps != type(uint256).max) {
if (newReferralFeeBps > MAX_REFERRAL_FEE_BPS || newReferralFeeBps > vault.performanceFeeBps()) revert ReferralFeeTooHigh();
}
if (newTwapInterval > 0 && (newTwapInterval < MIN_TWAP_INTERVAL || newTwapInterval > 1 hours)) revert InvalidTWAPInterval();
// V19-I-03: Revert on invalid slippage instead of silently skipping
if (newSlippageBps != type(uint256).max && newSlippageBps > 0 && newSlippageBps > MAX_SLIPPAGE_BPS) {
revert InvalidSlippage();
}
vault.setConfig(rangeMin, rangeMax, newReferralFeeBps, newTwapInterval, newSlippageBps);
}
/// @notice M-02 (Valves): runtime-adjust the total-position cap. Guarded so it can't be
/// set below the live position count (which would freeze new deposits).
function setMaxTotalPositions(uint256 newMax) external onlyVaultOwner {
if (newMax < vault.totalActivePositions()) revert InvalidMaxTotalPositions();
vault.setMaxTotalPositions(newMax);
}
function setKeeper(address keeper, bool authorized) external onlyVaultOwner {
vault.setKeeper(keeper, authorized);
}
function setFeeExempt(address user, bool exempt) external onlyVaultOwner {
vault.setFeeExempt(user, exempt);
}
function clearReferrer(address user) external onlyVaultOwner {
vault.clearReferrer(user);
}
function emergencyRevokeApproval(address token, address spender) external onlyVaultOwner {
vault.emergencyRevokeApproval(token, spender);
}
function emergencyRevokeNFTApproval(address nftManager, address operator) external onlyVaultOwner {
vault.emergencyRevokeNFTApproval(nftManager, operator);
}
function rescueERC721(address nftContract, uint256 tokenId, address to) external onlyVaultOwner {
if (to == address(0)) revert InvalidAddress();
(,,address posOwner,,,,,,,,,,,,,,) = vault.positions(tokenId);
if (posOwner != address(0)) revert PositionIsActive();
vault.rescueERC721(nftContract, tokenId, to);
}
}
Chain explorer3716msChain node84ms