AccessManager.test.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506
  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 groupMember = user1;
  170. const selector = web3.eth.abi.encodeFunctionSignature('restrictedFunction()');
  171. const otherSelector = web3.eth.abi.encodeFunctionSignature('otherRestrictedFunction()');
  172. beforeEach('deploying managed contract', async function () {
  173. await this.manager.createGroup(group, '', { from: admin });
  174. await this.manager.grantGroup(group, groupMember, { from: admin });
  175. this.managed = await AccessManaged.new(this.manager.address);
  176. });
  177. it('non-admin cannot change allowed groups', async function () {
  178. await expectRevert(
  179. this.manager.setFunctionAllowedGroup(this.managed.address, [selector], group, true, { from: nonAdmin }),
  180. 'missing role',
  181. );
  182. });
  183. it('single selector', async function () {
  184. const receipt = await this.manager.setFunctionAllowedGroup(this.managed.address, [selector], group, true, {
  185. from: admin,
  186. });
  187. expectEvent(receipt, 'GroupAllowed', {
  188. target: this.managed.address,
  189. selector: selector.padEnd(66, '0'), // there seems to be a bug in decoding the indexed bytes4
  190. group,
  191. allowed: true,
  192. });
  193. const allowedGroups = await this.manager.getFunctionAllowedGroups(this.managed.address, selector);
  194. expect(groupUtils.decodeBitmap(allowedGroups)).to.deep.equal([group]);
  195. const otherAllowedGroups = await this.manager.getFunctionAllowedGroups(this.managed.address, otherSelector);
  196. expect(groupUtils.decodeBitmap(otherAllowedGroups)).to.deep.equal([]);
  197. const restricted = await this.managed.restrictedFunction({ from: groupMember });
  198. expectEvent(restricted, 'RestrictedRan');
  199. await expectRevert(
  200. this.managed.otherRestrictedFunction({ from: groupMember }),
  201. 'AccessManaged: authority rejected',
  202. );
  203. });
  204. it('multiple selectors', async function () {
  205. const receipt = await this.manager.setFunctionAllowedGroup(
  206. this.managed.address,
  207. [selector, otherSelector],
  208. group,
  209. true,
  210. { from: admin },
  211. );
  212. expectEvent(receipt, 'GroupAllowed', {
  213. target: this.managed.address,
  214. selector: selector.padEnd(66, '0'), // there seems to be a bug in decoding the indexed bytes4
  215. group,
  216. allowed: true,
  217. });
  218. expectEvent(receipt, 'GroupAllowed', {
  219. target: this.managed.address,
  220. selector: otherSelector.padEnd(66, '0'), // there seems to be a bug in decoding the indexed bytes4
  221. group,
  222. allowed: true,
  223. });
  224. const allowedGroups = await this.manager.getFunctionAllowedGroups(this.managed.address, selector);
  225. expect(groupUtils.decodeBitmap(allowedGroups)).to.deep.equal([group]);
  226. const otherAllowedGroups = await this.manager.getFunctionAllowedGroups(this.managed.address, otherSelector);
  227. expect(groupUtils.decodeBitmap(otherAllowedGroups)).to.deep.equal([group]);
  228. const restricted = await this.managed.restrictedFunction({ from: groupMember });
  229. expectEvent(restricted, 'RestrictedRan');
  230. await this.managed.otherRestrictedFunction({ from: groupMember });
  231. expectEvent(restricted, 'RestrictedRan');
  232. });
  233. it('works on open target', async function () {
  234. await this.manager.setContractModeOpen(this.managed.address, { from: admin });
  235. await this.manager.setFunctionAllowedGroup(this.managed.address, [selector], group, false, { from: admin });
  236. });
  237. it('works on closed target', async function () {
  238. await this.manager.setContractModeClosed(this.managed.address, { from: admin });
  239. await this.manager.setFunctionAllowedGroup(this.managed.address, [selector], group, false, { from: admin });
  240. });
  241. });
  242. describe('disallowing', function () {
  243. const group = '1';
  244. const groupMember = user1;
  245. const selector = web3.eth.abi.encodeFunctionSignature('restrictedFunction()');
  246. const otherSelector = web3.eth.abi.encodeFunctionSignature('otherRestrictedFunction()');
  247. beforeEach('deploying managed contract', async function () {
  248. await this.manager.createGroup(group, '', { from: admin });
  249. await this.manager.grantGroup(group, groupMember, { from: admin });
  250. this.managed = await AccessManaged.new(this.manager.address);
  251. await this.manager.setFunctionAllowedGroup(this.managed.address, [selector, otherSelector], group, true, {
  252. from: admin,
  253. });
  254. });
  255. it('non-admin cannot change disallowed groups', async function () {
  256. await expectRevert(
  257. this.manager.setFunctionAllowedGroup(this.managed.address, [selector], group, false, { from: nonAdmin }),
  258. 'missing role',
  259. );
  260. });
  261. it('single selector', async function () {
  262. const receipt = await this.manager.setFunctionAllowedGroup(this.managed.address, [selector], group, false, {
  263. from: admin,
  264. });
  265. expectEvent(receipt, 'GroupAllowed', {
  266. target: this.managed.address,
  267. selector: selector.padEnd(66, '0'), // there seems to be a bug in decoding the indexed bytes4,
  268. group,
  269. allowed: false,
  270. });
  271. const allowedGroups = await this.manager.getFunctionAllowedGroups(this.managed.address, selector);
  272. expect(groupUtils.decodeBitmap(allowedGroups)).to.deep.equal([]);
  273. const otherAllowedGroups = await this.manager.getFunctionAllowedGroups(this.managed.address, otherSelector);
  274. expect(groupUtils.decodeBitmap(otherAllowedGroups)).to.deep.equal([group]);
  275. await expectRevert(this.managed.restrictedFunction({ from: groupMember }), 'AccessManaged: authority rejected');
  276. const otherRestricted = await this.managed.otherRestrictedFunction({ from: groupMember });
  277. expectEvent(otherRestricted, 'RestrictedRan');
  278. });
  279. it('multiple selectors', async function () {
  280. const receipt = await this.manager.setFunctionAllowedGroup(
  281. this.managed.address,
  282. [selector, otherSelector],
  283. group,
  284. false,
  285. { from: admin },
  286. );
  287. expectEvent(receipt, 'GroupAllowed', {
  288. target: this.managed.address,
  289. selector: selector.padEnd(66, '0'), // there seems to be a bug in decoding the indexed bytes4
  290. group,
  291. allowed: false,
  292. });
  293. expectEvent(receipt, 'GroupAllowed', {
  294. target: this.managed.address,
  295. selector: otherSelector.padEnd(66, '0'), // there seems to be a bug in decoding the indexed bytes4
  296. group,
  297. allowed: false,
  298. });
  299. const allowedGroups = await this.manager.getFunctionAllowedGroups(this.managed.address, selector);
  300. expect(groupUtils.decodeBitmap(allowedGroups)).to.deep.equal([]);
  301. const otherAllowedGroups = await this.manager.getFunctionAllowedGroups(this.managed.address, otherSelector);
  302. expect(groupUtils.decodeBitmap(otherAllowedGroups)).to.deep.equal([]);
  303. await expectRevert(this.managed.restrictedFunction({ from: groupMember }), 'AccessManaged: authority rejected');
  304. await expectRevert(
  305. this.managed.otherRestrictedFunction({ from: groupMember }),
  306. 'AccessManaged: authority rejected',
  307. );
  308. });
  309. it('works on open target', async function () {
  310. await this.manager.setContractModeOpen(this.managed.address, { from: admin });
  311. await this.manager.setFunctionAllowedGroup(this.managed.address, [selector], group, false, { from: admin });
  312. });
  313. it('works on closed target', async function () {
  314. await this.manager.setContractModeClosed(this.managed.address, { from: admin });
  315. await this.manager.setFunctionAllowedGroup(this.managed.address, [selector], group, false, { from: admin });
  316. });
  317. });
  318. describe('modes', function () {
  319. const group = '1';
  320. const selector = web3.eth.abi.encodeFunctionSignature('restrictedFunction()');
  321. beforeEach('deploying managed contract', async function () {
  322. this.managed = await AccessManaged.new(this.manager.address);
  323. await this.manager.createGroup('1', 'a group', { from: admin });
  324. await this.manager.setFunctionAllowedGroup(this.managed.address, [selector], group, true, { from: admin });
  325. });
  326. it('custom mode is default', async function () {
  327. expect(await this.manager.getContractMode(this.managed.address)).to.bignumber.equal(AccessMode.Custom);
  328. const allowedGroups = await this.manager.getFunctionAllowedGroups(this.managed.address, selector);
  329. expect(groupUtils.decodeBitmap(allowedGroups)).to.deep.equal([group]);
  330. });
  331. it('open mode', async function () {
  332. const receipt = await this.manager.setContractModeOpen(this.managed.address, { from: admin });
  333. expectEvent(receipt, 'AccessModeUpdated', {
  334. target: this.managed.address,
  335. mode: AccessMode.Open,
  336. });
  337. expect(await this.manager.getContractMode(this.managed.address)).to.bignumber.equal(AccessMode.Open);
  338. const allowedGroups = await this.manager.getFunctionAllowedGroups(this.managed.address, selector);
  339. expect(groupUtils.decodeBitmap(allowedGroups)).to.deep.equal([PUBLIC_GROUP]);
  340. });
  341. it('closed mode', async function () {
  342. const receipt = await this.manager.setContractModeClosed(this.managed.address, { from: admin });
  343. expectEvent(receipt, 'AccessModeUpdated', {
  344. target: this.managed.address,
  345. mode: AccessMode.Closed,
  346. });
  347. expect(await this.manager.getContractMode(this.managed.address)).to.bignumber.equal(AccessMode.Closed);
  348. const allowedGroups = await this.manager.getFunctionAllowedGroups(this.managed.address, selector);
  349. expect(groupUtils.decodeBitmap(allowedGroups)).to.deep.equal([]);
  350. });
  351. it('mode cycle', async function () {
  352. await this.manager.setContractModeOpen(this.managed.address, { from: admin });
  353. await this.manager.setContractModeClosed(this.managed.address, { from: admin });
  354. await this.manager.setContractModeCustom(this.managed.address, { from: admin });
  355. expect(await this.manager.getContractMode(this.managed.address)).to.bignumber.equal(AccessMode.Custom);
  356. const allowedGroups = await this.manager.getFunctionAllowedGroups(this.managed.address, selector);
  357. expect(groupUtils.decodeBitmap(allowedGroups)).to.deep.equal([group]);
  358. });
  359. it('non-admin cannot change mode', async function () {
  360. await expectRevert(this.manager.setContractModeCustom(this.managed.address), 'missing role');
  361. await expectRevert(this.manager.setContractModeOpen(this.managed.address), 'missing role');
  362. await expectRevert(this.manager.setContractModeClosed(this.managed.address), 'missing role');
  363. });
  364. });
  365. describe('transfering authority', function () {
  366. beforeEach('deploying managed contract', async function () {
  367. this.managed = await AccessManaged.new(this.manager.address);
  368. });
  369. it('admin can transfer authority', async function () {
  370. await this.manager.transferContractAuthority(this.managed.address, otherAuthority, { from: admin });
  371. expect(await this.managed.authority()).to.equal(otherAuthority);
  372. });
  373. it('non-admin cannot transfer authority', async function () {
  374. await expectRevert(
  375. this.manager.transferContractAuthority(this.managed.address, otherAuthority, { from: nonAdmin }),
  376. 'missing role',
  377. );
  378. });
  379. });
  380. describe('adapter', function () {
  381. const group = '0';
  382. beforeEach('deploying adapter', async function () {
  383. await this.manager.createGroup(group, 'a group', { from: admin });
  384. await this.manager.grantGroup(group, user1, { from: admin });
  385. this.adapter = await AccessManagerAdapter.new(this.manager.address);
  386. });
  387. it('with ownable', async function () {
  388. const target = await Ownable.new();
  389. await target.transferOwnership(this.adapter.address);
  390. const { data } = await target.$_checkOwner.request();
  391. const selector = data.slice(0, 10);
  392. await expectRevert(
  393. this.adapter.relay(target.address, data, { from: user1 }),
  394. 'AccessManagerAdapter: caller not allowed',
  395. );
  396. await this.manager.setFunctionAllowedGroup(target.address, [selector], group, true, { from: admin });
  397. await this.adapter.relay(target.address, data, { from: user1 });
  398. });
  399. it('with access control', async function () {
  400. const ROLE = web3.utils.soliditySha3('ROLE');
  401. const target = await AccessControl.new();
  402. await target.$_grantRole(ROLE, this.adapter.address);
  403. const { data } = await target.$_checkRole.request(ROLE);
  404. const selector = data.slice(0, 10);
  405. await expectRevert(
  406. this.adapter.relay(target.address, data, { from: user1 }),
  407. 'AccessManagerAdapter: caller not allowed',
  408. );
  409. await this.manager.setFunctionAllowedGroup(target.address, [selector], group, true, { from: admin });
  410. await this.adapter.relay(target.address, data, { from: user1 });
  411. });
  412. it('transfer authority', async function () {
  413. await this.manager.transferContractAuthority(this.adapter.address, otherAuthority, { from: admin });
  414. expect(await this.adapter.authority()).to.equal(otherAuthority);
  415. });
  416. });
  417. });