mirror of openzeppelin-contracts
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
openzeppelin-contracts/contracts/access/manager/AccessManager.sol

341 lines
13 KiB

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import "../AccessControl.sol";
import "../AccessControlDefaultAdminRules.sol";
import "./IAuthority.sol";
import "./AccessManaged.sol";
interface IAccessManager is IAuthority, IAccessControlDefaultAdminRules {
enum AccessMode {
Custom,
Closed,
Open
}
event GroupUpdated(uint8 indexed group, string name);
event GroupAllowed(address indexed target, bytes4 indexed selector, uint8 indexed group, bool allowed);
event AccessModeUpdated(address indexed target, AccessMode indexed mode);
function createGroup(uint8 group, string calldata name) external;
function updateGroupName(uint8 group, string calldata name) external;
function hasGroup(uint8 group) external view returns (bool);
function getUserGroups(address user) external view returns (bytes32 groups);
function grantGroup(uint8 group, address user) external;
function revokeGroup(uint8 group, address user) external;
function renounceGroup(uint8 group, address user) external;
function getFunctionAllowedGroups(address target, bytes4 selector) external view returns (bytes32 groups);
function setFunctionAllowedGroup(address target, bytes4[] calldata selectors, uint8 group, bool allowed) external;
function getContractMode(address target) external view returns (AccessMode);
function setContractModeCustom(address target) external;
function setContractModeOpen(address target) external;
function setContractModeClosed(address target) external;
function transferContractAuthority(address target, address newAuthority) external;
}
/**
* @dev AccessManager is a central contract to store the permissions of a system.
*
* The smart contracts under the control of an AccessManager instance will have a set of "restricted" functions, and the
* exact details of how access is restricted for each of those functions is configurable by the admins of the instance.
* These restrictions are expressed in terms of "groups".
*
* An AccessManager instance will define a set of groups. Each of them must be created before they can be granted, with
* a maximum of 255 created groups. Users can be added into any number of these groups. Each of them defines an
* AccessControl role, and may confer access to some of the restricted functions in the system, as configured by admins
* through the use of {setFunctionAllowedGroup}.
*
* Note that a function in a target contract may become permissioned in this way only when: 1) said contract is
* {AccessManaged} and is connected to this contract as its manager, and 2) said function is decorated with the
* `restricted` modifier.
*
* There is a special group defined by default named "public" which all accounts automatically have.
*
* Contracts can also be configured in two special modes: 1) the "open" mode, where all functions are allowed to the
* "public" group, and 2) the "closed" mode, where no function is allowed to any group.
*
* Since all the permissions of the managed system can be modified by the admins of this instance, it is expected that
* it will be highly secured (e.g., a multisig or a well-configured DAO). Additionally, {AccessControlDefaultAdminRules}
* is included to enforce security rules on this account.
*
* NOTE: Some of the functions in this contract, such as {getUserGroups}, return a `bytes32` bitmap to succintly
* represent a set of groups. In a bitmap, bit `n` (counting from the least significant bit) will be 1 if and only if
* the group with number `n` is in the set. For example, the hex value `0x05` represents the set of the two groups
* numbered 0 and 2 from its binary equivalence `0b101`
*/
contract AccessManager is IAccessManager, AccessControlDefaultAdminRules {
bytes32 _createdGroups;
// user -> groups
mapping(address => bytes32) private _userGroups;
// target -> selector -> groups
mapping(address => mapping(bytes4 => bytes32)) private _allowedGroups;
// target -> mode
mapping(address => AccessMode) private _contractMode;
uint8 private constant _GROUP_PUBLIC = type(uint8).max;
/**
* @dev Initializes an AccessManager with initial default admin and transfer delay.
*/
constructor(
uint48 initialDefaultAdminDelay,
address initialDefaultAdmin
) AccessControlDefaultAdminRules(initialDefaultAdminDelay, initialDefaultAdmin) {
_createGroup(_GROUP_PUBLIC, "public");
}
/**
* @dev Returns true if the caller can invoke on a target the function identified by a function selector.
* Entrypoint for {AccessManaged} contracts.
*/
function canCall(address caller, address target, bytes4 selector) public view virtual returns (bool) {
bytes32 allowedGroups = getFunctionAllowedGroups(target, selector);
bytes32 callerGroups = getUserGroups(caller);
return callerGroups & allowedGroups != 0;
}
/**
* @dev Creates a new group with a group number that can be chosen arbitrarily but must be unused, and gives it a
* human-readable name. The caller must be the default admin.
*
* Group numbers are not auto-incremented in order to avoid race conditions, but administrators can safely use
* sequential numbers.
*
* Emits {GroupUpdated}.
*/
function createGroup(uint8 group, string memory name) public virtual onlyRole(DEFAULT_ADMIN_ROLE) {
_createGroup(group, name);
}
/**
* @dev Updates an existing group's name. The caller must be the default admin.
*/
function updateGroupName(uint8 group, string memory name) public virtual onlyRole(DEFAULT_ADMIN_ROLE) {
require(group != _GROUP_PUBLIC, "AccessManager: built-in group");
require(hasGroup(group), "AccessManager: unknown group");
emit GroupUpdated(group, name);
}
/**
* @dev Returns true if the group has already been created via {createGroup}.
*/
function hasGroup(uint8 group) public view virtual returns (bool) {
return _getGroup(_createdGroups, group);
}
/**
* @dev Returns a bitmap of the groups the user has. See note on bitmaps above.
*/
function getUserGroups(address user) public view virtual returns (bytes32) {
return _userGroups[user] | _groupMask(_GROUP_PUBLIC);
}
/**
* @dev Grants a user a group.
*
* Emits {RoleGranted} with the role id of the group, if wasn't already held by the user.
*/
function grantGroup(uint8 group, address user) public virtual {
grantRole(_encodeGroupRole(group), user); // will check msg.sender
}
/**
* @dev Removes a group from a user.
*
* Emits {RoleRevoked} with the role id of the group, if previously held by the user.
*/
function revokeGroup(uint8 group, address user) public virtual {
revokeRole(_encodeGroupRole(group), user); // will check msg.sender
}
/**
* @dev Allows a user to renounce a group.
*
* Emits {RoleRevoked} with the role id of the group, if previously held by the user.
*/
function renounceGroup(uint8 group, address user) public virtual {
renounceRole(_encodeGroupRole(group), user); // will check msg.sender
}
/**
* @dev Returns a bitmap of the groups that are allowed to call a function of a target contract. If the target
* contract is in open or closed mode it will be reflected in the return value.
*/
function getFunctionAllowedGroups(address target, bytes4 selector) public view virtual returns (bytes32) {
AccessMode mode = getContractMode(target);
if (mode == AccessMode.Open) {
return _groupMask(_GROUP_PUBLIC);
} else if (mode == AccessMode.Closed) {
return 0;
} else {
return _allowedGroups[target][selector];
}
}
/**
* @dev Changes whether a group is allowed to call a function of a contract, according to the `allowed` argument.
* The caller must be the default admin.
*/
function setFunctionAllowedGroup(
address target,
bytes4[] calldata selectors,
uint8 group,
bool allowed
) public virtual onlyRole(DEFAULT_ADMIN_ROLE) {
for (uint256 i = 0; i < selectors.length; i++) {
bytes4 selector = selectors[i];
_allowedGroups[target][selector] = _withUpdatedGroup(_allowedGroups[target][selector], group, allowed);
emit GroupAllowed(target, selector, group, allowed);
}
}
/**
* @dev Returns the mode of the target contract, which may be custom (`0`), closed (`1`), or open (`2`).
*/
function getContractMode(address target) public view virtual returns (AccessMode) {
return _contractMode[target];
}
/**
* @dev Sets the target contract to be in custom restricted mode. All restricted functions in the target contract
* will follow the group-based restrictions defined by the AccessManager. The caller must be the default admin.
*/
function setContractModeCustom(address target) public virtual onlyRole(DEFAULT_ADMIN_ROLE) {
_setContractMode(target, AccessMode.Custom);
}
/**
* @dev Sets the target contract to be in "open" mode. All restricted functions in the target contract will become
* callable by anyone. The caller must be the default admin.
*/
function setContractModeOpen(address target) public virtual onlyRole(DEFAULT_ADMIN_ROLE) {
_setContractMode(target, AccessMode.Open);
}
/**
* @dev Sets the target contract to be in "closed" mode. All restricted functions in the target contract will be
* closed down and disallowed to all. The caller must be the default admin.
*/
function setContractModeClosed(address target) public virtual onlyRole(DEFAULT_ADMIN_ROLE) {
_setContractMode(target, AccessMode.Closed);
}
/**
* @dev Transfers a target contract onto a new authority. The caller must be the default admin.
*/
function transferContractAuthority(
address target,
address newAuthority
) public virtual onlyRole(DEFAULT_ADMIN_ROLE) {
AccessManaged(target).setAuthority(IAuthority(newAuthority));
}
/**
* @dev Creates a new group.
*
* Emits {GroupUpdated}.
*/
function _createGroup(uint8 group, string memory name) internal virtual {
require(!hasGroup(group), "AccessManager: existing group");
_createdGroups = _withUpdatedGroup(_createdGroups, group, true);
emit GroupUpdated(group, name);
}
/**
* @dev Augmented version of {AccessControl-_grantRole} that keeps track of user group bitmaps.
*/
function _grantRole(bytes32 role, address user) internal virtual override {
super._grantRole(role, user);
(bool isGroup, uint8 group) = _decodeGroupRole(role);
if (isGroup) {
require(hasGroup(group), "AccessManager: unknown group");
_userGroups[user] = _withUpdatedGroup(_userGroups[user], group, true);
}
}
/**
* @dev Augmented version of {AccessControl-_revokeRole} that keeps track of user group bitmaps.
*/
function _revokeRole(bytes32 role, address user) internal virtual override {
super._revokeRole(role, user);
(bool isGroup, uint8 group) = _decodeGroupRole(role);
if (isGroup) {
require(hasGroup(group), "AccessManager: unknown group");
require(group != _GROUP_PUBLIC, "AccessManager: irrevocable group");
_userGroups[user] = _withUpdatedGroup(_userGroups[user], group, false);
}
}
/**
* @dev Sets the restricted mode of a target contract.
*/
function _setContractMode(address target, AccessMode mode) internal virtual {
_contractMode[target] = mode;
emit AccessModeUpdated(target, mode);
}
/**
* @dev Returns the {AccessControl} role id that corresponds to a group.
*
* This role id starts with the ASCII characters `group:`, followed by zeroes, and ends with the single byte
* corresponding to the group number.
*/
function _encodeGroupRole(uint8 group) internal pure virtual returns (bytes32) {
return bytes32("group:") | bytes32(uint256(group));
}
/**
* @dev Decodes a role id into a group, if it is a role id of the kind returned by {_encodeGroupRole}.
*/
function _decodeGroupRole(bytes32 role) internal pure virtual returns (bool isGroup, uint8 group) {
bytes32 tagMask = ~bytes32(uint256(0xff));
bytes32 tag = role & tagMask;
isGroup = tag == bytes32("group:");
group = uint8(role[31]);
}
/**
* @dev Returns a bit mask where the only non-zero bit is the group number bit.
*/
function _groupMask(uint8 group) private pure returns (bytes32) {
return bytes32(1 << group);
}
/**
* @dev Returns the value of the group number bit in a bitmap.
*/
function _getGroup(bytes32 bitmap, uint8 group) private pure returns (bool) {
return bitmap & _groupMask(group) > 0;
}
/**
* @dev Returns a new group bitmap where a specific group was updated.
*/
function _withUpdatedGroup(bytes32 bitmap, uint8 group, bool value) private pure returns (bytes32) {
bytes32 mask = _groupMask(group);
if (value) {
return bitmap | mask;
} else {
return bitmap & ~mask;
}
}
}