GovernorCountingOverridable.test.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  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 { getDomain, OverrideBallot } = require('../../helpers/eip712');
  6. const { VoteType } = require('../../helpers/enums');
  7. const TOKENS = [
  8. { Token: '$ERC20VotesExtendedMock', mode: 'blocknumber' },
  9. // { Token: '$ERC20VotesExtendedTimestampMock', mode: 'timestamp' },
  10. ];
  11. const name = 'Override 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 value = ethers.parseEther('1');
  19. const signBallot = account => (contract, message) =>
  20. getDomain(contract).then(domain => account.signTypedData(domain, { OverrideBallot }, message));
  21. describe('GovernorCountingOverridable', function () {
  22. for (const { Token, mode } of TOKENS) {
  23. const fixture = async () => {
  24. const [owner, proposer, voter1, voter2, voter3, voter4, other] = 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('$GovernorCountingOverridableMock', [
  28. name, // name
  29. votingDelay, // initialVotingDelay
  30. votingPeriod, // initialVotingPeriod
  31. 0n, // initialProposalThreshold
  32. token, // tokenAddress
  33. 10n, // quorumNumeratorValue
  34. ]);
  35. await owner.sendTransaction({ to: mock, value });
  36. await token.$_mint(owner, tokenSupply);
  37. const helper = new GovernorHelper(mock, mode);
  38. await helper.connect(owner).delegate({ token, to: voter1, value: ethers.parseEther('10') });
  39. await helper.connect(owner).delegate({ token, to: voter2, value: ethers.parseEther('7') });
  40. await helper.connect(owner).delegate({ token, to: voter3, value: ethers.parseEther('5') });
  41. await helper.connect(owner).delegate({ token, to: voter4, value: ethers.parseEther('2') });
  42. return { owner, proposer, voter1, voter2, voter3, voter4, other, receiver, token, mock, helper };
  43. };
  44. describe(`using ${Token}`, function () {
  45. beforeEach(async function () {
  46. Object.assign(this, await loadFixture(fixture));
  47. // default proposal
  48. this.proposal = this.helper.setProposal(
  49. [
  50. {
  51. target: this.receiver.target,
  52. value,
  53. data: this.receiver.interface.encodeFunctionData('mockFunction'),
  54. },
  55. ],
  56. '<proposal description>',
  57. );
  58. });
  59. it('deployment check', async function () {
  60. expect(await this.mock.name()).to.equal(name);
  61. expect(await this.mock.token()).to.equal(this.token);
  62. expect(await this.mock.votingDelay()).to.equal(votingDelay);
  63. expect(await this.mock.votingPeriod()).to.equal(votingPeriod);
  64. expect(await this.mock.COUNTING_MODE()).to.equal('support=bravo,override&quorum=for,abstain&overridable=true');
  65. });
  66. it('nominal is unaffected', async function () {
  67. await this.helper.connect(this.proposer).propose();
  68. await this.helper.waitForSnapshot();
  69. await this.helper.connect(this.voter1).vote({ support: VoteType.For, reason: 'This is nice' });
  70. await this.helper.connect(this.voter2).vote({ support: VoteType.For });
  71. await this.helper.connect(this.voter3).vote({ support: VoteType.Against });
  72. await this.helper.connect(this.voter4).vote({ support: VoteType.Abstain });
  73. await this.helper.waitForDeadline();
  74. await this.helper.execute();
  75. expect(await this.mock.hasVoted(this.proposal.id, this.owner)).to.be.false;
  76. expect(await this.mock.hasVoted(this.proposal.id, this.voter1)).to.be.true;
  77. expect(await this.mock.hasVoted(this.proposal.id, this.voter2)).to.be.true;
  78. expect(await ethers.provider.getBalance(this.mock)).to.equal(0n);
  79. expect(await ethers.provider.getBalance(this.receiver)).to.equal(value);
  80. });
  81. describe('cast override vote', async function () {
  82. beforeEach(async function () {
  83. // user 1 -(delegate 10 tokens)-> user 2
  84. // user 2 -(delegate 7 tokens)-> user 2
  85. // user 3 -(delegate 5 tokens)-> user 1
  86. // user 4 -(delegate 2 tokens)-> user 2
  87. await this.token.connect(this.voter1).delegate(this.voter2);
  88. await this.token.connect(this.voter3).delegate(this.voter1);
  89. await this.token.connect(this.voter4).delegate(this.voter2);
  90. await mine();
  91. await this.helper.connect(this.proposer).propose();
  92. await this.helper.waitForSnapshot();
  93. });
  94. it('override after delegate vote', async function () {
  95. expect(await this.mock.hasVoted(this.helper.id, this.voter1)).to.be.false;
  96. expect(await this.mock.hasVoted(this.helper.id, this.voter2)).to.be.false;
  97. expect(await this.mock.hasVoted(this.helper.id, this.voter3)).to.be.false;
  98. expect(await this.mock.hasVoted(this.helper.id, this.voter4)).to.be.false;
  99. expect(await this.mock.hasVotedOverride(this.helper.id, this.voter1)).to.be.false;
  100. expect(await this.mock.hasVotedOverride(this.helper.id, this.voter2)).to.be.false;
  101. expect(await this.mock.hasVotedOverride(this.helper.id, this.voter3)).to.be.false;
  102. expect(await this.mock.hasVotedOverride(this.helper.id, this.voter4)).to.be.false;
  103. // user 2 votes
  104. await expect(this.helper.connect(this.voter2).vote({ support: VoteType.For }))
  105. .to.emit(this.mock, 'VoteCast')
  106. .withArgs(this.voter2, this.helper.id, VoteType.For, ethers.parseEther('19'), ''); // 10 + 7 + 2
  107. expect(await this.mock.proposalVotes(this.helper.id)).to.deep.eq(
  108. [0, 19, 0].map(x => ethers.parseEther(x.toString())),
  109. );
  110. expect(await this.mock.hasVoted(this.helper.id, this.voter2)).to.be.true;
  111. expect(await this.mock.hasVotedOverride(this.helper.id, this.voter2)).to.be.false;
  112. // user 1 overrides after user 2 votes
  113. const reason = "disagree with user 2's decision";
  114. await expect(this.mock.connect(this.voter1).castOverrideVote(this.helper.id, VoteType.Against, reason))
  115. .to.emit(this.mock, 'OverrideVoteCast')
  116. .withArgs(this.voter1, this.helper.id, VoteType.Against, ethers.parseEther('10'), reason)
  117. .to.emit(this.mock, 'VoteReduced')
  118. .withArgs(this.voter2, this.helper.id, VoteType.For, ethers.parseEther('10'));
  119. expect(await this.mock.proposalVotes(this.helper.id)).to.deep.eq(
  120. [10, 9, 0].map(x => ethers.parseEther(x.toString())),
  121. );
  122. expect(await this.mock.hasVoted(this.helper.id, this.voter1)).to.be.false;
  123. expect(await this.mock.hasVotedOverride(this.helper.id, this.voter1)).to.be.true;
  124. });
  125. it('override before delegate vote', async function () {
  126. expect(await this.mock.hasVoted(this.helper.id, this.voter1)).to.be.false;
  127. expect(await this.mock.hasVoted(this.helper.id, this.voter2)).to.be.false;
  128. expect(await this.mock.hasVoted(this.helper.id, this.voter3)).to.be.false;
  129. expect(await this.mock.hasVoted(this.helper.id, this.voter4)).to.be.false;
  130. expect(await this.mock.hasVotedOverride(this.helper.id, this.voter1)).to.be.false;
  131. expect(await this.mock.hasVotedOverride(this.helper.id, this.voter2)).to.be.false;
  132. expect(await this.mock.hasVotedOverride(this.helper.id, this.voter3)).to.be.false;
  133. expect(await this.mock.hasVotedOverride(this.helper.id, this.voter4)).to.be.false;
  134. // user 1 overrides before user 2 votes
  135. const reason = 'voter 2 is not voting';
  136. await expect(this.mock.connect(this.voter1).castOverrideVote(this.helper.id, VoteType.Against, reason))
  137. .to.emit(this.mock, 'OverrideVoteCast')
  138. .withArgs(this.voter1, this.helper.id, VoteType.Against, ethers.parseEther('10'), reason)
  139. .to.not.emit(this.mock, 'VoteReduced');
  140. expect(await this.mock.proposalVotes(this.helper.id)).to.deep.eq(
  141. [10, 0, 0].map(x => ethers.parseEther(x.toString())),
  142. );
  143. expect(await this.mock.hasVoted(this.helper.id, this.voter1)).to.be.false;
  144. expect(await this.mock.hasVotedOverride(this.helper.id, this.voter1)).to.be.true;
  145. // user 2 votes
  146. await expect(this.helper.connect(this.voter2).vote({ support: VoteType.For }))
  147. .to.emit(this.mock, 'VoteCast')
  148. .withArgs(this.voter2, this.helper.id, VoteType.For, ethers.parseEther('9'), ''); // 7 + 2
  149. expect(await this.mock.proposalVotes(this.helper.id)).to.deep.eq(
  150. [10, 9, 0].map(x => ethers.parseEther(x.toString())),
  151. );
  152. expect(await this.mock.hasVoted(this.helper.id, this.voter2)).to.be.true;
  153. expect(await this.mock.hasVotedOverride(this.helper.id, this.voter2)).to.be.false;
  154. });
  155. it('override before and after delegate vote', async function () {
  156. expect(await this.mock.hasVoted(this.helper.id, this.voter1)).to.be.false;
  157. expect(await this.mock.hasVoted(this.helper.id, this.voter2)).to.be.false;
  158. expect(await this.mock.hasVoted(this.helper.id, this.voter3)).to.be.false;
  159. expect(await this.mock.hasVoted(this.helper.id, this.voter4)).to.be.false;
  160. expect(await this.mock.hasVotedOverride(this.helper.id, this.voter1)).to.be.false;
  161. expect(await this.mock.hasVotedOverride(this.helper.id, this.voter2)).to.be.false;
  162. expect(await this.mock.hasVotedOverride(this.helper.id, this.voter3)).to.be.false;
  163. expect(await this.mock.hasVotedOverride(this.helper.id, this.voter4)).to.be.false;
  164. // user 1 overrides before user 2 votes
  165. const reason = 'voter 2 is not voting';
  166. await expect(this.mock.connect(this.voter1).castOverrideVote(this.helper.id, VoteType.Against, reason))
  167. .to.emit(this.mock, 'OverrideVoteCast')
  168. .withArgs(this.voter1, this.helper.id, VoteType.Against, ethers.parseEther('10'), reason)
  169. .to.not.emit(this.mock, 'VoteReduced');
  170. expect(await this.mock.proposalVotes(this.helper.id)).to.deep.eq(
  171. [10, 0, 0].map(x => ethers.parseEther(x.toString())),
  172. );
  173. expect(await this.mock.hasVoted(this.helper.id, this.voter1)).to.be.false;
  174. expect(await this.mock.hasVotedOverride(this.helper.id, this.voter1)).to.be.true;
  175. // user 2 votes
  176. await expect(this.helper.connect(this.voter2).vote({ support: VoteType.For }))
  177. .to.emit(this.mock, 'VoteCast')
  178. .withArgs(this.voter2, this.helper.id, VoteType.For, ethers.parseEther('9'), ''); // 7 + 2
  179. expect(await this.mock.proposalVotes(this.helper.id)).to.deep.eq(
  180. [10, 9, 0].map(x => ethers.parseEther(x.toString())),
  181. );
  182. expect(await this.mock.hasVoted(this.helper.id, this.voter2)).to.be.true;
  183. expect(await this.mock.hasVotedOverride(this.helper.id, this.voter2)).to.be.false;
  184. // User 4 overrides after user 2 votes
  185. const reason2 = "disagree with user 2's decision";
  186. await expect(this.mock.connect(this.voter4).castOverrideVote(this.helper.id, VoteType.Abstain, reason2))
  187. .to.emit(this.mock, 'OverrideVoteCast')
  188. .withArgs(this.voter4, this.helper.id, VoteType.Abstain, ethers.parseEther('2'), reason2)
  189. .to.emit(this.mock, 'VoteReduced')
  190. .withArgs(this.voter2, this.helper.id, VoteType.For, ethers.parseEther('2'));
  191. expect(await this.mock.proposalVotes(this.helper.id)).to.deep.eq(
  192. [10, 7, 2].map(x => ethers.parseEther(x.toString())),
  193. );
  194. expect(await this.mock.hasVoted(this.helper.id, this.voter4)).to.be.false;
  195. expect(await this.mock.hasVotedOverride(this.helper.id, this.voter4)).to.be.true;
  196. });
  197. it('vote (with delegated balance) and override (with self balance) are independent', async function () {
  198. expect(await this.mock.proposalVotes(this.helper.id)).to.deep.eq(
  199. [0, 0, 0].map(x => ethers.parseEther(x.toString())),
  200. );
  201. expect(await this.mock.hasVoted(this.helper.id, this.voter1)).to.be.false;
  202. expect(await this.mock.hasVotedOverride(this.helper.id, this.voter1)).to.be.false;
  203. // user 1 votes with delegated weight from user 3
  204. await expect(this.mock.connect(this.voter1).castVote(this.helper.id, VoteType.For))
  205. .to.emit(this.mock, 'VoteCast')
  206. .withArgs(this.voter1, this.helper.id, VoteType.For, ethers.parseEther('5'), '');
  207. // user 1 cast an override vote with its own balance (delegated to user 2)
  208. await expect(this.mock.connect(this.voter1).castOverrideVote(this.helper.id, VoteType.Against, ''))
  209. .to.emit(this.mock, 'OverrideVoteCast')
  210. .withArgs(this.voter1, this.helper.id, VoteType.Against, ethers.parseEther('10'), '');
  211. expect(await this.mock.proposalVotes(this.helper.id)).to.deep.eq(
  212. [10, 5, 0].map(x => ethers.parseEther(x.toString())),
  213. );
  214. expect(await this.mock.hasVoted(this.helper.id, this.voter1)).to.be.true;
  215. expect(await this.mock.hasVotedOverride(this.helper.id, this.voter1)).to.be.true;
  216. });
  217. it('can not override vote twice', async function () {
  218. await expect(this.mock.connect(this.voter1).castOverrideVote(this.helper.id, VoteType.Against, ''))
  219. .to.emit(this.mock, 'OverrideVoteCast')
  220. .withArgs(this.voter1, this.helper.id, VoteType.Against, ethers.parseEther('10'), '');
  221. await expect(this.mock.connect(this.voter1).castOverrideVote(this.helper.id, VoteType.Abstain, ''))
  222. .to.be.revertedWithCustomError(this.mock, 'GovernorAlreadyOverridenVote')
  223. .withArgs(this.voter1.address);
  224. });
  225. it('can not vote twice', async function () {
  226. await expect(this.mock.connect(this.voter1).castVote(this.helper.id, VoteType.Against));
  227. await expect(this.mock.connect(this.voter1).castVote(this.helper.id, VoteType.Abstain))
  228. .to.be.revertedWithCustomError(this.mock, 'GovernorAlreadyCastVote')
  229. .withArgs(this.voter1.address);
  230. });
  231. describe('invalid vote type', function () {
  232. it('override vote', async function () {
  233. await expect(
  234. this.mock.connect(this.voter1).castOverrideVote(this.helper.id, 3, ''),
  235. ).to.be.revertedWithCustomError(this.mock, 'GovernorInvalidVoteType');
  236. });
  237. it('traditional vote', async function () {
  238. await expect(this.mock.connect(this.voter1).castVote(this.helper.id, 3)).to.be.revertedWithCustomError(
  239. this.mock,
  240. 'GovernorInvalidVoteType',
  241. );
  242. });
  243. });
  244. describe('by signature', function () {
  245. it('EOA signature', async function () {
  246. const nonce = await this.mock.nonces(this.voter1);
  247. await expect(
  248. this.helper.overrideVote({
  249. support: VoteType.For,
  250. voter: this.voter1.address,
  251. nonce,
  252. signature: signBallot(this.voter1),
  253. }),
  254. )
  255. .to.emit(this.mock, 'OverrideVoteCast')
  256. .withArgs(this.voter1, this.helper.id, VoteType.For, ethers.parseEther('10'), '');
  257. expect(await this.mock.hasVotedOverride(this.proposal.id, this.voter1)).to.be.true;
  258. });
  259. it('revert if signature does not match signer', async function () {
  260. const nonce = await this.mock.nonces(this.voter1);
  261. const voteParams = {
  262. support: VoteType.For,
  263. voter: this.voter2.address,
  264. nonce,
  265. signature: signBallot(this.voter1),
  266. };
  267. await expect(this.helper.overrideVote(voteParams))
  268. .to.be.revertedWithCustomError(this.mock, 'GovernorInvalidSignature')
  269. .withArgs(voteParams.voter);
  270. });
  271. it('revert if vote nonce is incorrect', async function () {
  272. const nonce = await this.mock.nonces(this.voter1);
  273. const voteParams = {
  274. support: VoteType.For,
  275. voter: this.voter1.address,
  276. nonce: nonce + 1n,
  277. signature: signBallot(this.voter1),
  278. };
  279. await expect(this.helper.overrideVote(voteParams))
  280. .to.be.revertedWithCustomError(this.mock, 'GovernorInvalidSignature')
  281. .withArgs(voteParams.voter);
  282. });
  283. });
  284. });
  285. });
  286. }
  287. });