GovernorVotesQuorumFraction.test.js 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. const { ethers } = require('hardhat');
  2. const { expect } = require('chai');
  3. const { loadFixture, mine } = 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 ratio = 8n; // percents
  17. const newRatio = 6n; // percents
  18. const votingDelay = 4n;
  19. const votingPeriod = 16n;
  20. const value = ethers.parseEther('1');
  21. describe('GovernorVotesQuorumFraction', function () {
  22. for (const { Token, mode } of TOKENS) {
  23. const fixture = async () => {
  24. const [owner, voter1, voter2, voter3, voter4] = await ethers.getSigners();
  25. const receiver = await ethers.deployContract('CallReceiverMock');
  26. const token = await ethers.deployContract(Token, [tokenName, tokenSymbol, version]);
  27. const mock = await ethers.deployContract('$GovernorMock', [name, votingDelay, votingPeriod, 0n, token, ratio]);
  28. await owner.sendTransaction({ to: mock, value });
  29. await token.$_mint(owner, tokenSupply);
  30. const helper = new GovernorHelper(mock, mode);
  31. await helper.connect(owner).delegate({ token, to: voter1, value: ethers.parseEther('10') });
  32. await helper.connect(owner).delegate({ token, to: voter2, value: ethers.parseEther('7') });
  33. await helper.connect(owner).delegate({ token, to: voter3, value: ethers.parseEther('5') });
  34. await helper.connect(owner).delegate({ token, to: voter4, value: ethers.parseEther('2') });
  35. return { owner, voter1, voter2, voter3, voter4, receiver, token, mock, helper };
  36. };
  37. describe(`using ${Token}`, function () {
  38. beforeEach(async function () {
  39. Object.assign(this, await loadFixture(fixture));
  40. // default proposal
  41. this.proposal = this.helper.setProposal(
  42. [
  43. {
  44. target: this.receiver.target,
  45. value,
  46. data: this.receiver.interface.encodeFunctionData('mockFunction'),
  47. },
  48. ],
  49. '<proposal description>',
  50. );
  51. });
  52. it('deployment check', async function () {
  53. expect(await this.mock.name()).to.equal(name);
  54. expect(await this.mock.token()).to.equal(this.token);
  55. expect(await this.mock.votingDelay()).to.equal(votingDelay);
  56. expect(await this.mock.votingPeriod()).to.equal(votingPeriod);
  57. expect(await this.mock.quorum(0)).to.equal(0n);
  58. expect(await this.mock.quorumNumerator()).to.equal(ratio);
  59. expect(await this.mock.quorumDenominator()).to.equal(100n);
  60. expect(await time.clock[mode]().then(clock => this.mock.quorum(clock - 1n))).to.equal(
  61. (tokenSupply * ratio) / 100n,
  62. );
  63. });
  64. it('quroum reached', async function () {
  65. await this.helper.propose();
  66. await this.helper.waitForSnapshot();
  67. await this.helper.connect(this.voter1).vote({ support: VoteType.For });
  68. await this.helper.waitForDeadline();
  69. await this.helper.execute();
  70. });
  71. it('quroum not reached', async function () {
  72. await this.helper.propose();
  73. await this.helper.waitForSnapshot();
  74. await this.helper.connect(this.voter2).vote({ support: VoteType.For });
  75. await this.helper.waitForDeadline();
  76. await expect(this.helper.execute())
  77. .to.be.revertedWithCustomError(this.mock, 'GovernorUnexpectedProposalState')
  78. .withArgs(
  79. this.proposal.id,
  80. ProposalState.Defeated,
  81. GovernorHelper.proposalStatesToBitMap([ProposalState.Succeeded, ProposalState.Queued]),
  82. );
  83. });
  84. describe('onlyGovernance updates', function () {
  85. it('updateQuorumNumerator is protected', async function () {
  86. await expect(this.mock.connect(this.owner).updateQuorumNumerator(newRatio))
  87. .to.be.revertedWithCustomError(this.mock, 'GovernorOnlyExecutor')
  88. .withArgs(this.owner);
  89. });
  90. it('can updateQuorumNumerator through governance', async function () {
  91. this.helper.setProposal(
  92. [
  93. {
  94. target: this.mock.target,
  95. data: this.mock.interface.encodeFunctionData('updateQuorumNumerator', [newRatio]),
  96. },
  97. ],
  98. '<proposal description>',
  99. );
  100. await this.helper.propose();
  101. await this.helper.waitForSnapshot();
  102. await this.helper.connect(this.voter1).vote({ support: VoteType.For });
  103. await this.helper.waitForDeadline();
  104. await expect(this.helper.execute()).to.emit(this.mock, 'QuorumNumeratorUpdated').withArgs(ratio, newRatio);
  105. expect(await this.mock.quorumNumerator()).to.equal(newRatio);
  106. expect(await this.mock.quorumDenominator()).to.equal(100n);
  107. // it takes one block for the new quorum to take effect
  108. expect(await time.clock[mode]().then(blockNumber => this.mock.quorum(blockNumber - 1n))).to.equal(
  109. (tokenSupply * ratio) / 100n,
  110. );
  111. await mine();
  112. expect(await time.clock[mode]().then(blockNumber => this.mock.quorum(blockNumber - 1n))).to.equal(
  113. (tokenSupply * newRatio) / 100n,
  114. );
  115. });
  116. it('cannot updateQuorumNumerator over the maximum', async function () {
  117. const quorumNumerator = 101n;
  118. this.helper.setProposal(
  119. [
  120. {
  121. target: this.mock.target,
  122. data: this.mock.interface.encodeFunctionData('updateQuorumNumerator', [quorumNumerator]),
  123. },
  124. ],
  125. '<proposal description>',
  126. );
  127. await this.helper.propose();
  128. await this.helper.waitForSnapshot();
  129. await this.helper.connect(this.voter1).vote({ support: VoteType.For });
  130. await this.helper.waitForDeadline();
  131. const quorumDenominator = await this.mock.quorumDenominator();
  132. await expect(this.helper.execute())
  133. .to.be.revertedWithCustomError(this.mock, 'GovernorInvalidQuorumFraction')
  134. .withArgs(quorumNumerator, quorumDenominator);
  135. });
  136. });
  137. });
  138. }
  139. });