AccessManager.test.js 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927
  1. const { expectEvent, constants, time } = require('@openzeppelin/test-helpers');
  2. const { expectRevertCustomError } = require('../../helpers/customError');
  3. const { AccessMode } = require('../../helpers/enums');
  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 GROUPS = {
  9. ADMIN: web3.utils.toBN(0),
  10. SOME_ADMIN: web3.utils.toBN(17),
  11. SOME: web3.utils.toBN(42),
  12. PUBLIC: constants.MAX_UINT256,
  13. };
  14. Object.assign(GROUPS, Object.fromEntries(Object.entries(GROUPS).map(([key, value]) => [value, key])));
  15. const executeDelay = web3.utils.toBN(10);
  16. const grantDelay = web3.utils.toBN(10);
  17. const MAX_UINT = n => web3.utils.toBN(1).shln(n).subn(1);
  18. const split = delay => ({
  19. oldValue: web3.utils.toBN(delay).shrn(0).and(MAX_UINT(32)).toString(),
  20. newValue: web3.utils.toBN(delay).shrn(32).and(MAX_UINT(32)).toString(),
  21. effect: web3.utils.toBN(delay).shrn(64).and(MAX_UINT(48)).toString(),
  22. });
  23. contract('AccessManager', function (accounts) {
  24. const [admin, manager, member, user, other] = accounts;
  25. beforeEach(async function () {
  26. this.manager = await AccessManager.new(admin);
  27. this.target = await AccessManagedTarget.new(this.manager.address);
  28. // add member to group
  29. await this.manager.$_setGroupAdmin(GROUPS.SOME, GROUPS.SOME_ADMIN);
  30. await this.manager.$_setGroupGuardian(GROUPS.SOME, GROUPS.SOME_ADMIN);
  31. await this.manager.$_grantGroup(GROUPS.SOME_ADMIN, manager, 0, 0);
  32. await this.manager.$_grantGroup(GROUPS.SOME, member, 0, 0);
  33. // helpers for indirect calls
  34. this.call = [this.target.address, selector('fnRestricted()')];
  35. this.opId = web3.utils.keccak256(
  36. web3.eth.abi.encodeParameters(['address', 'address', 'bytes'], [user, ...this.call]),
  37. );
  38. this.schedule = (opts = {}) => this.manager.schedule(...this.call, { from: user, ...opts });
  39. this.relay = (opts = {}) => this.manager.relay(...this.call, { from: user, ...opts });
  40. this.cancel = (opts = {}) => this.manager.cancel(user, ...this.call, { from: user, ...opts });
  41. });
  42. it('groups are correctly initialized', async function () {
  43. // group admin
  44. expect(await this.manager.getGroupAdmin(GROUPS.ADMIN)).to.be.bignumber.equal(GROUPS.ADMIN);
  45. expect(await this.manager.getGroupAdmin(GROUPS.SOME_ADMIN)).to.be.bignumber.equal(GROUPS.ADMIN);
  46. expect(await this.manager.getGroupAdmin(GROUPS.SOME)).to.be.bignumber.equal(GROUPS.SOME_ADMIN);
  47. expect(await this.manager.getGroupAdmin(GROUPS.PUBLIC)).to.be.bignumber.equal(GROUPS.ADMIN);
  48. // group guardian
  49. expect(await this.manager.getGroupGuardian(GROUPS.ADMIN)).to.be.bignumber.equal(GROUPS.ADMIN);
  50. expect(await this.manager.getGroupGuardian(GROUPS.SOME_ADMIN)).to.be.bignumber.equal(GROUPS.ADMIN);
  51. expect(await this.manager.getGroupGuardian(GROUPS.SOME)).to.be.bignumber.equal(GROUPS.SOME_ADMIN);
  52. expect(await this.manager.getGroupGuardian(GROUPS.PUBLIC)).to.be.bignumber.equal(GROUPS.ADMIN);
  53. // group members
  54. expect(await this.manager.hasGroup(GROUPS.ADMIN, admin)).to.be.equal(true);
  55. expect(await this.manager.hasGroup(GROUPS.ADMIN, manager)).to.be.equal(false);
  56. expect(await this.manager.hasGroup(GROUPS.ADMIN, member)).to.be.equal(false);
  57. expect(await this.manager.hasGroup(GROUPS.ADMIN, user)).to.be.equal(false);
  58. expect(await this.manager.hasGroup(GROUPS.SOME_ADMIN, admin)).to.be.equal(false);
  59. expect(await this.manager.hasGroup(GROUPS.SOME_ADMIN, manager)).to.be.equal(true);
  60. expect(await this.manager.hasGroup(GROUPS.SOME_ADMIN, member)).to.be.equal(false);
  61. expect(await this.manager.hasGroup(GROUPS.SOME_ADMIN, user)).to.be.equal(false);
  62. expect(await this.manager.hasGroup(GROUPS.SOME, admin)).to.be.equal(false);
  63. expect(await this.manager.hasGroup(GROUPS.SOME, manager)).to.be.equal(false);
  64. expect(await this.manager.hasGroup(GROUPS.SOME, member)).to.be.equal(true);
  65. expect(await this.manager.hasGroup(GROUPS.SOME, user)).to.be.equal(false);
  66. expect(await this.manager.hasGroup(GROUPS.PUBLIC, admin)).to.be.equal(true);
  67. expect(await this.manager.hasGroup(GROUPS.PUBLIC, manager)).to.be.equal(true);
  68. expect(await this.manager.hasGroup(GROUPS.PUBLIC, member)).to.be.equal(true);
  69. expect(await this.manager.hasGroup(GROUPS.PUBLIC, user)).to.be.equal(true);
  70. });
  71. describe('Groups management', function () {
  72. describe('label group', function () {
  73. it('admin can emit a label event', async function () {
  74. expectEvent(await this.manager.labelGroup(GROUPS.SOME, 'Some label', { from: admin }), 'GroupLabel', {
  75. groupId: GROUPS.SOME,
  76. label: 'Some label',
  77. });
  78. });
  79. it('admin can re-emit a label event', async function () {
  80. await this.manager.labelGroup(GROUPS.SOME, 'Some label', { from: admin });
  81. expectEvent(await this.manager.labelGroup(GROUPS.SOME, 'Updated label', { from: admin }), 'GroupLabel', {
  82. groupId: GROUPS.SOME,
  83. label: 'Updated label',
  84. });
  85. });
  86. it('emitting a label is restricted', async function () {
  87. await expectRevertCustomError(
  88. this.manager.labelGroup(GROUPS.SOME, 'Invalid label', { from: other }),
  89. 'AccessControlUnauthorizedAccount',
  90. [other, GROUPS.ADMIN],
  91. );
  92. });
  93. });
  94. describe('grand group', function () {
  95. describe('without a grant delay', function () {
  96. it('without an execute delay', async function () {
  97. expect(await this.manager.hasGroup(GROUPS.SOME, user)).to.be.false;
  98. const { receipt } = await this.manager.grantGroup(GROUPS.SOME, user, 0, { from: manager });
  99. const timestamp = await clockFromReceipt.timestamp(receipt).then(web3.utils.toBN);
  100. expectEvent(receipt, 'GroupGranted', { groupId: GROUPS.SOME, account: user, since: timestamp, delay: '0' });
  101. expect(await this.manager.hasGroup(GROUPS.SOME, user)).to.be.true;
  102. const { delay, since } = await this.manager.getAccess(GROUPS.SOME, user);
  103. expect(delay).to.be.bignumber.equal('0');
  104. expect(since).to.be.bignumber.equal(timestamp);
  105. });
  106. it('with an execute delay', async function () {
  107. expect(await this.manager.hasGroup(GROUPS.SOME, user)).to.be.false;
  108. const { receipt } = await this.manager.grantGroup(GROUPS.SOME, user, executeDelay, { from: manager });
  109. const timestamp = await clockFromReceipt.timestamp(receipt).then(web3.utils.toBN);
  110. expectEvent(receipt, 'GroupGranted', {
  111. groupId: GROUPS.SOME,
  112. account: user,
  113. since: timestamp,
  114. delay: executeDelay,
  115. });
  116. expect(await this.manager.hasGroup(GROUPS.SOME, user)).to.be.true;
  117. const { delay, since } = await this.manager.getAccess(GROUPS.SOME, user);
  118. expect(delay).to.be.bignumber.equal(executeDelay);
  119. expect(since).to.be.bignumber.equal(timestamp);
  120. });
  121. it('to a user that is already in the group', async function () {
  122. expect(await this.manager.hasGroup(GROUPS.SOME, member)).to.be.true;
  123. await expectRevertCustomError(
  124. this.manager.grantGroup(GROUPS.SOME, member, 0, { from: manager }),
  125. 'AccessManagerAcountAlreadyInGroup',
  126. [GROUPS.SOME, member],
  127. );
  128. });
  129. it('to a user that is scheduled for joining the group', async function () {
  130. await this.manager.$_grantGroup(GROUPS.SOME, user, 10, 0); // grant delay 10
  131. expect(await this.manager.hasGroup(GROUPS.SOME, user)).to.be.false;
  132. await expectRevertCustomError(
  133. this.manager.grantGroup(GROUPS.SOME, user, 0, { from: manager }),
  134. 'AccessManagerAcountAlreadyInGroup',
  135. [GROUPS.SOME, user],
  136. );
  137. });
  138. it('grant group is restricted', async function () {
  139. await expectRevertCustomError(
  140. this.manager.grantGroup(GROUPS.SOME, user, 0, { from: other }),
  141. 'AccessControlUnauthorizedAccount',
  142. [other, GROUPS.SOME_ADMIN],
  143. );
  144. });
  145. });
  146. describe('with a grant delay', function () {
  147. beforeEach(async function () {
  148. await this.manager.$_setGrantDelay(GROUPS.SOME, grantDelay);
  149. });
  150. it('granted group is not active immediatly', async function () {
  151. const { receipt } = await this.manager.grantGroup(GROUPS.SOME, user, 0, { from: manager });
  152. const timestamp = await clockFromReceipt.timestamp(receipt).then(web3.utils.toBN);
  153. expectEvent(receipt, 'GroupGranted', {
  154. groupId: GROUPS.SOME,
  155. account: user,
  156. since: timestamp.add(grantDelay),
  157. delay: '0',
  158. });
  159. expect(await this.manager.hasGroup(GROUPS.SOME, user)).to.be.false;
  160. const { delay, since } = await this.manager.getAccess(GROUPS.SOME, user);
  161. expect(delay).to.be.bignumber.equal('0');
  162. expect(since).to.be.bignumber.equal(timestamp.add(grantDelay));
  163. });
  164. it('granted group is active after the delay', async function () {
  165. const { receipt } = await this.manager.grantGroup(GROUPS.SOME, user, 0, { from: manager });
  166. const timestamp = await clockFromReceipt.timestamp(receipt).then(web3.utils.toBN);
  167. expectEvent(receipt, 'GroupGranted', {
  168. groupId: GROUPS.SOME,
  169. account: user,
  170. since: timestamp.add(grantDelay),
  171. delay: '0',
  172. });
  173. await time.increase(grantDelay);
  174. expect(await this.manager.hasGroup(GROUPS.SOME, user)).to.be.true;
  175. const { delay, since } = await this.manager.getAccess(GROUPS.SOME, user);
  176. expect(delay).to.be.bignumber.equal('0');
  177. expect(since).to.be.bignumber.equal(timestamp.add(grantDelay));
  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)).to.be.true;
  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)).to.be.false;
  187. const { delay, since } = await this.manager.getAccess(GROUPS.SOME, user);
  188. expect(delay).to.be.bignumber.equal('0');
  189. expect(since).to.be.bignumber.equal('0');
  190. });
  191. it('from a user that is scheduled for joining the group', async function () {
  192. await this.manager.$_grantGroup(GROUPS.SOME, user, 10, 0); // grant delay 10
  193. expect(await this.manager.hasGroup(GROUPS.SOME, user)).to.be.false;
  194. const { receipt } = await this.manager.revokeGroup(GROUPS.SOME, user, { from: manager });
  195. expectEvent(receipt, 'GroupRevoked', { groupId: GROUPS.SOME, account: user });
  196. expect(await this.manager.hasGroup(GROUPS.SOME, user)).to.be.false;
  197. const { delay, since } = await this.manager.getAccess(GROUPS.SOME, user);
  198. expect(delay).to.be.bignumber.equal('0');
  199. expect(since).to.be.bignumber.equal('0');
  200. });
  201. it('from a user that is not in the group', async function () {
  202. expect(await this.manager.hasGroup(GROUPS.SOME, user)).to.be.false;
  203. await expectRevertCustomError(
  204. this.manager.revokeGroup(GROUPS.SOME, user, { from: manager }),
  205. 'AccessManagerAcountNotInGroup',
  206. [GROUPS.SOME, user],
  207. );
  208. });
  209. it('revoke group is restricted', async function () {
  210. await expectRevertCustomError(
  211. this.manager.revokeGroup(GROUPS.SOME, member, { from: other }),
  212. 'AccessControlUnauthorizedAccount',
  213. [other, GROUPS.SOME_ADMIN],
  214. );
  215. });
  216. });
  217. describe('renounce group', function () {
  218. it('for a user that is already in the group', async function () {
  219. expect(await this.manager.hasGroup(GROUPS.SOME, member)).to.be.true;
  220. const { receipt } = await this.manager.renounceGroup(GROUPS.SOME, member, { from: member });
  221. expectEvent(receipt, 'GroupRevoked', { groupId: GROUPS.SOME, account: member });
  222. expect(await this.manager.hasGroup(GROUPS.SOME, member)).to.be.false;
  223. const { delay, since } = await this.manager.getAccess(GROUPS.SOME, member);
  224. expect(delay).to.be.bignumber.equal('0');
  225. expect(since).to.be.bignumber.equal('0');
  226. });
  227. it('for a user that is schedule for joining the group', async function () {
  228. await this.manager.$_grantGroup(GROUPS.SOME, user, 10, 0); // grant delay 10
  229. expect(await this.manager.hasGroup(GROUPS.SOME, user)).to.be.false;
  230. const { receipt } = await this.manager.renounceGroup(GROUPS.SOME, user, { from: user });
  231. expectEvent(receipt, 'GroupRevoked', { groupId: GROUPS.SOME, account: user });
  232. expect(await this.manager.hasGroup(GROUPS.SOME, user)).to.be.false;
  233. const { delay, since } = await this.manager.getAccess(GROUPS.SOME, user);
  234. expect(delay).to.be.bignumber.equal('0');
  235. expect(since).to.be.bignumber.equal('0');
  236. });
  237. it('for a user that is not in the group', async function () {
  238. await expectRevertCustomError(
  239. this.manager.renounceGroup(GROUPS.SOME, user, { from: user }),
  240. 'AccessManagerAcountNotInGroup',
  241. [GROUPS.SOME, user],
  242. );
  243. });
  244. it('bad user confirmation', async function () {
  245. await expectRevertCustomError(
  246. this.manager.renounceGroup(GROUPS.SOME, member, { from: user }),
  247. 'AccessManagerBadConfirmation',
  248. [],
  249. );
  250. });
  251. });
  252. describe('change group admin', function () {
  253. it("admin can set any group's admin", async function () {
  254. expect(await this.manager.getGroupAdmin(GROUPS.SOME)).to.be.bignumber.equal(GROUPS.SOME_ADMIN);
  255. const { receipt } = await this.manager.setGroupAdmin(GROUPS.SOME, GROUPS.ADMIN, { from: admin });
  256. expectEvent(receipt, 'GroupAdminChanged', { groupId: GROUPS.SOME, admin: GROUPS.ADMIN });
  257. expect(await this.manager.getGroupAdmin(GROUPS.SOME)).to.be.bignumber.equal(GROUPS.ADMIN);
  258. });
  259. it("seeting a group's admin is restricted", async function () {
  260. await expectRevertCustomError(
  261. this.manager.setGroupAdmin(GROUPS.SOME, GROUPS.SOME, { from: manager }),
  262. 'AccessControlUnauthorizedAccount',
  263. [manager, GROUPS.ADMIN],
  264. );
  265. });
  266. });
  267. describe('change group guardian', function () {
  268. it("admin can set any group's admin", async function () {
  269. expect(await this.manager.getGroupGuardian(GROUPS.SOME)).to.be.bignumber.equal(GROUPS.SOME_ADMIN);
  270. const { receipt } = await this.manager.setGroupGuardian(GROUPS.SOME, GROUPS.ADMIN, { from: admin });
  271. expectEvent(receipt, 'GroupGuardianChanged', { groupId: GROUPS.SOME, guardian: GROUPS.ADMIN });
  272. expect(await this.manager.getGroupGuardian(GROUPS.SOME)).to.be.bignumber.equal(GROUPS.ADMIN);
  273. });
  274. it("setting a group's admin is restricted", async function () {
  275. await expectRevertCustomError(
  276. this.manager.setGroupGuardian(GROUPS.SOME, GROUPS.SOME, { from: other }),
  277. 'AccessControlUnauthorizedAccount',
  278. [other, GROUPS.ADMIN],
  279. );
  280. });
  281. });
  282. describe('change execution delay', function () {
  283. it('increassing the delay has immediate effect', async function () {
  284. const oldDelay = web3.utils.toBN(10);
  285. const newDelay = web3.utils.toBN(100);
  286. const { receipt: receipt1 } = await this.manager.$_setExecuteDelay(GROUPS.SOME, member, oldDelay);
  287. const timestamp1 = await clockFromReceipt.timestamp(receipt1).then(web3.utils.toBN);
  288. const delayBefore = await this.manager.getAccess(GROUPS.SOME, member).then(([, delay]) => split(delay));
  289. expect(delayBefore.oldValue).to.be.bignumber.equal('0');
  290. expect(delayBefore.newValue).to.be.bignumber.equal(oldDelay);
  291. expect(delayBefore.effect).to.be.bignumber.equal(timestamp1);
  292. const { receipt: receipt2 } = await this.manager.setExecuteDelay(GROUPS.SOME, member, newDelay, {
  293. from: manager,
  294. });
  295. const timestamp2 = await clockFromReceipt.timestamp(receipt2).then(web3.utils.toBN);
  296. expectEvent(receipt2, 'GroupExecutionDelayUpdate', {
  297. groupId: GROUPS.SOME,
  298. account: member,
  299. delay: newDelay,
  300. from: timestamp2,
  301. });
  302. // immediate effect
  303. const delayAfter = await this.manager.getAccess(GROUPS.SOME, member).then(([, delay]) => split(delay));
  304. expect(delayAfter.oldValue).to.be.bignumber.equal(oldDelay);
  305. expect(delayAfter.newValue).to.be.bignumber.equal(newDelay);
  306. expect(delayAfter.effect).to.be.bignumber.equal(timestamp2);
  307. });
  308. it('decreassing the delay takes time', async function () {
  309. const oldDelay = web3.utils.toBN(100);
  310. const newDelay = web3.utils.toBN(10);
  311. const { receipt: receipt1 } = await this.manager.$_setExecuteDelay(GROUPS.SOME, member, oldDelay);
  312. const timestamp1 = await clockFromReceipt.timestamp(receipt1).then(web3.utils.toBN);
  313. const delayBefore = await this.manager.getAccess(GROUPS.SOME, member).then(([, delay]) => split(delay));
  314. expect(delayBefore.oldValue).to.be.bignumber.equal('0');
  315. expect(delayBefore.newValue).to.be.bignumber.equal(oldDelay);
  316. expect(delayBefore.effect).to.be.bignumber.equal(timestamp1);
  317. const { receipt: receipt2 } = await this.manager.setExecuteDelay(GROUPS.SOME, member, newDelay, {
  318. from: manager,
  319. });
  320. const timestamp2 = await clockFromReceipt.timestamp(receipt2).then(web3.utils.toBN);
  321. expectEvent(receipt2, 'GroupExecutionDelayUpdate', {
  322. groupId: GROUPS.SOME,
  323. account: member,
  324. delay: newDelay,
  325. from: timestamp2.add(oldDelay).sub(newDelay),
  326. });
  327. // delayed effect
  328. const delayAfter = await this.manager.getAccess(GROUPS.SOME, member).then(([, delay]) => split(delay));
  329. expect(delayAfter.oldValue).to.be.bignumber.equal(oldDelay);
  330. expect(delayAfter.newValue).to.be.bignumber.equal(newDelay);
  331. expect(delayAfter.effect).to.be.bignumber.equal(timestamp2.add(oldDelay).sub(newDelay));
  332. });
  333. it('cannot set the delay of a non member', async function () {
  334. await expectRevertCustomError(
  335. this.manager.setExecuteDelay(GROUPS.SOME, other, executeDelay, { from: manager }),
  336. 'AccessManagerAcountNotInGroup',
  337. [GROUPS.SOME, other],
  338. );
  339. });
  340. it('can set a user execution delay during the grant delay', async function () {
  341. await this.manager.$_grantGroup(GROUPS.SOME, other, 10, 0);
  342. const { receipt } = await this.manager.setExecuteDelay(GROUPS.SOME, other, executeDelay, { from: manager });
  343. const timestamp = await clockFromReceipt.timestamp(receipt).then(web3.utils.toBN);
  344. expectEvent(receipt, 'GroupExecutionDelayUpdate', {
  345. groupId: GROUPS.SOME,
  346. account: other,
  347. delay: executeDelay,
  348. from: timestamp,
  349. });
  350. });
  351. it('changing the execution delay is restricted', async function () {
  352. await expectRevertCustomError(
  353. this.manager.setExecuteDelay(GROUPS.SOME, member, executeDelay, { from: other }),
  354. 'AccessControlUnauthorizedAccount',
  355. [GROUPS.SOME_ADMIN, other],
  356. );
  357. });
  358. });
  359. describe('change grant delay', function () {
  360. it('increassing the delay has immediate effect', async function () {
  361. const oldDelay = web3.utils.toBN(10);
  362. const newDelay = web3.utils.toBN(100);
  363. await this.manager.$_setGrantDelay(GROUPS.SOME, oldDelay);
  364. expect(await this.manager.getGroupGrantDelay(GROUPS.SOME)).to.be.bignumber.equal(oldDelay);
  365. const { receipt } = await this.manager.setGrantDelay(GROUPS.SOME, newDelay, { from: admin });
  366. const timestamp = await clockFromReceipt.timestamp(receipt).then(web3.utils.toBN);
  367. expectEvent(receipt, 'GroupGrantDelayChanged', { groupId: GROUPS.SOME, delay: newDelay, from: timestamp });
  368. expect(await this.manager.getGroupGrantDelay(GROUPS.SOME)).to.be.bignumber.equal(newDelay);
  369. });
  370. it('increassing the delay has delay effect', async function () {
  371. const oldDelay = web3.utils.toBN(100);
  372. const newDelay = web3.utils.toBN(10);
  373. await this.manager.$_setGrantDelay(GROUPS.SOME, oldDelay);
  374. expect(await this.manager.getGroupGrantDelay(GROUPS.SOME)).to.be.bignumber.equal(oldDelay);
  375. const { receipt } = await this.manager.setGrantDelay(GROUPS.SOME, newDelay, { from: admin });
  376. const timestamp = await clockFromReceipt.timestamp(receipt).then(web3.utils.toBN);
  377. expectEvent(receipt, 'GroupGrantDelayChanged', {
  378. groupId: GROUPS.SOME,
  379. delay: newDelay,
  380. from: timestamp.add(oldDelay).sub(newDelay),
  381. });
  382. expect(await this.manager.getGroupGrantDelay(GROUPS.SOME)).to.be.bignumber.equal(oldDelay);
  383. await time.increase(oldDelay.sub(newDelay));
  384. expect(await this.manager.getGroupGrantDelay(GROUPS.SOME)).to.be.bignumber.equal(newDelay);
  385. });
  386. it('changing the grant delay is restricted', async function () {
  387. await expectRevertCustomError(
  388. this.manager.setGrantDelay(GROUPS.SOME, grantDelay, { from: other }),
  389. 'AccessControlUnauthorizedAccount',
  390. [GROUPS.ADMIN, other],
  391. );
  392. });
  393. });
  394. });
  395. describe('Mode management', function () {
  396. for (const [modeName, mode] of Object.entries(AccessMode)) {
  397. describe(`setContractMode${modeName}`, function () {
  398. it('set the mode and emits an event', async function () {
  399. // set the target to another mode, so we can check the effects
  400. await this.manager.$_setContractMode(
  401. this.target.address,
  402. Object.values(AccessMode).find(m => m != mode),
  403. );
  404. expect(await this.manager.getContractMode(this.target.address)).to.not.be.bignumber.equal(mode);
  405. expectEvent(
  406. await this.manager[`setContractMode${modeName}`](this.target.address, { from: admin }),
  407. 'AccessModeUpdated',
  408. { target: this.target.address, mode },
  409. );
  410. expect(await this.manager.getContractMode(this.target.address)).to.be.bignumber.equal(mode);
  411. });
  412. it('is restricted', async function () {
  413. await expectRevertCustomError(
  414. this.manager[`setContractMode${modeName}`](this.target.address, { from: other }),
  415. 'AccessControlUnauthorizedAccount',
  416. [other, GROUPS.ADMIN],
  417. );
  418. });
  419. });
  420. }
  421. });
  422. describe('Change function permissions', function () {
  423. const sigs = ['someFunction()', 'someOtherFunction(uint256)', 'oneMoreFunction(address,uint8)'].map(selector);
  424. it('admin can set function allowed group', async function () {
  425. for (const sig of sigs) {
  426. expect(await this.manager.getFunctionAllowedGroup(this.target.address, sig)).to.be.bignumber.equal(
  427. GROUPS.ADMIN,
  428. );
  429. }
  430. const { receipt: receipt1 } = await this.manager.setFunctionAllowedGroup(this.target.address, sigs, GROUPS.SOME, {
  431. from: admin,
  432. });
  433. for (const sig of sigs) {
  434. expectEvent(receipt1, 'FunctionAllowedGroupUpdated', {
  435. target: this.target.address,
  436. selector: sig,
  437. groupId: GROUPS.SOME,
  438. });
  439. expect(await this.manager.getFunctionAllowedGroup(this.target.address, sig)).to.be.bignumber.equal(GROUPS.SOME);
  440. }
  441. const { receipt: receipt2 } = await this.manager.setFunctionAllowedGroup(
  442. this.target.address,
  443. [sigs[1]],
  444. GROUPS.SOME_ADMIN,
  445. { from: admin },
  446. );
  447. expectEvent(receipt2, 'FunctionAllowedGroupUpdated', {
  448. target: this.target.address,
  449. selector: sigs[1],
  450. groupId: GROUPS.SOME_ADMIN,
  451. });
  452. for (const sig of sigs) {
  453. expect(await this.manager.getFunctionAllowedGroup(this.target.address, sig)).to.be.bignumber.equal(
  454. sig == sigs[1] ? GROUPS.SOME_ADMIN : GROUPS.SOME,
  455. );
  456. }
  457. });
  458. it('changing function permissions is restricted', async function () {
  459. await expectRevertCustomError(
  460. this.manager.setFunctionAllowedGroup(this.target.address, sigs, GROUPS.SOME, { from: other }),
  461. 'AccessControlUnauthorizedAccount',
  462. [other, GROUPS.ADMIN],
  463. );
  464. });
  465. });
  466. describe('Calling restricted & unrestricted functions', function () {
  467. const product = (...arrays) => arrays.reduce((a, b) => a.flatMap(ai => b.map(bi => [ai, bi].flat())));
  468. for (const [callerOpt, targetOpt] of product(
  469. [
  470. { groups: [] },
  471. { groups: [GROUPS.SOME] },
  472. { groups: [GROUPS.SOME], delay: executeDelay },
  473. { groups: [GROUPS.SOME, GROUPS.PUBLIC], delay: executeDelay },
  474. ],
  475. [
  476. { mode: AccessMode.Open },
  477. { mode: AccessMode.Closed },
  478. { mode: AccessMode.Custom, group: GROUPS.ADMIN },
  479. { mode: AccessMode.Custom, group: GROUPS.SOME },
  480. { mode: AccessMode.Custom, group: GROUPS.PUBLIC },
  481. ],
  482. )) {
  483. const public =
  484. targetOpt.mode == AccessMode.Open || (targetOpt.mode == AccessMode.Custom && targetOpt.group == GROUPS.PUBLIC);
  485. // can we call with a delay ?
  486. const indirectSuccess =
  487. public || (targetOpt.mode == AccessMode.Custom && callerOpt.groups?.includes(targetOpt.group));
  488. // can we call without a delay ?
  489. const directSuccess =
  490. public ||
  491. (targetOpt.mode == AccessMode.Custom && callerOpt.groups?.includes(targetOpt.group) && !callerOpt.delay);
  492. const description = [
  493. 'Caller in groups',
  494. '[' + (callerOpt.groups ?? []).map(groupId => GROUPS[groupId]).join(', ') + ']',
  495. callerOpt.delay ? 'with a delay' : 'without a delay',
  496. '+',
  497. 'contract in mode',
  498. Object.keys(AccessMode)[targetOpt.mode.toNumber()],
  499. targetOpt.mode == AccessMode.Custom ? `(${GROUPS[targetOpt.group]})` : '',
  500. ].join(' ');
  501. describe(description, function () {
  502. beforeEach(async function () {
  503. // setup
  504. await Promise.all([
  505. this.manager.$_setContractMode(this.target.address, targetOpt.mode),
  506. targetOpt.group &&
  507. this.manager.$_setFunctionAllowedGroup(this.target.address, selector('fnRestricted()'), targetOpt.group),
  508. targetOpt.group &&
  509. this.manager.$_setFunctionAllowedGroup(
  510. this.target.address,
  511. selector('fnUnrestricted()'),
  512. targetOpt.group,
  513. ),
  514. ...(callerOpt.groups ?? [])
  515. .filter(groupId => groupId != GROUPS.PUBLIC)
  516. .map(groupId => this.manager.$_grantGroup(groupId, user, 0, callerOpt.delay ?? 0)),
  517. ]);
  518. // post setup checks
  519. expect(await this.manager.getContractMode(this.target.address)).to.be.bignumber.equal(targetOpt.mode);
  520. if (targetOpt.group) {
  521. expect(
  522. await this.manager.getFunctionAllowedGroup(this.target.address, selector('fnRestricted()')),
  523. ).to.be.bignumber.equal(targetOpt.group);
  524. expect(
  525. await this.manager.getFunctionAllowedGroup(this.target.address, selector('fnUnrestricted()')),
  526. ).to.be.bignumber.equal(targetOpt.group);
  527. }
  528. for (const groupId of callerOpt.groups ?? []) {
  529. const access = await this.manager.getAccess(groupId, user);
  530. if (groupId == GROUPS.PUBLIC) {
  531. expect(access.since).to.be.bignumber.eq('0');
  532. expect(access.delay).to.be.bignumber.eq('0');
  533. } else {
  534. expect(access.since).to.be.bignumber.gt('0');
  535. expect(access.delay).to.be.bignumber.eq(String(callerOpt.delay ?? 0));
  536. }
  537. }
  538. });
  539. it('canCall', async function () {
  540. const result = await this.manager.canCall(user, this.target.address, selector('fnRestricted()'));
  541. expect(result[0]).to.be.equal(directSuccess);
  542. expect(result[1]).to.be.bignumber.equal(!directSuccess && indirectSuccess ? callerOpt.delay ?? '0' : '0');
  543. });
  544. it('Calling a non restricted function never revert', async function () {
  545. expectEvent(await this.target.fnUnrestricted({ from: user }), 'CalledUnrestricted', {
  546. caller: user,
  547. });
  548. });
  549. it(`Calling a restricted function directly should ${directSuccess ? 'succeed' : 'revert'}`, async function () {
  550. const promise = this.target.fnRestricted({ from: user });
  551. if (directSuccess) {
  552. expectEvent(await promise, 'CalledRestricted', { caller: user });
  553. } else {
  554. await expectRevertCustomError(promise, 'AccessManagedUnauthorized', [user]);
  555. }
  556. });
  557. it('Calling indirectly: only relay', async function () {
  558. // relay without schedule
  559. if (directSuccess) {
  560. const { receipt, tx } = await this.relay();
  561. expectEvent.notEmitted(receipt, 'Executed', { operationId: this.opId });
  562. expectEvent.inTransaction(tx, this.target, 'Calledrestricted', { caller: this.manager.address });
  563. } else if (indirectSuccess) {
  564. await expectRevertCustomError(this.relay(), 'AccessManagerNotScheduled', [this.opId]);
  565. } else {
  566. await expectRevertCustomError(this.relay(), 'AccessManagerUnauthorizedCall', [user, ...this.call]);
  567. }
  568. });
  569. it('Calling indirectly: schedule and relay', async function () {
  570. if (directSuccess || indirectSuccess) {
  571. const { receipt } = await this.schedule();
  572. const timestamp = await clockFromReceipt.timestamp(receipt).then(web3.utils.toBN);
  573. expectEvent(receipt, 'Scheduled', {
  574. operationId: this.opId,
  575. caller: user,
  576. target: this.call[0],
  577. data: this.call[1],
  578. });
  579. // if can call directly, delay should be 0. Otherwize, the delay should be applied
  580. expect(await this.manager.getSchedule(this.opId)).to.be.bignumber.equal(
  581. timestamp.add(directSuccess ? web3.utils.toBN(0) : callerOpt.delay),
  582. );
  583. // execute without wait
  584. if (directSuccess) {
  585. const { receipt, tx } = await this.relay();
  586. expectEvent(receipt, 'Executed', { operationId: this.opId });
  587. expectEvent.inTransaction(tx, this.target, 'Calledrestricted', { caller: this.manager.address });
  588. expect(await this.manager.getSchedule(this.opId)).to.be.bignumber.equal('0');
  589. } else if (indirectSuccess) {
  590. await expectRevertCustomError(this.relay(), 'AccessManagerNotReady', [this.opId]);
  591. } else {
  592. await expectRevertCustomError(this.relay(), 'AccessManagerUnauthorizedCall', [user, ...this.call]);
  593. }
  594. } else {
  595. await expectRevertCustomError(this.schedule(), 'AccessManagerUnauthorizedCall', [user, ...this.call]);
  596. }
  597. });
  598. it('Calling indirectly: schedule wait and relay', async function () {
  599. if (directSuccess || indirectSuccess) {
  600. const { receipt } = await this.schedule();
  601. const timestamp = await clockFromReceipt.timestamp(receipt).then(web3.utils.toBN);
  602. expectEvent(receipt, 'Scheduled', {
  603. operationId: this.opId,
  604. caller: user,
  605. target: this.call[0],
  606. data: this.call[1],
  607. });
  608. // if can call directly, delay should be 0. Otherwize, the delay should be applied
  609. expect(await this.manager.getSchedule(this.opId)).to.be.bignumber.equal(
  610. timestamp.add(directSuccess ? web3.utils.toBN(0) : callerOpt.delay),
  611. );
  612. // wait
  613. await time.increase(callerOpt.delay ?? 0);
  614. // execute without wait
  615. if (directSuccess || indirectSuccess) {
  616. const { receipt, tx } = await this.relay();
  617. expectEvent(receipt, 'Executed', { operationId: this.opId });
  618. expectEvent.inTransaction(tx, this.target, 'Calledrestricted', { caller: this.manager.address });
  619. expect(await this.manager.getSchedule(this.opId)).to.be.bignumber.equal('0');
  620. } else {
  621. await expectRevertCustomError(this.relay(), 'AccessManagerUnauthorizedCall', [user, ...this.call]);
  622. }
  623. } else {
  624. await expectRevertCustomError(this.schedule(), 'AccessManagerUnauthorizedCall', [user, ...this.call]);
  625. }
  626. });
  627. });
  628. }
  629. });
  630. describe('Indirect execution corner-cases', async function () {
  631. beforeEach(async function () {
  632. await this.manager.$_setFunctionAllowedGroup(...this.call, GROUPS.SOME);
  633. await this.manager.$_grantGroup(GROUPS.SOME, user, 0, executeDelay);
  634. });
  635. it('Checking canCall when caller is the manager depend on the _relayIdentifier', async function () {
  636. expect(await this.manager.getContractMode(this.target.address)).to.be.bignumber.equal(AccessMode.Custom);
  637. const result = await this.manager.canCall(this.manager.address, this.target.address, '0x00000000');
  638. expect(result[0]).to.be.false;
  639. expect(result[1]).to.be.bignumber.equal('0');
  640. });
  641. it('Cannot execute earlier', async function () {
  642. const { receipt } = await this.schedule();
  643. const timestamp = await clockFromReceipt.timestamp(receipt).then(web3.utils.toBN);
  644. expect(await this.manager.getSchedule(this.opId)).to.be.bignumber.equal(timestamp.add(executeDelay));
  645. // we need to set the clock 2 seconds before the value, because the increaseTo "consumes" the timestamp
  646. // and the next transaction will be one after that (see check bellow)
  647. await time.increaseTo(timestamp.add(executeDelay).subn(2));
  648. // too early
  649. await expectRevertCustomError(this.relay(), 'AccessManagerNotReady', [this.opId]);
  650. // the revert happened one second before the execution delay expired
  651. expect(await time.latest()).to.be.bignumber.equal(timestamp.add(executeDelay).subn(1));
  652. // ok
  653. await this.relay();
  654. // the success happened when the delay was reached (earliest possible)
  655. expect(await time.latest()).to.be.bignumber.equal(timestamp.add(executeDelay));
  656. });
  657. it('Cannot schedule an already scheduled operation', async function () {
  658. const { receipt } = await this.schedule();
  659. expectEvent(receipt, 'Scheduled', {
  660. operationId: this.opId,
  661. caller: user,
  662. target: this.call[0],
  663. data: this.call[1],
  664. });
  665. await expectRevertCustomError(this.schedule(), 'AccessManagerAlreadyScheduled', [this.opId]);
  666. });
  667. it('Cannot cancel an operation that is not scheduled', async function () {
  668. await expectRevertCustomError(this.cancel(), 'AccessManagerNotScheduled', [this.opId]);
  669. });
  670. it('Cannot cancel an operation that is not already relayed', async function () {
  671. await this.schedule();
  672. await time.increase(executeDelay);
  673. await this.relay();
  674. await expectRevertCustomError(this.cancel(), 'AccessManagerNotScheduled', [this.opId]);
  675. });
  676. it('Scheduler can cancel', async function () {
  677. await this.schedule();
  678. expect(await this.manager.getSchedule(this.opId)).to.not.be.bignumber.equal('0');
  679. expectEvent(await this.cancel({ from: manager }), 'Canceled', { operationId: this.opId });
  680. expect(await this.manager.getSchedule(this.opId)).to.be.bignumber.equal('0');
  681. });
  682. it('Guardian can cancel', async function () {
  683. await this.schedule();
  684. expect(await this.manager.getSchedule(this.opId)).to.not.be.bignumber.equal('0');
  685. expectEvent(await this.cancel({ from: manager }), 'Canceled', { operationId: this.opId });
  686. expect(await this.manager.getSchedule(this.opId)).to.be.bignumber.equal('0');
  687. });
  688. it('Cancel is restricted', async function () {
  689. await this.schedule();
  690. expect(await this.manager.getSchedule(this.opId)).to.not.be.bignumber.equal('0');
  691. await expectRevertCustomError(this.cancel({ from: other }), 'AccessManagerCannotCancel', [
  692. other,
  693. user,
  694. ...this.call,
  695. ]);
  696. expect(await this.manager.getSchedule(this.opId)).to.not.be.bignumber.equal('0');
  697. });
  698. it('Can re-schedule after execution', async function () {
  699. await this.schedule();
  700. await time.increase(executeDelay);
  701. await this.relay();
  702. // reschedule
  703. const { receipt } = await this.schedule();
  704. expectEvent(receipt, 'Scheduled', {
  705. operationId: this.opId,
  706. caller: user,
  707. target: this.call[0],
  708. data: this.call[1],
  709. });
  710. });
  711. it('Can re-schedule after cancel', async function () {
  712. await this.schedule();
  713. await this.cancel();
  714. // reschedule
  715. const { receipt } = await this.schedule();
  716. expectEvent(receipt, 'Scheduled', {
  717. operationId: this.opId,
  718. caller: user,
  719. target: this.call[0],
  720. data: this.call[1],
  721. });
  722. });
  723. });
  724. describe('authority update', function () {
  725. beforeEach(async function () {
  726. this.newManager = await AccessManager.new(admin);
  727. });
  728. it('admin can change authority', async function () {
  729. expect(await this.target.authority()).to.be.equal(this.manager.address);
  730. const { tx } = await this.manager.updateAuthority(this.target.address, this.newManager.address, { from: admin });
  731. expectEvent.inTransaction(tx, this.target, 'AuthorityUpdated', { authority: this.newManager.address });
  732. expect(await this.target.authority()).to.be.equal(this.newManager.address);
  733. });
  734. it('cannot set an address without code as the authority', async function () {
  735. await expectRevertCustomError(
  736. this.manager.updateAuthority(this.target.address, user, { from: admin }),
  737. 'AccessManagedInvalidAuthority',
  738. [user],
  739. );
  740. });
  741. it('updateAuthority is restricted on manager', async function () {
  742. await expectRevertCustomError(
  743. this.manager.updateAuthority(this.target.address, this.newManager.address, { from: other }),
  744. 'AccessControlUnauthorizedAccount',
  745. [other, GROUPS.ADMIN],
  746. );
  747. });
  748. it('setAuthority is restricted on AccessManaged', async function () {
  749. await expectRevertCustomError(
  750. this.target.setAuthority(this.newManager.address, { from: admin }),
  751. 'AccessManagedUnauthorized',
  752. [admin],
  753. );
  754. });
  755. });
  756. });