Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions contracts/governance/README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ Counting modules determine valid voting options.

Timelock extensions add a delay for governance decisions to be executed. The workflow is extended to require a `queue` step before execution. With these modules, proposals are executed by the external timelock contract, thus it is the timelock that has to hold the assets that are being governed.

* {GovernorDelay}: Adds a simple configurable delay to all proposals without requiring an external timelock contract. The delay is enforced by the Governor itself, making it suitable for cases where a simple delay is needed without the complexity of external timelock contracts.

* {GovernorTimelockAccess}: Connects with an instance of an {AccessManager}. This allows restrictions (and delays) enforced by the manager to be considered by the Governor and integrated into the AccessManager's "schedule + execute" workflow.

* {GovernorTimelockControl}: Connects with an instance of {TimelockController}. Allows multiple proposers and executors, in addition to the Governor itself.
Expand Down Expand Up @@ -86,6 +88,8 @@ NOTE: Functions of the `Governor` contract do not include access control. If you

=== Extensions

{{GovernorDelay}}

{{GovernorTimelockAccess}}

{{GovernorTimelockControl}}
Expand Down
106 changes: 106 additions & 0 deletions contracts/governance/extensions/GovernorDelay.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.5.0) (governance/extensions/GovernorDelay.sol)

pragma solidity ^0.8.24;

import {IGovernor, Governor} from "../Governor.sol";
import {SafeCast} from "../../utils/math/SafeCast.sol";
import {Time} from "../../utils/types/Time.sol";

/**
* @dev Extension of {Governor} that adds a configurable delay to all successful proposals before they can be executed.
*
* This extension provides a simple way to add a delay to all proposals without requiring an external timelock contract.
* When a delay is set (greater than 0), all successful proposals must be queued and wait for the delay period to elapse
* before they can be executed.
*
* The delay is enforced by the Governor itself, unlike {GovernorTimelockControl} and {GovernorTimelockCompound} where
* the delay is enforced by an external timelock contract.
*
* NOTE: The delay is expressed in seconds and uses block.timestamp, regardless of the governor's clock mode. This is
* consistent with {proposalEta} which is documented to not follow ERC-6372 CLOCK_MODE and almost always be a timestamp.
*
* @custom:security-note This extension enforces delays at the Governor level. If you need more sophisticated delay
* mechanisms (e.g., cancellable operations, different delays per operation), consider using {GovernorTimelockAccess}
* with an {AccessManager}.
*/
abstract contract GovernorDelay is Governor {
using Time for *;

uint32 private _delay;

error GovernorUnmetDelay(uint256 proposalId, uint256 neededTimestamp);

event DelaySet(uint32 oldDelay, uint32 newDelay);

/**
* @dev Initialize the governor with an initial delay.
*/
constructor(uint32 initialDelay) {
_setDelay(initialDelay);
}

/**
* @dev Returns the delay in seconds that must elapse before a queued proposal can be executed.
*/
function delay() public view virtual returns (uint32) {
return _delay;
}

/**
* @dev Change the delay. This operation can only be performed through a governance proposal.
*
* Emits a {DelaySet} event.
*/
function setDelay(uint32 newDelay) public virtual onlyGovernance {
_setDelay(newDelay);
}

/**
* @dev Internal function to set the delay without access control.
*/
function _setDelay(uint32 newDelay) internal virtual {
emit DelaySet(_delay, newDelay);
_delay = newDelay;
}

/// @inheritdoc IGovernor
function proposalNeedsQueuing(uint256) public view virtual override returns (bool) {
return _delay > 0;
}

/**
* @dev Function to queue a proposal with the configured delay.
*/
function _queueOperations(
uint256 /* proposalId */,
address[] memory /* targets */,
uint256[] memory /* values */,
bytes[] memory /* calldatas */,
bytes32 /* descriptionHash */
) internal virtual override returns (uint48) {
if (_delay == 0) {
return 0;
}
return Time.timestamp() + _delay;
}

/**
* @dev Overridden version of the {Governor-_executeOperations} function that checks if the delay has elapsed.
*/
function _executeOperations(
uint256 proposalId,
address[] memory targets,
uint256[] memory values,
bytes[] memory calldatas,
bytes32 descriptionHash
) internal virtual override {
uint48 etaSeconds = SafeCast.toUint48(proposalEta(proposalId));
if (etaSeconds > 0 && block.timestamp < etaSeconds) {
revert GovernorUnmetDelay(proposalId, etaSeconds);
}

super._executeOperations(proposalId, targets, values, calldatas, descriptionHash);
}
}

51 changes: 51 additions & 0 deletions contracts/mocks/governance/GovernorDelayMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.24;

import {Governor} from "../../governance/Governor.sol";
import {GovernorDelay} from "../../governance/extensions/GovernorDelay.sol";
import {GovernorSettings} from "../../governance/extensions/GovernorSettings.sol";
import {GovernorCountingSimple} from "../../governance/extensions/GovernorCountingSimple.sol";
import {GovernorVotesQuorumFraction} from "../../governance/extensions/GovernorVotesQuorumFraction.sol";

abstract contract GovernorDelayMock is
GovernorSettings,
GovernorDelay,
GovernorVotesQuorumFraction,
GovernorCountingSimple
{
function quorum(uint256 blockNumber) public view override(Governor, GovernorVotesQuorumFraction) returns (uint256) {
return super.quorum(blockNumber);
}

function proposalThreshold() public view override(Governor, GovernorSettings) returns (uint256) {
return super.proposalThreshold();
}

function proposalNeedsQueuing(
uint256 proposalId
) public view virtual override(Governor, GovernorDelay) returns (bool) {
return super.proposalNeedsQueuing(proposalId);
}

function _queueOperations(
uint256 proposalId,
address[] memory targets,
uint256[] memory values,
bytes[] memory calldatas,
bytes32 descriptionHash
) internal override(Governor, GovernorDelay) returns (uint48) {
return super._queueOperations(proposalId, targets, values, calldatas, descriptionHash);
}

function _executeOperations(
uint256 proposalId,
address[] memory targets,
uint256[] memory values,
bytes[] memory calldatas,
bytes32 descriptionHash
) internal override(Governor, GovernorDelay) {
super._executeOperations(proposalId, targets, values, calldatas, descriptionHash);
}
}

Loading
Loading