all files / plugins/governance/ Governance.sol

100% Statements 21/21
100% Branches 6/6
100% Functions 15/15
100% Lines 22/22
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179                                                                                                      11×       11×                   14× 14×       14×                   32×                 64×                   10×                                                                                                   48×                           10× 10× 10× 10×      
// SPDX-License-Identifier: BlueOak-1.0.0
pragma solidity 0.8.17;
 
import "@openzeppelin/contracts/governance/Governor.sol";
import "@openzeppelin/contracts/governance/extensions/GovernorCountingSimple.sol";
import "@openzeppelin/contracts/governance/extensions/GovernorSettings.sol";
import "@openzeppelin/contracts/governance/extensions/GovernorTimelockControl.sol";
import "@openzeppelin/contracts/governance/extensions/GovernorVotes.sol";
import "@openzeppelin/contracts/governance/extensions/GovernorVotesQuorumFraction.sol";
import "../../interfaces/IStRSRVotes.sol";
 
/*
 * @title Governance
 * @dev Decentralized Governance for the Reserve Protocol.
 *
 * Note that due to the elastic supply of StRSR, proposalThreshold is handled
 *   very differently than the typical approach. It is in terms of micro %,
 *   as is _getVotes().
 *
 * 1 {micro %} = 1e8
 */
contract Governance is
    Governor,
    GovernorSettings,
    GovernorCountingSimple,
    GovernorVotes,
    GovernorVotesQuorumFraction,
    GovernorTimelockControl
{
    // 100%
    uint256 public constant ONE_HUNDRED_PERCENT = 1e8; // {micro %}
 
    // solhint-disable no-empty-blocks
    constructor(
        IStRSRVotes token_,
        TimelockController timelock_,
        uint256 votingDelay_, // in blocks
        uint256 votingPeriod_, // in blocks
        uint256 proposalThresholdAsMicroPercent_, // e.g. 1e4 for 0.01%
        uint256 quorumPercent // e.g 4 for 4%
    )
        Governor("Governor Alexios")
        GovernorSettings(votingDelay_, votingPeriod_, proposalThresholdAsMicroPercent_)
        GovernorVotes(IVotes(address(token_)))
        GovernorVotesQuorumFraction(quorumPercent)
        GovernorTimelockControl(timelock_)
    {}
 
    // solhint-enable no-empty-blocks
 
    function votingDelay() public view override(IGovernor, GovernorSettings) returns (uint256) {
        return super.votingDelay();
    }
 
    function votingPeriod() public view override(IGovernor, GovernorSettings) returns (uint256) {
        return super.votingPeriod();
    }
 
    /// @return {qStRSR} The number of votes required in order for a voter to become a proposer
    function proposalThreshold()
        public
        view
        override(Governor, GovernorSettings)
        returns (uint256)
    {
        uint256 asMicroPercent = super.proposalThreshold(); // {micro %}
        uint256 pastSupply = token.getPastTotalSupply(block.number - 1); // {qStRSR}
        // max StRSR supply is 1e38
 
        // CEIL to make sure thresholds near 0% don't get rounded down to 0 tokens
        return (asMicroPercent * pastSupply + (ONE_HUNDRED_PERCENT - 1)) / ONE_HUNDRED_PERCENT;
    }
 
    function quorum(uint256 blockNumber)
        public
        view
        virtual
        override(IGovernor, GovernorVotesQuorumFraction)
        returns (uint256)
    {
        return super.quorum(blockNumber);
    }
 
    function state(uint256 proposalId)
        public
        view
        override(Governor, GovernorTimelockControl)
        returns (ProposalState)
    {
        return GovernorTimelockControl.state(proposalId);
    }
 
    function propose(
        address[] memory targets,
        uint256[] memory values,
        bytes[] memory calldatas,
        string memory description
    ) public override(Governor, IGovernor) returns (uint256 proposalId) {
        // The super call checks that getVotes() >= proposalThreshold()
        return super.propose(targets, values, calldatas, description);
    }
 
    function queue(
        address[] memory targets,
        uint256[] memory values,
        bytes[] memory calldatas,
        bytes32 descriptionHash
    ) public override returns (uint256 proposalId) {
        proposalId = super.queue(targets, values, calldatas, descriptionHash);
        require(startedInSameEra(proposalId), "new era");
    }
 
    function cancel(
        address[] memory targets,
        uint256[] memory values,
        bytes[] memory calldatas,
        bytes32 descriptionHash
    ) external {
        uint256 proposalId = _cancel(targets, values, calldatas, descriptionHash);
        require(!startedInSameEra(proposalId), "same era");
    }
 
    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);
        require(startedInSameEra(proposalId), "new era");
    }
 
    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();
    }
 
    /// @return {qStRSR} The voting weight the account had at a previous block number
    function _getVotes(
        address account,
        uint256 blockNumber,
        bytes memory /*params*/
    ) internal view override(Governor, GovernorVotes) returns (uint256) {
        return token.getPastVotes(account, blockNumber); // {qStRSR}
    }
 
    function supportsInterface(bytes4 interfaceId)
        public
        view
        override(Governor, GovernorTimelockControl)
        returns (bool)
    {
        return super.supportsInterface(interfaceId);
    }
 
    // === Private ===
 
    function startedInSameEra(uint256 proposalId) private view returns (bool) {
        uint256 startBlock = proposalSnapshot(proposalId);
        uint256 pastEra = IStRSRVotes(address(token)).getPastEra(startBlock);
        uint256 currentEra = IStRSRVotes(address(token)).currentEra();
        return currentEra == pastEra;
    }
}