AccessManager.behavior.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711
  1. const { time } = require('@openzeppelin/test-helpers');
  2. const {
  3. time: { setNextBlockTimestamp },
  4. setStorageAt,
  5. mine,
  6. } = require('@nomicfoundation/hardhat-network-helpers');
  7. const { impersonate } = require('../../helpers/account');
  8. const { expectRevertCustomError } = require('../../helpers/customError');
  9. const { EXPIRATION, EXECUTION_ID_STORAGE_SLOT } = require('../../helpers/access-manager');
  10. // ============ COMMON PATHS ============
  11. const COMMON_IS_EXECUTING_PATH = {
  12. executing() {
  13. it('succeeds', async function () {
  14. await web3.eth.sendTransaction({ to: this.target.address, data: this.calldata, from: this.caller });
  15. });
  16. },
  17. notExecuting() {
  18. it('reverts as AccessManagerUnauthorizedAccount', async function () {
  19. await expectRevertCustomError(
  20. web3.eth.sendTransaction({ to: this.target.address, data: this.calldata, from: this.caller }),
  21. 'AccessManagerUnauthorizedAccount',
  22. [this.caller, this.role.id],
  23. );
  24. });
  25. },
  26. };
  27. const COMMON_GET_ACCESS_PATH = {
  28. requiredRoleIsGranted: {
  29. roleGrantingIsDelayed: {
  30. callerHasAnExecutionDelay: {
  31. beforeGrantDelay() {
  32. it('reverts as AccessManagerUnauthorizedAccount', async function () {
  33. await expectRevertCustomError(
  34. web3.eth.sendTransaction({ to: this.target.address, data: this.calldata, from: this.caller }),
  35. 'AccessManagerUnauthorizedAccount',
  36. [this.caller, this.role.id],
  37. );
  38. });
  39. },
  40. afterGrantDelay: undefined, // Diverges if there's an operation delay or not
  41. },
  42. callerHasNoExecutionDelay: {
  43. beforeGrantDelay() {
  44. it('reverts as AccessManagerUnauthorizedAccount', async function () {
  45. await expectRevertCustomError(
  46. web3.eth.sendTransaction({ to: this.target.address, data: this.calldata, from: this.caller }),
  47. 'AccessManagerUnauthorizedAccount',
  48. [this.caller, this.role.id],
  49. );
  50. });
  51. },
  52. afterGrantDelay() {
  53. it('succeeds called directly', async function () {
  54. await web3.eth.sendTransaction({ to: this.target.address, data: this.calldata, from: this.caller });
  55. });
  56. it('succeeds via execute', async function () {
  57. await this.manager.execute(this.target.address, this.calldata, { from: this.caller });
  58. });
  59. },
  60. },
  61. },
  62. roleGrantingIsNotDelayed: {
  63. callerHasAnExecutionDelay: undefined, // Diverges if there's an operation to schedule or not
  64. callerHasNoExecutionDelay() {
  65. it('succeeds called directly', async function () {
  66. await web3.eth.sendTransaction({ to: this.target.address, data: this.calldata, from: this.caller });
  67. });
  68. it('succeeds via execute', async function () {
  69. await this.manager.execute(this.target.address, this.calldata, { from: this.caller });
  70. });
  71. },
  72. },
  73. },
  74. requiredRoleIsNotGranted() {
  75. it('reverts as AccessManagerUnauthorizedAccount', async function () {
  76. await expectRevertCustomError(
  77. web3.eth.sendTransaction({ to: this.target.address, data: this.calldata, from: this.caller }),
  78. 'AccessManagerUnauthorizedAccount',
  79. [this.caller, this.role.id],
  80. );
  81. });
  82. },
  83. };
  84. const COMMON_SCHEDULABLE_PATH = {
  85. scheduled: {
  86. before() {
  87. it('reverts as AccessManagerNotReady', async function () {
  88. await expectRevertCustomError(
  89. web3.eth.sendTransaction({ to: this.target.address, data: this.calldata, from: this.caller }),
  90. 'AccessManagerNotReady',
  91. [this.operationId],
  92. );
  93. });
  94. },
  95. after() {
  96. it('succeeds called directly', async function () {
  97. await web3.eth.sendTransaction({ to: this.target.address, data: this.calldata, from: this.caller });
  98. });
  99. it('succeeds via execute', async function () {
  100. await this.manager.execute(this.target.address, this.calldata, { from: this.caller });
  101. });
  102. },
  103. expired() {
  104. it('reverts as AccessManagerExpired', async function () {
  105. await expectRevertCustomError(
  106. web3.eth.sendTransaction({ to: this.target.address, data: this.calldata, from: this.caller }),
  107. 'AccessManagerExpired',
  108. [this.operationId],
  109. );
  110. });
  111. },
  112. },
  113. notScheduled() {
  114. it('reverts as AccessManagerNotScheduled', async function () {
  115. await expectRevertCustomError(
  116. web3.eth.sendTransaction({ to: this.target.address, data: this.calldata, from: this.caller }),
  117. 'AccessManagerNotScheduled',
  118. [this.operationId],
  119. );
  120. });
  121. },
  122. };
  123. const COMMON_SCHEDULABLE_PATH_IF_ZERO_DELAY = {
  124. scheduled: {
  125. before() {
  126. it.skip('is not reachable without a delay');
  127. },
  128. after() {
  129. it.skip('is not reachable without a delay');
  130. },
  131. expired() {
  132. it.skip('is not reachable without a delay');
  133. },
  134. },
  135. notScheduled() {
  136. it('succeeds', async function () {
  137. await this.manager.execute(this.target.address, this.calldata, { from: this.caller });
  138. });
  139. },
  140. };
  141. // ============ MODE HELPERS ============
  142. /**
  143. * @requires this.{manager,target}
  144. */
  145. function shouldBehaveLikeClosable({ closed, open }) {
  146. describe('when the manager is closed', function () {
  147. beforeEach('close', async function () {
  148. await this.manager.$_setTargetClosed(this.target.address, true);
  149. });
  150. closed();
  151. });
  152. describe('when the manager is open', function () {
  153. beforeEach('open', async function () {
  154. await this.manager.$_setTargetClosed(this.target.address, false);
  155. });
  156. open();
  157. });
  158. }
  159. // ============ DELAY HELPERS ============
  160. /**
  161. * @requires this.{delay}
  162. */
  163. function shouldBehaveLikeDelay(type, { before, after }) {
  164. beforeEach('define timestamp when delay takes effect', async function () {
  165. const timestamp = await time.latest();
  166. this.delayEffect = timestamp.add(this.delay);
  167. });
  168. describe(`when ${type} delay has not taken effect yet`, function () {
  169. beforeEach(`set next block timestamp before ${type} takes effect`, async function () {
  170. await setNextBlockTimestamp(this.delayEffect.subn(1));
  171. });
  172. before();
  173. });
  174. describe(`when ${type} delay has taken effect`, function () {
  175. beforeEach(`set next block timestamp when ${type} takes effect`, async function () {
  176. await setNextBlockTimestamp(this.delayEffect);
  177. });
  178. after();
  179. });
  180. }
  181. // ============ OPERATION HELPERS ============
  182. /**
  183. * @requires this.{manager,scheduleIn,caller,target,calldata}
  184. */
  185. function shouldBehaveLikeSchedulableOperation({ scheduled: { before, after, expired }, notScheduled }) {
  186. describe('when operation is scheduled', function () {
  187. beforeEach('schedule operation', async function () {
  188. await impersonate(this.caller); // May be a contract
  189. const { operationId } = await scheduleOperation(this.manager, {
  190. caller: this.caller,
  191. target: this.target.address,
  192. calldata: this.calldata,
  193. delay: this.scheduleIn,
  194. });
  195. this.operationId = operationId;
  196. });
  197. describe('when operation is not ready for execution', function () {
  198. beforeEach('set next block time before operation is ready', async function () {
  199. this.scheduledAt = await time.latest();
  200. const schedule = await this.manager.getSchedule(this.operationId);
  201. await setNextBlockTimestamp(schedule.subn(1));
  202. });
  203. before();
  204. });
  205. describe('when operation is ready for execution', function () {
  206. beforeEach('set next block time when operation is ready for execution', async function () {
  207. this.scheduledAt = await time.latest();
  208. const schedule = await this.manager.getSchedule(this.operationId);
  209. await setNextBlockTimestamp(schedule);
  210. });
  211. after();
  212. });
  213. describe('when operation has expired', function () {
  214. beforeEach('set next block time when operation expired', async function () {
  215. this.scheduledAt = await time.latest();
  216. const schedule = await this.manager.getSchedule(this.operationId);
  217. await setNextBlockTimestamp(schedule.add(EXPIRATION));
  218. });
  219. expired();
  220. });
  221. });
  222. describe('when operation is not scheduled', function () {
  223. beforeEach('set expected operationId', async function () {
  224. this.operationId = await this.manager.hashOperation(this.caller, this.target.address, this.calldata);
  225. // Assert operation is not scheduled
  226. expect(await this.manager.getSchedule(this.operationId)).to.be.bignumber.equal(web3.utils.toBN(0));
  227. });
  228. notScheduled();
  229. });
  230. }
  231. /**
  232. * @requires this.{manager,roles,target,calldata}
  233. */
  234. function shouldBehaveLikeARestrictedOperation({ callerIsNotTheManager, callerIsTheManager }) {
  235. describe('when the call comes from the manager (msg.sender == manager)', function () {
  236. beforeEach('define caller as manager', async function () {
  237. this.caller = this.manager.address;
  238. await impersonate(this.caller);
  239. });
  240. shouldBehaveLikeCanCallExecuting(callerIsTheManager);
  241. });
  242. describe('when the call does not come from the manager (msg.sender != manager)', function () {
  243. beforeEach('define non manager caller', function () {
  244. this.caller = this.roles.SOME.members[0];
  245. });
  246. callerIsNotTheManager();
  247. });
  248. }
  249. /**
  250. * @requires this.{manager,roles,executionDelay,operationDelay,target}
  251. */
  252. function shouldBehaveLikeDelayedOperation() {
  253. describe('with operation delay', function () {
  254. describe('when operation delay is greater than execution delay', function () {
  255. beforeEach('set operation delay', async function () {
  256. this.operationDelay = this.executionDelay.add(time.duration.hours(1));
  257. await this.manager.$_setTargetAdminDelay(this.target.address, this.operationDelay);
  258. this.scheduleIn = this.operationDelay; // For shouldBehaveLikeSchedulableOperation
  259. });
  260. shouldBehaveLikeSchedulableOperation(COMMON_SCHEDULABLE_PATH);
  261. });
  262. describe('when operation delay is shorter than execution delay', function () {
  263. beforeEach('set operation delay', async function () {
  264. this.operationDelay = this.executionDelay.sub(time.duration.hours(1));
  265. await this.manager.$_setTargetAdminDelay(this.target.address, this.operationDelay);
  266. this.scheduleIn = this.executionDelay; // For shouldBehaveLikeSchedulableOperation
  267. });
  268. shouldBehaveLikeSchedulableOperation(COMMON_SCHEDULABLE_PATH);
  269. });
  270. });
  271. describe('without operation delay', function () {
  272. beforeEach('set operation delay', async function () {
  273. this.operationDelay = web3.utils.toBN(0);
  274. await this.manager.$_setTargetAdminDelay(this.target.address, this.operationDelay);
  275. this.scheduleIn = this.executionDelay; // For shouldBehaveLikeSchedulableOperation
  276. });
  277. shouldBehaveLikeSchedulableOperation(COMMON_SCHEDULABLE_PATH);
  278. });
  279. }
  280. // ============ METHOD HELPERS ============
  281. /**
  282. * @requires this.{manager,roles,role,target,calldata}
  283. */
  284. function shouldBehaveLikeCanCall({
  285. closed,
  286. open: {
  287. callerIsTheManager,
  288. callerIsNotTheManager: { publicRoleIsRequired, specificRoleIsRequired },
  289. },
  290. }) {
  291. shouldBehaveLikeClosable({
  292. closed,
  293. open() {
  294. shouldBehaveLikeARestrictedOperation({
  295. callerIsTheManager,
  296. callerIsNotTheManager() {
  297. shouldBehaveLikeHasRole({
  298. publicRoleIsRequired,
  299. specificRoleIsRequired,
  300. });
  301. },
  302. });
  303. },
  304. });
  305. }
  306. /**
  307. * @requires this.{target,calldata}
  308. */
  309. function shouldBehaveLikeCanCallExecuting({ executing, notExecuting }) {
  310. describe('when _executionId is in storage for target and selector', function () {
  311. beforeEach('set _executionId flag from calldata and target', async function () {
  312. const executionId = await web3.utils.keccak256(
  313. web3.eth.abi.encodeParameters(['address', 'bytes4'], [this.target.address, this.calldata.substring(0, 10)]),
  314. );
  315. await setStorageAt(this.manager.address, EXECUTION_ID_STORAGE_SLOT, executionId);
  316. });
  317. executing();
  318. });
  319. describe('when _executionId does not match target and selector', notExecuting);
  320. }
  321. /**
  322. * @requires this.{target,calldata,roles,role}
  323. */
  324. function shouldBehaveLikeHasRole({ publicRoleIsRequired, specificRoleIsRequired }) {
  325. describe('when the function requires the caller to be granted with the PUBLIC_ROLE', function () {
  326. beforeEach('set target function role as PUBLIC_ROLE', async function () {
  327. this.role = this.roles.PUBLIC;
  328. await this.manager.$_setTargetFunctionRole(this.target.address, this.calldata.substring(0, 10), this.role.id, {
  329. from: this.roles.ADMIN.members[0],
  330. });
  331. });
  332. publicRoleIsRequired();
  333. });
  334. describe('when the function requires the caller to be granted with a role other than PUBLIC_ROLE', function () {
  335. beforeEach('set target function role as PUBLIC_ROLE', async function () {
  336. await this.manager.$_setTargetFunctionRole(this.target.address, this.calldata.substring(0, 10), this.role.id, {
  337. from: this.roles.ADMIN.members[0],
  338. });
  339. });
  340. shouldBehaveLikeGetAccess(specificRoleIsRequired);
  341. });
  342. }
  343. /**
  344. * @requires this.{manager,role,caller}
  345. */
  346. function shouldBehaveLikeGetAccess({
  347. requiredRoleIsGranted: {
  348. roleGrantingIsDelayed: {
  349. // Because both grant and execution delay are set within the same $_grantRole call
  350. // it's not possible to create a set of tests that diverge between grant and execution delay.
  351. // Therefore, the shouldBehaveLikeDelay arguments are renamed for clarity:
  352. // before => beforeGrantDelay
  353. // after => afterGrantDelay
  354. callerHasAnExecutionDelay: { beforeGrantDelay: case1, afterGrantDelay: case2 },
  355. callerHasNoExecutionDelay: { beforeGrantDelay: case3, afterGrantDelay: case4 },
  356. },
  357. roleGrantingIsNotDelayed: { callerHasAnExecutionDelay: case5, callerHasNoExecutionDelay: case6 },
  358. },
  359. requiredRoleIsNotGranted,
  360. }) {
  361. describe('when the required role is granted to the caller', function () {
  362. describe('when role granting is delayed', function () {
  363. beforeEach('define delay', function () {
  364. this.grantDelay = time.duration.minutes(3);
  365. this.delay = this.grantDelay; // For shouldBehaveLikeDelay
  366. });
  367. describe('when caller has an execution delay', function () {
  368. beforeEach('set role and delay', async function () {
  369. this.executionDelay = time.duration.hours(10);
  370. this.delay = this.grantDelay;
  371. await this.manager.$_grantRole(this.role.id, this.caller, this.grantDelay, this.executionDelay);
  372. });
  373. shouldBehaveLikeDelay('grant', { before: case1, after: case2 });
  374. });
  375. describe('when caller has no execution delay', function () {
  376. beforeEach('set role and delay', async function () {
  377. this.executionDelay = web3.utils.toBN(0);
  378. await this.manager.$_grantRole(this.role.id, this.caller, this.grantDelay, this.executionDelay);
  379. });
  380. shouldBehaveLikeDelay('grant', { before: case3, after: case4 });
  381. });
  382. });
  383. describe('when role granting is not delayed', function () {
  384. beforeEach('define delay', function () {
  385. this.grantDelay = web3.utils.toBN(0);
  386. });
  387. describe('when caller has an execution delay', function () {
  388. beforeEach('set role and delay', async function () {
  389. this.executionDelay = time.duration.hours(10);
  390. await this.manager.$_grantRole(this.role.id, this.caller, this.grantDelay, this.executionDelay);
  391. });
  392. case5();
  393. });
  394. describe('when caller has no execution delay', function () {
  395. beforeEach('set role and delay', async function () {
  396. this.executionDelay = web3.utils.toBN(0);
  397. await this.manager.$_grantRole(this.role.id, this.caller, this.grantDelay, this.executionDelay);
  398. });
  399. case6();
  400. });
  401. });
  402. });
  403. describe('when role is not granted', function () {
  404. // Because this helper can be composed with other helpers, it's possible
  405. // that role has been set already by another helper.
  406. // Although this is highly unlikely, we check for it here to avoid false positives.
  407. beforeEach('assert role is unset', async function () {
  408. const { since } = await this.manager.getAccess(this.role.id, this.caller);
  409. expect(since).to.be.bignumber.equal(web3.utils.toBN(0));
  410. });
  411. requiredRoleIsNotGranted();
  412. });
  413. }
  414. // ============ ADMIN OPERATION HELPERS ============
  415. /**
  416. * @requires this.{manager,roles,calldata,role}
  417. */
  418. function shouldBehaveLikeDelayedAdminOperation() {
  419. const getAccessPath = COMMON_GET_ACCESS_PATH;
  420. getAccessPath.requiredRoleIsGranted.roleGrantingIsDelayed.callerHasAnExecutionDelay.afterGrantDelay = function () {
  421. beforeEach('consume previously set grant delay', async function () {
  422. // Consume previously set delay
  423. await mine();
  424. });
  425. shouldBehaveLikeDelayedOperation();
  426. };
  427. getAccessPath.requiredRoleIsGranted.roleGrantingIsNotDelayed.callerHasAnExecutionDelay = function () {
  428. beforeEach('set execution delay', async function () {
  429. this.scheduleIn = this.executionDelay; // For shouldBehaveLikeDelayedOperation
  430. });
  431. shouldBehaveLikeSchedulableOperation(COMMON_SCHEDULABLE_PATH);
  432. };
  433. beforeEach('set target as manager', function () {
  434. this.target = this.manager;
  435. });
  436. shouldBehaveLikeARestrictedOperation({
  437. callerIsTheManager: COMMON_IS_EXECUTING_PATH,
  438. callerIsNotTheManager() {
  439. shouldBehaveLikeHasRole({
  440. publicRoleIsRequired() {
  441. it('reverts as AccessManagerUnauthorizedAccount', async function () {
  442. await expectRevertCustomError(
  443. web3.eth.sendTransaction({ to: this.target.address, data: this.calldata, from: this.caller }),
  444. 'AccessManagerUnauthorizedAccount',
  445. [
  446. this.caller,
  447. this.roles.ADMIN.id, // Although PUBLIC is required, target function role doesn't apply to admin ops
  448. ],
  449. );
  450. });
  451. },
  452. specificRoleIsRequired: getAccessPath,
  453. });
  454. },
  455. });
  456. }
  457. /**
  458. * @requires this.{manager,roles,calldata,role}
  459. */
  460. function shouldBehaveLikeNotDelayedAdminOperation() {
  461. const getAccessPath = COMMON_GET_ACCESS_PATH;
  462. getAccessPath.requiredRoleIsGranted.roleGrantingIsDelayed.callerHasAnExecutionDelay.afterGrantDelay = function () {
  463. beforeEach('set execution delay', async function () {
  464. await mine();
  465. this.scheduleIn = this.executionDelay; // For shouldBehaveLikeSchedulableOperation
  466. });
  467. shouldBehaveLikeSchedulableOperation(COMMON_SCHEDULABLE_PATH);
  468. };
  469. getAccessPath.requiredRoleIsGranted.roleGrantingIsNotDelayed.callerHasAnExecutionDelay = function () {
  470. beforeEach('set execution delay', async function () {
  471. this.scheduleIn = this.executionDelay; // For shouldBehaveLikeSchedulableOperation
  472. });
  473. shouldBehaveLikeSchedulableOperation(COMMON_SCHEDULABLE_PATH);
  474. };
  475. beforeEach('set target as manager', function () {
  476. this.target = this.manager;
  477. });
  478. shouldBehaveLikeARestrictedOperation({
  479. callerIsTheManager: COMMON_IS_EXECUTING_PATH,
  480. callerIsNotTheManager() {
  481. shouldBehaveLikeHasRole({
  482. publicRoleIsRequired() {
  483. it('reverts as AccessManagerUnauthorizedAccount', async function () {
  484. await expectRevertCustomError(
  485. web3.eth.sendTransaction({ to: this.target.address, data: this.calldata, from: this.caller }),
  486. 'AccessManagerUnauthorizedAccount',
  487. [this.caller, this.roles.ADMIN.id], // Although PUBLIC_ROLE is required, admin ops are not subject to target function roles
  488. );
  489. });
  490. },
  491. specificRoleIsRequired: getAccessPath,
  492. });
  493. },
  494. });
  495. }
  496. /**
  497. * @requires this.{manager,roles,calldata,role}
  498. */
  499. function shouldBehaveLikeRoleAdminOperation(roleAdmin) {
  500. const getAccessPath = COMMON_GET_ACCESS_PATH;
  501. getAccessPath.requiredRoleIsGranted.roleGrantingIsDelayed.callerHasAnExecutionDelay.afterGrantDelay = function () {
  502. beforeEach('set operation delay', async function () {
  503. await mine();
  504. this.scheduleIn = this.executionDelay; // For shouldBehaveLikeSchedulableOperation
  505. });
  506. shouldBehaveLikeSchedulableOperation(COMMON_SCHEDULABLE_PATH);
  507. };
  508. getAccessPath.requiredRoleIsGranted.roleGrantingIsNotDelayed.callerHasAnExecutionDelay = function () {
  509. beforeEach('set execution delay', async function () {
  510. this.scheduleIn = this.executionDelay; // For shouldBehaveLikeSchedulableOperation
  511. });
  512. shouldBehaveLikeSchedulableOperation(COMMON_SCHEDULABLE_PATH);
  513. };
  514. beforeEach('set target as manager', function () {
  515. this.target = this.manager;
  516. });
  517. shouldBehaveLikeARestrictedOperation({
  518. callerIsTheManager: COMMON_IS_EXECUTING_PATH,
  519. callerIsNotTheManager() {
  520. shouldBehaveLikeHasRole({
  521. publicRoleIsRequired() {
  522. it('reverts as AccessManagerUnauthorizedAccount', async function () {
  523. await expectRevertCustomError(
  524. web3.eth.sendTransaction({ to: this.target.address, data: this.calldata, from: this.caller }),
  525. 'AccessManagerUnauthorizedAccount',
  526. [this.caller, roleAdmin], // Role admin ops require the role's admin
  527. );
  528. });
  529. },
  530. specificRoleIsRequired: getAccessPath,
  531. });
  532. },
  533. });
  534. }
  535. // ============ RESTRICTED OPERATION HELPERS ============
  536. /**
  537. * @requires this.{manager,roles,calldata,role}
  538. */
  539. function shouldBehaveLikeAManagedRestrictedOperation() {
  540. function revertUnauthorized() {
  541. it('reverts as AccessManagedUnauthorized', async function () {
  542. await expectRevertCustomError(
  543. web3.eth.sendTransaction({ to: this.target.address, data: this.calldata, from: this.caller }),
  544. 'AccessManagedUnauthorized',
  545. [this.caller],
  546. );
  547. });
  548. }
  549. const getAccessPath = COMMON_GET_ACCESS_PATH;
  550. getAccessPath.requiredRoleIsGranted.roleGrantingIsDelayed.callerHasAnExecutionDelay.beforeGrantDelay =
  551. revertUnauthorized;
  552. getAccessPath.requiredRoleIsGranted.roleGrantingIsDelayed.callerHasNoExecutionDelay.beforeGrantDelay =
  553. revertUnauthorized;
  554. getAccessPath.requiredRoleIsNotGranted = revertUnauthorized;
  555. getAccessPath.requiredRoleIsGranted.roleGrantingIsDelayed.callerHasAnExecutionDelay.afterGrantDelay = function () {
  556. beforeEach('consume previously set grant delay', async function () {
  557. // Consume previously set delay
  558. await mine();
  559. this.scheduleIn = this.executionDelay; // For shouldBehaveLikeSchedulableOperation
  560. });
  561. shouldBehaveLikeSchedulableOperation(COMMON_SCHEDULABLE_PATH);
  562. };
  563. getAccessPath.requiredRoleIsGranted.roleGrantingIsNotDelayed.callerHasAnExecutionDelay = function () {
  564. beforeEach('consume previously set grant delay', async function () {
  565. this.scheduleIn = this.executionDelay; // For shouldBehaveLikeSchedulableOperation
  566. });
  567. shouldBehaveLikeSchedulableOperation(COMMON_SCHEDULABLE_PATH);
  568. };
  569. const isExecutingPath = COMMON_IS_EXECUTING_PATH;
  570. isExecutingPath.notExecuting = revertUnauthorized;
  571. shouldBehaveLikeCanCall({
  572. closed: revertUnauthorized,
  573. open: {
  574. callerIsTheManager: isExecutingPath,
  575. callerIsNotTheManager: {
  576. publicRoleIsRequired() {
  577. it('succeeds called directly', async function () {
  578. await web3.eth.sendTransaction({ to: this.target.address, data: this.calldata, from: this.caller });
  579. });
  580. it('succeeds via execute', async function () {
  581. await this.manager.execute(this.target.address, this.calldata, { from: this.caller });
  582. });
  583. },
  584. specificRoleIsRequired: getAccessPath,
  585. },
  586. },
  587. });
  588. }
  589. // ============ HELPERS ============
  590. /**
  591. * @requires this.{manager, caller, target, calldata}
  592. */
  593. async function scheduleOperation(manager, { caller, target, calldata, delay }) {
  594. const timestamp = await time.latest();
  595. const scheduledAt = timestamp.addn(1);
  596. await setNextBlockTimestamp(scheduledAt); // Fix next block timestamp for predictability
  597. const { receipt } = await manager.schedule(target, calldata, scheduledAt.add(delay), {
  598. from: caller,
  599. });
  600. return {
  601. receipt,
  602. scheduledAt,
  603. operationId: await manager.hashOperation(caller, target, calldata),
  604. };
  605. }
  606. module.exports = {
  607. // COMMON PATHS
  608. COMMON_SCHEDULABLE_PATH,
  609. COMMON_SCHEDULABLE_PATH_IF_ZERO_DELAY,
  610. // MODE HELPERS
  611. shouldBehaveLikeClosable,
  612. // DELAY HELPERS
  613. shouldBehaveLikeDelay,
  614. // OPERATION HELPERS
  615. shouldBehaveLikeSchedulableOperation,
  616. // METHOD HELPERS
  617. shouldBehaveLikeCanCall,
  618. shouldBehaveLikeGetAccess,
  619. shouldBehaveLikeHasRole,
  620. // ADMIN OPERATION HELPERS
  621. shouldBehaveLikeDelayedAdminOperation,
  622. shouldBehaveLikeNotDelayedAdminOperation,
  623. shouldBehaveLikeRoleAdminOperation,
  624. // RESTRICTED OPERATION HELPERS
  625. shouldBehaveLikeAManagedRestrictedOperation,
  626. // HELPERS
  627. scheduleOperation,
  628. };