diff --git a/contracts/token/GovernorMills.sol b/contracts/token/GovernorMills.sol index fa90a57..934f847 100644 --- a/contracts/token/GovernorMills.sol +++ b/contracts/token/GovernorMills.sol @@ -1,6 +1,8 @@ pragma solidity ^0.5.16; pragma experimental ABIEncoderV2; +import {Guardian} from "./Guardian.sol"; + interface InvInterface { function getPriorVotes(address account, uint blockNumber) external view returns (uint96); function totalSupply() external view returns (uint256); @@ -31,11 +33,14 @@ contract GovernorMills { /// @notice The maximum number of actions that can be included in a proposal function proposalMaxOperations() public pure returns (uint) { return 20; } // 20 actions - /// @notice The delay before voting on a proposal may take place, once proposed - function votingDelay() public pure returns (uint) { return 1; } // 1 block + /// @notice The delay before voting on a proposal may take place, once proposed (in blocks) + uint256 public votingDelay = 1; // 1 block /// @notice The duration of voting on a proposal, in blocks - function votingPeriod() public pure returns (uint) { return 17280; } // ~3 days in blocks (assuming 15s blocks) + uint256 public votingPeriod = 23564; // ~3 days in blocks (assuming 11s blocks) + + /// @notice The duration for which a proposal is queueable after succeeding + uint256 public queuePeriod = 23564; /// @notice The address of the Protocol Timelock TimelockInterface public timelock; @@ -171,11 +176,20 @@ contract GovernorMills { /// @notice An event emitted when an address is added or removed from the proposer whitelist event ProposerWhitelistUpdated(address proposer, bool value); + /// @notice An event emitted when the voting delay is updated + event VotingDelayUpdated(uint256 oldVotingDelay, uint256 newVotingDelay); + + /// @notice An event emitted when the voting period is updated + event VotingPeriodUpdated(uint256 oldVotingPeriod, uint256 newVotingPeriod); + + /// @notice An event emitted when the queue period is updated + event QueuePeriodUpdated(uint256 oldQueuePeriod, uint256 newQueuePeriod); + constructor(TimelockInterface timelock_, InvInterface inv_, XinvInterface xinv_) public { timelock = timelock_; inv = inv_; xinv = xinv_; - guardian = msg.sender; + guardian = address(new Guardian(msg.sender)); } function _getPriorVotes(address _proposer, uint256 _blockNumber, uint256 _exchangeRate) internal view returns (uint96) { @@ -226,8 +240,8 @@ contract GovernorMills { require(proposersLatestProposalState != ProposalState.Pending, "GovernorMills::propose: one live proposal per proposer, found an already pending proposal"); } - uint startBlock = add256(block.number, votingDelay()); - uint endBlock = add256(startBlock, votingPeriod()); + uint startBlock = add256(block.number, votingDelay); + uint endBlock = add256(startBlock, votingPeriod); proposalCount++; Proposal memory newProposal = Proposal({ @@ -325,6 +339,52 @@ contract GovernorMills { emit QuorumUpdated(oldQuorum, newQuorum); } + /** + * @notice Update the voting delay (in blocks) before voting on a proposal may take place. + * @param newVotingDelay The new voting delay to set. + */ + function updateVotingDelay(uint256 newVotingDelay) public { + require(msg.sender == address(timelock), "GovernorMills::updateVotingDelay: sender must be timelock"); + require(newVotingDelay != votingDelay, "GovernorMills::updateVotingDelay: no change in value"); + + uint256 oldVotingDelay = votingDelay; + votingDelay = newVotingDelay; + + emit VotingDelayUpdated(oldVotingDelay, newVotingDelay); + } + + /** + * @notice Update the voting period (in blocks) for a proposal. + * @param newVotingPeriod The new voting period to set. + */ + function updateVotingPeriod(uint256 newVotingPeriod) public { + require(msg.sender == address(timelock), "GovernorMills::updateVotingPeriod: sender must be timelock"); + //Minimum voting period of 12 hours at 11s per block + require(newVotingPeriod >= 3927, "GovernorMills::updateVotingPeriod: voting period too short"); + require(newVotingPeriod != votingPeriod, "GovernorMills::updateVotingPeriod: no change in value"); + + uint256 oldVotingPeriod = votingPeriod; + votingPeriod = newVotingPeriod; + + emit VotingPeriodUpdated(oldVotingPeriod, newVotingPeriod); + } + + /** + * @notice Update the queue period (in blocks) for a proposal. + * @param newQueuePeriod The new queue period to set. + */ + function updateQueuePeriod(uint256 newQueuePeriod) public { + require(msg.sender == address(timelock), "GovernorMills::updateQueuePeriod: sender must be timelock"); + //Minimum queuing period of 12 hours at 11s per block + require(newQueuePeriod >= 3927, "GovernorMills::updateQueuePeriod: queue period too short"); + require(newQueuePeriod != queuePeriod, "GovernorMills::updateQueuePeriod: no change in value"); + + uint256 oldQueuePeriod = queuePeriod; + queuePeriod = newQueuePeriod; + + emit QueuePeriodUpdated(oldQueuePeriod, newQueuePeriod); + } + function acceptAdmin() public { require(msg.sender == guardian, "GovernorMills::acceptAdmin: sender must be gov guardian"); timelock.acceptAdmin(); @@ -364,6 +424,9 @@ contract GovernorMills { } else if (proposal.forVotes <= proposal.againstVotes || proposal.forVotes < quorumVotes) { return ProposalState.Defeated; } else if (proposal.eta == 0) { + if (block.number > add256(proposal.endBlock, queuePeriod)) { + return ProposalState.Expired; + } return ProposalState.Succeeded; } else if (proposal.executed) { return ProposalState.Executed; @@ -430,4 +493,4 @@ contract GovernorMills { return chainId; } -} \ No newline at end of file +} diff --git a/contracts/token/Guardian.sol b/contracts/token/Guardian.sol new file mode 100644 index 0000000..c7c0a7d --- /dev/null +++ b/contracts/token/Guardian.sol @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.5.16; + +interface IGovernorMills { + function cancel(uint256 proposalId) external; + function __queueSetTimelockPendingAdmin(address newPendingAdmin, uint256 eta) external; + function __executeSetTimelockPendingAdmin(address newPendingAdmin, uint256 eta) external; +} + +contract Guardian { + IGovernorMills public governorMills; + address public constant oldGovernor = 0x926dF14a23BE491164dCF93f4c468A50ef659D5B; + uint256 public revertDeadline; + address public deployer; + address public pendingDeployer; + address public rwg = 0xE3eD95e130ad9E15643f5A5f232a3daE980784cd; + address public pendingRwg; + mapping(uint256 => bool) public cancellableProposals; + + event GovernanceRevertQueued(uint256 indexed eta); + event GovernanceReverted(uint256 indexed eta); + event AllowCancel(uint256 indexed proposalId, bool decision); + event ExecuteCancel(uint256 indexed proposalId); + event PendingRwgSet(address indexed newPendingRwg); + event RwgSet(address indexed oldRwg, address indexed newRwg); + event PendingDeployerSet(address indexed newPendingDeployer); + event DeployerSet(address indexed oldDeployer, address indexed newDeployer); + + constructor(address _deployer) public { + revertDeadline = block.timestamp + 180 days; //6 month rollback window + governorMills = IGovernorMills(msg.sender); + deployer = _deployer; + } + + function queueRevertToPreviousGovernance(uint256 eta) external { + require(msg.sender == deployer || msg.sender == rwg, "Guardian: not deployer or rwg"); + require(block.timestamp <= revertDeadline, "Guardian: revert deadline exceeded"); + governorMills.__queueSetTimelockPendingAdmin(oldGovernor, eta); + emit GovernanceRevertQueued(eta); + } + + function executeRevertToPreviousGovernance(uint256 eta) external { + require(msg.sender == deployer || msg.sender == rwg, "Guardian: not deployer or rwg"); + governorMills.__executeSetTimelockPendingAdmin(oldGovernor, eta); + emit GovernanceReverted(eta); + } + + function allowCancel(uint256 proposalId, bool decision) external { + require(msg.sender == deployer, "Guardian: not deployer"); + cancellableProposals[proposalId] = decision; + emit AllowCancel(proposalId, decision); + } + + function executeCancel(uint256 proposalId) external { + require(msg.sender == rwg, "Guardian: not rwg"); + require(cancellableProposals[proposalId], "Guardian: not cancellable"); + governorMills.cancel(proposalId); + cancellableProposals[proposalId] = false; + emit ExecuteCancel(proposalId); + } + + function setPendingRwg(address _rwg) external { + require(msg.sender == rwg, "Guardian: not rwg"); + emit PendingRwgSet(_rwg); + pendingRwg = _rwg; + } + + function claimRwg() external { + require(msg.sender == pendingRwg, "Guardian: not pending rwg"); + emit RwgSet(rwg, pendingRwg); + rwg = pendingRwg; + pendingRwg = address(0); + } + + function setPendingDeployer(address _deployer) external { + require(msg.sender == deployer, "Guardian: not deployer"); + emit PendingDeployerSet(_deployer); + pendingDeployer = _deployer; + } + + function claimDeployer() external { + require(msg.sender == pendingDeployer, "Guardian: not pending deployer"); + emit DeployerSet(deployer, pendingDeployer); + deployer = pendingDeployer; + pendingDeployer = address(0); + } +}