AccessManager.test.js 49 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144
  1. const { web3 } = require('hardhat');
  2. const { expectEvent, time } = require('@openzeppelin/test-helpers');
  3. const { expectRevertCustomError } = require('../../helpers/customError');
  4. const { selector } = require('../../helpers/methods');
  5. const { clockFromReceipt } = require('../../helpers/time');
  6. const AccessManager = artifacts.require('$AccessManager');
  7. const AccessManagedTarget = artifacts.require('$AccessManagedTarget');
  8. const Ownable = artifacts.require('$Ownable');
  9. const MAX_UINT64 = web3.utils.toBN((2n ** 64n - 1n).toString());
  10. const GROUPS = {
  11. ADMIN: web3.utils.toBN(0),
  12. SOME_ADMIN: web3.utils.toBN(17),
  13. SOME: web3.utils.toBN(42),
  14. PUBLIC: MAX_UINT64,
  15. };
  16. Object.assign(GROUPS, Object.fromEntries(Object.entries(GROUPS).map(([key, value]) => [value, key])));
  17. const familyId = web3.utils.toBN(1);
  18. const executeDelay = web3.utils.toBN(10);
  19. const grantDelay = web3.utils.toBN(10);
  20. const formatAccess = access => [access[0], access[1].toString()];
  21. contract('AccessManager', function (accounts) {
  22. const [admin, manager, member, user, other] = accounts;
  23. beforeEach(async function () {
  24. this.manager = await AccessManager.new(admin);
  25. // add member to group
  26. await this.manager.$_setGroupAdmin(GROUPS.SOME, GROUPS.SOME_ADMIN);
  27. await this.manager.$_setGroupGuardian(GROUPS.SOME, GROUPS.SOME_ADMIN);
  28. await this.manager.$_grantGroup(GROUPS.SOME_ADMIN, manager, 0, 0);
  29. await this.manager.$_grantGroup(GROUPS.SOME, member, 0, 0);
  30. });
  31. it('groups are correctly initialized', async function () {
  32. // group admin
  33. expect(await this.manager.getGroupAdmin(GROUPS.ADMIN)).to.be.bignumber.equal(GROUPS.ADMIN);
  34. expect(await this.manager.getGroupAdmin(GROUPS.SOME_ADMIN)).to.be.bignumber.equal(GROUPS.ADMIN);
  35. expect(await this.manager.getGroupAdmin(GROUPS.SOME)).to.be.bignumber.equal(GROUPS.SOME_ADMIN);
  36. expect(await this.manager.getGroupAdmin(GROUPS.PUBLIC)).to.be.bignumber.equal(GROUPS.ADMIN);
  37. // group guardian
  38. expect(await this.manager.getGroupGuardian(GROUPS.ADMIN)).to.be.bignumber.equal(GROUPS.ADMIN);
  39. expect(await this.manager.getGroupGuardian(GROUPS.SOME_ADMIN)).to.be.bignumber.equal(GROUPS.ADMIN);
  40. expect(await this.manager.getGroupGuardian(GROUPS.SOME)).to.be.bignumber.equal(GROUPS.SOME_ADMIN);
  41. expect(await this.manager.getGroupGuardian(GROUPS.PUBLIC)).to.be.bignumber.equal(GROUPS.ADMIN);
  42. // group members
  43. expect(await this.manager.hasGroup(GROUPS.ADMIN, admin).then(formatAccess)).to.be.deep.equal([true, '0']);
  44. expect(await this.manager.hasGroup(GROUPS.ADMIN, manager).then(formatAccess)).to.be.deep.equal([false, '0']);
  45. expect(await this.manager.hasGroup(GROUPS.ADMIN, member).then(formatAccess)).to.be.deep.equal([false, '0']);
  46. expect(await this.manager.hasGroup(GROUPS.ADMIN, user).then(formatAccess)).to.be.deep.equal([false, '0']);
  47. expect(await this.manager.hasGroup(GROUPS.SOME_ADMIN, admin).then(formatAccess)).to.be.deep.equal([false, '0']);
  48. expect(await this.manager.hasGroup(GROUPS.SOME_ADMIN, manager).then(formatAccess)).to.be.deep.equal([true, '0']);
  49. expect(await this.manager.hasGroup(GROUPS.SOME_ADMIN, member).then(formatAccess)).to.be.deep.equal([false, '0']);
  50. expect(await this.manager.hasGroup(GROUPS.SOME_ADMIN, user).then(formatAccess)).to.be.deep.equal([false, '0']);
  51. expect(await this.manager.hasGroup(GROUPS.SOME, admin).then(formatAccess)).to.be.deep.equal([false, '0']);
  52. expect(await this.manager.hasGroup(GROUPS.SOME, manager).then(formatAccess)).to.be.deep.equal([false, '0']);
  53. expect(await this.manager.hasGroup(GROUPS.SOME, member).then(formatAccess)).to.be.deep.equal([true, '0']);
  54. expect(await this.manager.hasGroup(GROUPS.SOME, user).then(formatAccess)).to.be.deep.equal([false, '0']);
  55. expect(await this.manager.hasGroup(GROUPS.PUBLIC, admin).then(formatAccess)).to.be.deep.equal([true, '0']);
  56. expect(await this.manager.hasGroup(GROUPS.PUBLIC, manager).then(formatAccess)).to.be.deep.equal([true, '0']);
  57. expect(await this.manager.hasGroup(GROUPS.PUBLIC, member).then(formatAccess)).to.be.deep.equal([true, '0']);
  58. expect(await this.manager.hasGroup(GROUPS.PUBLIC, user).then(formatAccess)).to.be.deep.equal([true, '0']);
  59. });
  60. describe('Groups management', function () {
  61. describe('label group', function () {
  62. it('admin can emit a label event', async function () {
  63. expectEvent(await this.manager.labelGroup(GROUPS.SOME, 'Some label', { from: admin }), 'GroupLabel', {
  64. groupId: GROUPS.SOME,
  65. label: 'Some label',
  66. });
  67. });
  68. it('admin can re-emit a label event', async function () {
  69. await this.manager.labelGroup(GROUPS.SOME, 'Some label', { from: admin });
  70. expectEvent(await this.manager.labelGroup(GROUPS.SOME, 'Updated label', { from: admin }), 'GroupLabel', {
  71. groupId: GROUPS.SOME,
  72. label: 'Updated label',
  73. });
  74. });
  75. it('emitting a label is restricted', async function () {
  76. await expectRevertCustomError(
  77. this.manager.labelGroup(GROUPS.SOME, 'Invalid label', { from: other }),
  78. 'AccessManagerUnauthorizedAccount',
  79. [other, GROUPS.ADMIN],
  80. );
  81. });
  82. });
  83. describe('grant group', function () {
  84. describe('without a grant delay', function () {
  85. it('without an execute delay', async function () {
  86. expect(await this.manager.hasGroup(GROUPS.SOME, user).then(formatAccess)).to.be.deep.equal([false, '0']);
  87. const { receipt } = await this.manager.grantGroup(GROUPS.SOME, user, 0, { from: manager });
  88. const timestamp = await clockFromReceipt.timestamp(receipt).then(web3.utils.toBN);
  89. expectEvent(receipt, 'GroupGranted', { groupId: GROUPS.SOME, account: user, since: timestamp, delay: '0' });
  90. expect(await this.manager.hasGroup(GROUPS.SOME, user).then(formatAccess)).to.be.deep.equal([true, '0']);
  91. const access = await this.manager.getAccess(GROUPS.SOME, user);
  92. expect(access[0]).to.be.bignumber.equal(timestamp); // inGroupSince
  93. expect(access[1]).to.be.bignumber.equal('0'); // currentDelay
  94. expect(access[2]).to.be.bignumber.equal('0'); // pendingDelay
  95. expect(access[3]).to.be.bignumber.equal('0'); // effect
  96. });
  97. it('with an execute delay', async function () {
  98. expect(await this.manager.hasGroup(GROUPS.SOME, user).then(formatAccess)).to.be.deep.equal([false, '0']);
  99. const { receipt } = await this.manager.grantGroup(GROUPS.SOME, user, executeDelay, { from: manager });
  100. const timestamp = await clockFromReceipt.timestamp(receipt).then(web3.utils.toBN);
  101. expectEvent(receipt, 'GroupGranted', {
  102. groupId: GROUPS.SOME,
  103. account: user,
  104. since: timestamp,
  105. delay: executeDelay,
  106. });
  107. expect(await this.manager.hasGroup(GROUPS.SOME, user).then(formatAccess)).to.be.deep.equal([
  108. true,
  109. executeDelay.toString(),
  110. ]);
  111. const access = await this.manager.getAccess(GROUPS.SOME, user);
  112. expect(access[0]).to.be.bignumber.equal(timestamp); // inGroupSince
  113. expect(access[1]).to.be.bignumber.equal(executeDelay); // currentDelay
  114. expect(access[2]).to.be.bignumber.equal('0'); // pendingDelay
  115. expect(access[3]).to.be.bignumber.equal('0'); // effect
  116. });
  117. it('to a user that is already in the group', async function () {
  118. expect(await this.manager.hasGroup(GROUPS.SOME, member).then(formatAccess)).to.be.deep.equal([true, '0']);
  119. await expectRevertCustomError(
  120. this.manager.grantGroup(GROUPS.SOME, member, 0, { from: manager }),
  121. 'AccessManagerAccountAlreadyInGroup',
  122. [GROUPS.SOME, member],
  123. );
  124. });
  125. it('to a user that is scheduled for joining the group', async function () {
  126. await this.manager.$_grantGroup(GROUPS.SOME, user, 10, 0); // grant delay 10
  127. expect(await this.manager.hasGroup(GROUPS.SOME, user).then(formatAccess)).to.be.deep.equal([false, '0']);
  128. await expectRevertCustomError(
  129. this.manager.grantGroup(GROUPS.SOME, user, 0, { from: manager }),
  130. 'AccessManagerAccountAlreadyInGroup',
  131. [GROUPS.SOME, user],
  132. );
  133. });
  134. it('grant group is restricted', async function () {
  135. await expectRevertCustomError(
  136. this.manager.grantGroup(GROUPS.SOME, user, 0, { from: other }),
  137. 'AccessManagerUnauthorizedAccount',
  138. [other, GROUPS.SOME_ADMIN],
  139. );
  140. });
  141. });
  142. describe('with a grant delay', function () {
  143. beforeEach(async function () {
  144. await this.manager.$_setGrantDelay(GROUPS.SOME, grantDelay);
  145. });
  146. it('granted group is not active immediatly', async function () {
  147. const { receipt } = await this.manager.grantGroup(GROUPS.SOME, user, 0, { from: manager });
  148. const timestamp = await clockFromReceipt.timestamp(receipt).then(web3.utils.toBN);
  149. expectEvent(receipt, 'GroupGranted', {
  150. groupId: GROUPS.SOME,
  151. account: user,
  152. since: timestamp.add(grantDelay),
  153. delay: '0',
  154. });
  155. expect(await this.manager.hasGroup(GROUPS.SOME, user).then(formatAccess)).to.be.deep.equal([false, '0']);
  156. const access = await this.manager.getAccess(GROUPS.SOME, user);
  157. expect(access[0]).to.be.bignumber.equal(timestamp.add(grantDelay)); // inGroupSince
  158. expect(access[1]).to.be.bignumber.equal('0'); // currentDelay
  159. expect(access[2]).to.be.bignumber.equal('0'); // pendingDelay
  160. expect(access[3]).to.be.bignumber.equal('0'); // effect
  161. });
  162. it('granted group is active after the delay', async function () {
  163. const { receipt } = await this.manager.grantGroup(GROUPS.SOME, user, 0, { from: manager });
  164. const timestamp = await clockFromReceipt.timestamp(receipt).then(web3.utils.toBN);
  165. expectEvent(receipt, 'GroupGranted', {
  166. groupId: GROUPS.SOME,
  167. account: user,
  168. since: timestamp.add(grantDelay),
  169. delay: '0',
  170. });
  171. await time.increase(grantDelay);
  172. expect(await this.manager.hasGroup(GROUPS.SOME, user).then(formatAccess)).to.be.deep.equal([true, '0']);
  173. const access = await this.manager.getAccess(GROUPS.SOME, user);
  174. expect(access[0]).to.be.bignumber.equal(timestamp.add(grantDelay)); // inGroupSince
  175. expect(access[1]).to.be.bignumber.equal('0'); // currentDelay
  176. expect(access[2]).to.be.bignumber.equal('0'); // pendingDelay
  177. expect(access[3]).to.be.bignumber.equal('0'); // effect
  178. });
  179. });
  180. });
  181. describe('revoke group', function () {
  182. it('from a user that is already in the group', async function () {
  183. expect(await this.manager.hasGroup(GROUPS.SOME, member).then(formatAccess)).to.be.deep.equal([true, '0']);
  184. const { receipt } = await this.manager.revokeGroup(GROUPS.SOME, member, { from: manager });
  185. expectEvent(receipt, 'GroupRevoked', { groupId: GROUPS.SOME, account: member });
  186. expect(await this.manager.hasGroup(GROUPS.SOME, member).then(formatAccess)).to.be.deep.equal([false, '0']);
  187. const access = await this.manager.getAccess(GROUPS.SOME, user);
  188. expect(access[0]).to.be.bignumber.equal('0'); // inGroupSince
  189. expect(access[1]).to.be.bignumber.equal('0'); // currentDelay
  190. expect(access[2]).to.be.bignumber.equal('0'); // pendingDelay
  191. expect(access[3]).to.be.bignumber.equal('0'); // effect
  192. });
  193. it('from a user that is scheduled for joining the group', async function () {
  194. await this.manager.$_grantGroup(GROUPS.SOME, user, 10, 0); // grant delay 10
  195. expect(await this.manager.hasGroup(GROUPS.SOME, user).then(formatAccess)).to.be.deep.equal([false, '0']);
  196. const { receipt } = await this.manager.revokeGroup(GROUPS.SOME, user, { from: manager });
  197. expectEvent(receipt, 'GroupRevoked', { groupId: GROUPS.SOME, account: user });
  198. expect(await this.manager.hasGroup(GROUPS.SOME, user).then(formatAccess)).to.be.deep.equal([false, '0']);
  199. const access = await this.manager.getAccess(GROUPS.SOME, user);
  200. expect(access[0]).to.be.bignumber.equal('0'); // inGroupSince
  201. expect(access[1]).to.be.bignumber.equal('0'); // currentDelay
  202. expect(access[2]).to.be.bignumber.equal('0'); // pendingDelay
  203. expect(access[3]).to.be.bignumber.equal('0'); // effect
  204. });
  205. it('from a user that is not in the group', async function () {
  206. expect(await this.manager.hasGroup(GROUPS.SOME, user).then(formatAccess)).to.be.deep.equal([false, '0']);
  207. await expectRevertCustomError(
  208. this.manager.revokeGroup(GROUPS.SOME, user, { from: manager }),
  209. 'AccessManagerAccountNotInGroup',
  210. [GROUPS.SOME, user],
  211. );
  212. });
  213. it('revoke group is restricted', async function () {
  214. await expectRevertCustomError(
  215. this.manager.revokeGroup(GROUPS.SOME, member, { from: other }),
  216. 'AccessManagerUnauthorizedAccount',
  217. [other, GROUPS.SOME_ADMIN],
  218. );
  219. });
  220. });
  221. describe('renounce group', function () {
  222. it('for a user that is already in the group', async function () {
  223. expect(await this.manager.hasGroup(GROUPS.SOME, member).then(formatAccess)).to.be.deep.equal([true, '0']);
  224. const { receipt } = await this.manager.renounceGroup(GROUPS.SOME, member, { from: member });
  225. expectEvent(receipt, 'GroupRevoked', { groupId: GROUPS.SOME, account: member });
  226. expect(await this.manager.hasGroup(GROUPS.SOME, member).then(formatAccess)).to.be.deep.equal([false, '0']);
  227. const access = await this.manager.getAccess(GROUPS.SOME, member);
  228. expect(access[0]).to.be.bignumber.equal('0'); // inGroupSince
  229. expect(access[1]).to.be.bignumber.equal('0'); // currentDelay
  230. expect(access[2]).to.be.bignumber.equal('0'); // pendingDelay
  231. expect(access[3]).to.be.bignumber.equal('0'); // effect
  232. });
  233. it('for a user that is schedule for joining the group', async function () {
  234. await this.manager.$_grantGroup(GROUPS.SOME, user, 10, 0); // grant delay 10
  235. expect(await this.manager.hasGroup(GROUPS.SOME, user).then(formatAccess)).to.be.deep.equal([false, '0']);
  236. const { receipt } = await this.manager.renounceGroup(GROUPS.SOME, user, { from: user });
  237. expectEvent(receipt, 'GroupRevoked', { groupId: GROUPS.SOME, account: user });
  238. expect(await this.manager.hasGroup(GROUPS.SOME, user).then(formatAccess)).to.be.deep.equal([false, '0']);
  239. const access = await this.manager.getAccess(GROUPS.SOME, user);
  240. expect(access[0]).to.be.bignumber.equal('0'); // inGroupSince
  241. expect(access[1]).to.be.bignumber.equal('0'); // currentDelay
  242. expect(access[2]).to.be.bignumber.equal('0'); // pendingDelay
  243. expect(access[3]).to.be.bignumber.equal('0'); // effect
  244. });
  245. it('for a user that is not in the group', async function () {
  246. await expectRevertCustomError(
  247. this.manager.renounceGroup(GROUPS.SOME, user, { from: user }),
  248. 'AccessManagerAccountNotInGroup',
  249. [GROUPS.SOME, user],
  250. );
  251. });
  252. it('bad user confirmation', async function () {
  253. await expectRevertCustomError(
  254. this.manager.renounceGroup(GROUPS.SOME, member, { from: user }),
  255. 'AccessManagerBadConfirmation',
  256. [],
  257. );
  258. });
  259. });
  260. describe('change group admin', function () {
  261. it("admin can set any group's admin", async function () {
  262. expect(await this.manager.getGroupAdmin(GROUPS.SOME)).to.be.bignumber.equal(GROUPS.SOME_ADMIN);
  263. const { receipt } = await this.manager.setGroupAdmin(GROUPS.SOME, GROUPS.ADMIN, { from: admin });
  264. expectEvent(receipt, 'GroupAdminChanged', { groupId: GROUPS.SOME, admin: GROUPS.ADMIN });
  265. expect(await this.manager.getGroupAdmin(GROUPS.SOME)).to.be.bignumber.equal(GROUPS.ADMIN);
  266. });
  267. it("setting a group's admin is restricted", async function () {
  268. await expectRevertCustomError(
  269. this.manager.setGroupAdmin(GROUPS.SOME, GROUPS.SOME, { from: manager }),
  270. 'AccessManagerUnauthorizedAccount',
  271. [manager, GROUPS.ADMIN],
  272. );
  273. });
  274. });
  275. describe('change group guardian', function () {
  276. it("admin can set any group's admin", async function () {
  277. expect(await this.manager.getGroupGuardian(GROUPS.SOME)).to.be.bignumber.equal(GROUPS.SOME_ADMIN);
  278. const { receipt } = await this.manager.setGroupGuardian(GROUPS.SOME, GROUPS.ADMIN, { from: admin });
  279. expectEvent(receipt, 'GroupGuardianChanged', { groupId: GROUPS.SOME, guardian: GROUPS.ADMIN });
  280. expect(await this.manager.getGroupGuardian(GROUPS.SOME)).to.be.bignumber.equal(GROUPS.ADMIN);
  281. });
  282. it("setting a group's admin is restricted", async function () {
  283. await expectRevertCustomError(
  284. this.manager.setGroupGuardian(GROUPS.SOME, GROUPS.SOME, { from: other }),
  285. 'AccessManagerUnauthorizedAccount',
  286. [other, GROUPS.ADMIN],
  287. );
  288. });
  289. });
  290. describe('change execution delay', function () {
  291. it('increassing the delay has immediate effect', async function () {
  292. const oldDelay = web3.utils.toBN(10);
  293. const newDelay = web3.utils.toBN(100);
  294. await this.manager.$_setExecuteDelay(GROUPS.SOME, member, oldDelay);
  295. const accessBefore = await this.manager.getAccess(GROUPS.SOME, member);
  296. expect(accessBefore[1]).to.be.bignumber.equal(oldDelay); // currentDelay
  297. expect(accessBefore[2]).to.be.bignumber.equal('0'); // pendingDelay
  298. expect(accessBefore[3]).to.be.bignumber.equal('0'); // effect
  299. const { receipt } = await this.manager.setExecuteDelay(GROUPS.SOME, member, newDelay, {
  300. from: manager,
  301. });
  302. const timestamp = await clockFromReceipt.timestamp(receipt).then(web3.utils.toBN);
  303. expectEvent(receipt, 'GroupExecutionDelayUpdated', {
  304. groupId: GROUPS.SOME,
  305. account: member,
  306. delay: newDelay,
  307. from: timestamp,
  308. });
  309. // immediate effect
  310. const accessAfter = await this.manager.getAccess(GROUPS.SOME, member);
  311. expect(accessAfter[1]).to.be.bignumber.equal(newDelay); // currentDelay
  312. expect(accessAfter[2]).to.be.bignumber.equal('0'); // pendingDelay
  313. expect(accessAfter[3]).to.be.bignumber.equal('0'); // effect
  314. });
  315. it('decreassing the delay takes time', async function () {
  316. const oldDelay = web3.utils.toBN(100);
  317. const newDelay = web3.utils.toBN(10);
  318. await this.manager.$_setExecuteDelay(GROUPS.SOME, member, oldDelay);
  319. const accessBefore = await this.manager.getAccess(GROUPS.SOME, member);
  320. expect(accessBefore[1]).to.be.bignumber.equal(oldDelay); // currentDelay
  321. expect(accessBefore[2]).to.be.bignumber.equal('0'); // pendingDelay
  322. expect(accessBefore[3]).to.be.bignumber.equal('0'); // effect
  323. const { receipt } = await this.manager.setExecuteDelay(GROUPS.SOME, member, newDelay, {
  324. from: manager,
  325. });
  326. const timestamp = await clockFromReceipt.timestamp(receipt).then(web3.utils.toBN);
  327. expectEvent(receipt, 'GroupExecutionDelayUpdated', {
  328. groupId: GROUPS.SOME,
  329. account: member,
  330. delay: newDelay,
  331. from: timestamp.add(oldDelay).sub(newDelay),
  332. });
  333. // delayed effect
  334. const accessAfter = await this.manager.getAccess(GROUPS.SOME, member);
  335. expect(accessAfter[1]).to.be.bignumber.equal(oldDelay); // currentDelay
  336. expect(accessAfter[2]).to.be.bignumber.equal(newDelay); // pendingDelay
  337. expect(accessAfter[3]).to.be.bignumber.equal(timestamp.add(oldDelay).sub(newDelay)); // effect
  338. });
  339. it('cannot set the delay of a non member', async function () {
  340. await expectRevertCustomError(
  341. this.manager.setExecuteDelay(GROUPS.SOME, other, executeDelay, { from: manager }),
  342. 'AccessManagerAccountNotInGroup',
  343. [GROUPS.SOME, other],
  344. );
  345. });
  346. it('cannot set the delay of public and admin groups', async function () {
  347. for (const group of [GROUPS.PUBLIC, GROUPS.ADMIN]) {
  348. await expectRevertCustomError(
  349. this.manager.$_setExecuteDelay(group, other, executeDelay, { from: manager }),
  350. 'AccessManagerLockedGroup',
  351. [group],
  352. );
  353. }
  354. });
  355. it('can set a user execution delay during the grant delay', async function () {
  356. await this.manager.$_grantGroup(GROUPS.SOME, other, 10, 0);
  357. const { receipt } = await this.manager.setExecuteDelay(GROUPS.SOME, other, executeDelay, { from: manager });
  358. const timestamp = await clockFromReceipt.timestamp(receipt).then(web3.utils.toBN);
  359. expectEvent(receipt, 'GroupExecutionDelayUpdated', {
  360. groupId: GROUPS.SOME,
  361. account: other,
  362. delay: executeDelay,
  363. from: timestamp,
  364. });
  365. });
  366. it('changing the execution delay is restricted', async function () {
  367. await expectRevertCustomError(
  368. this.manager.setExecuteDelay(GROUPS.SOME, member, executeDelay, { from: other }),
  369. 'AccessManagerUnauthorizedAccount',
  370. [GROUPS.SOME_ADMIN, other],
  371. );
  372. });
  373. });
  374. describe('change grant delay', function () {
  375. it('increassing the delay has immediate effect', async function () {
  376. const oldDelay = web3.utils.toBN(10);
  377. const newDelay = web3.utils.toBN(100);
  378. await this.manager.$_setGrantDelay(GROUPS.SOME, oldDelay);
  379. expect(await this.manager.getGroupGrantDelay(GROUPS.SOME)).to.be.bignumber.equal(oldDelay);
  380. const { receipt } = await this.manager.setGrantDelay(GROUPS.SOME, newDelay, { from: admin });
  381. const timestamp = await clockFromReceipt.timestamp(receipt).then(web3.utils.toBN);
  382. expectEvent(receipt, 'GroupGrantDelayChanged', { groupId: GROUPS.SOME, delay: newDelay, from: timestamp });
  383. expect(await this.manager.getGroupGrantDelay(GROUPS.SOME)).to.be.bignumber.equal(newDelay);
  384. });
  385. it('increassing the delay has delay effect', async function () {
  386. const oldDelay = web3.utils.toBN(100);
  387. const newDelay = web3.utils.toBN(10);
  388. await this.manager.$_setGrantDelay(GROUPS.SOME, oldDelay);
  389. expect(await this.manager.getGroupGrantDelay(GROUPS.SOME)).to.be.bignumber.equal(oldDelay);
  390. const { receipt } = await this.manager.setGrantDelay(GROUPS.SOME, newDelay, { from: admin });
  391. const timestamp = await clockFromReceipt.timestamp(receipt).then(web3.utils.toBN);
  392. expectEvent(receipt, 'GroupGrantDelayChanged', {
  393. groupId: GROUPS.SOME,
  394. delay: newDelay,
  395. from: timestamp.add(oldDelay).sub(newDelay),
  396. });
  397. expect(await this.manager.getGroupGrantDelay(GROUPS.SOME)).to.be.bignumber.equal(oldDelay);
  398. await time.increase(oldDelay.sub(newDelay));
  399. expect(await this.manager.getGroupGrantDelay(GROUPS.SOME)).to.be.bignumber.equal(newDelay);
  400. });
  401. it('changing the grant delay is restricted', async function () {
  402. await expectRevertCustomError(
  403. this.manager.setGrantDelay(GROUPS.SOME, grantDelay, { from: other }),
  404. 'AccessManagerUnauthorizedAccount',
  405. [GROUPS.ADMIN, other],
  406. );
  407. });
  408. });
  409. });
  410. describe('with AccessManaged target contract', function () {
  411. beforeEach('deploy target contract', async function () {
  412. this.target = await AccessManagedTarget.new(this.manager.address);
  413. // helpers for indirect calls
  414. this.callData = selector('fnRestricted()');
  415. this.call = [this.target.address, this.callData];
  416. this.opId = web3.utils.keccak256(
  417. web3.eth.abi.encodeParameters(['address', 'address', 'bytes'], [user, ...this.call]),
  418. );
  419. this.direct = (opts = {}) => this.target.fnRestricted({ from: user, ...opts });
  420. this.schedule = (opts = {}) => this.manager.schedule(...this.call, 0, { from: user, ...opts });
  421. this.relay = (opts = {}) => this.manager.relay(...this.call, { from: user, ...opts });
  422. this.cancel = (opts = {}) => this.manager.cancel(user, ...this.call, { from: user, ...opts });
  423. });
  424. describe('Change function permissions', function () {
  425. const sigs = ['someFunction()', 'someOtherFunction(uint256)', 'oneMoreFunction(address,uint8)'].map(selector);
  426. it('admin can set function group', async function () {
  427. for (const sig of sigs) {
  428. expect(await this.manager.getFamilyFunctionGroup(familyId, sig)).to.be.bignumber.equal(GROUPS.ADMIN);
  429. }
  430. const { receipt: receipt1 } = await this.manager.setFamilyFunctionGroup(familyId, sigs, GROUPS.SOME, {
  431. from: admin,
  432. });
  433. for (const sig of sigs) {
  434. expectEvent(receipt1, 'FamilyFunctionGroupUpdated', {
  435. familyId,
  436. selector: sig,
  437. groupId: GROUPS.SOME,
  438. });
  439. expect(await this.manager.getFamilyFunctionGroup(familyId, sig)).to.be.bignumber.equal(GROUPS.SOME);
  440. }
  441. const { receipt: receipt2 } = await this.manager.setFamilyFunctionGroup(
  442. familyId,
  443. [sigs[1]],
  444. GROUPS.SOME_ADMIN,
  445. { from: admin },
  446. );
  447. expectEvent(receipt2, 'FamilyFunctionGroupUpdated', {
  448. familyId,
  449. selector: sigs[1],
  450. groupId: GROUPS.SOME_ADMIN,
  451. });
  452. for (const sig of sigs) {
  453. expect(await this.manager.getFamilyFunctionGroup(familyId, sig)).to.be.bignumber.equal(
  454. sig == sigs[1] ? GROUPS.SOME_ADMIN : GROUPS.SOME,
  455. );
  456. }
  457. });
  458. it('non-admin cannot set function group', async function () {
  459. await expectRevertCustomError(
  460. this.manager.setFamilyFunctionGroup(familyId, sigs, GROUPS.SOME, { from: other }),
  461. 'AccessManagerUnauthorizedAccount',
  462. [other, GROUPS.ADMIN],
  463. );
  464. });
  465. });
  466. // WIP
  467. describe('Calling restricted & unrestricted functions', function () {
  468. const product = (...arrays) => arrays.reduce((a, b) => a.flatMap(ai => b.map(bi => [...ai, bi])), [[]]);
  469. for (const [callerGroups, fnGroup, closed, delay] of product(
  470. [[], [GROUPS.SOME]],
  471. [undefined, GROUPS.ADMIN, GROUPS.SOME, GROUPS.PUBLIC],
  472. [false, true],
  473. [null, executeDelay],
  474. )) {
  475. // can we call with a delay ?
  476. const indirectSuccess = (fnGroup == GROUPS.PUBLIC || callerGroups.includes(fnGroup)) && !closed;
  477. // can we call without a delay ?
  478. const directSuccess = (fnGroup == GROUPS.PUBLIC || (callerGroups.includes(fnGroup) && !delay)) && !closed;
  479. const description = [
  480. 'Caller in groups',
  481. '[' + (callerGroups ?? []).map(groupId => GROUPS[groupId]).join(', ') + ']',
  482. delay ? 'with a delay' : 'without a delay',
  483. '+',
  484. 'functions open to groups',
  485. '[' + (GROUPS[fnGroup] ?? '') + ']',
  486. closed ? `(closed)` : '',
  487. ].join(' ');
  488. describe(description, function () {
  489. beforeEach(async function () {
  490. // setup
  491. await Promise.all([
  492. this.manager.$_setContractClosed(this.target.address, closed),
  493. this.manager.$_setContractFamily(this.target.address, familyId),
  494. fnGroup && this.manager.$_setFamilyFunctionGroup(familyId, selector('fnRestricted()'), fnGroup),
  495. fnGroup && this.manager.$_setFamilyFunctionGroup(familyId, selector('fnUnrestricted()'), fnGroup),
  496. ...callerGroups
  497. .filter(groupId => groupId != GROUPS.PUBLIC)
  498. .map(groupId => this.manager.$_grantGroup(groupId, user, 0, delay ?? 0)),
  499. ]);
  500. // post setup checks
  501. const result = await this.manager.getContractFamily(this.target.address);
  502. expect(result[0]).to.be.bignumber.equal(familyId);
  503. expect(result[1]).to.be.equal(closed);
  504. if (fnGroup) {
  505. expect(
  506. await this.manager.getFamilyFunctionGroup(familyId, selector('fnRestricted()')),
  507. ).to.be.bignumber.equal(fnGroup);
  508. expect(
  509. await this.manager.getFamilyFunctionGroup(familyId, selector('fnUnrestricted()')),
  510. ).to.be.bignumber.equal(fnGroup);
  511. }
  512. for (const groupId of callerGroups) {
  513. const access = await this.manager.getAccess(groupId, user);
  514. if (groupId == GROUPS.PUBLIC) {
  515. expect(access[0]).to.be.bignumber.equal('0'); // inGroupSince
  516. expect(access[1]).to.be.bignumber.equal('0'); // currentDelay
  517. expect(access[2]).to.be.bignumber.equal('0'); // pendingDelay
  518. expect(access[3]).to.be.bignumber.equal('0'); // effect
  519. } else {
  520. expect(access[0]).to.be.bignumber.gt('0'); // inGroupSince
  521. expect(access[1]).to.be.bignumber.eq(String(delay ?? 0)); // currentDelay
  522. expect(access[2]).to.be.bignumber.equal('0'); // pendingDelay
  523. expect(access[3]).to.be.bignumber.equal('0'); // effect
  524. }
  525. }
  526. });
  527. it('canCall', async function () {
  528. const result = await this.manager.canCall(user, this.target.address, selector('fnRestricted()'));
  529. expect(result[0]).to.be.equal(directSuccess);
  530. expect(result[1]).to.be.bignumber.equal(!directSuccess && indirectSuccess ? delay ?? '0' : '0');
  531. });
  532. it('Calling a non restricted function never revert', async function () {
  533. expectEvent(await this.target.fnUnrestricted({ from: user }), 'CalledUnrestricted', {
  534. caller: user,
  535. });
  536. });
  537. it(`Calling a restricted function directly should ${
  538. directSuccess ? 'succeed' : 'revert'
  539. }`, async function () {
  540. const promise = this.direct();
  541. if (directSuccess) {
  542. expectEvent(await promise, 'CalledRestricted', { caller: user });
  543. } else if (indirectSuccess) {
  544. await expectRevertCustomError(promise, 'AccessManagerNotScheduled', [this.opId]);
  545. } else {
  546. await expectRevertCustomError(promise, 'AccessManagedUnauthorized', [user]);
  547. }
  548. });
  549. it('Calling indirectly: only relay', async function () {
  550. // relay without schedule
  551. if (directSuccess) {
  552. const { receipt, tx } = await this.relay();
  553. expectEvent.notEmitted(receipt, 'OperationExecuted', { operationId: this.opId });
  554. await expectEvent.inTransaction(tx, this.target, 'CalledRestricted', { caller: this.manager.address });
  555. } else if (indirectSuccess) {
  556. await expectRevertCustomError(this.relay(), 'AccessManagerNotScheduled', [this.opId]);
  557. } else {
  558. await expectRevertCustomError(this.relay(), 'AccessManagerUnauthorizedCall', [user, ...this.call]);
  559. }
  560. });
  561. it('Calling indirectly: schedule and relay', async function () {
  562. if (directSuccess || indirectSuccess) {
  563. const { receipt } = await this.schedule();
  564. const timestamp = await clockFromReceipt.timestamp(receipt).then(web3.utils.toBN);
  565. expectEvent(receipt, 'OperationScheduled', {
  566. operationId: this.opId,
  567. caller: user,
  568. target: this.call[0],
  569. data: this.call[1],
  570. });
  571. // if can call directly, delay should be 0. Otherwise, the delay should be applied
  572. expect(await this.manager.getSchedule(this.opId)).to.be.bignumber.equal(
  573. timestamp.add(directSuccess ? web3.utils.toBN(0) : delay),
  574. );
  575. // execute without wait
  576. if (directSuccess) {
  577. const { receipt, tx } = await this.relay();
  578. await expectEvent.inTransaction(tx, this.target, 'CalledRestricted', { caller: this.manager.address });
  579. if (delay && fnGroup !== GROUPS.PUBLIC) {
  580. expectEvent(receipt, 'OperationExecuted', { operationId: this.opId });
  581. expect(await this.manager.getSchedule(this.opId)).to.be.bignumber.equal('0');
  582. }
  583. } else if (indirectSuccess) {
  584. await expectRevertCustomError(this.relay(), 'AccessManagerNotReady', [this.opId]);
  585. } else {
  586. await expectRevertCustomError(this.relay(), 'AccessManagerUnauthorizedCall', [user, ...this.call]);
  587. }
  588. } else {
  589. await expectRevertCustomError(this.schedule(), 'AccessManagerUnauthorizedCall', [user, ...this.call]);
  590. }
  591. });
  592. it('Calling indirectly: schedule wait and relay', async function () {
  593. if (directSuccess || indirectSuccess) {
  594. const { receipt } = await this.schedule();
  595. const timestamp = await clockFromReceipt.timestamp(receipt).then(web3.utils.toBN);
  596. expectEvent(receipt, 'OperationScheduled', {
  597. operationId: this.opId,
  598. caller: user,
  599. target: this.call[0],
  600. data: this.call[1],
  601. });
  602. // if can call directly, delay should be 0. Otherwise, the delay should be applied
  603. expect(await this.manager.getSchedule(this.opId)).to.be.bignumber.equal(
  604. timestamp.add(directSuccess ? web3.utils.toBN(0) : delay),
  605. );
  606. // wait
  607. await time.increase(delay ?? 0);
  608. // execute without wait
  609. if (directSuccess || indirectSuccess) {
  610. const { receipt, tx } = await this.relay();
  611. await expectEvent.inTransaction(tx, this.target, 'CalledRestricted', { caller: this.manager.address });
  612. if (delay && fnGroup !== GROUPS.PUBLIC) {
  613. expectEvent(receipt, 'OperationExecuted', { operationId: this.opId });
  614. expect(await this.manager.getSchedule(this.opId)).to.be.bignumber.equal('0');
  615. }
  616. } else {
  617. await expectRevertCustomError(this.relay(), 'AccessManagerUnauthorizedCall', [user, ...this.call]);
  618. }
  619. } else {
  620. await expectRevertCustomError(this.schedule(), 'AccessManagerUnauthorizedCall', [user, ...this.call]);
  621. }
  622. });
  623. it('Calling directly: schedule and call', async function () {
  624. if (directSuccess || indirectSuccess) {
  625. const { receipt } = await this.schedule();
  626. const timestamp = await clockFromReceipt.timestamp(receipt).then(web3.utils.toBN);
  627. expectEvent(receipt, 'OperationScheduled', {
  628. operationId: this.opId,
  629. caller: user,
  630. target: this.call[0],
  631. data: this.call[1],
  632. });
  633. // if can call directly, delay should be 0. Otherwise, the delay should be applied
  634. const schedule = timestamp.add(directSuccess ? web3.utils.toBN(0) : delay);
  635. expect(await this.manager.getSchedule(this.opId)).to.be.bignumber.equal(schedule);
  636. // execute without wait
  637. const promise = this.direct();
  638. if (directSuccess) {
  639. expectEvent(await promise, 'CalledRestricted', { caller: user });
  640. // schedule is not reset
  641. expect(await this.manager.getSchedule(this.opId)).to.be.bignumber.equal(schedule);
  642. } else if (indirectSuccess) {
  643. await expectRevertCustomError(promise, 'AccessManagerNotReady', [this.opId]);
  644. } else {
  645. await expectRevertCustomError(promise, 'AccessManagerUnauthorizedCall', [user, ...this.call]);
  646. }
  647. } else {
  648. await expectRevertCustomError(this.schedule(), 'AccessManagerUnauthorizedCall', [user, ...this.call]);
  649. }
  650. });
  651. it('Calling directly: schedule wait and call', async function () {
  652. if (directSuccess || indirectSuccess) {
  653. const { receipt } = await this.schedule();
  654. const timestamp = await clockFromReceipt.timestamp(receipt).then(web3.utils.toBN);
  655. expectEvent(receipt, 'OperationScheduled', {
  656. operationId: this.opId,
  657. caller: user,
  658. target: this.call[0],
  659. data: this.call[1],
  660. });
  661. // if can call directly, delay should be 0. Otherwise, the delay should be applied
  662. const schedule = timestamp.add(directSuccess ? web3.utils.toBN(0) : delay);
  663. expect(await this.manager.getSchedule(this.opId)).to.be.bignumber.equal(schedule);
  664. // wait
  665. await time.increase(delay ?? 0);
  666. // execute without wait
  667. const promise = await this.direct();
  668. if (directSuccess) {
  669. expectEvent(await promise, 'CalledRestricted', { caller: user });
  670. // schedule is not reset
  671. expect(await this.manager.getSchedule(this.opId)).to.be.bignumber.equal(schedule);
  672. } else if (indirectSuccess) {
  673. const receipt = await promise;
  674. expectEvent(receipt, 'CalledRestricted', { caller: user });
  675. await expectEvent.inTransaction(receipt.tx, this.manager, 'OperationExecuted', {
  676. operationId: this.opId,
  677. });
  678. // schedule is reset
  679. expect(await this.manager.getSchedule(this.opId)).to.be.bignumber.equal('0');
  680. } else {
  681. await expectRevertCustomError(this.direct(), 'AccessManagerUnauthorizedCall', [user, ...this.call]);
  682. }
  683. } else {
  684. await expectRevertCustomError(this.schedule(), 'AccessManagerUnauthorizedCall', [user, ...this.call]);
  685. }
  686. });
  687. it('Scheduling for later than needed'); // TODO
  688. });
  689. }
  690. });
  691. describe('Indirect execution corner-cases', async function () {
  692. beforeEach(async function () {
  693. await this.manager.$_setContractFamily(this.target.address, familyId);
  694. await this.manager.$_setFamilyFunctionGroup(familyId, this.callData, GROUPS.SOME);
  695. await this.manager.$_grantGroup(GROUPS.SOME, user, 0, executeDelay);
  696. });
  697. it('Checking canCall when caller is the manager depend on the _relayIdentifier', async function () {
  698. const result = await this.manager.canCall(this.manager.address, this.target.address, '0x00000000');
  699. expect(result[0]).to.be.false;
  700. expect(result[1]).to.be.bignumber.equal('0');
  701. });
  702. it('Cannot execute earlier', async function () {
  703. const { receipt } = await this.schedule();
  704. const timestamp = await clockFromReceipt.timestamp(receipt).then(web3.utils.toBN);
  705. expect(await this.manager.getSchedule(this.opId)).to.be.bignumber.equal(timestamp.add(executeDelay));
  706. // we need to set the clock 2 seconds before the value, because the increaseTo "consumes" the timestamp
  707. // and the next transaction will be one after that (see check below)
  708. await time.increaseTo(timestamp.add(executeDelay).subn(2));
  709. // too early
  710. await expectRevertCustomError(this.relay(), 'AccessManagerNotReady', [this.opId]);
  711. // the revert happened one second before the execution delay expired
  712. expect(await time.latest()).to.be.bignumber.equal(timestamp.add(executeDelay).subn(1));
  713. // ok
  714. await this.relay();
  715. // the success happened when the delay was reached (earliest possible)
  716. expect(await time.latest()).to.be.bignumber.equal(timestamp.add(executeDelay));
  717. });
  718. it('Cannot schedule an already scheduled operation', async function () {
  719. const { receipt } = await this.schedule();
  720. expectEvent(receipt, 'OperationScheduled', {
  721. operationId: this.opId,
  722. caller: user,
  723. target: this.call[0],
  724. data: this.call[1],
  725. });
  726. await expectRevertCustomError(this.schedule(), 'AccessManagerAlreadyScheduled', [this.opId]);
  727. });
  728. it('Cannot cancel an operation that is not scheduled', async function () {
  729. await expectRevertCustomError(this.cancel(), 'AccessManagerNotScheduled', [this.opId]);
  730. });
  731. it('Cannot cancel an operation that is not already relayed', async function () {
  732. await this.schedule();
  733. await time.increase(executeDelay);
  734. await this.relay();
  735. await expectRevertCustomError(this.cancel(), 'AccessManagerNotScheduled', [this.opId]);
  736. });
  737. it('Scheduler can cancel', async function () {
  738. await this.schedule();
  739. expect(await this.manager.getSchedule(this.opId)).to.not.be.bignumber.equal('0');
  740. expectEvent(await this.cancel({ from: manager }), 'OperationCanceled', { operationId: this.opId });
  741. expect(await this.manager.getSchedule(this.opId)).to.be.bignumber.equal('0');
  742. });
  743. it('Guardian can cancel', async function () {
  744. await this.schedule();
  745. expect(await this.manager.getSchedule(this.opId)).to.not.be.bignumber.equal('0');
  746. expectEvent(await this.cancel({ from: manager }), 'OperationCanceled', { operationId: this.opId });
  747. expect(await this.manager.getSchedule(this.opId)).to.be.bignumber.equal('0');
  748. });
  749. it('Cancel is restricted', async function () {
  750. await this.schedule();
  751. expect(await this.manager.getSchedule(this.opId)).to.not.be.bignumber.equal('0');
  752. await expectRevertCustomError(this.cancel({ from: other }), 'AccessManagerCannotCancel', [
  753. other,
  754. user,
  755. ...this.call,
  756. ]);
  757. expect(await this.manager.getSchedule(this.opId)).to.not.be.bignumber.equal('0');
  758. });
  759. it('Can re-schedule after execution', async function () {
  760. await this.schedule();
  761. await time.increase(executeDelay);
  762. await this.relay();
  763. // reschedule
  764. const { receipt } = await this.schedule();
  765. expectEvent(receipt, 'OperationScheduled', {
  766. operationId: this.opId,
  767. caller: user,
  768. target: this.call[0],
  769. data: this.call[1],
  770. });
  771. });
  772. it('Can re-schedule after cancel', async function () {
  773. await this.schedule();
  774. await this.cancel();
  775. // reschedule
  776. const { receipt } = await this.schedule();
  777. expectEvent(receipt, 'OperationScheduled', {
  778. operationId: this.opId,
  779. caller: user,
  780. target: this.call[0],
  781. data: this.call[1],
  782. });
  783. });
  784. });
  785. });
  786. describe('with Ownable target contract', function () {
  787. const groupId = web3.utils.toBN(1);
  788. beforeEach(async function () {
  789. this.ownable = await Ownable.new(this.manager.address);
  790. // add user to group
  791. await this.manager.$_grantGroup(groupId, user, 0, 0);
  792. });
  793. it('initial state', async function () {
  794. expect(await this.ownable.owner()).to.be.equal(this.manager.address);
  795. });
  796. describe('Contract is closed', function () {
  797. beforeEach(async function () {
  798. await this.manager.$_setContractClosed(this.ownable.address, true);
  799. });
  800. it('directly call: reverts', async function () {
  801. await expectRevertCustomError(this.ownable.$_checkOwner({ from: user }), 'OwnableUnauthorizedAccount', [user]);
  802. });
  803. it('relayed call (with group): reverts', async function () {
  804. await expectRevertCustomError(
  805. this.manager.relay(this.ownable.address, selector('$_checkOwner()'), { from: user }),
  806. 'AccessManagerUnauthorizedCall',
  807. [user, this.ownable.address, selector('$_checkOwner()')],
  808. );
  809. });
  810. it('relayed call (without group): reverts', async function () {
  811. await expectRevertCustomError(
  812. this.manager.relay(this.ownable.address, selector('$_checkOwner()'), { from: other }),
  813. 'AccessManagerUnauthorizedCall',
  814. [other, this.ownable.address, selector('$_checkOwner()')],
  815. );
  816. });
  817. });
  818. describe('Contract is managed', function () {
  819. beforeEach('add contract to family', async function () {
  820. await this.manager.$_setContractFamily(this.ownable.address, familyId);
  821. });
  822. describe('function is open to specific group', function () {
  823. beforeEach(async function () {
  824. await this.manager.$_setFamilyFunctionGroup(familyId, selector('$_checkOwner()'), groupId);
  825. });
  826. it('directly call: reverts', async function () {
  827. await expectRevertCustomError(this.ownable.$_checkOwner({ from: user }), 'OwnableUnauthorizedAccount', [
  828. user,
  829. ]);
  830. });
  831. it('relayed call (with group): success', async function () {
  832. await this.manager.relay(this.ownable.address, selector('$_checkOwner()'), { from: user });
  833. });
  834. it('relayed call (without group): reverts', async function () {
  835. await expectRevertCustomError(
  836. this.manager.relay(this.ownable.address, selector('$_checkOwner()'), { from: other }),
  837. 'AccessManagerUnauthorizedCall',
  838. [other, this.ownable.address, selector('$_checkOwner()')],
  839. );
  840. });
  841. });
  842. describe('function is open to public group', function () {
  843. beforeEach(async function () {
  844. await this.manager.$_setFamilyFunctionGroup(familyId, selector('$_checkOwner()'), GROUPS.PUBLIC);
  845. });
  846. it('directly call: reverts', async function () {
  847. await expectRevertCustomError(this.ownable.$_checkOwner({ from: user }), 'OwnableUnauthorizedAccount', [
  848. user,
  849. ]);
  850. });
  851. it('relayed call (with group): success', async function () {
  852. await this.manager.relay(this.ownable.address, selector('$_checkOwner()'), { from: user });
  853. });
  854. it('relayed call (without group): success', async function () {
  855. await this.manager.relay(this.ownable.address, selector('$_checkOwner()'), { from: other });
  856. });
  857. });
  858. });
  859. });
  860. describe('authority update', function () {
  861. beforeEach(async function () {
  862. this.newManager = await AccessManager.new(admin);
  863. this.target = await AccessManagedTarget.new(this.manager.address);
  864. });
  865. it('admin can change authority', async function () {
  866. expect(await this.target.authority()).to.be.equal(this.manager.address);
  867. const { tx } = await this.manager.updateAuthority(this.target.address, this.newManager.address, { from: admin });
  868. await expectEvent.inTransaction(tx, this.target, 'AuthorityUpdated', { authority: this.newManager.address });
  869. expect(await this.target.authority()).to.be.equal(this.newManager.address);
  870. });
  871. it('cannot set an address without code as the authority', async function () {
  872. await expectRevertCustomError(
  873. this.manager.updateAuthority(this.target.address, user, { from: admin }),
  874. 'AccessManagedInvalidAuthority',
  875. [user],
  876. );
  877. });
  878. it('updateAuthority is restricted on manager', async function () {
  879. await expectRevertCustomError(
  880. this.manager.updateAuthority(this.target.address, this.newManager.address, { from: other }),
  881. 'AccessManagerUnauthorizedAccount',
  882. [other, GROUPS.ADMIN],
  883. );
  884. });
  885. it('setAuthority is restricted on AccessManaged', async function () {
  886. await expectRevertCustomError(
  887. this.target.setAuthority(this.newManager.address, { from: admin }),
  888. 'AccessManagedUnauthorized',
  889. [admin],
  890. );
  891. });
  892. });
  893. // TODO: test all admin functions
  894. describe('family delays', function () {
  895. const otherFamilyId = '2';
  896. const delay = '1000';
  897. beforeEach('set contract family', async function () {
  898. this.target = await AccessManagedTarget.new(this.manager.address);
  899. await this.manager.setContractFamily(this.target.address, familyId, { from: admin });
  900. this.call = () => this.manager.setContractFamily(this.target.address, otherFamilyId, { from: admin });
  901. this.data = this.manager.contract.methods.setContractFamily(this.target.address, otherFamilyId).encodeABI();
  902. });
  903. it('without delay: succeeds', async function () {
  904. await this.call();
  905. });
  906. // TODO: here we need to check increase and decrease.
  907. // - Increasing should have immediate effect
  908. // - Decreassing should take time.
  909. describe('with delay', function () {
  910. beforeEach('set admin delay', async function () {
  911. this.tx = await this.manager.setFamilyAdminDelay(familyId, delay, { from: admin });
  912. this.opId = web3.utils.keccak256(
  913. web3.eth.abi.encodeParameters(['address', 'address', 'bytes'], [admin, this.manager.address, this.data]),
  914. );
  915. });
  916. it('emits event and sets delay', async function () {
  917. const from = await clockFromReceipt.timestamp(this.tx.receipt).then(web3.utils.toBN);
  918. expectEvent(this.tx.receipt, 'FamilyAdminDelayUpdated', { familyId, delay, from });
  919. expect(await this.manager.getFamilyAdminDelay(familyId)).to.be.bignumber.equal(delay);
  920. });
  921. it('without prior scheduling: reverts', async function () {
  922. await expectRevertCustomError(this.call(), 'AccessManagerNotScheduled', [this.opId]);
  923. });
  924. describe('with prior scheduling', async function () {
  925. beforeEach('schedule', async function () {
  926. await this.manager.schedule(this.manager.address, this.data, 0, { from: admin });
  927. });
  928. it('without delay: reverts', async function () {
  929. await expectRevertCustomError(this.call(), 'AccessManagerNotReady', [this.opId]);
  930. });
  931. it('with delay: succeeds', async function () {
  932. await time.increase(delay);
  933. await this.call();
  934. });
  935. });
  936. });
  937. });
  938. });