Skip to content

Commit

Permalink
Remove enumerable from AccessControl and add AccessControlEnumerable …
Browse files Browse the repository at this point in the history
…extension (#2512)

Co-authored-by: Francisco Giordano <frangio.1@gmail.com>
  • Loading branch information
Amxx and frangio committed Feb 19, 2021
1 parent 09734e8 commit e341bdc
Show file tree
Hide file tree
Showing 11 changed files with 302 additions and 217 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
* `ERC165`: Remove uses of storage in the base ERC165 implementation. ERC165 based contracts now use storage-less virtual functions. Old behaviour remains available in the `ERC165Storage` extension. ([#2505](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2505))
* `Initializable`: Make initializer check stricter during construction. ([#2531](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2531))
* `ERC721`: remove enumerability of tokens from the base implementation. This feature is now provided separately through the `ERC721Enumerable` extension. ([#2511](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2511))
* `AccessControl`: removed enumerability by default for a more lightweight contract. It is now opt-in through `AccessControlEnumerable`. ([#2512](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2512))

## 3.4.0 (2021-02-02)

Expand Down
50 changes: 13 additions & 37 deletions contracts/access/AccessControl.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@

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. This is a lightweight version that doesn't allow enumerating role
* members except through off-chain means by accessing the contract event logs. Some
* applications may benefit from on-chain enumerability, for those cases see
* {AccessControlEnumerable}.
*
* 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,11 +43,8 @@ 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;
mapping (address => bool) members;
bytes32 adminRole;
}

Expand Down Expand Up @@ -85,31 +83,7 @@ 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);
return _roles[role].members[account];
}

/**
Expand All @@ -133,7 +107,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(hasRole(getRoleAdmin(role), _msgSender()), "AccessControl: sender must be an admin to grant");

_grantRole(role, account);
}
Expand All @@ -148,7 +122,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(hasRole(getRoleAdmin(role), _msgSender()), "AccessControl: sender must be an admin to revoke");

_revokeRole(role, account);
}
Expand Down Expand Up @@ -199,18 +173,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);
emit RoleAdminChanged(role, getRoleAdmin(role), adminRole);
_roles[role].adminRole = adminRole;
}

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

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

pragma solidity ^0.8.0;

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

/**
* @dev Extension of {AccessControl} that allows enumerating the members of each role.
*/
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) public virtual override {
super.grantRole(role, account);
_roleMembers[role].add(account);
}

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

/**
* @dev Overload {_setupRole} to track enumerable memberships
*/
function _setupRole(bytes32 role, address account) internal virtual override {
super._setupRole(role, account);
_roleMembers[role].add(account);
}
}
2 changes: 2 additions & 0 deletions contracts/access/README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ This directory provides ways to restrict who can access the functions of a contr

{{AccessControl}}

{{AccessControlEnumerable}}

== Timelock

{{TimelockController}}
Expand Down
15 changes: 15 additions & 0 deletions contracts/mocks/AccessControlEnumerableMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

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

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

function setRoleAdmin(bytes32 roleId, bytes32 adminRoleId) public {
_setRoleAdmin(roleId, adminRoleId);
}
}
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 @@ -25,7 +25,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, ERC721Enumerable, ERC721Burnable, ERC721Pausable {
contract ERC721PresetMinterPauserAutoId is Context, AccessControlEnumerable, ERC721Enumerable, ERC721Burnable, ERC721Pausable {
using Counters for Counters.Counter;

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

0 comments on commit e341bdc

Please sign in to comment.