AccessManager.test.js 48 KB

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