Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove enumerable from AccessControl and add an AccessControlEnumerable extension #2512

Merged
merged 20 commits into from
Feb 19, 2021
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
67 changes: 18 additions & 49 deletions contracts/access/AccessControl.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@

pragma solidity ^0.8.0;

import "../utils/EnumerableSet.sol";
import "../utils/Address.sol";
import "../utils/Context.sol";

/**
* @dev Contract module that allows children to implement role-based access
* control mechanisms.
* control mechanisms. Light, not enumerable, version.
Amxx marked this conversation as resolved.
Show resolved Hide resolved
*
* Roles are referred to by their `bytes32` identifier. These should be exposed
* in the external API and be unique. The best way to achieve this is by
Expand Down Expand Up @@ -42,18 +40,11 @@ import "../utils/Context.sol";
* accounts that have been granted it.
*/
abstract contract AccessControl is Context {
using EnumerableSet for EnumerableSet.AddressSet;
using Address for address;

struct RoleData {
EnumerableSet.AddressSet members;
bytes32 adminRole;
}

mapping (bytes32 => RoleData) private _roles;
Amxx marked this conversation as resolved.
Show resolved Hide resolved

bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;

mapping (bytes32 => bytes32) private _admins;
mapping (bytes32 => mapping (address => bool)) private _members;

/**
* @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
*
Expand Down Expand Up @@ -84,32 +75,8 @@ abstract contract AccessControl is Context {
/**
* @dev Returns `true` if `account` has been granted `role`.
*/
function hasRole(bytes32 role, address account) public view returns (bool) {
return _roles[role].members.contains(account);
}

/**
* @dev Returns the number of accounts that have `role`. Can be used
* together with {getRoleMember} to enumerate all bearers of a role.
*/
function getRoleMemberCount(bytes32 role) public view returns (uint256) {
return _roles[role].members.length();
}

/**
* @dev Returns one of the accounts that have `role`. `index` must be a
* value between 0 and {getRoleMemberCount}, non-inclusive.
*
* Role bearers are not sorted in any particular way, and their ordering may
* change at any point.
*
* WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure
* you perform all queries on the same block. See the following
* https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]
* for more information.
*/
function getRoleMember(bytes32 role, uint256 index) public view returns (address) {
return _roles[role].members.at(index);
function hasRole(bytes32 role, address account) public view virtual returns (bool) {
return _members[role][account];
}

/**
Expand All @@ -118,8 +85,8 @@ abstract contract AccessControl is Context {
*
* To change a role's admin, use {_setRoleAdmin}.
*/
function getRoleAdmin(bytes32 role) public view returns (bytes32) {
return _roles[role].adminRole;
function getRoleAdmin(bytes32 role) public view virtual returns (bytes32) {
return _admins[role];
}

/**
Expand All @@ -133,7 +100,7 @@ abstract contract AccessControl is Context {
* - the caller must have ``role``'s admin role.
*/
function grantRole(bytes32 role, address account) public virtual {
require(hasRole(_roles[role].adminRole, _msgSender()), "AccessControl: sender must be an admin to grant");
require(AccessControl.hasRole(AccessControl.getRoleAdmin(role), _msgSender()), "AccessControl: sender must be an admin to grant");
Amxx marked this conversation as resolved.
Show resolved Hide resolved

_grantRole(role, account);
}
Expand All @@ -148,7 +115,7 @@ abstract contract AccessControl is Context {
* - the caller must have ``role``'s admin role.
*/
function revokeRole(bytes32 role, address account) public virtual {
require(hasRole(_roles[role].adminRole, _msgSender()), "AccessControl: sender must be an admin to revoke");
require(AccessControl.hasRole(AccessControl.getRoleAdmin(role), _msgSender()), "AccessControl: sender must be an admin to revoke");

_revokeRole(role, account);
}
Expand Down Expand Up @@ -199,18 +166,20 @@ abstract contract AccessControl is Context {
* Emits a {RoleAdminChanged} event.
*/
function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
emit RoleAdminChanged(role, _roles[role].adminRole, adminRole);
_roles[role].adminRole = adminRole;
emit RoleAdminChanged(role, AccessControl.getRoleAdmin(role), adminRole);
_admins[role] = adminRole;
}

function _grantRole(bytes32 role, address account) private {
if (_roles[role].members.add(account)) {
function _grantRole(bytes32 role, address account) internal virtual {
if (!AccessControl.hasRole(role, account)) {
_members[role][account] = true;
emit RoleGranted(role, account, _msgSender());
}
}

function _revokeRole(bytes32 role, address account) private {
if (_roles[role].members.remove(account)) {
function _revokeRole(bytes32 role, address account) internal virtual {
if (AccessControl.hasRole(role, account)) {
_members[role][account] = false;
emit RoleRevoked(role, account, _msgSender());
}
}
Expand Down
87 changes: 87 additions & 0 deletions contracts/access/AccessControlEnumerable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "./AccessControl.sol";
import "../utils/EnumerableSet.sol";

/**
* @dev Contract module that allows children to implement role-based access
* control mechanisms. Role to account enumerable version.
*
* Roles are referred to by their `bytes32` identifier. These should be exposed
* in the external API and be unique. The best way to achieve this is by
* using `public constant` hash digests:
*
* ```
* bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
* ```
*
* Roles can be used to represent a set of permissions. To restrict access to a
* function call, use {hasRole}:
*
* ```
* function foo() public {
* require(hasRole(MY_ROLE, msg.sender));
* ...
* }
* ```
*
* Roles can be granted and revoked dynamically via the {grantRole} and
* {revokeRole} functions. Each role has an associated admin role, and only
* accounts that have a role's admin role can call {grantRole} and {revokeRole}.
*
* By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
* that only accounts with this role will be able to grant or revoke other
* roles. More complex role relationships can be created by using
* {_setRoleAdmin}.
*
* WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
* grant and revoke this role. Extra precautions should be taken to secure
* accounts that have been granted it.
Amxx marked this conversation as resolved.
Show resolved Hide resolved
*/
abstract contract AccessControlEnumerable is AccessControl {
using EnumerableSet for EnumerableSet.AddressSet;

mapping (bytes32 => EnumerableSet.AddressSet) private _roleMembers;

/**
* @dev Returns one of the accounts that have `role`. `index` must be a
* value between 0 and {getRoleMemberCount}, non-inclusive.
*
* Role bearers are not sorted in any particular way, and their ordering may
* change at any point.
*
* WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure
* you perform all queries on the same block. See the following
* https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]
* for more information.
*/
function getRoleMember(bytes32 role, uint256 index) public view returns (address) {
return _roleMembers[role].at(index);
}

/**
* @dev Returns the number of accounts that have `role`. Can be used
* together with {getRoleMember} to enumerate all bearers of a role.
*/
function getRoleMemberCount(bytes32 role) public view returns (uint256) {
return _roleMembers[role].length();
}

/**
* @dev Overload {_grantRole} to track enumerable memberships
*/
function _grantRole(bytes32 role, address account) internal virtual override {
super._grantRole(role, account);
_roleMembers[role].add(account);
}

/**
* @dev Overload {_revokeRole} to track enumerable memberships
*/
function _revokeRole(bytes32 role, address account) internal virtual override {
super._revokeRole(role, account);
_roleMembers[role].remove(account);
}
}
87 changes: 87 additions & 0 deletions contracts/access/AccessControlEnumerable2.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "./AccessControl.sol";
import "../utils/EnumerableSet.sol";

/**
* @dev Contract module that allows children to implement role-based access
* control mechanisms. Account to role enumerable version.
*
* Roles are referred to by their `bytes32` identifier. These should be exposed
* in the external API and be unique. The best way to achieve this is by
* using `public constant` hash digests:
*
* ```
* bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
* ```
*
* Roles can be used to represent a set of permissions. To restrict access to a
* function call, use {hasRole}:
*
* ```
* function foo() public {
* require(hasRole(MY_ROLE, msg.sender));
* ...
* }
* ```
*
* Roles can be granted and revoked dynamically via the {grantRole} and
* {revokeRole} functions. Each role has an associated admin role, and only
* accounts that have a role's admin role can call {grantRole} and {revokeRole}.
*
* By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
* that only accounts with this role will be able to grant or revoke other
* roles. More complex role relationships can be created by using
* {_setRoleAdmin}.
*
* WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
* grant and revoke this role. Extra precautions should be taken to secure
* accounts that have been granted it.
*/
abstract contract AccessControlEnumerable2 is AccessControl {
Amxx marked this conversation as resolved.
Show resolved Hide resolved
using EnumerableSet for EnumerableSet.Bytes32Set;

mapping (address => EnumerableSet.Bytes32Set) private _addressRoles;

/**
* @dev Returns one of the roles that `account` has. `index` must be a
* value between 0 and {getAddressRoleCount}, non-inclusive.
*
* Role not sorted in any particular way, and their ordering may change at
* any point.
*
* WARNING: When using {getAddressRole} and {getAddressRoleCount}, make sure
* you perform all queries on the same block. See the following
* https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]
* for more information.
*/
function getAddressRole(address account, uint256 index) public view returns (bytes32) {
return _addressRoles[account].at(index);
}

/**
* @dev Returns the number of role that `account` has. Can be used
* together with {getAddressRole} to enumerate all role of an account.
*/
function getAddressRoleCount(address account) public view returns (uint256) {
return _addressRoles[account].length();
}

/**
* @dev Overload {_grantRole} to track enumerable memberships
*/
function _grantRole(bytes32 role, address account) internal virtual override {
super._grantRole(role, account);
_addressRoles[account].add(role);
}

/**
* @dev Overload {_revokeRole} to track enumerable memberships
*/
function _revokeRole(bytes32 role, address account) internal virtual override {
super._revokeRole(role, account);
_addressRoles[account].remove(role);
}
}
28 changes: 28 additions & 0 deletions contracts/mocks/AccessControlEnumerableMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "../access/AccessControlEnumerable.sol";
import "../access/AccessControlEnumerable2.sol";

contract AccessControlEnumerableMock is AccessControlEnumerable, AccessControlEnumerable2 {
constructor() {
_setupRole(DEFAULT_ADMIN_ROLE, _msgSender());
}

function setRoleAdmin(bytes32 roleId, bytes32 adminRoleId) public {
_setRoleAdmin(roleId, adminRoleId);
}

function _grantRole(bytes32 role, address account)
internal virtual override(AccessControlEnumerable, AccessControlEnumerable2)
{
super._grantRole(role, account);
}

function _revokeRole(bytes32 role, address account)
internal virtual override(AccessControlEnumerable, AccessControlEnumerable2)
{
super._revokeRole(role, account);
}
}
4 changes: 2 additions & 2 deletions contracts/presets/ERC1155PresetMinterPauser.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

pragma solidity ^0.8.0;

import "../access/AccessControl.sol";
import "../access/AccessControlEnumerable.sol";
import "../utils/Context.sol";
import "../token/ERC1155/ERC1155.sol";
import "../token/ERC1155/ERC1155Burnable.sol";
Expand All @@ -22,7 +22,7 @@ import "../token/ERC1155/ERC1155Pausable.sol";
* roles, as well as the default admin role, which will let it grant both minter
* and pauser roles to other accounts.
*/
contract ERC1155PresetMinterPauser is Context, AccessControl, ERC1155Burnable, ERC1155Pausable {
contract ERC1155PresetMinterPauser is Context, AccessControlEnumerable, ERC1155Burnable, ERC1155Pausable {
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");

Expand Down
4 changes: 2 additions & 2 deletions contracts/presets/ERC20PresetMinterPauser.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

pragma solidity ^0.8.0;

import "../access/AccessControl.sol";
import "../access/AccessControlEnumerable.sol";
import "../utils/Context.sol";
import "../token/ERC20/ERC20.sol";
import "../token/ERC20/ERC20Burnable.sol";
Expand All @@ -22,7 +22,7 @@ import "../token/ERC20/ERC20Pausable.sol";
* roles, as well as the default admin role, which will let it grant both minter
* and pauser roles to other accounts.
*/
contract ERC20PresetMinterPauser is Context, AccessControl, ERC20Burnable, ERC20Pausable {
contract ERC20PresetMinterPauser is Context, AccessControlEnumerable, ERC20Burnable, ERC20Pausable {
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");

Expand Down
4 changes: 2 additions & 2 deletions contracts/presets/ERC721PresetMinterPauserAutoId.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

pragma solidity ^0.8.0;

import "../access/AccessControl.sol";
import "../access/AccessControlEnumerable.sol";
import "../utils/Context.sol";
import "../utils/Counters.sol";
import "../token/ERC721/ERC721.sol";
Expand All @@ -24,7 +24,7 @@ import "../token/ERC721/ERC721Pausable.sol";
* roles, as well as the default admin role, which will let it grant both minter
* and pauser roles to other accounts.
*/
contract ERC721PresetMinterPauserAutoId is Context, AccessControl, ERC721Burnable, ERC721Pausable {
contract ERC721PresetMinterPauserAutoId is Context, AccessControlEnumerable, ERC721Burnable, ERC721Pausable {
using Counters for Counters.Counter;

bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
Expand Down