TimelockController.test.js 43 KB

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