GovernorCompatibilityBravo.test.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426
  1. const { BN, expectEvent, expectRevert } = require('@openzeppelin/test-helpers');
  2. const Enums = require('../../helpers/enums');
  3. const RLP = require('rlp');
  4. const {
  5. runGovernorWorkflow,
  6. } = require('../GovernorWorkflow.behavior');
  7. const Token = artifacts.require('ERC20VotesCompMock');
  8. const Timelock = artifacts.require('CompTimelock');
  9. const Governor = artifacts.require('GovernorCompatibilityBravoMock');
  10. const CallReceiver = artifacts.require('CallReceiverMock');
  11. function makeContractAddress (creator, nonce) {
  12. return web3.utils.toChecksumAddress(web3.utils.sha3(RLP.encode([creator, nonce])).slice(12).substring(14));
  13. }
  14. contract('GovernorCompatibilityBravo', function (accounts) {
  15. const [ owner, proposer, voter1, voter2, voter3, voter4, other ] = accounts;
  16. const name = 'OZ-Governor';
  17. // const version = '1';
  18. const tokenName = 'MockToken';
  19. const tokenSymbol = 'MTKN';
  20. const tokenSupply = web3.utils.toWei('100');
  21. const proposalThreshold = web3.utils.toWei('10');
  22. beforeEach(async function () {
  23. const [ deployer ] = await web3.eth.getAccounts();
  24. this.token = await Token.new(tokenName, tokenSymbol);
  25. // Need to predict governance address to set it as timelock admin with a delayed transfer
  26. const nonce = await web3.eth.getTransactionCount(deployer);
  27. const predictGovernor = makeContractAddress(deployer, nonce + 1);
  28. this.timelock = await Timelock.new(predictGovernor, 2 * 86400);
  29. this.mock = await Governor.new(name, this.token.address, 4, 16, proposalThreshold, this.timelock.address);
  30. this.receiver = await CallReceiver.new();
  31. await this.token.mint(owner, tokenSupply);
  32. await this.token.delegate(voter1, { from: voter1 });
  33. await this.token.delegate(voter2, { from: voter2 });
  34. await this.token.delegate(voter3, { from: voter3 });
  35. await this.token.delegate(voter4, { from: voter4 });
  36. await this.token.transfer(proposer, proposalThreshold, { from: owner });
  37. await this.token.delegate(proposer, { from: proposer });
  38. });
  39. it('deployment check', async function () {
  40. expect(await this.mock.name()).to.be.equal(name);
  41. expect(await this.mock.token()).to.be.equal(this.token.address);
  42. expect(await this.mock.votingDelay()).to.be.bignumber.equal('4');
  43. expect(await this.mock.votingPeriod()).to.be.bignumber.equal('16');
  44. expect(await this.mock.quorum(0)).to.be.bignumber.equal('0');
  45. expect(await this.mock.quorumVotes()).to.be.bignumber.equal('0');
  46. expect(await this.mock.COUNTING_MODE()).to.be.equal('support=bravo&quorum=bravo');
  47. });
  48. describe('nominal', function () {
  49. beforeEach(async function () {
  50. this.settings = {
  51. proposal: [
  52. [ this.receiver.address ], // targets
  53. [ web3.utils.toWei('0') ], // values
  54. [ this.receiver.contract.methods.mockFunction().encodeABI() ], // calldatas
  55. '<proposal description>', // description
  56. ],
  57. proposer,
  58. tokenHolder: owner,
  59. voters: [
  60. {
  61. voter: voter1,
  62. weight: web3.utils.toWei('1'),
  63. support: Enums.VoteType.Abstain,
  64. },
  65. {
  66. voter: voter2,
  67. weight: web3.utils.toWei('10'),
  68. support: Enums.VoteType.For,
  69. },
  70. {
  71. voter: voter3,
  72. weight: web3.utils.toWei('5'),
  73. support: Enums.VoteType.Against,
  74. },
  75. {
  76. voter: voter4,
  77. support: '100',
  78. error: 'GovernorCompatibilityBravo: invalid vote type',
  79. },
  80. {
  81. voter: voter1,
  82. support: Enums.VoteType.For,
  83. error: 'GovernorCompatibilityBravo: vote already cast',
  84. skip: true,
  85. },
  86. ],
  87. steps: {
  88. queue: { delay: 7 * 86400 },
  89. },
  90. };
  91. this.votingDelay = await this.mock.votingDelay();
  92. this.votingPeriod = await this.mock.votingPeriod();
  93. this.receipts = {};
  94. });
  95. afterEach(async function () {
  96. const proposal = await this.mock.proposals(this.id);
  97. expect(proposal.id).to.be.bignumber.equal(this.id);
  98. expect(proposal.proposer).to.be.equal(proposer);
  99. expect(proposal.eta).to.be.bignumber.equal(this.eta);
  100. expect(proposal.startBlock).to.be.bignumber.equal(this.snapshot);
  101. expect(proposal.endBlock).to.be.bignumber.equal(this.deadline);
  102. expect(proposal.canceled).to.be.equal(false);
  103. expect(proposal.executed).to.be.equal(true);
  104. for (const [key, value] of Object.entries(Enums.VoteType)) {
  105. expect(proposal[`${key.toLowerCase()}Votes`]).to.be.bignumber.equal(
  106. Object.values(this.settings.voters).filter(({ support }) => support === value).reduce(
  107. (acc, { weight }) => acc.add(new BN(weight)),
  108. new BN('0'),
  109. ),
  110. );
  111. }
  112. const action = await this.mock.getActions(this.id);
  113. expect(action.targets).to.be.deep.equal(this.settings.proposal[0]);
  114. // expect(action.values).to.be.deep.equal(this.settings.proposal[1]);
  115. expect(action.signatures).to.be.deep.equal(Array(this.settings.proposal[2].length).fill(''));
  116. expect(action.calldatas).to.be.deep.equal(this.settings.proposal[2]);
  117. for (const voter of this.settings.voters.filter(({ skip }) => !skip)) {
  118. expect(await this.mock.hasVoted(this.id, voter.voter)).to.be.equal(voter.error === undefined);
  119. const receipt = await this.mock.getReceipt(this.id, voter.voter);
  120. expect(receipt.hasVoted).to.be.equal(voter.error === undefined);
  121. expect(receipt.support).to.be.bignumber.equal(voter.error === undefined ? voter.support : '0');
  122. expect(receipt.votes).to.be.bignumber.equal(voter.error === undefined ? voter.weight : '0');
  123. }
  124. expectEvent(
  125. this.receipts.propose,
  126. 'ProposalCreated',
  127. {
  128. proposalId: this.id,
  129. proposer,
  130. targets: this.settings.proposal[0],
  131. // values: this.settings.proposal[1].map(value => new BN(value)),
  132. signatures: this.settings.proposal[2].map(() => ''),
  133. calldatas: this.settings.proposal[2],
  134. startBlock: new BN(this.receipts.propose.blockNumber).add(this.votingDelay),
  135. endBlock: new BN(this.receipts.propose.blockNumber).add(this.votingDelay).add(this.votingPeriod),
  136. description: this.settings.proposal[3],
  137. },
  138. );
  139. this.receipts.castVote.filter(Boolean).forEach(vote => {
  140. const { voter } = vote.logs.find(Boolean).args;
  141. expectEvent(
  142. vote,
  143. 'VoteCast',
  144. this.settings.voters.find(({ address }) => address === voter),
  145. );
  146. });
  147. expectEvent(
  148. this.receipts.execute,
  149. 'ProposalExecuted',
  150. { proposalId: this.id },
  151. );
  152. await expectEvent.inTransaction(
  153. this.receipts.execute.transactionHash,
  154. this.receiver,
  155. 'MockFunctionCalled',
  156. );
  157. });
  158. runGovernorWorkflow();
  159. });
  160. describe('with function selector and arguments', function () {
  161. beforeEach(async function () {
  162. this.settings = {
  163. proposal: [
  164. Array(4).fill(this.receiver.address),
  165. Array(4).fill(web3.utils.toWei('0')),
  166. [
  167. '',
  168. '',
  169. 'mockFunctionNonPayable()',
  170. 'mockFunctionWithArgs(uint256,uint256)',
  171. ],
  172. [
  173. this.receiver.contract.methods.mockFunction().encodeABI(),
  174. this.receiver.contract.methods.mockFunctionWithArgs(17, 42).encodeABI(),
  175. '0x',
  176. web3.eth.abi.encodeParameters(['uint256', 'uint256'], [18, 43]),
  177. ],
  178. '<proposal description>', // description
  179. ],
  180. proposer,
  181. tokenHolder: owner,
  182. voters: [
  183. {
  184. voter: voter1,
  185. weight: web3.utils.toWei('10'),
  186. support: Enums.VoteType.For,
  187. },
  188. ],
  189. steps: {
  190. queue: { delay: 7 * 86400 },
  191. },
  192. };
  193. });
  194. runGovernorWorkflow();
  195. afterEach(async function () {
  196. await expectEvent.inTransaction(
  197. this.receipts.execute.transactionHash,
  198. this.receiver,
  199. 'MockFunctionCalled',
  200. );
  201. await expectEvent.inTransaction(
  202. this.receipts.execute.transactionHash,
  203. this.receiver,
  204. 'MockFunctionCalled',
  205. );
  206. await expectEvent.inTransaction(
  207. this.receipts.execute.transactionHash,
  208. this.receiver,
  209. 'MockFunctionCalledWithArgs',
  210. { a: '17', b: '42' },
  211. );
  212. await expectEvent.inTransaction(
  213. this.receipts.execute.transactionHash,
  214. this.receiver,
  215. 'MockFunctionCalledWithArgs',
  216. { a: '18', b: '43' },
  217. );
  218. });
  219. });
  220. describe('proposalThreshold not reached', function () {
  221. beforeEach(async function () {
  222. this.settings = {
  223. proposal: [
  224. [ this.receiver.address ], // targets
  225. [ web3.utils.toWei('0') ], // values
  226. [ this.receiver.contract.methods.mockFunction().encodeABI() ], // calldatas
  227. '<proposal description>', // description
  228. ],
  229. proposer: other,
  230. steps: {
  231. propose: { error: 'GovernorCompatibilityBravo: proposer votes below proposal threshold' },
  232. wait: { enable: false },
  233. queue: { enable: false },
  234. execute: { enable: false },
  235. },
  236. };
  237. });
  238. runGovernorWorkflow();
  239. });
  240. describe('cancel', function () {
  241. beforeEach(async function () {
  242. this.settings = {
  243. proposal: [
  244. [ this.receiver.address ], // targets
  245. [ web3.utils.toWei('0') ], // values
  246. [ this.receiver.contract.methods.mockFunction().encodeABI() ], // calldatas
  247. '<proposal description>', // description
  248. ],
  249. proposer,
  250. tokenHolder: owner,
  251. steps: {
  252. wait: { enable: false },
  253. queue: { enable: false },
  254. execute: { enable: false },
  255. },
  256. };
  257. });
  258. describe('by proposer', function () {
  259. afterEach(async function () {
  260. await this.mock.cancel(this.id, { from: proposer });
  261. });
  262. runGovernorWorkflow();
  263. });
  264. describe('if proposer below threshold', function () {
  265. afterEach(async function () {
  266. await this.token.transfer(voter1, web3.utils.toWei('1'), { from: proposer });
  267. await this.mock.cancel(this.id);
  268. });
  269. runGovernorWorkflow();
  270. });
  271. describe('not if proposer above threshold', function () {
  272. afterEach(async function () {
  273. await expectRevert(
  274. this.mock.cancel(this.id),
  275. 'GovernorBravo: proposer above threshold',
  276. );
  277. });
  278. runGovernorWorkflow();
  279. });
  280. });
  281. describe('with compatibility interface', function () {
  282. beforeEach(async function () {
  283. this.settings = {
  284. proposal: [
  285. [ this.receiver.address ], // targets
  286. [ web3.utils.toWei('0') ], // values
  287. [ 'mockFunction()' ], // signatures
  288. [ '0x' ], // calldatas
  289. '<proposal description>', // description
  290. ],
  291. proposer,
  292. tokenHolder: owner,
  293. voters: [
  294. {
  295. voter: voter1,
  296. weight: web3.utils.toWei('1'),
  297. support: Enums.VoteType.Abstain,
  298. },
  299. {
  300. voter: voter2,
  301. weight: web3.utils.toWei('10'),
  302. support: Enums.VoteType.For,
  303. },
  304. {
  305. voter: voter3,
  306. weight: web3.utils.toWei('5'),
  307. support: Enums.VoteType.Against,
  308. },
  309. {
  310. voter: voter4,
  311. support: '100',
  312. error: 'GovernorCompatibilityBravo: invalid vote type',
  313. },
  314. {
  315. voter: voter1,
  316. support: Enums.VoteType.For,
  317. error: 'GovernorCompatibilityBravo: vote already cast',
  318. skip: true,
  319. },
  320. ],
  321. steps: {
  322. queue: { delay: 7 * 86400 },
  323. },
  324. };
  325. this.votingDelay = await this.mock.votingDelay();
  326. this.votingPeriod = await this.mock.votingPeriod();
  327. this.receipts = {};
  328. });
  329. afterEach(async function () {
  330. const proposal = await this.mock.proposals(this.id);
  331. expect(proposal.id).to.be.bignumber.equal(this.id);
  332. expect(proposal.proposer).to.be.equal(proposer);
  333. expect(proposal.eta).to.be.bignumber.equal(this.eta);
  334. expect(proposal.startBlock).to.be.bignumber.equal(this.snapshot);
  335. expect(proposal.endBlock).to.be.bignumber.equal(this.deadline);
  336. expect(proposal.canceled).to.be.equal(false);
  337. expect(proposal.executed).to.be.equal(true);
  338. for (const [key, value] of Object.entries(Enums.VoteType)) {
  339. expect(proposal[`${key.toLowerCase()}Votes`]).to.be.bignumber.equal(
  340. Object.values(this.settings.voters).filter(({ support }) => support === value).reduce(
  341. (acc, { weight }) => acc.add(new BN(weight)),
  342. new BN('0'),
  343. ),
  344. );
  345. }
  346. const action = await this.mock.getActions(this.id);
  347. expect(action.targets).to.be.deep.equal(this.settings.proposal[0]);
  348. // expect(action.values).to.be.deep.equal(this.settings.proposal[1]);
  349. expect(action.signatures).to.be.deep.equal(this.settings.proposal[2]);
  350. expect(action.calldatas).to.be.deep.equal(this.settings.proposal[3]);
  351. for (const voter of this.settings.voters.filter(({ skip }) => !skip)) {
  352. expect(await this.mock.hasVoted(this.id, voter.voter)).to.be.equal(voter.error === undefined);
  353. const receipt = await this.mock.getReceipt(this.id, voter.voter);
  354. expect(receipt.hasVoted).to.be.equal(voter.error === undefined);
  355. expect(receipt.support).to.be.bignumber.equal(voter.error === undefined ? voter.support : '0');
  356. expect(receipt.votes).to.be.bignumber.equal(voter.error === undefined ? voter.weight : '0');
  357. }
  358. expectEvent(
  359. this.receipts.propose,
  360. 'ProposalCreated',
  361. {
  362. proposalId: this.id,
  363. proposer,
  364. targets: this.settings.proposal[0],
  365. // values: this.settings.proposal[1].map(value => new BN(value)),
  366. signatures: this.settings.proposal[2].map(_ => ''),
  367. calldatas: this.settings.shortProposal[2],
  368. startBlock: new BN(this.receipts.propose.blockNumber).add(this.votingDelay),
  369. endBlock: new BN(this.receipts.propose.blockNumber).add(this.votingDelay).add(this.votingPeriod),
  370. description: this.settings.proposal[4],
  371. },
  372. );
  373. this.receipts.castVote.filter(Boolean).forEach(vote => {
  374. const { voter } = vote.logs.find(Boolean).args;
  375. expectEvent(
  376. vote,
  377. 'VoteCast',
  378. this.settings.voters.find(({ address }) => address === voter),
  379. );
  380. });
  381. expectEvent(
  382. this.receipts.execute,
  383. 'ProposalExecuted',
  384. { proposalId: this.id },
  385. );
  386. await expectEvent.inTransaction(
  387. this.receipts.execute.transactionHash,
  388. this.receiver,
  389. 'MockFunctionCalled',
  390. );
  391. });
  392. runGovernorWorkflow();
  393. });
  394. });