|
@@ -1,13 +1,11 @@
|
|
|
-const { BN, constants, expectEvent, expectRevert, time } = require('@openzeppelin/test-helpers');
|
|
|
+const { BN, expectEvent, expectRevert } = require('@openzeppelin/test-helpers');
|
|
|
+const { expect } = require('chai');
|
|
|
const ethSigUtil = require('eth-sig-util');
|
|
|
const Wallet = require('ethereumjs-wallet').default;
|
|
|
+const { fromRpcSig } = require('ethereumjs-util');
|
|
|
const Enums = require('../helpers/enums');
|
|
|
const { EIP712Domain } = require('../helpers/eip712');
|
|
|
-const { fromRpcSig } = require('ethereumjs-util');
|
|
|
-
|
|
|
-const {
|
|
|
- runGovernorWorkflow,
|
|
|
-} = require('./GovernorWorkflow.behavior');
|
|
|
+const { GovernorHelper } = require('../helpers/governance');
|
|
|
|
|
|
const {
|
|
|
shouldSupportInterfaces,
|
|
@@ -19,23 +17,40 @@ const CallReceiver = artifacts.require('CallReceiverMock');
|
|
|
|
|
|
contract('Governor', function (accounts) {
|
|
|
const [ owner, proposer, voter1, voter2, voter3, voter4 ] = accounts;
|
|
|
+ const empty = web3.utils.toChecksumAddress(web3.utils.randomHex(20));
|
|
|
|
|
|
const name = 'OZ-Governor';
|
|
|
const version = '1';
|
|
|
const tokenName = 'MockToken';
|
|
|
const tokenSymbol = 'MTKN';
|
|
|
const tokenSupply = web3.utils.toWei('100');
|
|
|
+ const votingDelay = new BN(4);
|
|
|
+ const votingPeriod = new BN(16);
|
|
|
+ const value = web3.utils.toWei('1');
|
|
|
|
|
|
beforeEach(async function () {
|
|
|
- this.owner = owner;
|
|
|
+ this.chainId = await web3.eth.getChainId();
|
|
|
this.token = await Token.new(tokenName, tokenSymbol);
|
|
|
- this.mock = await Governor.new(name, this.token.address, 4, 16, 10);
|
|
|
+ this.mock = await Governor.new(name, this.token.address, votingDelay, votingPeriod, 10);
|
|
|
this.receiver = await CallReceiver.new();
|
|
|
+
|
|
|
+ this.helper = new GovernorHelper(this.mock);
|
|
|
+
|
|
|
+ await web3.eth.sendTransaction({ from: owner, to: this.mock.address, value });
|
|
|
+
|
|
|
await this.token.mint(owner, tokenSupply);
|
|
|
- await this.token.delegate(voter1, { from: voter1 });
|
|
|
- await this.token.delegate(voter2, { from: voter2 });
|
|
|
- await this.token.delegate(voter3, { from: voter3 });
|
|
|
- await this.token.delegate(voter4, { from: voter4 });
|
|
|
+ await this.helper.delegate({ token: this.token, to: voter1, value: web3.utils.toWei('10') }, { from: owner });
|
|
|
+ await this.helper.delegate({ token: this.token, to: voter2, value: web3.utils.toWei('7') }, { from: owner });
|
|
|
+ await this.helper.delegate({ token: this.token, to: voter3, value: web3.utils.toWei('5') }, { from: owner });
|
|
|
+ await this.helper.delegate({ token: this.token, to: voter4, value: web3.utils.toWei('2') }, { from: owner });
|
|
|
+
|
|
|
+ this.proposal = this.helper.setProposal([
|
|
|
+ {
|
|
|
+ target: this.receiver.address,
|
|
|
+ data: this.receiver.contract.methods.mockFunction().encodeABI(),
|
|
|
+ value,
|
|
|
+ },
|
|
|
+ ], '<proposal description>');
|
|
|
});
|
|
|
|
|
|
shouldSupportInterfaces([
|
|
@@ -47,901 +62,516 @@ contract('Governor', function (accounts) {
|
|
|
it('deployment check', async function () {
|
|
|
expect(await this.mock.name()).to.be.equal(name);
|
|
|
expect(await this.mock.token()).to.be.equal(this.token.address);
|
|
|
- expect(await this.mock.votingDelay()).to.be.bignumber.equal('4');
|
|
|
- expect(await this.mock.votingPeriod()).to.be.bignumber.equal('16');
|
|
|
+ expect(await this.mock.votingDelay()).to.be.bignumber.equal(votingDelay);
|
|
|
+ expect(await this.mock.votingPeriod()).to.be.bignumber.equal(votingPeriod);
|
|
|
expect(await this.mock.quorum(0)).to.be.bignumber.equal('0');
|
|
|
expect(await this.mock.COUNTING_MODE()).to.be.equal('support=bravo&quorum=for,abstain');
|
|
|
});
|
|
|
|
|
|
- describe('scenario', function () {
|
|
|
- describe('nominal', function () {
|
|
|
- beforeEach(async function () {
|
|
|
- this.value = web3.utils.toWei('1');
|
|
|
-
|
|
|
- await web3.eth.sendTransaction({ from: owner, to: this.mock.address, value: this.value });
|
|
|
- expect(await web3.eth.getBalance(this.mock.address)).to.be.bignumber.equal(this.value);
|
|
|
- expect(await web3.eth.getBalance(this.receiver.address)).to.be.bignumber.equal('0');
|
|
|
-
|
|
|
- this.settings = {
|
|
|
- proposal: [
|
|
|
- [ this.receiver.address ],
|
|
|
- [ this.value ],
|
|
|
- [ this.receiver.contract.methods.mockFunction().encodeABI() ],
|
|
|
- '<proposal description>',
|
|
|
- ],
|
|
|
- proposer,
|
|
|
- tokenHolder: owner,
|
|
|
- voters: [
|
|
|
- { voter: voter1, weight: web3.utils.toWei('1'), support: Enums.VoteType.For, reason: 'This is nice' },
|
|
|
- { voter: voter2, weight: web3.utils.toWei('7'), support: Enums.VoteType.For },
|
|
|
- { voter: voter3, weight: web3.utils.toWei('5'), support: Enums.VoteType.Against },
|
|
|
- { voter: voter4, weight: web3.utils.toWei('2'), support: Enums.VoteType.Abstain },
|
|
|
- ],
|
|
|
- };
|
|
|
- this.votingDelay = await this.mock.votingDelay();
|
|
|
- this.votingPeriod = await this.mock.votingPeriod();
|
|
|
- });
|
|
|
+ it('nominal workflow', async function () {
|
|
|
+ // Before
|
|
|
+ expect(await this.mock.hasVoted(this.proposal.id, owner)).to.be.equal(false);
|
|
|
+ expect(await this.mock.hasVoted(this.proposal.id, voter1)).to.be.equal(false);
|
|
|
+ expect(await this.mock.hasVoted(this.proposal.id, voter2)).to.be.equal(false);
|
|
|
+ expect(await web3.eth.getBalance(this.mock.address)).to.be.bignumber.equal(value);
|
|
|
+ expect(await web3.eth.getBalance(this.receiver.address)).to.be.bignumber.equal('0');
|
|
|
+
|
|
|
+ // Run proposal
|
|
|
+ const txPropose = await this.helper.propose({ from: proposer });
|
|
|
+
|
|
|
+ expectEvent(
|
|
|
+ txPropose,
|
|
|
+ 'ProposalCreated',
|
|
|
+ {
|
|
|
+ proposalId: this.proposal.id,
|
|
|
+ proposer,
|
|
|
+ targets: this.proposal.targets,
|
|
|
+ // values: this.proposal.values,
|
|
|
+ signatures: this.proposal.signatures,
|
|
|
+ calldatas: this.proposal.data,
|
|
|
+ startBlock: new BN(txPropose.receipt.blockNumber).add(votingDelay),
|
|
|
+ endBlock: new BN(txPropose.receipt.blockNumber).add(votingDelay).add(votingPeriod),
|
|
|
+ description: this.proposal.description,
|
|
|
+ },
|
|
|
+ );
|
|
|
+
|
|
|
+ await this.helper.waitForSnapshot();
|
|
|
+
|
|
|
+ expectEvent(
|
|
|
+ await this.helper.vote({ support: Enums.VoteType.For, reason: 'This is nice' }, { from: voter1 }),
|
|
|
+ 'VoteCast',
|
|
|
+ {
|
|
|
+ voter: voter1,
|
|
|
+ support: Enums.VoteType.For,
|
|
|
+ reason: 'This is nice',
|
|
|
+ weight: web3.utils.toWei('10'),
|
|
|
+ },
|
|
|
+ );
|
|
|
+
|
|
|
+ expectEvent(
|
|
|
+ await this.helper.vote({ support: Enums.VoteType.For }, { from: voter2 }),
|
|
|
+ 'VoteCast',
|
|
|
+ {
|
|
|
+ voter: voter2,
|
|
|
+ support: Enums.VoteType.For,
|
|
|
+ weight: web3.utils.toWei('7'),
|
|
|
+ },
|
|
|
+ );
|
|
|
+
|
|
|
+ expectEvent(
|
|
|
+ await this.helper.vote({ support: Enums.VoteType.Against }, { from: voter3 }),
|
|
|
+ 'VoteCast',
|
|
|
+ {
|
|
|
+ voter: voter3,
|
|
|
+ support: Enums.VoteType.Against,
|
|
|
+ weight: web3.utils.toWei('5'),
|
|
|
+ },
|
|
|
+ );
|
|
|
+
|
|
|
+ expectEvent(
|
|
|
+ await this.helper.vote({ support: Enums.VoteType.Abstain }, { from: voter4 }),
|
|
|
+ 'VoteCast',
|
|
|
+ {
|
|
|
+ voter: voter4,
|
|
|
+ support: Enums.VoteType.Abstain,
|
|
|
+ weight: web3.utils.toWei('2'),
|
|
|
+ },
|
|
|
+ );
|
|
|
+
|
|
|
+ await this.helper.waitForDeadline();
|
|
|
+
|
|
|
+ const txExecute = await this.helper.execute();
|
|
|
+
|
|
|
+ expectEvent(
|
|
|
+ txExecute,
|
|
|
+ 'ProposalExecuted',
|
|
|
+ { proposalId: this.proposal.id },
|
|
|
+ );
|
|
|
+
|
|
|
+ await expectEvent.inTransaction(
|
|
|
+ txExecute.tx,
|
|
|
+ this.receiver,
|
|
|
+ 'MockFunctionCalled',
|
|
|
+ );
|
|
|
+
|
|
|
+ // After
|
|
|
+ expect(await this.mock.hasVoted(this.proposal.id, owner)).to.be.equal(false);
|
|
|
+ expect(await this.mock.hasVoted(this.proposal.id, voter1)).to.be.equal(true);
|
|
|
+ expect(await this.mock.hasVoted(this.proposal.id, voter2)).to.be.equal(true);
|
|
|
+ expect(await web3.eth.getBalance(this.mock.address)).to.be.bignumber.equal('0');
|
|
|
+ expect(await web3.eth.getBalance(this.receiver.address)).to.be.bignumber.equal(value);
|
|
|
+ });
|
|
|
|
|
|
- afterEach(async function () {
|
|
|
- expect(await this.mock.hasVoted(this.id, owner)).to.be.equal(false);
|
|
|
- expect(await this.mock.hasVoted(this.id, voter1)).to.be.equal(true);
|
|
|
- expect(await this.mock.hasVoted(this.id, voter2)).to.be.equal(true);
|
|
|
-
|
|
|
- await this.mock.proposalVotes(this.id).then(result => {
|
|
|
- for (const [key, value] of Object.entries(Enums.VoteType)) {
|
|
|
- expect(result[`${key.toLowerCase()}Votes`]).to.be.bignumber.equal(
|
|
|
- Object.values(this.settings.voters).filter(({ support }) => support === value).reduce(
|
|
|
- (acc, { weight }) => acc.add(new BN(weight)),
|
|
|
- new BN('0'),
|
|
|
- ),
|
|
|
- );
|
|
|
- }
|
|
|
- });
|
|
|
-
|
|
|
- expectEvent(
|
|
|
- this.receipts.propose,
|
|
|
- 'ProposalCreated',
|
|
|
- {
|
|
|
- proposalId: this.id,
|
|
|
- proposer,
|
|
|
- targets: this.settings.proposal[0],
|
|
|
- // values: this.settings.proposal[1].map(value => new BN(value)),
|
|
|
- signatures: this.settings.proposal[2].map(() => ''),
|
|
|
- calldatas: this.settings.proposal[2],
|
|
|
- startBlock: new BN(this.receipts.propose.blockNumber).add(this.votingDelay),
|
|
|
- endBlock: new BN(this.receipts.propose.blockNumber).add(this.votingDelay).add(this.votingPeriod),
|
|
|
- description: this.settings.proposal[3],
|
|
|
+ it('vote with signature', async function () {
|
|
|
+ const voterBySig = Wallet.generate();
|
|
|
+ const voterBySigAddress = web3.utils.toChecksumAddress(voterBySig.getAddressString());
|
|
|
+
|
|
|
+ const signature = async (message) => {
|
|
|
+ return fromRpcSig(ethSigUtil.signTypedMessage(
|
|
|
+ voterBySig.getPrivateKey(),
|
|
|
+ {
|
|
|
+ data: {
|
|
|
+ types: {
|
|
|
+ EIP712Domain,
|
|
|
+ Ballot: [
|
|
|
+ { name: 'proposalId', type: 'uint256' },
|
|
|
+ { name: 'support', type: 'uint8' },
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ domain: { name, version, chainId: this.chainId, verifyingContract: this.mock.address },
|
|
|
+ primaryType: 'Ballot',
|
|
|
+ message,
|
|
|
},
|
|
|
- );
|
|
|
+ },
|
|
|
+ ));
|
|
|
+ };
|
|
|
+
|
|
|
+ await this.token.delegate(voterBySigAddress, { from: voter1 });
|
|
|
+
|
|
|
+ // Run proposal
|
|
|
+ await this.helper.propose();
|
|
|
+ await this.helper.waitForSnapshot();
|
|
|
+ expectEvent(
|
|
|
+ await this.helper.vote({ support: Enums.VoteType.For, signature }),
|
|
|
+ 'VoteCast',
|
|
|
+ { voter: voterBySigAddress, support: Enums.VoteType.For },
|
|
|
+ );
|
|
|
+ await this.helper.waitForDeadline();
|
|
|
+ await this.helper.execute();
|
|
|
+
|
|
|
+ // After
|
|
|
+ expect(await this.mock.hasVoted(this.proposal.id, owner)).to.be.equal(false);
|
|
|
+ expect(await this.mock.hasVoted(this.proposal.id, voter1)).to.be.equal(false);
|
|
|
+ expect(await this.mock.hasVoted(this.proposal.id, voter2)).to.be.equal(false);
|
|
|
+ expect(await this.mock.hasVoted(this.proposal.id, voterBySigAddress)).to.be.equal(true);
|
|
|
+ });
|
|
|
|
|
|
- this.receipts.castVote.filter(Boolean).forEach(vote => {
|
|
|
- const { voter } = vote.logs.find(Boolean).args;
|
|
|
- expectEvent(
|
|
|
- vote,
|
|
|
- 'VoteCast',
|
|
|
- this.settings.voters.find(({ address }) => address === voter),
|
|
|
- );
|
|
|
- });
|
|
|
- expectEvent(
|
|
|
- this.receipts.execute,
|
|
|
- 'ProposalExecuted',
|
|
|
- { proposalId: this.id },
|
|
|
- );
|
|
|
- await expectEvent.inTransaction(
|
|
|
- this.receipts.execute.transactionHash,
|
|
|
- this.receiver,
|
|
|
- 'MockFunctionCalled',
|
|
|
- );
|
|
|
+ it('send ethers', async function () {
|
|
|
+ this.proposal = this.helper.setProposal([
|
|
|
+ {
|
|
|
+ target: empty,
|
|
|
+ value,
|
|
|
+ },
|
|
|
+ ], '<proposal description>');
|
|
|
+
|
|
|
+ // Before
|
|
|
+ expect(await web3.eth.getBalance(this.mock.address)).to.be.bignumber.equal(value);
|
|
|
+ expect(await web3.eth.getBalance(empty)).to.be.bignumber.equal('0');
|
|
|
+
|
|
|
+ // Run proposal
|
|
|
+ await this.helper.propose();
|
|
|
+ await this.helper.waitForSnapshot();
|
|
|
+ await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
|
|
|
+ await this.helper.waitForDeadline();
|
|
|
+ await this.helper.execute();
|
|
|
+
|
|
|
+ // After
|
|
|
+ expect(await web3.eth.getBalance(this.mock.address)).to.be.bignumber.equal('0');
|
|
|
+ expect(await web3.eth.getBalance(empty)).to.be.bignumber.equal(value);
|
|
|
+ });
|
|
|
|
|
|
- expect(await web3.eth.getBalance(this.mock.address)).to.be.bignumber.equal('0');
|
|
|
- expect(await web3.eth.getBalance(this.receiver.address)).to.be.bignumber.equal(this.value);
|
|
|
+ describe('should revert', function () {
|
|
|
+ describe('on propose', function () {
|
|
|
+ it('if proposal already exists', async function () {
|
|
|
+ await this.helper.propose();
|
|
|
+ await expectRevert(this.helper.propose(), 'Governor: proposal already exists');
|
|
|
});
|
|
|
- runGovernorWorkflow();
|
|
|
});
|
|
|
|
|
|
- describe('vote with signature', function () {
|
|
|
- beforeEach(async function () {
|
|
|
- const chainId = await web3.eth.getChainId();
|
|
|
- // generate voter by signature wallet
|
|
|
- const voterBySig = Wallet.generate();
|
|
|
- this.voter = web3.utils.toChecksumAddress(voterBySig.getAddressString());
|
|
|
- // use delegateBySig to enable vote delegation for this wallet
|
|
|
- const { v, r, s } = fromRpcSig(ethSigUtil.signTypedMessage(
|
|
|
- voterBySig.getPrivateKey(),
|
|
|
- {
|
|
|
- data: {
|
|
|
- types: {
|
|
|
- EIP712Domain,
|
|
|
- Delegation: [
|
|
|
- { name: 'delegatee', type: 'address' },
|
|
|
- { name: 'nonce', type: 'uint256' },
|
|
|
- { name: 'expiry', type: 'uint256' },
|
|
|
- ],
|
|
|
- },
|
|
|
- domain: { name: tokenName, version: '1', chainId, verifyingContract: this.token.address },
|
|
|
- primaryType: 'Delegation',
|
|
|
- message: { delegatee: this.voter, nonce: 0, expiry: constants.MAX_UINT256 },
|
|
|
- },
|
|
|
- },
|
|
|
- ));
|
|
|
- await this.token.delegateBySig(this.voter, 0, constants.MAX_UINT256, v, r, s);
|
|
|
- // prepare signature for vote by signature
|
|
|
- const signature = async (message) => {
|
|
|
- return fromRpcSig(ethSigUtil.signTypedMessage(
|
|
|
- voterBySig.getPrivateKey(),
|
|
|
- {
|
|
|
- data: {
|
|
|
- types: {
|
|
|
- EIP712Domain,
|
|
|
- Ballot: [
|
|
|
- { name: 'proposalId', type: 'uint256' },
|
|
|
- { name: 'support', type: 'uint8' },
|
|
|
- ],
|
|
|
- },
|
|
|
- domain: { name, version, chainId, verifyingContract: this.mock.address },
|
|
|
- primaryType: 'Ballot',
|
|
|
- message,
|
|
|
- },
|
|
|
- },
|
|
|
- ));
|
|
|
- };
|
|
|
-
|
|
|
- this.settings = {
|
|
|
- proposal: [
|
|
|
- [ this.receiver.address ],
|
|
|
- [ web3.utils.toWei('0') ],
|
|
|
- [ this.receiver.contract.methods.mockFunction().encodeABI() ],
|
|
|
- '<proposal description>',
|
|
|
- ],
|
|
|
- tokenHolder: owner,
|
|
|
- voters: [
|
|
|
- { voter: this.voter, signature, weight: web3.utils.toWei('10'), support: Enums.VoteType.For },
|
|
|
- ],
|
|
|
- };
|
|
|
- });
|
|
|
- afterEach(async function () {
|
|
|
- expect(await this.mock.hasVoted(this.id, owner)).to.be.equal(false);
|
|
|
- expect(await this.mock.hasVoted(this.id, voter1)).to.be.equal(false);
|
|
|
- expect(await this.mock.hasVoted(this.id, voter2)).to.be.equal(false);
|
|
|
- expect(await this.mock.hasVoted(this.id, this.voter)).to.be.equal(true);
|
|
|
-
|
|
|
- await this.mock.proposalVotes(this.id).then(result => {
|
|
|
- for (const [key, value] of Object.entries(Enums.VoteType)) {
|
|
|
- expect(result[`${key.toLowerCase()}Votes`]).to.be.bignumber.equal(
|
|
|
- Object.values(this.settings.voters).filter(({ support }) => support === value).reduce(
|
|
|
- (acc, { weight }) => acc.add(new BN(weight)),
|
|
|
- new BN('0'),
|
|
|
- ),
|
|
|
- );
|
|
|
- }
|
|
|
- });
|
|
|
-
|
|
|
- expectEvent(
|
|
|
- this.receipts.propose,
|
|
|
- 'ProposalCreated',
|
|
|
- { proposalId: this.id },
|
|
|
- );
|
|
|
- expectEvent(
|
|
|
- this.receipts.execute,
|
|
|
- 'ProposalExecuted',
|
|
|
- { proposalId: this.id },
|
|
|
- );
|
|
|
- await expectEvent.inTransaction(
|
|
|
- this.receipts.execute.transactionHash,
|
|
|
- this.receiver,
|
|
|
- 'MockFunctionCalled',
|
|
|
+ describe('on vote', function () {
|
|
|
+ it('if proposal does not exist', async function () {
|
|
|
+ await expectRevert(
|
|
|
+ this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }),
|
|
|
+ 'Governor: unknown proposal id',
|
|
|
);
|
|
|
});
|
|
|
- runGovernorWorkflow();
|
|
|
- });
|
|
|
|
|
|
- describe('send ethers', function () {
|
|
|
- beforeEach(async function () {
|
|
|
- this.receiver = { address: web3.utils.toChecksumAddress(web3.utils.randomHex(20)) };
|
|
|
- this.value = web3.utils.toWei('1');
|
|
|
-
|
|
|
- await web3.eth.sendTransaction({ from: owner, to: this.mock.address, value: this.value });
|
|
|
- expect(await web3.eth.getBalance(this.mock.address)).to.be.bignumber.equal(this.value);
|
|
|
- expect(await web3.eth.getBalance(this.receiver.address)).to.be.bignumber.equal('0');
|
|
|
-
|
|
|
- this.settings = {
|
|
|
- proposal: [
|
|
|
- [ this.receiver.address ],
|
|
|
- [ this.value ],
|
|
|
- [ '0x' ],
|
|
|
- '<proposal description>',
|
|
|
- ],
|
|
|
- tokenHolder: owner,
|
|
|
- voters: [
|
|
|
- { voter: voter1, weight: web3.utils.toWei('5'), support: Enums.VoteType.For },
|
|
|
- { voter: voter2, weight: web3.utils.toWei('5'), support: Enums.VoteType.Abstain },
|
|
|
- ],
|
|
|
- };
|
|
|
- });
|
|
|
- afterEach(async function () {
|
|
|
- expectEvent(
|
|
|
- this.receipts.propose,
|
|
|
- 'ProposalCreated',
|
|
|
- { proposalId: this.id },
|
|
|
- );
|
|
|
- expectEvent(
|
|
|
- this.receipts.execute,
|
|
|
- 'ProposalExecuted',
|
|
|
- { proposalId: this.id },
|
|
|
+ it('if voting has not started', async function () {
|
|
|
+ await this.helper.propose();
|
|
|
+ await expectRevert(
|
|
|
+ this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }),
|
|
|
+ 'Governor: vote not currently active',
|
|
|
);
|
|
|
- expect(await web3.eth.getBalance(this.mock.address)).to.be.bignumber.equal('0');
|
|
|
- expect(await web3.eth.getBalance(this.receiver.address)).to.be.bignumber.equal(this.value);
|
|
|
});
|
|
|
- runGovernorWorkflow();
|
|
|
- });
|
|
|
|
|
|
- describe('receiver revert without reason', function () {
|
|
|
- beforeEach(async function () {
|
|
|
- this.settings = {
|
|
|
- proposal: [
|
|
|
- [ this.receiver.address ],
|
|
|
- [ 0 ],
|
|
|
- [ this.receiver.contract.methods.mockFunctionRevertsNoReason().encodeABI() ],
|
|
|
- '<proposal description>',
|
|
|
- ],
|
|
|
- tokenHolder: owner,
|
|
|
- voters: [
|
|
|
- { voter: voter1, weight: web3.utils.toWei('10'), support: Enums.VoteType.For },
|
|
|
- ],
|
|
|
- steps: {
|
|
|
- execute: { error: 'Governor: call reverted without message' },
|
|
|
- },
|
|
|
- };
|
|
|
+ it('if support value is invalid', async function () {
|
|
|
+ await this.helper.propose();
|
|
|
+ await this.helper.waitForSnapshot();
|
|
|
+ await expectRevert(
|
|
|
+ this.helper.vote({ support: new BN('255') }),
|
|
|
+ 'GovernorVotingSimple: invalid value for enum VoteType',
|
|
|
+ );
|
|
|
});
|
|
|
- runGovernorWorkflow();
|
|
|
- });
|
|
|
|
|
|
- describe('receiver revert with reason', function () {
|
|
|
- beforeEach(async function () {
|
|
|
- this.settings = {
|
|
|
- proposal: [
|
|
|
- [ this.receiver.address ],
|
|
|
- [ 0 ],
|
|
|
- [ this.receiver.contract.methods.mockFunctionRevertsReason().encodeABI() ],
|
|
|
- '<proposal description>',
|
|
|
- ],
|
|
|
- tokenHolder: owner,
|
|
|
- voters: [
|
|
|
- { voter: voter1, weight: web3.utils.toWei('10'), support: Enums.VoteType.For },
|
|
|
- ],
|
|
|
- steps: {
|
|
|
- execute: { error: 'CallReceiverMock: reverting' },
|
|
|
- },
|
|
|
- };
|
|
|
+ it('if vote was already casted', async function () {
|
|
|
+ await this.helper.propose();
|
|
|
+ await this.helper.waitForSnapshot();
|
|
|
+ await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
|
|
|
+ await expectRevert(
|
|
|
+ this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }),
|
|
|
+ 'GovernorVotingSimple: vote already cast',
|
|
|
+ );
|
|
|
});
|
|
|
- runGovernorWorkflow();
|
|
|
- });
|
|
|
|
|
|
- describe('missing proposal', function () {
|
|
|
- beforeEach(async function () {
|
|
|
- this.settings = {
|
|
|
- proposal: [
|
|
|
- [ this.receiver.address ],
|
|
|
- [ web3.utils.toWei('0') ],
|
|
|
- [ this.receiver.contract.methods.mockFunction().encodeABI() ],
|
|
|
- '<proposal description>',
|
|
|
- ],
|
|
|
- tokenHolder: owner,
|
|
|
- voters: [
|
|
|
- {
|
|
|
- voter: voter1,
|
|
|
- weight: web3.utils.toWei('5'),
|
|
|
- support: Enums.VoteType.For,
|
|
|
- error: 'Governor: unknown proposal id',
|
|
|
- },
|
|
|
- {
|
|
|
- voter: voter2,
|
|
|
- weight: web3.utils.toWei('5'),
|
|
|
- support: Enums.VoteType.Abstain,
|
|
|
- error: 'Governor: unknown proposal id',
|
|
|
- },
|
|
|
- ],
|
|
|
- steps: {
|
|
|
- propose: { enable: false },
|
|
|
- wait: { enable: false },
|
|
|
- execute: { error: 'Governor: unknown proposal id' },
|
|
|
- },
|
|
|
- };
|
|
|
+ it('if voting is over', async function () {
|
|
|
+ await this.helper.propose();
|
|
|
+ await this.helper.waitForDeadline();
|
|
|
+ await expectRevert(
|
|
|
+ this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }),
|
|
|
+ 'Governor: vote not currently active',
|
|
|
+ );
|
|
|
});
|
|
|
- runGovernorWorkflow();
|
|
|
});
|
|
|
|
|
|
- describe('duplicate pending proposal', function () {
|
|
|
- beforeEach(async function () {
|
|
|
- this.settings = {
|
|
|
- proposal: [
|
|
|
- [ this.receiver.address ],
|
|
|
- [ web3.utils.toWei('0') ],
|
|
|
- [ this.receiver.contract.methods.mockFunction().encodeABI() ],
|
|
|
- '<proposal description>',
|
|
|
- ],
|
|
|
- steps: {
|
|
|
- wait: { enable: false },
|
|
|
- execute: { enable: false },
|
|
|
- },
|
|
|
- };
|
|
|
+ describe('on execute', function () {
|
|
|
+ it('if proposal does not exist', async function () {
|
|
|
+ await expectRevert(this.helper.execute(), 'Governor: unknown proposal id');
|
|
|
});
|
|
|
- afterEach(async function () {
|
|
|
- await expectRevert(this.mock.propose(...this.settings.proposal), 'Governor: proposal already exists');
|
|
|
- });
|
|
|
- runGovernorWorkflow();
|
|
|
- });
|
|
|
|
|
|
- describe('duplicate executed proposal', function () {
|
|
|
- beforeEach(async function () {
|
|
|
- this.settings = {
|
|
|
- proposal: [
|
|
|
- [ this.receiver.address ],
|
|
|
- [ web3.utils.toWei('0') ],
|
|
|
- [ this.receiver.contract.methods.mockFunction().encodeABI() ],
|
|
|
- '<proposal description>',
|
|
|
- ],
|
|
|
- tokenHolder: owner,
|
|
|
- voters: [
|
|
|
- { voter: voter1, weight: web3.utils.toWei('5'), support: Enums.VoteType.For },
|
|
|
- { voter: voter2, weight: web3.utils.toWei('5'), support: Enums.VoteType.Abstain },
|
|
|
- ],
|
|
|
- };
|
|
|
+ it('if quorum is not reached', async function () {
|
|
|
+ await this.helper.propose();
|
|
|
+ await this.helper.waitForSnapshot();
|
|
|
+ await this.helper.vote({ support: Enums.VoteType.For }, { from: voter3 });
|
|
|
+ await expectRevert(this.helper.execute(), 'Governor: proposal not successful');
|
|
|
});
|
|
|
- afterEach(async function () {
|
|
|
- await expectRevert(this.mock.propose(...this.settings.proposal), 'Governor: proposal already exists');
|
|
|
- });
|
|
|
- runGovernorWorkflow();
|
|
|
- });
|
|
|
|
|
|
- describe('Invalid vote type', function () {
|
|
|
- beforeEach(async function () {
|
|
|
- this.settings = {
|
|
|
- proposal: [
|
|
|
- [ this.receiver.address ],
|
|
|
- [ web3.utils.toWei('0') ],
|
|
|
- [ this.receiver.contract.methods.mockFunction().encodeABI() ],
|
|
|
- '<proposal description>',
|
|
|
- ],
|
|
|
- tokenHolder: owner,
|
|
|
- voters: [
|
|
|
- {
|
|
|
- voter: voter1,
|
|
|
- weight: web3.utils.toWei('10'),
|
|
|
- support: new BN('255'),
|
|
|
- error: 'GovernorVotingSimple: invalid value for enum VoteType',
|
|
|
- },
|
|
|
- ],
|
|
|
- steps: {
|
|
|
- wait: { enable: false },
|
|
|
- execute: { enable: false },
|
|
|
- },
|
|
|
- };
|
|
|
+ it('if score not reached', async function () {
|
|
|
+ await this.helper.propose();
|
|
|
+ await this.helper.waitForSnapshot();
|
|
|
+ await this.helper.vote({ support: Enums.VoteType.Against }, { from: voter1 });
|
|
|
+ await expectRevert(this.helper.execute(), 'Governor: proposal not successful');
|
|
|
});
|
|
|
- runGovernorWorkflow();
|
|
|
- });
|
|
|
|
|
|
- describe('double cast', function () {
|
|
|
- beforeEach(async function () {
|
|
|
- this.settings = {
|
|
|
- proposal: [
|
|
|
- [ this.receiver.address ],
|
|
|
- [ web3.utils.toWei('0') ],
|
|
|
- [ this.receiver.contract.methods.mockFunction().encodeABI() ],
|
|
|
- '<proposal description>',
|
|
|
- ],
|
|
|
- tokenHolder: owner,
|
|
|
- voters: [
|
|
|
- {
|
|
|
- voter: voter1,
|
|
|
- weight: web3.utils.toWei('5'),
|
|
|
- support: Enums.VoteType.For,
|
|
|
- },
|
|
|
- {
|
|
|
- voter: voter1,
|
|
|
- weight: web3.utils.toWei('5'),
|
|
|
- support: Enums.VoteType.For,
|
|
|
- error: 'GovernorVotingSimple: vote already cast',
|
|
|
- },
|
|
|
- ],
|
|
|
- };
|
|
|
+ it('if voting is not over', async function () {
|
|
|
+ await this.helper.propose();
|
|
|
+ await this.helper.waitForSnapshot();
|
|
|
+ await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
|
|
|
+ await expectRevert(this.helper.execute(), 'Governor: proposal not successful');
|
|
|
});
|
|
|
- runGovernorWorkflow();
|
|
|
- });
|
|
|
|
|
|
- describe('quorum not reached', function () {
|
|
|
- beforeEach(async function () {
|
|
|
- this.settings = {
|
|
|
- proposal: [
|
|
|
- [ this.receiver.address ],
|
|
|
- [ web3.utils.toWei('0') ],
|
|
|
- [ this.receiver.contract.methods.mockFunction().encodeABI() ],
|
|
|
- '<proposal description>',
|
|
|
- ],
|
|
|
- tokenHolder: owner,
|
|
|
- voters: [
|
|
|
- { voter: voter1, weight: web3.utils.toWei('5'), support: Enums.VoteType.For },
|
|
|
- { voter: voter2, weight: web3.utils.toWei('4'), support: Enums.VoteType.Abstain },
|
|
|
- { voter: voter3, weight: web3.utils.toWei('10'), support: Enums.VoteType.Against },
|
|
|
- ],
|
|
|
- steps: {
|
|
|
- execute: { error: 'Governor: proposal not successful' },
|
|
|
+ it('if receiver revert without reason', async function () {
|
|
|
+ this.proposal = this.helper.setProposal([
|
|
|
+ {
|
|
|
+ target: this.receiver.address,
|
|
|
+ data: this.receiver.contract.methods.mockFunctionRevertsNoReason().encodeABI(),
|
|
|
},
|
|
|
- };
|
|
|
+ ], '<proposal description>');
|
|
|
+
|
|
|
+ await this.helper.propose();
|
|
|
+ await this.helper.waitForSnapshot();
|
|
|
+ await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
|
|
|
+ await this.helper.waitForDeadline();
|
|
|
+ await expectRevert(this.helper.execute(), 'Governor: call reverted without message');
|
|
|
});
|
|
|
- runGovernorWorkflow();
|
|
|
- });
|
|
|
|
|
|
- describe('score not reached', function () {
|
|
|
- beforeEach(async function () {
|
|
|
- this.settings = {
|
|
|
- proposal: [
|
|
|
- [ this.receiver.address ],
|
|
|
- [ web3.utils.toWei('0') ],
|
|
|
- [ this.receiver.contract.methods.mockFunction().encodeABI() ],
|
|
|
- '<proposal description>',
|
|
|
- ],
|
|
|
- tokenHolder: owner,
|
|
|
- voters: [
|
|
|
- { voter: voter1, weight: web3.utils.toWei('10'), support: Enums.VoteType.Against },
|
|
|
- ],
|
|
|
- steps: {
|
|
|
- execute: { error: 'Governor: proposal not successful' },
|
|
|
+ it('if receiver revert with reason', async function () {
|
|
|
+ this.proposal = this.helper.setProposal([
|
|
|
+ {
|
|
|
+ target: this.receiver.address,
|
|
|
+ data: this.receiver.contract.methods.mockFunctionRevertsReason().encodeABI(),
|
|
|
},
|
|
|
- };
|
|
|
+ ], '<proposal description>');
|
|
|
+
|
|
|
+ await this.helper.propose();
|
|
|
+ await this.helper.waitForSnapshot();
|
|
|
+ await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
|
|
|
+ await this.helper.waitForDeadline();
|
|
|
+ await expectRevert(this.helper.execute(), 'CallReceiverMock: reverting');
|
|
|
});
|
|
|
- runGovernorWorkflow();
|
|
|
- });
|
|
|
|
|
|
- describe('vote not over', function () {
|
|
|
- beforeEach(async function () {
|
|
|
- this.settings = {
|
|
|
- proposal: [
|
|
|
- [ this.receiver.address ],
|
|
|
- [ web3.utils.toWei('0') ],
|
|
|
- [ this.receiver.contract.methods.mockFunction().encodeABI() ],
|
|
|
- '<proposal description>',
|
|
|
- ],
|
|
|
- tokenHolder: owner,
|
|
|
- voters: [
|
|
|
- { voter: voter1, weight: web3.utils.toWei('10'), support: Enums.VoteType.For },
|
|
|
- ],
|
|
|
- steps: {
|
|
|
- wait: { enable: false },
|
|
|
- execute: { error: 'Governor: proposal not successful' },
|
|
|
- },
|
|
|
- };
|
|
|
+ it('if proposal was already executed', async function () {
|
|
|
+ await this.helper.propose();
|
|
|
+ await this.helper.waitForSnapshot();
|
|
|
+ await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
|
|
|
+ await this.helper.waitForDeadline();
|
|
|
+ await this.helper.execute();
|
|
|
+ await expectRevert(this.helper.execute(), 'Governor: proposal not successful');
|
|
|
});
|
|
|
- runGovernorWorkflow();
|
|
|
});
|
|
|
});
|
|
|
|
|
|
describe('state', function () {
|
|
|
- describe('Unset', function () {
|
|
|
- beforeEach(async function () {
|
|
|
- this.settings = {
|
|
|
- proposal: [
|
|
|
- [ this.receiver.address ],
|
|
|
- [ web3.utils.toWei('0') ],
|
|
|
- [ this.receiver.contract.methods.mockFunction().encodeABI() ],
|
|
|
- '<proposal description>',
|
|
|
- ],
|
|
|
- steps: {
|
|
|
- propose: { enable: false },
|
|
|
- wait: { enable: false },
|
|
|
- execute: { enable: false },
|
|
|
- },
|
|
|
- };
|
|
|
- });
|
|
|
- afterEach(async function () {
|
|
|
- await expectRevert(this.mock.state(this.id), 'Governor: unknown proposal id');
|
|
|
- });
|
|
|
- runGovernorWorkflow();
|
|
|
+ it('Unset', async function () {
|
|
|
+ await expectRevert(this.mock.state(this.proposal.id), 'Governor: unknown proposal id');
|
|
|
});
|
|
|
|
|
|
- describe('Pending & Active', function () {
|
|
|
- beforeEach(async function () {
|
|
|
- this.settings = {
|
|
|
- proposal: [
|
|
|
- [ this.receiver.address ],
|
|
|
- [ web3.utils.toWei('0') ],
|
|
|
- [ this.receiver.contract.methods.mockFunction().encodeABI() ],
|
|
|
- '<proposal description>',
|
|
|
- ],
|
|
|
- steps: {
|
|
|
- propose: { noadvance: true },
|
|
|
- wait: { enable: false },
|
|
|
- execute: { enable: false },
|
|
|
- },
|
|
|
- };
|
|
|
- });
|
|
|
- afterEach(async function () {
|
|
|
- expect(await this.mock.state(this.id)).to.be.bignumber.equal(Enums.ProposalState.Pending);
|
|
|
-
|
|
|
- await time.advanceBlockTo(this.snapshot);
|
|
|
-
|
|
|
- expect(await this.mock.state(this.id)).to.be.bignumber.equal(Enums.ProposalState.Pending);
|
|
|
-
|
|
|
- await time.advanceBlock();
|
|
|
-
|
|
|
- expect(await this.mock.state(this.id)).to.be.bignumber.equal(Enums.ProposalState.Active);
|
|
|
- });
|
|
|
- runGovernorWorkflow();
|
|
|
+ it('Pending & Active', async function () {
|
|
|
+ await this.helper.propose();
|
|
|
+ expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Pending);
|
|
|
+ await this.helper.waitForSnapshot();
|
|
|
+ expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Pending);
|
|
|
+ await this.helper.waitForSnapshot(+1);
|
|
|
+ expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Active);
|
|
|
});
|
|
|
|
|
|
- describe('Defeated', function () {
|
|
|
- beforeEach(async function () {
|
|
|
- this.settings = {
|
|
|
- proposal: [
|
|
|
- [ this.receiver.address ],
|
|
|
- [ web3.utils.toWei('0') ],
|
|
|
- [ this.receiver.contract.methods.mockFunction().encodeABI() ],
|
|
|
- '<proposal description>',
|
|
|
- ],
|
|
|
- steps: {
|
|
|
- execute: { enable: false },
|
|
|
- },
|
|
|
- };
|
|
|
- });
|
|
|
- afterEach(async function () {
|
|
|
- expect(await this.mock.state(this.id)).to.be.bignumber.equal(Enums.ProposalState.Defeated);
|
|
|
- });
|
|
|
- runGovernorWorkflow();
|
|
|
+ it('Defeated', async function () {
|
|
|
+ await this.helper.propose();
|
|
|
+ await this.helper.waitForDeadline();
|
|
|
+ expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Active);
|
|
|
+ await this.helper.waitForDeadline(+1);
|
|
|
+ expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Defeated);
|
|
|
});
|
|
|
|
|
|
- describe('Succeeded', function () {
|
|
|
- beforeEach(async function () {
|
|
|
- this.settings = {
|
|
|
- proposal: [
|
|
|
- [ this.receiver.address ],
|
|
|
- [ web3.utils.toWei('0') ],
|
|
|
- [ this.receiver.contract.methods.mockFunction().encodeABI() ],
|
|
|
- '<proposal description>',
|
|
|
- ],
|
|
|
- tokenHolder: owner,
|
|
|
- voters: [
|
|
|
- { voter: voter1, weight: web3.utils.toWei('10'), support: Enums.VoteType.For },
|
|
|
- ],
|
|
|
- steps: {
|
|
|
- execute: { enable: false },
|
|
|
- },
|
|
|
- };
|
|
|
- });
|
|
|
- afterEach(async function () {
|
|
|
- expect(await this.mock.state(this.id)).to.be.bignumber.equal(Enums.ProposalState.Succeeded);
|
|
|
- });
|
|
|
- runGovernorWorkflow();
|
|
|
+ it('Succeeded', async function () {
|
|
|
+ await this.helper.propose();
|
|
|
+ await this.helper.waitForSnapshot();
|
|
|
+ await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
|
|
|
+ await this.helper.waitForDeadline();
|
|
|
+ expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Active);
|
|
|
+ await this.helper.waitForDeadline(+1);
|
|
|
+ expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Succeeded);
|
|
|
});
|
|
|
|
|
|
- describe('Executed', function () {
|
|
|
- beforeEach(async function () {
|
|
|
- this.settings = {
|
|
|
- proposal: [
|
|
|
- [ this.receiver.address ],
|
|
|
- [ web3.utils.toWei('0') ],
|
|
|
- [ this.receiver.contract.methods.mockFunction().encodeABI() ],
|
|
|
- '<proposal description>',
|
|
|
- ],
|
|
|
- tokenHolder: owner,
|
|
|
- voters: [
|
|
|
- { voter: voter1, weight: web3.utils.toWei('10'), support: Enums.VoteType.For },
|
|
|
- ],
|
|
|
- };
|
|
|
- });
|
|
|
- afterEach(async function () {
|
|
|
- expect(await this.mock.state(this.id)).to.be.bignumber.equal(Enums.ProposalState.Executed);
|
|
|
- });
|
|
|
- runGovernorWorkflow();
|
|
|
+ it('Executed', async function () {
|
|
|
+ await this.helper.propose();
|
|
|
+ await this.helper.waitForSnapshot();
|
|
|
+ await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
|
|
|
+ await this.helper.waitForDeadline();
|
|
|
+ await this.helper.execute();
|
|
|
+ expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Executed);
|
|
|
});
|
|
|
});
|
|
|
|
|
|
- describe('Cancel', function () {
|
|
|
- describe('Before proposal', function () {
|
|
|
- beforeEach(async function () {
|
|
|
- this.settings = {
|
|
|
- proposal: [
|
|
|
- [ this.receiver.address ],
|
|
|
- [ web3.utils.toWei('0') ],
|
|
|
- [ this.receiver.contract.methods.mockFunction().encodeABI() ],
|
|
|
- '<proposal description>',
|
|
|
- ],
|
|
|
- steps: {
|
|
|
- propose: { enable: false },
|
|
|
- wait: { enable: false },
|
|
|
- execute: { enable: false },
|
|
|
- },
|
|
|
- };
|
|
|
- });
|
|
|
- afterEach(async function () {
|
|
|
- await expectRevert(
|
|
|
- this.mock.cancel(...this.settings.proposal.slice(0, -1), this.descriptionHash),
|
|
|
- 'Governor: unknown proposal id',
|
|
|
- );
|
|
|
- });
|
|
|
- runGovernorWorkflow();
|
|
|
+ describe('cancel', function () {
|
|
|
+ it('before proposal', async function () {
|
|
|
+ await expectRevert(this.helper.cancel(), 'Governor: unknown proposal id');
|
|
|
});
|
|
|
|
|
|
- describe('After proposal', function () {
|
|
|
- beforeEach(async function () {
|
|
|
- this.settings = {
|
|
|
- proposal: [
|
|
|
- [ this.receiver.address ],
|
|
|
- [ web3.utils.toWei('0') ],
|
|
|
- [ this.receiver.contract.methods.mockFunction().encodeABI() ],
|
|
|
- '<proposal description>',
|
|
|
- ],
|
|
|
- steps: {
|
|
|
- wait: { enable: false },
|
|
|
- execute: { enable: false },
|
|
|
- },
|
|
|
- };
|
|
|
- });
|
|
|
- afterEach(async function () {
|
|
|
- await this.mock.cancel(...this.settings.proposal.slice(0, -1), this.descriptionHash);
|
|
|
- expect(await this.mock.state(this.id)).to.be.bignumber.equal(Enums.ProposalState.Canceled);
|
|
|
+ it('after proposal', async function () {
|
|
|
+ await this.helper.propose();
|
|
|
|
|
|
- await expectRevert(
|
|
|
- this.mock.castVote(this.id, new BN('100'), { from: voter1 }),
|
|
|
- 'Governor: vote not currently active',
|
|
|
- );
|
|
|
- });
|
|
|
- runGovernorWorkflow();
|
|
|
+ await this.helper.cancel();
|
|
|
+ expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Canceled);
|
|
|
+
|
|
|
+ await this.helper.waitForSnapshot();
|
|
|
+ await expectRevert(
|
|
|
+ this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }),
|
|
|
+ 'Governor: vote not currently active',
|
|
|
+ );
|
|
|
});
|
|
|
|
|
|
- describe('After vote', function () {
|
|
|
- beforeEach(async function () {
|
|
|
- this.settings = {
|
|
|
- proposal: [
|
|
|
- [ this.receiver.address ],
|
|
|
- [ web3.utils.toWei('0') ],
|
|
|
- [ this.receiver.contract.methods.mockFunction().encodeABI() ],
|
|
|
- '<proposal description>',
|
|
|
- ],
|
|
|
- tokenHolder: owner,
|
|
|
- voters: [
|
|
|
- { voter: voter1, weight: web3.utils.toWei('10'), support: Enums.VoteType.For },
|
|
|
- ],
|
|
|
- steps: {
|
|
|
- wait: { enable: false },
|
|
|
- execute: { enable: false },
|
|
|
- },
|
|
|
- };
|
|
|
- });
|
|
|
- afterEach(async function () {
|
|
|
- await this.mock.cancel(...this.settings.proposal.slice(0, -1), this.descriptionHash);
|
|
|
- expect(await this.mock.state(this.id)).to.be.bignumber.equal(Enums.ProposalState.Canceled);
|
|
|
+ it('after vote', async function () {
|
|
|
+ await this.helper.propose();
|
|
|
+ await this.helper.waitForSnapshot();
|
|
|
+ await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
|
|
|
|
|
|
- await expectRevert(
|
|
|
- this.mock.execute(...this.settings.proposal.slice(0, -1), this.descriptionHash),
|
|
|
- 'Governor: proposal not successful',
|
|
|
- );
|
|
|
- });
|
|
|
- runGovernorWorkflow();
|
|
|
+ await this.helper.cancel();
|
|
|
+ expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Canceled);
|
|
|
+
|
|
|
+ await this.helper.waitForDeadline();
|
|
|
+ await expectRevert(this.helper.execute(), 'Governor: proposal not successful');
|
|
|
});
|
|
|
|
|
|
- describe('After deadline', function () {
|
|
|
- beforeEach(async function () {
|
|
|
- this.settings = {
|
|
|
- proposal: [
|
|
|
- [ this.receiver.address ],
|
|
|
- [ web3.utils.toWei('0') ],
|
|
|
- [ this.receiver.contract.methods.mockFunction().encodeABI() ],
|
|
|
- '<proposal description>',
|
|
|
- ],
|
|
|
- tokenHolder: owner,
|
|
|
- voters: [
|
|
|
- { voter: voter1, weight: web3.utils.toWei('10'), support: Enums.VoteType.For },
|
|
|
- ],
|
|
|
- steps: {
|
|
|
- execute: { enable: false },
|
|
|
- },
|
|
|
- };
|
|
|
- });
|
|
|
- afterEach(async function () {
|
|
|
- await this.mock.cancel(...this.settings.proposal.slice(0, -1), this.descriptionHash);
|
|
|
- expect(await this.mock.state(this.id)).to.be.bignumber.equal(Enums.ProposalState.Canceled);
|
|
|
+ it('after deadline', async function () {
|
|
|
+ await this.helper.propose();
|
|
|
+ await this.helper.waitForSnapshot();
|
|
|
+ await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
|
|
|
+ await this.helper.waitForDeadline();
|
|
|
|
|
|
- await expectRevert(
|
|
|
- this.mock.execute(...this.settings.proposal.slice(0, -1), this.descriptionHash),
|
|
|
- 'Governor: proposal not successful',
|
|
|
- );
|
|
|
- });
|
|
|
- runGovernorWorkflow();
|
|
|
+ await this.helper.cancel();
|
|
|
+ expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Canceled);
|
|
|
+
|
|
|
+ await expectRevert(this.helper.execute(), 'Governor: proposal not successful');
|
|
|
});
|
|
|
|
|
|
- describe('After execution', function () {
|
|
|
- beforeEach(async function () {
|
|
|
- this.settings = {
|
|
|
- proposal: [
|
|
|
- [ this.receiver.address ],
|
|
|
- [ web3.utils.toWei('0') ],
|
|
|
- [ this.receiver.contract.methods.mockFunction().encodeABI() ],
|
|
|
- '<proposal description>',
|
|
|
- ],
|
|
|
- tokenHolder: owner,
|
|
|
- voters: [
|
|
|
- { voter: voter1, weight: web3.utils.toWei('10'), support: Enums.VoteType.For },
|
|
|
- ],
|
|
|
- };
|
|
|
- });
|
|
|
- afterEach(async function () {
|
|
|
- await expectRevert(
|
|
|
- this.mock.cancel(...this.settings.proposal.slice(0, -1), this.descriptionHash),
|
|
|
- 'Governor: proposal not active',
|
|
|
- );
|
|
|
- });
|
|
|
- runGovernorWorkflow();
|
|
|
+ it('after execution', async function () {
|
|
|
+ await this.helper.propose();
|
|
|
+ await this.helper.waitForSnapshot();
|
|
|
+ await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
|
|
|
+ await this.helper.waitForDeadline();
|
|
|
+ await this.helper.execute();
|
|
|
+
|
|
|
+ await expectRevert(this.helper.cancel(), 'Governor: proposal not active');
|
|
|
});
|
|
|
});
|
|
|
|
|
|
- describe('Proposal length', function () {
|
|
|
+ describe('proposal length', function () {
|
|
|
it('empty', async function () {
|
|
|
- await expectRevert(
|
|
|
- this.mock.propose(
|
|
|
- [],
|
|
|
- [],
|
|
|
- [],
|
|
|
- '<proposal description>',
|
|
|
- ),
|
|
|
- 'Governor: empty proposal',
|
|
|
- );
|
|
|
+ this.helper.setProposal([ ], '<proposal description>');
|
|
|
+ await expectRevert(this.helper.propose(), 'Governor: empty proposal');
|
|
|
});
|
|
|
|
|
|
it('missmatch #1', async function () {
|
|
|
- await expectRevert(
|
|
|
- this.mock.propose(
|
|
|
- [ ],
|
|
|
- [ web3.utils.toWei('0') ],
|
|
|
- [ this.receiver.contract.methods.mockFunction().encodeABI() ],
|
|
|
- '<proposal description>',
|
|
|
- ),
|
|
|
- 'Governor: invalid proposal length',
|
|
|
- );
|
|
|
+ this.helper.setProposal({
|
|
|
+ targets: [ ],
|
|
|
+ values: [ web3.utils.toWei('0') ],
|
|
|
+ data: [ this.receiver.contract.methods.mockFunction().encodeABI() ],
|
|
|
+ }, '<proposal description>');
|
|
|
+ await expectRevert(this.helper.propose(), 'Governor: invalid proposal length');
|
|
|
});
|
|
|
|
|
|
it('missmatch #2', async function () {
|
|
|
- await expectRevert(
|
|
|
- this.mock.propose(
|
|
|
- [ this.receiver.address ],
|
|
|
- [ ],
|
|
|
- [ this.receiver.contract.methods.mockFunction().encodeABI() ],
|
|
|
- '<proposal description>',
|
|
|
- ),
|
|
|
- 'Governor: invalid proposal length',
|
|
|
- );
|
|
|
+ this.helper.setProposal({
|
|
|
+ targets: [ this.receiver.address ],
|
|
|
+ values: [ ],
|
|
|
+ data: [ this.receiver.contract.methods.mockFunction().encodeABI() ],
|
|
|
+ }, '<proposal description>');
|
|
|
+ await expectRevert(this.helper.propose(), 'Governor: invalid proposal length');
|
|
|
});
|
|
|
|
|
|
it('missmatch #3', async function () {
|
|
|
- await expectRevert(
|
|
|
- this.mock.propose(
|
|
|
- [ this.receiver.address ],
|
|
|
- [ web3.utils.toWei('0') ],
|
|
|
- [ ],
|
|
|
- '<proposal description>',
|
|
|
- ),
|
|
|
- 'Governor: invalid proposal length',
|
|
|
- );
|
|
|
+ this.helper.setProposal({
|
|
|
+ targets: [ this.receiver.address ],
|
|
|
+ values: [ web3.utils.toWei('0') ],
|
|
|
+ data: [ ],
|
|
|
+ }, '<proposal description>');
|
|
|
+ await expectRevert(this.helper.propose(), 'Governor: invalid proposal length');
|
|
|
});
|
|
|
});
|
|
|
|
|
|
- describe('Settings update', function () {
|
|
|
- describe('setVotingDelay', function () {
|
|
|
- beforeEach(async function () {
|
|
|
- this.settings = {
|
|
|
- proposal: [
|
|
|
- [ this.mock.address ],
|
|
|
- [ web3.utils.toWei('0') ],
|
|
|
- [ this.mock.contract.methods.setVotingDelay('0').encodeABI() ],
|
|
|
- '<proposal description>',
|
|
|
- ],
|
|
|
- tokenHolder: owner,
|
|
|
- voters: [
|
|
|
- { voter: voter1, weight: web3.utils.toWei('10'), support: Enums.VoteType.For },
|
|
|
- ],
|
|
|
- };
|
|
|
- });
|
|
|
- afterEach(async function () {
|
|
|
- expect(await this.mock.votingDelay()).to.be.bignumber.equal('0');
|
|
|
+ describe('onlyGovernance updates', function () {
|
|
|
+ it('setVotingDelay is protected', async function () {
|
|
|
+ await expectRevert(this.mock.setVotingDelay('0'), 'Governor: onlyGovernance');
|
|
|
+ });
|
|
|
|
|
|
- expectEvent(
|
|
|
- this.receipts.execute,
|
|
|
- 'VotingDelaySet',
|
|
|
- { oldVotingDelay: '4', newVotingDelay: '0' },
|
|
|
- );
|
|
|
- });
|
|
|
- runGovernorWorkflow();
|
|
|
+ it('setVotingPeriod is protected', async function () {
|
|
|
+ await expectRevert(this.mock.setVotingPeriod('32'), 'Governor: onlyGovernance');
|
|
|
});
|
|
|
|
|
|
- describe('setVotingPeriod', function () {
|
|
|
- beforeEach(async function () {
|
|
|
- this.settings = {
|
|
|
- proposal: [
|
|
|
- [ this.mock.address ],
|
|
|
- [ web3.utils.toWei('0') ],
|
|
|
- [ this.mock.contract.methods.setVotingPeriod('32').encodeABI() ],
|
|
|
- '<proposal description>',
|
|
|
- ],
|
|
|
- tokenHolder: owner,
|
|
|
- voters: [
|
|
|
- { voter: voter1, weight: web3.utils.toWei('10'), support: Enums.VoteType.For },
|
|
|
- ],
|
|
|
- };
|
|
|
- });
|
|
|
- afterEach(async function () {
|
|
|
- expect(await this.mock.votingPeriod()).to.be.bignumber.equal('32');
|
|
|
+ it('setProposalThreshold is protected', async function () {
|
|
|
+ await expectRevert(this.mock.setProposalThreshold('1000000000000000000'), 'Governor: onlyGovernance');
|
|
|
+ });
|
|
|
|
|
|
- expectEvent(
|
|
|
- this.receipts.execute,
|
|
|
- 'VotingPeriodSet',
|
|
|
- { oldVotingPeriod: '16', newVotingPeriod: '32' },
|
|
|
- );
|
|
|
- });
|
|
|
- runGovernorWorkflow();
|
|
|
+ it('can setVotingDelay through governance', async function () {
|
|
|
+ this.helper.setProposal([
|
|
|
+ {
|
|
|
+ target: this.mock.address,
|
|
|
+ data: this.mock.contract.methods.setVotingDelay('0').encodeABI(),
|
|
|
+ },
|
|
|
+ ], '<proposal description>');
|
|
|
+
|
|
|
+ await this.helper.propose();
|
|
|
+ await this.helper.waitForSnapshot();
|
|
|
+ await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
|
|
|
+ await this.helper.waitForDeadline();
|
|
|
+
|
|
|
+ expectEvent(
|
|
|
+ await this.helper.execute(),
|
|
|
+ 'VotingDelaySet',
|
|
|
+ { oldVotingDelay: '4', newVotingDelay: '0' },
|
|
|
+ );
|
|
|
+
|
|
|
+ expect(await this.mock.votingDelay()).to.be.bignumber.equal('0');
|
|
|
});
|
|
|
|
|
|
- describe('setVotingPeriod to 0', function () {
|
|
|
- beforeEach(async function () {
|
|
|
- this.settings = {
|
|
|
- proposal: [
|
|
|
- [ this.mock.address ],
|
|
|
- [ web3.utils.toWei('0') ],
|
|
|
- [ this.mock.contract.methods.setVotingPeriod('0').encodeABI() ],
|
|
|
- '<proposal description>',
|
|
|
- ],
|
|
|
- tokenHolder: owner,
|
|
|
- voters: [
|
|
|
- { voter: voter1, weight: web3.utils.toWei('10'), support: Enums.VoteType.For },
|
|
|
- ],
|
|
|
- steps: {
|
|
|
- execute: { error: 'GovernorSettings: voting period too low' },
|
|
|
- },
|
|
|
- };
|
|
|
- });
|
|
|
- afterEach(async function () {
|
|
|
- expect(await this.mock.votingPeriod()).to.be.bignumber.equal('16');
|
|
|
- });
|
|
|
- runGovernorWorkflow();
|
|
|
+ it('can setVotingPeriod through governance', async function () {
|
|
|
+ this.helper.setProposal([
|
|
|
+ {
|
|
|
+ target: this.mock.address,
|
|
|
+ data: this.mock.contract.methods.setVotingPeriod('32').encodeABI(),
|
|
|
+ },
|
|
|
+ ], '<proposal description>');
|
|
|
+
|
|
|
+ await this.helper.propose();
|
|
|
+ await this.helper.waitForSnapshot();
|
|
|
+ await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
|
|
|
+ await this.helper.waitForDeadline();
|
|
|
+
|
|
|
+ expectEvent(
|
|
|
+ await this.helper.execute(),
|
|
|
+ 'VotingPeriodSet',
|
|
|
+ { oldVotingPeriod: '16', newVotingPeriod: '32' },
|
|
|
+ );
|
|
|
+
|
|
|
+ expect(await this.mock.votingPeriod()).to.be.bignumber.equal('32');
|
|
|
});
|
|
|
|
|
|
- describe('setProposalThreshold', function () {
|
|
|
- beforeEach(async function () {
|
|
|
- this.settings = {
|
|
|
- proposal: [
|
|
|
- [ this.mock.address ],
|
|
|
- [ web3.utils.toWei('0') ],
|
|
|
- [ this.mock.contract.methods.setProposalThreshold('1000000000000000000').encodeABI() ],
|
|
|
- '<proposal description>',
|
|
|
- ],
|
|
|
- tokenHolder: owner,
|
|
|
- voters: [
|
|
|
- { voter: voter1, weight: web3.utils.toWei('10'), support: Enums.VoteType.For },
|
|
|
- ],
|
|
|
- };
|
|
|
- });
|
|
|
- afterEach(async function () {
|
|
|
- expect(await this.mock.proposalThreshold()).to.be.bignumber.equal('1000000000000000000');
|
|
|
+ it('cannot setVotingPeriod to 0 through governance', async function () {
|
|
|
+ this.helper.setProposal([
|
|
|
+ {
|
|
|
+ target: this.mock.address,
|
|
|
+ data: this.mock.contract.methods.setVotingPeriod('0').encodeABI(),
|
|
|
+ },
|
|
|
+ ], '<proposal description>');
|
|
|
|
|
|
- expectEvent(
|
|
|
- this.receipts.execute,
|
|
|
- 'ProposalThresholdSet',
|
|
|
- { oldProposalThreshold: '0', newProposalThreshold: '1000000000000000000' },
|
|
|
- );
|
|
|
- });
|
|
|
- runGovernorWorkflow();
|
|
|
+ await this.helper.propose();
|
|
|
+ await this.helper.waitForSnapshot();
|
|
|
+ await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
|
|
|
+ await this.helper.waitForDeadline();
|
|
|
+
|
|
|
+ await expectRevert(this.helper.execute(), 'GovernorSettings: voting period too low');
|
|
|
});
|
|
|
|
|
|
- describe('update protected', function () {
|
|
|
- it('setVotingDelay', async function () {
|
|
|
- await expectRevert(this.mock.setVotingDelay('0'), 'Governor: onlyGovernance');
|
|
|
- });
|
|
|
+ it('can setProposalThreshold to 0 through governance', async function () {
|
|
|
+ this.helper.setProposal([
|
|
|
+ {
|
|
|
+ target: this.mock.address,
|
|
|
+ data: this.mock.contract.methods.setProposalThreshold('1000000000000000000').encodeABI(),
|
|
|
+ },
|
|
|
+ ], '<proposal description>');
|
|
|
|
|
|
- it('setVotingPeriod', async function () {
|
|
|
- await expectRevert(this.mock.setVotingPeriod('32'), 'Governor: onlyGovernance');
|
|
|
- });
|
|
|
+ await this.helper.propose();
|
|
|
+ await this.helper.waitForSnapshot();
|
|
|
+ await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
|
|
|
+ await this.helper.waitForDeadline();
|
|
|
|
|
|
- it('setProposalThreshold', async function () {
|
|
|
- await expectRevert(this.mock.setProposalThreshold('1000000000000000000'), 'Governor: onlyGovernance');
|
|
|
- });
|
|
|
+ expectEvent(
|
|
|
+ await this.helper.execute(),
|
|
|
+ 'ProposalThresholdSet',
|
|
|
+ { oldProposalThreshold: '0', newProposalThreshold: '1000000000000000000' },
|
|
|
+ );
|
|
|
+
|
|
|
+ expect(await this.mock.proposalThreshold()).to.be.bignumber.equal('1000000000000000000');
|
|
|
});
|
|
|
});
|
|
|
});
|