GovernorSuperQuorum.test.js 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  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 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 name = 'OZ-Governor';
  16. const version = '1';
  17. const tokenName = 'MockToken';
  18. const tokenSymbol = 'MTKN';
  19. const tokenSupply = ethers.parseEther('100');
  20. const votingDelay = 4n;
  21. const votingPeriod = 16n;
  22. const quorum = 10n;
  23. const superQuorum = 40n;
  24. const value = ethers.parseEther('1');
  25. const delay = time.duration.hours(1n);
  26. describe('GovernorSuperQuorum', function () {
  27. for (const { Token, mode } of TOKENS) {
  28. const fixture = async () => {
  29. const [proposer, voter1, voter2, voter3, voter4, voter5] = await ethers.getSigners();
  30. const receiver = await ethers.deployContract('CallReceiverMock');
  31. const timelock = await ethers.deployContract('TimelockController', [delay, [], [], proposer]);
  32. const token = await ethers.deployContract(Token, [tokenName, tokenSymbol, tokenName, version]);
  33. const mock = await ethers.deployContract('$GovernorSuperQuorumMock', [
  34. name,
  35. votingDelay, // initialVotingDelay
  36. votingPeriod, // initialVotingPeriod
  37. 0n, // initialProposalThreshold
  38. token,
  39. timelock,
  40. quorum,
  41. superQuorum,
  42. ]);
  43. await proposer.sendTransaction({ to: timelock, value });
  44. await token.$_mint(proposer, tokenSupply);
  45. await timelock.grantRole(PROPOSER_ROLE, mock);
  46. await timelock.grantRole(PROPOSER_ROLE, proposer);
  47. await timelock.grantRole(CANCELLER_ROLE, mock);
  48. await timelock.grantRole(CANCELLER_ROLE, proposer);
  49. await timelock.grantRole(EXECUTOR_ROLE, ethers.ZeroAddress);
  50. await timelock.revokeRole(DEFAULT_ADMIN_ROLE, proposer);
  51. const helper = new GovernorHelper(mock, mode);
  52. await helper.connect(proposer).delegate({ token, to: voter1, value: 40 });
  53. await helper.connect(proposer).delegate({ token, to: voter2, value: 30 });
  54. await helper.connect(proposer).delegate({ token, to: voter3, value: 20 });
  55. await helper.connect(proposer).delegate({ token, to: voter4, value: 15 });
  56. await helper.connect(proposer).delegate({ token, to: voter5, value: 5 });
  57. return { proposer, voter1, voter2, voter3, voter4, voter5, receiver, token, mock, timelock, helper };
  58. };
  59. describe(`using ${Token}`, function () {
  60. beforeEach(async function () {
  61. Object.assign(this, await loadFixture(fixture));
  62. // default proposal
  63. this.proposal = this.helper.setProposal(
  64. [
  65. {
  66. target: this.receiver.target,
  67. value,
  68. data: this.receiver.interface.encodeFunctionData('mockFunction'),
  69. },
  70. ],
  71. '<proposal description>',
  72. );
  73. });
  74. it('deployment check', async function () {
  75. await expect(this.mock.name()).to.eventually.equal(name);
  76. await expect(this.mock.token()).to.eventually.equal(this.token);
  77. await expect(this.mock.quorum(0)).to.eventually.equal(quorum);
  78. await expect(this.mock.superQuorum(0)).to.eventually.equal(superQuorum);
  79. });
  80. it('proposal succeeds early when super quorum is reached', async function () {
  81. await this.helper.connect(this.proposer).propose();
  82. await this.helper.waitForSnapshot();
  83. // Vote with voter2 (30) - above quorum (10) but below super quorum (40)
  84. await this.helper.connect(this.voter2).vote({ support: VoteType.For });
  85. await expect(this.mock.state(this.proposal.id)).to.eventually.equal(ProposalState.Active);
  86. // Vote with voter3 (20) to reach super quorum (50 total > 40)
  87. await this.helper.connect(this.voter3).vote({ support: VoteType.For });
  88. await expect(this.mock.proposalEta(this.proposal.id)).to.eventually.equal(0);
  89. // Should be succeeded since we reached super quorum and no eta is set
  90. await expect(this.mock.state(this.proposal.id)).to.eventually.equal(ProposalState.Succeeded);
  91. });
  92. it('proposal remains active if super quorum is not reached', async function () {
  93. await this.helper.connect(this.proposer).propose();
  94. await this.helper.waitForSnapshot();
  95. // Vote with voter4 (15) - below super quorum (40) but above quorum (10)
  96. await this.helper.connect(this.voter4).vote({ support: VoteType.For });
  97. await expect(this.mock.state(this.proposal.id)).to.eventually.equal(ProposalState.Active);
  98. // Vote with voter5 (5) - still below super quorum (total 20 < 40)
  99. await this.helper.connect(this.voter5).vote({ support: VoteType.For });
  100. await expect(this.mock.state(this.proposal.id)).to.eventually.equal(ProposalState.Active);
  101. // Wait for deadline
  102. await this.helper.waitForDeadline(1n);
  103. // Should succeed since deadline passed and we have enough support (20 > 10 quorum)
  104. await expect(this.mock.state(this.proposal.id)).to.eventually.equal(ProposalState.Succeeded);
  105. });
  106. it('proposal remains active if super quorum is reached but vote fails', async function () {
  107. await this.helper.connect(this.proposer).propose();
  108. await this.helper.waitForSnapshot();
  109. // Vote against with voter2 and voter3 (50)
  110. await this.helper.connect(this.voter2).vote({ support: VoteType.Against });
  111. await this.helper.connect(this.voter3).vote({ support: VoteType.Against });
  112. // Vote for with voter1 (40) (reaching super quorum)
  113. await this.helper.connect(this.voter1).vote({ support: VoteType.For });
  114. // should be active since super quorum is reached but vote fails
  115. await expect(this.mock.state(this.proposal.id)).to.eventually.equal(ProposalState.Active);
  116. // wait for deadline
  117. await this.helper.waitForDeadline(1n);
  118. // should be defeated since against votes are higher
  119. await expect(this.mock.state(this.proposal.id)).to.eventually.equal(ProposalState.Defeated);
  120. });
  121. it('proposal is queued if super quorum is reached and eta is set', async function () {
  122. await this.helper.connect(this.proposer).propose();
  123. await this.helper.waitForSnapshot();
  124. // Vote with voter1 (40) - reaching super quorum
  125. await this.helper.connect(this.voter1).vote({ support: VoteType.For });
  126. await this.helper.queue();
  127. // Queueing should set eta
  128. await expect(this.mock.proposalEta(this.proposal.id)).to.eventually.not.equal(0);
  129. // Should be queued since we reached super quorum and eta is set
  130. await expect(this.mock.state(this.proposal.id)).to.eventually.equal(ProposalState.Queued);
  131. });
  132. });
  133. }
  134. });