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

Add modifier & internal function with standard revert message in AccessControl #2609

Merged
merged 18 commits into from
Apr 16, 2021
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions contracts/access/AccessControl.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
pragma solidity ^0.8.0;

import "../utils/Context.sol";
import "../utils/Strings.sol";
import "../utils/introspection/ERC165.sol";

/**
Expand Down Expand Up @@ -91,6 +92,15 @@ abstract contract AccessControl is Context, IAccessControl, ERC165 {
*/
event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);

/**
* @dev Modifier that checks that an account has a specific role. Reverts
* with a standardized message including the required role.
*/
modifier onlyRole(bytes32 role) {
frangio marked this conversation as resolved.
Show resolved Hide resolved
_checkRole(role, _msgSender());
_;
}

/**
* @dev See {IERC165-supportsInterface}.
*/
Expand All @@ -106,6 +116,20 @@ abstract contract AccessControl is Context, IAccessControl, ERC165 {
return _roles[role].members[account];
}

/**
* @dev Revert with a standard message if `account` is missing `role`.
*/
function _checkRole(bytes32 role, address account) internal view {
if(!hasRole(role, account)) {
revert(string(abi.encodePacked(
"AccessControl: account ",
Strings.toHexString(uint160(account), 20),
" is missing role ",
Strings.toHexString(uint256(role), 32)
)));
}
}

/**
* @dev Returns the admin role that controls `role`. See {grantRole} and
* {revokeRole}.
Expand Down
12 changes: 6 additions & 6 deletions contracts/governance/TimelockController.sol
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ contract TimelockController is AccessControl {
* considered. Granting a role to `address(0)` is equivalent to enabling
* this role for everyone.
*/
modifier onlyRole(bytes32 role) {
modifier onlyRoleOrOpenRole(bytes32 role) {
Amxx marked this conversation as resolved.
Show resolved Hide resolved
require(hasRole(role, _msgSender()) || hasRole(role, address(0)), "TimelockController: sender requires permission");
_;
}
Expand Down Expand Up @@ -163,7 +163,7 @@ contract TimelockController is AccessControl {
*
* - the caller must have the 'proposer' role.
*/
function schedule(address target, uint256 value, bytes calldata data, bytes32 predecessor, bytes32 salt, uint256 delay) public virtual onlyRole(PROPOSER_ROLE) {
function schedule(address target, uint256 value, bytes calldata data, bytes32 predecessor, bytes32 salt, uint256 delay) public virtual onlyRoleOrOpenRole(PROPOSER_ROLE) {
bytes32 id = hashOperation(target, value, data, predecessor, salt);
_schedule(id, delay);
emit CallScheduled(id, 0, target, value, data, predecessor, delay);
Expand All @@ -178,7 +178,7 @@ contract TimelockController is AccessControl {
*
* - the caller must have the 'proposer' role.
*/
function scheduleBatch(address[] calldata targets, uint256[] calldata values, bytes[] calldata datas, bytes32 predecessor, bytes32 salt, uint256 delay) public virtual onlyRole(PROPOSER_ROLE) {
function scheduleBatch(address[] calldata targets, uint256[] calldata values, bytes[] calldata datas, bytes32 predecessor, bytes32 salt, uint256 delay) public virtual onlyRoleOrOpenRole(PROPOSER_ROLE) {
require(targets.length == values.length, "TimelockController: length mismatch");
require(targets.length == datas.length, "TimelockController: length mismatch");

Expand Down Expand Up @@ -206,7 +206,7 @@ contract TimelockController is AccessControl {
*
* - the caller must have the 'proposer' role.
*/
function cancel(bytes32 id) public virtual onlyRole(PROPOSER_ROLE) {
function cancel(bytes32 id) public virtual onlyRoleOrOpenRole(PROPOSER_ROLE) {
require(isOperationPending(id), "TimelockController: operation cannot be cancelled");
delete _timestamps[id];

Expand All @@ -222,7 +222,7 @@ contract TimelockController is AccessControl {
*
* - the caller must have the 'executor' role.
*/
function execute(address target, uint256 value, bytes calldata data, bytes32 predecessor, bytes32 salt) public payable virtual onlyRole(EXECUTOR_ROLE) {
function execute(address target, uint256 value, bytes calldata data, bytes32 predecessor, bytes32 salt) public payable virtual onlyRoleOrOpenRole(EXECUTOR_ROLE) {
bytes32 id = hashOperation(target, value, data, predecessor, salt);
_beforeCall(predecessor);
_call(id, 0, target, value, data);
Expand All @@ -238,7 +238,7 @@ contract TimelockController is AccessControl {
*
* - the caller must have the 'executor' role.
*/
function executeBatch(address[] calldata targets, uint256[] calldata values, bytes[] calldata datas, bytes32 predecessor, bytes32 salt) public payable virtual onlyRole(EXECUTOR_ROLE) {
function executeBatch(address[] calldata targets, uint256[] calldata values, bytes[] calldata datas, bytes32 predecessor, bytes32 salt) public payable virtual onlyRoleOrOpenRole(EXECUTOR_ROLE) {
require(targets.length == values.length, "TimelockController: length mismatch");
require(targets.length == datas.length, "TimelockController: length mismatch");

Expand Down
2 changes: 2 additions & 0 deletions contracts/mocks/AccessControlEnumerableMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@ contract AccessControlEnumerableMock is AccessControlEnumerable {
function setRoleAdmin(bytes32 roleId, bytes32 adminRoleId) public {
_setRoleAdmin(roleId, adminRoleId);
}

function senderProtected(bytes32 roleId) public onlyRole(roleId) {}
}
2 changes: 2 additions & 0 deletions contracts/mocks/AccessControlMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@ contract AccessControlMock is AccessControl {
function setRoleAdmin(bytes32 roleId, bytes32 adminRoleId) public {
_setRoleAdmin(roleId, adminRoleId);
}

function senderProtected(bytes32 roleId) public onlyRole(roleId) {}
}
31 changes: 26 additions & 5 deletions test/access/AccessControl.behavior.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,8 @@ function shouldBehaveLikeAccessControl (errorPrefix, admin, authorized, other, o
});

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);
beforeEach(async function () {
await this.accessControl.grantRole(ROLE, authorized, { from: admin });
});

it('non-admin cannot grant role to other accounts', async function () {
Expand Down Expand Up @@ -157,6 +154,30 @@ function shouldBehaveLikeAccessControl (errorPrefix, admin, authorized, other, o
);
});
});

describe('onlySenderWithRole modifier', function () {
Amxx marked this conversation as resolved.
Show resolved Hide resolved
beforeEach(async function () {
await this.accessControl.grantRole(ROLE, authorized, { from: admin });
});

it('do not revert if sender has role', async function () {
await this.accessControl.senderProtected(ROLE, { from: authorized });
});

it('revert if sender doesn\'t have role #1', async function () {
await expectRevert(
this.accessControl.senderProtected(ROLE, { from: other }),
`AccessControl: account ${other.toLowerCase()} is missing role ${ROLE}`,
);
});

it('revert if sender doesn\'t have role #2', async function () {
await expectRevert(
this.accessControl.senderProtected(OTHER_ROLE, { from: authorized }),
`AccessControl: account ${authorized.toLowerCase()} is missing role ${OTHER_ROLE}`,
);
});
});
}

function shouldBehaveLikeAccessControlEnumerable (errorPrefix, admin, authorized, other, otherAdmin, otherAuthorized) {
Expand Down