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× 6× 5× 2× 2× 3× 3× 2× 5× 48× 1× 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; } } |