ソースを参照

Improve Governor (#2794)

Co-authored-by: Hadrien Croubois <hadrien.croubois@gmail.com>
Francisco Giordano 4 年 前
コミット
4b152bd8ce

+ 0 - 20
contracts/governance/Governor.sol

@@ -136,26 +136,6 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor {
         return _proposals[proposalId].voteEnd.getDeadline();
     }
 
-    /**
-     * @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.
      */

+ 18 - 18
contracts/governance/IGovernor.sol

@@ -9,7 +9,7 @@ import "../utils/introspection/ERC165.sol";
  *
  * _Available since v4.3._
  */
-interface IGovernor is IERC165 {
+abstract contract IGovernor is IERC165 {
     enum ProposalState {
         Pending,
         Active,
@@ -57,13 +57,13 @@ interface IGovernor is IERC165 {
      * @notice module:core
      * @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
      * @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
@@ -82,7 +82,7 @@ interface IGovernor is IERC165 {
      * JavaScript class.
      */
     // 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
@@ -93,32 +93,32 @@ interface IGovernor is IERC165 {
         uint256[] calldata values,
         bytes[] calldata calldatas,
         bytes32 descriptionHash
-    ) external pure returns (uint256);
+    ) public pure virtual returns (uint256);
 
     /**
      * @notice module:core
      * @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
      * @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
      * @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
      * @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.
      */
-    function votingDelay() external view returns (uint256);
+    function votingDelay() public view virtual returns (uint256);
 
     /**
      * @notice module:user-config
@@ -127,7 +127,7 @@ interface IGovernor is IERC165 {
      * Note: the {votingDelay} can delay the start of the vote. This must be considered when setting the voting
      * duration compared to the voting delay.
      */
-    function votingPeriod() external view returns (uint256);
+    function votingPeriod() public view virtual returns (uint256);
 
     /**
      * @notice module:user-config
@@ -136,7 +136,7 @@ interface IGovernor is IERC165 {
      * 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}).
      */
-    function quorum(uint256 blockNumber) external view returns (uint256);
+    function quorum(uint256 blockNumber) public view virtual returns (uint256);
 
     /**
      * @notice module:reputation
@@ -145,13 +145,13 @@ interface IGovernor is IERC165 {
      * Note: this can be implemented in a number of ways, for example by reading the delegated balance from one (or
      * 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
      * @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
@@ -164,7 +164,7 @@ interface IGovernor is IERC165 {
         uint256[] memory values,
         bytes[] memory calldatas,
         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
@@ -179,14 +179,14 @@ interface IGovernor is IERC165 {
         uint256[] memory values,
         bytes[] memory calldatas,
         bytes32 descriptionHash
-    ) external payable returns (uint256 proposalId);
+    ) public payable virtual returns (uint256 proposalId);
 
     /**
      * @dev Cast a vote
      *
      * 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
@@ -197,7 +197,7 @@ interface IGovernor is IERC165 {
         uint256 proposalId,
         uint8 support,
         string calldata reason
-    ) external returns (uint256 balance);
+    ) public virtual returns (uint256 balance);
 
     /**
      * @dev Cast a vote using the user cryptographic signature.
@@ -210,5 +210,5 @@ interface IGovernor is IERC165 {
         uint8 v,
         bytes32 r,
         bytes32 s
-    ) external returns (uint256 balance);
+    ) public virtual returns (uint256 balance);
 }

+ 50 - 34
contracts/governance/compatibility/GovernorCompatibilityBravo.sol

@@ -5,6 +5,7 @@ pragma solidity ^0.8.0;
 import "../../utils/Counters.sol";
 import "../../utils/math/SafeCast.sol";
 import "../extensions/IGovernorTimelock.sol";
+import "../extensions/GovernorProposalThreshold.sol";
 import "../Governor.sol";
 import "./IGovernorCompatibilityBravo.sol";
 
@@ -16,7 +17,12 @@ import "./IGovernorCompatibilityBravo.sol";
  *
  * _Available since v4.3._
  */
-abstract contract GovernorCompatibilityBravo is IGovernorTimelock, IGovernorCompatibilityBravo, Governor {
+abstract contract GovernorCompatibilityBravo is
+    IGovernorTimelock,
+    IGovernorCompatibilityBravo,
+    Governor,
+    GovernorProposalThreshold
+{
     using Counters for Counters.Counter;
     using Timers for Timers.BlockNumber;
 
@@ -41,20 +47,6 @@ abstract contract GovernorCompatibilityBravo is IGovernorTimelock, IGovernorComp
 
     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
     function COUNTING_MODE() public pure virtual override returns (string memory) {
         return "support=bravo&quorum=bravo";
@@ -69,8 +61,9 @@ abstract contract GovernorCompatibilityBravo is IGovernorTimelock, IGovernorComp
         uint256[] memory values,
         bytes[] memory calldatas,
         string memory description
-    ) public virtual override(IGovernor, Governor) returns (uint256) {
-        return propose(targets, values, new string[](calldatas.length), calldatas, description);
+    ) public virtual override(IGovernor, Governor, GovernorProposalThreshold) returns (uint256) {
+        _storeProposal(_msgSender(), targets, values, new string[](calldatas.length), calldatas, description);
+        return super.propose(targets, values, calldatas, description);
     }
 
     /**
@@ -83,14 +76,8 @@ abstract contract GovernorCompatibilityBravo is IGovernorTimelock, IGovernorComp
         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"
-        );
-
-        uint256 proposalId = super.propose(targets, values, _encodeCalldata(signatures, calldatas), description);
-        _storeProposal(proposalId, _msgSender(), targets, values, signatures, calldatas, description);
-        return proposalId;
+        _storeProposal(_msgSender(), targets, values, signatures, calldatas, description);
+        return propose(targets, values, _encodeCalldata(signatures, calldatas), description);
     }
 
     /**
@@ -119,6 +106,22 @@ abstract contract GovernorCompatibilityBravo is IGovernorTimelock, IGovernorComp
         );
     }
 
+    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.
      */
@@ -132,7 +135,7 @@ abstract contract GovernorCompatibilityBravo is IGovernorTimelock, IGovernorComp
         for (uint256 i = 0; i < signatures.length; ++i) {
             fullcalldatas[i] = bytes(signatures[i]).length == 0
                 ? calldatas[i]
-                : abi.encodePacked(bytes4(keccak256(bytes(signatures[i]))), calldatas[i]);
+                : abi.encodeWithSignature(signatures[i], calldatas[i]);
         }
 
         return fullcalldatas;
@@ -142,7 +145,6 @@ abstract contract GovernorCompatibilityBravo is IGovernorTimelock, IGovernorComp
      * @dev Store proposal metadata for later lookup
      */
     function _storeProposal(
-        uint256 proposalId,
         address proposer,
         address[] memory targets,
         uint256[] memory values,
@@ -150,17 +152,31 @@ abstract contract GovernorCompatibilityBravo is IGovernorTimelock, IGovernorComp
         bytes[] memory calldatas,
         string memory description
     ) private {
-        ProposalDetails storage details = _proposalDetails[proposalId];
+        bytes32 descriptionHash = keccak256(bytes(description));
+        uint256 proposalId = hashProposal(targets, values, _encodeCalldata(signatures, calldatas), descriptionHash);
 
-        details.proposer = proposer;
-        details.targets = targets;
-        details.values = values;
-        details.signatures = signatures;
-        details.calldatas = calldatas;
-        details.descriptionHash = keccak256(bytes(description));
+        ProposalDetails storage details = _proposalDetails[proposalId];
+        if (details.descriptionHash == bytes32(0)) {
+            details.proposer = proposer;
+            details.targets = targets;
+            details.values = values;
+            details.signatures = signatures;
+            details.calldatas = calldatas;
+            details.descriptionHash = descriptionHash;
+        }
     }
 
     // ==================================================== 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(IGovernorCompatibilityBravo, GovernorProposalThreshold)
+        returns (uint256);
+
     /**
      * @dev See {IGovernorCompatibilityBravo-proposals}.
      */

+ 16 - 9
contracts/governance/compatibility/IGovernorCompatibilityBravo.sol

@@ -9,7 +9,7 @@ import "../IGovernor.sol";
  *
  * _Available since v4.3._
  */
-interface IGovernorCompatibilityBravo is IGovernor {
+abstract contract IGovernorCompatibilityBravo is IGovernor {
     /**
      * @dev Proposal structure from Compound Governor Bravo. Not actually used by the compatibility layer, as
      * {{proposal}} returns a very different structure.
@@ -44,14 +44,15 @@ interface IGovernorCompatibilityBravo is IGovernor {
     /**
      * @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"_.
      */
     function proposals(uint256)
-        external
+        public
         view
+        virtual
         returns (
             uint256 id,
             address proposer,
@@ -74,24 +75,30 @@ interface IGovernorCompatibilityBravo is IGovernor {
         string[] memory signatures,
         bytes[] memory calldatas,
         string memory description
-    ) external returns (uint256);
+    ) public virtual returns (uint256);
 
     /**
      * @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"_.
      */
-    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"_.
      */
     function getActions(uint256 proposalId)
-        external
+        public
         view
+        virtual
         returns (
             address[] memory targets,
             uint256[] memory values,
@@ -102,10 +109,10 @@ interface IGovernorCompatibilityBravo is IGovernor {
     /**
      * @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"_.
      */
-    function proposalThreshold() external view returns (uint256);
+    function proposalThreshold() public view virtual returns (uint256);
 }

+ 1 - 3
contracts/governance/extensions/GovernorCountingSimple.sol

@@ -66,9 +66,7 @@ abstract contract GovernorCountingSimple is Governor {
     function _quorumReached(uint256 proposalId) internal view virtual override returns (bool) {
         ProposalVote storage proposalvote = _proposalVotes[proposalId];
 
-        return
-            quorum(proposalSnapshot(proposalId)) <=
-            proposalvote.againstVotes + proposalvote.forVotes + proposalvote.abstainVotes;
+        return quorum(proposalSnapshot(proposalId)) <= proposalvote.forVotes + proposalvote.abstainVotes;
     }
 
     /**

+ 34 - 0
contracts/governance/extensions/GovernorProposalThreshold.sol

@@ -0,0 +1,34 @@
+// SPDX-License-Identifier: MIT
+
+pragma solidity ^0.8.0;
+
+import "../Governor.sol";
+
+/**
+ * @dev Extension of {Governor} for proposal restriction to token holders with a minimum balance.
+ *
+ * _Available since v4.3._
+ */
+abstract contract GovernorProposalThreshold is Governor {
+    /**
+     * @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);
+}

+ 7 - 7
contracts/governance/extensions/IGovernorTimelock.sol

@@ -9,17 +9,17 @@ import "../IGovernor.sol";
  *
  * _Available since v4.3._
  */
-interface IGovernorTimelock is IGovernor {
+abstract contract IGovernorTimelock is IGovernor {
     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(
-        address[] calldata targets,
-        uint256[] calldata values,
-        bytes[] calldata calldatas,
+        address[] memory targets,
+        uint256[] memory values,
+        bytes[] memory calldatas,
         bytes32 descriptionHash
-    ) external returns (uint256 proposalId);
+    ) public virtual returns (uint256 proposalId);
 }

+ 1 - 1
contracts/mocks/GovernorCompMock.sol

@@ -47,7 +47,7 @@ contract GovernorCompMock is Governor, GovernorVotesComp, GovernorCountingSimple
         public
         view
         virtual
-        override(Governor, GovernorVotesComp)
+        override(IGovernor, GovernorVotesComp)
         returns (uint256)
     {
         return super.getVotes(account, blockNumber);

+ 5 - 5
contracts/mocks/GovernorCompatibilityBravoMock.sol

@@ -34,11 +34,11 @@ contract GovernorCompatibilityBravoMock is GovernorCompatibilityBravo, GovernorT
         return super.supportsInterface(interfaceId);
     }
 
-    function votingDelay() public view override(IGovernor, Governor) returns (uint256) {
+    function votingDelay() public view override returns (uint256) {
         return _votingDelay;
     }
 
-    function votingPeriod() public view override(IGovernor, Governor) returns (uint256) {
+    function votingPeriod() public view override returns (uint256) {
         return _votingPeriod;
     }
 
@@ -46,7 +46,7 @@ contract GovernorCompatibilityBravoMock is GovernorCompatibilityBravo, GovernorT
         return _proposalThreshold;
     }
 
-    function quorum(uint256) public pure override(IGovernor, Governor) returns (uint256) {
+    function quorum(uint256) public pure override returns (uint256) {
         return 0;
     }
 
@@ -64,7 +64,7 @@ contract GovernorCompatibilityBravoMock is GovernorCompatibilityBravo, GovernorT
         public
         view
         virtual
-        override(GovernorCompatibilityBravo, GovernorTimelockCompound)
+        override(IGovernorTimelock, GovernorTimelockCompound)
         returns (uint256)
     {
         return super.proposalEta(proposalId);
@@ -84,7 +84,7 @@ contract GovernorCompatibilityBravoMock is GovernorCompatibilityBravo, GovernorT
         uint256[] memory values,
         bytes[] memory calldatas,
         bytes32 salt
-    ) public virtual override(GovernorCompatibilityBravo, GovernorTimelockCompound) returns (uint256) {
+    ) public virtual override(IGovernorTimelock, GovernorTimelockCompound) returns (uint256) {
         return super.queue(targets, values, calldatas, salt);
     }
 

+ 1 - 1
contracts/mocks/GovernorMock.sol

@@ -44,7 +44,7 @@ contract GovernorMock is Governor, GovernorVotesQuorumFraction, GovernorCounting
         public
         view
         virtual
-        override(Governor, GovernorVotes)
+        override(IGovernor, GovernorVotes)
         returns (uint256)
     {
         return super.getVotes(account, blockNumber);

+ 4 - 4
contracts/mocks/GovernorTimelockCompoundMock.sol

@@ -37,18 +37,18 @@ contract GovernorTimelockCompoundMock is GovernorTimelockCompound, GovernorVotes
         return super.supportsInterface(interfaceId);
     }
 
-    function votingDelay() public view override(IGovernor, Governor) returns (uint256) {
+    function votingDelay() public view override returns (uint256) {
         return _votingDelay;
     }
 
-    function votingPeriod() public view override(IGovernor, Governor) returns (uint256) {
+    function votingPeriod() public view override returns (uint256) {
         return _votingPeriod;
     }
 
     function quorum(uint256 blockNumber)
         public
         view
-        override(IGovernor, Governor, GovernorVotesQuorumFraction)
+        override(IGovernor, GovernorVotesQuorumFraction)
         returns (uint256)
     {
         return super.quorum(blockNumber);
@@ -99,7 +99,7 @@ contract GovernorTimelockCompoundMock is GovernorTimelockCompound, GovernorVotes
         public
         view
         virtual
-        override(IGovernor, Governor, GovernorVotes)
+        override(IGovernor, GovernorVotes)
         returns (uint256)
     {
         return super.getVotes(account, blockNumber);

+ 4 - 4
contracts/mocks/GovernorTimelockControlMock.sol

@@ -37,18 +37,18 @@ contract GovernorTimelockControlMock is GovernorTimelockControl, GovernorVotesQu
         return super.supportsInterface(interfaceId);
     }
 
-    function votingDelay() public view override(IGovernor, Governor) returns (uint256) {
+    function votingDelay() public view override returns (uint256) {
         return _votingDelay;
     }
 
-    function votingPeriod() public view override(IGovernor, Governor) returns (uint256) {
+    function votingPeriod() public view override returns (uint256) {
         return _votingPeriod;
     }
 
     function quorum(uint256 blockNumber)
         public
         view
-        override(IGovernor, Governor, GovernorVotesQuorumFraction)
+        override(IGovernor, GovernorVotesQuorumFraction)
         returns (uint256)
     {
         return super.quorum(blockNumber);
@@ -99,7 +99,7 @@ contract GovernorTimelockControlMock is GovernorTimelockControl, GovernorVotesQu
         public
         view
         virtual
-        override(IGovernor, Governor, GovernorVotes)
+        override(IGovernor, GovernorVotes)
         returns (uint256)
     {
         return super.getVotes(account, blockNumber);

+ 24 - 22
test/governance/Governor.test.js

@@ -29,7 +29,7 @@ contract('Governor', function (accounts) {
   beforeEach(async function () {
     this.owner = owner;
     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();
     await this.token.mint(owner, tokenSupply);
     await this.token.delegate(voter1, { from: voter1 });
@@ -72,7 +72,7 @@ contract('Governor', function (accounts) {
           tokenHolder: owner,
           voters: [
             { 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: voter4, weight: web3.utils.toWei('2'), support: Enums.VoteType.Abstain },
           ],
@@ -194,7 +194,7 @@ contract('Governor', function (accounts) {
           ],
           tokenHolder: owner,
           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) {
           ],
           tokenHolder: owner,
           voters: [
-            { voter: voter1, weight: web3.utils.toWei('1'), support: Enums.VoteType.For },
-            { voter: voter2, weight: web3.utils.toWei('1'), support: Enums.VoteType.Abstain },
+            { voter: voter1, weight: web3.utils.toWei('5'), support: Enums.VoteType.For },
+            { voter: voter2, weight: web3.utils.toWei('5'), support: Enums.VoteType.Abstain },
           ],
         };
       });
@@ -285,7 +285,7 @@ contract('Governor', function (accounts) {
           ],
           tokenHolder: owner,
           voters: [
-            { voter: voter1, weight: web3.utils.toWei('1'), support: Enums.VoteType.For },
+            { voter: voter1, weight: web3.utils.toWei('10'), support: Enums.VoteType.For },
           ],
           steps: {
             execute: { error: 'Governor: call reverted without message' },
@@ -306,7 +306,7 @@ contract('Governor', function (accounts) {
           ],
           tokenHolder: owner,
           voters: [
-            { voter: voter1, weight: web3.utils.toWei('1'), support: Enums.VoteType.For },
+            { voter: voter1, weight: web3.utils.toWei('10'), support: Enums.VoteType.For },
           ],
           steps: {
             execute: { error: 'CallReceiverMock: reverting' },
@@ -329,13 +329,13 @@ contract('Governor', function (accounts) {
           voters: [
             {
               voter: voter1,
-              weight: web3.utils.toWei('1'),
+              weight: web3.utils.toWei('5'),
               support: Enums.VoteType.For,
               error: 'Governor: unknown proposal id',
             },
             {
               voter: voter2,
-              weight: web3.utils.toWei('1'),
+              weight: web3.utils.toWei('5'),
               support: Enums.VoteType.Abstain,
               error: 'Governor: unknown proposal id',
             },
@@ -382,8 +382,8 @@ contract('Governor', function (accounts) {
           ],
           tokenHolder: owner,
           voters: [
-            { voter: voter1, weight: web3.utils.toWei('1'), support: Enums.VoteType.For },
-            { voter: voter2, weight: web3.utils.toWei('1'), support: Enums.VoteType.Abstain },
+            { voter: voter1, weight: web3.utils.toWei('5'), support: Enums.VoteType.For },
+            { voter: voter2, weight: web3.utils.toWei('5'), support: Enums.VoteType.Abstain },
           ],
         };
       });
@@ -406,7 +406,7 @@ contract('Governor', function (accounts) {
           voters: [
             {
               voter: voter1,
-              weight: web3.utils.toWei('1'),
+              weight: web3.utils.toWei('10'),
               support: new BN('255'),
               error: 'GovernorVotingSimple: invalid value for enum VoteType',
             },
@@ -433,12 +433,12 @@ contract('Governor', function (accounts) {
           voters: [
             {
               voter: voter1,
-              weight: web3.utils.toWei('1'),
+              weight: web3.utils.toWei('5'),
               support: Enums.VoteType.For,
             },
             {
               voter: voter1,
-              weight: web3.utils.toWei('1'),
+              weight: web3.utils.toWei('5'),
               support: Enums.VoteType.For,
               error: 'GovernorVotingSimple: vote already casted',
             },
@@ -459,7 +459,9 @@ contract('Governor', function (accounts) {
           ],
           tokenHolder: owner,
           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: {
             execute: { error: 'Governor: proposal not successful' },
@@ -480,7 +482,7 @@ contract('Governor', function (accounts) {
           ],
           tokenHolder: owner,
           voters: [
-            { voter: voter1, weight: web3.utils.toWei('1'), support: Enums.VoteType.Against },
+            { voter: voter1, weight: web3.utils.toWei('10'), support: Enums.VoteType.Against },
           ],
           steps: {
             execute: { error: 'Governor: proposal not successful' },
@@ -501,7 +503,7 @@ contract('Governor', function (accounts) {
           ],
           tokenHolder: owner,
           voters: [
-            { voter: voter1, weight: web3.utils.toWei('1'), support: Enums.VoteType.For },
+            { voter: voter1, weight: web3.utils.toWei('10'), support: Enums.VoteType.For },
           ],
           steps: {
             wait: { enable: false },
@@ -593,7 +595,7 @@ contract('Governor', function (accounts) {
           ],
           tokenHolder: owner,
           voters: [
-            { voter: voter1, weight: web3.utils.toWei('1'), support: Enums.VoteType.For },
+            { voter: voter1, weight: web3.utils.toWei('10'), support: Enums.VoteType.For },
           ],
           steps: {
             execute: { enable: false },
@@ -617,7 +619,7 @@ contract('Governor', function (accounts) {
           ],
           tokenHolder: owner,
           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) {
           ],
           tokenHolder: owner,
           voters: [
-            { voter: voter1, weight: web3.utils.toWei('1'), support: Enums.VoteType.For },
+            { voter: voter1, weight: web3.utils.toWei('10'), support: Enums.VoteType.For },
           ],
           steps: {
             wait: { enable: false },
@@ -723,7 +725,7 @@ contract('Governor', function (accounts) {
           ],
           tokenHolder: owner,
           voters: [
-            { voter: voter1, weight: web3.utils.toWei('1'), support: Enums.VoteType.For },
+            { voter: voter1, weight: web3.utils.toWei('10'), support: Enums.VoteType.For },
           ],
           steps: {
             execute: { enable: false },
@@ -753,7 +755,7 @@ contract('Governor', function (accounts) {
           ],
           tokenHolder: owner,
           voters: [
-            { voter: voter1, weight: web3.utils.toWei('1'), support: Enums.VoteType.For },
+            { voter: voter1, weight: web3.utils.toWei('10'), support: Enums.VoteType.For },
           ],
         };
       });

+ 45 - 0
test/governance/compatibility/GovernorCompatibilityBravo.test.js

@@ -215,6 +215,51 @@ contract('GovernorCompatibilityBravo', function (accounts) {
     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 () {
     beforeEach(async function () {
       this.settings = {

+ 1 - 1
test/utils/introspection/SupportsInterface.behavior.js

@@ -100,7 +100,7 @@ function shouldSupportInterfaces (interfaces = []) {
             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);
           });
         });