GovernorTimelockControl.test.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534
  1. const { constants, expectEvent, expectRevert, time } = require('@openzeppelin/test-helpers');
  2. const { expect } = require('chai');
  3. const Enums = require('../../helpers/enums');
  4. const {
  5. runGovernorWorkflow,
  6. } = require('../GovernorWorkflow.behavior');
  7. const {
  8. shouldSupportInterfaces,
  9. } = require('../../utils/introspection/SupportsInterface.behavior');
  10. const Token = artifacts.require('ERC20VotesMock');
  11. const Timelock = artifacts.require('TimelockController');
  12. const Governor = artifacts.require('GovernorTimelockControlMock');
  13. const CallReceiver = artifacts.require('CallReceiverMock');
  14. contract('GovernorTimelockControl', function (accounts) {
  15. const [ admin, voter, other ] = accounts;
  16. const TIMELOCK_ADMIN_ROLE = web3.utils.soliditySha3('TIMELOCK_ADMIN_ROLE');
  17. const PROPOSER_ROLE = web3.utils.soliditySha3('PROPOSER_ROLE');
  18. const EXECUTOR_ROLE = web3.utils.soliditySha3('EXECUTOR_ROLE');
  19. const CANCELLER_ROLE = web3.utils.soliditySha3('CANCELLER_ROLE');
  20. const name = 'OZ-Governor';
  21. // const version = '1';
  22. const tokenName = 'MockToken';
  23. const tokenSymbol = 'MTKN';
  24. const tokenSupply = web3.utils.toWei('100');
  25. beforeEach(async function () {
  26. const [ deployer ] = await web3.eth.getAccounts();
  27. this.token = await Token.new(tokenName, tokenSymbol);
  28. this.timelock = await Timelock.new(3600, [], []);
  29. this.mock = await Governor.new(name, this.token.address, 4, 16, this.timelock.address, 0);
  30. this.receiver = await CallReceiver.new();
  31. this.TIMELOCK_ADMIN_ROLE = await this.timelock.TIMELOCK_ADMIN_ROLE();
  32. this.PROPOSER_ROLE = await this.timelock.PROPOSER_ROLE();
  33. this.EXECUTOR_ROLE = await this.timelock.EXECUTOR_ROLE();
  34. this.CANCELLER_ROLE = await this.timelock.CANCELLER_ROLE();
  35. // normal setup: governor is proposer, everyone is executor, timelock is its own admin
  36. await this.timelock.grantRole(PROPOSER_ROLE, this.mock.address);
  37. await this.timelock.grantRole(PROPOSER_ROLE, admin);
  38. await this.timelock.grantRole(CANCELLER_ROLE, this.mock.address);
  39. await this.timelock.grantRole(CANCELLER_ROLE, admin);
  40. await this.timelock.grantRole(EXECUTOR_ROLE, constants.ZERO_ADDRESS);
  41. await this.timelock.revokeRole(TIMELOCK_ADMIN_ROLE, deployer);
  42. await this.token.mint(voter, tokenSupply);
  43. await this.token.delegate(voter, { from: voter });
  44. });
  45. shouldSupportInterfaces([
  46. 'ERC165',
  47. 'Governor',
  48. 'GovernorWithParams',
  49. 'GovernorTimelock',
  50. ]);
  51. it('doesn\'t accept ether transfers', async function () {
  52. await expectRevert.unspecified(web3.eth.sendTransaction({ from: voter, to: this.mock.address, value: 1 }));
  53. });
  54. it('post deployment check', async function () {
  55. expect(await this.mock.name()).to.be.equal(name);
  56. expect(await this.mock.token()).to.be.equal(this.token.address);
  57. expect(await this.mock.votingDelay()).to.be.bignumber.equal('4');
  58. expect(await this.mock.votingPeriod()).to.be.bignumber.equal('16');
  59. expect(await this.mock.quorum(0)).to.be.bignumber.equal('0');
  60. expect(await this.mock.timelock()).to.be.equal(this.timelock.address);
  61. });
  62. describe('nominal', function () {
  63. beforeEach(async function () {
  64. this.settings = {
  65. proposal: [
  66. [ this.receiver.address ],
  67. [ web3.utils.toWei('0') ],
  68. [ this.receiver.contract.methods.mockFunction().encodeABI() ],
  69. '<proposal description>',
  70. ],
  71. voters: [
  72. { voter: voter, support: Enums.VoteType.For },
  73. ],
  74. steps: {
  75. queue: { delay: 3600 },
  76. },
  77. };
  78. });
  79. afterEach(async function () {
  80. const timelockid = await this.timelock.hashOperationBatch(
  81. ...this.settings.proposal.slice(0, 3),
  82. '0x0',
  83. this.descriptionHash,
  84. );
  85. expectEvent(
  86. this.receipts.propose,
  87. 'ProposalCreated',
  88. { proposalId: this.id },
  89. );
  90. expectEvent(
  91. this.receipts.queue,
  92. 'ProposalQueued',
  93. { proposalId: this.id },
  94. );
  95. await expectEvent.inTransaction(
  96. this.receipts.queue.transactionHash,
  97. this.timelock,
  98. 'CallScheduled',
  99. { id: timelockid },
  100. );
  101. expectEvent(
  102. this.receipts.execute,
  103. 'ProposalExecuted',
  104. { proposalId: this.id },
  105. );
  106. await expectEvent.inTransaction(
  107. this.receipts.execute.transactionHash,
  108. this.timelock,
  109. 'CallExecuted',
  110. { id: timelockid },
  111. );
  112. await expectEvent.inTransaction(
  113. this.receipts.execute.transactionHash,
  114. this.receiver,
  115. 'MockFunctionCalled',
  116. );
  117. });
  118. runGovernorWorkflow();
  119. });
  120. describe('executed by other proposer', function () {
  121. beforeEach(async function () {
  122. this.settings = {
  123. proposal: [
  124. [ this.receiver.address ],
  125. [ web3.utils.toWei('0') ],
  126. [ this.receiver.contract.methods.mockFunction().encodeABI() ],
  127. '<proposal description>',
  128. ],
  129. voters: [
  130. { voter: voter, support: Enums.VoteType.For },
  131. ],
  132. steps: {
  133. queue: { delay: 3600 },
  134. execute: { enable: false },
  135. },
  136. };
  137. });
  138. afterEach(async function () {
  139. await this.timelock.executeBatch(
  140. ...this.settings.proposal.slice(0, 3),
  141. '0x0',
  142. this.descriptionHash,
  143. );
  144. expect(await this.mock.state(this.id)).to.be.bignumber.equal(Enums.ProposalState.Executed);
  145. await expectRevert(
  146. this.mock.execute(...this.settings.proposal.slice(0, -1), this.descriptionHash),
  147. 'Governor: proposal not successful',
  148. );
  149. });
  150. runGovernorWorkflow();
  151. });
  152. describe('not queued', function () {
  153. beforeEach(async function () {
  154. this.settings = {
  155. proposal: [
  156. [ this.receiver.address ],
  157. [ web3.utils.toWei('0') ],
  158. [ this.receiver.contract.methods.mockFunction().encodeABI() ],
  159. '<proposal description>',
  160. ],
  161. voters: [
  162. { voter: voter, support: Enums.VoteType.For },
  163. ],
  164. steps: {
  165. queue: { enable: false },
  166. execute: { error: 'TimelockController: operation is not ready' },
  167. },
  168. };
  169. });
  170. afterEach(async function () {
  171. expect(await this.mock.state(this.id)).to.be.bignumber.equal(Enums.ProposalState.Succeeded);
  172. });
  173. runGovernorWorkflow();
  174. });
  175. describe('to early', function () {
  176. beforeEach(async function () {
  177. this.settings = {
  178. proposal: [
  179. [ this.receiver.address ],
  180. [ web3.utils.toWei('0') ],
  181. [ this.receiver.contract.methods.mockFunction().encodeABI() ],
  182. '<proposal description>',
  183. ],
  184. voters: [
  185. { voter: voter, support: Enums.VoteType.For },
  186. ],
  187. steps: {
  188. execute: { error: 'TimelockController: operation is not ready' },
  189. },
  190. };
  191. });
  192. afterEach(async function () {
  193. expect(await this.mock.state(this.id)).to.be.bignumber.equal(Enums.ProposalState.Queued);
  194. });
  195. runGovernorWorkflow();
  196. });
  197. describe('re-queue / re-execute', function () {
  198. beforeEach(async function () {
  199. this.settings = {
  200. proposal: [
  201. [ this.receiver.address ],
  202. [ web3.utils.toWei('0') ],
  203. [ this.receiver.contract.methods.mockFunction().encodeABI() ],
  204. '<proposal description>',
  205. ],
  206. voters: [
  207. { voter: voter, support: Enums.VoteType.For },
  208. ],
  209. steps: {
  210. queue: { delay: 3600 },
  211. },
  212. };
  213. });
  214. afterEach(async function () {
  215. expect(await this.mock.state(this.id)).to.be.bignumber.equal(Enums.ProposalState.Executed);
  216. await expectRevert(
  217. this.mock.queue(...this.settings.proposal.slice(0, -1), this.descriptionHash),
  218. 'Governor: proposal not successful',
  219. );
  220. await expectRevert(
  221. this.mock.execute(...this.settings.proposal.slice(0, -1), this.descriptionHash),
  222. 'Governor: proposal not successful',
  223. );
  224. });
  225. runGovernorWorkflow();
  226. });
  227. describe('cancel before queue prevents scheduling', function () {
  228. beforeEach(async function () {
  229. this.settings = {
  230. proposal: [
  231. [ this.receiver.address ],
  232. [ web3.utils.toWei('0') ],
  233. [ this.receiver.contract.methods.mockFunction().encodeABI() ],
  234. '<proposal description>',
  235. ],
  236. voters: [
  237. { voter: voter, support: Enums.VoteType.For },
  238. ],
  239. steps: {
  240. queue: { enable: false },
  241. execute: { enable: false },
  242. },
  243. };
  244. });
  245. afterEach(async function () {
  246. expect(await this.mock.state(this.id)).to.be.bignumber.equal(Enums.ProposalState.Succeeded);
  247. expectEvent(
  248. await this.mock.cancel(...this.settings.proposal.slice(0, -1), this.descriptionHash),
  249. 'ProposalCanceled',
  250. { proposalId: this.id },
  251. );
  252. expect(await this.mock.state(this.id)).to.be.bignumber.equal(Enums.ProposalState.Canceled);
  253. await expectRevert(
  254. this.mock.queue(...this.settings.proposal.slice(0, -1), this.descriptionHash),
  255. 'Governor: proposal not successful',
  256. );
  257. });
  258. runGovernorWorkflow();
  259. });
  260. describe('cancel after queue prevents execution', function () {
  261. beforeEach(async function () {
  262. this.settings = {
  263. proposal: [
  264. [ this.receiver.address ],
  265. [ web3.utils.toWei('0') ],
  266. [ this.receiver.contract.methods.mockFunction().encodeABI() ],
  267. '<proposal description>',
  268. ],
  269. voters: [
  270. { voter: voter, support: Enums.VoteType.For },
  271. ],
  272. steps: {
  273. queue: { delay: 3600 },
  274. execute: { enable: false },
  275. },
  276. };
  277. });
  278. afterEach(async function () {
  279. const timelockid = await this.timelock.hashOperationBatch(
  280. ...this.settings.proposal.slice(0, 3),
  281. '0x0',
  282. this.descriptionHash,
  283. );
  284. expect(await this.mock.state(this.id)).to.be.bignumber.equal(Enums.ProposalState.Queued);
  285. const receipt = await this.mock.cancel(...this.settings.proposal.slice(0, -1), this.descriptionHash);
  286. expectEvent(
  287. receipt,
  288. 'ProposalCanceled',
  289. { proposalId: this.id },
  290. );
  291. await expectEvent.inTransaction(
  292. receipt.receipt.transactionHash,
  293. this.timelock,
  294. 'Cancelled',
  295. { id: timelockid },
  296. );
  297. expect(await this.mock.state(this.id)).to.be.bignumber.equal(Enums.ProposalState.Canceled);
  298. await expectRevert(
  299. this.mock.execute(...this.settings.proposal.slice(0, -1), this.descriptionHash),
  300. 'Governor: proposal not successful',
  301. );
  302. });
  303. runGovernorWorkflow();
  304. });
  305. describe('relay', function () {
  306. beforeEach(async function () {
  307. await this.token.mint(this.mock.address, 1);
  308. this.call = [
  309. this.token.address,
  310. 0,
  311. this.token.contract.methods.transfer(other, 1).encodeABI(),
  312. ];
  313. });
  314. it('protected', async function () {
  315. await expectRevert(
  316. this.mock.relay(...this.call),
  317. 'Governor: onlyGovernance',
  318. );
  319. });
  320. it('protected against other proposers', async function () {
  321. await this.timelock.schedule(
  322. this.mock.address,
  323. web3.utils.toWei('0'),
  324. this.mock.contract.methods.relay(...this.call).encodeABI(),
  325. constants.ZERO_BYTES32,
  326. constants.ZERO_BYTES32,
  327. 3600,
  328. { from: admin },
  329. );
  330. await time.increase(3600);
  331. await expectRevert(
  332. this.timelock.execute(
  333. this.mock.address,
  334. web3.utils.toWei('0'),
  335. this.mock.contract.methods.relay(...this.call).encodeABI(),
  336. constants.ZERO_BYTES32,
  337. constants.ZERO_BYTES32,
  338. { from: admin },
  339. ),
  340. 'TimelockController: underlying transaction reverted',
  341. );
  342. });
  343. describe('using workflow', function () {
  344. beforeEach(async function () {
  345. this.settings = {
  346. proposal: [
  347. [
  348. this.mock.address,
  349. ],
  350. [
  351. web3.utils.toWei('0'),
  352. ],
  353. [
  354. this.mock.contract.methods.relay(...this.call).encodeABI(),
  355. ],
  356. '<proposal description>',
  357. ],
  358. voters: [
  359. { voter: voter, support: Enums.VoteType.For },
  360. ],
  361. steps: {
  362. queue: { delay: 7 * 86400 },
  363. },
  364. };
  365. expect(await this.token.balanceOf(this.mock.address), 1);
  366. expect(await this.token.balanceOf(other), 0);
  367. });
  368. afterEach(async function () {
  369. expect(await this.token.balanceOf(this.mock.address), 0);
  370. expect(await this.token.balanceOf(other), 1);
  371. });
  372. runGovernorWorkflow();
  373. });
  374. });
  375. describe('cancel on timelock is forwarded in state', function () {
  376. beforeEach(async function () {
  377. this.settings = {
  378. proposal: [
  379. [ this.receiver.address ],
  380. [ web3.utils.toWei('0') ],
  381. [ this.receiver.contract.methods.mockFunction().encodeABI() ],
  382. '<proposal description>',
  383. ],
  384. voters: [
  385. { voter: voter, support: Enums.VoteType.For },
  386. ],
  387. steps: {
  388. queue: { delay: 3600 },
  389. execute: { enable: false },
  390. },
  391. };
  392. });
  393. afterEach(async function () {
  394. const timelockid = await this.timelock.hashOperationBatch(
  395. ...this.settings.proposal.slice(0, 3),
  396. '0x0',
  397. this.descriptionHash,
  398. );
  399. expect(await this.mock.state(this.id)).to.be.bignumber.equal(Enums.ProposalState.Queued);
  400. const receipt = await this.timelock.cancel(timelockid, { from: admin });
  401. expectEvent(
  402. receipt,
  403. 'Cancelled',
  404. { id: timelockid },
  405. );
  406. expect(await this.mock.state(this.id)).to.be.bignumber.equal(Enums.ProposalState.Canceled);
  407. });
  408. runGovernorWorkflow();
  409. });
  410. describe('updateTimelock', function () {
  411. beforeEach(async function () {
  412. this.newTimelock = await Timelock.new(3600, [], []);
  413. });
  414. it('protected', async function () {
  415. await expectRevert(
  416. this.mock.updateTimelock(this.newTimelock.address),
  417. 'Governor: onlyGovernance',
  418. );
  419. });
  420. describe('using workflow', function () {
  421. beforeEach(async function () {
  422. this.settings = {
  423. proposal: [
  424. [ this.mock.address ],
  425. [ web3.utils.toWei('0') ],
  426. [ this.mock.contract.methods.updateTimelock(this.newTimelock.address).encodeABI() ],
  427. '<proposal description>',
  428. ],
  429. voters: [
  430. { voter: voter, support: Enums.VoteType.For },
  431. ],
  432. steps: {
  433. queue: { delay: 3600 },
  434. },
  435. };
  436. });
  437. afterEach(async function () {
  438. expectEvent(
  439. this.receipts.propose,
  440. 'ProposalCreated',
  441. { proposalId: this.id },
  442. );
  443. expectEvent(
  444. this.receipts.execute,
  445. 'ProposalExecuted',
  446. { proposalId: this.id },
  447. );
  448. expectEvent(
  449. this.receipts.execute,
  450. 'TimelockChange',
  451. { oldTimelock: this.timelock.address, newTimelock: this.newTimelock.address },
  452. );
  453. expect(await this.mock.timelock()).to.be.bignumber.equal(this.newTimelock.address);
  454. });
  455. runGovernorWorkflow();
  456. });
  457. });
  458. describe('clear queue of pending governor calls', function () {
  459. beforeEach(async function () {
  460. this.settings = {
  461. proposal: [
  462. [ this.mock.address ],
  463. [ web3.utils.toWei('0') ],
  464. [ this.mock.contract.methods.nonGovernanceFunction().encodeABI() ],
  465. '<proposal description>',
  466. ],
  467. voters: [
  468. { voter: voter, support: Enums.VoteType.For },
  469. ],
  470. steps: {
  471. queue: { delay: 3600 },
  472. },
  473. };
  474. });
  475. afterEach(async function () {
  476. expectEvent(
  477. this.receipts.execute,
  478. 'ProposalExecuted',
  479. { proposalId: this.id },
  480. );
  481. });
  482. runGovernorWorkflow();
  483. });
  484. });