AccessManager.test.js 50 KB

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