diff --git a/src/contracts/defaultLimiter/DefaultLimiter.sol b/src/contracts/defaultLimiter/DefaultLimiter.sol new file mode 100644 index 00000000..d2309c3b --- /dev/null +++ b/src/contracts/defaultLimiter/DefaultLimiter.sol @@ -0,0 +1,247 @@ +import {IDefaultLimiter} from "src/interfaces/defaultLimiter/IDefaultLimiter.sol"; +import {ILimiter} from "src/interfaces/ILimiter.sol"; +import {IRegistry} from "src/interfaces/base/IRegistry.sol"; +import {IVault} from "src/interfaces/vault/v1/IVault.sol"; + +import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import {AccessControlUpgradeable} from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol"; +import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; + +// SPDX-License-Identifier: MIT +pragma solidity 0.8.25; + +contract DefaultLimiter is Initializable, IDefaultLimiter, AccessControlUpgradeable { + /** + * @inheritdoc IDefaultLimiter + */ + address public immutable NETWORK_REGISTRY; + + /** + * @inheritdoc IDefaultLimiter + */ + address public immutable VAULT_FACTORY; + + /** + * @inheritdoc IDefaultLimiter + */ + bytes32 public constant NETWORK_RESOLVER_LIMIT_SET_ROLE = keccak256("NETWORK_RESOLVER_LIMIT_SET_ROLE"); + + /** + * @inheritdoc IDefaultLimiter + */ + bytes32 public constant OPERATOR_NETWORK_LIMIT_SET_ROLE = keccak256("OPERATOR_NETWORK_LIMIT_SET_ROLE"); + + /** + * @inheritdoc IDefaultLimiter + */ + mapping(address vault => mapping(address network => mapping(address resolver => uint256 amount))) public + maxNetworkResolverLimit; + + /** + * @inheritdoc IDefaultLimiter + */ + mapping(address vault => mapping(address network => mapping(address resolver => DelayedLimit))) public + nextNetworkResolverLimit; + + /** + * @inheritdoc IDefaultLimiter + */ + mapping(address vault => mapping(address operator => mapping(address network => DelayedLimit))) public + nextOperatorNetworkLimit; + + mapping(address vault => mapping(address network => mapping(address resolver => Limit limit))) internal + _networkResolverLimit; + + mapping(address vault => mapping(address operator => mapping(address network => Limit limit))) internal + _operatorNetworkLimit; + + modifier onlyStakingController(address vault) { + if (IVault(vault).stakingController() != msg.sender) { + revert NotStakingController(); + } + _; + } + + constructor(address networkRegistry, address vaultFactory) { + NETWORK_REGISTRY = networkRegistry; + VAULT_FACTORY = vaultFactory; + } + + /** + * @inheritdoc ILimiter + */ + function networkResolverLimitIn( + address vault, + address network, + address resolver, + uint48 duration + ) public view returns (uint256) { + return _getLimitAt( + _networkResolverLimit[vault][network][resolver], + nextNetworkResolverLimit[vault][network][resolver], + Time.timestamp() + duration + ); + } + + /** + * @inheritdoc ILimiter + */ + function networkResolverLimit(address vault, address network, address resolver) public view returns (uint256) { + return networkResolverLimitIn(vault, network, resolver, 0); + } + + /** + * @inheritdoc ILimiter + */ + function operatorNetworkLimitIn( + address vault, + address operator, + address network, + uint48 duration + ) public view returns (uint256) { + return _getLimitAt( + _operatorNetworkLimit[vault][operator][network], + nextOperatorNetworkLimit[vault][operator][network], + Time.timestamp() + duration + ); + } + + /** + * @inheritdoc ILimiter + */ + function operatorNetworkLimit(address vault, address operator, address network) public view returns (uint256) { + return operatorNetworkLimitIn(vault, operator, network, 0); + } + + function initialize(address networkResolverLimiter, address operatorNetworkLimiter) external initializer { + _grantRole(NETWORK_RESOLVER_LIMIT_SET_ROLE, networkResolverLimiter); + _grantRole(OPERATOR_NETWORK_LIMIT_SET_ROLE, operatorNetworkLimiter); + } + + /** + * @inheritdoc IDefaultLimiter + */ + function setMaxNetworkResolverLimit(address vault, address resolver, uint256 amount) external { + if (maxNetworkResolverLimit[vault][msg.sender][resolver] == amount) { + revert AlreadySet(); + } + + if (!IRegistry(NETWORK_REGISTRY).isEntity(msg.sender)) { + revert NotNetwork(); + } + + maxNetworkResolverLimit[vault][msg.sender][resolver] = amount; + + Limit storage limit = _networkResolverLimit[vault][msg.sender][resolver]; + DelayedLimit storage nextLimit = nextNetworkResolverLimit[vault][msg.sender][resolver]; + + _updateLimit(limit, nextLimit); + + if (limit.amount > amount) { + limit.amount = amount; + } + if (nextLimit.amount > amount) { + nextLimit.amount = amount; + } + + emit SetMaxNetworkResolverLimit(vault, msg.sender, resolver, amount); + } + + /** + * @inheritdoc IDefaultLimiter + */ + function setNetworkResolverLimit( + address vault, + address network, + address resolver, + uint256 amount + ) external onlyRole(NETWORK_RESOLVER_LIMIT_SET_ROLE) { + if (amount > maxNetworkResolverLimit[vault][network][resolver]) { + revert ExceedsMaxNetworkResolverLimit(); + } + + Limit storage limit = _networkResolverLimit[vault][network][resolver]; + DelayedLimit storage nextLimit = nextNetworkResolverLimit[vault][network][resolver]; + + _setLimit(limit, nextLimit, vault, amount); + + emit SetNetworkResolverLimit(vault, network, resolver, amount); + } + + /** + * @inheritdoc IDefaultLimiter + */ + function setOperatorNetworkLimit( + address vault, + address operator, + address network, + uint256 amount + ) external onlyRole(OPERATOR_NETWORK_LIMIT_SET_ROLE) { + Limit storage limit = _operatorNetworkLimit[vault][operator][network]; + DelayedLimit storage nextLimit = nextOperatorNetworkLimit[vault][operator][network]; + + _setLimit(limit, nextLimit, vault, amount); + + emit SetOperatorNetworkLimit(vault, operator, network, amount); + } + + /** + * @inheritdoc ILimiter + */ + function onSlash( + address vault, + address network, + address resolver, + address operator, + uint256 slashedAmount + ) external onlyStakingController(vault) { + uint256 networkResolverLimit_ = networkResolverLimit(vault, network, resolver); + uint256 operatorNetworkLimit_ = operatorNetworkLimit(vault, operator, network); + + _updateLimit( + _networkResolverLimit[vault][network][resolver], nextNetworkResolverLimit[vault][network][resolver] + ); + _updateLimit( + _operatorNetworkLimit[vault][operator][network], nextOperatorNetworkLimit[vault][operator][network] + ); + + if (networkResolverLimit_ != type(uint256).max) { + _networkResolverLimit[vault][network][resolver].amount = networkResolverLimit_ - slashedAmount; + } + if (operatorNetworkLimit_ != type(uint256).max) { + _operatorNetworkLimit[vault][operator][network].amount = operatorNetworkLimit_ - slashedAmount; + } + } + + function _getLimitAt( + Limit storage limit, + DelayedLimit storage nextLimit, + uint48 timestamp + ) private view returns (uint256) { + if (nextLimit.timestamp == 0 || timestamp < nextLimit.timestamp) { + return limit.amount; + } + return nextLimit.amount; + } + + function _setLimit(Limit storage limit, DelayedLimit storage nextLimit, address vault, uint256 amount) private { + _updateLimit(limit, nextLimit); + + if (amount < limit.amount) { + nextLimit.amount = amount; + nextLimit.timestamp = IVault(vault).currentEpochStart() + 2 * IVault(vault).epochDuration(); + } else { + limit.amount = amount; + nextLimit.amount = 0; + nextLimit.timestamp = 0; + } + } + + function _updateLimit(Limit storage limit, DelayedLimit storage nextLimit) internal { + if (nextLimit.timestamp != 0 && nextLimit.timestamp <= Time.timestamp()) { + limit.amount = nextLimit.amount; + nextLimit.timestamp = 0; + nextLimit.amount = 0; + } + } +} diff --git a/src/contracts/defaultLimiter/DefaultLimiterFactory.sol b/src/contracts/defaultLimiter/DefaultLimiterFactory.sol new file mode 100644 index 00000000..c825883b --- /dev/null +++ b/src/contracts/defaultLimiter/DefaultLimiterFactory.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.25; + +import {DefaultLimiter} from "./DefaultLimiter.sol"; +import {Registry} from "src/contracts/base/Registry.sol"; + +import {IDefaultLimiterFactory} from "src/interfaces/defaultLimiter/IDefaultLimiterFactory.sol"; + +import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; + +contract DefaultLimiterFactory is Registry, IDefaultLimiterFactory { + using Clones for address; + + address private immutable LIMITER_IMPLEMENTATION; + + constructor(address networkRegistry, address vaultFactory) { + LIMITER_IMPLEMENTATION = address(new DefaultLimiter(networkRegistry, vaultFactory)); + } + + /** + * @inheritdoc IDefaultLimiterFactory + */ + function create(address networkResolverLimiter, address operatorNetworkLimiter) external returns (address) { + address limiter = LIMITER_IMPLEMENTATION.clone(); + DefaultLimiter(limiter).initialize(networkResolverLimiter, operatorNetworkLimiter); + _addEntity(limiter); + + return limiter; + } +} diff --git a/src/contracts/stakingController/v1/StakingController.sol b/src/contracts/stakingController/v1/StakingController.sol new file mode 100644 index 00000000..536bdc73 --- /dev/null +++ b/src/contracts/stakingController/v1/StakingController.sol @@ -0,0 +1,309 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.25; + +import {IStakingController} from "src/interfaces/stakingController/v1/IStakingController.sol"; +import {IRegistry} from "src/interfaces/base/IRegistry.sol"; +import {IVault} from "src/interfaces/vault/v1/IVault.sol"; +import {ILimiter} from "src/interfaces/ILimiter.sol"; +import {INetworkMiddlewareService} from "src/interfaces/INetworkMiddlewareService.sol"; +import {INetworkOptInService} from "src/interfaces/INetworkOptInService.sol"; +import {IOperatorOptInService} from "src/interfaces/IOperatorOptInService.sol"; +import {ICollateral} from "src/interfaces/base/ICollateral.sol"; + +import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; +import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; + +contract StakingController is Initializable, IStakingController { + /** + * @inheritdoc IStakingController + */ + address public immutable VAULT_FACTORY; + + /** + * @dev Some dead address to issue debt to. + */ + address internal constant DEAD = address(0xdEaD); + + /** + * @inheritdoc IStakingController + */ + address public immutable NETWORK_VAULT_OPT_IN_SERVICE; + + /** + * @inheritdoc IStakingController + */ + address public immutable OPERATOR_VAULT_OPT_IN_SERVICE; + + /** + * @inheritdoc IStakingController + */ + address public immutable OPERATOR_NETWORK_OPT_IN_SERVICE; + + /** + * @inheritdoc IStakingController + */ + address public immutable NETWORK_REGISTRY; + + /** + * @inheritdoc IStakingController + */ + address public immutable NETWORK_MIDDLEWARE_SERVICE; + + /** + * @inheritdoc IStakingController + */ + address public vault; + + /** + * @inheritdoc IStakingController + */ + address public limiter; + + /** + * @inheritdoc IStakingController + */ + SlashRequest[] public slashRequests; + + /** + * @inheritdoc IStakingController + */ + uint48 public vetoDuration; + + /** + * @inheritdoc IStakingController + */ + uint48 public executeDuration; + + constructor( + address _vault, + address networkRegistry, + address networkMiddlewareService, + address networkVaultOptInService, + address operatorVaultOptInService, + address operatorNetworkOptInService + ) { + _disableInitializers(); + if (!IRegistry(VAULT_FACTORY).isEntity(_vault)) { + revert NotVault(); + } + + NETWORK_REGISTRY = networkRegistry; + NETWORK_MIDDLEWARE_SERVICE = networkMiddlewareService; + NETWORK_VAULT_OPT_IN_SERVICE = networkVaultOptInService; + OPERATOR_VAULT_OPT_IN_SERVICE = operatorVaultOptInService; + OPERATOR_NETWORK_OPT_IN_SERVICE = operatorNetworkOptInService; + } + + /** + * @inheritdoc IStakingController + */ + function slashRequestsLength() external view returns (uint256) { + return slashRequests.length; + } + + /** + * @inheritdoc IStakingController + */ + function slashableAmountIn( + address network, + address resolver, + address operator, + uint48 duration + ) public view returns (uint256) { + return Math.min( + IVault(vault).totalSupplyIn(duration), + Math.min( + ILimiter(limiter).networkResolverLimitIn(vault, network, resolver, duration), + ILimiter(limiter).operatorNetworkLimitIn(vault, operator, network, duration) + ) + ); + } + + /** + * @inheritdoc IStakingController + */ + function slashableAmount(address network, address resolver, address operator) public view returns (uint256) { + return Math.min( + IVault(vault).totalSupply(), + Math.min( + ILimiter(limiter).networkResolverLimit(vault, network, resolver), + ILimiter(limiter).operatorNetworkLimit(vault, operator, network) + ) + ); + } + + /** + * @inheritdoc IStakingController + */ + function minStakeDuring( + address network, + address resolver, + address operator, + uint48 duration + ) external view returns (uint256) { + return Math.min( + IVault(vault).activeSupply(), + Math.min( + Math.min( + ILimiter(limiter).networkResolverLimit(vault, network, resolver), + ILimiter(limiter).networkResolverLimitIn(vault, network, resolver, duration) + ), + Math.min( + ILimiter(limiter).operatorNetworkLimit(vault, operator, network), + ILimiter(limiter).operatorNetworkLimitIn(vault, operator, network, duration) + ) + ) + ); + } + + function initialize(address _vault, uint48 _vetoDuration, uint48 _executeDuration) external initializer { + if (!IRegistry(VAULT_FACTORY).isEntity(_vault)) { + revert NotVault(); + } + + vault = _vault; + + if (_vetoDuration + _executeDuration > IVault(vault).epochDuration()) { + revert InvalidSlashDuration(); + } + + vetoDuration = _vetoDuration; + executeDuration = _executeDuration; + } + + /** + * @inheritdoc IStakingController + */ + function requestSlash( + address network, + address resolver, + address operator, + uint256 amount + ) external returns (uint256 slashIndex) { + if (INetworkMiddlewareService(NETWORK_MIDDLEWARE_SERVICE).middleware(network) != msg.sender) { + revert NotNetworkMiddleware(); + } + + uint256 slashableAmount_ = slashableAmountIn(network, resolver, operator, vetoDuration); + + if (amount == 0 || slashableAmount_ == 0) { + revert InsufficientSlash(); + } + + if (!INetworkOptInService(NETWORK_VAULT_OPT_IN_SERVICE).isOptedIn(network, resolver, vault)) { + revert NetworkNotOptedInVault(); + } + + if ( + !IOperatorOptInService(OPERATOR_VAULT_OPT_IN_SERVICE).wasOptedInAfter( + operator, + vault, + IVault(vault).currentEpoch() != 0 + ? IVault(vault).previousEpochStart() + : IVault(vault).currentEpochStart() + ) + ) { + revert OperatorNotOptedInVault(); + } + + if ( + !IOperatorOptInService(OPERATOR_NETWORK_OPT_IN_SERVICE).wasOptedInAfter( + operator, + network, + IVault(vault).currentEpoch() != 0 + ? IVault(vault).previousEpochStart() + : IVault(vault).currentEpochStart() + ) + ) { + revert OperatorNotOptedInNetwork(); + } + + if (amount > slashableAmount_) { + amount = slashableAmount_; + } + uint48 vetoDeadline = Time.timestamp() + vetoDuration; + uint48 executeDeadline = vetoDeadline + executeDuration; + + slashIndex = slashRequests.length; + slashRequests.push( + SlashRequest({ + network: network, + resolver: resolver, + operator: operator, + amount: amount, + vetoDeadline: vetoDeadline, + executeDeadline: executeDeadline, + completed: false + }) + ); + + emit RequestSlash(slashIndex, network, resolver, operator, amount, vetoDeadline, executeDeadline); + } + + /** + * @inheritdoc IStakingController + */ + function executeSlash(uint256 slashIndex) external returns (uint256 slashedAmount) { + if (slashIndex >= slashRequests.length) { + revert SlashRequestNotExist(); + } + + SlashRequest storage request = slashRequests[slashIndex]; + + if (request.resolver != address(0) && request.vetoDeadline > Time.timestamp()) { + revert VetoPeriodNotEnded(); + } + + if (request.executeDeadline <= Time.timestamp()) { + revert SlashPeriodEnded(); + } + + if (request.completed) { + revert SlashCompleted(); + } + + request.completed = true; + + slashedAmount = Math.min(request.amount, slashableAmount(request.network, request.resolver, request.operator)); + + slashedAmount = IVault(vault).onSlash(slashedAmount); + + emit ExecuteSlash(slashIndex, slashedAmount); + + if (slashedAmount == 0) { + return 0; + } + + ILimiter(limiter).onSlash(vault, request.network, request.resolver, request.operator, slashedAmount); + + ICollateral(IVault(vault).collateral()).issueDebt(DEAD, slashedAmount); + } + + /** + * @inheritdoc IStakingController + */ + function vetoSlash(uint256 slashIndex) external { + if (slashIndex >= slashRequests.length) { + revert SlashRequestNotExist(); + } + + SlashRequest storage request = slashRequests[slashIndex]; + + if (request.resolver != msg.sender) { + revert NotResolver(); + } + + if (request.vetoDeadline <= Time.timestamp()) { + revert VetoPeriodEnded(); + } + + if (request.completed) { + revert SlashCompleted(); + } + + request.completed = true; + + emit VetoSlash(slashIndex); + } +} diff --git a/src/contracts/stakingController/v1/StakingControllerFactory.sol b/src/contracts/stakingController/v1/StakingControllerFactory.sol new file mode 100644 index 00000000..a411fc56 --- /dev/null +++ b/src/contracts/stakingController/v1/StakingControllerFactory.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.25; + +import {StakingController} from "./StakingController.sol"; +import {Registry} from "src/contracts/base/Registry.sol"; + +import {IStakingControllerFactory} from "src/interfaces/stakingController/v1/IStakingControllerFactory.sol"; + +import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; + +contract StakingControllerFactory is Registry, IStakingControllerFactory { + using Clones for address; + + address private immutable STAKING_CONTROLLER_IMPLEMENTATION; + + constructor( + address networkRegistry, + address vaultFactory, + address networkMiddlewareService, + address networkVaultOptInService, + address operatorVaultOptInService, + address operatorNetworkOptInService + ) { + STAKING_CONTROLLER_IMPLEMENTATION = address( + new StakingController( + vaultFactory, + networkRegistry, + networkMiddlewareService, + networkVaultOptInService, + operatorVaultOptInService, + operatorNetworkOptInService + ) + ); + } + + /** + * @inheritdoc IStakingControllerFactory + */ + function create(address vault, uint48 vetoDuration, uint48 executeDuration) external returns (address) { + address stakingController = STAKING_CONTROLLER_IMPLEMENTATION.clone(); + StakingController(stakingController).initialize(vault, vetoDuration, executeDuration); + + _addEntity(stakingController); + + return stakingController; + } +} diff --git a/src/contracts/vault/v1/Vault.sol b/src/contracts/vault/v1/Vault.sol index bdb824bc..98f47469 100644 --- a/src/contracts/vault/v1/Vault.sol +++ b/src/contracts/vault/v1/Vault.sol @@ -5,11 +5,9 @@ import {MigratableEntity} from "src/contracts/base/MigratableEntity.sol"; import {VaultStorage} from "./VaultStorage.sol"; import {ICollateral} from "src/interfaces/base/ICollateral.sol"; -import {INetworkMiddlewareService} from "src/interfaces/INetworkMiddlewareService.sol"; -import {INetworkOptInService} from "src/interfaces/INetworkOptInService.sol"; -import {IOperatorOptInService} from "src/interfaces/IOperatorOptInService.sol"; import {IRegistry} from "src/interfaces/base/IRegistry.sol"; import {IVault} from "src/interfaces/vault/v1/IVault.sol"; +import {IStakingControllerFactory} from "src/interfaces/stakingController/v1/IStakingControllerFactory.sol"; import {Checkpoints} from "src/contracts/libraries/Checkpoints.sol"; import {ERC4626Math} from "src/contracts/libraries/ERC4626Math.sol"; @@ -23,6 +21,13 @@ contract Vault is VaultStorage, MigratableEntity, AccessControlUpgradeable, IVau using Checkpoints for Checkpoints.Trace256; using Math for uint256; + modifier onlyStakingController() { + if (msg.sender != stakingController) { + revert NotStakingController(); + } + _; + } + /** * @inheritdoc IVault */ @@ -74,103 +79,7 @@ contract Vault is VaultStorage, MigratableEntity, AccessControlUpgradeable, IVau ); } - /** - * @inheritdoc IVault - */ - function slashableAmountIn( - address network, - address resolver, - address operator, - uint48 duration - ) public view returns (uint256) { - return Math.min( - totalSupplyIn(duration), - Math.min( - networkResolverLimitIn(network, resolver, duration), operatorNetworkLimitIn(operator, network, duration) - ) - ); - } - - /** - * @inheritdoc IVault - */ - function slashableAmount(address network, address resolver, address operator) public view returns (uint256) { - return Math.min( - totalSupply(), Math.min(networkResolverLimit(network, resolver), operatorNetworkLimit(operator, network)) - ); - } - - /** - * @inheritdoc IVault - */ - function networkResolverLimitIn(address network, address resolver, uint48 duration) public view returns (uint256) { - return _getLimitAt( - _networkResolverLimit[network][resolver], - nextNetworkResolverLimit[network][resolver], - Time.timestamp() + duration - ); - } - - /** - * @inheritdoc IVault - */ - function networkResolverLimit(address network, address resolver) public view returns (uint256) { - return networkResolverLimitIn(network, resolver, 0); - } - - /** - * @inheritdoc IVault - */ - function operatorNetworkLimitIn(address operator, address network, uint48 duration) public view returns (uint256) { - return _getLimitAt( - _operatorNetworkLimit[operator][network], - nextOperatorNetworkLimit[operator][network], - Time.timestamp() + duration - ); - } - - /** - * @inheritdoc IVault - */ - function operatorNetworkLimit(address operator, address network) public view returns (uint256) { - return operatorNetworkLimitIn(operator, network, 0); - } - - /** - * @inheritdoc IVault - */ - function minStakeDuring( - address network, - address resolver, - address operator, - uint48 duration - ) external view returns (uint256) { - return Math.min( - activeSupply(), - Math.min( - Math.min(networkResolverLimit(network, resolver), networkResolverLimitIn(network, resolver, duration)), - Math.min(operatorNetworkLimit(operator, network), operatorNetworkLimitIn(operator, network, duration)) - ) - ); - } - - constructor( - address vaultFactory, - address networkRegistry, - address networkMiddlewareService, - address networkVaultOptInService, - address operatorVaultOptInService, - address operatorNetworkOptInService - ) - MigratableEntity(vaultFactory) - VaultStorage( - networkRegistry, - networkMiddlewareService, - networkVaultOptInService, - operatorVaultOptInService, - operatorNetworkOptInService - ) - { + constructor(address vaultFactory) MigratableEntity(vaultFactory) { _disableInitializers(); } @@ -262,97 +171,12 @@ contract Vault is VaultStorage, MigratableEntity, AccessControlUpgradeable, IVau /** * @inheritdoc IVault */ - function requestSlash( - address network, - address resolver, - address operator, - uint256 amount - ) external returns (uint256 slashIndex) { - if (INetworkMiddlewareService(NETWORK_MIDDLEWARE_SERVICE).middleware(network) != msg.sender) { - revert NotNetworkMiddleware(); - } - - uint256 slashableAmount_ = slashableAmountIn(network, resolver, operator, vetoDuration); - - if (amount == 0 || slashableAmount_ == 0) { - revert InsufficientSlash(); - } - - if (!INetworkOptInService(NETWORK_VAULT_OPT_IN_SERVICE).isOptedIn(network, resolver, address(this))) { - revert NetworkNotOptedInVault(); - } - - if ( - !IOperatorOptInService(OPERATOR_VAULT_OPT_IN_SERVICE).wasOptedInAfter( - operator, address(this), currentEpoch() != 0 ? previousEpochStart() : currentEpochStart() - ) - ) { - revert OperatorNotOptedInVault(); - } - - if ( - !IOperatorOptInService(OPERATOR_NETWORK_OPT_IN_SERVICE).wasOptedInAfter( - operator, network, currentEpoch() != 0 ? previousEpochStart() : currentEpochStart() - ) - ) { - revert OperatorNotOptedInNetwork(); - } - - if (amount > slashableAmount_) { - amount = slashableAmount_; - } - uint48 vetoDeadline = Time.timestamp() + vetoDuration; - uint48 executeDeadline = vetoDeadline + executeDuration; - - slashIndex = slashRequests.length; - slashRequests.push( - SlashRequest({ - network: network, - resolver: resolver, - operator: operator, - amount: amount, - vetoDeadline: vetoDeadline, - executeDeadline: executeDeadline, - completed: false - }) - ); - - emit RequestSlash(slashIndex, network, resolver, operator, amount, vetoDeadline, executeDeadline); - } - - /** - * @inheritdoc IVault - */ - function executeSlash(uint256 slashIndex) external returns (uint256 slashedAmount) { - if (slashIndex >= slashRequests.length) { - revert SlashRequestNotExist(); - } - - SlashRequest storage request = slashRequests[slashIndex]; - - if (request.resolver != address(0) && request.vetoDeadline > Time.timestamp()) { - revert VetoPeriodNotEnded(); - } - - if (request.executeDeadline <= Time.timestamp()) { - revert SlashPeriodEnded(); - } - - if (request.completed) { - revert SlashCompleted(); - } - - request.completed = true; - - slashedAmount = Math.min(request.amount, slashableAmount(request.network, request.resolver, request.operator)); - + function onSlash(uint256 slashedAmount) external onlyStakingController returns (uint256) { uint256 epoch = currentEpoch(); uint256 totalSupply_ = totalSupply(); uint256 activeSupply_ = activeSupply(); uint256 withdrawals_ = withdrawals[epoch]; uint256 nextWithdrawals_ = withdrawals[epoch + 1]; - uint256 networkResolverLimit_ = networkResolverLimit(request.network, request.resolver); - uint256 operatorNetworkLimit_ = operatorNetworkLimit(request.operator, request.network); uint256 activeSlashed = slashedAmount.mulDiv(activeSupply_, totalSupply_); uint256 withdrawalsSlashed = slashedAmount.mulDiv(withdrawals_, totalSupply_); @@ -362,8 +186,6 @@ contract Vault is VaultStorage, MigratableEntity, AccessControlUpgradeable, IVau slashedAmount = activeSlashed + withdrawalsSlashed + nextWithdrawalsSlashed; } - emit ExecuteSlash(slashIndex, slashedAmount); - if (slashedAmount == 0) { return 0; } @@ -371,129 +193,7 @@ contract Vault is VaultStorage, MigratableEntity, AccessControlUpgradeable, IVau _activeSupplies.push(Time.timestamp(), activeSupply_ - activeSlashed); withdrawals[epoch] = withdrawals_ - withdrawalsSlashed; withdrawals[epoch + 1] = nextWithdrawals_ - nextWithdrawalsSlashed; - - _updateLimit( - _networkResolverLimit[request.network][request.resolver], - nextNetworkResolverLimit[request.network][request.resolver] - ); - _updateLimit( - _operatorNetworkLimit[request.operator][request.network], - nextOperatorNetworkLimit[request.operator][request.network] - ); - - if (networkResolverLimit_ != type(uint256).max) { - _networkResolverLimit[request.network][request.resolver].amount = networkResolverLimit_ - slashedAmount; - } - if (operatorNetworkLimit_ != type(uint256).max) { - _operatorNetworkLimit[request.operator][request.network].amount = operatorNetworkLimit_ - slashedAmount; - } - - ICollateral(collateral).issueDebt(DEAD, slashedAmount); - } - - /** - * @inheritdoc IVault - */ - function vetoSlash(uint256 slashIndex) external { - if (slashIndex >= slashRequests.length) { - revert SlashRequestNotExist(); - } - - SlashRequest storage request = slashRequests[slashIndex]; - - if (request.resolver != msg.sender) { - revert NotResolver(); - } - - if (request.vetoDeadline <= Time.timestamp()) { - revert VetoPeriodEnded(); - } - - if (request.completed) { - revert SlashCompleted(); - } - - request.completed = true; - - emit VetoSlash(slashIndex); - } - - /** - * @inheritdoc IVault - */ - function setMaxNetworkResolverLimit(address resolver, uint256 amount) external { - if (maxNetworkResolverLimit[msg.sender][resolver] == amount) { - revert AlreadySet(); - } - - if (!IRegistry(NETWORK_REGISTRY).isEntity(msg.sender)) { - revert NotNetwork(); - } - - maxNetworkResolverLimit[msg.sender][resolver] = amount; - - Limit storage limit = _networkResolverLimit[msg.sender][resolver]; - DelayedLimit storage nextLimit = nextNetworkResolverLimit[msg.sender][resolver]; - - _updateLimit(limit, nextLimit); - - if (limit.amount > amount) { - limit.amount = amount; - } - if (nextLimit.amount > amount) { - nextLimit.amount = amount; - } - - emit SetMaxNetworkResolverLimit(msg.sender, resolver, amount); - } - - /** - * @inheritdoc IVault - */ - function setNetworkResolverLimit( - address network, - address resolver, - uint256 amount - ) external onlyRole(NETWORK_RESOLVER_LIMIT_SET_ROLE) { - if (amount > maxNetworkResolverLimit[network][resolver]) { - revert ExceedsMaxNetworkResolverLimit(); - } - - Limit storage limit = _networkResolverLimit[network][resolver]; - DelayedLimit storage nextLimit = nextNetworkResolverLimit[network][resolver]; - - _setLimit(limit, nextLimit, amount); - - emit SetNetworkResolverLimit(network, resolver, amount); - } - - /** - * @inheritdoc IVault - */ - function setOperatorNetworkLimit( - address operator, - address network, - uint256 amount - ) external onlyRole(OPERATOR_NETWORK_LIMIT_SET_ROLE) { - Limit storage limit = _operatorNetworkLimit[operator][network]; - DelayedLimit storage nextLimit = nextOperatorNetworkLimit[operator][network]; - - _setLimit(limit, nextLimit, amount); - - emit SetOperatorNetworkLimit(operator, network, amount); - } - - /** - * @inheritdoc IVault - */ - function setRewardsDistributor(address rewardsDistributor_) external onlyRole(REWARDS_DISTRIBUTOR_SET_ROLE) { - if (rewardsDistributor == rewardsDistributor_) { - revert AlreadySet(); - } - - rewardsDistributor = rewardsDistributor_; - - emit SetRewardsDistributor(rewardsDistributor_); + return slashedAmount; } /** @@ -550,10 +250,6 @@ contract Vault is VaultStorage, MigratableEntity, AccessControlUpgradeable, IVau revert InvalidEpochDuration(); } - if (params.vetoDuration + params.executeDuration > params.epochDuration) { - revert InvalidSlashDuration(); - } - if (params.adminFee > ADMIN_FEE_BASE) { revert InvalidAdminFee(); } @@ -563,54 +259,20 @@ contract Vault is VaultStorage, MigratableEntity, AccessControlUpgradeable, IVau epochDurationInit = Time.timestamp(); epochDuration = params.epochDuration; - vetoDuration = params.vetoDuration; - executeDuration = params.executeDuration; - - rewardsDistributor = params.rewardsDistributor; adminFee = params.adminFee; depositWhitelist = params.depositWhitelist; _grantRole(DEFAULT_ADMIN_ROLE, owner); - _grantRole(NETWORK_RESOLVER_LIMIT_SET_ROLE, owner); - _grantRole(OPERATOR_NETWORK_LIMIT_SET_ROLE, owner); if (params.depositWhitelist) { _grantRole(DEPOSITOR_WHITELIST_ROLE, owner); } + + IStakingControllerFactory(params.stakingControllerFactory).create( + address(this), params.vetoDuration, params.executeDuration + ); } function _migrate(uint64, bytes memory) internal override { revert(); } - - function _getLimitAt( - Limit storage limit, - DelayedLimit storage nextLimit, - uint48 timestamp - ) private view returns (uint256) { - if (nextLimit.timestamp == 0 || timestamp < nextLimit.timestamp) { - return limit.amount; - } - return nextLimit.amount; - } - - function _setLimit(Limit storage limit, DelayedLimit storage nextLimit, uint256 amount) private { - _updateLimit(limit, nextLimit); - - if (amount < limit.amount) { - nextLimit.amount = amount; - nextLimit.timestamp = currentEpochStart() + 2 * epochDuration; - } else { - limit.amount = amount; - nextLimit.amount = 0; - nextLimit.timestamp = 0; - } - } - - function _updateLimit(Limit storage limit, DelayedLimit storage nextLimit) internal { - if (nextLimit.timestamp != 0 && nextLimit.timestamp <= Time.timestamp()) { - limit.amount = nextLimit.amount; - nextLimit.timestamp = 0; - nextLimit.amount = 0; - } - } } diff --git a/src/contracts/vault/v1/VaultStorage.sol b/src/contracts/vault/v1/VaultStorage.sol index a1ba874e..b2b8fdd5 100644 --- a/src/contracts/vault/v1/VaultStorage.sol +++ b/src/contracts/vault/v1/VaultStorage.sol @@ -10,21 +10,11 @@ import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; contract VaultStorage is IVaultStorage { using Checkpoints for Checkpoints.Trace256; - /** - * @dev Some dead address to issue debt to. - */ - address internal constant DEAD = address(0xdEaD); - /** * @inheritdoc IVaultStorage */ uint256 public constant ADMIN_FEE_BASE = 10_000; - /** - * @inheritdoc IVaultStorage - */ - bytes32 public constant REWARDS_DISTRIBUTOR_SET_ROLE = keccak256("REWARDS_DISTRIBUTOR_SET_ROLE"); - /** * @inheritdoc IVaultStorage */ @@ -42,42 +32,12 @@ contract VaultStorage is IVaultStorage { /** * @inheritdoc IVaultStorage */ - bytes32 public constant NETWORK_RESOLVER_LIMIT_SET_ROLE = keccak256("NETWORK_RESOLVER_LIMIT_SET_ROLE"); - - /** - * @inheritdoc IVaultStorage - */ - bytes32 public constant OPERATOR_NETWORK_LIMIT_SET_ROLE = keccak256("OPERATOR_NETWORK_LIMIT_SET_ROLE"); - - /** - * @inheritdoc IVaultStorage - */ - address public immutable NETWORK_REGISTRY; - - /** - * @inheritdoc IVaultStorage - */ - address public immutable NETWORK_MIDDLEWARE_SERVICE; - - /** - * @inheritdoc IVaultStorage - */ - address public immutable NETWORK_VAULT_OPT_IN_SERVICE; - - /** - * @inheritdoc IVaultStorage - */ - address public immutable OPERATOR_VAULT_OPT_IN_SERVICE; - - /** - * @inheritdoc IVaultStorage - */ - address public immutable OPERATOR_NETWORK_OPT_IN_SERVICE; + address public collateral; /** * @inheritdoc IVaultStorage */ - address public collateral; + address public stakingController; /** * @inheritdoc IVaultStorage @@ -89,21 +49,6 @@ contract VaultStorage is IVaultStorage { */ uint48 public epochDuration; - /** - * @inheritdoc IVaultStorage - */ - uint48 public vetoDuration; - - /** - * @inheritdoc IVaultStorage - */ - uint48 public executeDuration; - - /** - * @inheritdoc IVaultStorage - */ - address public rewardsDistributor; - /** * @inheritdoc IVaultStorage */ @@ -139,50 +84,12 @@ contract VaultStorage is IVaultStorage { */ mapping(uint256 epoch => mapping(address account => uint256 amount)) public pendingWithdrawalSharesOf; - /** - * @inheritdoc IVaultStorage - */ - SlashRequest[] public slashRequests; - - /** - * @inheritdoc IVaultStorage - */ - mapping(address network => mapping(address resolver => uint256 amount)) public maxNetworkResolverLimit; - - /** - * @inheritdoc IVaultStorage - */ - mapping(address network => mapping(address resolver => DelayedLimit)) public nextNetworkResolverLimit; - - /** - * @inheritdoc IVaultStorage - */ - mapping(address operator => mapping(address network => DelayedLimit)) public nextOperatorNetworkLimit; - Checkpoints.Trace256 internal _activeShares; Checkpoints.Trace256 internal _activeSupplies; mapping(address account => Checkpoints.Trace256 shares) internal _activeSharesOf; - mapping(address network => mapping(address resolver => Limit limit)) internal _networkResolverLimit; - - mapping(address operator => mapping(address network => Limit limit)) internal _operatorNetworkLimit; - - constructor( - address networkRegistry, - address networkMiddlewareService, - address networkVaultOptInService, - address operatorVaultOptInService, - address operatorNetworkOptInService - ) { - NETWORK_REGISTRY = networkRegistry; - NETWORK_MIDDLEWARE_SERVICE = networkMiddlewareService; - NETWORK_VAULT_OPT_IN_SERVICE = networkVaultOptInService; - OPERATOR_VAULT_OPT_IN_SERVICE = operatorVaultOptInService; - OPERATOR_NETWORK_OPT_IN_SERVICE = operatorNetworkOptInService; - } - /** * @inheritdoc IVaultStorage */ @@ -281,11 +188,4 @@ contract VaultStorage is IVaultStorage { Checkpoints.Checkpoint256 memory checkpoint = _activeSharesOf[account].at(pos); return (checkpoint._key, checkpoint._value); } - - /** - * @inheritdoc IVaultStorage - */ - function slashRequestsLength() external view returns (uint256) { - return slashRequests.length; - } } diff --git a/src/interfaces/ILimiter.sol b/src/interfaces/ILimiter.sol new file mode 100644 index 00000000..55b35135 --- /dev/null +++ b/src/interfaces/ILimiter.sol @@ -0,0 +1,59 @@ +pragma solidity 0.8.25; + +interface ILimiter { + /** + * @notice Get a network-resolver limit for a particular network and resolver in `duration` seconds. + * @param network address of the network + * @param resolver address of the resolver + * @param duration duration to get the network-resolver limit in + * @return network-resolver limit in `duration` seconds + */ + function networkResolverLimitIn( + address vault, + address network, + address resolver, + uint48 duration + ) external view returns (uint256); + + /** + * @notice Get a network-resolver limit for a particular network and resolver. + * @param network address of the network + * @param resolver address of the resolver + * @return network-resolver limit + */ + function networkResolverLimit(address vault, address network, address resolver) external view returns (uint256); + + /** + * @notice Get an operator-network limit for a particular operator and network in `duration` seconds. + * @param operator address of the operator + * @param network address of the network + * @param duration duration to get the operator-network limit in + * @return operator-network limit in `duration` seconds + */ + function operatorNetworkLimitIn( + address vault, + address operator, + address network, + uint48 duration + ) external view returns (uint256); + + /** + * @notice Get an operator-network limit for a particular operator and network. + * @param operator address of the operator + * @param network address of the network + * @return operator-network limit + */ + function operatorNetworkLimit(address vault, address operator, address network) external view returns (uint256); + + /** + * @notice Slashing callback for limits decreasing. + * @param slashedAmount a + */ + function onSlash( + address vault, + address network, + address resolver, + address operator, + uint256 slashedAmount + ) external; +} diff --git a/src/interfaces/defaultLimiter/IDefaultLimiter.sol b/src/interfaces/defaultLimiter/IDefaultLimiter.sol new file mode 100644 index 00000000..8b6a9edf --- /dev/null +++ b/src/interfaces/defaultLimiter/IDefaultLimiter.sol @@ -0,0 +1,144 @@ +pragma solidity 0.8.25; + +import {ILimiter} from "src/interfaces/ILimiter.sol"; + +interface IDefaultLimiter is ILimiter { + error ExceedsMaxNetworkResolverLimit(); + error AlreadySet(); + error NotStakingController(); + error NotNetwork(); + + /** + * @notice Structure for a slashing limit. + * @param amount amount of the collateral that can be slashed + */ + struct Limit { + uint256 amount; + } + + /** + * @notice Structure for a slashing limit that will be set in the future (if a new limit won't be set). + * @param amount amount of the collateral that can be slashed + * @param timestamp timestamp when the limit will be set + */ + struct DelayedLimit { + uint256 amount; + uint48 timestamp; + } + + /** + * @notice Emitted when a maximum network-resolver limit is set. + * @param network address of the network + * @param resolver address of the resolver + * @param amount maximum network-resolver limit that can be set + */ + event SetMaxNetworkResolverLimit( + address indexed vault, address indexed network, address indexed resolver, uint256 amount + ); + + /** + * @notice Emitted when a network-resolver limit is set. + * @param network address of the network + * @param resolver address of the resolver + * @param amount maximum amount of the collateral that can be slashed + */ + event SetNetworkResolverLimit( + address indexed vault, address indexed network, address indexed resolver, uint256 amount + ); + + /** + * @notice Emitted when an operator-network limit is set. + * @param operator address of the operator + * @param network address of the network + * @param amount maximum amount of the collateral that can be slashed + */ + event SetOperatorNetworkLimit( + address indexed vault, address indexed operator, address indexed network, uint256 amount + ); + + /** + * @notice Get the network registry's address. + * @return address of the network registry + */ + function NETWORK_REGISTRY() external view returns (address); + + /** + * @notice Get the network-resolver limit setter's role. + */ + function NETWORK_RESOLVER_LIMIT_SET_ROLE() external view returns (bytes32); + + /** + * @notice Get the operator-network limit setter's role. + */ + function OPERATOR_NETWORK_LIMIT_SET_ROLE() external view returns (bytes32); + + /** + * @notice Get the vault factory's address. + * @return address of the vault factory + */ + function VAULT_FACTORY() external view returns (address); + + /** + * @notice Get a maximum network-resolver limit for a particular network and resolver. + * @param network address of the network + * @param resolver address of the resolver + * @return maximum network-resolver limit + */ + function maxNetworkResolverLimit( + address vault, + address network, + address resolver + ) external view returns (uint256); + + /** + * @notice Get the next network-resolver limit for a particular network and resolver. + * @param network address of the network + * @param resolver address of the resolver + * @return next network-resolver limit + * @return timestamp when the limit will be set + */ + function nextNetworkResolverLimit( + address vault, + address network, + address resolver + ) external view returns (uint256, uint48); + + /** + * @notice Get the next operator-network limit for a particular operator and network. + * @param operator address of the operator + * @param network address of the network + * @return next operator-network limit + * @return timestamp when the limit will be set + */ + function nextOperatorNetworkLimit( + address vault, + address operator, + address network + ) external view returns (uint256, uint48); + + /** + * @notice Set a maximum network-resolver limit. + * @param resolver address of the resolver + * @param amount maximum network-resolver limit that can be set + * @dev Only a network can call this function. + */ + function setMaxNetworkResolverLimit(address vault, address resolver, uint256 amount) external; + + /** + * @notice Set a network-resolver limit for a particular network and resolver. + * @param network address of the network + * @param resolver address of the resolver + * @param amount new maximum amount of the collateral that can be slashed + * @dev Only the NETWORK_RESOLVER_LIMIT_SET_ROLE holder can call this function. + */ + function setNetworkResolverLimit(address vault, address network, address resolver, uint256 amount) external; + + /** + * @notice Set an operator-network limit for a particular operator and network. + * @param operator address of the operator + * @param network address of the network + * @param amount new maximum amount of the collateral that can be slashed + * @dev Only the OPERATOR_NETWORK_LIMIT_SET_ROLE holder can call this function. + */ + function setOperatorNetworkLimit(address vault, address operator, address network, uint256 amount) external; +} diff --git a/src/interfaces/defaultLimiter/IDefaultLimiterFactory.sol b/src/interfaces/defaultLimiter/IDefaultLimiterFactory.sol new file mode 100644 index 00000000..e99af391 --- /dev/null +++ b/src/interfaces/defaultLimiter/IDefaultLimiterFactory.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.25; + +import {IRegistry} from "src/interfaces/base/IRegistry.sol"; + +interface IDefaultLimiterFactory is IRegistry { + /** + * @notice Create a default limiter. + */ + function create(address networkResolverLimiter, address operatorNetworkLimiter) external returns (address); +} diff --git a/src/interfaces/stakingController/v1/IStakingController.sol b/src/interfaces/stakingController/v1/IStakingController.sol new file mode 100644 index 00000000..0977372c --- /dev/null +++ b/src/interfaces/stakingController/v1/IStakingController.sol @@ -0,0 +1,235 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.25; + +interface IStakingController { + error InvalidSlashDuration(); + error InsufficientSlash(); + error NetworkNotOptedInVault(); + error NotNetwork(); + error NotNetworkMiddleware(); + error NotOperator(); + error NotResolver(); + error NotVault(); + error OperatorNotOptedInNetwork(); + error OperatorNotOptedInVault(); + error SlashCompleted(); + error SlashPeriodEnded(); + error SlashRequestNotExist(); + error VetoPeriodEnded(); + error VetoPeriodNotEnded(); + + /** + * @notice Structure for a slash request. + * @param network network that requested the slash + * @param resolver resolver that can veto the slash + * @param operator operator that could be slashed (if the request is not vetoed) + * @param amount maximum amount of the collateral to be slashed + * @param vetoDeadline deadline for the resolver to veto the slash (exclusively) + * @param executeDeadline deadline to execute slash (exclusively) + * @param completed if the slash was vetoed/executed + */ + struct SlashRequest { + address network; + address resolver; + address operator; + uint256 amount; + uint48 vetoDeadline; + uint48 executeDeadline; + bool completed; + } + + /** + * @notice Emitted when a slash request is created. + * @param slashIndex index of the slash request + * @param network network that requested the slash + * @param resolver resolver that can veto the slash + * @param operator operator that could be slashed (if the request is not vetoed) + * @param slashAmount maximum amount of the collateral to be slashed + * @param vetoDeadline deadline for the resolver to veto the slash (exclusively) + * @param executeDeadline deadline to execute slash (exclusively) + */ + event RequestSlash( + uint256 indexed slashIndex, + address indexed network, + address resolver, + address indexed operator, + uint256 slashAmount, + uint48 vetoDeadline, + uint48 executeDeadline + ); + + /** + * @notice Emitted when a slash request is executed. + * @param slashIndex index of the slash request + * @param slashedAmount amount of the collateral slashed + */ + event ExecuteSlash(uint256 indexed slashIndex, uint256 slashedAmount); + + /** + * @notice Emitted when a slash request is vetoed. + * @param slashIndex index of the slash request + */ + event VetoSlash(uint256 indexed slashIndex); + + /** + * @notice Get the network registry's address. + * @return address of the network registry + */ + function NETWORK_REGISTRY() external view returns (address); + + /** + * @notice Get the vault factory's address. + * @return address of the vault factory + */ + function VAULT_FACTORY() external view returns (address); + + /** + * @notice Get the network middleware service's address. + * @return address of the network middleware service + */ + function NETWORK_MIDDLEWARE_SERVICE() external view returns (address); + + /** + * @notice Get the network-vault opt-in service's address. + * @return address of the network-vault opt-in service + */ + function NETWORK_VAULT_OPT_IN_SERVICE() external view returns (address); + + /** + * @notice Get the operator-vault opt-in service's address. + * @return address of the operator-vault opt-in service + */ + function OPERATOR_VAULT_OPT_IN_SERVICE() external view returns (address); + + /** + * @notice Get the operator-network opt-in service's address. + * @return address of the operator-network opt-in service + */ + function OPERATOR_NETWORK_OPT_IN_SERVICE() external view returns (address); + + /** + * @notice Get the vault's address. + * @return address of the vault + */ + function vault() external view returns (address); + + /** + * @notice Get the limiter address. + * @return address of the limiter + */ + function limiter() external view returns (address); + + /** + * @notice Get a duration during which resolvers can veto slash requests. + * @return duration of the veto period + */ + function vetoDuration() external view returns (uint48); + + /** + * @notice Get a duration during which slash requests can be executed (after the veto period). + * @return duration of the slash period + */ + function executeDuration() external view returns (uint48); + + /** + * @notice Get a total number of slash requests. + * @return total number of slash requests + */ + function slashRequestsLength() external view returns (uint256); + + /** + * @notice Get a particular slash request. + * @param slashIndex index of the slash request + * @return network network that requested the slash + * @return resolver resolver that can veto the slash + * @return operator operator that could be slashed (if the request is not vetoed) + * @return amount maximum amount of the collateral to be slashed + * @return vetoDeadline deadline for the resolver to veto the slash (exclusively) + * @return executeDeadline deadline to execute slash (exclusively) + * @return completed if the slash was vetoed/executed + */ + function slashRequests(uint256 slashIndex) + external + view + returns ( + address network, + address resolver, + address operator, + uint256 amount, + uint48 vetoDeadline, + uint48 executeDeadline, + bool completed + ); + + /** + * @notice Get a maximum amount of collateral that can be slashed + * for a particular network, resolver, and operator in `duration` seconds. + * @param network address of the network + * @param resolver address of the resolver + * @param operator address of the operator + * @param duration duration to get the slashable amount in + * @return maximum amount of the collateral that can be slashed in `duration` seconds + */ + function slashableAmountIn( + address network, + address resolver, + address operator, + uint48 duration + ) external view returns (uint256); + + /** + * @notice Get a maximum amount of collateral that can be slashed for a particular network, resolver, and operator. + * @param network address of the network + * @param resolver address of the resolver + * @param operator address of the operator + * @return maximum amount of the collateral that can be slashed + */ + function slashableAmount(address network, address resolver, address operator) external view returns (uint256); + + /** + * @notice Get a minimum stake that a given network will be able to slash using a particular resolver + * for a certain operator during `duration` (if no cross-slashing). + * @param network address of the network + * @param resolver address of the resolver + * @param operator address of the operator + * @param duration duration to get the minimum slashable stake during + * @return minimum slashable stake during `duration` + */ + function minStakeDuring( + address network, + address resolver, + address operator, + uint48 duration + ) external view returns (uint256); + + /** + * @notice Request a slash using a network and a resolver for a particular operator by a given amount. + * @param network address of the network + * @param resolver address of the resolver + * @param operator address of the operator + * @param amount maximum amount of the collateral to be slashed + * @return slashIndex index of the slash request + * @dev Only network middleware can call this function. + */ + function requestSlash( + address network, + address resolver, + address operator, + uint256 amount + ) external returns (uint256 slashIndex); + + /** + * @notice Execute a slash with a given slash index. + * @param slashIndex index of the slash request + * @return slashedAmount amount of the collateral slashed + * @dev Anyone can call this function. + */ + function executeSlash(uint256 slashIndex) external returns (uint256 slashedAmount); + + /** + * @notice Veto a slash with a given slash index. + * @param slashIndex index of the slash request + * @dev Only a resolver can call this function. + */ + function vetoSlash(uint256 slashIndex) external; +} diff --git a/src/interfaces/stakingController/v1/IStakingControllerFactory.sol b/src/interfaces/stakingController/v1/IStakingControllerFactory.sol new file mode 100644 index 00000000..6b5cf8f0 --- /dev/null +++ b/src/interfaces/stakingController/v1/IStakingControllerFactory.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.25; + +import {IRegistry} from "src/interfaces/base/IRegistry.sol"; + +interface IStakingControllerFactory is IRegistry { + error NotVault(); + + /** + * @notice Create a staking controller. + * @param vault - address of the vault + */ + function create(address vault, uint48 vetoDuration, uint48 executeDuration) external returns (address); +} diff --git a/src/interfaces/vault/v1/IVault.sol b/src/interfaces/vault/v1/IVault.sol index 14b77919..ee30243b 100644 --- a/src/interfaces/vault/v1/IVault.sol +++ b/src/interfaces/vault/v1/IVault.sol @@ -6,49 +6,35 @@ import {IVaultStorage} from "./IVaultStorage.sol"; interface IVault is IVaultStorage { error AlreadyClaimed(); error AlreadySet(); - error ExceedsMaxNetworkResolverLimit(); error InsufficientClaim(); error InsufficientDeposit(); - error InsufficientSlash(); error InsufficientWithdrawal(); error InvalidAdminFee(); error InvalidEpoch(); error InvalidEpochDuration(); - error InvalidSlashDuration(); - error NetworkNotOptedInVault(); error NoDepositWhitelist(); - error NotNetwork(); - error NotNetworkMiddleware(); - error NotOperator(); - error NotResolver(); error NotWhitelistedDepositor(); - error OperatorNotOptedInNetwork(); - error OperatorNotOptedInVault(); - error SlashCompleted(); - error SlashPeriodEnded(); - error SlashRequestNotExist(); + error NotStakingController(); error TooMuchWithdraw(); - error VetoPeriodEnded(); - error VetoPeriodNotEnded(); /** * @notice Initial parameters needed for a vault deployment. * @param collateral vault's underlying collateral * @param epochDuration duration of the vault epoch (it determines sync points for withdrawals) - * @param vetoDuration duration of the veto period for a slash request - * @param executeDuration duration of the slash period for a slash request (after the veto duration has passed) - * @param rewardsDistributor address of the rewards distributor (it must implement IRewardsDistributor interface) * @param adminFee admin fee (up to ADMIN_FEE_BASE inclusively) * @param depositWhitelist if enabling deposit whitelist + * @param stakingControllerFactory factory for creating vault's staking controller + * @param vetoDuration duration of the veto period for a slash request + * @param executeDuration duration of the slash period for a slash request (after the veto duration has passed) */ struct InitParams { address collateral; uint48 epochDuration; - uint48 vetoDuration; - uint48 executeDuration; - address rewardsDistributor; uint256 adminFee; bool depositWhitelist; + address stakingControllerFactory; + uint48 vetoDuration; + uint48 executeDuration; } /** @@ -80,69 +66,6 @@ interface IVault is IVaultStorage { */ event Claim(address indexed claimer, address indexed recipient, uint256 amount); - /** - * @notice Emitted when a slash request is created. - * @param slashIndex index of the slash request - * @param network network that requested the slash - * @param resolver resolver that can veto the slash - * @param operator operator that could be slashed (if the request is not vetoed) - * @param slashAmount maximum amount of the collateral to be slashed - * @param vetoDeadline deadline for the resolver to veto the slash (exclusively) - * @param executeDeadline deadline to execute slash (exclusively) - */ - event RequestSlash( - uint256 indexed slashIndex, - address indexed network, - address resolver, - address indexed operator, - uint256 slashAmount, - uint48 vetoDeadline, - uint48 executeDeadline - ); - - /** - * @notice Emitted when a slash request is executed. - * @param slashIndex index of the slash request - * @param slashedAmount amount of the collateral slashed - */ - event ExecuteSlash(uint256 indexed slashIndex, uint256 slashedAmount); - - /** - * @notice Emitted when a slash request is vetoed. - * @param slashIndex index of the slash request - */ - event VetoSlash(uint256 indexed slashIndex); - - /** - * @notice Emitted when a maximum network-resolver limit is set. - * @param network address of the network - * @param resolver address of the resolver - * @param amount maximum network-resolver limit that can be set - */ - event SetMaxNetworkResolverLimit(address indexed network, address indexed resolver, uint256 amount); - - /** - * @notice Emitted when a network-resolver limit is set. - * @param network address of the network - * @param resolver address of the resolver - * @param amount maximum amount of the collateral that can be slashed - */ - event SetNetworkResolverLimit(address indexed network, address indexed resolver, uint256 amount); - - /** - * @notice Emitted when an operator-network limit is set. - * @param operator address of the operator - * @param network address of the network - * @param amount maximum amount of the collateral that can be slashed - */ - event SetOperatorNetworkLimit(address indexed operator, address indexed network, uint256 amount); - - /** - * @notice Emitted when a rewards distributor is set. - * @param rewardsDistributor address of the rewards distributor - */ - event SetRewardsDistributor(address rewardsDistributor); - /** * @notice Emitted when an admin fee is set. * @param adminFee admin fee @@ -199,89 +122,6 @@ interface IVault is IVaultStorage { */ function pendingWithdrawalsOf(uint256 epoch, address account) external view returns (uint256); - /** - * @notice Get a maximum amount of collateral that can be slashed - * for a particular network, resolver, and operator in `duration` seconds. - * @param network address of the network - * @param resolver address of the resolver - * @param operator address of the operator - * @param duration duration to get the slashable amount in - * @return maximum amount of the collateral that can be slashed in `duration` seconds - */ - function slashableAmountIn( - address network, - address resolver, - address operator, - uint48 duration - ) external view returns (uint256); - - /** - * @notice Get a maximum amount of collateral that can be slashed for a particular network, resolver, and operator. - * @param network address of the network - * @param resolver address of the resolver - * @param operator address of the operator - * @return maximum amount of the collateral that can be slashed - */ - function slashableAmount(address network, address resolver, address operator) external view returns (uint256); - - /** - * @notice Get a network-resolver limit for a particular network and resolver in `duration` seconds. - * @param network address of the network - * @param resolver address of the resolver - * @param duration duration to get the network-resolver limit in - * @return network-resolver limit in `duration` seconds - */ - function networkResolverLimitIn( - address network, - address resolver, - uint48 duration - ) external view returns (uint256); - - /** - * @notice Get a network-resolver limit for a particular network and resolver. - * @param network address of the network - * @param resolver address of the resolver - * @return network-resolver limit - */ - function networkResolverLimit(address network, address resolver) external view returns (uint256); - - /** - * @notice Get an operator-network limit for a particular operator and network in `duration` seconds. - * @param operator address of the operator - * @param network address of the network - * @param duration duration to get the operator-network limit in - * @return operator-network limit in `duration` seconds - */ - function operatorNetworkLimitIn( - address operator, - address network, - uint48 duration - ) external view returns (uint256); - - /** - * @notice Get an operator-network limit for a particular operator and network. - * @param operator address of the operator - * @param network address of the network - * @return operator-network limit - */ - function operatorNetworkLimit(address operator, address network) external view returns (uint256); - - /** - * @notice Get a minimum stake that a given network will be able to slash using a particular resolver - * for a certain operator during `duration` (if no cross-slashing). - * @param network address of the network - * @param resolver address of the resolver - * @param operator address of the operator - * @param duration duration to get the minimum slashable stake during - * @return minimum slashable stake during `duration` - */ - function minStakeDuring( - address network, - address resolver, - address operator, - uint48 duration - ) external view returns (uint256); - /** * @notice Deposit collateral into the vault. * @param onBehalfOf account the deposit is made on behalf of @@ -308,68 +148,11 @@ interface IVault is IVaultStorage { function claim(address recipient, uint256 epoch) external returns (uint256 amount); /** - * @notice Request a slash using a network and a resolver for a particular operator by a given amount. - * @param network address of the network - * @param resolver address of the resolver - * @param operator address of the operator - * @param amount maximum amount of the collateral to be slashed - * @return slashIndex index of the slash request - * @dev Only network middleware can call this function. - */ - function requestSlash( - address network, - address resolver, - address operator, - uint256 amount - ) external returns (uint256 slashIndex); - - /** - * @notice Execute a slash with a given slash index. - * @param slashIndex index of the slash request - * @return slashedAmount amount of the collateral slashed - * @dev Anyone can call this function. - */ - function executeSlash(uint256 slashIndex) external returns (uint256 slashedAmount); - - /** - * @notice Veto a slash with a given slash index. - * @param slashIndex index of the slash request - * @dev Only a resolver can call this function. - */ - function vetoSlash(uint256 slashIndex) external; - - /** - * @notice Set a maximum network-resolver limit. - * @param resolver address of the resolver - * @param amount maximum network-resolver limit that can be set - * @dev Only a network can call this function. - */ - function setMaxNetworkResolverLimit(address resolver, uint256 amount) external; - - /** - * @notice Set a network-resolver limit for a particular network and resolver. - * @param network address of the network - * @param resolver address of the resolver - * @param amount new maximum amount of the collateral that can be slashed - * @dev Only the NETWORK_RESOLVER_LIMIT_SET_ROLE holder can call this function. - */ - function setNetworkResolverLimit(address network, address resolver, uint256 amount) external; - - /** - * @notice Set an operator-network limit for a particular operator and network. - * @param operator address of the operator - * @param network address of the network - * @param amount new maximum amount of the collateral that can be slashed - * @dev Only the OPERATOR_NETWORK_LIMIT_SET_ROLE holder can call this function. - */ - function setOperatorNetworkLimit(address operator, address network, uint256 amount) external; - - /** - * @notice Set a rewards distributor. - * @param rewardsDistributor address of the rewards distributor - * @dev Only the REWARDS_DISTRIBUTOR_SET_ROLE holder can call this function. + * @notice Slash callback for burning collateral. + * @param slashedAmount amount to slash + * @return actualSlashedAmount amount of burned collateral */ - function setRewardsDistributor(address rewardsDistributor) external; + function onSlash(uint256 slashedAmount) external returns (uint256 actualSlashedAmount); /** * @notice Set an admin fee. diff --git a/src/interfaces/vault/v1/IVaultStorage.sol b/src/interfaces/vault/v1/IVaultStorage.sol index ea1299cc..2ac60921 100644 --- a/src/interfaces/vault/v1/IVaultStorage.sol +++ b/src/interfaces/vault/v1/IVaultStorage.sol @@ -5,55 +5,12 @@ interface IVaultStorage { error InvalidTimestamp(); error NoPreviousEpoch(); - /** - * @notice Structure for a slashing limit. - * @param amount amount of the collateral that can be slashed - */ - struct Limit { - uint256 amount; - } - - /** - * @notice Structure for a slashing limit that will be set in the future (if a new limit won't be set). - * @param amount amount of the collateral that can be slashed - * @param timestamp timestamp when the limit will be set - */ - struct DelayedLimit { - uint256 amount; - uint48 timestamp; - } - - /** - * @notice Structure for a slash request. - * @param network network that requested the slash - * @param resolver resolver that can veto the slash - * @param operator operator that could be slashed (if the request is not vetoed) - * @param amount maximum amount of the collateral to be slashed - * @param vetoDeadline deadline for the resolver to veto the slash (exclusively) - * @param executeDeadline deadline to execute slash (exclusively) - * @param completed if the slash was vetoed/executed - */ - struct SlashRequest { - address network; - address resolver; - address operator; - uint256 amount; - uint48 vetoDeadline; - uint48 executeDeadline; - bool completed; - } - /** * @notice Get the maximum admin fee (= 100%). * @return maximum admin fee */ function ADMIN_FEE_BASE() external view returns (uint256); - /** - * @notice Get the rewards distributor setter's role. - */ - function REWARDS_DISTRIBUTOR_SET_ROLE() external view returns (bytes32); - /** * @notice Get the admin fee setter's role. */ @@ -70,44 +27,10 @@ interface IVaultStorage { function DEPOSITOR_WHITELIST_ROLE() external view returns (bytes32); /** - * @notice Get the network-resolver limit setter's role. - */ - function NETWORK_RESOLVER_LIMIT_SET_ROLE() external view returns (bytes32); - - /** - * @notice Get the operator-network limit setter's role. - */ - function OPERATOR_NETWORK_LIMIT_SET_ROLE() external view returns (bytes32); - - /** - * @notice Get the network registry's address. - * @return address of the network registry - */ - function NETWORK_REGISTRY() external view returns (address); - - /** - * @notice Get the network middleware service's address. - * @return address of the network middleware service - */ - function NETWORK_MIDDLEWARE_SERVICE() external view returns (address); - - /** - * @notice Get the network-vault opt-in service's address. - * @return address of the network-vault opt-in service - */ - function NETWORK_VAULT_OPT_IN_SERVICE() external view returns (address); - - /** - * @notice Get the operator-vault opt-in service's address. - * @return address of the operator-vault opt-in service + * @notice Get a vault's staking controoler. + * @return vault's staking controller */ - function OPERATOR_VAULT_OPT_IN_SERVICE() external view returns (address); - - /** - * @notice Get the operator-network opt-in service's address. - * @return address of the operator-network opt-in service - */ - function OPERATOR_NETWORK_OPT_IN_SERVICE() external view returns (address); + function stakingController() external view returns (address); /** * @notice Get a vault collateral. @@ -154,25 +77,6 @@ interface IVaultStorage { */ function previousEpochStart() external view returns (uint48); - /** - * @notice Get a duration during which resolvers can veto slash requests. - * @return duration of the veto period - */ - function vetoDuration() external view returns (uint48); - - /** - * @notice Get a duration during which slash requests can be executed (after the veto period). - * @return duration of the slash period - */ - function executeDuration() external view returns (uint48); - - /** - * @notice Get a rewards distributor. - * @return address of the rewards distributor - * @dev It must implement the IRewardsDistributor interface. - */ - function rewardsDistributor() external view returns (address); - /** * @notice Get an admin fee. * @return admin fee @@ -286,60 +190,4 @@ interface IVaultStorage { * @return amount of pending withdrawal shares for the account at the epoch */ function pendingWithdrawalSharesOf(uint256 epoch, address account) external view returns (uint256); - - /** - * @notice Get a total number of slash requests. - * @return total number of slash requests - */ - function slashRequestsLength() external view returns (uint256); - - /** - * @notice Get a particular slash request. - * @param slashIndex index of the slash request - * @return network network that requested the slash - * @return resolver resolver that can veto the slash - * @return operator operator that could be slashed (if the request is not vetoed) - * @return amount maximum amount of the collateral to be slashed - * @return vetoDeadline deadline for the resolver to veto the slash (exclusively) - * @return executeDeadline deadline to execute slash (exclusively) - * @return completed if the slash was vetoed/executed - */ - function slashRequests(uint256 slashIndex) - external - view - returns ( - address network, - address resolver, - address operator, - uint256 amount, - uint48 vetoDeadline, - uint48 executeDeadline, - bool completed - ); - - /** - * @notice Get a maximum network-resolver limit for a particular network and resolver. - * @param network address of the network - * @param resolver address of the resolver - * @return maximum network-resolver limit - */ - function maxNetworkResolverLimit(address network, address resolver) external view returns (uint256); - - /** - * @notice Get the next network-resolver limit for a particular network and resolver. - * @param network address of the network - * @param resolver address of the resolver - * @return next network-resolver limit - * @return timestamp when the limit will be set - */ - function nextNetworkResolverLimit(address network, address resolver) external view returns (uint256, uint48); - - /** - * @notice Get the next operator-network limit for a particular operator and network. - * @param operator address of the operator - * @param network address of the network - * @return next operator-network limit - * @return timestamp when the limit will be set - */ - function nextOperatorNetworkLimit(address operator, address network) external view returns (uint256, uint48); }