// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (access/extensions/AccessControlDefaultAdminRules.sol) pragma solidity ^0.8.20; import {IAccessControlDefaultAdminRules} from "@openzeppelin/contracts/access/extensions/IAccessControlDefaultAdminRules.sol"; import {AccessControlUpgradeable} from "../AccessControlUpgradeable.sol"; import {IAccessControl} from "@openzeppelin/contracts/access/IAccessControl.sol"; import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; import {IERC5313} from "@openzeppelin/contracts/interfaces/IERC5313.sol"; import {Initializable} from "../../proxy/utils/Initializable.sol"; /** * @dev Extension of {AccessControl} that allows specifying special rules to manage * the `DEFAULT_ADMIN_ROLE` holder, which is a sensitive role with special permissions * over other roles that may potentially have privileged rights in the system. * * If a specific role doesn't have an admin role assigned, the holder of the * `DEFAULT_ADMIN_ROLE` will have the ability to grant it and revoke it. * * This contract implements the following risk mitigations on top of {AccessControl}: * * * Only one account holds the `DEFAULT_ADMIN_ROLE` since deployment until it's potentially renounced. * * Enforces a 2-step process to transfer the `DEFAULT_ADMIN_ROLE` to another account. * * Enforces a configurable delay between the two steps, with the ability to cancel before the transfer is accepted. * * The delay can be changed by scheduling, see {changeDefaultAdminDelay}. * * It is not possible to use another role to manage the `DEFAULT_ADMIN_ROLE`. * * Example usage: * * ```solidity * contract MyToken is AccessControlDefaultAdminRules { * constructor() AccessControlDefaultAdminRules( * 3 days, * msg.sender // Explicit initial `DEFAULT_ADMIN_ROLE` holder * ) {} * } * ``` */ abstract contract AccessControlDefaultAdminRulesUpgradeable is Initializable, IAccessControlDefaultAdminRules, IERC5313, AccessControlUpgradeable { /// @custom:storage-location erc7201:openzeppelin.storage.AccessControlDefaultAdminRules struct AccessControlDefaultAdminRulesStorage { // pending admin pair read/written together frequently address _pendingDefaultAdmin; uint48 _pendingDefaultAdminSchedule; // 0 == unset uint48 _currentDelay; address _currentDefaultAdmin; // pending delay pair read/written together frequently uint48 _pendingDelay; uint48 _pendingDelaySchedule; // 0 == unset } // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.AccessControlDefaultAdminRules")) - 1)) & ~bytes32(uint256(0xff)) bytes32 private constant AccessControlDefaultAdminRulesStorageLocation = 0xeef3dac4538c82c8ace4063ab0acd2d15cdb5883aa1dff7c2673abb3d8698400; function _getAccessControlDefaultAdminRulesStorage() private pure returns (AccessControlDefaultAdminRulesStorage storage $) { assembly { $.slot := AccessControlDefaultAdminRulesStorageLocation } } /** * @dev Sets the initial values for {defaultAdminDelay} and {defaultAdmin} address. */ function __AccessControlDefaultAdminRules_init(uint48 initialDelay, address initialDefaultAdmin) internal onlyInitializing { __AccessControlDefaultAdminRules_init_unchained(initialDelay, initialDefaultAdmin); } function __AccessControlDefaultAdminRules_init_unchained(uint48 initialDelay, address initialDefaultAdmin) internal onlyInitializing { AccessControlDefaultAdminRulesStorage storage $ = _getAccessControlDefaultAdminRulesStorage(); if (initialDefaultAdmin == address(0)) { revert AccessControlInvalidDefaultAdmin(address(0)); } $._currentDelay = initialDelay; _grantRole(DEFAULT_ADMIN_ROLE, initialDefaultAdmin); } /** * @dev See {IERC165-supportsInterface}. */ function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { return interfaceId == type(IAccessControlDefaultAdminRules).interfaceId || super.supportsInterface(interfaceId); } /** * @dev See {IERC5313-owner}. */ function owner() public view virtual returns (address) { return defaultAdmin(); } /// /// Override AccessControl role management /// /** * @dev See {AccessControl-grantRole}. Reverts for `DEFAULT_ADMIN_ROLE`. */ function grantRole(bytes32 role, address account) public virtual override(AccessControlUpgradeable, IAccessControl) { if (role == DEFAULT_ADMIN_ROLE) { revert AccessControlEnforcedDefaultAdminRules(); } super.grantRole(role, account); } /** * @dev See {AccessControl-revokeRole}. Reverts for `DEFAULT_ADMIN_ROLE`. */ function revokeRole(bytes32 role, address account) public virtual override(AccessControlUpgradeable, IAccessControl) { if (role == DEFAULT_ADMIN_ROLE) { revert AccessControlEnforcedDefaultAdminRules(); } super.revokeRole(role, account); } /** * @dev See {AccessControl-renounceRole}. * * For the `DEFAULT_ADMIN_ROLE`, it only allows renouncing in two steps by first calling * {beginDefaultAdminTransfer} to the `address(0)`, so it's required that the {pendingDefaultAdmin} schedule * has also passed when calling this function. * * After its execution, it will not be possible to call `onlyRole(DEFAULT_ADMIN_ROLE)` functions. * * NOTE: Renouncing `DEFAULT_ADMIN_ROLE` will leave the contract without a {defaultAdmin}, * thereby disabling any functionality that is only available for it, and the possibility of reassigning a * non-administrated role. */ function renounceRole(bytes32 role, address account) public virtual override(AccessControlUpgradeable, IAccessControl) { AccessControlDefaultAdminRulesStorage storage $ = _getAccessControlDefaultAdminRulesStorage(); if (role == DEFAULT_ADMIN_ROLE && account == defaultAdmin()) { (address newDefaultAdmin, uint48 schedule) = pendingDefaultAdmin(); if (newDefaultAdmin != address(0) || !_isScheduleSet(schedule) || !_hasSchedulePassed(schedule)) { revert AccessControlEnforcedDefaultAdminDelay(schedule); } delete $._pendingDefaultAdminSchedule; } super.renounceRole(role, account); } /** * @dev See {AccessControl-_grantRole}. * * For `DEFAULT_ADMIN_ROLE`, it only allows granting if there isn't already a {defaultAdmin} or if the * role has been previously renounced. * * NOTE: Exposing this function through another mechanism may make the `DEFAULT_ADMIN_ROLE` * assignable again. Make sure to guarantee this is the expected behavior in your implementation. */ function _grantRole(bytes32 role, address account) internal virtual override returns (bool) { AccessControlDefaultAdminRulesStorage storage $ = _getAccessControlDefaultAdminRulesStorage(); if (role == DEFAULT_ADMIN_ROLE) { if (defaultAdmin() != address(0)) { revert AccessControlEnforcedDefaultAdminRules(); } $._currentDefaultAdmin = account; } return super._grantRole(role, account); } /** * @dev See {AccessControl-_revokeRole}. */ function _revokeRole(bytes32 role, address account) internal virtual override returns (bool) { AccessControlDefaultAdminRulesStorage storage $ = _getAccessControlDefaultAdminRulesStorage(); if (role == DEFAULT_ADMIN_ROLE && account == defaultAdmin()) { delete $._currentDefaultAdmin; } return super._revokeRole(role, account); } /** * @dev See {AccessControl-_setRoleAdmin}. Reverts for `DEFAULT_ADMIN_ROLE`. */ function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual override { if (role == DEFAULT_ADMIN_ROLE) { revert AccessControlEnforcedDefaultAdminRules(); } super._setRoleAdmin(role, adminRole); } /// /// AccessControlDefaultAdminRules accessors /// /** * @inheritdoc IAccessControlDefaultAdminRules */ function defaultAdmin() public view virtual returns (address) { AccessControlDefaultAdminRulesStorage storage $ = _getAccessControlDefaultAdminRulesStorage(); return $._currentDefaultAdmin; } /** * @inheritdoc IAccessControlDefaultAdminRules */ function pendingDefaultAdmin() public view virtual returns (address newAdmin, uint48 schedule) { AccessControlDefaultAdminRulesStorage storage $ = _getAccessControlDefaultAdminRulesStorage(); return ($._pendingDefaultAdmin, $._pendingDefaultAdminSchedule); } /** * @inheritdoc IAccessControlDefaultAdminRules */ function defaultAdminDelay() public view virtual returns (uint48) { AccessControlDefaultAdminRulesStorage storage $ = _getAccessControlDefaultAdminRulesStorage(); uint48 schedule = $._pendingDelaySchedule; return (_isScheduleSet(schedule) && _hasSchedulePassed(schedule)) ? $._pendingDelay : $._currentDelay; } /** * @inheritdoc IAccessControlDefaultAdminRules */ function pendingDefaultAdminDelay() public view virtual returns (uint48 newDelay, uint48 schedule) { AccessControlDefaultAdminRulesStorage storage $ = _getAccessControlDefaultAdminRulesStorage(); schedule = $._pendingDelaySchedule; return (_isScheduleSet(schedule) && !_hasSchedulePassed(schedule)) ? ($._pendingDelay, schedule) : (0, 0); } /** * @inheritdoc IAccessControlDefaultAdminRules */ function defaultAdminDelayIncreaseWait() public view virtual returns (uint48) { return 5 days; } /// /// AccessControlDefaultAdminRules public and internal setters for defaultAdmin/pendingDefaultAdmin /// /** * @inheritdoc IAccessControlDefaultAdminRules */ function beginDefaultAdminTransfer(address newAdmin) public virtual onlyRole(DEFAULT_ADMIN_ROLE) { _beginDefaultAdminTransfer(newAdmin); } /** * @dev See {beginDefaultAdminTransfer}. * * Internal function without access restriction. */ function _beginDefaultAdminTransfer(address newAdmin) internal virtual { uint48 newSchedule = SafeCast.toUint48(block.timestamp) + defaultAdminDelay(); _setPendingDefaultAdmin(newAdmin, newSchedule); emit DefaultAdminTransferScheduled(newAdmin, newSchedule); } /** * @inheritdoc IAccessControlDefaultAdminRules */ function cancelDefaultAdminTransfer() public virtual onlyRole(DEFAULT_ADMIN_ROLE) { _cancelDefaultAdminTransfer(); } /** * @dev See {cancelDefaultAdminTransfer}. * * Internal function without access restriction. */ function _cancelDefaultAdminTransfer() internal virtual { _setPendingDefaultAdmin(address(0), 0); } /** * @inheritdoc IAccessControlDefaultAdminRules */ function acceptDefaultAdminTransfer() public virtual { (address newDefaultAdmin, ) = pendingDefaultAdmin(); if (_msgSender() != newDefaultAdmin) { // Enforce newDefaultAdmin explicit acceptance. revert AccessControlInvalidDefaultAdmin(_msgSender()); } _acceptDefaultAdminTransfer(); } /** * @dev See {acceptDefaultAdminTransfer}. * * Internal function without access restriction. */ function _acceptDefaultAdminTransfer() internal virtual { AccessControlDefaultAdminRulesStorage storage $ = _getAccessControlDefaultAdminRulesStorage(); (address newAdmin, uint48 schedule) = pendingDefaultAdmin(); if (!_isScheduleSet(schedule) || !_hasSchedulePassed(schedule)) { revert AccessControlEnforcedDefaultAdminDelay(schedule); } _revokeRole(DEFAULT_ADMIN_ROLE, defaultAdmin()); _grantRole(DEFAULT_ADMIN_ROLE, newAdmin); delete $._pendingDefaultAdmin; delete $._pendingDefaultAdminSchedule; } /// /// AccessControlDefaultAdminRules public and internal setters for defaultAdminDelay/pendingDefaultAdminDelay /// /** * @inheritdoc IAccessControlDefaultAdminRules */ function changeDefaultAdminDelay(uint48 newDelay) public virtual onlyRole(DEFAULT_ADMIN_ROLE) { _changeDefaultAdminDelay(newDelay); } /** * @dev See {changeDefaultAdminDelay}. * * Internal function without access restriction. */ function _changeDefaultAdminDelay(uint48 newDelay) internal virtual { uint48 newSchedule = SafeCast.toUint48(block.timestamp) + _delayChangeWait(newDelay); _setPendingDelay(newDelay, newSchedule); emit DefaultAdminDelayChangeScheduled(newDelay, newSchedule); } /** * @inheritdoc IAccessControlDefaultAdminRules */ function rollbackDefaultAdminDelay() public virtual onlyRole(DEFAULT_ADMIN_ROLE) { _rollbackDefaultAdminDelay(); } /** * @dev See {rollbackDefaultAdminDelay}. * * Internal function without access restriction. */ function _rollbackDefaultAdminDelay() internal virtual { _setPendingDelay(0, 0); } /** * @dev Returns the amount of seconds to wait after the `newDelay` will * become the new {defaultAdminDelay}. * * The value returned guarantees that if the delay is reduced, it will go into effect * after a wait that honors the previously set delay. * * See {defaultAdminDelayIncreaseWait}. */ function _delayChangeWait(uint48 newDelay) internal view virtual returns (uint48) { uint48 currentDelay = defaultAdminDelay(); // When increasing the delay, we schedule the delay change to occur after a period of "new delay" has passed, up // to a maximum given by defaultAdminDelayIncreaseWait, by default 5 days. For example, if increasing from 1 day // to 3 days, the new delay will come into effect after 3 days. If increasing from 1 day to 10 days, the new // delay will come into effect after 5 days. The 5 day wait period is intended to be able to fix an error like // using milliseconds instead of seconds. // // When decreasing the delay, we wait the difference between "current delay" and "new delay". This guarantees // that an admin transfer cannot be made faster than "current delay" at the time the delay change is scheduled. // For example, if decreasing from 10 days to 3 days, the new delay will come into effect after 7 days. return newDelay > currentDelay ? uint48(Math.min(newDelay, defaultAdminDelayIncreaseWait())) // no need to safecast, both inputs are uint48 : currentDelay - newDelay; } /// /// Private setters /// /** * @dev Setter of the tuple for pending admin and its schedule. * * May emit a DefaultAdminTransferCanceled event. */ function _setPendingDefaultAdmin(address newAdmin, uint48 newSchedule) private { AccessControlDefaultAdminRulesStorage storage $ = _getAccessControlDefaultAdminRulesStorage(); (, uint48 oldSchedule) = pendingDefaultAdmin(); $._pendingDefaultAdmin = newAdmin; $._pendingDefaultAdminSchedule = newSchedule; // An `oldSchedule` from `pendingDefaultAdmin()` is only set if it hasn't been accepted. if (_isScheduleSet(oldSchedule)) { // Emit for implicit cancellations when another default admin was scheduled. emit DefaultAdminTransferCanceled(); } } /** * @dev Setter of the tuple for pending delay and its schedule. * * May emit a DefaultAdminDelayChangeCanceled event. */ function _setPendingDelay(uint48 newDelay, uint48 newSchedule) private { AccessControlDefaultAdminRulesStorage storage $ = _getAccessControlDefaultAdminRulesStorage(); uint48 oldSchedule = $._pendingDelaySchedule; if (_isScheduleSet(oldSchedule)) { if (_hasSchedulePassed(oldSchedule)) { // Materialize a virtual delay $._currentDelay = $._pendingDelay; } else { // Emit for implicit cancellations when another delay was scheduled. emit DefaultAdminDelayChangeCanceled(); } } $._pendingDelay = newDelay; $._pendingDelaySchedule = newSchedule; } /// /// Private helpers /// /** * @dev Defines if an `schedule` is considered set. For consistency purposes. */ function _isScheduleSet(uint48 schedule) private pure returns (bool) { return schedule != 0; } /** * @dev Defines if an `schedule` is considered passed. For consistency purposes. */ function _hasSchedulePassed(uint48 schedule) private view returns (bool) { return schedule < block.timestamp; } }