Commit f130b81b by github-actions

Transpile ba5c1202

parent 10f0f1a9
...@@ -56,6 +56,7 @@ abstract contract GovernorUpgradeable is Initializable, ContextUpgradeable, ERC1 ...@@ -56,6 +56,7 @@ abstract contract GovernorUpgradeable is Initializable, ContextUpgradeable, ERC1
__Context_init_unchained(); __Context_init_unchained();
__ERC165_init_unchained(); __ERC165_init_unchained();
__EIP712_init_unchained(name_, version()); __EIP712_init_unchained(name_, version());
__IGovernor_init_unchained();
__Governor_init_unchained(name_); __Governor_init_unchained(name_);
} }
...@@ -145,26 +146,6 @@ abstract contract GovernorUpgradeable is Initializable, ContextUpgradeable, ERC1 ...@@ -145,26 +146,6 @@ abstract contract GovernorUpgradeable is Initializable, ContextUpgradeable, ERC1
} }
/** /**
* @dev See {IGovernor-votingDelay}
*/
function votingDelay() public view virtual override returns (uint256);
/**
* @dev See {IGovernor-votingPeriod}
*/
function votingPeriod() public view virtual override returns (uint256);
/**
* @dev See {IGovernor-quorum}
*/
function quorum(uint256 blockNumber) public view virtual override returns (uint256);
/**
* @dev See {IGovernor-getVotes}
*/
function getVotes(address account, uint256 blockNumber) public view virtual override returns (uint256);
/**
* @dev Amount of votes already casted passes the threshold limit. * @dev Amount of votes already casted passes the threshold limit.
*/ */
function _quorumReached(uint256 proposalId) internal view virtual returns (bool); function _quorumReached(uint256 proposalId) internal view virtual returns (bool);
......
...@@ -3,13 +3,20 @@ ...@@ -3,13 +3,20 @@
pragma solidity ^0.8.0; pragma solidity ^0.8.0;
import "../utils/introspection/ERC165Upgradeable.sol"; import "../utils/introspection/ERC165Upgradeable.sol";
import "../proxy/utils/Initializable.sol";
/** /**
* @dev Interface of the {Governor} core. * @dev Interface of the {Governor} core.
* *
* _Available since v4.3._ * _Available since v4.3._
*/ */
interface IGovernorUpgradeable is IERC165Upgradeable { abstract contract IGovernorUpgradeable is Initializable, IERC165Upgradeable {
function __IGovernor_init() internal initializer {
__IGovernor_init_unchained();
}
function __IGovernor_init_unchained() internal initializer {
}
enum ProposalState { enum ProposalState {
Pending, Pending,
Active, Active,
...@@ -57,13 +64,13 @@ interface IGovernorUpgradeable is IERC165Upgradeable { ...@@ -57,13 +64,13 @@ interface IGovernorUpgradeable is IERC165Upgradeable {
* @notice module:core * @notice module:core
* @dev Name of the governor instance (used in building the ERC712 domain separator). * @dev Name of the governor instance (used in building the ERC712 domain separator).
*/ */
function name() external view returns (string memory); function name() public view virtual returns (string memory);
/** /**
* @notice module:core * @notice module:core
* @dev Version of the governor instance (used in building the ERC712 domain separator). Default: "1" * @dev Version of the governor instance (used in building the ERC712 domain separator). Default: "1"
*/ */
function version() external view returns (string memory); function version() public view virtual returns (string memory);
/** /**
* @notice module:voting * @notice module:voting
...@@ -82,7 +89,7 @@ interface IGovernorUpgradeable is IERC165Upgradeable { ...@@ -82,7 +89,7 @@ interface IGovernorUpgradeable is IERC165Upgradeable {
* JavaScript class. * JavaScript class.
*/ */
// solhint-disable-next-line func-name-mixedcase // solhint-disable-next-line func-name-mixedcase
function COUNTING_MODE() external pure returns (string memory); function COUNTING_MODE() public pure virtual returns (string memory);
/** /**
* @notice module:core * @notice module:core
...@@ -93,32 +100,32 @@ interface IGovernorUpgradeable is IERC165Upgradeable { ...@@ -93,32 +100,32 @@ interface IGovernorUpgradeable is IERC165Upgradeable {
uint256[] calldata values, uint256[] calldata values,
bytes[] calldata calldatas, bytes[] calldata calldatas,
bytes32 descriptionHash bytes32 descriptionHash
) external pure returns (uint256); ) public pure virtual returns (uint256);
/** /**
* @notice module:core * @notice module:core
* @dev Current state of a proposal, following Compound's convention * @dev Current state of a proposal, following Compound's convention
*/ */
function state(uint256 proposalId) external view returns (ProposalState); function state(uint256 proposalId) public view virtual returns (ProposalState);
/** /**
* @notice module:core * @notice module:core
* @dev block number used to retrieve user's votes and quorum. * @dev block number used to retrieve user's votes and quorum.
*/ */
function proposalSnapshot(uint256 proposalId) external view returns (uint256); function proposalSnapshot(uint256 proposalId) public view virtual returns (uint256);
/** /**
* @notice module:core * @notice module:core
* @dev timestamp at which votes close. * @dev timestamp at which votes close.
*/ */
function proposalDeadline(uint256 proposalId) external view returns (uint256); function proposalDeadline(uint256 proposalId) public view virtual returns (uint256);
/** /**
* @notice module:user-config * @notice module:user-config
* @dev delay, in number of block, between the proposal is created and the vote starts. This can be increassed to * @dev delay, in number of block, between the proposal is created and the vote starts. This can be increassed to
* leave time for users to buy voting power, of delegate it, before the voting of a proposal starts. * leave time for users to buy voting power, of delegate it, before the voting of a proposal starts.
*/ */
function votingDelay() external view returns (uint256); function votingDelay() public view virtual returns (uint256);
/** /**
* @notice module:user-config * @notice module:user-config
...@@ -127,7 +134,7 @@ interface IGovernorUpgradeable is IERC165Upgradeable { ...@@ -127,7 +134,7 @@ interface IGovernorUpgradeable is IERC165Upgradeable {
* Note: the {votingDelay} can delay the start of the vote. This must be considered when setting the voting * Note: the {votingDelay} can delay the start of the vote. This must be considered when setting the voting
* duration compared to the voting delay. * duration compared to the voting delay.
*/ */
function votingPeriod() external view returns (uint256); function votingPeriod() public view virtual returns (uint256);
/** /**
* @notice module:user-config * @notice module:user-config
...@@ -136,7 +143,7 @@ interface IGovernorUpgradeable is IERC165Upgradeable { ...@@ -136,7 +143,7 @@ interface IGovernorUpgradeable is IERC165Upgradeable {
* Note: The `blockNumber` parameter corresponds to the snaphot used for counting vote. This allows to scale the * Note: The `blockNumber` parameter corresponds to the snaphot used for counting vote. This allows to scale the
* quroum depending on values such as the totalSupply of a token at this block (see {ERC20Votes}). * quroum depending on values such as the totalSupply of a token at this block (see {ERC20Votes}).
*/ */
function quorum(uint256 blockNumber) external view returns (uint256); function quorum(uint256 blockNumber) public view virtual returns (uint256);
/** /**
* @notice module:reputation * @notice module:reputation
...@@ -145,13 +152,13 @@ interface IGovernorUpgradeable is IERC165Upgradeable { ...@@ -145,13 +152,13 @@ interface IGovernorUpgradeable is IERC165Upgradeable {
* Note: this can be implemented in a number of ways, for example by reading the delegated balance from one (or * Note: this can be implemented in a number of ways, for example by reading the delegated balance from one (or
* multiple), {ERC20Votes} tokens. * multiple), {ERC20Votes} tokens.
*/ */
function getVotes(address account, uint256 blockNumber) external view returns (uint256); function getVotes(address account, uint256 blockNumber) public view virtual returns (uint256);
/** /**
* @notice module:voting * @notice module:voting
* @dev Returns weither `account` has casted a vote on `proposalId`. * @dev Returns weither `account` has casted a vote on `proposalId`.
*/ */
function hasVoted(uint256 proposalId, address account) external view returns (bool); function hasVoted(uint256 proposalId, address account) public view virtual returns (bool);
/** /**
* @dev Create a new proposal. Vote start {IGovernor-votingDelay} blocks after the proposal is created and ends * @dev Create a new proposal. Vote start {IGovernor-votingDelay} blocks after the proposal is created and ends
...@@ -164,7 +171,7 @@ interface IGovernorUpgradeable is IERC165Upgradeable { ...@@ -164,7 +171,7 @@ interface IGovernorUpgradeable is IERC165Upgradeable {
uint256[] memory values, uint256[] memory values,
bytes[] memory calldatas, bytes[] memory calldatas,
string memory description string memory description
) external returns (uint256 proposalId); ) public virtual returns (uint256 proposalId);
/** /**
* @dev Execute a successful proposal. This requiers the quorum to be reached, the vote to be successful, and the * @dev Execute a successful proposal. This requiers the quorum to be reached, the vote to be successful, and the
...@@ -179,14 +186,14 @@ interface IGovernorUpgradeable is IERC165Upgradeable { ...@@ -179,14 +186,14 @@ interface IGovernorUpgradeable is IERC165Upgradeable {
uint256[] memory values, uint256[] memory values,
bytes[] memory calldatas, bytes[] memory calldatas,
bytes32 descriptionHash bytes32 descriptionHash
) external payable returns (uint256 proposalId); ) public payable virtual returns (uint256 proposalId);
/** /**
* @dev Cast a vote * @dev Cast a vote
* *
* Emits a {VoteCast} event. * Emits a {VoteCast} event.
*/ */
function castVote(uint256 proposalId, uint8 support) external returns (uint256 balance); function castVote(uint256 proposalId, uint8 support) public virtual returns (uint256 balance);
/** /**
* @dev Cast a with a reason * @dev Cast a with a reason
...@@ -197,7 +204,7 @@ interface IGovernorUpgradeable is IERC165Upgradeable { ...@@ -197,7 +204,7 @@ interface IGovernorUpgradeable is IERC165Upgradeable {
uint256 proposalId, uint256 proposalId,
uint8 support, uint8 support,
string calldata reason string calldata reason
) external returns (uint256 balance); ) public virtual returns (uint256 balance);
/** /**
* @dev Cast a vote using the user cryptographic signature. * @dev Cast a vote using the user cryptographic signature.
...@@ -210,5 +217,6 @@ interface IGovernorUpgradeable is IERC165Upgradeable { ...@@ -210,5 +217,6 @@ interface IGovernorUpgradeable is IERC165Upgradeable {
uint8 v, uint8 v,
bytes32 r, bytes32 r,
bytes32 s bytes32 s
) external returns (uint256 balance); ) public virtual returns (uint256 balance);
uint256[50] private __gap;
} }
...@@ -5,6 +5,7 @@ pragma solidity ^0.8.0; ...@@ -5,6 +5,7 @@ pragma solidity ^0.8.0;
import "../../utils/CountersUpgradeable.sol"; import "../../utils/CountersUpgradeable.sol";
import "../../utils/math/SafeCastUpgradeable.sol"; import "../../utils/math/SafeCastUpgradeable.sol";
import "../extensions/IGovernorTimelockUpgradeable.sol"; import "../extensions/IGovernorTimelockUpgradeable.sol";
import "../extensions/GovernorProposalThresholdUpgradeable.sol";
import "../GovernorUpgradeable.sol"; import "../GovernorUpgradeable.sol";
import "./IGovernorCompatibilityBravoUpgradeable.sol"; import "./IGovernorCompatibilityBravoUpgradeable.sol";
import "../../proxy/utils/Initializable.sol"; import "../../proxy/utils/Initializable.sol";
...@@ -17,10 +18,19 @@ import "../../proxy/utils/Initializable.sol"; ...@@ -17,10 +18,19 @@ import "../../proxy/utils/Initializable.sol";
* *
* _Available since v4.3._ * _Available since v4.3._
*/ */
abstract contract GovernorCompatibilityBravoUpgradeable is Initializable, IGovernorTimelockUpgradeable, IGovernorCompatibilityBravoUpgradeable, GovernorUpgradeable { abstract contract GovernorCompatibilityBravoUpgradeable is
Initializable, IGovernorTimelockUpgradeable,
IGovernorCompatibilityBravoUpgradeable,
GovernorUpgradeable,
GovernorProposalThresholdUpgradeable
{
function __GovernorCompatibilityBravo_init() internal initializer { function __GovernorCompatibilityBravo_init() internal initializer {
__Context_init_unchained(); __Context_init_unchained();
__ERC165_init_unchained(); __ERC165_init_unchained();
__IGovernor_init_unchained();
__IGovernorTimelock_init_unchained();
__IGovernorCompatibilityBravo_init_unchained();
__GovernorProposalThreshold_init_unchained();
__GovernorCompatibilityBravo_init_unchained(); __GovernorCompatibilityBravo_init_unchained();
} }
...@@ -50,20 +60,6 @@ abstract contract GovernorCompatibilityBravoUpgradeable is Initializable, IGover ...@@ -50,20 +60,6 @@ abstract contract GovernorCompatibilityBravoUpgradeable is Initializable, IGover
mapping(uint256 => ProposalDetails) private _proposalDetails; mapping(uint256 => ProposalDetails) private _proposalDetails;
// public for hooking
function proposalThreshold() public view virtual override returns (uint256);
// public for hooking
function proposalEta(uint256 proposalId) public view virtual override returns (uint256);
// public for hooking
function queue(
address[] memory targets,
uint256[] memory values,
bytes[] memory calldatas,
bytes32 descriptionHash
) public virtual override returns (uint256);
// solhint-disable-next-line func-name-mixedcase // solhint-disable-next-line func-name-mixedcase
function COUNTING_MODE() public pure virtual override returns (string memory) { function COUNTING_MODE() public pure virtual override returns (string memory) {
return "support=bravo&quorum=bravo"; return "support=bravo&quorum=bravo";
...@@ -78,8 +74,9 @@ abstract contract GovernorCompatibilityBravoUpgradeable is Initializable, IGover ...@@ -78,8 +74,9 @@ abstract contract GovernorCompatibilityBravoUpgradeable is Initializable, IGover
uint256[] memory values, uint256[] memory values,
bytes[] memory calldatas, bytes[] memory calldatas,
string memory description string memory description
) public virtual override(IGovernorUpgradeable, GovernorUpgradeable) returns (uint256) { ) public virtual override(IGovernorUpgradeable, GovernorUpgradeable, GovernorProposalThresholdUpgradeable) returns (uint256) {
return propose(targets, values, new string[](calldatas.length), calldatas, description); _storeProposal(_msgSender(), targets, values, new string[](calldatas.length), calldatas, description);
return super.propose(targets, values, calldatas, description);
} }
/** /**
...@@ -92,14 +89,8 @@ abstract contract GovernorCompatibilityBravoUpgradeable is Initializable, IGover ...@@ -92,14 +89,8 @@ abstract contract GovernorCompatibilityBravoUpgradeable is Initializable, IGover
bytes[] memory calldatas, bytes[] memory calldatas,
string memory description string memory description
) public virtual override returns (uint256) { ) public virtual override returns (uint256) {
require( _storeProposal(_msgSender(), targets, values, signatures, calldatas, description);
getVotes(msg.sender, block.number - 1) >= proposalThreshold(), return propose(targets, values, _encodeCalldata(signatures, calldatas), description);
"GovernorCompatibilityBravo: proposer votes below proposal threshold"
);
uint256 proposalId = super.propose(targets, values, _encodeCalldata(signatures, calldatas), description);
_storeProposal(proposalId, _msgSender(), targets, values, signatures, calldatas, description);
return proposalId;
} }
/** /**
...@@ -128,6 +119,22 @@ abstract contract GovernorCompatibilityBravoUpgradeable is Initializable, IGover ...@@ -128,6 +119,22 @@ abstract contract GovernorCompatibilityBravoUpgradeable is Initializable, IGover
); );
} }
function cancel(uint256 proposalId) public virtual override {
ProposalDetails storage details = _proposalDetails[proposalId];
require(
_msgSender() == details.proposer || getVotes(details.proposer, block.number - 1) < proposalThreshold(),
"GovernorBravo: proposer above threshold"
);
_cancel(
details.targets,
details.values,
_encodeCalldata(details.signatures, details.calldatas),
details.descriptionHash
);
}
/** /**
* @dev Encodes calldatas with optional function signature. * @dev Encodes calldatas with optional function signature.
*/ */
...@@ -141,7 +148,7 @@ abstract contract GovernorCompatibilityBravoUpgradeable is Initializable, IGover ...@@ -141,7 +148,7 @@ abstract contract GovernorCompatibilityBravoUpgradeable is Initializable, IGover
for (uint256 i = 0; i < signatures.length; ++i) { for (uint256 i = 0; i < signatures.length; ++i) {
fullcalldatas[i] = bytes(signatures[i]).length == 0 fullcalldatas[i] = bytes(signatures[i]).length == 0
? calldatas[i] ? calldatas[i]
: abi.encodePacked(bytes4(keccak256(bytes(signatures[i]))), calldatas[i]); : abi.encodeWithSignature(signatures[i], calldatas[i]);
} }
return fullcalldatas; return fullcalldatas;
...@@ -151,7 +158,6 @@ abstract contract GovernorCompatibilityBravoUpgradeable is Initializable, IGover ...@@ -151,7 +158,6 @@ abstract contract GovernorCompatibilityBravoUpgradeable is Initializable, IGover
* @dev Store proposal metadata for later lookup * @dev Store proposal metadata for later lookup
*/ */
function _storeProposal( function _storeProposal(
uint256 proposalId,
address proposer, address proposer,
address[] memory targets, address[] memory targets,
uint256[] memory values, uint256[] memory values,
...@@ -159,18 +165,32 @@ abstract contract GovernorCompatibilityBravoUpgradeable is Initializable, IGover ...@@ -159,18 +165,32 @@ abstract contract GovernorCompatibilityBravoUpgradeable is Initializable, IGover
bytes[] memory calldatas, bytes[] memory calldatas,
string memory description string memory description
) private { ) private {
ProposalDetails storage details = _proposalDetails[proposalId]; bytes32 descriptionHash = keccak256(bytes(description));
uint256 proposalId = hashProposal(targets, values, _encodeCalldata(signatures, calldatas), descriptionHash);
ProposalDetails storage details = _proposalDetails[proposalId];
if (details.descriptionHash == bytes32(0)) {
details.proposer = proposer; details.proposer = proposer;
details.targets = targets; details.targets = targets;
details.values = values; details.values = values;
details.signatures = signatures; details.signatures = signatures;
details.calldatas = calldatas; details.calldatas = calldatas;
details.descriptionHash = keccak256(bytes(description)); details.descriptionHash = descriptionHash;
}
} }
// ==================================================== Views ===================================================== // ==================================================== Views =====================================================
/** /**
* @dev Part of the Governor Bravo's interface: _"The number of votes required in order for a voter to become a proposer"_.
*/
function proposalThreshold()
public
view
virtual
override(IGovernorCompatibilityBravoUpgradeable, GovernorProposalThresholdUpgradeable)
returns (uint256);
/**
* @dev See {IGovernorCompatibilityBravo-proposals}. * @dev See {IGovernorCompatibilityBravo-proposals}.
*/ */
function proposals(uint256 proposalId) function proposals(uint256 proposalId)
......
...@@ -3,13 +3,21 @@ ...@@ -3,13 +3,21 @@
pragma solidity ^0.8.0; pragma solidity ^0.8.0;
import "../IGovernorUpgradeable.sol"; import "../IGovernorUpgradeable.sol";
import "../../proxy/utils/Initializable.sol";
/** /**
* @dev Interface extension that adds missing functions to the {Governor} core to provide `GovernorBravo` compatibility. * @dev Interface extension that adds missing functions to the {Governor} core to provide `GovernorBravo` compatibility.
* *
* _Available since v4.3._ * _Available since v4.3._
*/ */
interface IGovernorCompatibilityBravoUpgradeable is IGovernorUpgradeable { abstract contract IGovernorCompatibilityBravoUpgradeable is Initializable, IGovernorUpgradeable {
function __IGovernorCompatibilityBravo_init() internal initializer {
__IGovernor_init_unchained();
__IGovernorCompatibilityBravo_init_unchained();
}
function __IGovernorCompatibilityBravo_init_unchained() internal initializer {
}
/** /**
* @dev Proposal structure from Compound Governor Bravo. Not actually used by the compatibility layer, as * @dev Proposal structure from Compound Governor Bravo. Not actually used by the compatibility layer, as
* {{proposal}} returns a very different structure. * {{proposal}} returns a very different structure.
...@@ -44,14 +52,15 @@ interface IGovernorCompatibilityBravoUpgradeable is IGovernorUpgradeable { ...@@ -44,14 +52,15 @@ interface IGovernorCompatibilityBravoUpgradeable is IGovernorUpgradeable {
/** /**
* @dev Part of the Governor Bravo's interface. * @dev Part of the Governor Bravo's interface.
*/ */
function quorumVotes() external view returns (uint256); function quorumVotes() public view virtual returns (uint256);
/** /**
* @dev Part of the Governor Bravo's interface: _"The official record of all proposals ever proposed"_. * @dev Part of the Governor Bravo's interface: _"The official record of all proposals ever proposed"_.
*/ */
function proposals(uint256) function proposals(uint256)
external public
view view
virtual
returns ( returns (
uint256 id, uint256 id,
address proposer, address proposer,
...@@ -74,24 +83,30 @@ interface IGovernorCompatibilityBravoUpgradeable is IGovernorUpgradeable { ...@@ -74,24 +83,30 @@ interface IGovernorCompatibilityBravoUpgradeable is IGovernorUpgradeable {
string[] memory signatures, string[] memory signatures,
bytes[] memory calldatas, bytes[] memory calldatas,
string memory description string memory description
) external returns (uint256); ) public virtual returns (uint256);
/** /**
* @dev Part of the Governor Bravo's interface: _"Queues a proposal of state succeeded"_. * @dev Part of the Governor Bravo's interface: _"Queues a proposal of state succeeded"_.
*/ */
function queue(uint256 proposalId) external; function queue(uint256 proposalId) public virtual;
/** /**
* @dev Part of the Governor Bravo's interface: _"Executes a queued proposal if eta has passed"_. * @dev Part of the Governor Bravo's interface: _"Executes a queued proposal if eta has passed"_.
*/ */
function execute(uint256 proposalId) external payable; function execute(uint256 proposalId) public payable virtual;
/**
* @dev Cancels a proposal only if sender is the proposer, or proposer delegates dropped below proposal threshold.
*/
function cancel(uint256 proposalId) public virtual;
/** /**
* @dev Part of the Governor Bravo's interface: _"Gets actions of a proposal"_. * @dev Part of the Governor Bravo's interface: _"Gets actions of a proposal"_.
*/ */
function getActions(uint256 proposalId) function getActions(uint256 proposalId)
external public
view view
virtual
returns ( returns (
address[] memory targets, address[] memory targets,
uint256[] memory values, uint256[] memory values,
...@@ -102,10 +117,11 @@ interface IGovernorCompatibilityBravoUpgradeable is IGovernorUpgradeable { ...@@ -102,10 +117,11 @@ interface IGovernorCompatibilityBravoUpgradeable is IGovernorUpgradeable {
/** /**
* @dev Part of the Governor Bravo's interface: _"Gets the receipt for a voter on a given proposal"_. * @dev Part of the Governor Bravo's interface: _"Gets the receipt for a voter on a given proposal"_.
*/ */
function getReceipt(uint256 proposalId, address voter) external view returns (Receipt memory); function getReceipt(uint256 proposalId, address voter) public view virtual returns (Receipt memory);
/** /**
* @dev Part of the Governor Bravo's interface: _"The number of votes required in order for a voter to become a proposer"_. * @dev Part of the Governor Bravo's interface: _"The number of votes required in order for a voter to become a proposer"_.
*/ */
function proposalThreshold() external view returns (uint256); function proposalThreshold() public view virtual returns (uint256);
uint256[50] private __gap;
} }
...@@ -14,6 +14,7 @@ abstract contract GovernorCountingSimpleUpgradeable is Initializable, GovernorUp ...@@ -14,6 +14,7 @@ abstract contract GovernorCountingSimpleUpgradeable is Initializable, GovernorUp
function __GovernorCountingSimple_init() internal initializer { function __GovernorCountingSimple_init() internal initializer {
__Context_init_unchained(); __Context_init_unchained();
__ERC165_init_unchained(); __ERC165_init_unchained();
__IGovernor_init_unchained();
__GovernorCountingSimple_init_unchained(); __GovernorCountingSimple_init_unchained();
} }
...@@ -75,9 +76,7 @@ abstract contract GovernorCountingSimpleUpgradeable is Initializable, GovernorUp ...@@ -75,9 +76,7 @@ abstract contract GovernorCountingSimpleUpgradeable is Initializable, GovernorUp
function _quorumReached(uint256 proposalId) internal view virtual override returns (bool) { function _quorumReached(uint256 proposalId) internal view virtual override returns (bool) {
ProposalVote storage proposalvote = _proposalVotes[proposalId]; ProposalVote storage proposalvote = _proposalVotes[proposalId];
return return quorum(proposalSnapshot(proposalId)) <= proposalvote.forVotes + proposalvote.abstainVotes;
quorum(proposalSnapshot(proposalId)) <=
proposalvote.againstVotes + proposalvote.forVotes + proposalvote.abstainVotes;
} }
/** /**
......
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../GovernorUpgradeable.sol";
import "../../proxy/utils/Initializable.sol";
/**
* @dev Extension of {Governor} for proposal restriction to token holders with a minimum balance.
*
* _Available since v4.3._
*/
abstract contract GovernorProposalThresholdUpgradeable is Initializable, GovernorUpgradeable {
function __GovernorProposalThreshold_init() internal initializer {
__Context_init_unchained();
__ERC165_init_unchained();
__EIP712_init_unchained(name_, version());
__IGovernor_init_unchained();
__GovernorProposalThreshold_init_unchained();
}
function __GovernorProposalThreshold_init_unchained() internal initializer {
}
/**
* @dev See {IGovernor-propose}.
*/
function propose(
address[] memory targets,
uint256[] memory values,
bytes[] memory calldatas,
string memory description
) public virtual override returns (uint256) {
require(
getVotes(msg.sender, block.number - 1) >= proposalThreshold(),
"GovernorCompatibilityBravo: proposer votes below proposal threshold"
);
return super.propose(targets, values, calldatas, description);
}
/**
* @dev Part of the Governor Bravo's interface: _"The number of votes required in order for a voter to become a proposer"_.
*/
function proposalThreshold() public view virtual returns (uint256);
uint256[50] private __gap;
}
...@@ -96,6 +96,8 @@ abstract contract GovernorTimelockCompoundUpgradeable is Initializable, IGoverno ...@@ -96,6 +96,8 @@ abstract contract GovernorTimelockCompoundUpgradeable is Initializable, IGoverno
function __GovernorTimelockCompound_init(ICompoundTimelockUpgradeable timelockAddress) internal initializer { function __GovernorTimelockCompound_init(ICompoundTimelockUpgradeable timelockAddress) internal initializer {
__Context_init_unchained(); __Context_init_unchained();
__ERC165_init_unchained(); __ERC165_init_unchained();
__IGovernor_init_unchained();
__IGovernorTimelock_init_unchained();
__GovernorTimelockCompound_init_unchained(timelockAddress); __GovernorTimelockCompound_init_unchained(timelockAddress);
} }
......
...@@ -33,6 +33,8 @@ abstract contract GovernorTimelockControlUpgradeable is Initializable, IGovernor ...@@ -33,6 +33,8 @@ abstract contract GovernorTimelockControlUpgradeable is Initializable, IGovernor
function __GovernorTimelockControl_init(TimelockControllerUpgradeable timelockAddress) internal initializer { function __GovernorTimelockControl_init(TimelockControllerUpgradeable timelockAddress) internal initializer {
__Context_init_unchained(); __Context_init_unchained();
__ERC165_init_unchained(); __ERC165_init_unchained();
__IGovernor_init_unchained();
__IGovernorTimelock_init_unchained();
__GovernorTimelockControl_init_unchained(timelockAddress); __GovernorTimelockControl_init_unchained(timelockAddress);
} }
......
...@@ -17,6 +17,7 @@ abstract contract GovernorVotesCompUpgradeable is Initializable, GovernorUpgrade ...@@ -17,6 +17,7 @@ abstract contract GovernorVotesCompUpgradeable is Initializable, GovernorUpgrade
function __GovernorVotesComp_init(ERC20VotesCompUpgradeable token_) internal initializer { function __GovernorVotesComp_init(ERC20VotesCompUpgradeable token_) internal initializer {
__Context_init_unchained(); __Context_init_unchained();
__ERC165_init_unchained(); __ERC165_init_unchained();
__IGovernor_init_unchained();
__GovernorVotesComp_init_unchained(token_); __GovernorVotesComp_init_unchained(token_);
} }
......
...@@ -19,6 +19,7 @@ abstract contract GovernorVotesQuorumFractionUpgradeable is Initializable, Gover ...@@ -19,6 +19,7 @@ abstract contract GovernorVotesQuorumFractionUpgradeable is Initializable, Gover
function __GovernorVotesQuorumFraction_init(uint256 quorumNumeratorValue) internal initializer { function __GovernorVotesQuorumFraction_init(uint256 quorumNumeratorValue) internal initializer {
__Context_init_unchained(); __Context_init_unchained();
__ERC165_init_unchained(); __ERC165_init_unchained();
__IGovernor_init_unchained();
__GovernorVotesQuorumFraction_init_unchained(quorumNumeratorValue); __GovernorVotesQuorumFraction_init_unchained(quorumNumeratorValue);
} }
......
...@@ -18,6 +18,7 @@ abstract contract GovernorVotesUpgradeable is Initializable, GovernorUpgradeable ...@@ -18,6 +18,7 @@ abstract contract GovernorVotesUpgradeable is Initializable, GovernorUpgradeable
function __GovernorVotes_init(ERC20VotesUpgradeable tokenAddress) internal initializer { function __GovernorVotes_init(ERC20VotesUpgradeable tokenAddress) internal initializer {
__Context_init_unchained(); __Context_init_unchained();
__ERC165_init_unchained(); __ERC165_init_unchained();
__IGovernor_init_unchained();
__GovernorVotes_init_unchained(tokenAddress); __GovernorVotes_init_unchained(tokenAddress);
} }
......
...@@ -3,23 +3,32 @@ ...@@ -3,23 +3,32 @@
pragma solidity ^0.8.0; pragma solidity ^0.8.0;
import "../IGovernorUpgradeable.sol"; import "../IGovernorUpgradeable.sol";
import "../../proxy/utils/Initializable.sol";
/** /**
* @dev Extension of the {IGovernor} for timelock supporting modules. * @dev Extension of the {IGovernor} for timelock supporting modules.
* *
* _Available since v4.3._ * _Available since v4.3._
*/ */
interface IGovernorTimelockUpgradeable is IGovernorUpgradeable { abstract contract IGovernorTimelockUpgradeable is Initializable, IGovernorUpgradeable {
function __IGovernorTimelock_init() internal initializer {
__IGovernor_init_unchained();
__IGovernorTimelock_init_unchained();
}
function __IGovernorTimelock_init_unchained() internal initializer {
}
event ProposalQueued(uint256 proposalId, uint256 eta); event ProposalQueued(uint256 proposalId, uint256 eta);
function timelock() external view returns (address); function timelock() public view virtual returns (address);
function proposalEta(uint256 proposalId) external view returns (uint256); function proposalEta(uint256 proposalId) public view virtual returns (uint256);
function queue( function queue(
address[] calldata targets, address[] memory targets,
uint256[] calldata values, uint256[] memory values,
bytes[] calldata calldatas, bytes[] memory calldatas,
bytes32 descriptionHash bytes32 descriptionHash
) external returns (uint256 proposalId); ) public virtual returns (uint256 proposalId);
uint256[50] private __gap;
} }
...@@ -20,6 +20,7 @@ contract GovernorCompMockUpgradeable is Initializable, GovernorUpgradeable, Gove ...@@ -20,6 +20,7 @@ contract GovernorCompMockUpgradeable is Initializable, GovernorUpgradeable, Gove
__Context_init_unchained(); __Context_init_unchained();
__ERC165_init_unchained(); __ERC165_init_unchained();
__EIP712_init_unchained(name_, version()); __EIP712_init_unchained(name_, version());
__IGovernor_init_unchained();
__Governor_init_unchained(name_); __Governor_init_unchained(name_);
__GovernorVotesComp_init_unchained(token_); __GovernorVotesComp_init_unchained(token_);
__GovernorCountingSimple_init_unchained(); __GovernorCountingSimple_init_unchained();
...@@ -63,7 +64,7 @@ contract GovernorCompMockUpgradeable is Initializable, GovernorUpgradeable, Gove ...@@ -63,7 +64,7 @@ contract GovernorCompMockUpgradeable is Initializable, GovernorUpgradeable, Gove
public public
view view
virtual virtual
override(GovernorUpgradeable, GovernorVotesCompUpgradeable) override(IGovernorUpgradeable, GovernorVotesCompUpgradeable)
returns (uint256) returns (uint256)
{ {
return super.getVotes(account, blockNumber); return super.getVotes(account, blockNumber);
......
...@@ -23,7 +23,11 @@ contract GovernorCompatibilityBravoMockUpgradeable is Initializable, GovernorCom ...@@ -23,7 +23,11 @@ contract GovernorCompatibilityBravoMockUpgradeable is Initializable, GovernorCom
__Context_init_unchained(); __Context_init_unchained();
__ERC165_init_unchained(); __ERC165_init_unchained();
__EIP712_init_unchained(name_, version()); __EIP712_init_unchained(name_, version());
__IGovernor_init_unchained();
__IGovernorTimelock_init_unchained();
__IGovernorCompatibilityBravo_init_unchained();
__Governor_init_unchained(name_); __Governor_init_unchained(name_);
__GovernorProposalThreshold_init_unchained();
__GovernorCompatibilityBravo_init_unchained(); __GovernorCompatibilityBravo_init_unchained();
__GovernorTimelockCompound_init_unchained(timelock_); __GovernorTimelockCompound_init_unchained(timelock_);
__GovernorVotesComp_init_unchained(token_); __GovernorVotesComp_init_unchained(token_);
...@@ -53,11 +57,11 @@ contract GovernorCompatibilityBravoMockUpgradeable is Initializable, GovernorCom ...@@ -53,11 +57,11 @@ contract GovernorCompatibilityBravoMockUpgradeable is Initializable, GovernorCom
return super.supportsInterface(interfaceId); return super.supportsInterface(interfaceId);
} }
function votingDelay() public view override(IGovernorUpgradeable, GovernorUpgradeable) returns (uint256) { function votingDelay() public view override returns (uint256) {
return _votingDelay; return _votingDelay;
} }
function votingPeriod() public view override(IGovernorUpgradeable, GovernorUpgradeable) returns (uint256) { function votingPeriod() public view override returns (uint256) {
return _votingPeriod; return _votingPeriod;
} }
...@@ -65,7 +69,7 @@ contract GovernorCompatibilityBravoMockUpgradeable is Initializable, GovernorCom ...@@ -65,7 +69,7 @@ contract GovernorCompatibilityBravoMockUpgradeable is Initializable, GovernorCom
return _proposalThreshold; return _proposalThreshold;
} }
function quorum(uint256) public pure override(IGovernorUpgradeable, GovernorUpgradeable) returns (uint256) { function quorum(uint256) public pure override returns (uint256) {
return 0; return 0;
} }
...@@ -83,7 +87,7 @@ contract GovernorCompatibilityBravoMockUpgradeable is Initializable, GovernorCom ...@@ -83,7 +87,7 @@ contract GovernorCompatibilityBravoMockUpgradeable is Initializable, GovernorCom
public public
view view
virtual virtual
override(GovernorCompatibilityBravoUpgradeable, GovernorTimelockCompoundUpgradeable) override(IGovernorTimelockUpgradeable, GovernorTimelockCompoundUpgradeable)
returns (uint256) returns (uint256)
{ {
return super.proposalEta(proposalId); return super.proposalEta(proposalId);
...@@ -103,7 +107,7 @@ contract GovernorCompatibilityBravoMockUpgradeable is Initializable, GovernorCom ...@@ -103,7 +107,7 @@ contract GovernorCompatibilityBravoMockUpgradeable is Initializable, GovernorCom
uint256[] memory values, uint256[] memory values,
bytes[] memory calldatas, bytes[] memory calldatas,
bytes32 salt bytes32 salt
) public virtual override(GovernorCompatibilityBravoUpgradeable, GovernorTimelockCompoundUpgradeable) returns (uint256) { ) public virtual override(IGovernorTimelockUpgradeable, GovernorTimelockCompoundUpgradeable) returns (uint256) {
return super.queue(targets, values, calldatas, salt); return super.queue(targets, values, calldatas, salt);
} }
......
...@@ -21,6 +21,7 @@ contract GovernorMockUpgradeable is Initializable, GovernorUpgradeable, Governor ...@@ -21,6 +21,7 @@ contract GovernorMockUpgradeable is Initializable, GovernorUpgradeable, Governor
__Context_init_unchained(); __Context_init_unchained();
__ERC165_init_unchained(); __ERC165_init_unchained();
__EIP712_init_unchained(name_, version()); __EIP712_init_unchained(name_, version());
__IGovernor_init_unchained();
__Governor_init_unchained(name_); __Governor_init_unchained(name_);
__GovernorVotes_init_unchained(token_); __GovernorVotes_init_unchained(token_);
__GovernorVotesQuorumFraction_init_unchained(quorumNumerator_); __GovernorVotesQuorumFraction_init_unchained(quorumNumerator_);
...@@ -62,7 +63,7 @@ contract GovernorMockUpgradeable is Initializable, GovernorUpgradeable, Governor ...@@ -62,7 +63,7 @@ contract GovernorMockUpgradeable is Initializable, GovernorUpgradeable, Governor
public public
view view
virtual virtual
override(GovernorUpgradeable, GovernorVotesUpgradeable) override(IGovernorUpgradeable, GovernorVotesUpgradeable)
returns (uint256) returns (uint256)
{ {
return super.getVotes(account, blockNumber); return super.getVotes(account, blockNumber);
......
...@@ -22,6 +22,8 @@ contract GovernorTimelockCompoundMockUpgradeable is Initializable, GovernorTimel ...@@ -22,6 +22,8 @@ contract GovernorTimelockCompoundMockUpgradeable is Initializable, GovernorTimel
__Context_init_unchained(); __Context_init_unchained();
__ERC165_init_unchained(); __ERC165_init_unchained();
__EIP712_init_unchained(name_, version()); __EIP712_init_unchained(name_, version());
__IGovernor_init_unchained();
__IGovernorTimelock_init_unchained();
__Governor_init_unchained(name_); __Governor_init_unchained(name_);
__GovernorTimelockCompound_init_unchained(timelock_); __GovernorTimelockCompound_init_unchained(timelock_);
__GovernorVotes_init_unchained(token_); __GovernorVotes_init_unchained(token_);
...@@ -52,18 +54,18 @@ contract GovernorTimelockCompoundMockUpgradeable is Initializable, GovernorTimel ...@@ -52,18 +54,18 @@ contract GovernorTimelockCompoundMockUpgradeable is Initializable, GovernorTimel
return super.supportsInterface(interfaceId); return super.supportsInterface(interfaceId);
} }
function votingDelay() public view override(IGovernorUpgradeable, GovernorUpgradeable) returns (uint256) { function votingDelay() public view override returns (uint256) {
return _votingDelay; return _votingDelay;
} }
function votingPeriod() public view override(IGovernorUpgradeable, GovernorUpgradeable) returns (uint256) { function votingPeriod() public view override returns (uint256) {
return _votingPeriod; return _votingPeriod;
} }
function quorum(uint256 blockNumber) function quorum(uint256 blockNumber)
public public
view view
override(IGovernorUpgradeable, GovernorUpgradeable, GovernorVotesQuorumFractionUpgradeable) override(IGovernorUpgradeable, GovernorVotesQuorumFractionUpgradeable)
returns (uint256) returns (uint256)
{ {
return super.quorum(blockNumber); return super.quorum(blockNumber);
...@@ -114,7 +116,7 @@ contract GovernorTimelockCompoundMockUpgradeable is Initializable, GovernorTimel ...@@ -114,7 +116,7 @@ contract GovernorTimelockCompoundMockUpgradeable is Initializable, GovernorTimel
public public
view view
virtual virtual
override(IGovernorUpgradeable, GovernorUpgradeable, GovernorVotesUpgradeable) override(IGovernorUpgradeable, GovernorVotesUpgradeable)
returns (uint256) returns (uint256)
{ {
return super.getVotes(account, blockNumber); return super.getVotes(account, blockNumber);
......
...@@ -22,6 +22,8 @@ contract GovernorTimelockControlMockUpgradeable is Initializable, GovernorTimelo ...@@ -22,6 +22,8 @@ contract GovernorTimelockControlMockUpgradeable is Initializable, GovernorTimelo
__Context_init_unchained(); __Context_init_unchained();
__ERC165_init_unchained(); __ERC165_init_unchained();
__EIP712_init_unchained(name_, version()); __EIP712_init_unchained(name_, version());
__IGovernor_init_unchained();
__IGovernorTimelock_init_unchained();
__Governor_init_unchained(name_); __Governor_init_unchained(name_);
__GovernorTimelockControl_init_unchained(timelock_); __GovernorTimelockControl_init_unchained(timelock_);
__GovernorVotes_init_unchained(token_); __GovernorVotes_init_unchained(token_);
...@@ -52,18 +54,18 @@ contract GovernorTimelockControlMockUpgradeable is Initializable, GovernorTimelo ...@@ -52,18 +54,18 @@ contract GovernorTimelockControlMockUpgradeable is Initializable, GovernorTimelo
return super.supportsInterface(interfaceId); return super.supportsInterface(interfaceId);
} }
function votingDelay() public view override(IGovernorUpgradeable, GovernorUpgradeable) returns (uint256) { function votingDelay() public view override returns (uint256) {
return _votingDelay; return _votingDelay;
} }
function votingPeriod() public view override(IGovernorUpgradeable, GovernorUpgradeable) returns (uint256) { function votingPeriod() public view override returns (uint256) {
return _votingPeriod; return _votingPeriod;
} }
function quorum(uint256 blockNumber) function quorum(uint256 blockNumber)
public public
view view
override(IGovernorUpgradeable, GovernorUpgradeable, GovernorVotesQuorumFractionUpgradeable) override(IGovernorUpgradeable, GovernorVotesQuorumFractionUpgradeable)
returns (uint256) returns (uint256)
{ {
return super.quorum(blockNumber); return super.quorum(blockNumber);
...@@ -114,7 +116,7 @@ contract GovernorTimelockControlMockUpgradeable is Initializable, GovernorTimelo ...@@ -114,7 +116,7 @@ contract GovernorTimelockControlMockUpgradeable is Initializable, GovernorTimelo
public public
view view
virtual virtual
override(IGovernorUpgradeable, GovernorUpgradeable, GovernorVotesUpgradeable) override(IGovernorUpgradeable, GovernorVotesUpgradeable)
returns (uint256) returns (uint256)
{ {
return super.getVotes(account, blockNumber); return super.getVotes(account, blockNumber);
......
...@@ -14,4 +14,6 @@ ...@@ -14,4 +14,6 @@
** xref:erc777.adoc[ERC777] ** xref:erc777.adoc[ERC777]
** xref:erc1155.adoc[ERC1155] ** xref:erc1155.adoc[ERC1155]
* xref:governance.adoc[Governance]
* xref:utilities.adoc[Utilities] * xref:utilities.adoc[Utilities]
= How to set up on-chain governance
In this guide we will learn how OpenZeppelin’s Govenor contract works, how to set it up, and how to use it to create proposals, vote for them, and execute them, using tools provided by Ethers.js and Tally.
== Introduction
Decentralized protocols are in constant evolution from the moment they are publicly released. Often, the initial team retains control of this evolution in the first stages, but eventually delegates it to a community of stakeholders. The process by which this community makes decisions is called on-chain governance, and it has become a central component of decentralized protocols, fueling varied decisions such as parameter tweaking, smart contract upgrades, integrations with other protocols, treasury management, grants, etc.
This governance protocol is generally implemented in a special-purpose contract called “Governor”. The GovernorAlpha and GovernorBravo contracts designed by Compound have been very successful and popular so far, with the downside that projects with different requirements have had to fork the code to customize it for their needs, which can pose a high risk of introducing security issues. For OpenZeppelin Contracts, we set out to build a modular system of Governor contracts so that forking is not needed, and different requirements can be accommodated by writing small modules using Solidity inheritance. You will find the most common requirements out of the box in OpenZeppelin Contracts, but writing additional ones is simple, and we will be adding new features as requested by the community in future releases. Additionally, the design of OpenZeppelin Governor requires minimal use of storage and results in more gas efficient operation.
== Compatibility
OpenZeppelin’s Governor system was designed with a concern for compatibility with existing systems that were based on Compound’s GovernorAlpha and GovernorBravo. Because of this, you will find that many modules are presented in two variants, one of which is built for compatibility with those systems.
=== ERC20Votes & ERC20VotesComp
The ERC20 extension to keep track of votes and vote delegation is one such case. The shorter one is the more generic version because it can support token supplies greater than 2^96, while the “Comp” variant is limited in that regard, but exactly fits the interface of the COMP token that is used by GovernorAlpha and Bravo. Both contract variants share the same events, so they are fully compatible when looking at events only.
=== Governor & GovernorCompatibilityBravo
An OpenZeppelin Governor contract is by default not interface-compatible with GovernorAlpha or Bravo, since some of the functions are different or missing, although it shares all of the same events. However, it’s possible to opt in to full compatibility by inheriting from the GovernorCompatibilityBravo module. The contract will be cheaper to deploy and use without this module.
=== GovernorTimelockControl & GovernorTimelockCompound
When using a timelock with your Governor contract, you can use either OpenZeppelin’s TimelockController or Compound’s Timelock. Based on the choice of timelock, you should choose the corresponding Governor module: GovernorTimelockControl or GovernorTimelockCompound respectively. This allows you to migrate an existing GovernorAlpha instance to an OpenZeppelin-based Governor without changing the timelock in use.
=== Tally
Tally is a full-fledged application for user owned on-chain governance. It comprises a voting dashboard, proposal creation wizard, real time research and analysis, and educational content.
For all of these options, the Governor will be compatible with Tally: users will be able to create proposals, visualize voting power and advocates, navigate proposals, and cast votes. For proposal creation in particular, projects can also use Defender Admin as an alternative interface.
In the rest of this guide, we will focus on a fresh deploy of the vanilla OpenZeppelin Governor features without concern for compatibility with GovernorAlpha or Bravo.
== Setup
=== Token
The voting power of each account in our governance setup will be determined by an ERC20 token. The token has to implement the ERC20Votes extension. This extension will keep track of historical balances so that voting power is retrieved from past snapshots rather than current balance, which is an important protection that prevents double voting.
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.2;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/draft-ERC20Permit.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol";
contract MyToken is ERC20, ERC20Permit, ERC20Votes {
constructor() ERC20("MyToken", "MTK") ERC20Permit("MyToken") {}
// The functions below are overrides required by Solidity.
function _afterTokenTransfer(address from, address to, uint256 amount)
internal
override(ERC20, ERC20Votes)
{
super._afterTokenTransfer(from, to, amount);
}
function _mint(address to, uint256 amount)
internal
override(ERC20, ERC20Votes)
{
super._mint(to, amount);
}
function _burn(address account, uint256 amount)
internal
override(ERC20, ERC20Votes)
{
super._burn(account, amount);
}
}
```
If your project already has a live token that does not include ERC20Votes and is not upgradeable, you can wrap it in a governance token by using ERC20Wrapper. This will allow token holders to participate in governance by wrapping their tokens 1-to-1.
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.2;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/draft-ERC20Permit.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Wrapper.sol";
contract MyToken is ERC20, ERC20Permit, ERC20Votes, ERC20Wrapper {
constructor(IERC20 wrappedToken)
ERC20("MyToken", "MTK")
ERC20Permit("MyToken")
ERC20Wrapper(wrappedToken)
{}
// The functions below are overrides required by Solidity.
function _afterTokenTransfer(address from, address to, uint256 amount)
internal
override(ERC20, ERC20Votes)
{
super._afterTokenTransfer(from, to, amount);
}
function _mint(address to, uint256 amount)
internal
override(ERC20, ERC20Votes)
{
super._mint(to, amount);
}
function _burn(address account, uint256 amount)
internal
override(ERC20, ERC20Votes)
{
super._burn(account, amount);
}
}
```
NOTE: Voting power could be determined in different ways: multiple ERC20 tokens, ERC721 tokens, sybil resistant identities, etc. All of these options are potentially supported by writing a custom Votes module for your Governor.
=== Governor
Initially, we will build a Governor without a timelock. The core logic is given by the Governor contract, but we still need to choose: 1) how voting power is determined, 2) how many votes are needed for quorum, and 3) what options people have when casting a vote and how those votes are counted. Each of these aspects is customizable by writing your own module, or more easily choosing one from OpenZeppelin Contracts.
For 1) we will use the GovernorVotes module, which hooks to an ERC20Votes instance to determine the voting power of an account based on the token balance they hold when a proposal becomes active. This module requires as a constructor parameter the address of the token.
For 2) we will use GovernorVotesQuorumFraction which works together with ERC20Votes to define quorum as a percentage of the total supply at the block a proposal’s voting power is retrieved. This requires a constructor parameter to set the percentage. Most Governors nowadays use 4%, so we will initialize the module with parameter 4 (this indicates the percentage, resulting in 4%).
For 3) we will use GovernorCountingSimple, a module that offers 3 options to voters: For, Against, and Abstain, and where only For and Abstain votes are counted towards quorum.
Besides these modules, Governor itself has some parameters we must set.
votingDelay: How long after a proposal is created should voting power be fixed. A large voting delay gives users time to unstake tokens if necessary.
votingPeriod: How long does a proposal remain open to votes.
These parameters are specified in number of blocks. Assuming block time of around 13.14 seconds, we will set votingDelay = 1 day = 6570 blocks, and votingPeriod = 1 week = 45992 blocks.
We can optionally set a proposal threshold as well. This restricts proposal creation to accounts who have enough voting power.
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.2;
import "./governance/Governor.sol";
import "./governance/compatibility/GovernorCompatibilityBravo.sol";
import "./governance/extensions/GovernorVotes.sol";
import "./governance/extensions/GovernorVotesQuorumFraction.sol";
import "./governance/extensions/GovernorTimelockControl.sol";
contract MyGovernor is Governor, GovernorCompatibilityBravo, GovernorVotes, GovernorVotesQuorumFraction, GovernorTimelockControl {
constructor(ERC20Votes _token, TimelockController _timelock)
Governor("MyGovernor")
GovernorVotes(_token)
GovernorVotesQuorumFraction(4)
GovernorTimelockControl(_timelock)
{}
function votingDelay() public pure override returns (uint256) {
return 6575; // 1 day
}
function votingPeriod() public pure override returns (uint256) {
return 46027; // 1 week
}
function proposalThreshold() public pure override returns (uint256) {
return 0;
}
// The functions below are overrides required by Solidity.
function quorum(uint256 blockNumber)
public
view
override(IGovernor, GovernorVotesQuorumFraction)
returns (uint256)
{
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, IGovernor, GovernorTimelockControl)
returns (ProposalState)
{
return super.state(proposalId);
}
function propose(address[] memory targets, uint256[] memory values, bytes[] memory calldatas, string memory description)
public
override(Governor, GovernorCompatibilityBravo, IGovernor)
returns (uint256)
{
return super.propose(targets, values, calldatas, description);
}
function _execute(uint256 proposalId, address[] memory targets, uint256[] memory values, bytes[] memory calldatas, bytes32 descriptionHash)
internal
override(Governor, GovernorTimelockControl)
{
super._execute(proposalId, targets, values, calldatas, descriptionHash);
}
function _cancel(address[] memory targets, uint256[] memory values, bytes[] memory calldatas, bytes32 descriptionHash)
internal
override(Governor, GovernorTimelockControl)
returns (uint256)
{
return super._cancel(targets, values, calldatas, descriptionHash);
}
function _executor()
internal
view
override(Governor, GovernorTimelockControl)
returns (address)
{
return super._executor();
}
function supportsInterface(bytes4 interfaceId)
public
view
override(Governor, IERC165, GovernorTimelockControl)
returns (bool)
{
return super.supportsInterface(interfaceId);
}
}
```
=== Timelock
It is good practice to add a timelock to governance decisions. This allows users to exit the system if they disagree with a decision before it is executed. We will use OpenZeppelin’s TimelockController in combination with the GovernorTimelockControl module.
TimelockController uses an AccessControl setup that we need to understand in order to set up roles. The Proposer role is in charge of queueing operations: this is the role the Governor instance should be granted, and it should likely be the only proposer in the system. The Executor role is in charge of executing already available operations: we can assign this role to the special zero address to allow anyone to execute (if operations can be particularly time sensitive, the Governor should be made Executor instead). Lastly, there is the Admin role, which can grant and revoke the two previous roles: this is a very sensitive role that will be granted automatically to both deployer and timelock itself, but should be renounced by the deployer after setup.
== Proposal Lifecycle
Let’s walk through how to create and execute a proposal on our newly deployed Governor.
A proposal is a sequence of actions that the Governor contract will perform if it passes. Each action consists of a target address, calldata encoding a function call, and an amount of ETH to include. Additionally, a proposal includes a human-readable description.
=== Create a Proposal
Let’s say we want to create a proposal to give a team a grant, in the form of ERC20 tokens from the governance treasury. This proposal will consist of a single action where the target is the ERC20 token, calldata is the encoded function call `transfer(<team wallet>, <grant amount>)`, and with 0 ETH attached.
Generally a proposal will be created with the help of an interface such as Tally or Defender. Here we will show how to create the proposal using Ethers.js.
First we get all the parameters necessary for the proposal action.
```javascript
const tokenAddress = ...;
const token = await ethers.getContractAt(‘ERC20’, tokenAddress);
const teamAddress = ...;
const grantAmount = ...;
const transferCalldata = token.interface.encodeFunctionData(‘transfer’, [teamAddress, grantAmount]);
```
Now we are ready to call the propose function of the governor. Note that we don’t pass in one array of actions, but instead three arrays corresponding to the list of targets, the list of values, and the list of calldatas. In this case it’s a single action, so it’s simple:
```javascript
await governor.propose(
[tokenAddress],
[0],
[transferCalldata],
“Proposal #1: Give grant to team”,
);
```
This will create a new proposal, with a proposal id that is obtained by hashing together the proposal data, and which will also be found in an event in the logs of the transaction.
=== Cast a Vote
Once a proposal is active, stakeholders can cast their vote. This is done through a function in the Governor contract that users can invoke directly from a governance UI such as Tally.
image::tally-vote.png[Voting in Tally]
=== Execute the Proposal
Once the voting period is over, if quorum was reached (enough voting power participated) and the majority voted in favor, the proposal is considered successful and can proceed to be executed. This can also be done in Tally in the "Administration Panel" section of a project.
image::tally-admin.png[Administration Panel in Tally]
We will see now how to do this manually using Ethers.js.
If a timelock was set up, the first step to execution is queueing. You will notice that both the queue and execute functions require passing in all of the proposal parameters, as opposed to just the proposal id. This is necessary because this data is not stored on chain, as a measure to save gas. Note that these parameters can always be found in the events emitted by the contract. The only parameter that is not sent in its entirety is the description, since this is only needed in its hashed form to compute the proposal id.
To queue, we call the queue function:
```javascript
const descriptionHash = ethers.utils.id(“Proposal #1: Give grant to team”);
await governor.queue(
[tokenAddress],
[0],
[transferCalldata],
descriptionHash,
);
```
This will cause the governor to interact with the timelock contract and queue the actions for execution after the required delay.
After enough time has passed (according to the timelock parameters), the proposal can be executed. If there was no timelock to begin with, this step can be ran immediately after the proposal succeeds.
```javascript
await governor.execute(
[tokenAddress],
[0],
[transferCalldata],
descriptionHash,
);
```
Executing the proposal will transfer the ERC20 tokens to the chosen recipient. To wrap up: we set up a system where a treasury is controlled by the collective decision of the token holders of a project, and all actions are executed via proposals enforced by on-chain votes.
...@@ -7,9 +7,28 @@ const fs = require('fs'); ...@@ -7,9 +7,28 @@ const fs = require('fs');
const path = require('path'); const path = require('path');
const argv = require('yargs/yargs')() const argv = require('yargs/yargs')()
.env('') .env('')
.boolean('enableGasReport') .options({
.boolean('ci') ci: {
.string('compileMode') type: 'boolean',
default: false,
},
gas: {
alias: 'enableGasReport',
type: 'boolean',
default: false,
},
mode: {
alias: 'compileMode',
type: 'string',
choices: [ 'production', 'development' ],
default: 'development',
},
compiler: {
alias: 'compileVersion',
type: 'string',
default: '0.8.3',
},
})
.argv; .argv;
require('@nomiclabs/hardhat-truffle5'); require('@nomiclabs/hardhat-truffle5');
...@@ -30,7 +49,7 @@ const withOptimizations = argv.enableGasReport || argv.compileMode === 'producti ...@@ -30,7 +49,7 @@ const withOptimizations = argv.enableGasReport || argv.compileMode === 'producti
*/ */
module.exports = { module.exports = {
solidity: { solidity: {
version: '0.8.3', version: argv.compiler,
settings: { settings: {
optimizer: { optimizer: {
enabled: withOptimizations, enabled: withOptimizations,
......
...@@ -7,9 +7,9 @@ index 1f6895a6..86518b84 100644 ...@@ -7,9 +7,9 @@ index 1f6895a6..86518b84 100644
__Context_init_unchained(); __Context_init_unchained();
__ERC165_init_unchained(); __ERC165_init_unchained();
- __EIP712_init_unchained(name_, version()); - __EIP712_init_unchained(name_, version());
__GovernorCompatibilityBravo_init_unchained(); __IGovernor_init_unchained();
} __IGovernorTimelock_init_unchained();
__IGovernorCompatibilityBravo_init_unchained();
diff --git a/contracts/governance/extensions/GovernorCountingSimpleUpgradeable.sol b/contracts/governance/extensions/GovernorCountingSimpleUpgradeable.sol diff --git a/contracts/governance/extensions/GovernorCountingSimpleUpgradeable.sol b/contracts/governance/extensions/GovernorCountingSimpleUpgradeable.sol
index 4873166b..6a88e6b4 100644 index 4873166b..6a88e6b4 100644
--- a/contracts/governance/extensions/GovernorCountingSimpleUpgradeable.sol --- a/contracts/governance/extensions/GovernorCountingSimpleUpgradeable.sol
...@@ -19,9 +19,9 @@ index 4873166b..6a88e6b4 100644 ...@@ -19,9 +19,9 @@ index 4873166b..6a88e6b4 100644
__Context_init_unchained(); __Context_init_unchained();
__ERC165_init_unchained(); __ERC165_init_unchained();
- __EIP712_init_unchained(name_, version()); - __EIP712_init_unchained(name_, version());
__IGovernor_init_unchained();
__GovernorCountingSimple_init_unchained(); __GovernorCountingSimple_init_unchained();
} }
diff --git a/contracts/governance/extensions/GovernorTimelockCompoundUpgradeable.sol b/contracts/governance/extensions/GovernorTimelockCompoundUpgradeable.sol diff --git a/contracts/governance/extensions/GovernorTimelockCompoundUpgradeable.sol b/contracts/governance/extensions/GovernorTimelockCompoundUpgradeable.sol
index c6ed355a..9236c546 100644 index c6ed355a..9236c546 100644
--- a/contracts/governance/extensions/GovernorTimelockCompoundUpgradeable.sol --- a/contracts/governance/extensions/GovernorTimelockCompoundUpgradeable.sol
...@@ -31,9 +31,9 @@ index c6ed355a..9236c546 100644 ...@@ -31,9 +31,9 @@ index c6ed355a..9236c546 100644
__Context_init_unchained(); __Context_init_unchained();
__ERC165_init_unchained(); __ERC165_init_unchained();
- __EIP712_init_unchained(name_, version()); - __EIP712_init_unchained(name_, version());
__IGovernor_init_unchained();
__IGovernorTimelock_init_unchained();
__GovernorTimelockCompound_init_unchained(timelockAddress); __GovernorTimelockCompound_init_unchained(timelockAddress);
}
diff --git a/contracts/governance/extensions/GovernorTimelockControlUpgradeable.sol b/contracts/governance/extensions/GovernorTimelockControlUpgradeable.sol diff --git a/contracts/governance/extensions/GovernorTimelockControlUpgradeable.sol b/contracts/governance/extensions/GovernorTimelockControlUpgradeable.sol
index 3d6a5de5..ad5f505e 100644 index 3d6a5de5..ad5f505e 100644
--- a/contracts/governance/extensions/GovernorTimelockControlUpgradeable.sol --- a/contracts/governance/extensions/GovernorTimelockControlUpgradeable.sol
...@@ -43,9 +43,9 @@ index 3d6a5de5..ad5f505e 100644 ...@@ -43,9 +43,9 @@ index 3d6a5de5..ad5f505e 100644
__Context_init_unchained(); __Context_init_unchained();
__ERC165_init_unchained(); __ERC165_init_unchained();
- __EIP712_init_unchained(name_, version()); - __EIP712_init_unchained(name_, version());
__IGovernor_init_unchained();
__IGovernorTimelock_init_unchained();
__GovernorTimelockControl_init_unchained(timelockAddress); __GovernorTimelockControl_init_unchained(timelockAddress);
}
diff --git a/contracts/governance/extensions/GovernorVotesCompUpgradeable.sol b/contracts/governance/extensions/GovernorVotesCompUpgradeable.sol diff --git a/contracts/governance/extensions/GovernorVotesCompUpgradeable.sol b/contracts/governance/extensions/GovernorVotesCompUpgradeable.sol
index cc83b3ed..5398f15b 100644 index cc83b3ed..5398f15b 100644
--- a/contracts/governance/extensions/GovernorVotesCompUpgradeable.sol --- a/contracts/governance/extensions/GovernorVotesCompUpgradeable.sol
...@@ -55,9 +55,9 @@ index cc83b3ed..5398f15b 100644 ...@@ -55,9 +55,9 @@ index cc83b3ed..5398f15b 100644
__Context_init_unchained(); __Context_init_unchained();
__ERC165_init_unchained(); __ERC165_init_unchained();
- __EIP712_init_unchained(name_, version()); - __EIP712_init_unchained(name_, version());
__IGovernor_init_unchained();
__GovernorVotesComp_init_unchained(token_); __GovernorVotesComp_init_unchained(token_);
} }
diff --git a/contracts/governance/extensions/GovernorVotesQuorumFractionUpgradeable.sol b/contracts/governance/extensions/GovernorVotesQuorumFractionUpgradeable.sol diff --git a/contracts/governance/extensions/GovernorVotesQuorumFractionUpgradeable.sol b/contracts/governance/extensions/GovernorVotesQuorumFractionUpgradeable.sol
index 5d7a88bc..39f97903 100644 index 5d7a88bc..39f97903 100644
--- a/contracts/governance/extensions/GovernorVotesQuorumFractionUpgradeable.sol --- a/contracts/governance/extensions/GovernorVotesQuorumFractionUpgradeable.sol
...@@ -67,9 +67,9 @@ index 5d7a88bc..39f97903 100644 ...@@ -67,9 +67,9 @@ index 5d7a88bc..39f97903 100644
__Context_init_unchained(); __Context_init_unchained();
__ERC165_init_unchained(); __ERC165_init_unchained();
- __EIP712_init_unchained(name_, version()); - __EIP712_init_unchained(name_, version());
__IGovernor_init_unchained();
__GovernorVotesQuorumFraction_init_unchained(quorumNumeratorValue); __GovernorVotesQuorumFraction_init_unchained(quorumNumeratorValue);
} }
diff --git a/contracts/governance/extensions/GovernorVotesUpgradeable.sol b/contracts/governance/extensions/GovernorVotesUpgradeable.sol diff --git a/contracts/governance/extensions/GovernorVotesUpgradeable.sol b/contracts/governance/extensions/GovernorVotesUpgradeable.sol
index cdfd0ae7..48408d9c 100644 index cdfd0ae7..48408d9c 100644
--- a/contracts/governance/extensions/GovernorVotesUpgradeable.sol --- a/contracts/governance/extensions/GovernorVotesUpgradeable.sol
...@@ -79,6 +79,6 @@ index cdfd0ae7..48408d9c 100644 ...@@ -79,6 +79,6 @@ index cdfd0ae7..48408d9c 100644
__Context_init_unchained(); __Context_init_unchained();
__ERC165_init_unchained(); __ERC165_init_unchained();
- __EIP712_init_unchained(name_, version()); - __EIP712_init_unchained(name_, version());
__IGovernor_init_unchained();
__GovernorVotes_init_unchained(tokenAddress); __GovernorVotes_init_unchained(tokenAddress);
} }
...@@ -29,7 +29,7 @@ contract('Governor', function (accounts) { ...@@ -29,7 +29,7 @@ contract('Governor', function (accounts) {
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, 0); this.mock = await Governor.new(name, this.token.address, 4, 16, 10);
this.receiver = await CallReceiver.new(); this.receiver = await CallReceiver.new();
await this.token.mint(owner, tokenSupply); await this.token.mint(owner, tokenSupply);
await this.token.delegate(voter1, { from: voter1 }); await this.token.delegate(voter1, { from: voter1 });
...@@ -72,7 +72,7 @@ contract('Governor', function (accounts) { ...@@ -72,7 +72,7 @@ contract('Governor', function (accounts) {
tokenHolder: owner, tokenHolder: owner,
voters: [ voters: [
{ voter: voter1, weight: web3.utils.toWei('1'), support: Enums.VoteType.For, reason: 'This is nice' }, { voter: voter1, weight: web3.utils.toWei('1'), support: Enums.VoteType.For, reason: 'This is nice' },
{ voter: voter2, weight: web3.utils.toWei('10'), support: Enums.VoteType.For }, { voter: voter2, weight: web3.utils.toWei('7'), support: Enums.VoteType.For },
{ voter: voter3, weight: web3.utils.toWei('5'), support: Enums.VoteType.Against }, { voter: voter3, weight: web3.utils.toWei('5'), support: Enums.VoteType.Against },
{ voter: voter4, weight: web3.utils.toWei('2'), support: Enums.VoteType.Abstain }, { voter: voter4, weight: web3.utils.toWei('2'), support: Enums.VoteType.Abstain },
], ],
...@@ -194,7 +194,7 @@ contract('Governor', function (accounts) { ...@@ -194,7 +194,7 @@ contract('Governor', function (accounts) {
], ],
tokenHolder: owner, tokenHolder: owner,
voters: [ voters: [
{ voter: this.voter, signature, weight: web3.utils.toWei('1'), support: Enums.VoteType.For }, { voter: this.voter, signature, weight: web3.utils.toWei('10'), support: Enums.VoteType.For },
], ],
}; };
}); });
...@@ -252,8 +252,8 @@ contract('Governor', function (accounts) { ...@@ -252,8 +252,8 @@ contract('Governor', function (accounts) {
], ],
tokenHolder: owner, tokenHolder: owner,
voters: [ voters: [
{ voter: voter1, weight: web3.utils.toWei('1'), support: Enums.VoteType.For }, { voter: voter1, weight: web3.utils.toWei('5'), support: Enums.VoteType.For },
{ voter: voter2, weight: web3.utils.toWei('1'), support: Enums.VoteType.Abstain }, { voter: voter2, weight: web3.utils.toWei('5'), support: Enums.VoteType.Abstain },
], ],
}; };
}); });
...@@ -285,7 +285,7 @@ contract('Governor', function (accounts) { ...@@ -285,7 +285,7 @@ contract('Governor', function (accounts) {
], ],
tokenHolder: owner, tokenHolder: owner,
voters: [ voters: [
{ voter: voter1, weight: web3.utils.toWei('1'), support: Enums.VoteType.For }, { voter: voter1, weight: web3.utils.toWei('10'), support: Enums.VoteType.For },
], ],
steps: { steps: {
execute: { error: 'Governor: call reverted without message' }, execute: { error: 'Governor: call reverted without message' },
...@@ -306,7 +306,7 @@ contract('Governor', function (accounts) { ...@@ -306,7 +306,7 @@ contract('Governor', function (accounts) {
], ],
tokenHolder: owner, tokenHolder: owner,
voters: [ voters: [
{ voter: voter1, weight: web3.utils.toWei('1'), support: Enums.VoteType.For }, { voter: voter1, weight: web3.utils.toWei('10'), support: Enums.VoteType.For },
], ],
steps: { steps: {
execute: { error: 'CallReceiverMock: reverting' }, execute: { error: 'CallReceiverMock: reverting' },
...@@ -329,13 +329,13 @@ contract('Governor', function (accounts) { ...@@ -329,13 +329,13 @@ contract('Governor', function (accounts) {
voters: [ voters: [
{ {
voter: voter1, voter: voter1,
weight: web3.utils.toWei('1'), weight: web3.utils.toWei('5'),
support: Enums.VoteType.For, support: Enums.VoteType.For,
error: 'Governor: unknown proposal id', error: 'Governor: unknown proposal id',
}, },
{ {
voter: voter2, voter: voter2,
weight: web3.utils.toWei('1'), weight: web3.utils.toWei('5'),
support: Enums.VoteType.Abstain, support: Enums.VoteType.Abstain,
error: 'Governor: unknown proposal id', error: 'Governor: unknown proposal id',
}, },
...@@ -382,8 +382,8 @@ contract('Governor', function (accounts) { ...@@ -382,8 +382,8 @@ contract('Governor', function (accounts) {
], ],
tokenHolder: owner, tokenHolder: owner,
voters: [ voters: [
{ voter: voter1, weight: web3.utils.toWei('1'), support: Enums.VoteType.For }, { voter: voter1, weight: web3.utils.toWei('5'), support: Enums.VoteType.For },
{ voter: voter2, weight: web3.utils.toWei('1'), support: Enums.VoteType.Abstain }, { voter: voter2, weight: web3.utils.toWei('5'), support: Enums.VoteType.Abstain },
], ],
}; };
}); });
...@@ -406,7 +406,7 @@ contract('Governor', function (accounts) { ...@@ -406,7 +406,7 @@ contract('Governor', function (accounts) {
voters: [ voters: [
{ {
voter: voter1, voter: voter1,
weight: web3.utils.toWei('1'), weight: web3.utils.toWei('10'),
support: new BN('255'), support: new BN('255'),
error: 'GovernorVotingSimple: invalid value for enum VoteType', error: 'GovernorVotingSimple: invalid value for enum VoteType',
}, },
...@@ -433,12 +433,12 @@ contract('Governor', function (accounts) { ...@@ -433,12 +433,12 @@ contract('Governor', function (accounts) {
voters: [ voters: [
{ {
voter: voter1, voter: voter1,
weight: web3.utils.toWei('1'), weight: web3.utils.toWei('5'),
support: Enums.VoteType.For, support: Enums.VoteType.For,
}, },
{ {
voter: voter1, voter: voter1,
weight: web3.utils.toWei('1'), weight: web3.utils.toWei('5'),
support: Enums.VoteType.For, support: Enums.VoteType.For,
error: 'GovernorVotingSimple: vote already casted', error: 'GovernorVotingSimple: vote already casted',
}, },
...@@ -459,7 +459,9 @@ contract('Governor', function (accounts) { ...@@ -459,7 +459,9 @@ contract('Governor', function (accounts) {
], ],
tokenHolder: owner, tokenHolder: owner,
voters: [ voters: [
{ voter: voter1, weight: web3.utils.toWei('0'), support: Enums.VoteType.For }, { 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: { steps: {
execute: { error: 'Governor: proposal not successful' }, execute: { error: 'Governor: proposal not successful' },
...@@ -480,7 +482,7 @@ contract('Governor', function (accounts) { ...@@ -480,7 +482,7 @@ contract('Governor', function (accounts) {
], ],
tokenHolder: owner, tokenHolder: owner,
voters: [ voters: [
{ voter: voter1, weight: web3.utils.toWei('1'), support: Enums.VoteType.Against }, { voter: voter1, weight: web3.utils.toWei('10'), support: Enums.VoteType.Against },
], ],
steps: { steps: {
execute: { error: 'Governor: proposal not successful' }, execute: { error: 'Governor: proposal not successful' },
...@@ -501,7 +503,7 @@ contract('Governor', function (accounts) { ...@@ -501,7 +503,7 @@ contract('Governor', function (accounts) {
], ],
tokenHolder: owner, tokenHolder: owner,
voters: [ voters: [
{ voter: voter1, weight: web3.utils.toWei('1'), support: Enums.VoteType.For }, { voter: voter1, weight: web3.utils.toWei('10'), support: Enums.VoteType.For },
], ],
steps: { steps: {
wait: { enable: false }, wait: { enable: false },
...@@ -593,7 +595,7 @@ contract('Governor', function (accounts) { ...@@ -593,7 +595,7 @@ contract('Governor', function (accounts) {
], ],
tokenHolder: owner, tokenHolder: owner,
voters: [ voters: [
{ voter: voter1, weight: web3.utils.toWei('1'), support: Enums.VoteType.For }, { voter: voter1, weight: web3.utils.toWei('10'), support: Enums.VoteType.For },
], ],
steps: { steps: {
execute: { enable: false }, execute: { enable: false },
...@@ -617,7 +619,7 @@ contract('Governor', function (accounts) { ...@@ -617,7 +619,7 @@ contract('Governor', function (accounts) {
], ],
tokenHolder: owner, tokenHolder: owner,
voters: [ voters: [
{ voter: voter1, weight: web3.utils.toWei('1'), support: Enums.VoteType.For }, { voter: voter1, weight: web3.utils.toWei('10'), support: Enums.VoteType.For },
], ],
}; };
}); });
...@@ -692,7 +694,7 @@ contract('Governor', function (accounts) { ...@@ -692,7 +694,7 @@ contract('Governor', function (accounts) {
], ],
tokenHolder: owner, tokenHolder: owner,
voters: [ voters: [
{ voter: voter1, weight: web3.utils.toWei('1'), support: Enums.VoteType.For }, { voter: voter1, weight: web3.utils.toWei('10'), support: Enums.VoteType.For },
], ],
steps: { steps: {
wait: { enable: false }, wait: { enable: false },
...@@ -723,7 +725,7 @@ contract('Governor', function (accounts) { ...@@ -723,7 +725,7 @@ contract('Governor', function (accounts) {
], ],
tokenHolder: owner, tokenHolder: owner,
voters: [ voters: [
{ voter: voter1, weight: web3.utils.toWei('1'), support: Enums.VoteType.For }, { voter: voter1, weight: web3.utils.toWei('10'), support: Enums.VoteType.For },
], ],
steps: { steps: {
execute: { enable: false }, execute: { enable: false },
...@@ -753,7 +755,7 @@ contract('Governor', function (accounts) { ...@@ -753,7 +755,7 @@ contract('Governor', function (accounts) {
], ],
tokenHolder: owner, tokenHolder: owner,
voters: [ voters: [
{ voter: voter1, weight: web3.utils.toWei('1'), support: Enums.VoteType.For }, { voter: voter1, weight: web3.utils.toWei('10'), support: Enums.VoteType.For },
], ],
}; };
}); });
......
...@@ -215,6 +215,51 @@ contract('GovernorCompatibilityBravo', function (accounts) { ...@@ -215,6 +215,51 @@ contract('GovernorCompatibilityBravo', function (accounts) {
runGovernorWorkflow(); runGovernorWorkflow();
}); });
describe('cancel', function () {
beforeEach(async function () {
this.settings = {
proposal: [
[ this.receiver.address ], // targets
[ web3.utils.toWei('0') ], // values
[ this.receiver.contract.methods.mockFunction().encodeABI() ], // calldatas
'<proposal description>', // description
],
proposer,
tokenHolder: owner,
steps: {
wait: { enable: false },
queue: { enable: false },
execute: { enable: false },
},
};
});
describe('by proposer', function () {
afterEach(async function () {
await this.mock.cancel(this.id, { from: proposer });
});
runGovernorWorkflow();
});
describe('if proposer below threshold', function () {
afterEach(async function () {
await this.token.transfer(voter1, web3.utils.toWei('1'), { from: proposer });
await this.mock.cancel(this.id);
});
runGovernorWorkflow();
});
describe('not if proposer above threshold', function () {
afterEach(async function () {
await expectRevert(
this.mock.cancel(this.id),
'GovernorBravo: proposer above threshold',
);
});
runGovernorWorkflow();
});
});
describe('with compatibility interface', function () { describe('with compatibility interface', function () {
beforeEach(async function () { beforeEach(async function () {
this.settings = { this.settings = {
......
...@@ -678,7 +678,7 @@ function shouldBehaveLikeERC721 (errorPrefix, owner, newOwner, approved, another ...@@ -678,7 +678,7 @@ function shouldBehaveLikeERC721 (errorPrefix, owner, newOwner, approved, another
describe('_burn', function () { describe('_burn', function () {
it('reverts when burning a non-existent token id', async function () { it('reverts when burning a non-existent token id', async function () {
await expectRevert( await expectRevert(
this.token.burn(firstTokenId), 'ERC721: owner query for nonexistent token', this.token.burn(nonExistentTokenId), 'ERC721: owner query for nonexistent token',
); );
}); });
......
...@@ -100,7 +100,7 @@ function shouldSupportInterfaces (interfaces = []) { ...@@ -100,7 +100,7 @@ function shouldSupportInterfaces (interfaces = []) {
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('claims support [skip-on-coverage]', async function () {
expect(await this.contractUnderTest.supportsInterface(interfaceId)).to.equal(true); expect(await this.contractUnderTest.supportsInterface(interfaceId)).to.equal(true);
}); });
}); });
......
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