Skip to content

Commit

Permalink
Mock and test usage of Govenance extension params
Browse files Browse the repository at this point in the history
  • Loading branch information
apbendi committed Jan 17, 2022
1 parent 7a33925 commit a2367da
Show file tree
Hide file tree
Showing 2 changed files with 207 additions and 0 deletions.
61 changes: 61 additions & 0 deletions contracts/mocks/GovernorWithParamsMock.sol
@@ -0,0 +1,61 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "../governance/extensions/GovernorCountingSimple.sol";
import "../governance/extensions/GovernorVotes.sol";

contract GovernorWithParamsMock is GovernorVotes, GovernorCountingSimple {
event CountParams(uint256 uintParam, string strParam);

constructor(string memory name_, IVotes token_) Governor(name_) GovernorVotes(token_) {}

function quorum(uint256) public pure override returns (uint256) {
return 0;
}

function votingDelay() public pure override returns (uint256) {
return 4;
}

function votingPeriod() public pure override returns (uint256) {
return 16;
}

function _getVotes(
address account,
uint256 blockNumber,
bytes memory params
) internal view virtual override(Governor, GovernorVotes) returns (uint256) {
uint256 reduction = 0;
// If the user provides parameters, we reduce the voting weight by the amount of the integer param
if (params.length > 0) {
(reduction, ) = abi.decode(params, (uint256, string));
}
// reverts on overflow
return GovernorVotes._getVotes(account, blockNumber, params) - reduction;
}

function _countVote(
uint256 proposalId,
address account,
uint8 support,
uint256 weight,
bytes memory params
) internal virtual override(Governor, GovernorCountingSimple) {
if (params.length > 0) {
(uint256 _uintParam, string memory _strParam) = abi.decode(params, (uint256, string));
emit CountParams(_uintParam, _strParam);
}
return GovernorCountingSimple._countVote(proposalId, account, support, weight, params);
}

function cancel(
address[] memory targets,
uint256[] memory values,
bytes[] memory calldatas,
bytes32 salt
) public returns (uint256 proposalId) {
return _cancel(targets, values, calldatas, salt);
}
}
146 changes: 146 additions & 0 deletions test/governance/extensions/GovernorWithParams.test.js
@@ -0,0 +1,146 @@
const { BN, expectEvent, expectRevert, time } = require('@openzeppelin/test-helpers');
const { web3 } = require('@openzeppelin/test-helpers/src/setup');
const Enums = require('../../helpers/enums');

const { runGovernorWorkflow } = require('../GovernorWorkflow.behavior');

const Token = artifacts.require('ERC20VotesCompMock');
const Governor = artifacts.require('GovernorWithParamsMock');
const CallReceiver = artifacts.require('CallReceiverMock');

contract('GovernorWithParams', function (accounts) {
const [owner, proposer, voter1, voter2, voter3, voter4] = accounts;

const name = 'OZ-Governor';
const tokenName = 'MockToken';
const tokenSymbol = 'MTKN';
const tokenSupply = web3.utils.toWei('100');
const votingDelay = new BN(4);
const votingPeriod = new BN(16);

beforeEach(async function () {
this.owner = owner;
this.token = await Token.new(tokenName, tokenSymbol);
this.mock = await Governor.new(name, this.token.address);
this.receiver = await CallReceiver.new();
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 });
});

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(votingDelay);
expect(await this.mock.votingPeriod()).to.be.bignumber.equal(votingPeriod);
});

describe('nominal is unaffected', function () {
beforeEach(async function () {
this.settings = {
proposal: [
[this.receiver.address],
[0],
[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 },
],
};
});

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')),
);
}
});

const startBlock = new BN(this.receipts.propose.blockNumber).add(votingDelay);
const endBlock = new BN(this.receipts.propose.blockNumber).add(votingDelay).add(votingPeriod);
expect(await this.mock.proposalSnapshot(this.id)).to.be.bignumber.equal(startBlock);
expect(await this.mock.proposalDeadline(this.id)).to.be.bignumber.equal(endBlock);

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,
endBlock,
description: this.settings.proposal[3],
});

this.receipts.castVote.filter(Boolean).forEach((vote) => {
const { voter } = vote.logs.filter(({ event }) => event === 'VoteCast').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');
});
runGovernorWorkflow();
});

describe('Voting with params is properly supported', function () {
const voter2Weight = web3.utils.toWei('1.0');
beforeEach(async function () {
this.settings = {
proposal: [
[this.receiver.address],
[0],
[this.receiver.contract.methods.mockFunction().encodeABI()],
'<proposal description>',
],
proposer,
tokenHolder: owner,
voters: [
{ voter: voter1, weight: web3.utils.toWei('0.2'), support: Enums.VoteType.Against },
{ voter: voter2, weight: voter2Weight }, // do not actually vote, only getting tokens
{ voter: voter3, weight: web3.utils.toWei('0.9') }, // do not actually vote, only getting tokens
],
steps: {
wait: { enable: false },
execute: { enable: false },
},
};
});

afterEach(async function () {
expect(await this.mock.state(this.id)).to.be.bignumber.equal(Enums.ProposalState.Active);

const uintParam = new BN(1);
const strParam = 'These are my params';
const reducedWeight = (new BN(voter2Weight)).sub(uintParam);
const params = web3.eth.abi.encodeParameters(['uint256', 'string'], [uintParam, strParam]);
const tx = await this.mock.castVoteWithReasonAndParams(this.id, Enums.VoteType.For, '', params, { from: voter2 });

expectEvent(tx, 'CountParams', { uintParam, strParam });
expectEvent(tx, 'VoteCast', {voter: voter2, weight: reducedWeight});

// TODO: Cast vote with voter3 using params & signature; confirm events exist in tx receipt
});
runGovernorWorkflow();
});
});

0 comments on commit a2367da

Please sign in to comment.