Commit d85136b4 by github-actions

Merge upstream master into patched/master

parents fdc9cc04 6a5bbfc4
const { expectRevert, time } = require('@openzeppelin/test-helpers');
async function getReceiptOrRevert (promise, error = undefined) {
if (error) {
await expectRevert(promise, error);
return undefined;
} else {
const { receipt } = await promise;
return receipt;
}
}
function tryGet (obj, path = '') {
try {
return path.split('.').reduce((o, k) => o[k], obj);
} catch (_) {
return undefined;
}
}
function zip (...args) {
return Array(Math.max(...args.map(array => array.length)))
.fill()
.map((_, i) => args.map(array => array[i]));
}
function concatHex (...args) {
return web3.utils.bytesToHex([].concat(...args.map(h => web3.utils.hexToBytes(h || '0x'))));
}
function runGovernorWorkflow () {
beforeEach(async function () {
this.receipts = {};
// distinguish depending on the proposal length
// - length 4: propose(address[], uint256[], bytes[], string) → GovernorCore
// - length 5: propose(address[], uint256[], string[], bytes[], string) → GovernorCompatibilityBravo
this.useCompatibilityInterface = this.settings.proposal.length === 5;
// compute description hash
this.descriptionHash = web3.utils.keccak256(this.settings.proposal.slice(-1).find(Boolean));
// condensed proposal, used for queue and execute operation
this.settings.shortProposal = [
// targets
this.settings.proposal[0],
// values
this.settings.proposal[1],
// calldata (prefix selector if necessary)
this.useCompatibilityInterface
? zip(
this.settings.proposal[2].map(selector => selector && web3.eth.abi.encodeFunctionSignature(selector)),
this.settings.proposal[3],
).map(hexs => concatHex(...hexs))
: this.settings.proposal[2],
// descriptionHash
this.descriptionHash,
];
// proposal id
this.id = await this.mock.hashProposal(...this.settings.shortProposal);
});
it('run', async function () {
// transfer tokens
if (tryGet(this.settings, 'voters')) {
for (const voter of this.settings.voters) {
if (voter.weight) {
await this.token.transfer(voter.voter, voter.weight, { from: this.settings.tokenHolder });
} else if (voter.nfts) {
for (const nft of voter.nfts) {
await this.token.transferFrom(this.settings.tokenHolder, voter.voter, nft,
{ from: this.settings.tokenHolder });
}
}
}
}
// propose
if (this.mock.propose && tryGet(this.settings, 'steps.propose.enable') !== false) {
this.receipts.propose = await getReceiptOrRevert(
this.mock.methods[
this.useCompatibilityInterface
? 'propose(address[],uint256[],string[],bytes[],string)'
: 'propose(address[],uint256[],bytes[],string)'
](
...this.settings.proposal,
{ from: this.settings.proposer },
),
tryGet(this.settings, 'steps.propose.error'),
);
if (tryGet(this.settings, 'steps.propose.error') === undefined) {
this.deadline = await this.mock.proposalDeadline(this.id);
this.snapshot = await this.mock.proposalSnapshot(this.id);
}
if (tryGet(this.settings, 'steps.propose.delay')) {
await time.increase(tryGet(this.settings, 'steps.propose.delay'));
}
if (
tryGet(this.settings, 'steps.propose.error') === undefined &&
tryGet(this.settings, 'steps.propose.noadvance') !== true
) {
await time.advanceBlockTo(this.snapshot.addn(1));
}
}
// vote
if (tryGet(this.settings, 'voters')) {
this.receipts.castVote = [];
for (const voter of this.settings.voters.filter(({ support }) => !!support)) {
if (!voter.signature) {
this.receipts.castVote.push(
await getReceiptOrRevert(
voter.reason
? this.mock.castVoteWithReason(this.id, voter.support, voter.reason, { from: voter.voter })
: this.mock.castVote(this.id, voter.support, { from: voter.voter }),
voter.error,
),
);
} else {
const { v, r, s } = await voter.signature({ proposalId: this.id, support: voter.support });
this.receipts.castVote.push(
await getReceiptOrRevert(
this.mock.castVoteBySig(this.id, voter.support, v, r, s),
voter.error,
),
);
}
if (tryGet(voter, 'delay')) {
await time.increase(tryGet(voter, 'delay'));
}
}
}
// fast forward
if (tryGet(this.settings, 'steps.wait.enable') !== false) {
await time.advanceBlockTo(this.deadline.addn(1));
}
// queue
if (this.mock.queue && tryGet(this.settings, 'steps.queue.enable') !== false) {
this.receipts.queue = await getReceiptOrRevert(
this.useCompatibilityInterface
? this.mock.methods['queue(uint256)'](
this.id,
{ from: this.settings.queuer },
)
: this.mock.methods['queue(address[],uint256[],bytes[],bytes32)'](
...this.settings.shortProposal,
{ from: this.settings.queuer },
),
tryGet(this.settings, 'steps.queue.error'),
);
this.eta = await this.mock.proposalEta(this.id);
if (tryGet(this.settings, 'steps.queue.delay')) {
await time.increase(tryGet(this.settings, 'steps.queue.delay'));
}
}
// execute
if (this.mock.execute && tryGet(this.settings, 'steps.execute.enable') !== false) {
this.receipts.execute = await getReceiptOrRevert(
this.useCompatibilityInterface
? this.mock.methods['execute(uint256)'](
this.id,
{ from: this.settings.executer },
)
: this.mock.methods['execute(address[],uint256[],bytes[],bytes32)'](
...this.settings.shortProposal,
{ from: this.settings.executer },
),
tryGet(this.settings, 'steps.execute.error'),
);
if (tryGet(this.settings, 'steps.execute.delay')) {
await time.increase(tryGet(this.settings, 'steps.execute.delay'));
}
}
});
}
module.exports = {
runGovernorWorkflow,
};
const { BN, expectEvent } = require('@openzeppelin/test-helpers'); const { BN } = require('@openzeppelin/test-helpers');
const { expect } = require('chai');
const Enums = require('../../helpers/enums'); const Enums = require('../../helpers/enums');
const { GovernorHelper } = require('../../helpers/governance');
const {
runGovernorWorkflow,
} = require('./../GovernorWorkflow.behavior');
const Token = artifacts.require('ERC20VotesCompMock'); const Token = artifacts.require('ERC20VotesCompMock');
const Governor = artifacts.require('GovernorCompMock'); const Governor = artifacts.require('GovernorCompMock');
...@@ -17,71 +15,64 @@ contract('GovernorComp', function (accounts) { ...@@ -17,71 +15,64 @@ contract('GovernorComp', function (accounts) {
const tokenName = 'MockToken'; const tokenName = 'MockToken';
const tokenSymbol = 'MTKN'; const tokenSymbol = 'MTKN';
const tokenSupply = web3.utils.toWei('100'); 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 () { beforeEach(async function () {
this.owner = owner; this.owner = owner;
this.token = await Token.new(tokenName, tokenSymbol); this.token = await Token.new(tokenName, tokenSymbol);
this.mock = await Governor.new(name, this.token.address); this.mock = await Governor.new(name, this.token.address);
this.receiver = await CallReceiver.new(); 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.mint(owner, tokenSupply);
await this.token.delegate(voter1, { from: voter1 }); await this.helper.delegate({ token: this.token, to: voter1, value: web3.utils.toWei('10') }, { from: owner });
await this.token.delegate(voter2, { from: voter2 }); await this.helper.delegate({ token: this.token, to: voter2, value: web3.utils.toWei('7') }, { from: owner });
await this.token.delegate(voter3, { from: voter3 }); await this.helper.delegate({ token: this.token, to: voter3, value: web3.utils.toWei('5') }, { from: owner });
await this.token.delegate(voter4, { from: voter4 }); await this.helper.delegate({ token: this.token, to: voter4, value: web3.utils.toWei('2') }, { from: owner });
// default proposal
this.proposal = this.helper.setProposal([
{
target: this.receiver.address,
value,
data: this.receiver.contract.methods.mockFunction().encodeABI(),
},
], '<proposal description>');
}); });
it('deployment check', async function () { it('deployment check', async function () {
expect(await this.mock.name()).to.be.equal(name); expect(await this.mock.name()).to.be.equal(name);
expect(await this.mock.token()).to.be.equal(this.token.address); 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.votingDelay()).to.be.bignumber.equal(votingDelay);
expect(await this.mock.votingPeriod()).to.be.bignumber.equal('16'); 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.quorum(0)).to.be.bignumber.equal('0');
}); });
describe('voting with comp token', function () { it('voting with comp token', async function () {
beforeEach(async function () { await this.helper.propose();
this.settings = { await this.helper.waitForSnapshot();
proposal: [ await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
[ this.receiver.address ], await this.helper.vote({ support: Enums.VoteType.For }, { from: voter2 });
[ web3.utils.toWei('0') ], await this.helper.vote({ support: Enums.VoteType.Against }, { from: voter3 });
[ this.receiver.contract.methods.mockFunction().encodeABI() ], await this.helper.vote({ support: Enums.VoteType.Abstain }, { from: voter4 });
'<proposal description>', await this.helper.waitForDeadline();
], await this.helper.execute();
tokenHolder: owner,
voters: [
{ voter: voter1, weight: web3.utils.toWei('1'), support: Enums.VoteType.For },
{ voter: voter2, weight: web3.utils.toWei('10'), 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 },
],
};
});
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);
expect(await this.mock.hasVoted(this.id, voter3)).to.be.equal(true);
expect(await this.mock.hasVoted(this.id, voter4)).to.be.equal(true);
this.receipts.castVote.filter(Boolean).forEach(vote => { expect(await this.mock.hasVoted(this.proposal.id, owner)).to.be.equal(false);
const { voter } = vote.logs.find(Boolean).args; expect(await this.mock.hasVoted(this.proposal.id, voter1)).to.be.equal(true);
expectEvent( expect(await this.mock.hasVoted(this.proposal.id, voter2)).to.be.equal(true);
vote, expect(await this.mock.hasVoted(this.proposal.id, voter3)).to.be.equal(true);
'VoteCast', expect(await this.mock.hasVoted(this.proposal.id, voter4)).to.be.equal(true);
this.settings.voters.find(({ address }) => address === voter),
); await this.mock.proposalVotes(this.proposal.id).then(results => {
}); expect(results.forVotes).to.be.bignumber.equal(web3.utils.toWei('17'));
await this.mock.proposalVotes(this.id).then(result => { expect(results.againstVotes).to.be.bignumber.equal(web3.utils.toWei('5'));
for (const [key, value] of Object.entries(Enums.VoteType)) { expect(results.abstainVotes).to.be.bignumber.equal(web3.utils.toWei('2'));
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'),
),
);
}
});
}); });
runGovernorWorkflow();
}); });
}); });
const { expectEvent } = require('@openzeppelin/test-helpers'); const { BN, expectEvent } = require('@openzeppelin/test-helpers');
const { BN } = require('bn.js'); const { expect } = require('chai');
const Enums = require('../../helpers/enums'); const Enums = require('../../helpers/enums');
const { GovernorHelper } = require('../../helpers/governance');
const {
runGovernorWorkflow,
} = require('./../GovernorWorkflow.behavior');
const Token = artifacts.require('ERC721VotesMock'); const Token = artifacts.require('ERC721VotesMock');
const Governor = artifacts.require('GovernorVoteMocks'); const Governor = artifacts.require('GovernorVoteMocks');
...@@ -14,105 +11,94 @@ contract('GovernorERC721Mock', function (accounts) { ...@@ -14,105 +11,94 @@ contract('GovernorERC721Mock', function (accounts) {
const [ owner, voter1, voter2, voter3, voter4 ] = accounts; const [ owner, voter1, voter2, voter3, voter4 ] = accounts;
const name = 'OZ-Governor'; const name = 'OZ-Governor';
// const version = '1';
const tokenName = 'MockNFToken'; const tokenName = 'MockNFToken';
const tokenSymbol = 'MTKN'; const tokenSymbol = 'MTKN';
const NFT0 = web3.utils.toWei('100'); const NFT0 = new BN(0);
const NFT1 = web3.utils.toWei('10'); const NFT1 = new BN(1);
const NFT2 = web3.utils.toWei('20'); const NFT2 = new BN(2);
const NFT3 = web3.utils.toWei('30'); const NFT3 = new BN(3);
const NFT4 = web3.utils.toWei('40'); const NFT4 = new BN(4);
const votingDelay = new BN(4);
// Must be the same as in contract const votingPeriod = new BN(16);
const ProposalState = { const value = web3.utils.toWei('1');
Pending: new BN('0'),
Active: new BN('1'),
Canceled: new BN('2'),
Defeated: new BN('3'),
Succeeded: new BN('4'),
Queued: new BN('5'),
Expired: new BN('6'),
Executed: new BN('7'),
};
beforeEach(async function () { beforeEach(async function () {
this.owner = owner; this.owner = owner;
this.token = await Token.new(tokenName, tokenSymbol); this.token = await Token.new(tokenName, tokenSymbol);
this.mock = await Governor.new(name, this.token.address); this.mock = await Governor.new(name, this.token.address);
this.receiver = await CallReceiver.new(); this.receiver = await CallReceiver.new();
await this.token.mint(owner, NFT0);
await this.token.mint(owner, NFT1); this.helper = new GovernorHelper(this.mock);
await this.token.mint(owner, NFT2);
await this.token.mint(owner, NFT3); await web3.eth.sendTransaction({ from: owner, to: this.mock.address, value });
await this.token.mint(owner, NFT4);
await Promise.all([ NFT0, NFT1, NFT2, NFT3, NFT4 ].map(tokenId => this.token.mint(owner, tokenId)));
await this.token.delegate(voter1, { from: voter1 }); await this.helper.delegate({ token: this.token, to: voter1, tokenId: NFT0 }, { from: owner });
await this.token.delegate(voter2, { from: voter2 }); await this.helper.delegate({ token: this.token, to: voter2, tokenId: NFT1 }, { from: owner });
await this.token.delegate(voter3, { from: voter3 }); await this.helper.delegate({ token: this.token, to: voter2, tokenId: NFT2 }, { from: owner });
await this.token.delegate(voter4, { from: voter4 }); await this.helper.delegate({ token: this.token, to: voter3, tokenId: NFT3 }, { from: owner });
await this.helper.delegate({ token: this.token, to: voter4, tokenId: NFT4 }, { from: owner });
// default proposal
this.proposal = this.helper.setProposal([
{
target: this.receiver.address,
value,
data: this.receiver.contract.methods.mockFunction().encodeABI(),
},
], '<proposal description>');
}); });
it('deployment check', async function () { it('deployment check', async function () {
expect(await this.mock.name()).to.be.equal(name); expect(await this.mock.name()).to.be.equal(name);
expect(await this.mock.token()).to.be.equal(this.token.address); 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.votingDelay()).to.be.bignumber.equal(votingDelay);
expect(await this.mock.votingPeriod()).to.be.bignumber.equal('16'); 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.quorum(0)).to.be.bignumber.equal('0');
}); });
describe('voting with ERC721 token', function () { it('voting with ERC721 token', async function () {
beforeEach(async function () { await this.helper.propose();
this.settings = { await this.helper.waitForSnapshot();
proposal: [
[ this.receiver.address ],
[ web3.utils.toWei('0') ],
[ this.receiver.contract.methods.mockFunction().encodeABI() ],
'<proposal description>',
],
tokenHolder: owner,
voters: [
{ voter: voter1, nfts: [NFT0], support: Enums.VoteType.For },
{ voter: voter2, nfts: [NFT1, NFT2], support: Enums.VoteType.For },
{ voter: voter3, nfts: [NFT3], support: Enums.VoteType.Against },
{ voter: voter4, nfts: [NFT4], support: Enums.VoteType.Abstain },
],
};
});
afterEach(async function () {
expect(await this.mock.hasVoted(this.id, owner)).to.be.equal(false);
for (const vote of this.receipts.castVote.filter(Boolean)) { expectEvent(
const { voter } = vote.logs.find(Boolean).args; await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }),
'VoteCast',
{ voter: voter1, support: Enums.VoteType.For, weight: '1' },
);
expect(await this.mock.hasVoted(this.id, voter)).to.be.equal(true); expectEvent(
await this.helper.vote({ support: Enums.VoteType.For }, { from: voter2 }),
'VoteCast',
{ voter: voter2, support: Enums.VoteType.For, weight: '2' },
);
expectEvent( expectEvent(
vote, await this.helper.vote({ support: Enums.VoteType.Against }, { from: voter3 }),
'VoteCast', 'VoteCast',
this.settings.voters.find(({ address }) => address === voter), { voter: voter3, support: Enums.VoteType.Against, weight: '1' },
); );
if (voter === voter2) { expectEvent(
expect(await this.token.getVotes(voter, vote.blockNumber)).to.be.bignumber.equal('2'); await this.helper.vote({ support: Enums.VoteType.Abstain }, { from: voter4 }),
} else { 'VoteCast',
expect(await this.token.getVotes(voter, vote.blockNumber)).to.be.bignumber.equal('1'); { voter: voter4, support: Enums.VoteType.Abstain, weight: '1' },
}
}
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, { nfts }) => acc.add(new BN(nfts.length)),
new BN('0'),
),
); );
}
});
expect(await this.mock.state(this.id)).to.be.bignumber.equal(ProposalState.Executed); await this.helper.waitForDeadline();
}); await this.helper.execute();
runGovernorWorkflow(); 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 this.mock.hasVoted(this.proposal.id, voter3)).to.be.equal(true);
expect(await this.mock.hasVoted(this.proposal.id, voter4)).to.be.equal(true);
await this.mock.proposalVotes(this.proposal.id).then(results => {
expect(results.forVotes).to.be.bignumber.equal('3');
expect(results.againstVotes).to.be.bignumber.equal('1');
expect(results.abstainVotes).to.be.bignumber.equal('1');
});
}); });
}); });
const { BN, expectEvent, time } = require('@openzeppelin/test-helpers'); const { BN, expectEvent, expectRevert, time } = require('@openzeppelin/test-helpers');
const { expect } = require('chai');
const Enums = require('../../helpers/enums'); const Enums = require('../../helpers/enums');
const { GovernorHelper } = require('../../helpers/governance');
const {
runGovernorWorkflow,
} = require('./../GovernorWorkflow.behavior');
const Token = artifacts.require('ERC20VotesMock'); const Token = artifacts.require('ERC20VotesMock');
const Governor = artifacts.require('GovernorMock'); const Governor = artifacts.require('GovernorMock');
...@@ -19,24 +17,41 @@ contract('GovernorVotesQuorumFraction', function (accounts) { ...@@ -19,24 +17,41 @@ contract('GovernorVotesQuorumFraction', function (accounts) {
const tokenSupply = new BN(web3.utils.toWei('100')); const tokenSupply = new BN(web3.utils.toWei('100'));
const ratio = new BN(8); // percents const ratio = new BN(8); // percents
const newRatio = new BN(6); // percents const newRatio = new BN(6); // percents
const votingDelay = new BN(4);
const votingPeriod = new BN(16);
const value = web3.utils.toWei('1');
beforeEach(async function () { beforeEach(async function () {
this.owner = owner; this.owner = owner;
this.token = await Token.new(tokenName, tokenSymbol); this.token = await Token.new(tokenName, tokenSymbol);
this.mock = await Governor.new(name, this.token.address, 4, 16, ratio); this.mock = await Governor.new(name, this.token.address, votingDelay, votingPeriod, ratio);
this.receiver = await CallReceiver.new(); 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.mint(owner, tokenSupply);
await this.token.delegate(voter1, { from: voter1 }); await this.helper.delegate({ token: this.token, to: voter1, value: web3.utils.toWei('10') }, { from: owner });
await this.token.delegate(voter2, { from: voter2 }); await this.helper.delegate({ token: this.token, to: voter2, value: web3.utils.toWei('7') }, { from: owner });
await this.token.delegate(voter3, { from: voter3 }); await this.helper.delegate({ token: this.token, to: voter3, value: web3.utils.toWei('5') }, { from: owner });
await this.token.delegate(voter4, { from: voter4 }); await this.helper.delegate({ token: this.token, to: voter4, value: web3.utils.toWei('2') }, { from: owner });
// default proposal
this.proposal = this.helper.setProposal([
{
target: this.receiver.address,
value,
data: this.receiver.contract.methods.mockFunction().encodeABI(),
},
], '<proposal description>');
}); });
it('deployment check', async function () { it('deployment check', async function () {
expect(await this.mock.name()).to.be.equal(name); expect(await this.mock.name()).to.be.equal(name);
expect(await this.mock.token()).to.be.equal(this.token.address); 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.votingDelay()).to.be.bignumber.equal(votingDelay);
expect(await this.mock.votingPeriod()).to.be.bignumber.equal('16'); 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.quorum(0)).to.be.bignumber.equal('0');
expect(await this.mock.quorumNumerator()).to.be.bignumber.equal(ratio); expect(await this.mock.quorumNumerator()).to.be.bignumber.equal(ratio);
expect(await this.mock.quorumDenominator()).to.be.bignumber.equal('100'); expect(await this.mock.quorumDenominator()).to.be.bignumber.equal('100');
...@@ -44,51 +59,47 @@ contract('GovernorVotesQuorumFraction', function (accounts) { ...@@ -44,51 +59,47 @@ contract('GovernorVotesQuorumFraction', function (accounts) {
.to.be.bignumber.equal(tokenSupply.mul(ratio).divn(100)); .to.be.bignumber.equal(tokenSupply.mul(ratio).divn(100));
}); });
describe('quroum not reached', function () { it('quroum reached', async function () {
beforeEach(async function () { await this.helper.propose();
this.settings = { await this.helper.waitForSnapshot();
proposal: [ await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
[ this.receiver.address ], await this.helper.waitForDeadline();
[ web3.utils.toWei('0') ], await this.helper.execute();
[ this.receiver.contract.methods.mockFunction().encodeABI() ],
'<proposal description>',
],
tokenHolder: owner,
voters: [
{ voter: voter1, weight: web3.utils.toWei('1'), support: Enums.VoteType.For },
],
steps: {
execute: { error: 'Governor: proposal not successful' },
},
};
}); });
runGovernorWorkflow();
it('quroum not reached', async function () {
await this.helper.propose();
await this.helper.waitForSnapshot();
await this.helper.vote({ support: Enums.VoteType.For }, { from: voter2 });
await this.helper.waitForDeadline();
await expectRevert(this.helper.execute(), 'Governor: proposal not successful');
}); });
describe('update quorum ratio through proposal', function () { describe('onlyGovernance updates', function () {
beforeEach(async function () { it('updateQuorumNumerator is protected', async function () {
this.settings = { await expectRevert(
proposal: [ this.mock.updateQuorumNumerator(newRatio),
[ this.mock.address ], 'Governor: onlyGovernance',
[ web3.utils.toWei('0') ], );
[ this.mock.contract.methods.updateQuorumNumerator(newRatio).encodeABI() ],
'<proposal description>',
],
tokenHolder: owner,
voters: [
{ voter: voter1, weight: tokenSupply, support: Enums.VoteType.For },
],
};
}); });
afterEach(async function () {
await expectEvent.inTransaction( it('can updateQuorumNumerator through governance', async function () {
this.receipts.execute.transactionHash, this.helper.setProposal([
this.mock,
'QuorumNumeratorUpdated',
{ {
oldQuorumNumerator: ratio, target: this.mock.address,
newQuorumNumerator: newRatio, data: this.mock.contract.methods.updateQuorumNumerator(newRatio).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(),
'QuorumNumeratorUpdated',
{ oldQuorumNumerator: ratio, newQuorumNumerator: newRatio },
); );
expect(await this.mock.quorumNumerator()).to.be.bignumber.equal(newRatio); expect(await this.mock.quorumNumerator()).to.be.bignumber.equal(newRatio);
...@@ -96,27 +107,24 @@ contract('GovernorVotesQuorumFraction', function (accounts) { ...@@ -96,27 +107,24 @@ contract('GovernorVotesQuorumFraction', function (accounts) {
expect(await time.latestBlock().then(blockNumber => this.mock.quorum(blockNumber.subn(1)))) expect(await time.latestBlock().then(blockNumber => this.mock.quorum(blockNumber.subn(1))))
.to.be.bignumber.equal(tokenSupply.mul(newRatio).divn(100)); .to.be.bignumber.equal(tokenSupply.mul(newRatio).divn(100));
}); });
runGovernorWorkflow();
});
describe('update quorum over the maximum', function () { it('cannot updateQuorumNumerator over the maximum', async function () {
beforeEach(async function () { this.helper.setProposal([
this.settings = { {
proposal: [ target: this.mock.address,
[ this.mock.address ], data: this.mock.contract.methods.updateQuorumNumerator('101').encodeABI(),
[ web3.utils.toWei('0') ],
[ this.mock.contract.methods.updateQuorumNumerator(new BN(101)).encodeABI() ],
'<proposal description>',
],
tokenHolder: owner,
voters: [
{ voter: voter1, weight: tokenSupply, support: Enums.VoteType.For },
],
steps: {
execute: { error: 'GovernorVotesQuorumFraction: quorumNumerator over quorumDenominator' },
}, },
}; ], '<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(),
'GovernorVotesQuorumFraction: quorumNumerator over quorumDenominator',
);
}); });
runGovernorWorkflow();
}); });
}); });
const { time } = require('@openzeppelin/test-helpers');
function zip (...args) {
return Array(Math.max(...args.map(array => array.length)))
.fill()
.map((_, i) => args.map(array => array[i]));
}
function concatHex (...args) {
return web3.utils.bytesToHex([].concat(...args.map(h => web3.utils.hexToBytes(h || '0x'))));
}
function concatOpts (args, opts = null) {
return opts ? args.concat(opts) : args;
}
class GovernorHelper {
constructor (governor) {
this.governor = governor;
}
delegate (delegation = {}, opts = null) {
return Promise.all([
delegation.token.delegate(delegation.to, { from: delegation.to }),
delegation.value &&
delegation.token.transfer(...concatOpts([ delegation.to, delegation.value ]), opts),
delegation.tokenId &&
delegation.token.ownerOf(delegation.tokenId).then(owner =>
delegation.token.transferFrom(...concatOpts([ owner, delegation.to, delegation.tokenId ], opts)),
),
]);
}
propose (opts = null) {
const proposal = this.currentProposal;
return this.governor.methods[
proposal.useCompatibilityInterface
? 'propose(address[],uint256[],string[],bytes[],string)'
: 'propose(address[],uint256[],bytes[],string)'
](...concatOpts(proposal.fullProposal, opts));
}
queue (opts = null) {
const proposal = this.currentProposal;
return proposal.useCompatibilityInterface
? this.governor.methods['queue(uint256)'](...concatOpts(
[ proposal.id ],
opts,
))
: this.governor.methods['queue(address[],uint256[],bytes[],bytes32)'](...concatOpts(
proposal.shortProposal,
opts,
));
}
execute (opts = null) {
const proposal = this.currentProposal;
return proposal.useCompatibilityInterface
? this.governor.methods['execute(uint256)'](...concatOpts(
[ proposal.id ],
opts,
))
: this.governor.methods['execute(address[],uint256[],bytes[],bytes32)'](...concatOpts(
proposal.shortProposal,
opts,
));
}
cancel (opts = null) {
const proposal = this.currentProposal;
return proposal.useCompatibilityInterface
? this.governor.methods['cancel(uint256)'](...concatOpts(
[ proposal.id ],
opts,
))
: this.governor.methods['cancel(address[],uint256[],bytes[],bytes32)'](...concatOpts(
proposal.shortProposal,
opts,
));
}
vote (vote = {}, opts = null) {
const proposal = this.currentProposal;
return vote.signature
// if signature, and either params or reason →
? vote.params || vote.reason
? vote.signature({
proposalId: proposal.id,
support: vote.support,
reason: vote.reason || '',
params: vote.params || '',
}).then(({ v, r, s }) => this.governor.castVoteWithReasonAndParamsBySig(...concatOpts(
[ proposal.id, vote.support, vote.reason || '', vote.params || '', v, r, s ],
opts,
)))
: vote.signature({
proposalId: proposal.id,
support: vote.support,
}).then(({ v, r, s }) => this.governor.castVoteBySig(...concatOpts(
[ proposal.id, vote.support, v, r, s ],
opts,
)))
: vote.params
// otherwize if params
? this.governor.castVoteWithReasonAndParams(...concatOpts(
[ proposal.id, vote.support, vote.reason || '', vote.params ],
opts,
))
: vote.reason
// otherwize if reason
? this.governor.castVoteWithReason(...concatOpts(
[ proposal.id, vote.support, vote.reason ],
opts,
))
: this.governor.castVote(...concatOpts(
[ proposal.id, vote.support ],
opts,
));
}
waitForSnapshot (offset = 0) {
const proposal = this.currentProposal;
return this.governor.proposalSnapshot(proposal.id)
.then(blockNumber => time.advanceBlockTo(blockNumber.addn(offset)));
}
waitForDeadline (offset = 0) {
const proposal = this.currentProposal;
return this.governor.proposalDeadline(proposal.id)
.then(blockNumber => time.advanceBlockTo(blockNumber.addn(offset)));
}
waitForEta (offset = 0) {
const proposal = this.currentProposal;
return this.governor.proposalEta(proposal.id)
.then(timestamp => time.increaseTo(timestamp.addn(offset)));
}
/**
* Specify a proposal either as
* 1) an array of objects [{ target, value, data, signature? }]
* 2) an object of arrays { targets: [], values: [], data: [], signatures?: [] }
*/
setProposal (actions, description) {
let targets, values, signatures, data, useCompatibilityInterface;
if (Array.isArray(actions)) {
useCompatibilityInterface = actions.some(a => 'signature' in a);
targets = actions.map(a => a.target);
values = actions.map(a => a.value || '0');
signatures = actions.map(a => a.signature || '');
data = actions.map(a => a.data || '0x');
} else {
useCompatibilityInterface = Array.isArray(actions.signatures);
({ targets, values, signatures = [], data } = actions);
}
const fulldata = zip(signatures.map(s => s && web3.eth.abi.encodeFunctionSignature(s)), data)
.map(hexs => concatHex(...hexs));
const descriptionHash = web3.utils.keccak256(description);
// condensed version for queing end executing
const shortProposal = [
targets,
values,
fulldata,
descriptionHash,
];
// full version for proposing
const fullProposal = [
targets,
values,
...(useCompatibilityInterface ? [ signatures ] : []),
data,
description,
];
// proposal id
const id = web3.utils.toBN(web3.utils.keccak256(web3.eth.abi.encodeParameters(
[ 'address[]', 'uint256[]', 'bytes[]', 'bytes32' ],
shortProposal,
)));
this.currentProposal = {
id,
targets,
values,
signatures,
data,
fulldata,
description,
descriptionHash,
shortProposal,
fullProposal,
useCompatibilityInterface,
};
return this.currentProposal;
}
}
module.exports = {
GovernorHelper,
};
...@@ -112,35 +112,34 @@ for (const k of Object.getOwnPropertyNames(INTERFACES)) { ...@@ -112,35 +112,34 @@ for (const k of Object.getOwnPropertyNames(INTERFACES)) {
} }
function shouldSupportInterfaces (interfaces = []) { function shouldSupportInterfaces (interfaces = []) {
describe('Contract interface', function () { describe('ERC165', function () {
beforeEach(function () { beforeEach(function () {
this.contractUnderTest = this.mock || this.token || this.holder || this.accessControl; this.contractUnderTest = this.mock || this.token || this.holder || this.accessControl;
}); });
it('supportsInterface uses less than 30k gas', async function () {
for (const k of interfaces) { for (const k of interfaces) {
const interfaceId = INTERFACE_IDS[k]; const interfaceId = INTERFACE_IDS[k];
describe(k, function () {
describe('ERC165\'s supportsInterface(bytes4)', function () {
it('uses less than 30k gas', async function () {
expect(await this.contractUnderTest.supportsInterface.estimateGas(interfaceId)).to.be.lte(30000); expect(await this.contractUnderTest.supportsInterface.estimateGas(interfaceId)).to.be.lte(30000);
}
}); });
it('claims support', async function () { it('all interfaces are reported as supported', async function () {
for (const k of interfaces) {
const interfaceId = INTERFACE_IDS[k];
expect(await this.contractUnderTest.supportsInterface(interfaceId)).to.equal(true); expect(await this.contractUnderTest.supportsInterface(interfaceId)).to.equal(true);
}); }
}); });
it('all interface functions are in ABI', async function () {
for (const k of interfaces) {
for (const fnName of INTERFACES[k]) { for (const fnName of INTERFACES[k]) {
const fnSig = FN_SIGNATURES[fnName]; const fnSig = FN_SIGNATURES[fnName];
describe(fnName, function () {
it('has to be implemented', function () {
expect(this.contractUnderTest.abi.filter(fn => fn.signature === fnSig).length).to.equal(1); expect(this.contractUnderTest.abi.filter(fn => fn.signature === fnSig).length).to.equal(1);
});
});
} }
});
} }
}); });
});
} }
module.exports = { module.exports = {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment