GovernorPreventLateQuorum.test.js 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. const { ethers } = require('hardhat');
  2. const { expect } = require('chai');
  3. const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
  4. const { GovernorHelper } = require('../../helpers/governance');
  5. const { ProposalState, VoteType } = require('../../helpers/enums');
  6. const time = require('../../helpers/time');
  7. const TOKENS = [
  8. { Token: '$ERC20Votes', mode: 'blocknumber' },
  9. { Token: '$ERC20VotesTimestampMock', mode: 'timestamp' },
  10. ];
  11. const name = 'OZ-Governor';
  12. const version = '1';
  13. const tokenName = 'MockToken';
  14. const tokenSymbol = 'MTKN';
  15. const tokenSupply = ethers.parseEther('100');
  16. const votingDelay = 4n;
  17. const votingPeriod = 16n;
  18. const lateQuorumVoteExtension = 8n;
  19. const quorum = ethers.parseEther('1');
  20. const value = ethers.parseEther('1');
  21. describe('GovernorPreventLateQuorum', function () {
  22. for (const { Token, mode } of TOKENS) {
  23. const fixture = async () => {
  24. const [owner, proposer, voter1, voter2, voter3, voter4] = await ethers.getSigners();
  25. const receiver = await ethers.deployContract('CallReceiverMock');
  26. const token = await ethers.deployContract(Token, [tokenName, tokenSymbol, tokenName, version]);
  27. const mock = await ethers.deployContract('$GovernorPreventLateQuorumMock', [
  28. name, // name
  29. votingDelay, // initialVotingDelay
  30. votingPeriod, // initialVotingPeriod
  31. 0n, // initialProposalThreshold
  32. token, // tokenAddress
  33. lateQuorumVoteExtension,
  34. quorum,
  35. ]);
  36. await owner.sendTransaction({ to: mock, value });
  37. await token.$_mint(owner, tokenSupply);
  38. const helper = new GovernorHelper(mock, mode);
  39. await helper.connect(owner).delegate({ token, to: voter1, value: ethers.parseEther('10') });
  40. await helper.connect(owner).delegate({ token, to: voter2, value: ethers.parseEther('7') });
  41. await helper.connect(owner).delegate({ token, to: voter3, value: ethers.parseEther('5') });
  42. await helper.connect(owner).delegate({ token, to: voter4, value: ethers.parseEther('2') });
  43. return { owner, proposer, voter1, voter2, voter3, voter4, receiver, token, mock, helper };
  44. };
  45. describe(`using ${Token}`, function () {
  46. beforeEach(async function () {
  47. Object.assign(this, await loadFixture(fixture));
  48. // initiate fresh proposal
  49. this.proposal = this.helper.setProposal(
  50. [
  51. {
  52. target: this.receiver.target,
  53. data: this.receiver.interface.encodeFunctionData('mockFunction'),
  54. value,
  55. },
  56. ],
  57. '<proposal description>',
  58. );
  59. });
  60. it('deployment check', async function () {
  61. expect(await this.mock.name()).to.equal(name);
  62. expect(await this.mock.token()).to.equal(this.token);
  63. expect(await this.mock.votingDelay()).to.equal(votingDelay);
  64. expect(await this.mock.votingPeriod()).to.equal(votingPeriod);
  65. expect(await this.mock.quorum(0)).to.equal(quorum);
  66. expect(await this.mock.lateQuorumVoteExtension()).to.equal(lateQuorumVoteExtension);
  67. });
  68. it('nominal workflow unaffected', async function () {
  69. const txPropose = await this.helper.connect(this.proposer).propose();
  70. await this.helper.waitForSnapshot();
  71. await this.helper.connect(this.voter1).vote({ support: VoteType.For });
  72. await this.helper.connect(this.voter2).vote({ support: VoteType.For });
  73. await this.helper.connect(this.voter3).vote({ support: VoteType.Against });
  74. await this.helper.connect(this.voter4).vote({ support: VoteType.Abstain });
  75. await this.helper.waitForDeadline();
  76. await this.helper.execute();
  77. expect(await this.mock.hasVoted(this.proposal.id, this.owner)).to.be.false;
  78. expect(await this.mock.hasVoted(this.proposal.id, this.voter1)).to.be.true;
  79. expect(await this.mock.hasVoted(this.proposal.id, this.voter2)).to.be.true;
  80. expect(await this.mock.hasVoted(this.proposal.id, this.voter3)).to.be.true;
  81. expect(await this.mock.hasVoted(this.proposal.id, this.voter4)).to.be.true;
  82. expect(await this.mock.proposalVotes(this.proposal.id)).to.deep.equal([
  83. ethers.parseEther('5'), // againstVotes
  84. ethers.parseEther('17'), // forVotes
  85. ethers.parseEther('2'), // abstainVotes
  86. ]);
  87. const voteStart = (await time.clockFromReceipt[mode](txPropose)) + votingDelay;
  88. const voteEnd = (await time.clockFromReceipt[mode](txPropose)) + votingDelay + votingPeriod;
  89. expect(await this.mock.proposalSnapshot(this.proposal.id)).to.equal(voteStart);
  90. expect(await this.mock.proposalDeadline(this.proposal.id)).to.equal(voteEnd);
  91. await expect(txPropose)
  92. .to.emit(this.mock, 'ProposalCreated')
  93. .withArgs(
  94. this.proposal.id,
  95. this.proposer,
  96. this.proposal.targets,
  97. this.proposal.values,
  98. this.proposal.signatures,
  99. this.proposal.data,
  100. voteStart,
  101. voteEnd,
  102. this.proposal.description,
  103. );
  104. });
  105. it('Delay is extended to prevent last minute take-over', async function () {
  106. const txPropose = await this.helper.connect(this.proposer).propose();
  107. // compute original schedule
  108. const snapshotTimepoint = (await time.clockFromReceipt[mode](txPropose)) + votingDelay;
  109. const deadlineTimepoint = (await time.clockFromReceipt[mode](txPropose)) + votingDelay + votingPeriod;
  110. expect(await this.mock.proposalSnapshot(this.proposal.id)).to.equal(snapshotTimepoint);
  111. expect(await this.mock.proposalDeadline(this.proposal.id)).to.equal(deadlineTimepoint);
  112. // wait for the last minute to vote
  113. await this.helper.waitForDeadline(-1n);
  114. const txVote = await this.helper.connect(this.voter2).vote({ support: VoteType.For });
  115. // cannot execute yet
  116. expect(await this.mock.state(this.proposal.id)).to.equal(ProposalState.Active);
  117. // compute new extended schedule
  118. const extendedDeadline = (await time.clockFromReceipt[mode](txVote)) + lateQuorumVoteExtension;
  119. expect(await this.mock.proposalSnapshot(this.proposal.id)).to.equal(snapshotTimepoint);
  120. expect(await this.mock.proposalDeadline(this.proposal.id)).to.equal(extendedDeadline);
  121. // still possible to vote
  122. await this.helper.connect(this.voter1).vote({ support: VoteType.Against });
  123. await this.helper.waitForDeadline();
  124. expect(await this.mock.state(this.proposal.id)).to.equal(ProposalState.Active);
  125. await this.helper.waitForDeadline(1n);
  126. expect(await this.mock.state(this.proposal.id)).to.equal(ProposalState.Defeated);
  127. // check extension event
  128. await expect(txVote).to.emit(this.mock, 'ProposalExtended').withArgs(this.proposal.id, extendedDeadline);
  129. });
  130. describe('onlyGovernance updates', function () {
  131. it('setLateQuorumVoteExtension is protected', async function () {
  132. await expect(this.mock.connect(this.owner).setLateQuorumVoteExtension(0n))
  133. .to.be.revertedWithCustomError(this.mock, 'GovernorOnlyExecutor')
  134. .withArgs(this.owner);
  135. });
  136. it('can setLateQuorumVoteExtension through governance', async function () {
  137. this.helper.setProposal(
  138. [
  139. {
  140. target: this.mock.target,
  141. data: this.mock.interface.encodeFunctionData('setLateQuorumVoteExtension', [0n]),
  142. },
  143. ],
  144. '<proposal description>',
  145. );
  146. await this.helper.propose();
  147. await this.helper.waitForSnapshot();
  148. await this.helper.connect(this.voter1).vote({ support: VoteType.For });
  149. await this.helper.waitForDeadline();
  150. await expect(this.helper.execute())
  151. .to.emit(this.mock, 'LateQuorumVoteExtensionSet')
  152. .withArgs(lateQuorumVoteExtension, 0n);
  153. expect(await this.mock.lateQuorumVoteExtension()).to.equal(0n);
  154. });
  155. });
  156. });
  157. }
  158. });