Governor.test.js 40 KB

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