Governor.test.js 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014
  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, Ballot } = 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 { shouldBehaveLikeERC6372 } = require('./utils/ERC6372.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. shouldBehaveLikeERC6372(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. },
  180. domain,
  181. message,
  182. },
  183. });
  184. };
  185. afterEach('no other votes are cast for proposalId', async function () {
  186. expect(await this.mock.hasVoted(this.proposal.id, owner)).to.be.equal(false);
  187. expect(await this.mock.hasVoted(this.proposal.id, voter1)).to.be.equal(false);
  188. expect(await this.mock.hasVoted(this.proposal.id, voter2)).to.be.equal(false);
  189. });
  190. it('votes with an EOA signature', async function () {
  191. const voterBySig = Wallet.generate();
  192. const voterBySigAddress = web3.utils.toChecksumAddress(voterBySig.getAddressString());
  193. await this.token.delegate(voterBySigAddress, { from: voter1 });
  194. const nonce = await this.mock.nonces(voterBySigAddress);
  195. // Run proposal
  196. await this.helper.propose();
  197. await this.helper.waitForSnapshot();
  198. expectEvent(
  199. await this.helper.vote({
  200. support: Enums.VoteType.For,
  201. voter: voterBySigAddress,
  202. nonce,
  203. signature: sign(voterBySig.getPrivateKey()),
  204. }),
  205. 'VoteCast',
  206. {
  207. voter: voterBySigAddress,
  208. support: Enums.VoteType.For,
  209. },
  210. );
  211. await this.helper.waitForDeadline();
  212. await this.helper.execute();
  213. // After
  214. expect(await this.mock.hasVoted(this.proposal.id, voterBySigAddress)).to.be.equal(true);
  215. expect(await this.mock.nonces(voterBySigAddress)).to.be.bignumber.equal(nonce.addn(1));
  216. });
  217. it('votes with a valid EIP-1271 signature', async function () {
  218. const ERC1271WalletOwner = Wallet.generate();
  219. ERC1271WalletOwner.address = web3.utils.toChecksumAddress(ERC1271WalletOwner.getAddressString());
  220. const wallet = await ERC1271WalletMock.new(ERC1271WalletOwner.address);
  221. await this.token.delegate(wallet.address, { from: voter1 });
  222. const nonce = await this.mock.nonces(wallet.address);
  223. // Run proposal
  224. await this.helper.propose();
  225. await this.helper.waitForSnapshot();
  226. expectEvent(
  227. await this.helper.vote({
  228. support: Enums.VoteType.For,
  229. voter: wallet.address,
  230. nonce,
  231. signature: sign(ERC1271WalletOwner.getPrivateKey()),
  232. }),
  233. 'VoteCast',
  234. {
  235. voter: wallet.address,
  236. support: Enums.VoteType.For,
  237. },
  238. );
  239. await this.helper.waitForDeadline();
  240. await this.helper.execute();
  241. // After
  242. expect(await this.mock.hasVoted(this.proposal.id, wallet.address)).to.be.equal(true);
  243. expect(await this.mock.nonces(wallet.address)).to.be.bignumber.equal(nonce.addn(1));
  244. });
  245. afterEach('no other votes are cast', async function () {
  246. expect(await this.mock.hasVoted(this.proposal.id, owner)).to.be.equal(false);
  247. expect(await this.mock.hasVoted(this.proposal.id, voter1)).to.be.equal(false);
  248. expect(await this.mock.hasVoted(this.proposal.id, voter2)).to.be.equal(false);
  249. });
  250. });
  251. describe('should revert', function () {
  252. describe('on propose', function () {
  253. it('if proposal already exists', async function () {
  254. await this.helper.propose();
  255. await expectRevertCustomError(this.helper.propose(), 'GovernorUnexpectedProposalState', [
  256. this.proposal.id,
  257. Enums.ProposalState.Pending,
  258. ZERO_BYTES32,
  259. ]);
  260. });
  261. it('if proposer has below threshold votes', async function () {
  262. const votes = web3.utils.toWei('10');
  263. const threshold = web3.utils.toWei('1000');
  264. await this.mock.$_setProposalThreshold(threshold);
  265. await expectRevertCustomError(this.helper.propose({ from: voter1 }), 'GovernorInsufficientProposerVotes', [
  266. voter1,
  267. votes,
  268. threshold,
  269. ]);
  270. });
  271. });
  272. describe('on vote', function () {
  273. it('if proposal does not exist', async function () {
  274. await expectRevertCustomError(
  275. this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }),
  276. 'GovernorNonexistentProposal',
  277. [this.proposal.id],
  278. );
  279. });
  280. it('if voting has not started', async function () {
  281. await this.helper.propose();
  282. await expectRevertCustomError(
  283. this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }),
  284. 'GovernorUnexpectedProposalState',
  285. [this.proposal.id, Enums.ProposalState.Pending, proposalStatesToBitMap([Enums.ProposalState.Active])],
  286. );
  287. });
  288. it('if support value is invalid', async function () {
  289. await this.helper.propose();
  290. await this.helper.waitForSnapshot();
  291. await expectRevertCustomError(
  292. this.helper.vote({ support: web3.utils.toBN('255') }),
  293. 'GovernorInvalidVoteType',
  294. [],
  295. );
  296. });
  297. it('if vote was already casted', async function () {
  298. await this.helper.propose();
  299. await this.helper.waitForSnapshot();
  300. await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
  301. await expectRevertCustomError(
  302. this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }),
  303. 'GovernorAlreadyCastVote',
  304. [voter1],
  305. );
  306. });
  307. it('if voting is over', async function () {
  308. await this.helper.propose();
  309. await this.helper.waitForDeadline();
  310. await expectRevertCustomError(
  311. this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }),
  312. 'GovernorUnexpectedProposalState',
  313. [this.proposal.id, Enums.ProposalState.Defeated, proposalStatesToBitMap([Enums.ProposalState.Active])],
  314. );
  315. });
  316. });
  317. describe('on vote by signature', function () {
  318. beforeEach(async function () {
  319. this.voterBySig = Wallet.generate();
  320. this.voterBySig.address = web3.utils.toChecksumAddress(this.voterBySig.getAddressString());
  321. this.data = (contract, message) =>
  322. getDomain(contract).then(domain => ({
  323. primaryType: 'Ballot',
  324. types: {
  325. EIP712Domain: domainType(domain),
  326. Ballot,
  327. },
  328. domain,
  329. message,
  330. }));
  331. this.signature = (contract, message) =>
  332. this.data(contract, message).then(data =>
  333. ethSigUtil.signTypedMessage(this.voterBySig.getPrivateKey(), { data }),
  334. );
  335. await this.token.delegate(this.voterBySig.address, { from: voter1 });
  336. // Run proposal
  337. await this.helper.propose();
  338. await this.helper.waitForSnapshot();
  339. });
  340. it('if signature does not match signer', async function () {
  341. const nonce = await this.mock.nonces(this.voterBySig.address);
  342. const voteParams = {
  343. support: Enums.VoteType.For,
  344. voter: this.voterBySig.address,
  345. nonce,
  346. signature: async (...params) => {
  347. const sig = await this.signature(...params);
  348. const tamperedSig = web3.utils.hexToBytes(sig);
  349. tamperedSig[42] ^= 0xff;
  350. return web3.utils.bytesToHex(tamperedSig);
  351. },
  352. };
  353. await expectRevertCustomError(this.helper.vote(voteParams), 'GovernorInvalidSignature', [voteParams.voter]);
  354. });
  355. it('if vote nonce is incorrect', async function () {
  356. const nonce = await this.mock.nonces(this.voterBySig.address);
  357. const voteParams = {
  358. support: Enums.VoteType.For,
  359. voter: this.voterBySig.address,
  360. nonce: nonce.addn(1),
  361. signature: this.signature,
  362. };
  363. await expectRevertCustomError(
  364. this.helper.vote(voteParams),
  365. // The signature check implies the nonce can't be tampered without changing the signer
  366. 'GovernorInvalidSignature',
  367. [voteParams.voter],
  368. );
  369. });
  370. });
  371. describe('on queue', function () {
  372. it('always', async function () {
  373. await this.helper.propose({ from: proposer });
  374. await this.helper.waitForSnapshot();
  375. await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
  376. await this.helper.waitForDeadline();
  377. await expectRevertCustomError(this.helper.queue(), 'GovernorQueueNotImplemented', []);
  378. });
  379. });
  380. describe('on execute', function () {
  381. it('if proposal does not exist', async function () {
  382. await expectRevertCustomError(this.helper.execute(), 'GovernorNonexistentProposal', [this.proposal.id]);
  383. });
  384. it('if quorum is not reached', async function () {
  385. await this.helper.propose();
  386. await this.helper.waitForSnapshot();
  387. await this.helper.vote({ support: Enums.VoteType.For }, { from: voter3 });
  388. await expectRevertCustomError(this.helper.execute(), 'GovernorUnexpectedProposalState', [
  389. this.proposal.id,
  390. Enums.ProposalState.Active,
  391. proposalStatesToBitMap([Enums.ProposalState.Succeeded, Enums.ProposalState.Queued]),
  392. ]);
  393. });
  394. it('if score not reached', async function () {
  395. await this.helper.propose();
  396. await this.helper.waitForSnapshot();
  397. await this.helper.vote({ support: Enums.VoteType.Against }, { from: voter1 });
  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 voting is not over', async function () {
  405. await this.helper.propose();
  406. await this.helper.waitForSnapshot();
  407. await this.helper.vote({ support: Enums.VoteType.For }, { 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 receiver revert without reason', async function () {
  415. this.helper.setProposal(
  416. [
  417. {
  418. target: this.receiver.address,
  419. data: this.receiver.contract.methods.mockFunctionRevertsNoReason().encodeABI(),
  420. },
  421. ],
  422. '<proposal description>',
  423. );
  424. await this.helper.propose();
  425. await this.helper.waitForSnapshot();
  426. await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
  427. await this.helper.waitForDeadline();
  428. await expectRevertCustomError(this.helper.execute(), 'FailedInnerCall', []);
  429. });
  430. it('if receiver revert with reason', async function () {
  431. this.helper.setProposal(
  432. [
  433. {
  434. target: this.receiver.address,
  435. data: this.receiver.contract.methods.mockFunctionRevertsReason().encodeABI(),
  436. },
  437. ],
  438. '<proposal description>',
  439. );
  440. await this.helper.propose();
  441. await this.helper.waitForSnapshot();
  442. await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
  443. await this.helper.waitForDeadline();
  444. await expectRevert(this.helper.execute(), 'CallReceiverMock: reverting');
  445. });
  446. it('if proposal was already executed', async function () {
  447. await this.helper.propose();
  448. await this.helper.waitForSnapshot();
  449. await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
  450. await this.helper.waitForDeadline();
  451. await this.helper.execute();
  452. await expectRevertCustomError(this.helper.execute(), 'GovernorUnexpectedProposalState', [
  453. this.proposal.id,
  454. Enums.ProposalState.Executed,
  455. proposalStatesToBitMap([Enums.ProposalState.Succeeded, Enums.ProposalState.Queued]),
  456. ]);
  457. });
  458. });
  459. });
  460. describe('state', function () {
  461. it('Unset', async function () {
  462. await expectRevertCustomError(this.mock.state(this.proposal.id), 'GovernorNonexistentProposal', [
  463. this.proposal.id,
  464. ]);
  465. });
  466. it('Pending & Active', async function () {
  467. await this.helper.propose();
  468. expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Pending);
  469. await this.helper.waitForSnapshot();
  470. expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Pending);
  471. await this.helper.waitForSnapshot(+1);
  472. expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Active);
  473. });
  474. it('Defeated', async function () {
  475. await this.helper.propose();
  476. await this.helper.waitForDeadline();
  477. expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Active);
  478. await this.helper.waitForDeadline(+1);
  479. expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Defeated);
  480. });
  481. it('Succeeded', async function () {
  482. await this.helper.propose();
  483. await this.helper.waitForSnapshot();
  484. await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
  485. await this.helper.waitForDeadline();
  486. expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Active);
  487. await this.helper.waitForDeadline(+1);
  488. expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Succeeded);
  489. });
  490. it('Executed', async function () {
  491. await this.helper.propose();
  492. await this.helper.waitForSnapshot();
  493. await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
  494. await this.helper.waitForDeadline();
  495. await this.helper.execute();
  496. expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Executed);
  497. });
  498. });
  499. describe('cancel', function () {
  500. describe('internal', function () {
  501. it('before proposal', async function () {
  502. await expectRevertCustomError(this.helper.cancel('internal'), 'GovernorNonexistentProposal', [
  503. this.proposal.id,
  504. ]);
  505. });
  506. it('after proposal', async function () {
  507. await this.helper.propose();
  508. await this.helper.cancel('internal');
  509. expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Canceled);
  510. await this.helper.waitForSnapshot();
  511. await expectRevertCustomError(
  512. this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }),
  513. 'GovernorUnexpectedProposalState',
  514. [this.proposal.id, Enums.ProposalState.Canceled, proposalStatesToBitMap([Enums.ProposalState.Active])],
  515. );
  516. });
  517. it('after vote', async function () {
  518. await this.helper.propose();
  519. await this.helper.waitForSnapshot();
  520. await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
  521. await this.helper.cancel('internal');
  522. expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Canceled);
  523. await this.helper.waitForDeadline();
  524. await expectRevertCustomError(this.helper.execute(), 'GovernorUnexpectedProposalState', [
  525. this.proposal.id,
  526. Enums.ProposalState.Canceled,
  527. proposalStatesToBitMap([Enums.ProposalState.Succeeded, Enums.ProposalState.Queued]),
  528. ]);
  529. });
  530. it('after deadline', async function () {
  531. await this.helper.propose();
  532. await this.helper.waitForSnapshot();
  533. await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
  534. await this.helper.waitForDeadline();
  535. await this.helper.cancel('internal');
  536. expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Canceled);
  537. await expectRevertCustomError(this.helper.execute(), 'GovernorUnexpectedProposalState', [
  538. this.proposal.id,
  539. Enums.ProposalState.Canceled,
  540. proposalStatesToBitMap([Enums.ProposalState.Succeeded, Enums.ProposalState.Queued]),
  541. ]);
  542. });
  543. it('after execution', async function () {
  544. await this.helper.propose();
  545. await this.helper.waitForSnapshot();
  546. await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
  547. await this.helper.waitForDeadline();
  548. await this.helper.execute();
  549. await expectRevertCustomError(this.helper.cancel('internal'), 'GovernorUnexpectedProposalState', [
  550. this.proposal.id,
  551. Enums.ProposalState.Executed,
  552. proposalStatesToBitMap(
  553. [Enums.ProposalState.Canceled, Enums.ProposalState.Expired, Enums.ProposalState.Executed],
  554. { inverted: true },
  555. ),
  556. ]);
  557. });
  558. });
  559. describe('public', function () {
  560. it('before proposal', async function () {
  561. await expectRevertCustomError(this.helper.cancel('external'), 'GovernorNonexistentProposal', [
  562. this.proposal.id,
  563. ]);
  564. });
  565. it('after proposal', async function () {
  566. await this.helper.propose();
  567. await this.helper.cancel('external');
  568. });
  569. it('after proposal - restricted to proposer', async function () {
  570. await this.helper.propose();
  571. await expectRevertCustomError(this.helper.cancel('external', { from: owner }), 'GovernorOnlyProposer', [
  572. owner,
  573. ]);
  574. });
  575. it('after vote started', async function () {
  576. await this.helper.propose();
  577. await this.helper.waitForSnapshot(1); // snapshot + 1 block
  578. await expectRevertCustomError(this.helper.cancel('external'), 'GovernorUnexpectedProposalState', [
  579. this.proposal.id,
  580. Enums.ProposalState.Active,
  581. proposalStatesToBitMap([Enums.ProposalState.Pending]),
  582. ]);
  583. });
  584. it('after vote', async function () {
  585. await this.helper.propose();
  586. await this.helper.waitForSnapshot();
  587. await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
  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 deadline', 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 this.helper.waitForDeadline();
  599. await expectRevertCustomError(this.helper.cancel('external'), 'GovernorUnexpectedProposalState', [
  600. this.proposal.id,
  601. Enums.ProposalState.Succeeded,
  602. proposalStatesToBitMap([Enums.ProposalState.Pending]),
  603. ]);
  604. });
  605. it('after execution', async function () {
  606. await this.helper.propose();
  607. await this.helper.waitForSnapshot();
  608. await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
  609. await this.helper.waitForDeadline();
  610. await this.helper.execute();
  611. await expectRevertCustomError(this.helper.cancel('external'), 'GovernorUnexpectedProposalState', [
  612. this.proposal.id,
  613. Enums.ProposalState.Executed,
  614. proposalStatesToBitMap([Enums.ProposalState.Pending]),
  615. ]);
  616. });
  617. });
  618. });
  619. describe('proposal length', function () {
  620. it('empty', async function () {
  621. this.helper.setProposal([], '<proposal description>');
  622. await expectRevertCustomError(this.helper.propose(), 'GovernorInvalidProposalLength', [0, 0, 0]);
  623. });
  624. it('mismatch #1', async function () {
  625. this.helper.setProposal(
  626. {
  627. targets: [],
  628. values: [web3.utils.toWei('0')],
  629. data: [this.receiver.contract.methods.mockFunction().encodeABI()],
  630. },
  631. '<proposal description>',
  632. );
  633. await expectRevertCustomError(this.helper.propose(), 'GovernorInvalidProposalLength', [0, 1, 1]);
  634. });
  635. it('mismatch #2', async function () {
  636. this.helper.setProposal(
  637. {
  638. targets: [this.receiver.address],
  639. values: [],
  640. data: [this.receiver.contract.methods.mockFunction().encodeABI()],
  641. },
  642. '<proposal description>',
  643. );
  644. await expectRevertCustomError(this.helper.propose(), 'GovernorInvalidProposalLength', [1, 1, 0]);
  645. });
  646. it('mismatch #3', async function () {
  647. this.helper.setProposal(
  648. {
  649. targets: [this.receiver.address],
  650. values: [web3.utils.toWei('0')],
  651. data: [],
  652. },
  653. '<proposal description>',
  654. );
  655. await expectRevertCustomError(this.helper.propose(), 'GovernorInvalidProposalLength', [1, 0, 1]);
  656. });
  657. });
  658. describe('frontrun protection using description suffix', function () {
  659. describe('without protection', function () {
  660. describe('without suffix', function () {
  661. it('proposer can propose', async function () {
  662. expectEvent(await this.helper.propose({ from: proposer }), 'ProposalCreated');
  663. });
  664. it('someone else can propose', async function () {
  665. expectEvent(await this.helper.propose({ from: voter1 }), 'ProposalCreated');
  666. });
  667. });
  668. describe('with different suffix', function () {
  669. beforeEach(async function () {
  670. this.proposal = this.helper.setProposal(
  671. [
  672. {
  673. target: this.receiver.address,
  674. data: this.receiver.contract.methods.mockFunction().encodeABI(),
  675. value,
  676. },
  677. ],
  678. `<proposal description>#wrong-suffix=${proposer}`,
  679. );
  680. });
  681. it('proposer can propose', async function () {
  682. expectEvent(await this.helper.propose({ from: proposer }), 'ProposalCreated');
  683. });
  684. it('someone else can propose', async function () {
  685. expectEvent(await this.helper.propose({ from: voter1 }), 'ProposalCreated');
  686. });
  687. });
  688. describe('with proposer suffix but bad address part', function () {
  689. beforeEach(async function () {
  690. this.proposal = this.helper.setProposal(
  691. [
  692. {
  693. target: this.receiver.address,
  694. data: this.receiver.contract.methods.mockFunction().encodeABI(),
  695. value,
  696. },
  697. ],
  698. `<proposal description>#proposer=0x3C44CdDdB6a900fa2b585dd299e03d12FA429XYZ`, // XYZ are not a valid hex char
  699. );
  700. });
  701. it('propose can propose', async function () {
  702. expectEvent(await this.helper.propose({ from: proposer }), 'ProposalCreated');
  703. });
  704. it('someone else can propose', async function () {
  705. expectEvent(await this.helper.propose({ from: voter1 }), 'ProposalCreated');
  706. });
  707. });
  708. });
  709. describe('with protection via proposer suffix', function () {
  710. beforeEach(async function () {
  711. this.proposal = this.helper.setProposal(
  712. [
  713. {
  714. target: this.receiver.address,
  715. data: this.receiver.contract.methods.mockFunction().encodeABI(),
  716. value,
  717. },
  718. ],
  719. `<proposal description>#proposer=${proposer}`,
  720. );
  721. });
  722. it('proposer can propose', async function () {
  723. expectEvent(await this.helper.propose({ from: proposer }), 'ProposalCreated');
  724. });
  725. it('someone else cannot propose', async function () {
  726. await expectRevertCustomError(this.helper.propose({ from: voter1 }), 'GovernorRestrictedProposer', [
  727. voter1,
  728. ]);
  729. });
  730. });
  731. });
  732. describe('onlyGovernance updates', function () {
  733. it('setVotingDelay is protected', async function () {
  734. await expectRevertCustomError(this.mock.setVotingDelay('0', { from: owner }), 'GovernorOnlyExecutor', [
  735. owner,
  736. ]);
  737. });
  738. it('setVotingPeriod is protected', async function () {
  739. await expectRevertCustomError(this.mock.setVotingPeriod('32', { from: owner }), 'GovernorOnlyExecutor', [
  740. owner,
  741. ]);
  742. });
  743. it('setProposalThreshold is protected', async function () {
  744. await expectRevertCustomError(
  745. this.mock.setProposalThreshold('1000000000000000000', { from: owner }),
  746. 'GovernorOnlyExecutor',
  747. [owner],
  748. );
  749. });
  750. it('can setVotingDelay through governance', async function () {
  751. this.helper.setProposal(
  752. [
  753. {
  754. target: this.mock.address,
  755. data: this.mock.contract.methods.setVotingDelay('0').encodeABI(),
  756. },
  757. ],
  758. '<proposal description>',
  759. );
  760. await this.helper.propose();
  761. await this.helper.waitForSnapshot();
  762. await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
  763. await this.helper.waitForDeadline();
  764. expectEvent(await this.helper.execute(), 'VotingDelaySet', { oldVotingDelay: '4', newVotingDelay: '0' });
  765. expect(await this.mock.votingDelay()).to.be.bignumber.equal('0');
  766. });
  767. it('can setVotingPeriod through governance', async function () {
  768. this.helper.setProposal(
  769. [
  770. {
  771. target: this.mock.address,
  772. data: this.mock.contract.methods.setVotingPeriod('32').encodeABI(),
  773. },
  774. ],
  775. '<proposal description>',
  776. );
  777. await this.helper.propose();
  778. await this.helper.waitForSnapshot();
  779. await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
  780. await this.helper.waitForDeadline();
  781. expectEvent(await this.helper.execute(), 'VotingPeriodSet', { oldVotingPeriod: '16', newVotingPeriod: '32' });
  782. expect(await this.mock.votingPeriod()).to.be.bignumber.equal('32');
  783. });
  784. it('cannot setVotingPeriod to 0 through governance', async function () {
  785. const votingPeriod = 0;
  786. this.helper.setProposal(
  787. [
  788. {
  789. target: this.mock.address,
  790. data: this.mock.contract.methods.setVotingPeriod(votingPeriod).encodeABI(),
  791. },
  792. ],
  793. '<proposal description>',
  794. );
  795. await this.helper.propose();
  796. await this.helper.waitForSnapshot();
  797. await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
  798. await this.helper.waitForDeadline();
  799. await expectRevertCustomError(this.helper.execute(), 'GovernorInvalidVotingPeriod', [votingPeriod]);
  800. });
  801. it('can setProposalThreshold to 0 through governance', async function () {
  802. this.helper.setProposal(
  803. [
  804. {
  805. target: this.mock.address,
  806. data: this.mock.contract.methods.setProposalThreshold('1000000000000000000').encodeABI(),
  807. },
  808. ],
  809. '<proposal description>',
  810. );
  811. await this.helper.propose();
  812. await this.helper.waitForSnapshot();
  813. await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
  814. await this.helper.waitForDeadline();
  815. expectEvent(await this.helper.execute(), 'ProposalThresholdSet', {
  816. oldProposalThreshold: '0',
  817. newProposalThreshold: '1000000000000000000',
  818. });
  819. expect(await this.mock.proposalThreshold()).to.be.bignumber.equal('1000000000000000000');
  820. });
  821. });
  822. describe('safe receive', function () {
  823. describe('ERC721', function () {
  824. const name = 'Non Fungible Token';
  825. const symbol = 'NFT';
  826. const tokenId = web3.utils.toBN(1);
  827. beforeEach(async function () {
  828. this.token = await ERC721.new(name, symbol);
  829. await this.token.$_mint(owner, tokenId);
  830. });
  831. it('can receive an ERC721 safeTransfer', async function () {
  832. await this.token.safeTransferFrom(owner, this.mock.address, tokenId, { from: owner });
  833. });
  834. });
  835. describe('ERC1155', function () {
  836. const uri = 'https://token-cdn-domain/{id}.json';
  837. const tokenIds = {
  838. 1: web3.utils.toBN(1000),
  839. 2: web3.utils.toBN(2000),
  840. 3: web3.utils.toBN(3000),
  841. };
  842. beforeEach(async function () {
  843. this.token = await ERC1155.new(uri);
  844. await this.token.$_mintBatch(owner, Object.keys(tokenIds), Object.values(tokenIds), '0x');
  845. });
  846. it('can receive ERC1155 safeTransfer', async function () {
  847. await this.token.safeTransferFrom(
  848. owner,
  849. this.mock.address,
  850. ...Object.entries(tokenIds)[0], // id + amount
  851. '0x',
  852. { from: owner },
  853. );
  854. });
  855. it('can receive ERC1155 safeBatchTransfer', async function () {
  856. await this.token.safeBatchTransferFrom(
  857. owner,
  858. this.mock.address,
  859. Object.keys(tokenIds),
  860. Object.values(tokenIds),
  861. '0x',
  862. { from: owner },
  863. );
  864. });
  865. });
  866. });
  867. });
  868. }
  869. });