IGovernedSafeDiamondCut
Title: IGovernedSafeDiamondCut
Author: David Dada <daveproxy80@gmail.com> (https://github.com/dadadave80)
External interface for the Safe-multisig-gated diamond-cut facet WITH a built-in timelock.
Cuts cannot be applied synchronously; they must first be scheduleCut-ed by the pinned Safe,
mature past a minDelay, and only then be executeCut-ed. A pending operation can be
cancelCut-ed by the Safe. This module deliberately does NOT expose diamondCut at the
canonical selector 0x1f931c1c — every cut is delayed — so its scheduling surface forms a
genuinely new interface with its own ERC-165 id.
The authority is a pinned Gnosis Safe address (an M-of-N smart-contract multisig). The Safe
collects owner signatures off-chain and verifies the threshold on-chain inside
execTransaction, then dispatches the schedule/execute/cancel call to this facet. The facet
does NOT re-verify signatures; it trusts solely that msg.sender == the pinned Safe. The Safe
MUST invoke this facet with operation = Call (NEVER DelegateCall) — a DelegateCall would run
this facet's code in the Safe's own context, where msg.sender is whoever called the Safe,
defeating the gate. An operation id is keccak256(abi.encode(cuts, init, calldata, salt)); the
salt lets the same cut payload be scheduled more than once over the diamond's lifetime.
Functions
scheduleCut
Schedules a cut for later execution. Gated to the pinned Safe and rejected while emergency-stopped. Reverts CutAlreadyScheduled if the id is already scheduled or was executed.
function scheduleCut(FacetCut[] calldata _diamondCut, address _init, bytes calldata _calldata, bytes32 _salt)
external
returns (bytes32 id);Parameters
| Name | Type | Description |
|---|---|---|
| _diamondCut | FacetCut[] | The facet addresses, cut actions, and function selectors. |
| _init | address | The address delegatecalled after the cut (address(0) to skip). |
| _calldata | bytes | The calldata passed to _init. |
| _salt | bytes32 | A caller-chosen salt disambiguating otherwise-identical payloads. |
Returns
| Name | Type | Description |
|---|---|---|
| id | bytes32 | The operation id keccak256(abi.encode(_diamondCut, _init, _calldata, _salt)). |
executeCut
Executes a previously-scheduled, matured cut. Gated to the pinned Safe and rejected while
emergency-stopped. Reverts CutNotScheduled if unknown, cancelled or already executed,
CutNotReady if before eta, and the shared frozen-selector guard if a Replace/Remove
touches a frozen selector. The
arguments must match those passed to scheduleCut exactly (they re-derive the id).
function executeCut(FacetCut[] calldata _diamondCut, address _init, bytes calldata _calldata, bytes32 _salt)
external
payable;Parameters
| Name | Type | Description |
|---|---|---|
| _diamondCut | FacetCut[] | The facet addresses, cut actions, and function selectors. |
| _init | address | The address delegatecalled after the cut (address(0) to skip). |
| _calldata | bytes | The calldata passed to _init. |
| _salt | bytes32 | The salt used at schedule time. |
cancelCut
Cancels a scheduled, not-yet-executed operation. Gated to the pinned Safe. Reverts CutNotScheduled if the id is not currently scheduled (including an executed id).
function cancelCut(bytes32 _id) external;Parameters
| Name | Type | Description |
|---|---|---|
| _id | bytes32 | The operation id to cancel. |
getTimestamp
Returns the ready timestamp (eta) of an operation.
function getTimestamp(bytes32 _id) external view returns (uint256);Parameters
| Name | Type | Description |
|---|---|---|
| _id | bytes32 | The operation id to query. |
Returns
| Name | Type | Description |
|---|---|---|
| <none> | uint256 | The eta; 0 if never scheduled or cancelled; 1 once executed. |
isOperationPending
Returns whether _id is scheduled but not yet matured (eta in the future).
function isOperationPending(bytes32 _id) external view returns (bool);Parameters
| Name | Type | Description |
|---|---|---|
| _id | bytes32 | The operation id to query. |
Returns
| Name | Type | Description |
|---|---|---|
| <none> | bool |
isOperationReady
Returns whether _id is scheduled and matured (eta reached, not yet executed).
function isOperationReady(bytes32 _id) external view returns (bool);Parameters
| Name | Type | Description |
|---|---|---|
| _id | bytes32 | The operation id to query. |
Returns
| Name | Type | Description |
|---|---|---|
| <none> | bool |
isOperationDone
Returns whether _id has been executed. False for never-scheduled and cancelled ids.
function isOperationDone(bytes32 _id) external view returns (bool);Parameters
| Name | Type | Description |
|---|---|---|
| _id | bytes32 | The operation id to query. |
Returns
| Name | Type | Description |
|---|---|---|
| <none> | bool |
minDelay
Returns the minimum timelock delay (seconds) enforced between schedule and execute for operations scheduled now. A requested change shows here only once it takes effect.
function minDelay() external view returns (uint256);Returns
| Name | Type | Description |
|---|---|---|
| <none> | uint256 |
Events
CutScheduled
Emitted when the pinned Safe schedules a cut. The operation becomes executable at eta.
event CutScheduled(bytes32 indexed id, uint256 facetCutCount, address indexed init, bytes32 salt, uint256 eta);Parameters
| Name | Type | Description |
|---|---|---|
| id | bytes32 | The operation id keccak256(abi.encode(cuts, init, calldata, salt)). |
| facetCutCount | uint256 | The number of FacetCut entries scheduled. |
| init | address | The init address that will be delegatecalled after the cut (address(0) if none). |
| salt | bytes32 | The caller-chosen salt disambiguating otherwise-identical payloads. |
| eta | uint256 | The earliest block.timestamp at which the operation may be executed. |
CutExecuted
Emitted after a scheduled, matured cut is executed and applied.
event CutExecuted(bytes32 indexed id);Parameters
| Name | Type | Description |
|---|---|---|
| id | bytes32 | The operation id that was executed. |
CutCancelled
Emitted when the pinned Safe cancels a still-pending operation.
event CutCancelled(bytes32 indexed id);Parameters
| Name | Type | Description |
|---|---|---|
| id | bytes32 | The operation id that was cancelled. |
MinDelayChangeScheduled
Emitted when the pinned Safe requests a minimum-delay change. The new delay does not apply
until effectAt, which is max(oldDelay, newDelay) seconds after the request, so lowering
the delay always waits out the delay it replaces.
event MinDelayChangeScheduled(uint256 oldDelay, uint256 newDelay, uint256 effectAt);Parameters
| Name | Type | Description |
|---|---|---|
| oldDelay | uint256 | The minimum delay (seconds) in force when the change was requested. |
| newDelay | uint256 | The requested minimum delay (seconds). |
| effectAt | uint256 | The timestamp from which newDelay applies to newly scheduled cuts. |
Errors
CutAlreadyScheduled
Thrown when scheduling an operation id that is already scheduled (pending or matured) or was already executed. Re-running an executed cut takes a new salt.
error CutAlreadyScheduled(bytes32 id);Parameters
| Name | Type | Description |
|---|---|---|
| id | bytes32 | The duplicate operation id. |
CutNotScheduled
Thrown when executing or cancelling an operation id that is not currently scheduled: never scheduled, cancelled, or already executed.
error CutNotScheduled(bytes32 id);Parameters
| Name | Type | Description |
|---|---|---|
| id | bytes32 | The unknown operation id. |
CutNotReady
Thrown when executing an operation before its eta (timelock not yet elapsed).
error CutNotReady(bytes32 id, uint256 eta);Parameters
| Name | Type | Description |
|---|---|---|
| id | bytes32 | The operation id. |
| eta | uint256 | The earliest timestamp at which execution is permitted. |