From dccff11142e9add02a5b779ec68b6af38b9e3fa0 Mon Sep 17 00:00:00 2001 From: 08xmt Date: Wed, 29 Apr 2026 15:03:35 +0200 Subject: [PATCH 1/4] Update governorMills with settable vote delay, period and queue period --- contracts/token/GovernorMills.sol | 71 ++++++++++++++++++++++++++++--- 1 file changed, 65 insertions(+), 6 deletions(-) diff --git a/contracts/token/GovernorMills.sol b/contracts/token/GovernorMills.sol index fa90a57..bcac60b 100644 --- a/contracts/token/GovernorMills.sol +++ b/contracts/token/GovernorMills.sol @@ -31,11 +31,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 after which a proposal needs to be queued after succeeding + uint256 public queuePeriod = 23564; /// @notice The address of the Protocol Timelock TimelockInterface public timelock; @@ -171,6 +174,15 @@ 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_; @@ -226,8 +238,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 +337,50 @@ 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"); + require(newVotingPeriod >= 328, "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"); + require(newQueuePeriod >= 328, "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 +420,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 +489,4 @@ contract GovernorMills { return chainId; } -} \ No newline at end of file +} From 4670a993be19542627264fbbd97df5fa825098aa Mon Sep 17 00:00:00 2001 From: 08xmt Date: Thu, 7 May 2026 10:27:39 +0200 Subject: [PATCH 2/4] Fix documentation and increase minimum periods --- contracts/token/GovernorMills.sol | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/contracts/token/GovernorMills.sol b/contracts/token/GovernorMills.sol index bcac60b..c687e5c 100644 --- a/contracts/token/GovernorMills.sol +++ b/contracts/token/GovernorMills.sol @@ -37,7 +37,7 @@ contract GovernorMills { /// @notice The duration of voting on a proposal, in blocks uint256 public votingPeriod = 23564; // ~3 days in blocks (assuming 11s blocks) - /// @notice The duration after which a proposal needs to be queued after succeeding + /// @notice The duration for which a proposal is queueable after succeeding uint256 public queuePeriod = 23564; /// @notice The address of the Protocol Timelock @@ -357,7 +357,8 @@ contract GovernorMills { */ function updateVotingPeriod(uint256 newVotingPeriod) public { require(msg.sender == address(timelock), "GovernorMills::updateVotingPeriod: sender must be timelock"); - require(newVotingPeriod >= 328, "GovernorMills::updateVotingPeriod: voting period too short"); + //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; @@ -372,7 +373,8 @@ contract GovernorMills { */ function updateQueuePeriod(uint256 newQueuePeriod) public { require(msg.sender == address(timelock), "GovernorMills::updateQueuePeriod: sender must be timelock"); - require(newQueuePeriod >= 328, "GovernorMills::updateQueuePeriod: queue period too short"); + //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; From 1f0e325b9c2b603f711fe46f44dcb587863f4926 Mon Sep 17 00:00:00 2001 From: 08xmt Date: Thu, 14 May 2026 15:11:13 +0200 Subject: [PATCH 3/4] Integrate Guardian contract in GovernorMills --- contracts/token/GovernorMills.sol | 4 +- contracts/token/Guardian.sol | 86 +++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 contracts/token/Guardian.sol diff --git a/contracts/token/GovernorMills.sol b/contracts/token/GovernorMills.sol index c687e5c..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); @@ -187,7 +189,7 @@ contract GovernorMills { 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) { diff --git a/contracts/token/Guardian.sol b/contracts/token/Guardian.sol new file mode 100644 index 0000000..e662b8e --- /dev/null +++ b/contracts/token/Guardian.sol @@ -0,0 +1,86 @@ +// 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); + 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); + } +} From bd68d95578ac8fe3405535bda02b1f8bcc6c38e6 Mon Sep 17 00:00:00 2001 From: 08xmt Date: Mon, 18 May 2026 08:24:41 +0200 Subject: [PATCH 4/4] Remove proposal ID from cancellable after being cancelled --- contracts/token/Guardian.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/token/Guardian.sol b/contracts/token/Guardian.sol index e662b8e..c7c0a7d 100644 --- a/contracts/token/Guardian.sol +++ b/contracts/token/Guardian.sol @@ -55,6 +55,7 @@ contract Guardian { require(msg.sender == rwg, "Guardian: not rwg"); require(cancellableProposals[proposalId], "Guardian: not cancellable"); governorMills.cancel(proposalId); + cancellableProposals[proposalId] = false; emit ExecuteCancel(proposalId); }