Governor.test.js 31 KB

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