-
Notifications
You must be signed in to change notification settings - Fork 11.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Mock and test usage of Govenance extension params
- Loading branch information
Showing
2 changed files
with
207 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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(); | ||
}); | ||
}); |