Merge branch 'OpenZeppelin:master' into certora/erc20

pull/3478/head
Michael D. George 3 years ago committed by GitHub
commit 7ab95baab8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      .github/workflows/docs.yml
  2. 6
      .github/workflows/test.yml
  3. 7
      CHANGELOG.md
  4. 126
      contracts/governance/Governor.sol
  5. 64
      contracts/governance/IGovernor.sol
  6. 3
      contracts/governance/compatibility/GovernorCompatibilityBravo.sol
  7. 3
      contracts/governance/extensions/GovernorCountingSimple.sol
  8. 5
      contracts/governance/extensions/GovernorPreventLateQuorum.sol
  9. 8
      contracts/governance/extensions/GovernorVotes.sol
  10. 8
      contracts/governance/extensions/GovernorVotesComp.sol
  11. 10
      contracts/mocks/GovernorCompMock.sol
  12. 10
      contracts/mocks/GovernorCompatibilityBravoMock.sol
  13. 10
      contracts/mocks/GovernorMock.sol
  14. 5
      contracts/mocks/GovernorPreventLateQuorumMock.sol
  15. 10
      contracts/mocks/GovernorTimelockCompoundMock.sol
  16. 10
      contracts/mocks/GovernorTimelockControlMock.sol
  17. 10
      contracts/mocks/GovernorVoteMock.sol
  18. 61
      contracts/mocks/GovernorWithParamsMock.sol
  19. 9
      contracts/mocks/wizard/MyGovernor1.sol
  20. 9
      contracts/mocks/wizard/MyGovernor2.sol
  21. 9
      contracts/mocks/wizard/MyGovernor3.sol
  22. 1
      test/governance/Governor.test.js
  23. 1
      test/governance/extensions/GovernorTimelockCompound.test.js
  24. 1
      test/governance/extensions/GovernorTimelockControl.test.js
  25. 238
      test/governance/extensions/GovernorWithParams.test.js
  26. 22
      test/utils/introspection/SupportsInterface.behavior.js

@ -8,7 +8,7 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 12.x

@ -12,7 +12,7 @@ jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 12.x
@ -36,7 +36,7 @@ jobs:
coverage:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
with:
fetch-depth: 2
- uses: actions/setup-node@v3
@ -58,7 +58,7 @@ jobs:
slither:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 12.x

@ -7,6 +7,13 @@
* `ERC1155`: Add a `_afterTokenTransfer` hook for improved extensibility. ([#3166](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3166))
* `DoubleEndedQueue`: a new data structure that supports efficient push and pop to both front and back, useful for FIFO and LIFO queues. ([#3153](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3153))
* `Governor`: improved security of `onlyGovernance` modifier when using an external executor contract (e.g. a timelock) that can operate without necessarily going through the governance protocol. ([#3147](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3147))
* `Governor`: Add a way to parameterize votes. This can be used to implement voting systems such as fractionalized voting, ERC721 based voting, or any number of other systems. The `params` argument added to `_countVote` method, and included in the newly added `_getVotes` method, can be used by counting and voting modules respectively for such purposes.
### Breaking changes
* `Governor`: Adds internal virtual `_getVotes` method that must be implemented; this is a breaking change for existing concrete extensions to `Governor`. To fix this on an existing voting module extension, rename `getVotes` to `_getVotes` and add a `bytes memory` argument. ([#3043](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3043))
* `Governor`: Adds `params` parameter to internal virtual `_countVote ` method; this is a breaking change for existing concrete extensions to `Governor`. To fix this on an existing counting module extension, add a `bytes memory` argument to `_countVote`. ([#3043](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3043))
* `Governor`: Does not emit `VoteCast` event when params data is non-empty; instead emits `VoteCastWithParams` event. To fix this on an integration that consumes the `VoteCast` event, also fetch/monitor `VoteCastWithParams` events. ([#3043](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3043))
## 4.5.0 (2022-02-09)

@ -19,7 +19,7 @@ import "./IGovernor.sol";
* This contract is abstract and requires several function to be implemented in various modules:
*
* - A counting module must implement {quorum}, {_quorumReached}, {_voteSucceeded} and {_countVote}
* - A voting module must implement {getVotes}
* - A voting module must implement {_getVotes}
* - Additionanly, the {votingPeriod} must also be implemented
*
* _Available since v4.3._
@ -30,6 +30,8 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor {
using Timers for Timers.BlockNumber;
bytes32 public constant BALLOT_TYPEHASH = keccak256("Ballot(uint256 proposalId,uint8 support)");
bytes32 public constant EXTENDED_BALLOT_TYPEHASH =
keccak256("ExtendedBallot(uint256 proposalId,uint8 support,string reason,bytes params)");
struct ProposalCore {
Timers.BlockNumber voteStart;
@ -86,7 +88,16 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor {
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC165) returns (bool) {
return interfaceId == type(IGovernor).interfaceId || super.supportsInterface(interfaceId);
// In addition to the current interfaceId, also support previous version of the interfaceId that did not
// include the castVoteWithReasonAndParams() function as standard
return
interfaceId ==
(type(IGovernor).interfaceId ^
this.castVoteWithReasonAndParams.selector ^
this.castVoteWithReasonAndParamsBySig.selector ^
this.getVotesWithParams.selector) ||
interfaceId == type(IGovernor).interfaceId ||
super.supportsInterface(interfaceId);
}
/**
@ -193,6 +204,15 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor {
*/
function _voteSucceeded(uint256 proposalId) internal view virtual returns (bool);
/**
* @dev Get the voting weight of `account` at a specific `blockNumber`, for a vote as described by `params`.
*/
function _getVotes(
address account,
uint256 blockNumber,
bytes memory params
) internal view virtual returns (uint256);
/**
* @dev Register a vote with a given support and voting weight.
*
@ -202,9 +222,20 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor {
uint256 proposalId,
address account,
uint8 support,
uint256 weight
uint256 weight,
bytes memory params
) internal virtual;
/**
* @dev Default additional encoded parameters used by castVote methods that don't include them
*
* Note: Should be overriden by specific implementations to use an appropriate value, the
* meaning of the additional params, in the context of that implementation
*/
function _defaultParams() internal view virtual returns (bytes memory) {
return "";
}
/**
* @dev See {IGovernor-propose}.
*/
@ -355,6 +386,24 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor {
return proposalId;
}
/**
* @dev See {IGovernor-getVotes}.
*/
function getVotes(address account, uint256 blockNumber) public view virtual override returns (uint256) {
return _getVotes(account, blockNumber, _defaultParams());
}
/**
* @dev See {IGovernor-getVotesWithParams}.
*/
function getVotesWithParams(
address account,
uint256 blockNumber,
bytes memory params
) public view virtual override returns (uint256) {
return _getVotes(account, blockNumber, params);
}
/**
* @dev See {IGovernor-castVote}.
*/
@ -375,6 +424,19 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor {
return _castVote(proposalId, voter, support, reason);
}
/**
* @dev See {IGovernor-castVoteWithReasonAndParams}.
*/
function castVoteWithReasonAndParams(
uint256 proposalId,
uint8 support,
string calldata reason,
bytes memory params
) public virtual override returns (uint256) {
address voter = _msgSender();
return _castVote(proposalId, voter, support, reason, params);
}
/**
* @dev See {IGovernor-castVoteBySig}.
*/
@ -394,9 +456,41 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor {
return _castVote(proposalId, voter, support, "");
}
/**
* @dev See {IGovernor-castVoteWithReasonAndParamsBySig}.
*/
function castVoteWithReasonAndParamsBySig(
uint256 proposalId,
uint8 support,
string calldata reason,
bytes memory params,
uint8 v,
bytes32 r,
bytes32 s
) public virtual override returns (uint256) {
address voter = ECDSA.recover(
_hashTypedDataV4(
keccak256(
abi.encode(
EXTENDED_BALLOT_TYPEHASH,
proposalId,
support,
keccak256(bytes(reason)),
keccak256(params)
)
)
),
v,
r,
s
);
return _castVote(proposalId, voter, support, reason, params);
}
/**
* @dev Internal vote casting mechanism: Check that the vote is pending, that it has not been cast yet, retrieve
* voting weight using {IGovernor-getVotes} and call the {_countVote} internal function.
* voting weight using {IGovernor-getVotes} and call the {_countVote} internal function. Uses the _defaultParams().
*
* Emits a {IGovernor-VoteCast} event.
*/
@ -405,14 +499,34 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor {
address account,
uint8 support,
string memory reason
) internal virtual returns (uint256) {
return _castVote(proposalId, account, support, reason, _defaultParams());
}
/**
* @dev Internal vote casting mechanism: Check that the vote is pending, that it has not been cast yet, retrieve
* voting weight using {IGovernor-getVotes} and call the {_countVote} internal function.
*
* Emits a {IGovernor-VoteCast} event.
*/
function _castVote(
uint256 proposalId,
address account,
uint8 support,
string memory reason,
bytes memory params
) internal virtual returns (uint256) {
ProposalCore storage proposal = _proposals[proposalId];
require(state(proposalId) == ProposalState.Active, "Governor: vote not currently active");
uint256 weight = getVotes(account, proposal.voteStart.getDeadline());
_countVote(proposalId, account, support, weight);
uint256 weight = _getVotes(account, proposal.voteStart.getDeadline(), params);
_countVote(proposalId, account, support, weight, params);
if (params.length == 0) {
emit VoteCast(account, proposalId, support, weight, reason);
} else {
emit VoteCastWithParams(account, proposalId, support, weight, reason, params);
}
return weight;
}

@ -48,12 +48,27 @@ abstract contract IGovernor is IERC165 {
event ProposalExecuted(uint256 proposalId);
/**
* @dev Emitted when a vote is cast.
* @dev Emitted when a vote is cast without params.
*
* Note: `support` values should be seen as buckets. There interpretation depends on the voting module used.
* Note: `support` values should be seen as buckets. Their interpretation depends on the voting module used.
*/
event VoteCast(address indexed voter, uint256 proposalId, uint8 support, uint256 weight, string reason);
/**
* @dev Emitted when a vote is cast with params.
*
* Note: `support` values should be seen as buckets. Their interpretation depends on the voting module used.
* `params` are additional encoded parameters. Their intepepretation also depends on the voting module used.
*/
event VoteCastWithParams(
address indexed voter,
uint256 proposalId,
uint8 support,
uint256 weight,
string reason,
bytes params
);
/**
* @notice module:core
* @dev Name of the governor instance (used in building the ERC712 domain separator).
@ -78,6 +93,12 @@ abstract contract IGovernor is IERC165 {
* - `quorum=bravo` means that only For votes are counted towards quorum.
* - `quorum=for,abstain` means that both For and Abstain votes are counted towards quorum.
*
* If a counting module makes use of encoded `params`, it should include this under a `params` key with a unique
* name that describes the behavior. For example:
*
* - `params=fractional` might refer to a scheme where votes are divided fractionally between for/against/abstain.
* - `params=erc721` might refer to a scheme where specific NFTs are delegated to vote.
*
* NOTE: The string can be decoded by the standard
* https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams[`URLSearchParams`]
* JavaScript class.
@ -151,6 +172,16 @@ abstract contract IGovernor is IERC165 {
*/
function getVotes(address account, uint256 blockNumber) public view virtual returns (uint256);
/**
* @notice module:reputation
* @dev Voting power of an `account` at a specific `blockNumber` given additional encoded parameters.
*/
function getVotesWithParams(
address account,
uint256 blockNumber,
bytes memory params
) public view virtual returns (uint256);
/**
* @notice module:voting
* @dev Returns weither `account` has cast a vote on `proposalId`.
@ -204,7 +235,19 @@ abstract contract IGovernor is IERC165 {
) public virtual returns (uint256 balance);
/**
* @dev Cast a vote using the user cryptographic signature.
* @dev Cast a vote with a reason and additional encoded parameters
*
* Emits a {VoteCast} event.
*/
function castVoteWithReasonAndParams(
uint256 proposalId,
uint8 support,
string calldata reason,
bytes memory params
) public virtual returns (uint256 balance);
/**
* @dev Cast a vote using the user's cryptographic signature.
*
* Emits a {VoteCast} event.
*/
@ -215,4 +258,19 @@ abstract contract IGovernor is IERC165 {
bytes32 r,
bytes32 s
) public virtual returns (uint256 balance);
/**
* @dev Cast a vote with a reason and additional encoded parameters using the user's cryptographic signature.
*
* Emits a {VoteCast} event.
*/
function castVoteWithReasonAndParamsBySig(
uint256 proposalId,
uint8 support,
string calldata reason,
bytes memory params,
uint8 v,
bytes32 r,
bytes32 s
) public virtual returns (uint256 balance);
}

@ -265,7 +265,8 @@ abstract contract GovernorCompatibilityBravo is IGovernorTimelock, IGovernorComp
uint256 proposalId,
address account,
uint8 support,
uint256 weight
uint256 weight,
bytes memory // params
) internal virtual override {
ProposalDetails storage details = _proposalDetails[proposalId];
Receipt storage receipt = details.receipts[account];

@ -86,7 +86,8 @@ abstract contract GovernorCountingSimple is Governor {
uint256 proposalId,
address account,
uint8 support,
uint256 weight
uint256 weight,
bytes memory // params
) internal virtual override {
ProposalVote storage proposalvote = _proposalVotes[proposalId];

@ -57,9 +57,10 @@ abstract contract GovernorPreventLateQuorum is Governor {
uint256 proposalId,
address account,
uint8 support,
string memory reason
string memory reason,
bytes memory params
) internal virtual override returns (uint256) {
uint256 result = super._castVote(proposalId, account, support, reason);
uint256 result = super._castVote(proposalId, account, support, reason, params);
Timers.BlockNumber storage extendedDeadline = _extendedDeadlines[proposalId];

@ -19,9 +19,13 @@ abstract contract GovernorVotes is Governor {
}
/**
* Read the voting weight from the token's built in snapshot mechanism (see {IGovernor-getVotes}).
* Read the voting weight from the token's built in snapshot mechanism (see {Governor-_getVotes}).
*/
function getVotes(address account, uint256 blockNumber) public view virtual override returns (uint256) {
function _getVotes(
address account,
uint256 blockNumber,
bytes memory /*params*/
) internal view virtual override returns (uint256) {
return token.getPastVotes(account, blockNumber);
}
}

@ -19,9 +19,13 @@ abstract contract GovernorVotesComp is Governor {
}
/**
* Read the voting weight from the token's built in snapshot mechanism (see {IGovernor-getVotes}).
* Read the voting weight from the token's built in snapshot mechanism (see {Governor-_getVotes}).
*/
function getVotes(address account, uint256 blockNumber) public view virtual override returns (uint256) {
function _getVotes(
address account,
uint256 blockNumber,
bytes memory /*params*/
) internal view virtual override returns (uint256) {
return token.getPriorVotes(account, blockNumber);
}
}

@ -28,14 +28,4 @@ contract GovernorCompMock is GovernorVotesComp, GovernorCountingSimple {
) public returns (uint256 proposalId) {
return _cancel(targets, values, calldatas, salt);
}
function getVotes(address account, uint256 blockNumber)
public
view
virtual
override(IGovernor, GovernorVotesComp)
returns (uint256)
{
return super.getVotes(account, blockNumber);
}
}

@ -124,16 +124,6 @@ contract GovernorCompatibilityBravoMock is
return super._cancel(targets, values, calldatas, salt);
}
function getVotes(address account, uint256 blockNumber)
public
view
virtual
override(IGovernor, GovernorVotesComp)
returns (uint256)
{
return super.getVotes(account, blockNumber);
}
function _executor() internal view virtual override(Governor, GovernorTimelockCompound) returns (address) {
return super._executor();
}

@ -35,16 +35,6 @@ contract GovernorMock is
return _cancel(targets, values, calldatas, salt);
}
function getVotes(address account, uint256 blockNumber)
public
view
virtual
override(IGovernor, GovernorVotes)
returns (uint256)
{
return super.getVotes(account, blockNumber);
}
function proposalThreshold() public view override(Governor, GovernorSettings) returns (uint256) {
return super.proposalThreshold();
}

@ -53,8 +53,9 @@ contract GovernorPreventLateQuorumMock is
uint256 proposalId,
address account,
uint8 support,
string memory reason
string memory reason,
bytes memory params
) internal virtual override(Governor, GovernorPreventLateQuorum) returns (uint256) {
return super._castVote(proposalId, account, support, reason);
return super._castVote(proposalId, account, support, reason, params);
}
}

@ -92,16 +92,6 @@ contract GovernorTimelockCompoundMock is
return super._cancel(targets, values, calldatas, salt);
}
function getVotes(address account, uint256 blockNumber)
public
view
virtual
override(IGovernor, GovernorVotes)
returns (uint256)
{
return super.getVotes(account, blockNumber);
}
function _executor() internal view virtual override(Governor, GovernorTimelockCompound) returns (address) {
return super._executor();
}

@ -92,16 +92,6 @@ contract GovernorTimelockControlMock is
return super._cancel(targets, values, calldatas, descriptionHash);
}
function getVotes(address account, uint256 blockNumber)
public
view
virtual
override(IGovernor, GovernorVotes)
returns (uint256)
{
return super.getVotes(account, blockNumber);
}
function _executor() internal view virtual override(Governor, GovernorTimelockControl) returns (address) {
return super._executor();
}

@ -28,14 +28,4 @@ contract GovernorVoteMocks is GovernorVotes, GovernorCountingSimple {
) public returns (uint256 proposalId) {
return _cancel(targets, values, calldatas, salt);
}
function getVotes(address account, uint256 blockNumber)
public
view
virtual
override(IGovernor, GovernorVotes)
returns (uint256)
{
return super.getVotes(account, blockNumber);
}
}

@ -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 super._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 super._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);
}
}

@ -40,15 +40,6 @@ contract MyGovernor1 is
return super.quorum(blockNumber);
}
function getVotes(address account, uint256 blockNumber)
public
view
override(IGovernor, GovernorVotes)
returns (uint256)
{
return super.getVotes(account, blockNumber);
}
function state(uint256 proposalId) public view override(Governor, GovernorTimelockControl) returns (ProposalState) {
return super.state(proposalId);
}

@ -46,15 +46,6 @@ contract MyGovernor2 is
return super.quorum(blockNumber);
}
function getVotes(address account, uint256 blockNumber)
public
view
override(IGovernor, GovernorVotes)
returns (uint256)
{
return super.getVotes(account, blockNumber);
}
function state(uint256 proposalId) public view override(Governor, GovernorTimelockControl) returns (ProposalState) {
return super.state(proposalId);
}

@ -44,15 +44,6 @@ contract MyGovernor is
return super.quorum(blockNumber);
}
function getVotes(address account, uint256 blockNumber)
public
view
override(IGovernor, GovernorVotes)
returns (uint256)
{
return super.getVotes(account, blockNumber);
}
function state(uint256 proposalId)
public
view

@ -41,6 +41,7 @@ contract('Governor', function (accounts) {
shouldSupportInterfaces([
'ERC165',
'Governor',
'GovernorWithParams',
]);
it('deployment check', async function () {

@ -48,6 +48,7 @@ contract('GovernorTimelockCompound', function (accounts) {
shouldSupportInterfaces([
'ERC165',
'Governor',
'GovernorWithParams',
'GovernorTimelock',
]);

@ -43,6 +43,7 @@ contract('GovernorTimelockControl', function (accounts) {
shouldSupportInterfaces([
'ERC165',
'Governor',
'GovernorWithParams',
'GovernorTimelock',
]);

@ -0,0 +1,238 @@
const { BN, constants, expectEvent } = require('@openzeppelin/test-helpers');
const { web3 } = require('@openzeppelin/test-helpers/src/setup');
const Enums = require('../../helpers/enums');
const ethSigUtil = require('eth-sig-util');
const Wallet = require('ethereumjs-wallet').default;
const { EIP712Domain } = require('../../helpers/eip712');
const { fromRpcSig } = require('ethereumjs-util');
const { runGovernorWorkflow } = require('../GovernorWorkflow.behavior');
const { expect } = require('chai');
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 version = '1';
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 tokenss
],
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, 'VoteCastWithParams', { voter: voter2, weight: reducedWeight, params });
const votes = await this.mock.proposalVotes(this.id);
expect(votes.forVotes).to.be.bignumber.equal(reducedWeight);
});
runGovernorWorkflow();
});
describe('Voting with params by signature is properly supported', function () {
const voterBySig = Wallet.generate(); // generate voter by signature wallet
const sigVoterWeight = web3.utils.toWei('1.0');
beforeEach(async function () {
this.chainId = await web3.eth.getChainId();
this.voter = web3.utils.toChecksumAddress(voterBySig.getAddressString());
// use delegateBySig to enable vote delegation sig voting 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: this.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);
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: this.voter, weight: sigVoterWeight }, // 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 reason = 'This is my reason';
const uintParam = new BN(1);
const strParam = 'These are my params';
const reducedWeight = new BN(sigVoterWeight).sub(uintParam);
const params = web3.eth.abi.encodeParameters(['uint256', 'string'], [uintParam, strParam]);
// prepare signature for vote by signature
const { v, r, s } = fromRpcSig(
ethSigUtil.signTypedMessage(voterBySig.getPrivateKey(), {
data: {
types: {
EIP712Domain,
ExtendedBallot: [
{ name: 'proposalId', type: 'uint256' },
{ name: 'support', type: 'uint8' },
{ name: 'reason', type: 'string' },
{ name: 'params', type: 'bytes' },
],
},
domain: { name, version, chainId: this.chainId, verifyingContract: this.mock.address },
primaryType: 'ExtendedBallot',
message: { proposalId: this.id, support: Enums.VoteType.For, reason, params },
},
}),
);
const tx = await this.mock.castVoteWithReasonAndParamsBySig(this.id, Enums.VoteType.For, reason, params, v, r, s);
expectEvent(tx, 'CountParams', { uintParam, strParam });
expectEvent(tx, 'VoteCastWithParams', { voter: this.voter, weight: reducedWeight, params });
const votes = await this.mock.proposalVotes(this.id);
expect(votes.forVotes).to.be.bignumber.equal(reducedWeight);
});
runGovernorWorkflow();
});
});

@ -69,6 +69,28 @@ const INTERFACES = {
'castVoteWithReason(uint256,uint8,string)',
'castVoteBySig(uint256,uint8,uint8,bytes32,bytes32)',
],
GovernorWithParams: [
'name()',
'version()',
'COUNTING_MODE()',
'hashProposal(address[],uint256[],bytes[],bytes32)',
'state(uint256)',
'proposalSnapshot(uint256)',
'proposalDeadline(uint256)',
'votingDelay()',
'votingPeriod()',
'quorum(uint256)',
'getVotes(address,uint256)',
'getVotesWithParams(address,uint256,bytes)',
'hasVoted(uint256,address)',
'propose(address[],uint256[],bytes[],string)',
'execute(address[],uint256[],bytes[],bytes32)',
'castVote(uint256,uint8)',
'castVoteWithReason(uint256,uint8,string)',
'castVoteWithReasonAndParams(uint256,uint8,string,bytes)',
'castVoteBySig(uint256,uint8,uint8,bytes32,bytes32)',
'castVoteWithReasonAndParamsBySig(uint256,uint8,string,bytes,uint8,bytes32,bytes32)',
],
GovernorTimelock: [
'timelock()',
'proposalEta(uint256)',

Loading…
Cancel
Save