diff --git a/contracts/access/rbac/Roles.sol b/contracts/access/rbac/Roles.sol index de46894095f..d478f6afa65 100644 --- a/contracts/access/rbac/Roles.sol +++ b/contracts/access/rbac/Roles.sol @@ -21,6 +21,17 @@ library Roles { _role.bearer[_account] = true; } + /** + * @dev give multiple accounts access to this role + */ + function addMany(Role storage _role, address[] _accounts) + internal + { + for (uint256 i = 0; i < _accounts.length; ++i) { + add(_role, _accounts[i]); + } + } + /** * @dev remove an account's access to this role */ diff --git a/contracts/mocks/RolesMock.sol b/contracts/mocks/RolesMock.sol new file mode 100644 index 00000000000..cf20dfa45e1 --- /dev/null +++ b/contracts/mocks/RolesMock.sol @@ -0,0 +1,30 @@ +pragma solidity ^0.4.24; + +import "../access/rbac/Roles.sol"; + + +contract RolesMock { + using Roles for Roles.Role; + + Roles.Role private dummyRole; + + function add(address _account) public { + dummyRole.add(_account); + } + + function addMany(address[] _accounts) public { + dummyRole.addMany(_accounts); + } + + function remove(address _account) public { + dummyRole.remove(_account); + } + + function check(address _account) public view { + dummyRole.check(_account); + } + + function has(address _account) public view returns (bool) { + return dummyRole.has(_account); + } +} diff --git a/test/ownership/rbac/RBAC.test.js b/test/access/rbac/RBAC.test.js similarity index 100% rename from test/ownership/rbac/RBAC.test.js rename to test/access/rbac/RBAC.test.js diff --git a/test/access/rbac/Roles.test.js b/test/access/rbac/Roles.test.js new file mode 100644 index 00000000000..d7269714d5f --- /dev/null +++ b/test/access/rbac/Roles.test.js @@ -0,0 +1,72 @@ +const { assertRevert } = require('../../helpers/assertRevert'); + +const RolesMock = artifacts.require('RolesMock'); + +require('chai') + .should(); + +contract('Roles', function ([_, authorized, otherAuthorized, anyone]) { + beforeEach(async function () { + this.roles = await RolesMock.new(); + this.testRole = async (account, expected) => { + if (expected) { + (await this.roles.has(account)).should.equal(true); + await this.roles.check(account); // this call shouldn't revert, but is otherwise a no-op + } else { + (await this.roles.has(account)).should.equal(false); + await assertRevert(this.roles.check(account)); + } + }; + }); + + context('initially', function () { + it('doesn\'t pre-assign roles', async function () { + await this.testRole(authorized, false); + await this.testRole(otherAuthorized, false); + await this.testRole(anyone, false); + }); + + describe('adding roles', function () { + it('adds roles to a single account', async function () { + await this.roles.add(authorized); + await this.testRole(authorized, true); + await this.testRole(anyone, false); + }); + + it('adds roles to an already-assigned account', async function () { + await this.roles.add(authorized); + await this.roles.add(authorized); + await this.testRole(authorized, true); + }); + + it('adds roles to multiple accounts', async function () { + await this.roles.addMany([authorized, otherAuthorized]); + await this.testRole(authorized, true); + await this.testRole(otherAuthorized, true); + }); + + it('adds roles to multiple identical accounts', async function () { + await this.roles.addMany([authorized, authorized]); + await this.testRole(authorized, true); + }); + }); + }); + + context('with added roles', function () { + beforeEach(async function () { + await this.roles.addMany([authorized, otherAuthorized]); + }); + + describe('removing roles', function () { + it('removes a single role', async function () { + await this.roles.remove(authorized); + await this.testRole(authorized, false); + await this.testRole(otherAuthorized, true); + }); + + it('removes unassigned roles', async function () { + await this.roles.remove(anyone); + }); + }); + }); +});