|
| 1 | +// SPDX-License-Identifier: MIT |
| 2 | + |
| 3 | +pragma solidity ^0.8.24; |
| 4 | + |
| 5 | +import {AccessManager} from "../../access/manager/AccessManager.sol"; |
| 6 | +import {EnumerableSet} from "../../utils/structs/EnumerableSet.sol"; |
| 7 | + |
| 8 | +/** |
| 9 | + * @dev Extension of {AccessManager} that allows enumerating the members of each role |
| 10 | + * and the target functions each role is allowed to call. |
| 11 | + * |
| 12 | + * NOTE: Given {ADMIN_ROLE} is the default role for every restricted function, the |
| 13 | + * {getRoleTargetFunctions} and {getRoleTargetFunctionCount} functions will return an empty array |
| 14 | + * and 0 respectively. |
| 15 | + */ |
| 16 | +abstract contract AccessManagerEnumerable is AccessManager { |
| 17 | + using EnumerableSet for EnumerableSet.AddressSet; |
| 18 | + using EnumerableSet for EnumerableSet.Bytes4Set; |
| 19 | + |
| 20 | + mapping(uint64 roleId => EnumerableSet.AddressSet) private _roleMembers; |
| 21 | + mapping(uint64 roleId => mapping(address target => EnumerableSet.Bytes4Set)) private _roleTargetFunctions; |
| 22 | + |
| 23 | + /** |
| 24 | + * @dev Returns the number of accounts that have `roleId`. Can be used |
| 25 | + * together with {getRoleMember} to enumerate all bearers of a role. |
| 26 | + */ |
| 27 | + function getRoleMemberCount(uint64 roleId) public view virtual returns (uint256) { |
| 28 | + return _roleMembers[roleId].length(); |
| 29 | + } |
| 30 | + |
| 31 | + /** |
| 32 | + * @dev Returns one of the accounts that have `roleId`. `index` must be a |
| 33 | + * value between 0 and {getRoleMemberCount}, non-inclusive. |
| 34 | + * |
| 35 | + * Role bearers are not sorted in any particular way, and their ordering may change at any point. |
| 36 | + * |
| 37 | + * WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure |
| 38 | + * you perform all queries on the same block. See the following |
| 39 | + * https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post] |
| 40 | + * for more information. |
| 41 | + */ |
| 42 | + function getRoleMember(uint64 roleId, uint256 index) public view virtual returns (address) { |
| 43 | + return _roleMembers[roleId].at(index); |
| 44 | + } |
| 45 | + |
| 46 | + /** |
| 47 | + * @dev Returns a range of accounts that have `roleId`. `start` and `end` define the range bounds. |
| 48 | + * `start` is inclusive and `end` is exclusive. |
| 49 | + * |
| 50 | + * Role bearers are not sorted in any particular way, and their ordering may change at any point. |
| 51 | + * |
| 52 | + * It is not necessary to call {getRoleMemberCount} before calling this function. Using `start = 0` and |
| 53 | + * `end = type(uint256).max` will return every member of `roleId`. |
| 54 | + * |
| 55 | + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed |
| 56 | + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that |
| 57 | + * this function has an unbounded cost, and using it as part of a state-changing function may render the function |
| 58 | + * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. |
| 59 | + */ |
| 60 | + function getRoleMembers(uint64 roleId, uint256 start, uint256 end) public view virtual returns (address[] memory) { |
| 61 | + return _roleMembers[roleId].values(start, end); |
| 62 | + } |
| 63 | + |
| 64 | + /** |
| 65 | + * @dev Returns the number of target function selectors that require `roleId` for the given `target`. |
| 66 | + * Can be used together with {getRoleTargetFunction} to enumerate all target functions for a role on a specific target. |
| 67 | + * |
| 68 | + * NOTE: Given {ADMIN_ROLE} is the default role for every restricted function, passing {ADMIN_ROLE} as `roleId` will |
| 69 | + * return 0. See {_updateRoleTargetFunction} for more details. |
| 70 | + */ |
| 71 | + function getRoleTargetFunctionCount(uint64 roleId, address target) public view virtual returns (uint256) { |
| 72 | + return _roleTargetFunctions[roleId][target].length(); |
| 73 | + } |
| 74 | + |
| 75 | + /** |
| 76 | + * @dev Returns one of the target function selectors that require `roleId` for the given `target`. |
| 77 | + * `index` must be a value between 0 and {getRoleTargetFunctionCount}, non-inclusive. |
| 78 | + * |
| 79 | + * Target function selectors are not sorted in any particular way, and their ordering may change at any point. |
| 80 | + * |
| 81 | + * WARNING: When using {getRoleTargetFunction} and {getRoleTargetFunctionCount}, make sure |
| 82 | + * you perform all queries on the same block. See the following |
| 83 | + * https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post] |
| 84 | + * for more information. |
| 85 | + */ |
| 86 | + function getRoleTargetFunction(uint64 roleId, address target, uint256 index) public view virtual returns (bytes4) { |
| 87 | + return _roleTargetFunctions[roleId][target].at(index); |
| 88 | + } |
| 89 | + |
| 90 | + /** |
| 91 | + * @dev Returns a range of target function selectors that require `roleId` for the given `target`. |
| 92 | + * `start` and `end` define the range bounds. `start` is inclusive and `end` is exclusive. |
| 93 | + * |
| 94 | + * Target function selectors are not sorted in any particular way, and their ordering may change at any point. |
| 95 | + * |
| 96 | + * It is not necessary to call {getRoleTargetFunctionCount} before calling this function. Using `start = 0` and |
| 97 | + * `end = type(uint256).max` will return every function selector that `roleId` is allowed to call on `target`. |
| 98 | + * |
| 99 | + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed |
| 100 | + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that |
| 101 | + * this function has an unbounded cost, and using it as part of a state-changing function may render the function |
| 102 | + * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. |
| 103 | + * |
| 104 | + * NOTE: Given {ADMIN_ROLE} is the default role for every restricted function, passing {ADMIN_ROLE} as `roleId` will |
| 105 | + * return an empty array. See {_updateRoleTargetFunction} for more details. |
| 106 | + */ |
| 107 | + function getRoleTargetFunctions( |
| 108 | + uint64 roleId, |
| 109 | + address target, |
| 110 | + uint256 start, |
| 111 | + uint256 end |
| 112 | + ) public view virtual returns (bytes4[] memory) { |
| 113 | + return _roleTargetFunctions[roleId][target].values(start, end); |
| 114 | + } |
| 115 | + |
| 116 | + /// @dev See {AccessManager-_grantRole}. Adds the account to the role members set. |
| 117 | + function _grantRole( |
| 118 | + uint64 roleId, |
| 119 | + address account, |
| 120 | + uint32 grantDelay, |
| 121 | + uint32 executionDelay |
| 122 | + ) internal virtual override returns (bool) { |
| 123 | + bool granted = super._grantRole(roleId, account, grantDelay, executionDelay); |
| 124 | + if (granted) { |
| 125 | + _roleMembers[roleId].add(account); |
| 126 | + } |
| 127 | + return granted; |
| 128 | + } |
| 129 | + |
| 130 | + /// @dev See {AccessManager-_revokeRole}. Removes the account from the role members set. |
| 131 | + function _revokeRole(uint64 roleId, address account) internal virtual override returns (bool) { |
| 132 | + bool revoked = super._revokeRole(roleId, account); |
| 133 | + if (revoked) { |
| 134 | + _roleMembers[roleId].remove(account); |
| 135 | + } |
| 136 | + return revoked; |
| 137 | + } |
| 138 | + |
| 139 | + /** |
| 140 | + * @dev See {AccessManager-_setTargetFunctionRole}. Adds the selector to the role target functions set. |
| 141 | + * |
| 142 | + * NOTE: This function does not track function selectors for the {ADMIN_ROLE}, since exhaustively tracking |
| 143 | + * all restricted/admin functions is impractical (by default, all restricted functions are assigned to {ADMIN_ROLE}). |
| 144 | + * Therefore, roles assigned as {ADMIN_ROLE} will not have their selectors included in this extension's tracking. |
| 145 | + */ |
| 146 | + function _setTargetFunctionRole(address target, bytes4 selector, uint64 roleId) internal virtual override { |
| 147 | + // cache old role ID |
| 148 | + uint64 oldRoleId = getTargetFunctionRole(target, selector); |
| 149 | + |
| 150 | + // call super |
| 151 | + super._setTargetFunctionRole(target, selector, roleId); |
| 152 | + |
| 153 | + // update enumerable sets |
| 154 | + if (oldRoleId != ADMIN_ROLE) { |
| 155 | + _roleTargetFunctions[oldRoleId][target].remove(selector); |
| 156 | + } |
| 157 | + if (roleId != ADMIN_ROLE) { |
| 158 | + _roleTargetFunctions[roleId][target].add(selector); |
| 159 | + } |
| 160 | + } |
| 161 | +} |
0 commit comments