GovernorCompatibilityBravo.test.js 17 KB

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