GovernedSafeDiamondCut
Inherits: IGovernedSafeDiamondCut, ISafeAuthority, IUpgradeRegistry, IFrozenSelectors, IEmergencyCut
Title: GovernedSafeDiamondCut
Author: David Dada <daveproxy80@gmail.com> (https://github.com/dadadave80)
Stateless Diamond facet exposing a Safe-multisig-gated EIP-2535 cut WITH a self-contained
timelock: the pinned Safe scheduleCuts an upgrade, it matures past minDelay, and only
then can the Safe executeCut it; a still-pending operation can be cancelCut-ed. No
external Governor/TimelockController is required. Also serves the append-only on-chain
IUpgradeRegistry, the frozen-selector protection layer, the guardian-gated emergency
removal escape hatch, and self-administered Safe-rotation / min-delay setters. Unlike
SafeDiamondCut it deliberately does NOT serve the synchronous cut selector 0x1f931c1c
(every cut is delayed).
All logic lives in GovernedSafeDiamondCutLib. This contract is stateless and forwards its
calls to the library; inherit it in your Diamond to add Safe-gated, timelocked upgrades. The
scheduling surface (scheduleCut/executeCut/cancelCut + timelock views) is a genuinely new
interface advertised via ERC-165 id 0xacb1aeb6; the registry / frozen / emergency / rotation
surface are plain facet functions sharing the same module slot.
AUTHORITY: the Safe collects M-of-N owner signatures off-chain and verifies the threshold
on-chain in execTransaction, then calls this facet. The facet does NOT re-verify signatures;
it trusts solely that msg.sender == the pinned Safe. The Safe MUST dispatch with
operation = Call (NEVER DelegateCall): a DelegateCall would run this code in the Safe's own
context, where msg.sender is whoever called the Safe, defeating the gate.
Notes:
- lattice-version: 0.1.0
- lattice-source: Lattice original
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
virtual
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
virtual;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 virtual;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 virtual 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 virtual 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 virtual 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 virtual 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 virtual returns (uint256);Returns
| Name | Type | Description |
|---|---|---|
| <none> | uint256 |
setMinDelay
Requests a new minimum timelock delay (seconds). Self-administered: callable only by the
pinned Safe. The change takes effect max(current, new) seconds later, so a lowered delay
waits out the delay it replaces, and applies to operations scheduled from then on. Emits
MinDelayChangeScheduled.
function setMinDelay(uint256 _newDelay) external virtual;Parameters
| Name | Type | Description |
|---|---|---|
| _newDelay | uint256 | The new minimum delay (seconds). |
setSafe
Rotates the pinned Safe authority to _newSafe. Self-administered: callable ONLY by the
current pinned Safe, so only the existing multisig can hand authority to its successor.
_newSafe is validated (non-zero, real Safe whose threshold is at least the minimum
meaningful threshold of 1). Emits SafeRotated.
function setSafe(address _newSafe) external virtual;Parameters
| Name | Type | Description |
|---|---|---|
| _newSafe | address | The new Safe to pin as the cut authority. |
safe
Returns the currently pinned Safe authority.
function safe() external view virtual returns (address);Returns
| Name | Type | Description |
|---|---|---|
| <none> | address | The pinned Safe address. |
emergencyRemoveCut
Zero-delay, removal-only emergency cut: instantly unbinds the supplied selectors from
the diamond. Callable ONLY by an EMERGENCY_GUARDIAN_ROLE holder, and — unlike the
governed diamondCut — it INTENTIONALLY works while EmergencyStop is engaged (it is the
panic button). Every FacetCut must use action Remove (revert
EmergencyCutMustBeRemoveOnly otherwise); none may target the cut entrypoint (revert
EmergencyCutEntrypointProtected), a stop-recovery selector (same revert) or a frozen
selector (revert IFrozenSelectors.FrozenSelectorProtected); no init delegatecall is
permitted (nothing to initialize on a pure removal). The removal is recorded in the IUpgradeRegistry and
emits EmergencyCutExecuted.
function emergencyRemoveCut(FacetCut[] calldata _cuts) external virtual;Parameters
| Name | Type | Description |
|---|---|---|
| _cuts | FacetCut[] | The facet cuts to apply; every entry MUST be a Remove (facetAddress == address(0)). |
cutCount
Returns the number of governed cuts recorded so far, which is also the latest version.
function cutCount() external view virtual returns (uint256);Returns
| Name | Type | Description |
|---|---|---|
| <none> | uint256 |
getCutRecord
Returns the immutable audit record for a given registry version.
Versions are 1-indexed; an unwritten version (0, or any value > cutCount) returns a zero-valued CutRecord.
function getCutRecord(uint256 _version) external view virtual returns (IUpgradeRegistry.CutRecord memory);Parameters
| Name | Type | Description |
|---|---|---|
| _version | uint256 | The registry version to look up. |
Returns
| Name | Type | Description |
|---|---|---|
| <none> | IUpgradeRegistry.CutRecord |
freezeSelectors
Permanently marks selectors as frozen: once frozen, a selector can never be the
target of a Replace or Remove in any future governed cut. Append-only — there is
deliberately no unfreeze. Gated like the facet's cut: on GovernedDiamondCut, behind
UPGRADE_EXECUTOR_ROLE (the single-holder role that gates diamondCut), so in its recipes
only a timelock-relayed governance proposal can freeze (a co-cut self-call relay such as
AccessManager execute would also reach it); on SafeDiamondCut and
GovernedSafeDiamondCut, by the pinned Safe.
function freezeSelectors(bytes4[] calldata _selectors) external virtual;Parameters
| Name | Type | Description |
|---|---|---|
| _selectors | bytes4[] | The function selectors to freeze. Already-frozen selectors are idempotent. |
isSelectorFrozen
Returns whether selector is in the frozen set.
function isSelectorFrozen(bytes4 _selector) external view virtual returns (bool);Parameters
| Name | Type | Description |
|---|---|---|
| _selector | bytes4 | The function selector to query. |
Returns
| Name | Type | Description |
|---|---|---|
| <none> | bool | true if the selector is frozen (protected from Replace/Remove). |
frozenSelectors
Returns the full set of frozen selectors.
function frozenSelectors() external view virtual returns (bytes4[] memory);Returns
| Name | Type | Description |
|---|---|---|
| <none> | bytes4[] | The frozen selectors, in insertion order (subject to swap-and-pop reordering on internal set mechanics, though the set is append-only so order is stable here). |
previewCut
Pre-flight simulation of the frozen-selector guard against the CURRENT frozen set, with
no state change and no broadcast. Lets an upgrade workflow fail fast — before proposing
— if a cut would touch a frozen selector. Mirrors exactly the check the guarded
diamondCut performs before applying: only Replace/Remove actions are inspected.
This is a pure read of the frozen set; it does NOT re-implement diamond-lib's collision / immutable-function / bytecode-existence checks, which fire at execution time.
function previewCut(FacetCut[] calldata _cuts) external view virtual returns (bool ok, bytes4 offendingSelector);Parameters
| Name | Type | Description |
|---|---|---|
| _cuts | FacetCut[] | The candidate facet cuts to simulate. |
Returns
| Name | Type | Description |
|---|---|---|
| ok | bool | true if no Replace/Remove in cuts targets a frozen selector. |
| offendingSelector | bytes4 | The first frozen selector a Replace/Remove would touch (zero if ok). |
verifyInterfaceRegistered
Verifies whether interfaceId is currently advertised in the diamond's ERC-165 map.
A thin wrapper over the shared ERC-165 support read, so an upgrade workflow can confirm an
expected interface is still (or now) advertised after a cut. Equivalent to calling
supportsInterface(interfaceId) on the diamond.
function verifyInterfaceRegistered(bytes4 _interfaceId) external view virtual returns (bool);Parameters
| Name | Type | Description |
|---|---|---|
| _interfaceId | bytes4 | The ERC-165 interface identifier to check. |
Returns
| Name | Type | Description |
|---|---|---|
| <none> | bool | true if the interface is advertised. |
exportSelectors
ERC-8153 selector export: this facet's cuttable selectors, tightly packed (4 bytes each).
Excludes exportSelectors() itself (0x0ef22643) - it is never cut into a diamond. Order matches
forge inspect GovernedSafeDiamondCut methodIdentifiers (alphabetical by signature); kept in exact parity by
ExportSelectorsParityTest. Chunks:
cancelCut(bytes32) 0x55cb2751
cutCount() 0xaa982c45
emergencyRemoveCut((address,uint8,bytes4[])[]) 0xc83542a6
executeCut((address,uint8,bytes4[])[],address,bytes,bytes32) 0xcb8f92b3
freezeSelectors(bytes4[]) 0x4487678f
frozenSelectors() 0x22cabf70
getCutRecord(uint256) 0x3adda78e
getTimestamp(bytes32) 0xd45c4435
isOperationDone(bytes32) 0x2ab0f529
isOperationPending(bytes32) 0x584b153e
isOperationReady(bytes32) 0x13bc9f20
isSelectorFrozen(bytes4) 0xc8d8e114
minDelay() 0xc63c4e9b
previewCut((address,uint8,bytes4[])[]) 0x35342750
safe() 0x186f0354
scheduleCut((address,uint8,bytes4[])[],address,bytes,bytes32) 0x41d26ecd
setMinDelay(uint256) 0xba29482f
setSafe(address) 0x5db0cb94
verifyInterfaceRegistered(bytes4) 0x0746a956
function exportSelectors() external pure virtual returns (bytes memory selectors);Returns
| Name | Type | Description |
|---|---|---|
| selectors | bytes |