123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167 |
- // SPDX-License-Identifier: MIT
- // OpenZeppelin Contracts (last updated v5.1.0) (governance/extensions/GovernorTimelockCompound.sol)
- pragma solidity ^0.8.20;
- import {IGovernor, Governor} from "../Governor.sol";
- import {ICompoundTimelock} from "../../vendor/compound/ICompoundTimelock.sol";
- import {Address} from "../../utils/Address.sol";
- import {SafeCast} from "../../utils/math/SafeCast.sol";
- /**
- * @dev Extension of {Governor} that binds the execution process to a Compound Timelock. This adds a delay, enforced by
- * the external timelock to all successful proposals (in addition to the voting duration). The {Governor} needs to be
- * the admin of the timelock for any operation to be performed. A public, unrestricted,
- * {GovernorTimelockCompound-__acceptAdmin} is available to accept ownership of the timelock.
- *
- * Using this model means the proposal will be operated by the {TimelockController} and not by the {Governor}. Thus,
- * the assets and permissions must be attached to the {TimelockController}. Any asset sent to the {Governor} will be
- * inaccessible from a proposal, unless executed via {Governor-relay}.
- */
- abstract contract GovernorTimelockCompound is Governor {
- ICompoundTimelock private _timelock;
- /**
- * @dev Emitted when the timelock controller used for proposal execution is modified.
- */
- event TimelockChange(address oldTimelock, address newTimelock);
- /**
- * @dev Set the timelock.
- */
- constructor(ICompoundTimelock timelockAddress) {
- _updateTimelock(timelockAddress);
- }
- /**
- * @dev Overridden version of the {Governor-state} function with added support for the `Expired` state.
- */
- function state(uint256 proposalId) public view virtual override returns (ProposalState) {
- ProposalState currentState = super.state(proposalId);
- return
- (currentState == ProposalState.Queued &&
- block.timestamp >= proposalEta(proposalId) + _timelock.GRACE_PERIOD())
- ? ProposalState.Expired
- : currentState;
- }
- /**
- * @dev Public accessor to check the address of the timelock
- */
- function timelock() public view virtual returns (address) {
- return address(_timelock);
- }
- /**
- * @dev See {IGovernor-proposalNeedsQueuing}.
- */
- function proposalNeedsQueuing(uint256) public view virtual override returns (bool) {
- return true;
- }
- /**
- * @dev Function to queue a proposal to the timelock.
- */
- function _queueOperations(
- uint256 proposalId,
- address[] memory targets,
- uint256[] memory values,
- bytes[] memory calldatas,
- bytes32 /*descriptionHash*/
- ) internal virtual override returns (uint48) {
- uint48 etaSeconds = SafeCast.toUint48(block.timestamp + _timelock.delay());
- for (uint256 i = 0; i < targets.length; ++i) {
- if (
- _timelock.queuedTransactions(keccak256(abi.encode(targets[i], values[i], "", calldatas[i], etaSeconds)))
- ) {
- revert GovernorAlreadyQueuedProposal(proposalId);
- }
- _timelock.queueTransaction(targets[i], values[i], "", calldatas[i], etaSeconds);
- }
- return etaSeconds;
- }
- /**
- * @dev Overridden version of the {Governor-_executeOperations} function that run the already queued proposal
- * through the timelock.
- */
- function _executeOperations(
- uint256 proposalId,
- address[] memory targets,
- uint256[] memory values,
- bytes[] memory calldatas,
- bytes32 /*descriptionHash*/
- ) internal virtual override {
- uint256 etaSeconds = proposalEta(proposalId);
- if (etaSeconds == 0) {
- revert GovernorNotQueuedProposal(proposalId);
- }
- Address.sendValue(payable(_timelock), msg.value);
- for (uint256 i = 0; i < targets.length; ++i) {
- _timelock.executeTransaction(targets[i], values[i], "", calldatas[i], etaSeconds);
- }
- }
- /**
- * @dev Overridden version of the {Governor-_cancel} function to cancel the timelocked proposal if it has already
- * been queued.
- */
- function _cancel(
- address[] memory targets,
- uint256[] memory values,
- bytes[] memory calldatas,
- bytes32 descriptionHash
- ) internal virtual override returns (uint256) {
- uint256 proposalId = super._cancel(targets, values, calldatas, descriptionHash);
- uint256 etaSeconds = proposalEta(proposalId);
- if (etaSeconds > 0) {
- // do external call later
- for (uint256 i = 0; i < targets.length; ++i) {
- _timelock.cancelTransaction(targets[i], values[i], "", calldatas[i], etaSeconds);
- }
- }
- return proposalId;
- }
- /**
- * @dev Address through which the governor executes action. In this case, the timelock.
- */
- function _executor() internal view virtual override returns (address) {
- return address(_timelock);
- }
- /**
- * @dev Accept admin right over the timelock.
- */
- // solhint-disable-next-line private-vars-leading-underscore
- function __acceptAdmin() public {
- _timelock.acceptAdmin();
- }
- /**
- * @dev Public endpoint to update the underlying timelock instance. Restricted to the timelock itself, so updates
- * must be proposed, scheduled, and executed through governance proposals.
- *
- * For security reasons, the timelock must be handed over to another admin before setting up a new one. The two
- * operations (hand over the timelock) and do the update can be batched in a single proposal.
- *
- * Note that if the timelock admin has been handed over in a previous operation, we refuse updates made through the
- * timelock if admin of the timelock has already been accepted and the operation is executed outside the scope of
- * governance.
- * CAUTION: It is not recommended to change the timelock while there are other queued governance proposals.
- */
- function updateTimelock(ICompoundTimelock newTimelock) external virtual onlyGovernance {
- _updateTimelock(newTimelock);
- }
- function _updateTimelock(ICompoundTimelock newTimelock) private {
- emit TimelockChange(address(_timelock), address(newTimelock));
- _timelock = newTimelock;
- }
- }
|