Skip to content

Commit 29ffe6f

Browse files
frangioAmxx
andauthored
Add ERC165 interface detection to AccessControl (#2562)
Co-authored-by: Hadrien Croubois <[email protected]>
1 parent ec63c60 commit 29ffe6f

File tree

9 files changed

+78
-11
lines changed

9 files changed

+78
-11
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
* Rename `UpgradeableProxy` to `ERC1967Proxy`. ([#2547](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2547))
1919
* `ERC777`: Optimize the gas costs of the constructor. ([#2551](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2551))
2020
* `ERC721URIStorage`: Add a new extension that implements the `_setTokenURI` behavior as it was available in 3.4.0. ([#2555](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2555))
21+
* `AccessControl`: Added ERC165 interface detection. ([#2562](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2562))
2122

2223
### How to upgrade from 3.x
2324

contracts/access/AccessControl.sol

+26-6
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,18 @@
33
pragma solidity ^0.8.0;
44

55
import "../utils/Context.sol";
6+
import "../utils/introspection/ERC165.sol";
7+
8+
/**
9+
* @dev External interface of AccessControl declared to support ERC165 detection.
10+
*/
11+
interface IAccessControl {
12+
function hasRole(bytes32 role, address account) external view returns (bool);
13+
function getRoleAdmin(bytes32 role) external view returns (bytes32);
14+
function grantRole(bytes32 role, address account) external;
15+
function revokeRole(bytes32 role, address account) external;
16+
function renounceRole(bytes32 role, address account) external;
17+
}
618

719
/**
820
* @dev Contract module that allows children to implement role-based access
@@ -42,7 +54,7 @@ import "../utils/Context.sol";
4254
* grant and revoke this role. Extra precautions should be taken to secure
4355
* accounts that have been granted it.
4456
*/
45-
abstract contract AccessControl is Context {
57+
abstract contract AccessControl is Context, IAccessControl, ERC165 {
4658
struct RoleData {
4759
mapping (address => bool) members;
4860
bytes32 adminRole;
@@ -79,10 +91,18 @@ abstract contract AccessControl is Context {
7991
*/
8092
event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);
8193

94+
/**
95+
* @dev See {IERC165-supportsInterface}.
96+
*/
97+
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
98+
return interfaceId == type(IAccessControl).interfaceId
99+
|| super.supportsInterface(interfaceId);
100+
}
101+
82102
/**
83103
* @dev Returns `true` if `account` has been granted `role`.
84104
*/
85-
function hasRole(bytes32 role, address account) public view returns (bool) {
105+
function hasRole(bytes32 role, address account) public view override returns (bool) {
86106
return _roles[role].members[account];
87107
}
88108

@@ -92,7 +112,7 @@ abstract contract AccessControl is Context {
92112
*
93113
* To change a role's admin, use {_setRoleAdmin}.
94114
*/
95-
function getRoleAdmin(bytes32 role) public view returns (bytes32) {
115+
function getRoleAdmin(bytes32 role) public view override returns (bytes32) {
96116
return _roles[role].adminRole;
97117
}
98118

@@ -106,7 +126,7 @@ abstract contract AccessControl is Context {
106126
*
107127
* - the caller must have ``role``'s admin role.
108128
*/
109-
function grantRole(bytes32 role, address account) public virtual {
129+
function grantRole(bytes32 role, address account) public virtual override {
110130
require(hasRole(getRoleAdmin(role), _msgSender()), "AccessControl: sender must be an admin to grant");
111131

112132
_grantRole(role, account);
@@ -121,7 +141,7 @@ abstract contract AccessControl is Context {
121141
*
122142
* - the caller must have ``role``'s admin role.
123143
*/
124-
function revokeRole(bytes32 role, address account) public virtual {
144+
function revokeRole(bytes32 role, address account) public virtual override {
125145
require(hasRole(getRoleAdmin(role), _msgSender()), "AccessControl: sender must be an admin to revoke");
126146

127147
_revokeRole(role, account);
@@ -141,7 +161,7 @@ abstract contract AccessControl is Context {
141161
*
142162
* - the caller must be `account`.
143163
*/
144-
function renounceRole(bytes32 role, address account) public virtual {
164+
function renounceRole(bytes32 role, address account) public virtual override {
145165
require(account == _msgSender(), "AccessControl: can only renounce roles for self");
146166

147167
_revokeRole(role, account);

contracts/access/AccessControlEnumerable.sol

+19-3
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,30 @@ pragma solidity ^0.8.0;
55
import "./AccessControl.sol";
66
import "../utils/structs/EnumerableSet.sol";
77

8+
/**
9+
* @dev External interface of AccessControlEnumerable declared to support ERC165 detection.
10+
*/
11+
interface IAccessControlEnumerable {
12+
function getRoleMember(bytes32 role, uint256 index) external view returns (address);
13+
function getRoleMemberCount(bytes32 role) external view returns (uint256);
14+
}
15+
816
/**
917
* @dev Extension of {AccessControl} that allows enumerating the members of each role.
1018
*/
11-
abstract contract AccessControlEnumerable is AccessControl {
19+
abstract contract AccessControlEnumerable is IAccessControlEnumerable, AccessControl {
1220
using EnumerableSet for EnumerableSet.AddressSet;
1321

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

24+
/**
25+
* @dev See {IERC165-supportsInterface}.
26+
*/
27+
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
28+
return interfaceId == type(IAccessControlEnumerable).interfaceId
29+
|| super.supportsInterface(interfaceId);
30+
}
31+
1632
/**
1733
* @dev Returns one of the accounts that have `role`. `index` must be a
1834
* value between 0 and {getRoleMemberCount}, non-inclusive.
@@ -25,15 +41,15 @@ abstract contract AccessControlEnumerable is AccessControl {
2541
* https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]
2642
* for more information.
2743
*/
28-
function getRoleMember(bytes32 role, uint256 index) public view returns (address) {
44+
function getRoleMember(bytes32 role, uint256 index) public view override returns (address) {
2945
return _roleMembers[role].at(index);
3046
}
3147

3248
/**
3349
* @dev Returns the number of accounts that have `role`. Can be used
3450
* together with {getRoleMember} to enumerate all bearers of a role.
3551
*/
36-
function getRoleMemberCount(bytes32 role) public view returns (uint256) {
52+
function getRoleMemberCount(bytes32 role) public view override returns (uint256) {
3753
return _roleMembers[role].length();
3854
}
3955

contracts/token/ERC1155/presets/ERC1155PresetMinterPauser.sol

+7
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,13 @@ contract ERC1155PresetMinterPauser is Context, AccessControlEnumerable, ERC1155B
8989
_unpause();
9090
}
9191

92+
/**
93+
* @dev See {IERC165-supportsInterface}.
94+
*/
95+
function supportsInterface(bytes4 interfaceId) public view virtual override(AccessControlEnumerable, ERC1155) returns (bool) {
96+
return super.supportsInterface(interfaceId);
97+
}
98+
9299
function _beforeTokenTransfer(
93100
address operator,
94101
address from,

contracts/token/ERC721/presets/ERC721PresetMinterPauserAutoId.sol

+1-1
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ contract ERC721PresetMinterPauserAutoId is Context, AccessControlEnumerable, ERC
110110
/**
111111
* @dev See {IERC165-supportsInterface}.
112112
*/
113-
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721, ERC721Enumerable) returns (bool) {
113+
function supportsInterface(bytes4 interfaceId) public view virtual override(AccessControlEnumerable, ERC721, ERC721Enumerable) returns (bool) {
114114
return super.supportsInterface(interfaceId);
115115
}
116116
}

test/access/AccessControl.behavior.js

+6
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
const { expectEvent, expectRevert } = require('@openzeppelin/test-helpers');
22
const { expect } = require('chai');
33

4+
const { shouldSupportInterfaces } = require('../utils/introspection/SupportsInterface.behavior');
5+
46
const DEFAULT_ADMIN_ROLE = '0x0000000000000000000000000000000000000000000000000000000000000000';
57
const ROLE = web3.utils.soliditySha3('ROLE');
68
const OTHER_ROLE = web3.utils.soliditySha3('OTHER_ROLE');
79

810
function shouldBehaveLikeAccessControl (errorPrefix, admin, authorized, other, otherAdmin, otherAuthorized) {
11+
shouldSupportInterfaces(['AccessControl']);
12+
913
describe('default admin', function () {
1014
it('deployer has default admin role', async function () {
1115
expect(await this.accessControl.hasRole(DEFAULT_ADMIN_ROLE, admin)).to.equal(true);
@@ -156,6 +160,8 @@ function shouldBehaveLikeAccessControl (errorPrefix, admin, authorized, other, o
156160
}
157161

158162
function shouldBehaveLikeAccessControlEnumerable (errorPrefix, admin, authorized, other, otherAdmin, otherAuthorized) {
163+
shouldSupportInterfaces(['AccessControlEnumerable']);
164+
159165
describe('enumerating', function () {
160166
it('role bearers can be enumerated', async function () {
161167
await this.accessControl.grantRole(ROLE, authorized, { from: admin });

test/token/ERC1155/presets/ERC1155PresetMinterPauser.test.js

+3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const { BN, constants, expectEvent, expectRevert } = require('@openzeppelin/test-helpers');
22
const { ZERO_ADDRESS } = constants;
3+
const { shouldSupportInterfaces } = require('../../../utils/introspection/SupportsInterface.behavior');
34

45
const { expect } = require('chai');
56

@@ -24,6 +25,8 @@ contract('ERC1155PresetMinterPauser', function (accounts) {
2425
this.token = await ERC1155PresetMinterPauser.new(uri, { from: deployer });
2526
});
2627

28+
shouldSupportInterfaces(['ERC1155', 'AccessControl', 'AccessControlEnumerable']);
29+
2730
it('deployer has the default admin role', async function () {
2831
expect(await this.token.getRoleMemberCount(DEFAULT_ADMIN_ROLE)).to.be.bignumber.equal('1');
2932
expect(await this.token.getRoleMember(DEFAULT_ADMIN_ROLE, 0)).to.equal(deployer);

test/token/ERC721/presets/ERC721PresetMinterPauserAutoId.test.js

+3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const { BN, constants, expectEvent, expectRevert } = require('@openzeppelin/test-helpers');
22
const { ZERO_ADDRESS } = constants;
3+
const { shouldSupportInterfaces } = require('../../../utils/introspection/SupportsInterface.behavior');
34

45
const { expect } = require('chai');
56

@@ -19,6 +20,8 @@ contract('ERC721PresetMinterPauserAutoId', function (accounts) {
1920
this.token = await ERC721PresetMinterPauserAutoId.new(name, symbol, baseURI, { from: deployer });
2021
});
2122

23+
shouldSupportInterfaces(['ERC721', 'ERC721Enumerable', 'AccessControl', 'AccessControlEnumerable']);
24+
2225
it('token has correct name', async function () {
2326
expect(await this.token.name()).to.equal(name);
2427
});

test/utils/introspection/SupportsInterface.behavior.js

+12-1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,17 @@ const INTERFACES = {
3939
'onERC1155Received(address,address,uint256,uint256,bytes)',
4040
'onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)',
4141
],
42+
AccessControl: [
43+
'hasRole(bytes32,address)',
44+
'getRoleAdmin(bytes32)',
45+
'grantRole(bytes32,address)',
46+
'revokeRole(bytes32,address)',
47+
'renounceRole(bytes32,address)',
48+
],
49+
AccessControlEnumerable: [
50+
'getRoleMember(bytes32,uint256)',
51+
'getRoleMemberCount(bytes32)',
52+
],
4253
};
4354

4455
const INTERFACE_IDS = {};
@@ -54,7 +65,7 @@ for (const k of Object.getOwnPropertyNames(INTERFACES)) {
5465
function shouldSupportInterfaces (interfaces = []) {
5566
describe('Contract interface', function () {
5667
beforeEach(function () {
57-
this.contractUnderTest = this.mock || this.token || this.holder;
68+
this.contractUnderTest = this.mock || this.token || this.holder || this.accessControl;
5869
});
5970

6071
for (const k of interfaces) {

0 commit comments

Comments
 (0)