Governor.test.js 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946
  1. const { BN, constants, expectEvent, expectRevert, time } = require('@openzeppelin/test-helpers');
  2. const ethSigUtil = require('eth-sig-util');
  3. const Wallet = require('ethereumjs-wallet').default;
  4. const Enums = require('../helpers/enums');
  5. const { EIP712Domain } = require('../helpers/eip712');
  6. const { fromRpcSig } = require('ethereumjs-util');
  7. const {
  8. runGovernorWorkflow,
  9. } = require('./GovernorWorkflow.behavior');
  10. const {
  11. shouldSupportInterfaces,
  12. } = require('../utils/introspection/SupportsInterface.behavior');
  13. const Token = artifacts.require('ERC20VotesMock');
  14. const Governor = artifacts.require('GovernorMock');
  15. const CallReceiver = artifacts.require('CallReceiverMock');
  16. contract('Governor', function (accounts) {
  17. const [ owner, proposer, voter1, voter2, voter3, voter4 ] = accounts;
  18. const name = 'OZ-Governor';
  19. const version = '1';
  20. const tokenName = 'MockToken';
  21. const tokenSymbol = 'MTKN';
  22. const tokenSupply = web3.utils.toWei('100');
  23. beforeEach(async function () {
  24. this.owner = owner;
  25. this.token = await Token.new(tokenName, tokenSymbol);
  26. this.mock = await Governor.new(name, this.token.address, 4, 16, 10);
  27. this.receiver = await CallReceiver.new();
  28. await this.token.mint(owner, tokenSupply);
  29. await this.token.delegate(voter1, { from: voter1 });
  30. await this.token.delegate(voter2, { from: voter2 });
  31. await this.token.delegate(voter3, { from: voter3 });
  32. await this.token.delegate(voter4, { from: voter4 });
  33. });
  34. shouldSupportInterfaces([
  35. 'ERC165',
  36. 'Governor',
  37. ]);
  38. it('deployment check', async function () {
  39. expect(await this.mock.name()).to.be.equal(name);
  40. expect(await this.mock.token()).to.be.equal(this.token.address);
  41. expect(await this.mock.votingDelay()).to.be.bignumber.equal('4');
  42. expect(await this.mock.votingPeriod()).to.be.bignumber.equal('16');
  43. expect(await this.mock.quorum(0)).to.be.bignumber.equal('0');
  44. expect(await this.mock.COUNTING_MODE()).to.be.equal('support=bravo&quorum=for,abstain');
  45. });
  46. describe('scenario', function () {
  47. describe('nominal', function () {
  48. beforeEach(async function () {
  49. this.value = web3.utils.toWei('1');
  50. await web3.eth.sendTransaction({ from: owner, to: this.mock.address, value: this.value });
  51. expect(await web3.eth.getBalance(this.mock.address)).to.be.bignumber.equal(this.value);
  52. expect(await web3.eth.getBalance(this.receiver.address)).to.be.bignumber.equal('0');
  53. this.settings = {
  54. proposal: [
  55. [ this.receiver.address ],
  56. [ this.value ],
  57. [ this.receiver.contract.methods.mockFunction().encodeABI() ],
  58. '<proposal description>',
  59. ],
  60. proposer,
  61. tokenHolder: owner,
  62. voters: [
  63. { voter: voter1, weight: web3.utils.toWei('1'), support: Enums.VoteType.For, reason: 'This is nice' },
  64. { voter: voter2, weight: web3.utils.toWei('7'), support: Enums.VoteType.For },
  65. { voter: voter3, weight: web3.utils.toWei('5'), support: Enums.VoteType.Against },
  66. { voter: voter4, weight: web3.utils.toWei('2'), support: Enums.VoteType.Abstain },
  67. ],
  68. };
  69. this.votingDelay = await this.mock.votingDelay();
  70. this.votingPeriod = await this.mock.votingPeriod();
  71. });
  72. afterEach(async function () {
  73. expect(await this.mock.hasVoted(this.id, owner)).to.be.equal(false);
  74. expect(await this.mock.hasVoted(this.id, voter1)).to.be.equal(true);
  75. expect(await this.mock.hasVoted(this.id, voter2)).to.be.equal(true);
  76. await this.mock.proposalVotes(this.id).then(result => {
  77. for (const [key, value] of Object.entries(Enums.VoteType)) {
  78. expect(result[`${key.toLowerCase()}Votes`]).to.be.bignumber.equal(
  79. Object.values(this.settings.voters).filter(({ support }) => support === value).reduce(
  80. (acc, { weight }) => acc.add(new BN(weight)),
  81. new BN('0'),
  82. ),
  83. );
  84. }
  85. });
  86. expectEvent(
  87. this.receipts.propose,
  88. 'ProposalCreated',
  89. {
  90. proposalId: this.id,
  91. proposer,
  92. targets: this.settings.proposal[0],
  93. // values: this.settings.proposal[1].map(value => new BN(value)),
  94. signatures: this.settings.proposal[2].map(() => ''),
  95. calldatas: this.settings.proposal[2],
  96. startBlock: new BN(this.receipts.propose.blockNumber).add(this.votingDelay),
  97. endBlock: new BN(this.receipts.propose.blockNumber).add(this.votingDelay).add(this.votingPeriod),
  98. description: this.settings.proposal[3],
  99. },
  100. );
  101. this.receipts.castVote.filter(Boolean).forEach(vote => {
  102. const { voter } = vote.logs.find(Boolean).args;
  103. expectEvent(
  104. vote,
  105. 'VoteCast',
  106. this.settings.voters.find(({ address }) => address === voter),
  107. );
  108. });
  109. expectEvent(
  110. this.receipts.execute,
  111. 'ProposalExecuted',
  112. { proposalId: this.id },
  113. );
  114. await expectEvent.inTransaction(
  115. this.receipts.execute.transactionHash,
  116. this.receiver,
  117. 'MockFunctionCalled',
  118. );
  119. expect(await web3.eth.getBalance(this.mock.address)).to.be.bignumber.equal('0');
  120. expect(await web3.eth.getBalance(this.receiver.address)).to.be.bignumber.equal(this.value);
  121. });
  122. runGovernorWorkflow();
  123. });
  124. describe('vote with signature', function () {
  125. beforeEach(async function () {
  126. const chainId = await web3.eth.getChainId();
  127. // generate voter by signature wallet
  128. const voterBySig = Wallet.generate();
  129. this.voter = web3.utils.toChecksumAddress(voterBySig.getAddressString());
  130. // use delegateBySig to enable vote delegation for this wallet
  131. const { v, r, s } = fromRpcSig(ethSigUtil.signTypedMessage(
  132. voterBySig.getPrivateKey(),
  133. {
  134. data: {
  135. types: {
  136. EIP712Domain,
  137. Delegation: [
  138. { name: 'delegatee', type: 'address' },
  139. { name: 'nonce', type: 'uint256' },
  140. { name: 'expiry', type: 'uint256' },
  141. ],
  142. },
  143. domain: { name: tokenName, version: '1', chainId, verifyingContract: this.token.address },
  144. primaryType: 'Delegation',
  145. message: { delegatee: this.voter, nonce: 0, expiry: constants.MAX_UINT256 },
  146. },
  147. },
  148. ));
  149. await this.token.delegateBySig(this.voter, 0, constants.MAX_UINT256, v, r, s);
  150. // prepare signature for vote by signature
  151. const signature = async (message) => {
  152. return fromRpcSig(ethSigUtil.signTypedMessage(
  153. voterBySig.getPrivateKey(),
  154. {
  155. data: {
  156. types: {
  157. EIP712Domain,
  158. Ballot: [
  159. { name: 'proposalId', type: 'uint256' },
  160. { name: 'support', type: 'uint8' },
  161. ],
  162. },
  163. domain: { name, version, chainId, verifyingContract: this.mock.address },
  164. primaryType: 'Ballot',
  165. message,
  166. },
  167. },
  168. ));
  169. };
  170. this.settings = {
  171. proposal: [
  172. [ this.receiver.address ],
  173. [ web3.utils.toWei('0') ],
  174. [ this.receiver.contract.methods.mockFunction().encodeABI() ],
  175. '<proposal description>',
  176. ],
  177. tokenHolder: owner,
  178. voters: [
  179. { voter: this.voter, signature, weight: web3.utils.toWei('10'), support: Enums.VoteType.For },
  180. ],
  181. };
  182. });
  183. afterEach(async function () {
  184. expect(await this.mock.hasVoted(this.id, owner)).to.be.equal(false);
  185. expect(await this.mock.hasVoted(this.id, voter1)).to.be.equal(false);
  186. expect(await this.mock.hasVoted(this.id, voter2)).to.be.equal(false);
  187. expect(await this.mock.hasVoted(this.id, this.voter)).to.be.equal(true);
  188. await this.mock.proposalVotes(this.id).then(result => {
  189. for (const [key, value] of Object.entries(Enums.VoteType)) {
  190. expect(result[`${key.toLowerCase()}Votes`]).to.be.bignumber.equal(
  191. Object.values(this.settings.voters).filter(({ support }) => support === value).reduce(
  192. (acc, { weight }) => acc.add(new BN(weight)),
  193. new BN('0'),
  194. ),
  195. );
  196. }
  197. });
  198. expectEvent(
  199. this.receipts.propose,
  200. 'ProposalCreated',
  201. { proposalId: this.id },
  202. );
  203. expectEvent(
  204. this.receipts.execute,
  205. 'ProposalExecuted',
  206. { proposalId: this.id },
  207. );
  208. await expectEvent.inTransaction(
  209. this.receipts.execute.transactionHash,
  210. this.receiver,
  211. 'MockFunctionCalled',
  212. );
  213. });
  214. runGovernorWorkflow();
  215. });
  216. describe('send ethers', function () {
  217. beforeEach(async function () {
  218. this.receiver = { address: web3.utils.toChecksumAddress(web3.utils.randomHex(20)) };
  219. this.value = web3.utils.toWei('1');
  220. await web3.eth.sendTransaction({ from: owner, to: this.mock.address, value: this.value });
  221. expect(await web3.eth.getBalance(this.mock.address)).to.be.bignumber.equal(this.value);
  222. expect(await web3.eth.getBalance(this.receiver.address)).to.be.bignumber.equal('0');
  223. this.settings = {
  224. proposal: [
  225. [ this.receiver.address ],
  226. [ this.value ],
  227. [ '0x' ],
  228. '<proposal description>',
  229. ],
  230. tokenHolder: owner,
  231. voters: [
  232. { voter: voter1, weight: web3.utils.toWei('5'), support: Enums.VoteType.For },
  233. { voter: voter2, weight: web3.utils.toWei('5'), support: Enums.VoteType.Abstain },
  234. ],
  235. };
  236. });
  237. afterEach(async function () {
  238. expectEvent(
  239. this.receipts.propose,
  240. 'ProposalCreated',
  241. { proposalId: this.id },
  242. );
  243. expectEvent(
  244. this.receipts.execute,
  245. 'ProposalExecuted',
  246. { proposalId: this.id },
  247. );
  248. expect(await web3.eth.getBalance(this.mock.address)).to.be.bignumber.equal('0');
  249. expect(await web3.eth.getBalance(this.receiver.address)).to.be.bignumber.equal(this.value);
  250. });
  251. runGovernorWorkflow();
  252. });
  253. describe('receiver revert without reason', function () {
  254. beforeEach(async function () {
  255. this.settings = {
  256. proposal: [
  257. [ this.receiver.address ],
  258. [ 0 ],
  259. [ this.receiver.contract.methods.mockFunctionRevertsNoReason().encodeABI() ],
  260. '<proposal description>',
  261. ],
  262. tokenHolder: owner,
  263. voters: [
  264. { voter: voter1, weight: web3.utils.toWei('10'), support: Enums.VoteType.For },
  265. ],
  266. steps: {
  267. execute: { error: 'Governor: call reverted without message' },
  268. },
  269. };
  270. });
  271. runGovernorWorkflow();
  272. });
  273. describe('receiver revert with reason', function () {
  274. beforeEach(async function () {
  275. this.settings = {
  276. proposal: [
  277. [ this.receiver.address ],
  278. [ 0 ],
  279. [ this.receiver.contract.methods.mockFunctionRevertsReason().encodeABI() ],
  280. '<proposal description>',
  281. ],
  282. tokenHolder: owner,
  283. voters: [
  284. { voter: voter1, weight: web3.utils.toWei('10'), support: Enums.VoteType.For },
  285. ],
  286. steps: {
  287. execute: { error: 'CallReceiverMock: reverting' },
  288. },
  289. };
  290. });
  291. runGovernorWorkflow();
  292. });
  293. describe('missing proposal', function () {
  294. beforeEach(async function () {
  295. this.settings = {
  296. proposal: [
  297. [ this.receiver.address ],
  298. [ web3.utils.toWei('0') ],
  299. [ this.receiver.contract.methods.mockFunction().encodeABI() ],
  300. '<proposal description>',
  301. ],
  302. tokenHolder: owner,
  303. voters: [
  304. {
  305. voter: voter1,
  306. weight: web3.utils.toWei('5'),
  307. support: Enums.VoteType.For,
  308. error: 'Governor: unknown proposal id',
  309. },
  310. {
  311. voter: voter2,
  312. weight: web3.utils.toWei('5'),
  313. support: Enums.VoteType.Abstain,
  314. error: 'Governor: unknown proposal id',
  315. },
  316. ],
  317. steps: {
  318. propose: { enable: false },
  319. wait: { enable: false },
  320. execute: { error: 'Governor: unknown proposal id' },
  321. },
  322. };
  323. });
  324. runGovernorWorkflow();
  325. });
  326. describe('duplicate pending proposal', function () {
  327. beforeEach(async function () {
  328. this.settings = {
  329. proposal: [
  330. [ this.receiver.address ],
  331. [ web3.utils.toWei('0') ],
  332. [ this.receiver.contract.methods.mockFunction().encodeABI() ],
  333. '<proposal description>',
  334. ],
  335. steps: {
  336. wait: { enable: false },
  337. execute: { enable: false },
  338. },
  339. };
  340. });
  341. afterEach(async function () {
  342. await expectRevert(this.mock.propose(...this.settings.proposal), 'Governor: proposal already exists');
  343. });
  344. runGovernorWorkflow();
  345. });
  346. describe('duplicate executed proposal', function () {
  347. beforeEach(async function () {
  348. this.settings = {
  349. proposal: [
  350. [ this.receiver.address ],
  351. [ web3.utils.toWei('0') ],
  352. [ this.receiver.contract.methods.mockFunction().encodeABI() ],
  353. '<proposal description>',
  354. ],
  355. tokenHolder: owner,
  356. voters: [
  357. { voter: voter1, weight: web3.utils.toWei('5'), support: Enums.VoteType.For },
  358. { voter: voter2, weight: web3.utils.toWei('5'), support: Enums.VoteType.Abstain },
  359. ],
  360. };
  361. });
  362. afterEach(async function () {
  363. await expectRevert(this.mock.propose(...this.settings.proposal), 'Governor: proposal already exists');
  364. });
  365. runGovernorWorkflow();
  366. });
  367. describe('Invalid vote type', function () {
  368. beforeEach(async function () {
  369. this.settings = {
  370. proposal: [
  371. [ this.receiver.address ],
  372. [ web3.utils.toWei('0') ],
  373. [ this.receiver.contract.methods.mockFunction().encodeABI() ],
  374. '<proposal description>',
  375. ],
  376. tokenHolder: owner,
  377. voters: [
  378. {
  379. voter: voter1,
  380. weight: web3.utils.toWei('10'),
  381. support: new BN('255'),
  382. error: 'GovernorVotingSimple: invalid value for enum VoteType',
  383. },
  384. ],
  385. steps: {
  386. wait: { enable: false },
  387. execute: { enable: false },
  388. },
  389. };
  390. });
  391. runGovernorWorkflow();
  392. });
  393. describe('double cast', function () {
  394. beforeEach(async function () {
  395. this.settings = {
  396. proposal: [
  397. [ this.receiver.address ],
  398. [ web3.utils.toWei('0') ],
  399. [ this.receiver.contract.methods.mockFunction().encodeABI() ],
  400. '<proposal description>',
  401. ],
  402. tokenHolder: owner,
  403. voters: [
  404. {
  405. voter: voter1,
  406. weight: web3.utils.toWei('5'),
  407. support: Enums.VoteType.For,
  408. },
  409. {
  410. voter: voter1,
  411. weight: web3.utils.toWei('5'),
  412. support: Enums.VoteType.For,
  413. error: 'GovernorVotingSimple: vote already cast',
  414. },
  415. ],
  416. };
  417. });
  418. runGovernorWorkflow();
  419. });
  420. describe('quorum not reached', function () {
  421. beforeEach(async function () {
  422. this.settings = {
  423. proposal: [
  424. [ this.receiver.address ],
  425. [ web3.utils.toWei('0') ],
  426. [ this.receiver.contract.methods.mockFunction().encodeABI() ],
  427. '<proposal description>',
  428. ],
  429. tokenHolder: owner,
  430. voters: [
  431. { voter: voter1, weight: web3.utils.toWei('5'), support: Enums.VoteType.For },
  432. { voter: voter2, weight: web3.utils.toWei('4'), support: Enums.VoteType.Abstain },
  433. { voter: voter3, weight: web3.utils.toWei('10'), support: Enums.VoteType.Against },
  434. ],
  435. steps: {
  436. execute: { error: 'Governor: proposal not successful' },
  437. },
  438. };
  439. });
  440. runGovernorWorkflow();
  441. });
  442. describe('score not reached', function () {
  443. beforeEach(async function () {
  444. this.settings = {
  445. proposal: [
  446. [ this.receiver.address ],
  447. [ web3.utils.toWei('0') ],
  448. [ this.receiver.contract.methods.mockFunction().encodeABI() ],
  449. '<proposal description>',
  450. ],
  451. tokenHolder: owner,
  452. voters: [
  453. { voter: voter1, weight: web3.utils.toWei('10'), support: Enums.VoteType.Against },
  454. ],
  455. steps: {
  456. execute: { error: 'Governor: proposal not successful' },
  457. },
  458. };
  459. });
  460. runGovernorWorkflow();
  461. });
  462. describe('vote not over', function () {
  463. beforeEach(async function () {
  464. this.settings = {
  465. proposal: [
  466. [ this.receiver.address ],
  467. [ web3.utils.toWei('0') ],
  468. [ this.receiver.contract.methods.mockFunction().encodeABI() ],
  469. '<proposal description>',
  470. ],
  471. tokenHolder: owner,
  472. voters: [
  473. { voter: voter1, weight: web3.utils.toWei('10'), support: Enums.VoteType.For },
  474. ],
  475. steps: {
  476. wait: { enable: false },
  477. execute: { error: 'Governor: proposal not successful' },
  478. },
  479. };
  480. });
  481. runGovernorWorkflow();
  482. });
  483. });
  484. describe('state', function () {
  485. describe('Unset', function () {
  486. beforeEach(async function () {
  487. this.settings = {
  488. proposal: [
  489. [ this.receiver.address ],
  490. [ web3.utils.toWei('0') ],
  491. [ this.receiver.contract.methods.mockFunction().encodeABI() ],
  492. '<proposal description>',
  493. ],
  494. steps: {
  495. propose: { enable: false },
  496. wait: { enable: false },
  497. execute: { enable: false },
  498. },
  499. };
  500. });
  501. afterEach(async function () {
  502. await expectRevert(this.mock.state(this.id), 'Governor: unknown proposal id');
  503. });
  504. runGovernorWorkflow();
  505. });
  506. describe('Pending & Active', function () {
  507. beforeEach(async function () {
  508. this.settings = {
  509. proposal: [
  510. [ this.receiver.address ],
  511. [ web3.utils.toWei('0') ],
  512. [ this.receiver.contract.methods.mockFunction().encodeABI() ],
  513. '<proposal description>',
  514. ],
  515. steps: {
  516. propose: { noadvance: true },
  517. wait: { enable: false },
  518. execute: { enable: false },
  519. },
  520. };
  521. });
  522. afterEach(async function () {
  523. expect(await this.mock.state(this.id)).to.be.bignumber.equal(Enums.ProposalState.Pending);
  524. await time.advanceBlockTo(this.snapshot);
  525. expect(await this.mock.state(this.id)).to.be.bignumber.equal(Enums.ProposalState.Pending);
  526. await time.advanceBlock();
  527. expect(await this.mock.state(this.id)).to.be.bignumber.equal(Enums.ProposalState.Active);
  528. });
  529. runGovernorWorkflow();
  530. });
  531. describe('Defeated', function () {
  532. beforeEach(async function () {
  533. this.settings = {
  534. proposal: [
  535. [ this.receiver.address ],
  536. [ web3.utils.toWei('0') ],
  537. [ this.receiver.contract.methods.mockFunction().encodeABI() ],
  538. '<proposal description>',
  539. ],
  540. steps: {
  541. execute: { enable: false },
  542. },
  543. };
  544. });
  545. afterEach(async function () {
  546. expect(await this.mock.state(this.id)).to.be.bignumber.equal(Enums.ProposalState.Defeated);
  547. });
  548. runGovernorWorkflow();
  549. });
  550. describe('Succeeded', function () {
  551. beforeEach(async function () {
  552. this.settings = {
  553. proposal: [
  554. [ this.receiver.address ],
  555. [ web3.utils.toWei('0') ],
  556. [ this.receiver.contract.methods.mockFunction().encodeABI() ],
  557. '<proposal description>',
  558. ],
  559. tokenHolder: owner,
  560. voters: [
  561. { voter: voter1, weight: web3.utils.toWei('10'), support: Enums.VoteType.For },
  562. ],
  563. steps: {
  564. execute: { enable: false },
  565. },
  566. };
  567. });
  568. afterEach(async function () {
  569. expect(await this.mock.state(this.id)).to.be.bignumber.equal(Enums.ProposalState.Succeeded);
  570. });
  571. runGovernorWorkflow();
  572. });
  573. describe('Executed', function () {
  574. beforeEach(async function () {
  575. this.settings = {
  576. proposal: [
  577. [ this.receiver.address ],
  578. [ web3.utils.toWei('0') ],
  579. [ this.receiver.contract.methods.mockFunction().encodeABI() ],
  580. '<proposal description>',
  581. ],
  582. tokenHolder: owner,
  583. voters: [
  584. { voter: voter1, weight: web3.utils.toWei('10'), support: Enums.VoteType.For },
  585. ],
  586. };
  587. });
  588. afterEach(async function () {
  589. expect(await this.mock.state(this.id)).to.be.bignumber.equal(Enums.ProposalState.Executed);
  590. });
  591. runGovernorWorkflow();
  592. });
  593. });
  594. describe('Cancel', function () {
  595. describe('Before proposal', function () {
  596. beforeEach(async function () {
  597. this.settings = {
  598. proposal: [
  599. [ this.receiver.address ],
  600. [ web3.utils.toWei('0') ],
  601. [ this.receiver.contract.methods.mockFunction().encodeABI() ],
  602. '<proposal description>',
  603. ],
  604. steps: {
  605. propose: { enable: false },
  606. wait: { enable: false },
  607. execute: { enable: false },
  608. },
  609. };
  610. });
  611. afterEach(async function () {
  612. await expectRevert(
  613. this.mock.cancel(...this.settings.proposal.slice(0, -1), this.descriptionHash),
  614. 'Governor: unknown proposal id',
  615. );
  616. });
  617. runGovernorWorkflow();
  618. });
  619. describe('After proposal', function () {
  620. beforeEach(async function () {
  621. this.settings = {
  622. proposal: [
  623. [ this.receiver.address ],
  624. [ web3.utils.toWei('0') ],
  625. [ this.receiver.contract.methods.mockFunction().encodeABI() ],
  626. '<proposal description>',
  627. ],
  628. steps: {
  629. wait: { enable: false },
  630. execute: { enable: false },
  631. },
  632. };
  633. });
  634. afterEach(async function () {
  635. await this.mock.cancel(...this.settings.proposal.slice(0, -1), this.descriptionHash);
  636. expect(await this.mock.state(this.id)).to.be.bignumber.equal(Enums.ProposalState.Canceled);
  637. await expectRevert(
  638. this.mock.castVote(this.id, new BN('100'), { from: voter1 }),
  639. 'Governor: vote not currently active',
  640. );
  641. });
  642. runGovernorWorkflow();
  643. });
  644. describe('After vote', function () {
  645. beforeEach(async function () {
  646. this.settings = {
  647. proposal: [
  648. [ this.receiver.address ],
  649. [ web3.utils.toWei('0') ],
  650. [ this.receiver.contract.methods.mockFunction().encodeABI() ],
  651. '<proposal description>',
  652. ],
  653. tokenHolder: owner,
  654. voters: [
  655. { voter: voter1, weight: web3.utils.toWei('10'), support: Enums.VoteType.For },
  656. ],
  657. steps: {
  658. wait: { enable: false },
  659. execute: { enable: false },
  660. },
  661. };
  662. });
  663. afterEach(async function () {
  664. await this.mock.cancel(...this.settings.proposal.slice(0, -1), this.descriptionHash);
  665. expect(await this.mock.state(this.id)).to.be.bignumber.equal(Enums.ProposalState.Canceled);
  666. await expectRevert(
  667. this.mock.execute(...this.settings.proposal.slice(0, -1), this.descriptionHash),
  668. 'Governor: proposal not successful',
  669. );
  670. });
  671. runGovernorWorkflow();
  672. });
  673. describe('After deadline', function () {
  674. beforeEach(async function () {
  675. this.settings = {
  676. proposal: [
  677. [ this.receiver.address ],
  678. [ web3.utils.toWei('0') ],
  679. [ this.receiver.contract.methods.mockFunction().encodeABI() ],
  680. '<proposal description>',
  681. ],
  682. tokenHolder: owner,
  683. voters: [
  684. { voter: voter1, weight: web3.utils.toWei('10'), support: Enums.VoteType.For },
  685. ],
  686. steps: {
  687. execute: { enable: false },
  688. },
  689. };
  690. });
  691. afterEach(async function () {
  692. await this.mock.cancel(...this.settings.proposal.slice(0, -1), this.descriptionHash);
  693. expect(await this.mock.state(this.id)).to.be.bignumber.equal(Enums.ProposalState.Canceled);
  694. await expectRevert(
  695. this.mock.execute(...this.settings.proposal.slice(0, -1), this.descriptionHash),
  696. 'Governor: proposal not successful',
  697. );
  698. });
  699. runGovernorWorkflow();
  700. });
  701. describe('After execution', function () {
  702. beforeEach(async function () {
  703. this.settings = {
  704. proposal: [
  705. [ this.receiver.address ],
  706. [ web3.utils.toWei('0') ],
  707. [ this.receiver.contract.methods.mockFunction().encodeABI() ],
  708. '<proposal description>',
  709. ],
  710. tokenHolder: owner,
  711. voters: [
  712. { voter: voter1, weight: web3.utils.toWei('10'), support: Enums.VoteType.For },
  713. ],
  714. };
  715. });
  716. afterEach(async function () {
  717. await expectRevert(
  718. this.mock.cancel(...this.settings.proposal.slice(0, -1), this.descriptionHash),
  719. 'Governor: proposal not active',
  720. );
  721. });
  722. runGovernorWorkflow();
  723. });
  724. });
  725. describe('Proposal length', function () {
  726. it('empty', async function () {
  727. await expectRevert(
  728. this.mock.propose(
  729. [],
  730. [],
  731. [],
  732. '<proposal description>',
  733. ),
  734. 'Governor: empty proposal',
  735. );
  736. });
  737. it('missmatch #1', async function () {
  738. await expectRevert(
  739. this.mock.propose(
  740. [ ],
  741. [ web3.utils.toWei('0') ],
  742. [ this.receiver.contract.methods.mockFunction().encodeABI() ],
  743. '<proposal description>',
  744. ),
  745. 'Governor: invalid proposal length',
  746. );
  747. });
  748. it('missmatch #2', async function () {
  749. await expectRevert(
  750. this.mock.propose(
  751. [ this.receiver.address ],
  752. [ ],
  753. [ this.receiver.contract.methods.mockFunction().encodeABI() ],
  754. '<proposal description>',
  755. ),
  756. 'Governor: invalid proposal length',
  757. );
  758. });
  759. it('missmatch #3', async function () {
  760. await expectRevert(
  761. this.mock.propose(
  762. [ this.receiver.address ],
  763. [ web3.utils.toWei('0') ],
  764. [ ],
  765. '<proposal description>',
  766. ),
  767. 'Governor: invalid proposal length',
  768. );
  769. });
  770. });
  771. describe('Settings update', function () {
  772. describe('setVotingDelay', function () {
  773. beforeEach(async function () {
  774. this.settings = {
  775. proposal: [
  776. [ this.mock.address ],
  777. [ web3.utils.toWei('0') ],
  778. [ this.mock.contract.methods.setVotingDelay('0').encodeABI() ],
  779. '<proposal description>',
  780. ],
  781. tokenHolder: owner,
  782. voters: [
  783. { voter: voter1, weight: web3.utils.toWei('10'), support: Enums.VoteType.For },
  784. ],
  785. };
  786. });
  787. afterEach(async function () {
  788. expect(await this.mock.votingDelay()).to.be.bignumber.equal('0');
  789. expectEvent(
  790. this.receipts.execute,
  791. 'VotingDelaySet',
  792. { oldVotingDelay: '4', newVotingDelay: '0' },
  793. );
  794. });
  795. runGovernorWorkflow();
  796. });
  797. describe('setVotingPeriod', function () {
  798. beforeEach(async function () {
  799. this.settings = {
  800. proposal: [
  801. [ this.mock.address ],
  802. [ web3.utils.toWei('0') ],
  803. [ this.mock.contract.methods.setVotingPeriod('32').encodeABI() ],
  804. '<proposal description>',
  805. ],
  806. tokenHolder: owner,
  807. voters: [
  808. { voter: voter1, weight: web3.utils.toWei('10'), support: Enums.VoteType.For },
  809. ],
  810. };
  811. });
  812. afterEach(async function () {
  813. expect(await this.mock.votingPeriod()).to.be.bignumber.equal('32');
  814. expectEvent(
  815. this.receipts.execute,
  816. 'VotingPeriodSet',
  817. { oldVotingPeriod: '16', newVotingPeriod: '32' },
  818. );
  819. });
  820. runGovernorWorkflow();
  821. });
  822. describe('setVotingPeriod to 0', function () {
  823. beforeEach(async function () {
  824. this.settings = {
  825. proposal: [
  826. [ this.mock.address ],
  827. [ web3.utils.toWei('0') ],
  828. [ this.mock.contract.methods.setVotingPeriod('0').encodeABI() ],
  829. '<proposal description>',
  830. ],
  831. tokenHolder: owner,
  832. voters: [
  833. { voter: voter1, weight: web3.utils.toWei('10'), support: Enums.VoteType.For },
  834. ],
  835. steps: {
  836. execute: { error: 'GovernorSettings: voting period too low' },
  837. },
  838. };
  839. });
  840. afterEach(async function () {
  841. expect(await this.mock.votingPeriod()).to.be.bignumber.equal('16');
  842. });
  843. runGovernorWorkflow();
  844. });
  845. describe('setProposalThreshold', function () {
  846. beforeEach(async function () {
  847. this.settings = {
  848. proposal: [
  849. [ this.mock.address ],
  850. [ web3.utils.toWei('0') ],
  851. [ this.mock.contract.methods.setProposalThreshold('1000000000000000000').encodeABI() ],
  852. '<proposal description>',
  853. ],
  854. tokenHolder: owner,
  855. voters: [
  856. { voter: voter1, weight: web3.utils.toWei('10'), support: Enums.VoteType.For },
  857. ],
  858. };
  859. });
  860. afterEach(async function () {
  861. expect(await this.mock.proposalThreshold()).to.be.bignumber.equal('1000000000000000000');
  862. expectEvent(
  863. this.receipts.execute,
  864. 'ProposalThresholdSet',
  865. { oldProposalThreshold: '0', newProposalThreshold: '1000000000000000000' },
  866. );
  867. });
  868. runGovernorWorkflow();
  869. });
  870. describe('update protected', function () {
  871. it('setVotingDelay', async function () {
  872. await expectRevert(this.mock.setVotingDelay('0'), 'Governor: onlyGovernance');
  873. });
  874. it('setVotingPeriod', async function () {
  875. await expectRevert(this.mock.setVotingPeriod('32'), 'Governor: onlyGovernance');
  876. });
  877. it('setProposalThreshold', async function () {
  878. await expectRevert(this.mock.setProposalThreshold('1000000000000000000'), 'Governor: onlyGovernance');
  879. });
  880. });
  881. });
  882. });