Skip to content
Lattice

GovernedSafeDiamondCut

Git Source

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

NameTypeDescription
_diamondCutFacetCut[]The facet addresses, cut actions, and function selectors.
_initaddressThe address delegatecalled after the cut (address(0) to skip).
_calldatabytesThe calldata passed to _init.
_saltbytes32A caller-chosen salt disambiguating otherwise-identical payloads.

Returns

NameTypeDescription
idbytes32The 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

NameTypeDescription
_diamondCutFacetCut[]The facet addresses, cut actions, and function selectors.
_initaddressThe address delegatecalled after the cut (address(0) to skip).
_calldatabytesThe calldata passed to _init.
_saltbytes32The 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

NameTypeDescription
_idbytes32The operation id to cancel.

getTimestamp

Returns the ready timestamp (eta) of an operation.

function getTimestamp(bytes32 _id) external view virtual returns (uint256);

Parameters

NameTypeDescription
_idbytes32The operation id to query.

Returns

NameTypeDescription
<none>uint256The 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

NameTypeDescription
_idbytes32The operation id to query.

Returns

NameTypeDescription
<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

NameTypeDescription
_idbytes32The operation id to query.

Returns

NameTypeDescription
<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

NameTypeDescription
_idbytes32The operation id to query.

Returns

NameTypeDescription
<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

NameTypeDescription
<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

NameTypeDescription
_newDelayuint256The 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

NameTypeDescription
_newSafeaddressThe new Safe to pin as the cut authority.

safe

Returns the currently pinned Safe authority.

function safe() external view virtual returns (address);

Returns

NameTypeDescription
<none>addressThe 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

NameTypeDescription
_cutsFacetCut[]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

NameTypeDescription
<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

NameTypeDescription
_versionuint256The registry version to look up.

Returns

NameTypeDescription
<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

NameTypeDescription
_selectorsbytes4[]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

NameTypeDescription
_selectorbytes4The function selector to query.

Returns

NameTypeDescription
<none>booltrue 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

NameTypeDescription
<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

NameTypeDescription
_cutsFacetCut[]The candidate facet cuts to simulate.

Returns

NameTypeDescription
okbooltrue if no Replace/Remove in cuts targets a frozen selector.
offendingSelectorbytes4The 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

NameTypeDescription
_interfaceIdbytes4The ERC-165 interface identifier to check.

Returns

NameTypeDescription
<none>booltrue 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

NameTypeDescription
selectorsbytes