Skip to content

Commit e341bdc

Browse files
Amxxfrangio
andauthored
Remove enumerable from AccessControl and add AccessControlEnumerable extension (#2512)
Co-authored-by: Francisco Giordano <[email protected]>
1 parent 09734e8 commit e341bdc

11 files changed

+302
-217
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
* `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))
1212
* `Initializable`: Make initializer check stricter during construction. ([#2531](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2531))
1313
* `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))
14+
* `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))
1415

1516
## 3.4.0 (2021-02-02)
1617

contracts/access/AccessControl.sol

Lines changed: 13 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@
22

33
pragma solidity ^0.8.0;
44

5-
import "../utils/EnumerableSet.sol";
6-
import "../utils/Address.sol";
75
import "../utils/Context.sol";
86

97
/**
108
* @dev Contract module that allows children to implement role-based access
11-
* control mechanisms.
9+
* control mechanisms. This is a lightweight version that doesn't allow enumerating role
10+
* members except through off-chain means by accessing the contract event logs. Some
11+
* applications may benefit from on-chain enumerability, for those cases see
12+
* {AccessControlEnumerable}.
1213
*
1314
* Roles are referred to by their `bytes32` identifier. These should be exposed
1415
* in the external API and be unique. The best way to achieve this is by
@@ -42,11 +43,8 @@ import "../utils/Context.sol";
4243
* accounts that have been granted it.
4344
*/
4445
abstract contract AccessControl is Context {
45-
using EnumerableSet for EnumerableSet.AddressSet;
46-
using Address for address;
47-
4846
struct RoleData {
49-
EnumerableSet.AddressSet members;
47+
mapping (address => bool) members;
5048
bytes32 adminRole;
5149
}
5250

@@ -85,31 +83,7 @@ abstract contract AccessControl is Context {
8583
* @dev Returns `true` if `account` has been granted `role`.
8684
*/
8785
function hasRole(bytes32 role, address account) public view returns (bool) {
88-
return _roles[role].members.contains(account);
89-
}
90-
91-
/**
92-
* @dev Returns the number of accounts that have `role`. Can be used
93-
* together with {getRoleMember} to enumerate all bearers of a role.
94-
*/
95-
function getRoleMemberCount(bytes32 role) public view returns (uint256) {
96-
return _roles[role].members.length();
97-
}
98-
99-
/**
100-
* @dev Returns one of the accounts that have `role`. `index` must be a
101-
* value between 0 and {getRoleMemberCount}, non-inclusive.
102-
*
103-
* Role bearers are not sorted in any particular way, and their ordering may
104-
* change at any point.
105-
*
106-
* WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure
107-
* you perform all queries on the same block. See the following
108-
* https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]
109-
* for more information.
110-
*/
111-
function getRoleMember(bytes32 role, uint256 index) public view returns (address) {
112-
return _roles[role].members.at(index);
86+
return _roles[role].members[account];
11387
}
11488

11589
/**
@@ -133,7 +107,7 @@ abstract contract AccessControl is Context {
133107
* - the caller must have ``role``'s admin role.
134108
*/
135109
function grantRole(bytes32 role, address account) public virtual {
136-
require(hasRole(_roles[role].adminRole, _msgSender()), "AccessControl: sender must be an admin to grant");
110+
require(hasRole(getRoleAdmin(role), _msgSender()), "AccessControl: sender must be an admin to grant");
137111

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

153127
_revokeRole(role, account);
154128
}
@@ -199,18 +173,20 @@ abstract contract AccessControl is Context {
199173
* Emits a {RoleAdminChanged} event.
200174
*/
201175
function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
202-
emit RoleAdminChanged(role, _roles[role].adminRole, adminRole);
176+
emit RoleAdminChanged(role, getRoleAdmin(role), adminRole);
203177
_roles[role].adminRole = adminRole;
204178
}
205179

206180
function _grantRole(bytes32 role, address account) private {
207-
if (_roles[role].members.add(account)) {
181+
if (!hasRole(role, account)) {
182+
_roles[role].members[account] = true;
208183
emit RoleGranted(role, account, _msgSender());
209184
}
210185
}
211186

212187
function _revokeRole(bytes32 role, address account) private {
213-
if (_roles[role].members.remove(account)) {
188+
if (hasRole(role, account)) {
189+
_roles[role].members[account] = false;
214190
emit RoleRevoked(role, account, _msgSender());
215191
}
216192
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.0;
4+
5+
import "./AccessControl.sol";
6+
import "../utils/EnumerableSet.sol";
7+
8+
/**
9+
* @dev Extension of {AccessControl} that allows enumerating the members of each role.
10+
*/
11+
abstract contract AccessControlEnumerable is AccessControl {
12+
using EnumerableSet for EnumerableSet.AddressSet;
13+
14+
mapping (bytes32 => EnumerableSet.AddressSet) private _roleMembers;
15+
16+
/**
17+
* @dev Returns one of the accounts that have `role`. `index` must be a
18+
* value between 0 and {getRoleMemberCount}, non-inclusive.
19+
*
20+
* Role bearers are not sorted in any particular way, and their ordering may
21+
* change at any point.
22+
*
23+
* WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure
24+
* you perform all queries on the same block. See the following
25+
* https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]
26+
* for more information.
27+
*/
28+
function getRoleMember(bytes32 role, uint256 index) public view returns (address) {
29+
return _roleMembers[role].at(index);
30+
}
31+
32+
/**
33+
* @dev Returns the number of accounts that have `role`. Can be used
34+
* together with {getRoleMember} to enumerate all bearers of a role.
35+
*/
36+
function getRoleMemberCount(bytes32 role) public view returns (uint256) {
37+
return _roleMembers[role].length();
38+
}
39+
40+
/**
41+
* @dev Overload {grantRole} to track enumerable memberships
42+
*/
43+
function grantRole(bytes32 role, address account) public virtual override {
44+
super.grantRole(role, account);
45+
_roleMembers[role].add(account);
46+
}
47+
48+
/**
49+
* @dev Overload {revokeRole} to track enumerable memberships
50+
*/
51+
function revokeRole(bytes32 role, address account) public virtual override {
52+
super.revokeRole(role, account);
53+
_roleMembers[role].remove(account);
54+
}
55+
56+
/**
57+
* @dev Overload {_setupRole} to track enumerable memberships
58+
*/
59+
function _setupRole(bytes32 role, address account) internal virtual override {
60+
super._setupRole(role, account);
61+
_roleMembers[role].add(account);
62+
}
63+
}

contracts/access/README.adoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ This directory provides ways to restrict who can access the functions of a contr
1515

1616
{{AccessControl}}
1717

18+
{{AccessControlEnumerable}}
19+
1820
== Timelock
1921

2022
{{TimelockController}}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.0;
4+
5+
import "../access/AccessControlEnumerable.sol";
6+
7+
contract AccessControlEnumerableMock is AccessControlEnumerable {
8+
constructor() {
9+
_setupRole(DEFAULT_ADMIN_ROLE, _msgSender());
10+
}
11+
12+
function setRoleAdmin(bytes32 roleId, bytes32 adminRoleId) public {
13+
_setRoleAdmin(roleId, adminRoleId);
14+
}
15+
}

contracts/presets/ERC1155PresetMinterPauser.sol

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
pragma solidity ^0.8.0;
44

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

contracts/presets/ERC20PresetMinterPauser.sol

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
pragma solidity ^0.8.0;
44

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

contracts/presets/ERC721PresetMinterPauserAutoId.sol

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
pragma solidity ^0.8.0;
44

5-
import "../access/AccessControl.sol";
5+
import "../access/AccessControlEnumerable.sol";
66
import "../utils/Context.sol";
77
import "../utils/Counters.sol";
88
import "../token/ERC721/ERC721.sol";
@@ -25,7 +25,7 @@ import "../token/ERC721/ERC721Pausable.sol";
2525
* roles, as well as the default admin role, which will let it grant both minter
2626
* and pauser roles to other accounts.
2727
*/
28-
contract ERC721PresetMinterPauserAutoId is Context, AccessControl, ERC721Enumerable, ERC721Burnable, ERC721Pausable {
28+
contract ERC721PresetMinterPauserAutoId is Context, AccessControlEnumerable, ERC721Enumerable, ERC721Burnable, ERC721Pausable {
2929
using Counters for Counters.Counter;
3030

3131
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");

0 commit comments

Comments
 (0)