123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506 |
- 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);
- });
- });
- });
|