Remove enumerable from AccessControl and add AccessControlEnumerable extension (#2512)
Co-authored-by: Francisco Giordano <frangio.1@gmail.com>
This commit is contained in:
@ -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)
|
||||
|
||||
|
||||
@ -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
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -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);
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
63
contracts/access/AccessControlEnumerable.sol
Normal file
63
contracts/access/AccessControlEnumerable.sol
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
@ -15,6 +15,8 @@ This directory provides ways to restrict who can access the functions of a contr
|
||||
|
||||
{{AccessControl}}
|
||||
|
||||
{{AccessControlEnumerable}}
|
||||
|
||||
== Timelock
|
||||
|
||||
{{TimelockController}}
|
||||
|
||||
15
contracts/mocks/AccessControlEnumerableMock.sol
Normal file
15
contracts/mocks/AccessControlEnumerableMock.sol
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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";
|
||||
@ -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");
|
||||
|
||||
|
||||
@ -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";
|
||||
@ -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");
|
||||
|
||||
|
||||
@ -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";
|
||||
@ -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");
|
||||
|
||||
182
test/access/AccessControl.behavior.js
Normal file
182
test/access/AccessControl.behavior.js
Normal file
@ -0,0 +1,182 @@
|
||||
const { expectEvent, expectRevert } = require('@openzeppelin/test-helpers');
|
||||
const { expect } = require('chai');
|
||||
|
||||
const DEFAULT_ADMIN_ROLE = '0x0000000000000000000000000000000000000000000000000000000000000000';
|
||||
const ROLE = web3.utils.soliditySha3('ROLE');
|
||||
const OTHER_ROLE = web3.utils.soliditySha3('OTHER_ROLE');
|
||||
|
||||
function shouldBehaveLikeAccessControl (errorPrefix, admin, authorized, other, otherAdmin, otherAuthorized) {
|
||||
describe('default admin', function () {
|
||||
it('deployer has default admin role', async function () {
|
||||
expect(await this.accessControl.hasRole(DEFAULT_ADMIN_ROLE, admin)).to.equal(true);
|
||||
});
|
||||
|
||||
it('other roles\'s admin is the default admin role', async function () {
|
||||
expect(await this.accessControl.getRoleAdmin(ROLE)).to.equal(DEFAULT_ADMIN_ROLE);
|
||||
});
|
||||
|
||||
it('default admin role\'s admin is itself', async function () {
|
||||
expect(await this.accessControl.getRoleAdmin(DEFAULT_ADMIN_ROLE)).to.equal(DEFAULT_ADMIN_ROLE);
|
||||
});
|
||||
});
|
||||
|
||||
describe('granting', function () {
|
||||
it('admin can grant role to other accounts', async function () {
|
||||
const receipt = await this.accessControl.grantRole(ROLE, authorized, { from: admin });
|
||||
expectEvent(receipt, 'RoleGranted', { account: authorized, role: ROLE, sender: admin });
|
||||
|
||||
expect(await this.accessControl.hasRole(ROLE, authorized)).to.equal(true);
|
||||
});
|
||||
|
||||
it('non-admin cannot grant role to other accounts', async function () {
|
||||
await expectRevert(
|
||||
this.accessControl.grantRole(ROLE, authorized, { from: other }),
|
||||
`${errorPrefix}: sender must be an admin to grant`,
|
||||
);
|
||||
});
|
||||
|
||||
it('accounts can be granted a role multiple times', async function () {
|
||||
await this.accessControl.grantRole(ROLE, authorized, { from: admin });
|
||||
const receipt = await this.accessControl.grantRole(ROLE, authorized, { from: admin });
|
||||
expectEvent.notEmitted(receipt, 'RoleGranted');
|
||||
});
|
||||
});
|
||||
|
||||
describe('revoking', function () {
|
||||
it('roles that are not had can be revoked', async function () {
|
||||
expect(await this.accessControl.hasRole(ROLE, authorized)).to.equal(false);
|
||||
|
||||
const receipt = await this.accessControl.revokeRole(ROLE, authorized, { from: admin });
|
||||
expectEvent.notEmitted(receipt, 'RoleRevoked');
|
||||
});
|
||||
|
||||
context('with granted role', function () {
|
||||
beforeEach(async function () {
|
||||
await this.accessControl.grantRole(ROLE, authorized, { from: admin });
|
||||
});
|
||||
|
||||
it('admin can revoke role', async function () {
|
||||
const receipt = await this.accessControl.revokeRole(ROLE, authorized, { from: admin });
|
||||
expectEvent(receipt, 'RoleRevoked', { account: authorized, role: ROLE, sender: admin });
|
||||
|
||||
expect(await this.accessControl.hasRole(ROLE, authorized)).to.equal(false);
|
||||
});
|
||||
|
||||
it('non-admin cannot revoke role', async function () {
|
||||
await expectRevert(
|
||||
this.accessControl.revokeRole(ROLE, authorized, { from: other }),
|
||||
`${errorPrefix}: sender must be an admin to revoke`,
|
||||
);
|
||||
});
|
||||
|
||||
it('a role can be revoked multiple times', async function () {
|
||||
await this.accessControl.revokeRole(ROLE, authorized, { from: admin });
|
||||
|
||||
const receipt = await this.accessControl.revokeRole(ROLE, authorized, { from: admin });
|
||||
expectEvent.notEmitted(receipt, 'RoleRevoked');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('renouncing', function () {
|
||||
it('roles that are not had can be renounced', async function () {
|
||||
const receipt = await this.accessControl.renounceRole(ROLE, authorized, { from: authorized });
|
||||
expectEvent.notEmitted(receipt, 'RoleRevoked');
|
||||
});
|
||||
|
||||
context('with granted role', function () {
|
||||
beforeEach(async function () {
|
||||
await this.accessControl.grantRole(ROLE, authorized, { from: admin });
|
||||
});
|
||||
|
||||
it('bearer can renounce role', async function () {
|
||||
const receipt = await this.accessControl.renounceRole(ROLE, authorized, { from: authorized });
|
||||
expectEvent(receipt, 'RoleRevoked', { account: authorized, role: ROLE, sender: authorized });
|
||||
|
||||
expect(await this.accessControl.hasRole(ROLE, authorized)).to.equal(false);
|
||||
});
|
||||
|
||||
it('only the sender can renounce their roles', async function () {
|
||||
await expectRevert(
|
||||
this.accessControl.renounceRole(ROLE, authorized, { from: admin }),
|
||||
`${errorPrefix}: can only renounce roles for self`,
|
||||
);
|
||||
});
|
||||
|
||||
it('a role can be renounced multiple times', async function () {
|
||||
await this.accessControl.renounceRole(ROLE, authorized, { from: authorized });
|
||||
|
||||
const receipt = await this.accessControl.renounceRole(ROLE, authorized, { from: authorized });
|
||||
expectEvent.notEmitted(receipt, 'RoleRevoked');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('setting role admin', function () {
|
||||
beforeEach(async function () {
|
||||
const receipt = await this.accessControl.setRoleAdmin(ROLE, OTHER_ROLE);
|
||||
expectEvent(receipt, 'RoleAdminChanged', {
|
||||
role: ROLE,
|
||||
previousAdminRole: DEFAULT_ADMIN_ROLE,
|
||||
newAdminRole: OTHER_ROLE,
|
||||
});
|
||||
|
||||
await this.accessControl.grantRole(OTHER_ROLE, otherAdmin, { from: admin });
|
||||
});
|
||||
|
||||
it('a role\'s admin role can be changed', async function () {
|
||||
expect(await this.accessControl.getRoleAdmin(ROLE)).to.equal(OTHER_ROLE);
|
||||
});
|
||||
|
||||
it('the new admin can grant roles', async function () {
|
||||
const receipt = await this.accessControl.grantRole(ROLE, authorized, { from: otherAdmin });
|
||||
expectEvent(receipt, 'RoleGranted', { account: authorized, role: ROLE, sender: otherAdmin });
|
||||
});
|
||||
|
||||
it('the new admin can revoke roles', async function () {
|
||||
await this.accessControl.grantRole(ROLE, authorized, { from: otherAdmin });
|
||||
const receipt = await this.accessControl.revokeRole(ROLE, authorized, { from: otherAdmin });
|
||||
expectEvent(receipt, 'RoleRevoked', { account: authorized, role: ROLE, sender: otherAdmin });
|
||||
});
|
||||
|
||||
it('a role\'s previous admins no longer grant roles', async function () {
|
||||
await expectRevert(
|
||||
this.accessControl.grantRole(ROLE, authorized, { from: admin }),
|
||||
'AccessControl: sender must be an admin to grant',
|
||||
);
|
||||
});
|
||||
|
||||
it('a role\'s previous admins no longer revoke roles', async function () {
|
||||
await expectRevert(
|
||||
this.accessControl.revokeRole(ROLE, authorized, { from: admin }),
|
||||
'AccessControl: sender must be an admin to revoke',
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function shouldBehaveLikeAccessControlEnumerable (errorPrefix, admin, authorized, other, otherAdmin, otherAuthorized) {
|
||||
describe('enumerating', function () {
|
||||
it('role bearers can be enumerated', async function () {
|
||||
await this.accessControl.grantRole(ROLE, authorized, { from: admin });
|
||||
await this.accessControl.grantRole(ROLE, other, { from: admin });
|
||||
await this.accessControl.grantRole(ROLE, otherAuthorized, { from: admin });
|
||||
await this.accessControl.revokeRole(ROLE, other, { from: admin });
|
||||
|
||||
const memberCount = await this.accessControl.getRoleMemberCount(ROLE);
|
||||
expect(memberCount).to.bignumber.equal('2');
|
||||
|
||||
const bearers = [];
|
||||
for (let i = 0; i < memberCount; ++i) {
|
||||
bearers.push(await this.accessControl.getRoleMember(ROLE, i));
|
||||
}
|
||||
|
||||
expect(bearers).to.have.members([authorized, otherAuthorized]);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
shouldBehaveLikeAccessControl,
|
||||
shouldBehaveLikeAccessControlEnumerable,
|
||||
};
|
||||
@ -1,182 +1,13 @@
|
||||
const { expectEvent, expectRevert } = require('@openzeppelin/test-helpers');
|
||||
|
||||
const { expect } = require('chai');
|
||||
const {
|
||||
shouldBehaveLikeAccessControl,
|
||||
} = require('./AccessControl.behavior.js');
|
||||
|
||||
const AccessControlMock = artifacts.require('AccessControlMock');
|
||||
|
||||
contract('AccessControl', function (accounts) {
|
||||
const [ admin, authorized, otherAuthorized, other, otherAdmin ] = accounts;
|
||||
|
||||
const DEFAULT_ADMIN_ROLE = '0x0000000000000000000000000000000000000000000000000000000000000000';
|
||||
const ROLE = web3.utils.soliditySha3('ROLE');
|
||||
const OTHER_ROLE = web3.utils.soliditySha3('OTHER_ROLE');
|
||||
|
||||
beforeEach(async function () {
|
||||
this.accessControl = await AccessControlMock.new({ from: admin });
|
||||
this.accessControl = await AccessControlMock.new({ from: accounts[0] });
|
||||
});
|
||||
|
||||
describe('default admin', function () {
|
||||
it('deployer has default admin role', async function () {
|
||||
expect(await this.accessControl.hasRole(DEFAULT_ADMIN_ROLE, admin)).to.equal(true);
|
||||
});
|
||||
|
||||
it('other roles\'s admin is the default admin role', async function () {
|
||||
expect(await this.accessControl.getRoleAdmin(ROLE)).to.equal(DEFAULT_ADMIN_ROLE);
|
||||
});
|
||||
|
||||
it('default admin role\'s admin is itself', async function () {
|
||||
expect(await this.accessControl.getRoleAdmin(DEFAULT_ADMIN_ROLE)).to.equal(DEFAULT_ADMIN_ROLE);
|
||||
});
|
||||
});
|
||||
|
||||
describe('granting', function () {
|
||||
it('admin can grant role to other accounts', async function () {
|
||||
const receipt = await this.accessControl.grantRole(ROLE, authorized, { from: admin });
|
||||
expectEvent(receipt, 'RoleGranted', { account: authorized, role: ROLE, sender: admin });
|
||||
|
||||
expect(await this.accessControl.hasRole(ROLE, authorized)).to.equal(true);
|
||||
});
|
||||
|
||||
it('non-admin cannot grant role to other accounts', async function () {
|
||||
await expectRevert(
|
||||
this.accessControl.grantRole(ROLE, authorized, { from: other }),
|
||||
'AccessControl: sender must be an admin to grant',
|
||||
);
|
||||
});
|
||||
|
||||
it('accounts can be granted a role multiple times', async function () {
|
||||
await this.accessControl.grantRole(ROLE, authorized, { from: admin });
|
||||
const receipt = await this.accessControl.grantRole(ROLE, authorized, { from: admin });
|
||||
expectEvent.notEmitted(receipt, 'RoleGranted');
|
||||
});
|
||||
});
|
||||
|
||||
describe('revoking', function () {
|
||||
it('roles that are not had can be revoked', async function () {
|
||||
expect(await this.accessControl.hasRole(ROLE, authorized)).to.equal(false);
|
||||
|
||||
const receipt = await this.accessControl.revokeRole(ROLE, authorized, { from: admin });
|
||||
expectEvent.notEmitted(receipt, 'RoleRevoked');
|
||||
});
|
||||
|
||||
context('with granted role', function () {
|
||||
beforeEach(async function () {
|
||||
await this.accessControl.grantRole(ROLE, authorized, { from: admin });
|
||||
});
|
||||
|
||||
it('admin can revoke role', async function () {
|
||||
const receipt = await this.accessControl.revokeRole(ROLE, authorized, { from: admin });
|
||||
expectEvent(receipt, 'RoleRevoked', { account: authorized, role: ROLE, sender: admin });
|
||||
|
||||
expect(await this.accessControl.hasRole(ROLE, authorized)).to.equal(false);
|
||||
});
|
||||
|
||||
it('non-admin cannot revoke role', async function () {
|
||||
await expectRevert(
|
||||
this.accessControl.revokeRole(ROLE, authorized, { from: other }),
|
||||
'AccessControl: sender must be an admin to revoke',
|
||||
);
|
||||
});
|
||||
|
||||
it('a role can be revoked multiple times', async function () {
|
||||
await this.accessControl.revokeRole(ROLE, authorized, { from: admin });
|
||||
|
||||
const receipt = await this.accessControl.revokeRole(ROLE, authorized, { from: admin });
|
||||
expectEvent.notEmitted(receipt, 'RoleRevoked');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('renouncing', function () {
|
||||
it('roles that are not had can be renounced', async function () {
|
||||
const receipt = await this.accessControl.renounceRole(ROLE, authorized, { from: authorized });
|
||||
expectEvent.notEmitted(receipt, 'RoleRevoked');
|
||||
});
|
||||
|
||||
context('with granted role', function () {
|
||||
beforeEach(async function () {
|
||||
await this.accessControl.grantRole(ROLE, authorized, { from: admin });
|
||||
});
|
||||
|
||||
it('bearer can renounce role', async function () {
|
||||
const receipt = await this.accessControl.renounceRole(ROLE, authorized, { from: authorized });
|
||||
expectEvent(receipt, 'RoleRevoked', { account: authorized, role: ROLE, sender: authorized });
|
||||
|
||||
expect(await this.accessControl.hasRole(ROLE, authorized)).to.equal(false);
|
||||
});
|
||||
|
||||
it('only the sender can renounce their roles', async function () {
|
||||
await expectRevert(
|
||||
this.accessControl.renounceRole(ROLE, authorized, { from: admin }),
|
||||
'AccessControl: can only renounce roles for self',
|
||||
);
|
||||
});
|
||||
|
||||
it('a role can be renounced multiple times', async function () {
|
||||
await this.accessControl.renounceRole(ROLE, authorized, { from: authorized });
|
||||
|
||||
const receipt = await this.accessControl.renounceRole(ROLE, authorized, { from: authorized });
|
||||
expectEvent.notEmitted(receipt, 'RoleRevoked');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('enumerating', function () {
|
||||
it('role bearers can be enumerated', async function () {
|
||||
await this.accessControl.grantRole(ROLE, authorized, { from: admin });
|
||||
await this.accessControl.grantRole(ROLE, otherAuthorized, { from: admin });
|
||||
|
||||
const memberCount = await this.accessControl.getRoleMemberCount(ROLE);
|
||||
expect(memberCount).to.bignumber.equal('2');
|
||||
|
||||
const bearers = [];
|
||||
for (let i = 0; i < memberCount; ++i) {
|
||||
bearers.push(await this.accessControl.getRoleMember(ROLE, i));
|
||||
}
|
||||
|
||||
expect(bearers).to.have.members([authorized, otherAuthorized]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setting role admin', function () {
|
||||
beforeEach(async function () {
|
||||
const receipt = await this.accessControl.setRoleAdmin(ROLE, OTHER_ROLE);
|
||||
expectEvent(receipt, 'RoleAdminChanged', {
|
||||
role: ROLE,
|
||||
previousAdminRole: DEFAULT_ADMIN_ROLE,
|
||||
newAdminRole: OTHER_ROLE,
|
||||
});
|
||||
|
||||
await this.accessControl.grantRole(OTHER_ROLE, otherAdmin, { from: admin });
|
||||
});
|
||||
|
||||
it('a role\'s admin role can be changed', async function () {
|
||||
expect(await this.accessControl.getRoleAdmin(ROLE)).to.equal(OTHER_ROLE);
|
||||
});
|
||||
|
||||
it('the new admin can grant roles', async function () {
|
||||
const receipt = await this.accessControl.grantRole(ROLE, authorized, { from: otherAdmin });
|
||||
expectEvent(receipt, 'RoleGranted', { account: authorized, role: ROLE, sender: otherAdmin });
|
||||
});
|
||||
|
||||
it('the new admin can revoke roles', async function () {
|
||||
await this.accessControl.grantRole(ROLE, authorized, { from: otherAdmin });
|
||||
const receipt = await this.accessControl.revokeRole(ROLE, authorized, { from: otherAdmin });
|
||||
expectEvent(receipt, 'RoleRevoked', { account: authorized, role: ROLE, sender: otherAdmin });
|
||||
});
|
||||
|
||||
it('a role\'s previous admins no longer grant roles', async function () {
|
||||
await expectRevert(
|
||||
this.accessControl.grantRole(ROLE, authorized, { from: admin }),
|
||||
'AccessControl: sender must be an admin to grant',
|
||||
);
|
||||
});
|
||||
|
||||
it('a role\'s previous admins no longer revoke roles', async function () {
|
||||
await expectRevert(
|
||||
this.accessControl.revokeRole(ROLE, authorized, { from: admin }),
|
||||
'AccessControl: sender must be an admin to revoke',
|
||||
);
|
||||
});
|
||||
});
|
||||
shouldBehaveLikeAccessControl('AccessControl', ...accounts);
|
||||
});
|
||||
|
||||
15
test/access/AccessControlEnumerable.test.js
Normal file
15
test/access/AccessControlEnumerable.test.js
Normal file
@ -0,0 +1,15 @@
|
||||
const {
|
||||
shouldBehaveLikeAccessControl,
|
||||
shouldBehaveLikeAccessControlEnumerable,
|
||||
} = require('./AccessControl.behavior.js');
|
||||
|
||||
const AccessControlMock = artifacts.require('AccessControlEnumerableMock');
|
||||
|
||||
contract('AccessControl', function (accounts) {
|
||||
beforeEach(async function () {
|
||||
this.accessControl = await AccessControlMock.new({ from: accounts[0] });
|
||||
});
|
||||
|
||||
shouldBehaveLikeAccessControl('AccessControl', ...accounts);
|
||||
shouldBehaveLikeAccessControlEnumerable('AccessControl', ...accounts);
|
||||
});
|
||||
Reference in New Issue
Block a user