GovernorWithParams.test.js 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. const { BN, constants, expectEvent } = require('@openzeppelin/test-helpers');
  2. const { web3 } = require('@openzeppelin/test-helpers/src/setup');
  3. const Enums = require('../../helpers/enums');
  4. const ethSigUtil = require('eth-sig-util');
  5. const Wallet = require('ethereumjs-wallet').default;
  6. const { EIP712Domain } = require('../../helpers/eip712');
  7. const { fromRpcSig } = require('ethereumjs-util');
  8. const { runGovernorWorkflow } = require('../GovernorWorkflow.behavior');
  9. const { expect } = require('chai');
  10. const Token = artifacts.require('ERC20VotesCompMock');
  11. const Governor = artifacts.require('GovernorWithParamsMock');
  12. const CallReceiver = artifacts.require('CallReceiverMock');
  13. contract('GovernorWithParams', function (accounts) {
  14. const [owner, proposer, voter1, voter2, voter3, voter4] = accounts;
  15. const name = 'OZ-Governor';
  16. const version = '1';
  17. const tokenName = 'MockToken';
  18. const tokenSymbol = 'MTKN';
  19. const tokenSupply = web3.utils.toWei('100');
  20. const votingDelay = new BN(4);
  21. const votingPeriod = new BN(16);
  22. beforeEach(async function () {
  23. this.owner = owner;
  24. this.token = await Token.new(tokenName, tokenSymbol);
  25. this.mock = await Governor.new(name, this.token.address);
  26. this.receiver = await CallReceiver.new();
  27. await this.token.mint(owner, tokenSupply);
  28. await this.token.delegate(voter1, { from: voter1 });
  29. await this.token.delegate(voter2, { from: voter2 });
  30. await this.token.delegate(voter3, { from: voter3 });
  31. await this.token.delegate(voter4, { from: voter4 });
  32. });
  33. it('deployment check', async function () {
  34. expect(await this.mock.name()).to.be.equal(name);
  35. expect(await this.mock.token()).to.be.equal(this.token.address);
  36. expect(await this.mock.votingDelay()).to.be.bignumber.equal(votingDelay);
  37. expect(await this.mock.votingPeriod()).to.be.bignumber.equal(votingPeriod);
  38. });
  39. describe('nominal is unaffected', function () {
  40. beforeEach(async function () {
  41. this.settings = {
  42. proposal: [
  43. [this.receiver.address],
  44. [0],
  45. [this.receiver.contract.methods.mockFunction().encodeABI()],
  46. '<proposal description>',
  47. ],
  48. proposer,
  49. tokenHolder: owner,
  50. voters: [
  51. { voter: voter1, weight: web3.utils.toWei('1'), support: Enums.VoteType.For, reason: 'This is nice' },
  52. { voter: voter2, weight: web3.utils.toWei('7'), support: Enums.VoteType.For },
  53. { voter: voter3, weight: web3.utils.toWei('5'), support: Enums.VoteType.Against },
  54. { voter: voter4, weight: web3.utils.toWei('2'), support: Enums.VoteType.Abstain },
  55. ],
  56. };
  57. });
  58. afterEach(async function () {
  59. expect(await this.mock.hasVoted(this.id, owner)).to.be.equal(false);
  60. expect(await this.mock.hasVoted(this.id, voter1)).to.be.equal(true);
  61. expect(await this.mock.hasVoted(this.id, voter2)).to.be.equal(true);
  62. await this.mock.proposalVotes(this.id).then((result) => {
  63. for (const [key, value] of Object.entries(Enums.VoteType)) {
  64. expect(result[`${key.toLowerCase()}Votes`]).to.be.bignumber.equal(
  65. Object.values(this.settings.voters)
  66. .filter(({ support }) => support === value)
  67. .reduce((acc, { weight }) => acc.add(new BN(weight)), new BN('0')),
  68. );
  69. }
  70. });
  71. const startBlock = new BN(this.receipts.propose.blockNumber).add(votingDelay);
  72. const endBlock = new BN(this.receipts.propose.blockNumber).add(votingDelay).add(votingPeriod);
  73. expect(await this.mock.proposalSnapshot(this.id)).to.be.bignumber.equal(startBlock);
  74. expect(await this.mock.proposalDeadline(this.id)).to.be.bignumber.equal(endBlock);
  75. expectEvent(this.receipts.propose, 'ProposalCreated', {
  76. proposalId: this.id,
  77. proposer,
  78. targets: this.settings.proposal[0],
  79. // values: this.settings.proposal[1].map(value => new BN(value)),
  80. signatures: this.settings.proposal[2].map(() => ''),
  81. calldatas: this.settings.proposal[2],
  82. startBlock,
  83. endBlock,
  84. description: this.settings.proposal[3],
  85. });
  86. this.receipts.castVote.filter(Boolean).forEach((vote) => {
  87. const { voter } = vote.logs.filter(({ event }) => event === 'VoteCast').find(Boolean).args;
  88. expectEvent(
  89. vote,
  90. 'VoteCast',
  91. this.settings.voters.find(({ address }) => address === voter),
  92. );
  93. });
  94. expectEvent(this.receipts.execute, 'ProposalExecuted', { proposalId: this.id });
  95. await expectEvent.inTransaction(this.receipts.execute.transactionHash, this.receiver, 'MockFunctionCalled');
  96. });
  97. runGovernorWorkflow();
  98. });
  99. describe('Voting with params is properly supported', function () {
  100. const voter2Weight = web3.utils.toWei('1.0');
  101. beforeEach(async function () {
  102. this.settings = {
  103. proposal: [
  104. [this.receiver.address],
  105. [0],
  106. [this.receiver.contract.methods.mockFunction().encodeABI()],
  107. '<proposal description>',
  108. ],
  109. proposer,
  110. tokenHolder: owner,
  111. voters: [
  112. { voter: voter1, weight: web3.utils.toWei('0.2'), support: Enums.VoteType.Against },
  113. { voter: voter2, weight: voter2Weight }, // do not actually vote, only getting tokenss
  114. ],
  115. steps: {
  116. wait: { enable: false },
  117. execute: { enable: false },
  118. },
  119. };
  120. });
  121. afterEach(async function () {
  122. expect(await this.mock.state(this.id)).to.be.bignumber.equal(Enums.ProposalState.Active);
  123. const uintParam = new BN(1);
  124. const strParam = 'These are my params';
  125. const reducedWeight = new BN(voter2Weight).sub(uintParam);
  126. const params = web3.eth.abi.encodeParameters(['uint256', 'string'], [uintParam, strParam]);
  127. const tx = await this.mock.castVoteWithReasonAndParams(this.id, Enums.VoteType.For, '', params, { from: voter2 });
  128. expectEvent(tx, 'CountParams', { uintParam, strParam });
  129. expectEvent(tx, 'VoteCastWithParams', { voter: voter2, weight: reducedWeight, params });
  130. const votes = await this.mock.proposalVotes(this.id);
  131. expect(votes.forVotes).to.be.bignumber.equal(reducedWeight);
  132. });
  133. runGovernorWorkflow();
  134. });
  135. describe('Voting with params by signature is properly supported', function () {
  136. const voterBySig = Wallet.generate(); // generate voter by signature wallet
  137. const sigVoterWeight = web3.utils.toWei('1.0');
  138. beforeEach(async function () {
  139. this.chainId = await web3.eth.getChainId();
  140. this.voter = web3.utils.toChecksumAddress(voterBySig.getAddressString());
  141. // use delegateBySig to enable vote delegation sig voting wallet
  142. const { v, r, s } = fromRpcSig(
  143. ethSigUtil.signTypedMessage(voterBySig.getPrivateKey(), {
  144. data: {
  145. types: {
  146. EIP712Domain,
  147. Delegation: [
  148. { name: 'delegatee', type: 'address' },
  149. { name: 'nonce', type: 'uint256' },
  150. { name: 'expiry', type: 'uint256' },
  151. ],
  152. },
  153. domain: { name: tokenName, version: '1', chainId: this.chainId, verifyingContract: this.token.address },
  154. primaryType: 'Delegation',
  155. message: { delegatee: this.voter, nonce: 0, expiry: constants.MAX_UINT256 },
  156. },
  157. }),
  158. );
  159. await this.token.delegateBySig(this.voter, 0, constants.MAX_UINT256, v, r, s);
  160. this.settings = {
  161. proposal: [
  162. [this.receiver.address],
  163. [0],
  164. [this.receiver.contract.methods.mockFunction().encodeABI()],
  165. '<proposal description>',
  166. ],
  167. proposer,
  168. tokenHolder: owner,
  169. voters: [
  170. { voter: voter1, weight: web3.utils.toWei('0.2'), support: Enums.VoteType.Against },
  171. { voter: this.voter, weight: sigVoterWeight }, // do not actually vote, only getting tokens
  172. ],
  173. steps: {
  174. wait: { enable: false },
  175. execute: { enable: false },
  176. },
  177. };
  178. });
  179. afterEach(async function () {
  180. expect(await this.mock.state(this.id)).to.be.bignumber.equal(Enums.ProposalState.Active);
  181. const reason = 'This is my reason';
  182. const uintParam = new BN(1);
  183. const strParam = 'These are my params';
  184. const reducedWeight = new BN(sigVoterWeight).sub(uintParam);
  185. const params = web3.eth.abi.encodeParameters(['uint256', 'string'], [uintParam, strParam]);
  186. // prepare signature for vote by signature
  187. const { v, r, s } = fromRpcSig(
  188. ethSigUtil.signTypedMessage(voterBySig.getPrivateKey(), {
  189. data: {
  190. types: {
  191. EIP712Domain,
  192. ExtendedBallot: [
  193. { name: 'proposalId', type: 'uint256' },
  194. { name: 'support', type: 'uint8' },
  195. { name: 'reason', type: 'string' },
  196. { name: 'params', type: 'bytes' },
  197. ],
  198. },
  199. domain: { name, version, chainId: this.chainId, verifyingContract: this.mock.address },
  200. primaryType: 'ExtendedBallot',
  201. message: { proposalId: this.id, support: Enums.VoteType.For, reason, params },
  202. },
  203. }),
  204. );
  205. const tx = await this.mock.castVoteWithReasonAndParamsBySig(this.id, Enums.VoteType.For, reason, params, v, r, s);
  206. expectEvent(tx, 'CountParams', { uintParam, strParam });
  207. expectEvent(tx, 'VoteCastWithParams', { voter: this.voter, weight: reducedWeight, params });
  208. const votes = await this.mock.proposalVotes(this.id);
  209. expect(votes.forVotes).to.be.bignumber.equal(reducedWeight);
  210. });
  211. runGovernorWorkflow();
  212. });
  213. });