TimelockController.test.js 45 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279
  1. const { ethers } = require('hardhat');
  2. const { expect } = require('chai');
  3. const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
  4. const { PANIC_CODES } = require('@nomicfoundation/hardhat-chai-matchers/panic');
  5. const { GovernorHelper } = require('../helpers/governance');
  6. const { OperationState } = require('../helpers/enums');
  7. const time = require('../helpers/time');
  8. const { shouldSupportInterfaces } = require('../utils/introspection/SupportsInterface.behavior');
  9. const salt = '0x025e7b0be353a74631ad648c667493c0e1cd31caa4cc2d3520fdc171ea0cc726'; // a random value
  10. const MINDELAY = time.duration.days(1);
  11. const DEFAULT_ADMIN_ROLE = ethers.ZeroHash;
  12. const PROPOSER_ROLE = ethers.id('PROPOSER_ROLE');
  13. const EXECUTOR_ROLE = ethers.id('EXECUTOR_ROLE');
  14. const CANCELLER_ROLE = ethers.id('CANCELLER_ROLE');
  15. const getAddress = obj => obj.address ?? obj.target ?? obj;
  16. function genOperation(target, value, data, predecessor, salt) {
  17. const id = ethers.keccak256(
  18. ethers.AbiCoder.defaultAbiCoder().encode(
  19. ['address', 'uint256', 'bytes', 'uint256', 'bytes32'],
  20. [getAddress(target), value, data, predecessor, salt],
  21. ),
  22. );
  23. return { id, target, value, data, predecessor, salt };
  24. }
  25. function genOperationBatch(targets, values, payloads, predecessor, salt) {
  26. const id = ethers.keccak256(
  27. ethers.AbiCoder.defaultAbiCoder().encode(
  28. ['address[]', 'uint256[]', 'bytes[]', 'uint256', 'bytes32'],
  29. [targets.map(getAddress), values, payloads, predecessor, salt],
  30. ),
  31. );
  32. return { id, targets, values, payloads, predecessor, salt };
  33. }
  34. async function fixture() {
  35. const [admin, proposer, canceller, executor, other] = await ethers.getSigners();
  36. const mock = await ethers.deployContract('TimelockController', [MINDELAY, [proposer], [executor], admin]);
  37. const callreceivermock = await ethers.deployContract('CallReceiverMock');
  38. const implementation2 = await ethers.deployContract('Implementation2');
  39. expect(await mock.hasRole(CANCELLER_ROLE, proposer)).to.be.true;
  40. await mock.connect(admin).revokeRole(CANCELLER_ROLE, proposer);
  41. await mock.connect(admin).grantRole(CANCELLER_ROLE, canceller);
  42. return {
  43. admin,
  44. proposer,
  45. canceller,
  46. executor,
  47. other,
  48. mock,
  49. callreceivermock,
  50. implementation2,
  51. };
  52. }
  53. describe('TimelockController', function () {
  54. beforeEach(async function () {
  55. Object.assign(this, await loadFixture(fixture));
  56. });
  57. shouldSupportInterfaces(['ERC1155Receiver']);
  58. it('initial state', async function () {
  59. expect(await this.mock.getMinDelay()).to.equal(MINDELAY);
  60. expect(await this.mock.DEFAULT_ADMIN_ROLE()).to.equal(DEFAULT_ADMIN_ROLE);
  61. expect(await this.mock.PROPOSER_ROLE()).to.equal(PROPOSER_ROLE);
  62. expect(await this.mock.EXECUTOR_ROLE()).to.equal(EXECUTOR_ROLE);
  63. expect(await this.mock.CANCELLER_ROLE()).to.equal(CANCELLER_ROLE);
  64. expect(
  65. await Promise.all(
  66. [PROPOSER_ROLE, CANCELLER_ROLE, EXECUTOR_ROLE].map(role => this.mock.hasRole(role, this.proposer)),
  67. ),
  68. ).to.deep.equal([true, false, false]);
  69. expect(
  70. await Promise.all(
  71. [PROPOSER_ROLE, CANCELLER_ROLE, EXECUTOR_ROLE].map(role => this.mock.hasRole(role, this.canceller)),
  72. ),
  73. ).to.deep.equal([false, true, false]);
  74. expect(
  75. await Promise.all(
  76. [PROPOSER_ROLE, CANCELLER_ROLE, EXECUTOR_ROLE].map(role => this.mock.hasRole(role, this.executor)),
  77. ),
  78. ).to.deep.equal([false, false, true]);
  79. });
  80. it('optional admin', async function () {
  81. const mock = await ethers.deployContract('TimelockController', [
  82. MINDELAY,
  83. [this.proposer],
  84. [this.executor],
  85. ethers.ZeroAddress,
  86. ]);
  87. expect(await mock.hasRole(DEFAULT_ADMIN_ROLE, this.admin)).to.be.false;
  88. expect(await mock.hasRole(DEFAULT_ADMIN_ROLE, mock.target)).to.be.true;
  89. });
  90. describe('methods', function () {
  91. describe('operation hashing', function () {
  92. it('hashOperation', async function () {
  93. this.operation = genOperation(
  94. '0x29cebefe301c6ce1bb36b58654fea275e1cacc83',
  95. '0xf94fdd6e21da21d2',
  96. '0xa3bc5104',
  97. '0xba41db3be0a9929145cfe480bd0f1f003689104d275ae912099f925df424ef94',
  98. '0x60d9109846ab510ed75c15f979ae366a8a2ace11d34ba9788c13ac296db50e6e',
  99. );
  100. expect(
  101. await this.mock.hashOperation(
  102. this.operation.target,
  103. this.operation.value,
  104. this.operation.data,
  105. this.operation.predecessor,
  106. this.operation.salt,
  107. ),
  108. ).to.equal(this.operation.id);
  109. });
  110. it('hashOperationBatch', async function () {
  111. this.operation = genOperationBatch(
  112. Array(8).fill('0x2d5f21620e56531c1d59c2df9b8e95d129571f71'),
  113. Array(8).fill('0x2b993cfce932ccee'),
  114. Array(8).fill('0xcf51966b'),
  115. '0xce8f45069cc71d25f71ba05062de1a3974f9849b004de64a70998bca9d29c2e7',
  116. '0x8952d74c110f72bfe5accdf828c74d53a7dfb71235dfa8a1e8c75d8576b372ff',
  117. );
  118. expect(
  119. await this.mock.hashOperationBatch(
  120. this.operation.targets,
  121. this.operation.values,
  122. this.operation.payloads,
  123. this.operation.predecessor,
  124. this.operation.salt,
  125. ),
  126. ).to.equal(this.operation.id);
  127. });
  128. });
  129. describe('simple', function () {
  130. describe('schedule', function () {
  131. beforeEach(async function () {
  132. this.operation = genOperation(
  133. '0x31754f590B97fD975Eb86938f18Cc304E264D2F2',
  134. 0n,
  135. '0x3bf92ccc',
  136. ethers.ZeroHash,
  137. salt,
  138. );
  139. });
  140. it('proposer can schedule', async function () {
  141. const tx = await this.mock
  142. .connect(this.proposer)
  143. .schedule(
  144. this.operation.target,
  145. this.operation.value,
  146. this.operation.data,
  147. this.operation.predecessor,
  148. this.operation.salt,
  149. MINDELAY,
  150. );
  151. expect(tx)
  152. .to.emit(this.mock, 'CallScheduled')
  153. .withArgs(
  154. this.operation.id,
  155. 0n,
  156. this.operation.target,
  157. this.operation.value,
  158. this.operation.data,
  159. this.operation.predecessor,
  160. MINDELAY,
  161. )
  162. .to.emit(this.mock, 'CallSalt')
  163. .withArgs(this.operation.id, this.operation.salt);
  164. expect(await this.mock.getTimestamp(this.operation.id)).to.equal(
  165. (await time.clockFromReceipt.timestamp(tx)) + MINDELAY,
  166. );
  167. });
  168. it('prevent overwriting active operation', async function () {
  169. await this.mock
  170. .connect(this.proposer)
  171. .schedule(
  172. this.operation.target,
  173. this.operation.value,
  174. this.operation.data,
  175. this.operation.predecessor,
  176. this.operation.salt,
  177. MINDELAY,
  178. );
  179. await expect(
  180. this.mock
  181. .connect(this.proposer)
  182. .schedule(
  183. this.operation.target,
  184. this.operation.value,
  185. this.operation.data,
  186. this.operation.predecessor,
  187. this.operation.salt,
  188. MINDELAY,
  189. ),
  190. )
  191. .to.be.revertedWithCustomError(this.mock, 'TimelockUnexpectedOperationState')
  192. .withArgs(this.operation.id, GovernorHelper.proposalStatesToBitMap(OperationState.Unset));
  193. });
  194. it('prevent non-proposer from committing', async function () {
  195. await expect(
  196. this.mock
  197. .connect(this.other)
  198. .schedule(
  199. this.operation.target,
  200. this.operation.value,
  201. this.operation.data,
  202. this.operation.predecessor,
  203. this.operation.salt,
  204. MINDELAY,
  205. ),
  206. )
  207. .to.be.revertedWithCustomError(this.mock, 'AccessControlUnauthorizedAccount')
  208. .withArgs(this.other, PROPOSER_ROLE);
  209. });
  210. it('enforce minimum delay', async function () {
  211. await expect(
  212. this.mock
  213. .connect(this.proposer)
  214. .schedule(
  215. this.operation.target,
  216. this.operation.value,
  217. this.operation.data,
  218. this.operation.predecessor,
  219. this.operation.salt,
  220. MINDELAY - 1n,
  221. ),
  222. )
  223. .to.be.revertedWithCustomError(this.mock, 'TimelockInsufficientDelay')
  224. .withArgs(MINDELAY - 1n, MINDELAY);
  225. });
  226. it('schedule operation with salt zero', async function () {
  227. await expect(
  228. this.mock
  229. .connect(this.proposer)
  230. .schedule(
  231. this.operation.target,
  232. this.operation.value,
  233. this.operation.data,
  234. this.operation.predecessor,
  235. ethers.ZeroHash,
  236. MINDELAY,
  237. ),
  238. ).to.not.emit(this.mock, 'CallSalt');
  239. });
  240. });
  241. describe('execute', function () {
  242. beforeEach(async function () {
  243. this.operation = genOperation(
  244. '0xAe22104DCD970750610E6FE15E623468A98b15f7',
  245. 0n,
  246. '0x13e414de',
  247. ethers.ZeroHash,
  248. '0xc1059ed2dc130227aa1d1d539ac94c641306905c020436c636e19e3fab56fc7f',
  249. );
  250. });
  251. it('revert if operation is not scheduled', async function () {
  252. await expect(
  253. this.mock
  254. .connect(this.executor)
  255. .execute(
  256. this.operation.target,
  257. this.operation.value,
  258. this.operation.data,
  259. this.operation.predecessor,
  260. this.operation.salt,
  261. ),
  262. )
  263. .to.be.revertedWithCustomError(this.mock, 'TimelockUnexpectedOperationState')
  264. .withArgs(this.operation.id, GovernorHelper.proposalStatesToBitMap(OperationState.Ready));
  265. });
  266. describe('with scheduled operation', function () {
  267. beforeEach(async function () {
  268. await this.mock
  269. .connect(this.proposer)
  270. .schedule(
  271. this.operation.target,
  272. this.operation.value,
  273. this.operation.data,
  274. this.operation.predecessor,
  275. this.operation.salt,
  276. MINDELAY,
  277. );
  278. });
  279. it('revert if execution comes too early 1/2', async function () {
  280. await expect(
  281. this.mock
  282. .connect(this.executor)
  283. .execute(
  284. this.operation.target,
  285. this.operation.value,
  286. this.operation.data,
  287. this.operation.predecessor,
  288. this.operation.salt,
  289. ),
  290. )
  291. .to.be.revertedWithCustomError(this.mock, 'TimelockUnexpectedOperationState')
  292. .withArgs(this.operation.id, GovernorHelper.proposalStatesToBitMap(OperationState.Ready));
  293. });
  294. it('revert if execution comes too early 2/2', async function () {
  295. // -1 is too tight, test sometime fails
  296. await this.mock.getTimestamp(this.operation.id).then(clock => time.increaseTo.timestamp(clock - 5n));
  297. await expect(
  298. this.mock
  299. .connect(this.executor)
  300. .execute(
  301. this.operation.target,
  302. this.operation.value,
  303. this.operation.data,
  304. this.operation.predecessor,
  305. this.operation.salt,
  306. ),
  307. )
  308. .to.be.revertedWithCustomError(this.mock, 'TimelockUnexpectedOperationState')
  309. .withArgs(this.operation.id, GovernorHelper.proposalStatesToBitMap(OperationState.Ready));
  310. });
  311. describe('on time', function () {
  312. beforeEach(async function () {
  313. await this.mock.getTimestamp(this.operation.id).then(time.increaseTo.timestamp);
  314. });
  315. it('executor can reveal', async function () {
  316. await expect(
  317. this.mock
  318. .connect(this.executor)
  319. .execute(
  320. this.operation.target,
  321. this.operation.value,
  322. this.operation.data,
  323. this.operation.predecessor,
  324. this.operation.salt,
  325. ),
  326. )
  327. .to.emit(this.mock, 'CallExecuted')
  328. .withArgs(this.operation.id, 0n, this.operation.target, this.operation.value, this.operation.data);
  329. });
  330. it('prevent non-executor from revealing', async function () {
  331. await expect(
  332. this.mock
  333. .connect(this.other)
  334. .execute(
  335. this.operation.target,
  336. this.operation.value,
  337. this.operation.data,
  338. this.operation.predecessor,
  339. this.operation.salt,
  340. ),
  341. )
  342. .to.be.revertedWithCustomError(this.mock, 'AccessControlUnauthorizedAccount')
  343. .withArgs(this.other, EXECUTOR_ROLE);
  344. });
  345. it('prevents reentrancy execution', async function () {
  346. // Create operation
  347. const reentrant = await ethers.deployContract('$TimelockReentrant');
  348. const reentrantOperation = genOperation(
  349. reentrant,
  350. 0n,
  351. reentrant.interface.encodeFunctionData('reenter'),
  352. ethers.ZeroHash,
  353. salt,
  354. );
  355. // Schedule so it can be executed
  356. await this.mock
  357. .connect(this.proposer)
  358. .schedule(
  359. reentrantOperation.target,
  360. reentrantOperation.value,
  361. reentrantOperation.data,
  362. reentrantOperation.predecessor,
  363. reentrantOperation.salt,
  364. MINDELAY,
  365. );
  366. // Advance on time to make the operation executable
  367. await this.mock.getTimestamp(reentrantOperation.id).then(time.increaseTo.timestamp);
  368. // Grant executor role to the reentrant contract
  369. await this.mock.connect(this.admin).grantRole(EXECUTOR_ROLE, reentrant);
  370. // Prepare reenter
  371. const data = this.mock.interface.encodeFunctionData('execute', [
  372. getAddress(reentrantOperation.target),
  373. reentrantOperation.value,
  374. reentrantOperation.data,
  375. reentrantOperation.predecessor,
  376. reentrantOperation.salt,
  377. ]);
  378. await reentrant.enableRentrancy(this.mock, data);
  379. // Expect to fail
  380. await expect(
  381. this.mock
  382. .connect(this.executor)
  383. .execute(
  384. reentrantOperation.target,
  385. reentrantOperation.value,
  386. reentrantOperation.data,
  387. reentrantOperation.predecessor,
  388. reentrantOperation.salt,
  389. ),
  390. )
  391. .to.be.revertedWithCustomError(this.mock, 'TimelockUnexpectedOperationState')
  392. .withArgs(reentrantOperation.id, GovernorHelper.proposalStatesToBitMap(OperationState.Ready));
  393. // Disable reentrancy
  394. await reentrant.disableReentrancy();
  395. const nonReentrantOperation = reentrantOperation; // Not anymore
  396. // Try again successfully
  397. await expect(
  398. this.mock
  399. .connect(this.executor)
  400. .execute(
  401. nonReentrantOperation.target,
  402. nonReentrantOperation.value,
  403. nonReentrantOperation.data,
  404. nonReentrantOperation.predecessor,
  405. nonReentrantOperation.salt,
  406. ),
  407. )
  408. .to.emit(this.mock, 'CallExecuted')
  409. .withArgs(
  410. nonReentrantOperation.id,
  411. 0n,
  412. getAddress(nonReentrantOperation),
  413. nonReentrantOperation.value,
  414. nonReentrantOperation.data,
  415. );
  416. });
  417. });
  418. });
  419. });
  420. });
  421. describe('batch', function () {
  422. describe('schedule', function () {
  423. beforeEach(async function () {
  424. this.operation = genOperationBatch(
  425. Array(8).fill('0xEd912250835c812D4516BBD80BdaEA1bB63a293C'),
  426. Array(8).fill(0n),
  427. Array(8).fill('0x2fcb7a88'),
  428. ethers.ZeroHash,
  429. '0x6cf9d042ade5de78bed9ffd075eb4b2a4f6b1736932c2dc8af517d6e066f51f5',
  430. );
  431. });
  432. it('proposer can schedule', async function () {
  433. const tx = this.mock
  434. .connect(this.proposer)
  435. .scheduleBatch(
  436. this.operation.targets,
  437. this.operation.values,
  438. this.operation.payloads,
  439. this.operation.predecessor,
  440. this.operation.salt,
  441. MINDELAY,
  442. );
  443. for (const i in this.operation.targets) {
  444. await expect(tx)
  445. .to.emit(this.mock, 'CallScheduled')
  446. .withArgs(
  447. this.operation.id,
  448. i,
  449. getAddress(this.operation.targets[i]),
  450. this.operation.values[i],
  451. this.operation.payloads[i],
  452. this.operation.predecessor,
  453. MINDELAY,
  454. )
  455. .to.emit(this.mock, 'CallSalt')
  456. .withArgs(this.operation.id, this.operation.salt);
  457. }
  458. expect(await this.mock.getTimestamp(this.operation.id)).to.equal(
  459. (await time.clockFromReceipt.timestamp(tx)) + MINDELAY,
  460. );
  461. });
  462. it('prevent overwriting active operation', async function () {
  463. await this.mock
  464. .connect(this.proposer)
  465. .scheduleBatch(
  466. this.operation.targets,
  467. this.operation.values,
  468. this.operation.payloads,
  469. this.operation.predecessor,
  470. this.operation.salt,
  471. MINDELAY,
  472. );
  473. await expect(
  474. this.mock
  475. .connect(this.proposer)
  476. .scheduleBatch(
  477. this.operation.targets,
  478. this.operation.values,
  479. this.operation.payloads,
  480. this.operation.predecessor,
  481. this.operation.salt,
  482. MINDELAY,
  483. ),
  484. )
  485. .to.be.revertedWithCustomError(this.mock, 'TimelockUnexpectedOperationState')
  486. .withArgs(this.operation.id, GovernorHelper.proposalStatesToBitMap(OperationState.Unset));
  487. });
  488. it('length of batch parameter must match #1', async function () {
  489. await expect(
  490. this.mock
  491. .connect(this.proposer)
  492. .scheduleBatch(
  493. this.operation.targets,
  494. [],
  495. this.operation.payloads,
  496. this.operation.predecessor,
  497. this.operation.salt,
  498. MINDELAY,
  499. ),
  500. )
  501. .to.be.revertedWithCustomError(this.mock, 'TimelockInvalidOperationLength')
  502. .withArgs(this.operation.targets.length, this.operation.payloads.length, 0n);
  503. });
  504. it('length of batch parameter must match #1', async function () {
  505. await expect(
  506. this.mock
  507. .connect(this.proposer)
  508. .scheduleBatch(
  509. this.operation.targets,
  510. this.operation.values,
  511. [],
  512. this.operation.predecessor,
  513. this.operation.salt,
  514. MINDELAY,
  515. ),
  516. )
  517. .to.be.revertedWithCustomError(this.mock, 'TimelockInvalidOperationLength')
  518. .withArgs(this.operation.targets.length, 0n, this.operation.payloads.length);
  519. });
  520. it('prevent non-proposer from committing', async function () {
  521. await expect(
  522. this.mock
  523. .connect(this.other)
  524. .scheduleBatch(
  525. this.operation.targets,
  526. this.operation.values,
  527. this.operation.payloads,
  528. this.operation.predecessor,
  529. this.operation.salt,
  530. MINDELAY,
  531. ),
  532. )
  533. .to.be.revertedWithCustomError(this.mock, 'AccessControlUnauthorizedAccount')
  534. .withArgs(this.other, PROPOSER_ROLE);
  535. });
  536. it('enforce minimum delay', async function () {
  537. await expect(
  538. this.mock
  539. .connect(this.proposer)
  540. .scheduleBatch(
  541. this.operation.targets,
  542. this.operation.values,
  543. this.operation.payloads,
  544. this.operation.predecessor,
  545. this.operation.salt,
  546. MINDELAY - 1n,
  547. ),
  548. )
  549. .to.be.revertedWithCustomError(this.mock, 'TimelockInsufficientDelay')
  550. .withArgs(MINDELAY - 1n, MINDELAY);
  551. });
  552. });
  553. describe('execute', function () {
  554. beforeEach(async function () {
  555. this.operation = genOperationBatch(
  556. Array(8).fill('0x76E53CcEb05131Ef5248553bEBDb8F70536830b1'),
  557. Array(8).fill(0n),
  558. Array(8).fill('0x58a60f63'),
  559. ethers.ZeroHash,
  560. '0x9545eeabc7a7586689191f78a5532443698538e54211b5bd4d7dc0fc0102b5c7',
  561. );
  562. });
  563. it('revert if operation is not scheduled', async function () {
  564. await expect(
  565. this.mock
  566. .connect(this.executor)
  567. .executeBatch(
  568. this.operation.targets,
  569. this.operation.values,
  570. this.operation.payloads,
  571. this.operation.predecessor,
  572. this.operation.salt,
  573. ),
  574. )
  575. .to.be.revertedWithCustomError(this.mock, 'TimelockUnexpectedOperationState')
  576. .withArgs(this.operation.id, GovernorHelper.proposalStatesToBitMap(OperationState.Ready));
  577. });
  578. describe('with scheduled operation', function () {
  579. beforeEach(async function () {
  580. await this.mock
  581. .connect(this.proposer)
  582. .scheduleBatch(
  583. this.operation.targets,
  584. this.operation.values,
  585. this.operation.payloads,
  586. this.operation.predecessor,
  587. this.operation.salt,
  588. MINDELAY,
  589. );
  590. });
  591. it('revert if execution comes too early 1/2', async function () {
  592. await expect(
  593. this.mock
  594. .connect(this.executor)
  595. .executeBatch(
  596. this.operation.targets,
  597. this.operation.values,
  598. this.operation.payloads,
  599. this.operation.predecessor,
  600. this.operation.salt,
  601. ),
  602. )
  603. .to.be.revertedWithCustomError(this.mock, 'TimelockUnexpectedOperationState')
  604. .withArgs(this.operation.id, GovernorHelper.proposalStatesToBitMap(OperationState.Ready));
  605. });
  606. it('revert if execution comes too early 2/2', async function () {
  607. // -1 is to tight, test sometime fails
  608. await this.mock.getTimestamp(this.operation.id).then(clock => time.increaseTo.timestamp(clock - 5n));
  609. await expect(
  610. this.mock
  611. .connect(this.executor)
  612. .executeBatch(
  613. this.operation.targets,
  614. this.operation.values,
  615. this.operation.payloads,
  616. this.operation.predecessor,
  617. this.operation.salt,
  618. ),
  619. )
  620. .to.be.revertedWithCustomError(this.mock, 'TimelockUnexpectedOperationState')
  621. .withArgs(this.operation.id, GovernorHelper.proposalStatesToBitMap(OperationState.Ready));
  622. });
  623. describe('on time', function () {
  624. beforeEach(async function () {
  625. await this.mock.getTimestamp(this.operation.id).then(time.increaseTo.timestamp);
  626. });
  627. it('executor can reveal', async function () {
  628. const tx = this.mock
  629. .connect(this.executor)
  630. .executeBatch(
  631. this.operation.targets,
  632. this.operation.values,
  633. this.operation.payloads,
  634. this.operation.predecessor,
  635. this.operation.salt,
  636. );
  637. for (const i in this.operation.targets) {
  638. expect(tx)
  639. .to.emit(this.mock, 'CallExecuted')
  640. .withArgs(
  641. this.operation.id,
  642. i,
  643. this.operation.targets[i],
  644. this.operation.values[i],
  645. this.operation.payloads[i],
  646. );
  647. }
  648. });
  649. it('prevent non-executor from revealing', async function () {
  650. await expect(
  651. this.mock
  652. .connect(this.other)
  653. .executeBatch(
  654. this.operation.targets,
  655. this.operation.values,
  656. this.operation.payloads,
  657. this.operation.predecessor,
  658. this.operation.salt,
  659. ),
  660. )
  661. .to.be.revertedWithCustomError(this.mock, 'AccessControlUnauthorizedAccount')
  662. .withArgs(this.other, EXECUTOR_ROLE);
  663. });
  664. it('length mismatch #1', async function () {
  665. await expect(
  666. this.mock
  667. .connect(this.executor)
  668. .executeBatch(
  669. [],
  670. this.operation.values,
  671. this.operation.payloads,
  672. this.operation.predecessor,
  673. this.operation.salt,
  674. ),
  675. )
  676. .to.be.revertedWithCustomError(this.mock, 'TimelockInvalidOperationLength')
  677. .withArgs(0, this.operation.payloads.length, this.operation.values.length);
  678. });
  679. it('length mismatch #2', async function () {
  680. await expect(
  681. this.mock
  682. .connect(this.executor)
  683. .executeBatch(
  684. this.operation.targets,
  685. [],
  686. this.operation.payloads,
  687. this.operation.predecessor,
  688. this.operation.salt,
  689. ),
  690. )
  691. .to.be.revertedWithCustomError(this.mock, 'TimelockInvalidOperationLength')
  692. .withArgs(this.operation.targets.length, this.operation.payloads.length, 0n);
  693. });
  694. it('length mismatch #3', async function () {
  695. await expect(
  696. this.mock
  697. .connect(this.executor)
  698. .executeBatch(
  699. this.operation.targets,
  700. this.operation.values,
  701. [],
  702. this.operation.predecessor,
  703. this.operation.salt,
  704. ),
  705. )
  706. .to.be.revertedWithCustomError(this.mock, 'TimelockInvalidOperationLength')
  707. .withArgs(this.operation.targets.length, 0n, this.operation.values.length);
  708. });
  709. it('prevents reentrancy execution', async function () {
  710. // Create operation
  711. const reentrant = await ethers.deployContract('$TimelockReentrant');
  712. const reentrantBatchOperation = genOperationBatch(
  713. [reentrant],
  714. [0n],
  715. [reentrant.interface.encodeFunctionData('reenter')],
  716. ethers.ZeroHash,
  717. salt,
  718. );
  719. // Schedule so it can be executed
  720. await this.mock
  721. .connect(this.proposer)
  722. .scheduleBatch(
  723. reentrantBatchOperation.targets,
  724. reentrantBatchOperation.values,
  725. reentrantBatchOperation.payloads,
  726. reentrantBatchOperation.predecessor,
  727. reentrantBatchOperation.salt,
  728. MINDELAY,
  729. );
  730. // Advance on time to make the operation executable
  731. await this.mock.getTimestamp(reentrantBatchOperation.id).then(time.increaseTo.timestamp);
  732. // Grant executor role to the reentrant contract
  733. await this.mock.connect(this.admin).grantRole(EXECUTOR_ROLE, reentrant);
  734. // Prepare reenter
  735. const data = this.mock.interface.encodeFunctionData('executeBatch', [
  736. reentrantBatchOperation.targets.map(getAddress),
  737. reentrantBatchOperation.values,
  738. reentrantBatchOperation.payloads,
  739. reentrantBatchOperation.predecessor,
  740. reentrantBatchOperation.salt,
  741. ]);
  742. await reentrant.enableRentrancy(this.mock, data);
  743. // Expect to fail
  744. await expect(
  745. this.mock
  746. .connect(this.executor)
  747. .executeBatch(
  748. reentrantBatchOperation.targets,
  749. reentrantBatchOperation.values,
  750. reentrantBatchOperation.payloads,
  751. reentrantBatchOperation.predecessor,
  752. reentrantBatchOperation.salt,
  753. ),
  754. )
  755. .to.be.revertedWithCustomError(this.mock, 'TimelockUnexpectedOperationState')
  756. .withArgs(reentrantBatchOperation.id, GovernorHelper.proposalStatesToBitMap(OperationState.Ready));
  757. // Disable reentrancy
  758. await reentrant.disableReentrancy();
  759. const nonReentrantBatchOperation = reentrantBatchOperation; // Not anymore
  760. // Try again successfully
  761. const tx = this.mock
  762. .connect(this.executor)
  763. .executeBatch(
  764. nonReentrantBatchOperation.targets,
  765. nonReentrantBatchOperation.values,
  766. nonReentrantBatchOperation.payloads,
  767. nonReentrantBatchOperation.predecessor,
  768. nonReentrantBatchOperation.salt,
  769. );
  770. for (const i in nonReentrantBatchOperation.targets) {
  771. expect(tx)
  772. .to.emit(this.mock, 'CallExecuted')
  773. .withArgs(
  774. nonReentrantBatchOperation.id,
  775. i,
  776. nonReentrantBatchOperation.targets[i],
  777. nonReentrantBatchOperation.values[i],
  778. nonReentrantBatchOperation.payloads[i],
  779. );
  780. }
  781. });
  782. });
  783. });
  784. it('partial execution', async function () {
  785. const operation = genOperationBatch(
  786. [this.callreceivermock, this.callreceivermock, this.callreceivermock],
  787. [0n, 0n, 0n],
  788. [
  789. this.callreceivermock.interface.encodeFunctionData('mockFunction'),
  790. this.callreceivermock.interface.encodeFunctionData('mockFunctionRevertsNoReason'),
  791. this.callreceivermock.interface.encodeFunctionData('mockFunction'),
  792. ],
  793. ethers.ZeroHash,
  794. '0x8ac04aa0d6d66b8812fb41d39638d37af0a9ab11da507afd65c509f8ed079d3e',
  795. );
  796. await this.mock
  797. .connect(this.proposer)
  798. .scheduleBatch(
  799. operation.targets,
  800. operation.values,
  801. operation.payloads,
  802. operation.predecessor,
  803. operation.salt,
  804. MINDELAY,
  805. );
  806. await this.mock.getTimestamp(operation.id).then(time.increaseTo.timestamp);
  807. await expect(
  808. this.mock
  809. .connect(this.executor)
  810. .executeBatch(
  811. operation.targets,
  812. operation.values,
  813. operation.payloads,
  814. operation.predecessor,
  815. operation.salt,
  816. ),
  817. ).to.be.revertedWithCustomError(this.mock, 'FailedCall');
  818. });
  819. });
  820. });
  821. describe('cancel', function () {
  822. beforeEach(async function () {
  823. this.operation = genOperation(
  824. '0xC6837c44AA376dbe1d2709F13879E040CAb653ca',
  825. 0n,
  826. '0x296e58dd',
  827. ethers.ZeroHash,
  828. '0xa2485763600634800df9fc9646fb2c112cf98649c55f63dd1d9c7d13a64399d9',
  829. );
  830. await this.mock
  831. .connect(this.proposer)
  832. .schedule(
  833. this.operation.target,
  834. this.operation.value,
  835. this.operation.data,
  836. this.operation.predecessor,
  837. this.operation.salt,
  838. MINDELAY,
  839. );
  840. });
  841. it('canceller can cancel', async function () {
  842. await expect(this.mock.connect(this.canceller).cancel(this.operation.id))
  843. .to.emit(this.mock, 'Cancelled')
  844. .withArgs(this.operation.id);
  845. });
  846. it('cannot cancel invalid operation', async function () {
  847. await expect(this.mock.connect(this.canceller).cancel(ethers.ZeroHash))
  848. .to.be.revertedWithCustomError(this.mock, 'TimelockUnexpectedOperationState')
  849. .withArgs(
  850. ethers.ZeroHash,
  851. GovernorHelper.proposalStatesToBitMap([OperationState.Waiting, OperationState.Ready]),
  852. );
  853. });
  854. it('prevent non-canceller from canceling', async function () {
  855. await expect(this.mock.connect(this.other).cancel(this.operation.id))
  856. .to.be.revertedWithCustomError(this.mock, 'AccessControlUnauthorizedAccount')
  857. .withArgs(this.other, CANCELLER_ROLE);
  858. });
  859. });
  860. });
  861. describe('maintenance', function () {
  862. it('prevent unauthorized maintenance', async function () {
  863. await expect(this.mock.connect(this.other).updateDelay(0n))
  864. .to.be.revertedWithCustomError(this.mock, 'TimelockUnauthorizedCaller')
  865. .withArgs(this.other);
  866. });
  867. it('timelock scheduled maintenance', async function () {
  868. const newDelay = time.duration.hours(6);
  869. const operation = genOperation(
  870. this.mock,
  871. 0n,
  872. this.mock.interface.encodeFunctionData('updateDelay', [newDelay]),
  873. ethers.ZeroHash,
  874. '0xf8e775b2c5f4d66fb5c7fa800f35ef518c262b6014b3c0aee6ea21bff157f108',
  875. );
  876. await this.mock
  877. .connect(this.proposer)
  878. .schedule(operation.target, operation.value, operation.data, operation.predecessor, operation.salt, MINDELAY);
  879. await this.mock.getTimestamp(operation.id).then(time.increaseTo.timestamp);
  880. await expect(
  881. this.mock
  882. .connect(this.executor)
  883. .execute(operation.target, operation.value, operation.data, operation.predecessor, operation.salt),
  884. )
  885. .to.emit(this.mock, 'MinDelayChange')
  886. .withArgs(MINDELAY, newDelay);
  887. expect(await this.mock.getMinDelay()).to.equal(newDelay);
  888. });
  889. });
  890. describe('dependency', function () {
  891. beforeEach(async function () {
  892. this.operation1 = genOperation(
  893. '0xdE66bD4c97304200A95aE0AadA32d6d01A867E39',
  894. 0n,
  895. '0x01dc731a',
  896. ethers.ZeroHash,
  897. '0x64e932133c7677402ead2926f86205e2ca4686aebecf5a8077627092b9bb2feb',
  898. );
  899. this.operation2 = genOperation(
  900. '0x3c7944a3F1ee7fc8c5A5134ba7c79D11c3A1FCa3',
  901. 0n,
  902. '0x8f531849',
  903. this.operation1.id,
  904. '0x036e1311cac523f9548e6461e29fb1f8f9196b91910a41711ea22f5de48df07d',
  905. );
  906. await this.mock
  907. .connect(this.proposer)
  908. .schedule(
  909. this.operation1.target,
  910. this.operation1.value,
  911. this.operation1.data,
  912. this.operation1.predecessor,
  913. this.operation1.salt,
  914. MINDELAY,
  915. );
  916. await this.mock
  917. .connect(this.proposer)
  918. .schedule(
  919. this.operation2.target,
  920. this.operation2.value,
  921. this.operation2.data,
  922. this.operation2.predecessor,
  923. this.operation2.salt,
  924. MINDELAY,
  925. );
  926. await this.mock.getTimestamp(this.operation2.id).then(time.increaseTo.timestamp);
  927. });
  928. it('cannot execute before dependency', async function () {
  929. await expect(
  930. this.mock
  931. .connect(this.executor)
  932. .execute(
  933. this.operation2.target,
  934. this.operation2.value,
  935. this.operation2.data,
  936. this.operation2.predecessor,
  937. this.operation2.salt,
  938. ),
  939. )
  940. .to.be.revertedWithCustomError(this.mock, 'TimelockUnexecutedPredecessor')
  941. .withArgs(this.operation1.id);
  942. });
  943. it('can execute after dependency', async function () {
  944. await this.mock
  945. .connect(this.executor)
  946. .execute(
  947. this.operation1.target,
  948. this.operation1.value,
  949. this.operation1.data,
  950. this.operation1.predecessor,
  951. this.operation1.salt,
  952. );
  953. await this.mock
  954. .connect(this.executor)
  955. .execute(
  956. this.operation2.target,
  957. this.operation2.value,
  958. this.operation2.data,
  959. this.operation2.predecessor,
  960. this.operation2.salt,
  961. );
  962. });
  963. });
  964. describe('usage scenario', function () {
  965. this.timeout(10000);
  966. it('call', async function () {
  967. const operation = genOperation(
  968. this.implementation2,
  969. 0n,
  970. this.implementation2.interface.encodeFunctionData('setValue', [42n]),
  971. ethers.ZeroHash,
  972. '0x8043596363daefc89977b25f9d9b4d06c3910959ef0c4d213557a903e1b555e2',
  973. );
  974. await this.mock
  975. .connect(this.proposer)
  976. .schedule(operation.target, operation.value, operation.data, operation.predecessor, operation.salt, MINDELAY);
  977. await this.mock.getTimestamp(operation.id).then(time.increaseTo.timestamp);
  978. await this.mock
  979. .connect(this.executor)
  980. .execute(operation.target, operation.value, operation.data, operation.predecessor, operation.salt);
  981. expect(await this.implementation2.getValue()).to.equal(42n);
  982. });
  983. it('call reverting', async function () {
  984. const operation = genOperation(
  985. this.callreceivermock,
  986. 0n,
  987. this.callreceivermock.interface.encodeFunctionData('mockFunctionRevertsNoReason'),
  988. ethers.ZeroHash,
  989. '0xb1b1b276fdf1a28d1e00537ea73b04d56639128b08063c1a2f70a52e38cba693',
  990. );
  991. await this.mock
  992. .connect(this.proposer)
  993. .schedule(operation.target, operation.value, operation.data, operation.predecessor, operation.salt, MINDELAY);
  994. await this.mock.getTimestamp(operation.id).then(time.increaseTo.timestamp);
  995. await expect(
  996. this.mock
  997. .connect(this.executor)
  998. .execute(operation.target, operation.value, operation.data, operation.predecessor, operation.salt),
  999. ).to.be.revertedWithCustomError(this.mock, 'FailedCall');
  1000. });
  1001. it('call throw', async function () {
  1002. const operation = genOperation(
  1003. this.callreceivermock,
  1004. 0n,
  1005. this.callreceivermock.interface.encodeFunctionData('mockFunctionThrows'),
  1006. ethers.ZeroHash,
  1007. '0xe5ca79f295fc8327ee8a765fe19afb58f4a0cbc5053642bfdd7e73bc68e0fc67',
  1008. );
  1009. await this.mock
  1010. .connect(this.proposer)
  1011. .schedule(operation.target, operation.value, operation.data, operation.predecessor, operation.salt, MINDELAY);
  1012. await this.mock.getTimestamp(operation.id).then(time.increaseTo.timestamp);
  1013. // Targeted function reverts with a panic code (0x1) + the timelock bubble the panic code
  1014. await expect(
  1015. this.mock
  1016. .connect(this.executor)
  1017. .execute(operation.target, operation.value, operation.data, operation.predecessor, operation.salt),
  1018. ).to.be.revertedWithPanic(PANIC_CODES.ASSERTION_ERROR);
  1019. });
  1020. it('call out of gas', async function () {
  1021. const operation = genOperation(
  1022. this.callreceivermock,
  1023. 0n,
  1024. this.callreceivermock.interface.encodeFunctionData('mockFunctionOutOfGas'),
  1025. ethers.ZeroHash,
  1026. '0xf3274ce7c394c5b629d5215723563a744b817e1730cca5587c567099a14578fd',
  1027. );
  1028. await this.mock
  1029. .connect(this.proposer)
  1030. .schedule(operation.target, operation.value, operation.data, operation.predecessor, operation.salt, MINDELAY);
  1031. await this.mock.getTimestamp(operation.id).then(time.increaseTo.timestamp);
  1032. await expect(
  1033. this.mock
  1034. .connect(this.executor)
  1035. .execute(operation.target, operation.value, operation.data, operation.predecessor, operation.salt, {
  1036. gasLimit: '100000',
  1037. }),
  1038. ).to.be.revertedWithCustomError(this.mock, 'FailedCall');
  1039. });
  1040. it('call payable with eth', async function () {
  1041. const operation = genOperation(
  1042. this.callreceivermock,
  1043. 1,
  1044. this.callreceivermock.interface.encodeFunctionData('mockFunction'),
  1045. ethers.ZeroHash,
  1046. '0x5ab73cd33477dcd36c1e05e28362719d0ed59a7b9ff14939de63a43073dc1f44',
  1047. );
  1048. await this.mock
  1049. .connect(this.proposer)
  1050. .schedule(operation.target, operation.value, operation.data, operation.predecessor, operation.salt, MINDELAY);
  1051. await this.mock.getTimestamp(operation.id).then(time.increaseTo.timestamp);
  1052. expect(await ethers.provider.getBalance(this.mock)).to.equal(0n);
  1053. expect(await ethers.provider.getBalance(this.callreceivermock)).to.equal(0n);
  1054. await this.mock
  1055. .connect(this.executor)
  1056. .execute(operation.target, operation.value, operation.data, operation.predecessor, operation.salt, {
  1057. value: 1,
  1058. });
  1059. expect(await ethers.provider.getBalance(this.mock)).to.equal(0n);
  1060. expect(await ethers.provider.getBalance(this.callreceivermock)).to.equal(1n);
  1061. });
  1062. it('call nonpayable with eth', async function () {
  1063. const operation = genOperation(
  1064. this.callreceivermock,
  1065. 1,
  1066. this.callreceivermock.interface.encodeFunctionData('mockFunctionNonPayable'),
  1067. ethers.ZeroHash,
  1068. '0xb78edbd920c7867f187e5aa6294ae5a656cfbf0dea1ccdca3751b740d0f2bdf8',
  1069. );
  1070. await this.mock
  1071. .connect(this.proposer)
  1072. .schedule(operation.target, operation.value, operation.data, operation.predecessor, operation.salt, MINDELAY);
  1073. await this.mock.getTimestamp(operation.id).then(time.increaseTo.timestamp);
  1074. expect(await ethers.provider.getBalance(this.mock)).to.equal(0n);
  1075. expect(await ethers.provider.getBalance(this.callreceivermock)).to.equal(0n);
  1076. await expect(
  1077. this.mock
  1078. .connect(this.executor)
  1079. .execute(operation.target, operation.value, operation.data, operation.predecessor, operation.salt),
  1080. ).to.be.revertedWithCustomError(this.mock, 'FailedCall');
  1081. expect(await ethers.provider.getBalance(this.mock)).to.equal(0n);
  1082. expect(await ethers.provider.getBalance(this.callreceivermock)).to.equal(0n);
  1083. });
  1084. it('call reverting with eth', async function () {
  1085. const operation = genOperation(
  1086. this.callreceivermock,
  1087. 1,
  1088. this.callreceivermock.interface.encodeFunctionData('mockFunctionRevertsNoReason'),
  1089. ethers.ZeroHash,
  1090. '0xdedb4563ef0095db01d81d3f2decf57cf83e4a72aa792af14c43a792b56f4de6',
  1091. );
  1092. await this.mock
  1093. .connect(this.proposer)
  1094. .schedule(operation.target, operation.value, operation.data, operation.predecessor, operation.salt, MINDELAY);
  1095. await this.mock.getTimestamp(operation.id).then(time.increaseTo.timestamp);
  1096. expect(await ethers.provider.getBalance(this.mock)).to.equal(0n);
  1097. expect(await ethers.provider.getBalance(this.callreceivermock)).to.equal(0n);
  1098. await expect(
  1099. this.mock
  1100. .connect(this.executor)
  1101. .execute(operation.target, operation.value, operation.data, operation.predecessor, operation.salt),
  1102. ).to.be.revertedWithCustomError(this.mock, 'FailedCall');
  1103. expect(await ethers.provider.getBalance(this.mock)).to.equal(0n);
  1104. expect(await ethers.provider.getBalance(this.callreceivermock)).to.equal(0n);
  1105. });
  1106. });
  1107. describe('safe receive', function () {
  1108. describe('ERC721', function () {
  1109. const tokenId = 1n;
  1110. beforeEach(async function () {
  1111. this.token = await ethers.deployContract('$ERC721', ['Non Fungible Token', 'NFT']);
  1112. await this.token.$_mint(this.other, tokenId);
  1113. });
  1114. it('can receive an ERC721 safeTransfer', async function () {
  1115. await this.token.connect(this.other).safeTransferFrom(this.other, this.mock, tokenId);
  1116. });
  1117. });
  1118. describe('ERC1155', function () {
  1119. const tokenIds = {
  1120. 1: 1000n,
  1121. 2: 2000n,
  1122. 3: 3000n,
  1123. };
  1124. beforeEach(async function () {
  1125. this.token = await ethers.deployContract('$ERC1155', ['https://token-cdn-domain/{id}.json']);
  1126. await this.token.$_mintBatch(this.other, Object.keys(tokenIds), Object.values(tokenIds), '0x');
  1127. });
  1128. it('can receive ERC1155 safeTransfer', async function () {
  1129. await this.token.connect(this.other).safeTransferFrom(
  1130. this.other,
  1131. this.mock,
  1132. ...Object.entries(tokenIds)[0n], // id + amount
  1133. '0x',
  1134. );
  1135. });
  1136. it('can receive ERC1155 safeBatchTransfer', async function () {
  1137. await this.token
  1138. .connect(this.other)
  1139. .safeBatchTransferFrom(this.other, this.mock, Object.keys(tokenIds), Object.values(tokenIds), '0x');
  1140. });
  1141. });
  1142. });
  1143. });