GovernorTimelockControl.test.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  1. const { constants, expectEvent, expectRevert } = 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 [ voter ] = accounts;
  16. const name = 'OZ-Governor';
  17. // const version = '1';
  18. const tokenName = 'MockToken';
  19. const tokenSymbol = 'MTKN';
  20. const tokenSupply = web3.utils.toWei('100');
  21. beforeEach(async function () {
  22. const [ deployer ] = await web3.eth.getAccounts();
  23. this.token = await Token.new(tokenName, tokenSymbol);
  24. this.timelock = await Timelock.new(3600, [], []);
  25. this.mock = await Governor.new(name, this.token.address, 4, 16, this.timelock.address, 0);
  26. this.receiver = await CallReceiver.new();
  27. // normal setup: governor is proposer, everyone is executor, timelock is its own admin
  28. await this.timelock.grantRole(await this.timelock.PROPOSER_ROLE(), this.mock.address);
  29. await this.timelock.grantRole(await this.timelock.EXECUTOR_ROLE(), constants.ZERO_ADDRESS);
  30. await this.timelock.revokeRole(await this.timelock.TIMELOCK_ADMIN_ROLE(), deployer);
  31. await this.token.mint(voter, tokenSupply);
  32. await this.token.delegate(voter, { from: voter });
  33. });
  34. shouldSupportInterfaces([
  35. 'ERC165',
  36. 'Governor',
  37. 'GovernorTimelock',
  38. ]);
  39. it('doesn\'t accept ether transfers', async function () {
  40. await expectRevert.unspecified(web3.eth.sendTransaction({ from: voter, to: this.mock.address, value: 1 }));
  41. });
  42. it('post deployment check', async function () {
  43. expect(await this.mock.name()).to.be.equal(name);
  44. expect(await this.mock.token()).to.be.equal(this.token.address);
  45. expect(await this.mock.votingDelay()).to.be.bignumber.equal('4');
  46. expect(await this.mock.votingPeriod()).to.be.bignumber.equal('16');
  47. expect(await this.mock.quorum(0)).to.be.bignumber.equal('0');
  48. expect(await this.mock.timelock()).to.be.equal(this.timelock.address);
  49. });
  50. describe('nominal', function () {
  51. beforeEach(async function () {
  52. this.settings = {
  53. proposal: [
  54. [ this.receiver.address ],
  55. [ web3.utils.toWei('0') ],
  56. [ this.receiver.contract.methods.mockFunction().encodeABI() ],
  57. '<proposal description>',
  58. ],
  59. voters: [
  60. { voter: voter, support: Enums.VoteType.For },
  61. ],
  62. steps: {
  63. queue: { delay: 3600 },
  64. },
  65. };
  66. });
  67. afterEach(async function () {
  68. const timelockid = await this.timelock.hashOperationBatch(
  69. ...this.settings.proposal.slice(0, 3),
  70. '0x0',
  71. this.descriptionHash,
  72. );
  73. expectEvent(
  74. this.receipts.propose,
  75. 'ProposalCreated',
  76. { proposalId: this.id },
  77. );
  78. expectEvent(
  79. this.receipts.queue,
  80. 'ProposalQueued',
  81. { proposalId: this.id },
  82. );
  83. await expectEvent.inTransaction(
  84. this.receipts.queue.transactionHash,
  85. this.timelock,
  86. 'CallScheduled',
  87. { id: timelockid },
  88. );
  89. expectEvent(
  90. this.receipts.execute,
  91. 'ProposalExecuted',
  92. { proposalId: this.id },
  93. );
  94. await expectEvent.inTransaction(
  95. this.receipts.execute.transactionHash,
  96. this.timelock,
  97. 'CallExecuted',
  98. { id: timelockid },
  99. );
  100. await expectEvent.inTransaction(
  101. this.receipts.execute.transactionHash,
  102. this.receiver,
  103. 'MockFunctionCalled',
  104. );
  105. });
  106. runGovernorWorkflow();
  107. });
  108. describe('executed by other proposer', function () {
  109. beforeEach(async function () {
  110. this.settings = {
  111. proposal: [
  112. [ this.receiver.address ],
  113. [ web3.utils.toWei('0') ],
  114. [ this.receiver.contract.methods.mockFunction().encodeABI() ],
  115. '<proposal description>',
  116. ],
  117. voters: [
  118. { voter: voter, support: Enums.VoteType.For },
  119. ],
  120. steps: {
  121. queue: { delay: 3600 },
  122. execute: { enable: false },
  123. },
  124. };
  125. });
  126. afterEach(async function () {
  127. await this.timelock.executeBatch(
  128. ...this.settings.proposal.slice(0, 3),
  129. '0x0',
  130. this.descriptionHash,
  131. );
  132. expect(await this.mock.state(this.id)).to.be.bignumber.equal(Enums.ProposalState.Executed);
  133. await expectRevert(
  134. this.mock.execute(...this.settings.proposal.slice(0, -1), this.descriptionHash),
  135. 'Governor: proposal not successful',
  136. );
  137. });
  138. runGovernorWorkflow();
  139. });
  140. describe('not queued', function () {
  141. beforeEach(async function () {
  142. this.settings = {
  143. proposal: [
  144. [ this.receiver.address ],
  145. [ web3.utils.toWei('0') ],
  146. [ this.receiver.contract.methods.mockFunction().encodeABI() ],
  147. '<proposal description>',
  148. ],
  149. voters: [
  150. { voter: voter, support: Enums.VoteType.For },
  151. ],
  152. steps: {
  153. queue: { enable: false },
  154. execute: { error: 'TimelockController: operation is not ready' },
  155. },
  156. };
  157. });
  158. afterEach(async function () {
  159. expect(await this.mock.state(this.id)).to.be.bignumber.equal(Enums.ProposalState.Succeeded);
  160. });
  161. runGovernorWorkflow();
  162. });
  163. describe('to early', function () {
  164. beforeEach(async function () {
  165. this.settings = {
  166. proposal: [
  167. [ this.receiver.address ],
  168. [ web3.utils.toWei('0') ],
  169. [ this.receiver.contract.methods.mockFunction().encodeABI() ],
  170. '<proposal description>',
  171. ],
  172. voters: [
  173. { voter: voter, support: Enums.VoteType.For },
  174. ],
  175. steps: {
  176. execute: { error: 'TimelockController: operation is not ready' },
  177. },
  178. };
  179. });
  180. afterEach(async function () {
  181. expect(await this.mock.state(this.id)).to.be.bignumber.equal(Enums.ProposalState.Queued);
  182. });
  183. runGovernorWorkflow();
  184. });
  185. describe('re-queue / re-execute', function () {
  186. beforeEach(async function () {
  187. this.settings = {
  188. proposal: [
  189. [ this.receiver.address ],
  190. [ web3.utils.toWei('0') ],
  191. [ this.receiver.contract.methods.mockFunction().encodeABI() ],
  192. '<proposal description>',
  193. ],
  194. voters: [
  195. { voter: voter, support: Enums.VoteType.For },
  196. ],
  197. steps: {
  198. queue: { delay: 3600 },
  199. },
  200. };
  201. });
  202. afterEach(async function () {
  203. expect(await this.mock.state(this.id)).to.be.bignumber.equal(Enums.ProposalState.Executed);
  204. await expectRevert(
  205. this.mock.queue(...this.settings.proposal.slice(0, -1), this.descriptionHash),
  206. 'Governor: proposal not successful',
  207. );
  208. await expectRevert(
  209. this.mock.execute(...this.settings.proposal.slice(0, -1), this.descriptionHash),
  210. 'Governor: proposal not successful',
  211. );
  212. });
  213. runGovernorWorkflow();
  214. });
  215. describe('cancel before queue prevents scheduling', function () {
  216. beforeEach(async function () {
  217. this.settings = {
  218. proposal: [
  219. [ this.receiver.address ],
  220. [ web3.utils.toWei('0') ],
  221. [ this.receiver.contract.methods.mockFunction().encodeABI() ],
  222. '<proposal description>',
  223. ],
  224. voters: [
  225. { voter: voter, support: Enums.VoteType.For },
  226. ],
  227. steps: {
  228. queue: { enable: false },
  229. execute: { enable: false },
  230. },
  231. };
  232. });
  233. afterEach(async function () {
  234. expect(await this.mock.state(this.id)).to.be.bignumber.equal(Enums.ProposalState.Succeeded);
  235. expectEvent(
  236. await this.mock.cancel(...this.settings.proposal.slice(0, -1), this.descriptionHash),
  237. 'ProposalCanceled',
  238. { proposalId: this.id },
  239. );
  240. expect(await this.mock.state(this.id)).to.be.bignumber.equal(Enums.ProposalState.Canceled);
  241. await expectRevert(
  242. this.mock.queue(...this.settings.proposal.slice(0, -1), this.descriptionHash),
  243. 'Governor: proposal not successful',
  244. );
  245. });
  246. runGovernorWorkflow();
  247. });
  248. describe('cancel after queue prevents execution', function () {
  249. beforeEach(async function () {
  250. this.settings = {
  251. proposal: [
  252. [ this.receiver.address ],
  253. [ web3.utils.toWei('0') ],
  254. [ this.receiver.contract.methods.mockFunction().encodeABI() ],
  255. '<proposal description>',
  256. ],
  257. voters: [
  258. { voter: voter, support: Enums.VoteType.For },
  259. ],
  260. steps: {
  261. queue: { delay: 3600 },
  262. execute: { enable: false },
  263. },
  264. };
  265. });
  266. afterEach(async function () {
  267. const timelockid = await this.timelock.hashOperationBatch(
  268. ...this.settings.proposal.slice(0, 3),
  269. '0x0',
  270. this.descriptionHash,
  271. );
  272. expect(await this.mock.state(this.id)).to.be.bignumber.equal(Enums.ProposalState.Queued);
  273. const receipt = await this.mock.cancel(...this.settings.proposal.slice(0, -1), this.descriptionHash);
  274. expectEvent(
  275. receipt,
  276. 'ProposalCanceled',
  277. { proposalId: this.id },
  278. );
  279. await expectEvent.inTransaction(
  280. receipt.receipt.transactionHash,
  281. this.timelock,
  282. 'Cancelled',
  283. { id: timelockid },
  284. );
  285. expect(await this.mock.state(this.id)).to.be.bignumber.equal(Enums.ProposalState.Canceled);
  286. await expectRevert(
  287. this.mock.execute(...this.settings.proposal.slice(0, -1), this.descriptionHash),
  288. 'Governor: proposal not successful',
  289. );
  290. });
  291. runGovernorWorkflow();
  292. });
  293. describe('updateTimelock', function () {
  294. beforeEach(async function () {
  295. this.newTimelock = await Timelock.new(3600, [], []);
  296. });
  297. it('protected', async function () {
  298. await expectRevert(
  299. this.mock.updateTimelock(this.newTimelock.address),
  300. 'Governor: onlyGovernance',
  301. );
  302. });
  303. describe('using workflow', function () {
  304. beforeEach(async function () {
  305. this.settings = {
  306. proposal: [
  307. [ this.mock.address ],
  308. [ web3.utils.toWei('0') ],
  309. [ this.mock.contract.methods.updateTimelock(this.newTimelock.address).encodeABI() ],
  310. '<proposal description>',
  311. ],
  312. voters: [
  313. { voter: voter, support: Enums.VoteType.For },
  314. ],
  315. steps: {
  316. queue: { delay: 3600 },
  317. },
  318. };
  319. });
  320. afterEach(async function () {
  321. expectEvent(
  322. this.receipts.propose,
  323. 'ProposalCreated',
  324. { proposalId: this.id },
  325. );
  326. expectEvent(
  327. this.receipts.execute,
  328. 'ProposalExecuted',
  329. { proposalId: this.id },
  330. );
  331. expectEvent(
  332. this.receipts.execute,
  333. 'TimelockChange',
  334. { oldTimelock: this.timelock.address, newTimelock: this.newTimelock.address },
  335. );
  336. expect(await this.mock.timelock()).to.be.bignumber.equal(this.newTimelock.address);
  337. });
  338. runGovernorWorkflow();
  339. });
  340. });
  341. });