mirror of openzeppelin-contracts
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
openzeppelin-contracts/test/access/manager/AccessManager.test.js

507 lines
21 KiB

const {
expectEvent,
expectRevert,
time: { duration },
} = require('@openzeppelin/test-helpers');
const { AccessMode } = require('../../helpers/enums');
const AccessManager = artifacts.require('AccessManager');
const AccessManagerAdapter = artifacts.require('AccessManagerAdapter');
const AccessManaged = artifacts.require('$AccessManagedMock');
const Ownable = artifacts.require('$Ownable');
const AccessControl = artifacts.require('$AccessControl');
const groupUtils = {
mask: group => 1n << BigInt(group),
decodeBitmap: hexBitmap => {
const m = BigInt(hexBitmap);
const allGroups = new Array(256).fill().map((_, i) => i.toString());
return allGroups.filter(i => (m & groupUtils.mask(i)) !== 0n);
},
role: group => web3.utils.asciiToHex('group:').padEnd(64, '0') + group.toString(16).padStart(2, '0'),
};
const PUBLIC_GROUP = '255';
contract('AccessManager', function (accounts) {
const [admin, nonAdmin, user1, user2, otherAuthority] = accounts;
beforeEach('deploy', async function () {
this.delay = duration.days(1);
this.manager = await AccessManager.new(this.delay, admin);
});
it('configures default admin rules', async function () {
expect(await this.manager.defaultAdmin()).to.equal(admin);
expect(await this.manager.defaultAdminDelay()).to.be.bignumber.equal(this.delay);
});
describe('groups', function () {
const group = '0';
const name = 'dao';
const otherGroup = '1';
const otherName = 'council';
describe('public group', function () {
it('is created automatically', async function () {
await expectEvent.inConstruction(this.manager, 'GroupUpdated', {
group: PUBLIC_GROUP,
name: 'public',
});
});
it('includes all users automatically', async function () {
const groups = groupUtils.decodeBitmap(await this.manager.getUserGroups(user1));
expect(groups).to.include(PUBLIC_GROUP);
});
});
describe('creating', function () {
it('admin can create groups', async function () {
const created = await this.manager.createGroup(group, name, { from: admin });
expectEvent(created, 'GroupUpdated', { group, name });
expect(await this.manager.hasGroup(group)).to.equal(true);
expect(await this.manager.hasGroup(otherGroup)).to.equal(false);
});
it('non-admin cannot create groups', async function () {
await expectRevert(this.manager.createGroup(group, name, { from: nonAdmin }), 'missing role');
});
it('cannot recreate a group', async function () {
await this.manager.createGroup(group, name, { from: admin });
await expectRevert(this.manager.createGroup(group, name, { from: admin }), 'AccessManager: existing group');
});
});
describe('updating', function () {
beforeEach('create group', async function () {
await this.manager.createGroup(group, name, { from: admin });
});
it('admin can update group', async function () {
const updated = await this.manager.updateGroupName(group, otherName, { from: admin });
expectEvent(updated, 'GroupUpdated', { group, name: otherName });
});
it('non-admin cannot update group', async function () {
await expectRevert(this.manager.updateGroupName(group, name, { from: nonAdmin }), 'missing role');
});
it('cannot update built in group', async function () {
await expectRevert(
this.manager.updateGroupName(PUBLIC_GROUP, name, { from: admin }),
'AccessManager: built-in group',
);
});
it('cannot update nonexistent group', async function () {
await expectRevert(
this.manager.updateGroupName(otherGroup, name, { from: admin }),
'AccessManager: unknown group',
);
});
});
describe('granting', function () {
beforeEach('create group', async function () {
await this.manager.createGroup(group, name, { from: admin });
});
it('admin can grant group', async function () {
const granted = await this.manager.grantGroup(group, user1, { from: admin });
expectEvent(granted, 'RoleGranted', { account: user1, role: groupUtils.role(group) });
const groups = groupUtils.decodeBitmap(await this.manager.getUserGroups(user1));
expect(groups).to.include(group);
});
it('non-admin cannot grant group', async function () {
await expectRevert(this.manager.grantGroup(group, user1, { from: nonAdmin }), 'missing role');
});
it('cannot grant nonexistent group', async function () {
await expectRevert(this.manager.grantGroup(otherGroup, user1, { from: admin }), 'AccessManager: unknown group');
});
});
describe('revoking & renouncing', function () {
beforeEach('create and grant group', async function () {
await this.manager.createGroup(group, name, { from: admin });
await this.manager.grantGroup(group, user1, { from: admin });
});
it('admin can revoke group', async function () {
await this.manager.revokeGroup(group, user1, { from: admin });
const groups = groupUtils.decodeBitmap(await this.manager.getUserGroups(user1));
expect(groups).to.not.include(group);
});
it('non-admin cannot revoke group', async function () {
await expectRevert(this.manager.revokeGroup(group, user1, { from: nonAdmin }), 'missing role');
});
it('user can renounce group', async function () {
await this.manager.renounceGroup(group, user1, { from: user1 });
const groups = groupUtils.decodeBitmap(await this.manager.getUserGroups(user1));
expect(groups).to.not.include(group);
});
it(`user cannot renounce other user's groups`, async function () {
await expectRevert(
this.manager.renounceGroup(group, user1, { from: user2 }),
'can only renounce roles for self',
);
await expectRevert(
this.manager.renounceGroup(group, user2, { from: user1 }),
'can only renounce roles for self',
);
});
it('cannot revoke public group', async function () {
await expectRevert(
this.manager.revokeGroup(PUBLIC_GROUP, user1, { from: admin }),
'AccessManager: irrevocable group',
);
});
it('cannot revoke nonexistent group', async function () {
await expectRevert(
this.manager.revokeGroup(otherGroup, user1, { from: admin }),
'AccessManager: unknown group',
);
await expectRevert(
this.manager.renounceGroup(otherGroup, user1, { from: user1 }),
'AccessManager: unknown group',
);
});
});
describe('querying', function () {
it('returns expected groups', async function () {
const getGroups = () => this.manager.getUserGroups(user1);
// only public group initially
expect(await getGroups()).to.equal('0x8000000000000000000000000000000000000000000000000000000000000000');
await this.manager.createGroup('0', '0', { from: admin });
await this.manager.grantGroup('0', user1, { from: admin });
expect(await getGroups()).to.equal('0x8000000000000000000000000000000000000000000000000000000000000001');
await this.manager.createGroup('1', '1', { from: admin });
await this.manager.grantGroup('1', user1, { from: admin });
expect(await getGroups()).to.equal('0x8000000000000000000000000000000000000000000000000000000000000003');
await this.manager.createGroup('16', '16', { from: admin });
await this.manager.grantGroup('16', user1, { from: admin });
expect(await getGroups()).to.equal('0x8000000000000000000000000000000000000000000000000000000000010003');
});
});
});
describe('allowing', function () {
const group = '1';
const groupMember = user1;
const selector = web3.eth.abi.encodeFunctionSignature('restrictedFunction()');
const otherSelector = web3.eth.abi.encodeFunctionSignature('otherRestrictedFunction()');
beforeEach('deploying managed contract', async function () {
await this.manager.createGroup(group, '', { from: admin });
await this.manager.grantGroup(group, groupMember, { from: admin });
this.managed = await AccessManaged.new(this.manager.address);
});
it('non-admin cannot change allowed groups', async function () {
await expectRevert(
this.manager.setFunctionAllowedGroup(this.managed.address, [selector], group, true, { from: nonAdmin }),
'missing role',
);
});
it('single selector', async function () {
const receipt = await this.manager.setFunctionAllowedGroup(this.managed.address, [selector], group, true, {
from: admin,
});
expectEvent(receipt, 'GroupAllowed', {
target: this.managed.address,
selector: selector.padEnd(66, '0'), // there seems to be a bug in decoding the indexed bytes4
group,
allowed: true,
});
const allowedGroups = await this.manager.getFunctionAllowedGroups(this.managed.address, selector);
expect(groupUtils.decodeBitmap(allowedGroups)).to.deep.equal([group]);
const otherAllowedGroups = await this.manager.getFunctionAllowedGroups(this.managed.address, otherSelector);
expect(groupUtils.decodeBitmap(otherAllowedGroups)).to.deep.equal([]);
const restricted = await this.managed.restrictedFunction({ from: groupMember });
expectEvent(restricted, 'RestrictedRan');
await expectRevert(
this.managed.otherRestrictedFunction({ from: groupMember }),
'AccessManaged: authority rejected',
);
});
it('multiple selectors', async function () {
const receipt = await this.manager.setFunctionAllowedGroup(
this.managed.address,
[selector, otherSelector],
group,
true,
{ from: admin },
);
expectEvent(receipt, 'GroupAllowed', {
target: this.managed.address,
selector: selector.padEnd(66, '0'), // there seems to be a bug in decoding the indexed bytes4
group,
allowed: true,
});
expectEvent(receipt, 'GroupAllowed', {
target: this.managed.address,
selector: otherSelector.padEnd(66, '0'), // there seems to be a bug in decoding the indexed bytes4
group,
allowed: true,
});
const allowedGroups = await this.manager.getFunctionAllowedGroups(this.managed.address, selector);
expect(groupUtils.decodeBitmap(allowedGroups)).to.deep.equal([group]);
const otherAllowedGroups = await this.manager.getFunctionAllowedGroups(this.managed.address, otherSelector);
expect(groupUtils.decodeBitmap(otherAllowedGroups)).to.deep.equal([group]);
const restricted = await this.managed.restrictedFunction({ from: groupMember });
expectEvent(restricted, 'RestrictedRan');
await this.managed.otherRestrictedFunction({ from: groupMember });
expectEvent(restricted, 'RestrictedRan');
});
it('works on open target', async function () {
await this.manager.setContractModeOpen(this.managed.address, { from: admin });
await this.manager.setFunctionAllowedGroup(this.managed.address, [selector], group, false, { from: admin });
});
it('works on closed target', async function () {
await this.manager.setContractModeClosed(this.managed.address, { from: admin });
await this.manager.setFunctionAllowedGroup(this.managed.address, [selector], group, false, { from: admin });
});
});
describe('disallowing', function () {
const group = '1';
const groupMember = user1;
const selector = web3.eth.abi.encodeFunctionSignature('restrictedFunction()');
const otherSelector = web3.eth.abi.encodeFunctionSignature('otherRestrictedFunction()');
beforeEach('deploying managed contract', async function () {
await this.manager.createGroup(group, '', { from: admin });
await this.manager.grantGroup(group, groupMember, { from: admin });
this.managed = await AccessManaged.new(this.manager.address);
await this.manager.setFunctionAllowedGroup(this.managed.address, [selector, otherSelector], group, true, {
from: admin,
});
});
it('non-admin cannot change disallowed groups', async function () {
await expectRevert(
this.manager.setFunctionAllowedGroup(this.managed.address, [selector], group, false, { from: nonAdmin }),
'missing role',
);
});
it('single selector', async function () {
const receipt = await this.manager.setFunctionAllowedGroup(this.managed.address, [selector], group, false, {
from: admin,
});
expectEvent(receipt, 'GroupAllowed', {
target: this.managed.address,
selector: selector.padEnd(66, '0'), // there seems to be a bug in decoding the indexed bytes4,
group,
allowed: false,
});
const allowedGroups = await this.manager.getFunctionAllowedGroups(this.managed.address, selector);
expect(groupUtils.decodeBitmap(allowedGroups)).to.deep.equal([]);
const otherAllowedGroups = await this.manager.getFunctionAllowedGroups(this.managed.address, otherSelector);
expect(groupUtils.decodeBitmap(otherAllowedGroups)).to.deep.equal([group]);
await expectRevert(this.managed.restrictedFunction({ from: groupMember }), 'AccessManaged: authority rejected');
const otherRestricted = await this.managed.otherRestrictedFunction({ from: groupMember });
expectEvent(otherRestricted, 'RestrictedRan');
});
it('multiple selectors', async function () {
const receipt = await this.manager.setFunctionAllowedGroup(
this.managed.address,
[selector, otherSelector],
group,
false,
{ from: admin },
);
expectEvent(receipt, 'GroupAllowed', {
target: this.managed.address,
selector: selector.padEnd(66, '0'), // there seems to be a bug in decoding the indexed bytes4
group,
allowed: false,
});
expectEvent(receipt, 'GroupAllowed', {
target: this.managed.address,
selector: otherSelector.padEnd(66, '0'), // there seems to be a bug in decoding the indexed bytes4
group,
allowed: false,
});
const allowedGroups = await this.manager.getFunctionAllowedGroups(this.managed.address, selector);
expect(groupUtils.decodeBitmap(allowedGroups)).to.deep.equal([]);
const otherAllowedGroups = await this.manager.getFunctionAllowedGroups(this.managed.address, otherSelector);
expect(groupUtils.decodeBitmap(otherAllowedGroups)).to.deep.equal([]);
await expectRevert(this.managed.restrictedFunction({ from: groupMember }), 'AccessManaged: authority rejected');
await expectRevert(
this.managed.otherRestrictedFunction({ from: groupMember }),
'AccessManaged: authority rejected',
);
});
it('works on open target', async function () {
await this.manager.setContractModeOpen(this.managed.address, { from: admin });
await this.manager.setFunctionAllowedGroup(this.managed.address, [selector], group, false, { from: admin });
});
it('works on closed target', async function () {
await this.manager.setContractModeClosed(this.managed.address, { from: admin });
await this.manager.setFunctionAllowedGroup(this.managed.address, [selector], group, false, { from: admin });
});
});
describe('modes', function () {
const group = '1';
const selector = web3.eth.abi.encodeFunctionSignature('restrictedFunction()');
beforeEach('deploying managed contract', async function () {
this.managed = await AccessManaged.new(this.manager.address);
await this.manager.createGroup('1', 'a group', { from: admin });
await this.manager.setFunctionAllowedGroup(this.managed.address, [selector], group, true, { from: admin });
});
it('custom mode is default', async function () {
expect(await this.manager.getContractMode(this.managed.address)).to.bignumber.equal(AccessMode.Custom);
const allowedGroups = await this.manager.getFunctionAllowedGroups(this.managed.address, selector);
expect(groupUtils.decodeBitmap(allowedGroups)).to.deep.equal([group]);
});
it('open mode', async function () {
const receipt = await this.manager.setContractModeOpen(this.managed.address, { from: admin });
expectEvent(receipt, 'AccessModeUpdated', {
target: this.managed.address,
mode: AccessMode.Open,
});
expect(await this.manager.getContractMode(this.managed.address)).to.bignumber.equal(AccessMode.Open);
const allowedGroups = await this.manager.getFunctionAllowedGroups(this.managed.address, selector);
expect(groupUtils.decodeBitmap(allowedGroups)).to.deep.equal([PUBLIC_GROUP]);
});
it('closed mode', async function () {
const receipt = await this.manager.setContractModeClosed(this.managed.address, { from: admin });
expectEvent(receipt, 'AccessModeUpdated', {
target: this.managed.address,
mode: AccessMode.Closed,
});
expect(await this.manager.getContractMode(this.managed.address)).to.bignumber.equal(AccessMode.Closed);
const allowedGroups = await this.manager.getFunctionAllowedGroups(this.managed.address, selector);
expect(groupUtils.decodeBitmap(allowedGroups)).to.deep.equal([]);
});
it('mode cycle', async function () {
await this.manager.setContractModeOpen(this.managed.address, { from: admin });
await this.manager.setContractModeClosed(this.managed.address, { from: admin });
await this.manager.setContractModeCustom(this.managed.address, { from: admin });
expect(await this.manager.getContractMode(this.managed.address)).to.bignumber.equal(AccessMode.Custom);
const allowedGroups = await this.manager.getFunctionAllowedGroups(this.managed.address, selector);
expect(groupUtils.decodeBitmap(allowedGroups)).to.deep.equal([group]);
});
it('non-admin cannot change mode', async function () {
await expectRevert(this.manager.setContractModeCustom(this.managed.address), 'missing role');
await expectRevert(this.manager.setContractModeOpen(this.managed.address), 'missing role');
await expectRevert(this.manager.setContractModeClosed(this.managed.address), 'missing role');
});
});
describe('transfering authority', function () {
beforeEach('deploying managed contract', async function () {
this.managed = await AccessManaged.new(this.manager.address);
});
it('admin can transfer authority', async function () {
await this.manager.transferContractAuthority(this.managed.address, otherAuthority, { from: admin });
expect(await this.managed.authority()).to.equal(otherAuthority);
});
it('non-admin cannot transfer authority', async function () {
await expectRevert(
this.manager.transferContractAuthority(this.managed.address, otherAuthority, { from: nonAdmin }),
'missing role',
);
});
});
describe('adapter', function () {
const group = '0';
beforeEach('deploying adapter', async function () {
await this.manager.createGroup(group, 'a group', { from: admin });
await this.manager.grantGroup(group, user1, { from: admin });
this.adapter = await AccessManagerAdapter.new(this.manager.address);
});
it('with ownable', async function () {
const target = await Ownable.new();
await target.transferOwnership(this.adapter.address);
const { data } = await target.$_checkOwner.request();
const selector = data.slice(0, 10);
await expectRevert(
this.adapter.relay(target.address, data, { from: user1 }),
'AccessManagerAdapter: caller not allowed',
);
await this.manager.setFunctionAllowedGroup(target.address, [selector], group, true, { from: admin });
await this.adapter.relay(target.address, data, { from: user1 });
});
it('with access control', async function () {
const ROLE = web3.utils.soliditySha3('ROLE');
const target = await AccessControl.new();
await target.$_grantRole(ROLE, this.adapter.address);
const { data } = await target.$_checkRole.request(ROLE);
const selector = data.slice(0, 10);
await expectRevert(
this.adapter.relay(target.address, data, { from: user1 }),
'AccessManagerAdapter: caller not allowed',
);
await this.manager.setFunctionAllowedGroup(target.address, [selector], group, true, { from: admin });
await this.adapter.relay(target.address, data, { from: user1 });
});
it('transfer authority', async function () {
await this.manager.transferContractAuthority(this.adapter.address, otherAuthority, { from: admin });
expect(await this.adapter.authority()).to.equal(otherAuthority);
});
});
});