// SPDX-License-Identifier: MIT pragma solidity ^0.8.9; import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import { PausableUpgradeable } from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol"; import "./interfaces/IDelegationSlasher.sol"; import "./interfaces/IDelegation.sol"; import "./interfaces/IDelegationManager.sol"; /** * @title The primary 'slashing' contract. * @notice This contract specifies details on slashing. The functionalities are: * - adding contracts who have permission to perform slashing, * - revoking permission for slashing from specified contracts, * - calling investManager to do actual slashing. */ abstract contract DelegationSlasher is Initializable, OwnableUpgradeable, PausableUpgradeable, IDelegationSlasher { // ,DSTest /// @notice The central InvestmentManager contract IDelegationManager public immutable investmentManager; /// @notice The Delegation contract IDelegation public immutable delegation; // contract address => whether or not the contract is allowed to slash any staker (or operator) mapping(address => bool) public globallyPermissionedContracts; // user => contract => the time before which the contract is allowed to slash the user mapping(address => mapping(address => uint32)) public bondedUntil; // staker => if their funds are 'frozen' and potentially subject to slashing or not mapping(address => bool) internal frozenStatus; uint32 internal constant MAX_BONDED_UNTIL = type(uint32).max; event GloballyPermissionedContractAdded(address indexed contractAdded); event GloballyPermissionedContractRemoved(address indexed contractRemoved); event OptedIntoSlashing(address indexed operator, address indexed contractAddress); event SlashingAbilityRevoked(address indexed operator, address indexed contractAddress, uint32 unbondedAfter); event OperatorSlashed(address indexed slashedOperator, address indexed slashingContract); event FrozenStatusReset(address indexed previouslySlashedAddress); constructor(IDelegationManager _investmentManager, IDelegation _delegation) { investmentManager = _investmentManager; delegation = _delegation; _disableInitializers(); } // EXTERNAL FUNCTIONS function initialize( address initialOwner ) external initializer { _transferOwnership(initialOwner); // add InvestmentManager & Delegation to list of permissioned contracts _addGloballyPermissionedContract(address(investmentManager)); _addGloballyPermissionedContract(address(delegation)); } /** * @notice Gives the `contractAddress` permission to slash the funds of the caller. * @dev Typically, this function must be called prior to registering for a middleware. */ function allowToSlash(address contractAddress) external { _optIntoSlashing(msg.sender, contractAddress); } /* TODO: we still need to figure out how/when to appropriately call this function perhaps a registry can safely call this function after an operator has been deregistered for a very safe amount of time (like a month) */ /// @notice Called by a contract to revoke its ability to slash `operator`, once `unbondedAfter` is reached. function revokeSlashingAbility(address operator, uint32 unbondedAfter) external { _revokeSlashingAbility(operator, msg.sender, unbondedAfter); } /** * @notice Used for 'slashing' a certain operator. * @param toBeFrozen The operator to be frozen. * @dev Technically the operator is 'frozen' (hence the name of this function), and then subject to slashing pending a decision by a human-in-the-loop. * @dev The operator must have previously given the caller (which should be a contract) the ability to slash them, through a call to `allowToSlash`. */ function freezeOperator(address toBeFrozen) external whenNotPaused { require( canSlash(toBeFrozen, msg.sender), "Slasher.freezeOperator: msg.sender does not have permission to slash this operator" ); _freezeOperator(toBeFrozen, msg.sender); } /** * @notice Used to give global slashing permission to `contracts`. * @dev Callable only by the contract owner (i.e. governance). */ function addGloballyPermissionedContracts(address[] calldata contracts) external onlyOwner { for (uint256 i = 0; i < contracts.length;) { _addGloballyPermissionedContract(contracts[i]); unchecked { ++i; } } } /** * @notice Used to revoke global slashing permission from `contracts`. * @dev Callable only by the contract owner (i.e. governance). */ function removeGloballyPermissionedContracts(address[] calldata contracts) external onlyOwner { for (uint256 i = 0; i < contracts.length;) { _removeGloballyPermissionedContract(contracts[i]); unchecked { ++i; } } } /** * @notice Removes the 'frozen' status from each of the `frozenAddresses` * @dev Callable only by the contract owner (i.e. governance). */ function resetFrozenStatus(address[] calldata frozenAddresses) external onlyOwner { for (uint256 i = 0; i < frozenAddresses.length;) { _resetFrozenStatus(frozenAddresses[i]); unchecked { ++i; } } } // INTERNAL FUNCTIONS function _optIntoSlashing(address operator, address contractAddress) internal { //allow the contract to slash anytime before a time VERY far in the future bondedUntil[operator][contractAddress] = MAX_BONDED_UNTIL; emit OptedIntoSlashing(operator, contractAddress); } function _revokeSlashingAbility(address operator, address contractAddress, uint32 unbondedAfter) internal { if (bondedUntil[operator][contractAddress] == MAX_BONDED_UNTIL) { //contractAddress can now only slash operator before unbondedAfter bondedUntil[operator][contractAddress] = unbondedAfter; emit SlashingAbilityRevoked(operator, contractAddress, unbondedAfter); } } function _addGloballyPermissionedContract(address contractToAdd) internal { if (!globallyPermissionedContracts[contractToAdd]) { globallyPermissionedContracts[contractToAdd] = true; emit GloballyPermissionedContractAdded(contractToAdd); } } function _removeGloballyPermissionedContract(address contractToRemove) internal { if (globallyPermissionedContracts[contractToRemove]) { globallyPermissionedContracts[contractToRemove] = false; emit GloballyPermissionedContractRemoved(contractToRemove); } } function _freezeOperator(address toBeFrozen, address slashingContract) internal { if (!frozenStatus[toBeFrozen]) { frozenStatus[toBeFrozen] = true; emit OperatorSlashed(toBeFrozen, slashingContract); } } function _resetFrozenStatus(address previouslySlashedAddress) internal { if (frozenStatus[previouslySlashedAddress]) { frozenStatus[previouslySlashedAddress] = false; emit FrozenStatusReset(previouslySlashedAddress); } } // VIEW FUNCTIONS /** * @notice Used to determine whether `staker` is actively 'frozen'. If a staker is frozen, then they are potentially subject to * slashing of their funds, and cannot cannot deposit or withdraw from the investmentManager until the slashing process is completed * and the staker's status is reset (to 'unfrozen'). * @return Returns 'true' if `staker` themselves has their status set to frozen, OR if the staker is delegated * to an operator who has their status set to frozen. Otherwise returns 'false'. */ function isFrozen(address staker) external view returns (bool) { if (frozenStatus[staker]) { return true; } else if (delegation.isDelegated(staker)) { address operatorAddress = delegation.delegatedTo(staker); return (frozenStatus[operatorAddress]); } else { return false; } } /// @notice Returns true if `slashingContract` is currently allowed to slash `toBeSlashed`. function canSlash(address toBeSlashed, address slashingContract) public view returns (bool) { if (globallyPermissionedContracts[slashingContract]) { return true; } else if (block.timestamp < bondedUntil[toBeSlashed][slashingContract]) { return true; } else { return false; } } }