AccessManager.test.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514
  1. const {
  2. expectEvent,
  3. expectRevert,
  4. time: { duration },
  5. } = require('@openzeppelin/test-helpers');
  6. const { AccessMode } = require('../../helpers/enums');
  7. const AccessManager = artifacts.require('AccessManager');
  8. const AccessManagerAdapter = artifacts.require('AccessManagerAdapter');
  9. const AccessManaged = artifacts.require('$AccessManagedMock');
  10. const Ownable = artifacts.require('$Ownable');
  11. const AccessControl = artifacts.require('$AccessControl');
  12. const groupUtils = {
  13. mask: group => 1n << BigInt(group),
  14. decodeBitmap: hexBitmap => {
  15. const m = BigInt(hexBitmap);
  16. const allGroups = new Array(256).fill().map((_, i) => i.toString());
  17. return allGroups.filter(i => (m & groupUtils.mask(i)) !== 0n);
  18. },
  19. role: group => web3.utils.asciiToHex('group:').padEnd(64, '0') + group.toString(16).padStart(2, '0'),
  20. };
  21. const PUBLIC_GROUP = '255';
  22. contract('AccessManager', function (accounts) {
  23. const [admin, nonAdmin, user1, user2, otherAuthority] = accounts;
  24. beforeEach('deploy', async function () {
  25. this.delay = duration.days(1);
  26. this.manager = await AccessManager.new(this.delay, admin);
  27. });
  28. it('configures default admin rules', async function () {
  29. expect(await this.manager.defaultAdmin()).to.equal(admin);
  30. expect(await this.manager.defaultAdminDelay()).to.be.bignumber.equal(this.delay);
  31. });
  32. describe('groups', function () {
  33. const group = '0';
  34. const name = 'dao';
  35. const otherGroup = '1';
  36. const otherName = 'council';
  37. describe('public group', function () {
  38. it('is created automatically', async function () {
  39. await expectEvent.inConstruction(this.manager, 'GroupUpdated', {
  40. group: PUBLIC_GROUP,
  41. name: 'public',
  42. });
  43. });
  44. it('includes all users automatically', async function () {
  45. const groups = groupUtils.decodeBitmap(await this.manager.getUserGroups(user1));
  46. expect(groups).to.include(PUBLIC_GROUP);
  47. });
  48. });
  49. describe('creating', function () {
  50. it('admin can create groups', async function () {
  51. const created = await this.manager.createGroup(group, name, { from: admin });
  52. expectEvent(created, 'GroupUpdated', { group, name });
  53. expect(await this.manager.hasGroup(group)).to.equal(true);
  54. expect(await this.manager.hasGroup(otherGroup)).to.equal(false);
  55. });
  56. it('non-admin cannot create groups', async function () {
  57. await expectRevert(this.manager.createGroup(group, name, { from: nonAdmin }), 'missing role');
  58. });
  59. it('cannot recreate a group', async function () {
  60. await this.manager.createGroup(group, name, { from: admin });
  61. await expectRevert(this.manager.createGroup(group, name, { from: admin }), 'AccessManager: existing group');
  62. });
  63. });
  64. describe('updating', function () {
  65. beforeEach('create group', async function () {
  66. await this.manager.createGroup(group, name, { from: admin });
  67. });
  68. it('admin can update group', async function () {
  69. const updated = await this.manager.updateGroupName(group, otherName, { from: admin });
  70. expectEvent(updated, 'GroupUpdated', { group, name: otherName });
  71. });
  72. it('non-admin cannot update group', async function () {
  73. await expectRevert(this.manager.updateGroupName(group, name, { from: nonAdmin }), 'missing role');
  74. });
  75. it('cannot update built in group', async function () {
  76. await expectRevert(
  77. this.manager.updateGroupName(PUBLIC_GROUP, name, { from: admin }),
  78. 'AccessManager: built-in group',
  79. );
  80. });
  81. it('cannot update nonexistent group', async function () {
  82. await expectRevert(
  83. this.manager.updateGroupName(otherGroup, name, { from: admin }),
  84. 'AccessManager: unknown group',
  85. );
  86. });
  87. });
  88. describe('granting', function () {
  89. beforeEach('create group', async function () {
  90. await this.manager.createGroup(group, name, { from: admin });
  91. });
  92. it('admin can grant group', async function () {
  93. const granted = await this.manager.grantGroup(group, user1, { from: admin });
  94. expectEvent(granted, 'RoleGranted', { account: user1, role: groupUtils.role(group) });
  95. const groups = groupUtils.decodeBitmap(await this.manager.getUserGroups(user1));
  96. expect(groups).to.include(group);
  97. });
  98. it('non-admin cannot grant group', async function () {
  99. await expectRevert(this.manager.grantGroup(group, user1, { from: nonAdmin }), 'missing role');
  100. });
  101. it('cannot grant nonexistent group', async function () {
  102. await expectRevert(this.manager.grantGroup(otherGroup, user1, { from: admin }), 'AccessManager: unknown group');
  103. });
  104. });
  105. describe('revoking & renouncing', function () {
  106. beforeEach('create and grant group', async function () {
  107. await this.manager.createGroup(group, name, { from: admin });
  108. await this.manager.grantGroup(group, user1, { from: admin });
  109. });
  110. it('admin can revoke group', async function () {
  111. await this.manager.revokeGroup(group, user1, { from: admin });
  112. const groups = groupUtils.decodeBitmap(await this.manager.getUserGroups(user1));
  113. expect(groups).to.not.include(group);
  114. });
  115. it('non-admin cannot revoke group', async function () {
  116. await expectRevert(this.manager.revokeGroup(group, user1, { from: nonAdmin }), 'missing role');
  117. });
  118. it('user can renounce group', async function () {
  119. await this.manager.renounceGroup(group, user1, { from: user1 });
  120. const groups = groupUtils.decodeBitmap(await this.manager.getUserGroups(user1));
  121. expect(groups).to.not.include(group);
  122. });
  123. it(`user cannot renounce other user's groups`, async function () {
  124. await expectRevert(
  125. this.manager.renounceGroup(group, user1, { from: user2 }),
  126. 'can only renounce roles for self',
  127. );
  128. await expectRevert(
  129. this.manager.renounceGroup(group, user2, { from: user1 }),
  130. 'can only renounce roles for self',
  131. );
  132. });
  133. it('cannot revoke public group', async function () {
  134. await expectRevert(
  135. this.manager.revokeGroup(PUBLIC_GROUP, user1, { from: admin }),
  136. 'AccessManager: irrevocable group',
  137. );
  138. });
  139. it('cannot revoke nonexistent group', async function () {
  140. await expectRevert(
  141. this.manager.revokeGroup(otherGroup, user1, { from: admin }),
  142. 'AccessManager: unknown group',
  143. );
  144. await expectRevert(
  145. this.manager.renounceGroup(otherGroup, user1, { from: user1 }),
  146. 'AccessManager: unknown group',
  147. );
  148. });
  149. });
  150. describe('querying', function () {
  151. it('returns expected groups', async function () {
  152. const getGroups = () => this.manager.getUserGroups(user1);
  153. // only public group initially
  154. expect(await getGroups()).to.equal('0x8000000000000000000000000000000000000000000000000000000000000000');
  155. await this.manager.createGroup('0', '0', { from: admin });
  156. await this.manager.grantGroup('0', user1, { from: admin });
  157. expect(await getGroups()).to.equal('0x8000000000000000000000000000000000000000000000000000000000000001');
  158. await this.manager.createGroup('1', '1', { from: admin });
  159. await this.manager.grantGroup('1', user1, { from: admin });
  160. expect(await getGroups()).to.equal('0x8000000000000000000000000000000000000000000000000000000000000003');
  161. await this.manager.createGroup('16', '16', { from: admin });
  162. await this.manager.grantGroup('16', user1, { from: admin });
  163. expect(await getGroups()).to.equal('0x8000000000000000000000000000000000000000000000000000000000010003');
  164. });
  165. });
  166. });
  167. describe('allowing', function () {
  168. const group = '1';
  169. const otherGroup = '2';
  170. const groupMember = user1;
  171. const selector = web3.eth.abi.encodeFunctionSignature('restrictedFunction()');
  172. const otherSelector = web3.eth.abi.encodeFunctionSignature('otherRestrictedFunction()');
  173. beforeEach('deploying managed contract', async function () {
  174. await this.manager.createGroup(group, '', { from: admin });
  175. await this.manager.grantGroup(group, groupMember, { from: admin });
  176. this.managed = await AccessManaged.new(this.manager.address);
  177. });
  178. it('non-admin cannot change allowed groups', async function () {
  179. await expectRevert(
  180. this.manager.setFunctionAllowedGroup(this.managed.address, [selector], group, true, { from: nonAdmin }),
  181. 'missing role',
  182. );
  183. });
  184. it('single selector', async function () {
  185. const receipt = await this.manager.setFunctionAllowedGroup(this.managed.address, [selector], group, true, {
  186. from: admin,
  187. });
  188. expectEvent(receipt, 'GroupAllowed', {
  189. target: this.managed.address,
  190. selector: selector.padEnd(66, '0'), // there seems to be a bug in decoding the indexed bytes4
  191. group,
  192. allowed: true,
  193. });
  194. const allowedGroups = await this.manager.getFunctionAllowedGroups(this.managed.address, selector);
  195. expect(groupUtils.decodeBitmap(allowedGroups)).to.deep.equal([group]);
  196. const otherAllowedGroups = await this.manager.getFunctionAllowedGroups(this.managed.address, otherSelector);
  197. expect(groupUtils.decodeBitmap(otherAllowedGroups)).to.deep.equal([]);
  198. const restricted = await this.managed.restrictedFunction({ from: groupMember });
  199. expectEvent(restricted, 'RestrictedRan');
  200. await expectRevert(
  201. this.managed.otherRestrictedFunction({ from: groupMember }),
  202. 'AccessManaged: authority rejected',
  203. );
  204. });
  205. it('multiple selectors', async function () {
  206. const receipt = await this.manager.setFunctionAllowedGroup(
  207. this.managed.address,
  208. [selector, otherSelector],
  209. group,
  210. true,
  211. { from: admin },
  212. );
  213. expectEvent(receipt, 'GroupAllowed', {
  214. target: this.managed.address,
  215. selector: selector.padEnd(66, '0'), // there seems to be a bug in decoding the indexed bytes4
  216. group,
  217. allowed: true,
  218. });
  219. expectEvent(receipt, 'GroupAllowed', {
  220. target: this.managed.address,
  221. selector: otherSelector.padEnd(66, '0'), // there seems to be a bug in decoding the indexed bytes4
  222. group,
  223. allowed: true,
  224. });
  225. const allowedGroups = await this.manager.getFunctionAllowedGroups(this.managed.address, selector);
  226. expect(groupUtils.decodeBitmap(allowedGroups)).to.deep.equal([group]);
  227. const otherAllowedGroups = await this.manager.getFunctionAllowedGroups(this.managed.address, otherSelector);
  228. expect(groupUtils.decodeBitmap(otherAllowedGroups)).to.deep.equal([group]);
  229. const restricted = await this.managed.restrictedFunction({ from: groupMember });
  230. expectEvent(restricted, 'RestrictedRan');
  231. await this.managed.otherRestrictedFunction({ from: groupMember });
  232. expectEvent(restricted, 'RestrictedRan');
  233. });
  234. it('works on open target', async function () {
  235. await this.manager.setContractModeOpen(this.managed.address, { from: admin });
  236. await this.manager.setFunctionAllowedGroup(this.managed.address, [selector], group, false, { from: admin });
  237. });
  238. it('works on closed target', async function () {
  239. await this.manager.setContractModeClosed(this.managed.address, { from: admin });
  240. await this.manager.setFunctionAllowedGroup(this.managed.address, [selector], group, false, { from: admin });
  241. });
  242. it('cannot allow nonexistent group', async function () {
  243. await expectRevert(
  244. this.manager.setFunctionAllowedGroup(this.managed.address, [selector], otherGroup, true, { from: admin }),
  245. 'AccessManager: unknown group',
  246. );
  247. });
  248. });
  249. describe('disallowing', function () {
  250. const group = '1';
  251. const groupMember = user1;
  252. const selector = web3.eth.abi.encodeFunctionSignature('restrictedFunction()');
  253. const otherSelector = web3.eth.abi.encodeFunctionSignature('otherRestrictedFunction()');
  254. beforeEach('deploying managed contract', async function () {
  255. await this.manager.createGroup(group, '', { from: admin });
  256. await this.manager.grantGroup(group, groupMember, { from: admin });
  257. this.managed = await AccessManaged.new(this.manager.address);
  258. await this.manager.setFunctionAllowedGroup(this.managed.address, [selector, otherSelector], group, true, {
  259. from: admin,
  260. });
  261. });
  262. it('non-admin cannot change disallowed groups', async function () {
  263. await expectRevert(
  264. this.manager.setFunctionAllowedGroup(this.managed.address, [selector], group, false, { from: nonAdmin }),
  265. 'missing role',
  266. );
  267. });
  268. it('single selector', async function () {
  269. const receipt = await this.manager.setFunctionAllowedGroup(this.managed.address, [selector], group, false, {
  270. from: admin,
  271. });
  272. expectEvent(receipt, 'GroupAllowed', {
  273. target: this.managed.address,
  274. selector: selector.padEnd(66, '0'), // there seems to be a bug in decoding the indexed bytes4,
  275. group,
  276. allowed: false,
  277. });
  278. const allowedGroups = await this.manager.getFunctionAllowedGroups(this.managed.address, selector);
  279. expect(groupUtils.decodeBitmap(allowedGroups)).to.deep.equal([]);
  280. const otherAllowedGroups = await this.manager.getFunctionAllowedGroups(this.managed.address, otherSelector);
  281. expect(groupUtils.decodeBitmap(otherAllowedGroups)).to.deep.equal([group]);
  282. await expectRevert(this.managed.restrictedFunction({ from: groupMember }), 'AccessManaged: authority rejected');
  283. const otherRestricted = await this.managed.otherRestrictedFunction({ from: groupMember });
  284. expectEvent(otherRestricted, 'RestrictedRan');
  285. });
  286. it('multiple selectors', async function () {
  287. const receipt = await this.manager.setFunctionAllowedGroup(
  288. this.managed.address,
  289. [selector, otherSelector],
  290. group,
  291. false,
  292. { from: admin },
  293. );
  294. expectEvent(receipt, 'GroupAllowed', {
  295. target: this.managed.address,
  296. selector: selector.padEnd(66, '0'), // there seems to be a bug in decoding the indexed bytes4
  297. group,
  298. allowed: false,
  299. });
  300. expectEvent(receipt, 'GroupAllowed', {
  301. target: this.managed.address,
  302. selector: otherSelector.padEnd(66, '0'), // there seems to be a bug in decoding the indexed bytes4
  303. group,
  304. allowed: false,
  305. });
  306. const allowedGroups = await this.manager.getFunctionAllowedGroups(this.managed.address, selector);
  307. expect(groupUtils.decodeBitmap(allowedGroups)).to.deep.equal([]);
  308. const otherAllowedGroups = await this.manager.getFunctionAllowedGroups(this.managed.address, otherSelector);
  309. expect(groupUtils.decodeBitmap(otherAllowedGroups)).to.deep.equal([]);
  310. await expectRevert(this.managed.restrictedFunction({ from: groupMember }), 'AccessManaged: authority rejected');
  311. await expectRevert(
  312. this.managed.otherRestrictedFunction({ from: groupMember }),
  313. 'AccessManaged: authority rejected',
  314. );
  315. });
  316. it('works on open target', async function () {
  317. await this.manager.setContractModeOpen(this.managed.address, { from: admin });
  318. await this.manager.setFunctionAllowedGroup(this.managed.address, [selector], group, false, { from: admin });
  319. });
  320. it('works on closed target', async function () {
  321. await this.manager.setContractModeClosed(this.managed.address, { from: admin });
  322. await this.manager.setFunctionAllowedGroup(this.managed.address, [selector], group, false, { from: admin });
  323. });
  324. });
  325. describe('modes', function () {
  326. const group = '1';
  327. const selector = web3.eth.abi.encodeFunctionSignature('restrictedFunction()');
  328. beforeEach('deploying managed contract', async function () {
  329. this.managed = await AccessManaged.new(this.manager.address);
  330. await this.manager.createGroup('1', 'a group', { from: admin });
  331. await this.manager.setFunctionAllowedGroup(this.managed.address, [selector], group, true, { from: admin });
  332. });
  333. it('custom mode is default', async function () {
  334. expect(await this.manager.getContractMode(this.managed.address)).to.bignumber.equal(AccessMode.Custom);
  335. const allowedGroups = await this.manager.getFunctionAllowedGroups(this.managed.address, selector);
  336. expect(groupUtils.decodeBitmap(allowedGroups)).to.deep.equal([group]);
  337. });
  338. it('open mode', async function () {
  339. const receipt = await this.manager.setContractModeOpen(this.managed.address, { from: admin });
  340. expectEvent(receipt, 'AccessModeUpdated', {
  341. target: this.managed.address,
  342. mode: AccessMode.Open,
  343. });
  344. expect(await this.manager.getContractMode(this.managed.address)).to.bignumber.equal(AccessMode.Open);
  345. const allowedGroups = await this.manager.getFunctionAllowedGroups(this.managed.address, selector);
  346. expect(groupUtils.decodeBitmap(allowedGroups)).to.deep.equal([PUBLIC_GROUP]);
  347. });
  348. it('closed mode', async function () {
  349. const receipt = await this.manager.setContractModeClosed(this.managed.address, { from: admin });
  350. expectEvent(receipt, 'AccessModeUpdated', {
  351. target: this.managed.address,
  352. mode: AccessMode.Closed,
  353. });
  354. expect(await this.manager.getContractMode(this.managed.address)).to.bignumber.equal(AccessMode.Closed);
  355. const allowedGroups = await this.manager.getFunctionAllowedGroups(this.managed.address, selector);
  356. expect(groupUtils.decodeBitmap(allowedGroups)).to.deep.equal([]);
  357. });
  358. it('mode cycle', async function () {
  359. await this.manager.setContractModeOpen(this.managed.address, { from: admin });
  360. await this.manager.setContractModeClosed(this.managed.address, { from: admin });
  361. await this.manager.setContractModeCustom(this.managed.address, { from: admin });
  362. expect(await this.manager.getContractMode(this.managed.address)).to.bignumber.equal(AccessMode.Custom);
  363. const allowedGroups = await this.manager.getFunctionAllowedGroups(this.managed.address, selector);
  364. expect(groupUtils.decodeBitmap(allowedGroups)).to.deep.equal([group]);
  365. });
  366. it('non-admin cannot change mode', async function () {
  367. await expectRevert(this.manager.setContractModeCustom(this.managed.address), 'missing role');
  368. await expectRevert(this.manager.setContractModeOpen(this.managed.address), 'missing role');
  369. await expectRevert(this.manager.setContractModeClosed(this.managed.address), 'missing role');
  370. });
  371. });
  372. describe('transfering authority', function () {
  373. beforeEach('deploying managed contract', async function () {
  374. this.managed = await AccessManaged.new(this.manager.address);
  375. });
  376. it('admin can transfer authority', async function () {
  377. await this.manager.transferContractAuthority(this.managed.address, otherAuthority, { from: admin });
  378. expect(await this.managed.authority()).to.equal(otherAuthority);
  379. });
  380. it('non-admin cannot transfer authority', async function () {
  381. await expectRevert(
  382. this.manager.transferContractAuthority(this.managed.address, otherAuthority, { from: nonAdmin }),
  383. 'missing role',
  384. );
  385. });
  386. });
  387. describe('adapter', function () {
  388. const group = '0';
  389. beforeEach('deploying adapter', async function () {
  390. await this.manager.createGroup(group, 'a group', { from: admin });
  391. await this.manager.grantGroup(group, user1, { from: admin });
  392. this.adapter = await AccessManagerAdapter.new(this.manager.address);
  393. });
  394. it('with ownable', async function () {
  395. const target = await Ownable.new();
  396. await target.transferOwnership(this.adapter.address);
  397. const { data } = await target.$_checkOwner.request();
  398. const selector = data.slice(0, 10);
  399. await expectRevert(
  400. this.adapter.relay(target.address, data, { from: user1 }),
  401. 'AccessManagerAdapter: caller not allowed',
  402. );
  403. await this.manager.setFunctionAllowedGroup(target.address, [selector], group, true, { from: admin });
  404. await this.adapter.relay(target.address, data, { from: user1 });
  405. });
  406. it('with access control', async function () {
  407. const ROLE = web3.utils.soliditySha3('ROLE');
  408. const target = await AccessControl.new();
  409. await target.$_grantRole(ROLE, this.adapter.address);
  410. const { data } = await target.$_checkRole.request(ROLE);
  411. const selector = data.slice(0, 10);
  412. await expectRevert(
  413. this.adapter.relay(target.address, data, { from: user1 }),
  414. 'AccessManagerAdapter: caller not allowed',
  415. );
  416. await this.manager.setFunctionAllowedGroup(target.address, [selector], group, true, { from: admin });
  417. await this.adapter.relay(target.address, data, { from: user1 });
  418. });
  419. it('transfer authority', async function () {
  420. await this.manager.transferContractAuthority(this.adapter.address, otherAuthority, { from: admin });
  421. expect(await this.adapter.authority()).to.equal(otherAuthority);
  422. });
  423. });
  424. });