GovernorNoncesKeyed.test.js 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  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 { getDomain, Ballot, ExtendedBallot } = require('../../helpers/eip712');
  6. const { VoteType } = require('../../helpers/enums');
  7. const { shouldBehaveLikeNoncesKeyed } = require('../../utils/Nonces.behavior');
  8. const name = 'OZ-Governor';
  9. const version = '1';
  10. const tokenName = 'MockToken';
  11. const tokenSymbol = 'MTKN';
  12. const tokenSupply = ethers.parseEther('100');
  13. const votingDelay = 4n;
  14. const votingPeriod = 16n;
  15. const value = ethers.parseEther('1');
  16. const signBallot = account => (contract, message) =>
  17. getDomain(contract).then(domain => account.signTypedData(domain, { Ballot }, message));
  18. const signExtendedBallot = account => (contract, message) =>
  19. getDomain(contract).then(domain => account.signTypedData(domain, { ExtendedBallot }, message));
  20. describe('GovernorNoncesKeyed', function () {
  21. const fixture = async () => {
  22. const [owner, proposer, voter1, voter2, voter3, voter4, userEOA] = await ethers.getSigners();
  23. const receiver = await ethers.deployContract('CallReceiverMock');
  24. const token = await ethers.deployContract('$ERC20Votes', [tokenName, tokenSymbol, tokenName, version]);
  25. const mock = await ethers.deployContract('$GovernorNoncesKeyedMock', [
  26. name, // name
  27. votingDelay, // initialVotingDelay
  28. votingPeriod, // initialVotingPeriod
  29. 0n, // initialProposalThreshold
  30. token, // tokenAddress
  31. 10n, // quorumNumeratorValue
  32. ]);
  33. await owner.sendTransaction({ to: mock, value });
  34. await token.$_mint(owner, tokenSupply);
  35. const helper = new GovernorHelper(mock, 'blocknumber');
  36. await helper.connect(owner).delegate({ token: token, to: voter1, value: ethers.parseEther('10') });
  37. await helper.connect(owner).delegate({ token: token, to: voter2, value: ethers.parseEther('7') });
  38. await helper.connect(owner).delegate({ token: token, to: voter3, value: ethers.parseEther('5') });
  39. await helper.connect(owner).delegate({ token: token, to: voter4, value: ethers.parseEther('2') });
  40. return {
  41. owner,
  42. proposer,
  43. voter1,
  44. voter2,
  45. voter3,
  46. voter4,
  47. userEOA,
  48. receiver,
  49. token,
  50. mock,
  51. helper,
  52. };
  53. };
  54. beforeEach(async function () {
  55. Object.assign(this, await loadFixture(fixture));
  56. // default proposal
  57. this.proposal = this.helper.setProposal(
  58. [
  59. {
  60. target: this.receiver.target,
  61. value,
  62. data: this.receiver.interface.encodeFunctionData('mockFunction'),
  63. },
  64. ],
  65. '<proposal description>',
  66. );
  67. });
  68. it('deployment check', async function () {
  69. await expect(this.mock.name()).to.eventually.equal(name);
  70. await expect(this.mock.token()).to.eventually.equal(this.token);
  71. await expect(this.mock.votingDelay()).to.eventually.equal(votingDelay);
  72. await expect(this.mock.votingPeriod()).to.eventually.equal(votingPeriod);
  73. });
  74. describe('vote with signature', function () {
  75. for (const nonceType of ['default', 'keyed']) {
  76. describe(`with ${nonceType} nonce`, function () {
  77. beforeEach(async function () {
  78. await this.helper.propose();
  79. const maskedProposalId = BigInt(this.helper.id) & (2n ** 192n - 1n);
  80. this.getNonce = async address => {
  81. return await (nonceType === 'default'
  82. ? this.mock.nonces(address)
  83. : this.mock['nonces(address,uint192)'](address, maskedProposalId));
  84. };
  85. });
  86. it('votes with an EOA signature', async function () {
  87. await this.token.connect(this.voter1).delegate(this.userEOA);
  88. const nonce = await this.getNonce(this.userEOA);
  89. await this.helper.waitForSnapshot();
  90. await expect(
  91. this.helper.vote({
  92. support: VoteType.For,
  93. voter: this.userEOA.address,
  94. nonce,
  95. signature: signBallot(this.userEOA),
  96. }),
  97. )
  98. .to.emit(this.mock, 'VoteCast')
  99. .withArgs(this.userEOA, this.proposal.id, VoteType.For, ethers.parseEther('10'), '');
  100. await this.helper.waitForDeadline();
  101. await this.helper.execute();
  102. // After
  103. expect(await this.mock.hasVoted(this.proposal.id, this.userEOA)).to.be.true;
  104. expect(await this.getNonce(this.userEOA)).to.equal(nonce + 1n);
  105. });
  106. it('votes with an EOA signature with reason', async function () {
  107. await this.token.connect(this.voter1).delegate(this.userEOA);
  108. const nonce = await this.getNonce(this.userEOA);
  109. await this.helper.waitForSnapshot();
  110. await expect(
  111. this.helper.vote({
  112. support: VoteType.For,
  113. voter: this.userEOA.address,
  114. nonce,
  115. reason: 'This is an example reason',
  116. signature: signExtendedBallot(this.userEOA),
  117. }),
  118. )
  119. .to.emit(this.mock, 'VoteCast')
  120. .withArgs(
  121. this.userEOA,
  122. this.proposal.id,
  123. VoteType.For,
  124. ethers.parseEther('10'),
  125. 'This is an example reason',
  126. );
  127. await this.helper.waitForDeadline();
  128. await this.helper.execute();
  129. // After
  130. expect(await this.mock.hasVoted(this.proposal.id, this.userEOA)).to.be.true;
  131. expect(await this.getNonce(this.userEOA)).to.equal(nonce + 1n);
  132. });
  133. it('votes with a valid EIP-1271 signature', async function () {
  134. const wallet = await ethers.deployContract('ERC1271WalletMock', [this.userEOA]);
  135. await this.token.connect(this.voter1).delegate(wallet);
  136. const nonce = await this.getNonce(wallet.target);
  137. await this.helper.waitForSnapshot();
  138. await expect(
  139. this.helper.vote({
  140. support: VoteType.For,
  141. voter: wallet.target,
  142. nonce,
  143. signature: signBallot(this.userEOA),
  144. }),
  145. )
  146. .to.emit(this.mock, 'VoteCast')
  147. .withArgs(wallet, this.proposal.id, VoteType.For, ethers.parseEther('10'), '');
  148. await this.helper.waitForDeadline();
  149. await this.helper.execute();
  150. // After
  151. expect(await this.mock.hasVoted(this.proposal.id, wallet)).to.be.true;
  152. expect(await this.getNonce(wallet)).to.equal(nonce + 1n);
  153. });
  154. afterEach('no other votes are cast', async function () {
  155. expect(await this.mock.hasVoted(this.proposal.id, this.owner)).to.be.false;
  156. expect(await this.mock.hasVoted(this.proposal.id, this.voter1)).to.be.false;
  157. expect(await this.mock.hasVoted(this.proposal.id, this.voter2)).to.be.false;
  158. });
  159. });
  160. }
  161. });
  162. describe('on vote by signature', function () {
  163. beforeEach(async function () {
  164. await this.token.connect(this.voter1).delegate(this.userEOA);
  165. // Run proposal
  166. await this.helper.propose();
  167. await this.helper.waitForSnapshot();
  168. });
  169. it('if signature does not match signer', async function () {
  170. const nonce = await this.mock.nonces(this.userEOA);
  171. function tamper(str, index, mask) {
  172. const arrayStr = ethers.getBytes(str);
  173. arrayStr[index] ^= mask;
  174. return ethers.hexlify(arrayStr);
  175. }
  176. const voteParams = {
  177. support: VoteType.For,
  178. voter: this.userEOA.address,
  179. nonce,
  180. signature: (...args) => signBallot(this.userEOA)(...args).then(sig => tamper(sig, 42, 0xff)),
  181. };
  182. await expect(this.helper.vote(voteParams))
  183. .to.be.revertedWithCustomError(this.mock, 'GovernorInvalidSignature')
  184. .withArgs(voteParams.voter);
  185. });
  186. for (const nonceType of ['default', 'keyed']) {
  187. it(`if vote nonce is incorrect with ${nonceType} nonce`, async function () {
  188. const nonce = await (nonceType === 'default'
  189. ? this.mock.nonces(this.userEOA)
  190. : this.mock['nonces(address,uint192)'](this.userEOA, BigInt(this.helper.id) & (2n ** 192n - 1n)));
  191. const voteParams = {
  192. support: VoteType.For,
  193. voter: this.userEOA.address,
  194. nonce: nonce + 1n,
  195. signature: signBallot(this.userEOA),
  196. };
  197. await expect(this.helper.vote(voteParams))
  198. .to.be.revertedWithCustomError(this.mock, 'GovernorInvalidSignature')
  199. .withArgs(voteParams.voter);
  200. });
  201. }
  202. });
  203. shouldBehaveLikeNoncesKeyed();
  204. });