GovernedDiamondCutLib
Title: GovernedDiamondCut Library
Author: David Dada <daveproxy80@gmail.com> (https://github.com/dadadave80)
Library that gates EIP-2535 diamond cuts behind EmergencyStop + a single-holder role,
then delegates the actual cut to DiamondLib.diamondCut. Introduces no new cut logic.
Three-layer pattern: this library holds all logic and the namespaced storage. The stateless GovernedDiamondCut facet delegates its single external call here.
Functions
governedDiamondCutStorage
Returns a storage reference to the GovernedDiamondCutStorage struct.
function governedDiamondCutStorage() internal pure returns (GovernedDiamondCutStorage storage $);Returns
| Name | Type | Description |
|---|---|---|
| $ | GovernedDiamondCutStorage |
__GovernedDiamondCut_init
Initializes the GovernedDiamondCut module.
Grants UPGRADE_EXECUTOR_ROLE to address(this) AND pins that role to administer ITSELF, so
the role is held solely by address(this) and cannot be granted by anyone else. The
self-administration step is load-bearing: without it the role's admin defaults to
DEFAULT_ADMIN_ROLE (0x00), which would let any DEFAULT_ADMIN_ROLE holder (e.g. a live
admin EOA) grantRole(UPGRADE_EXECUTOR_ROLE, attacker) and then diamondCut directly,
bypassing the entire Governor + TimelockController + vote + delay path. With the role
self-administered, only an existing holder can grant it — and the only holder is
address(this), which the recipe reaches only through a timelock-relayed passed proposal. A
co-cut facet that relays outside calls back into the diamond (e.g. AccessManager execute)
reaches it too; see the UPGRADE_EXECUTOR_ROLE caveat above. _setRoleAdmin is
the UNGATED setter (the gated setRoleAdmin would require the initializer to itself hold the
current admin role, which is not guaranteed during bootstrap). Must be called inside the
preInitializer/postInitializer window; AccessControl must already be initialized (the role
writes target its storage). Does NOT register an ERC-165 interface: 0x1f931c1c is already
registered by DiamondLib.registerInterface() (see the ERC165_MAP_ICUT_SLOT note above).
function __GovernedDiamondCut_init() internal;diamondCut
Guarded diamond cut: reverts if emergency-stopped, then requires the caller to hold
UPGRADE_EXECUTOR_ROLE, then delegates to DiamondLib.diamondCut. Introduces no new
cut logic — all selector-collision, immutable-function, bytecode-existence, and
init-delegatecall handling is diamond-lib's.
function diamondCut(FacetCut[] calldata _diamondCut, address _init, bytes calldata _calldata) internal;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. |
emergencyRemoveCut
Zero-delay, REMOVAL-ONLY escape hatch: lets a guardian instantly unbind a compromised
or buggy facet's selectors WITHOUT a governance round — and, by design, even while the
normal cut path is halted by EmergencyStop. Deliberately constrained so a rogue guardian
can only AMPUTATE code (every entry must be Remove), never add or replace it (that
still requires diamondCut under full governance), can never remove diamondCut itself
or a stop-recovery selector (even when unfrozen), and can never remove a frozen selector.
It CAN remove any other
unfrozen selector, including the Governor/Timelock path that reaches diamondCut, so the
guardian is trusted for governance liveness until that path is frozen (see the
EMERGENCY_GUARDIAN_ROLE note above).
Guard ordering and rationale:
- Authority FIRST: only an EMERGENCY_GUARDIAN_ROLE holder may proceed.
- NO
EmergencyStopLib.checkNotStopped()— INTENTIONAL. This is the panic button; it MUST remain usable precisely when upgrades are stopped (the same guardian who tripped the stop uses it to surgically remove the offending facet). The governeddiamondCutkeeps the stop gate; only this removal-only path bypasses it. - Removal-only: every FacetCut action must be
Remove; anAdd/ReplacerevertsIEmergencyCut.EmergencyCutMustBeRemoveOnlywith the offending action. diamond-lib additionally enforcesfacetAddress == address(0)forRemove. - Entrypoint + frozen protection:
diamondCutand the stop-recovery selectors (EmergencyStopLib.isRecoverySelector) revertIEmergencyCut.EmergencyCutEntrypointProtectedeven with an empty frozen set, and a frozen selector revertsIFrozenSelectors.FrozenSelectorProtected. - NO init delegatecall: applied as
DiamondLib.diamondCut(cuts, address(0), ""). A pure removal has nothing to initialize, and forbidding init denies a rogue guardian any arbitrary-delegatecall vector. The removal is recorded in the SAME append-only registry as governed cuts (so it appears in cut history) and additionally emitsIEmergencyCut.EmergencyCutExecutedso it is auditable as an emergency action.
function emergencyRemoveCut(FacetCut[] calldata _cuts) internal;Parameters
| Name | Type | Description |
|---|---|---|
| _cuts | FacetCut[] | The facet cuts to apply; every entry MUST be a Remove (facetAddress == address(0)). |
freezeSelectors
Permanently freezes _selectors: each becomes ineligible to be the target of a
Replace/Remove in any future governed cut. Gated behind UPGRADE_EXECUTOR_ROLE — the
same single-holder role that gates diamondCut — so freezing takes a timelock-relayed
governance proposal (or whatever else can reach that role; see the caveat on
UPGRADE_EXECUTOR_ROLE). Append-only: already-frozen selectors are idempotent no-ops, and
there is no unfreeze.
function freezeSelectors(bytes4[] calldata _selectors) internal;Parameters
| Name | Type | Description |
|---|---|---|
| _selectors | bytes4[] | The function selectors to freeze. |
_enforceNotFrozen
Reverts IFrozenSelectors.FrozenSelectorProtected if any Replace/Remove action in
_diamondCut targets a frozen selector. Add actions are never inspected. This is the
shared core of both the guarded diamondCut (reverting form) and previewCut (the
reverting form is reused via a try/catch-free direct read in previewCut).
function _enforceNotFrozen(GovernedDiamondCutStorage storage $, FacetCut[] calldata _diamondCut) private view;Parameters
| Name | Type | Description |
|---|---|---|
| $ | GovernedDiamondCutStorage | |
| _diamondCut | FacetCut[] |
cutCount
Returns the number of governed cuts applied so far (also the latest registry version).
function cutCount() internal view 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 IUpgradeRegistry.CutRecord.
function getCutRecord(uint256 _version) internal view returns (IUpgradeRegistry.CutRecord memory);Parameters
| Name | Type | Description |
|---|---|---|
| _version | uint256 | The registry version to look up. |
Returns
| Name | Type | Description |
|---|---|---|
| <none> | IUpgradeRegistry.CutRecord |
isSelectorFrozen
Returns whether _selector is in the frozen set (protected from Replace/Remove).
function isSelectorFrozen(bytes4 _selector) internal view returns (bool);Parameters
| Name | Type | Description |
|---|---|---|
| _selector | bytes4 | The function selector to query. |
Returns
| Name | Type | Description |
|---|---|---|
| <none> | bool |
frozenSelectors
Returns the full set of frozen selectors.
function frozenSelectors() internal view returns (bytes4[] memory);Returns
| Name | Type | Description |
|---|---|---|
| <none> | bytes4[] |
previewCut
Pre-flight simulation of the frozen-selector guard against the current set, with no
state change. Mirrors exactly the check the guarded diamondCut performs before
applying: only Replace/Remove actions are inspected. Pure read of the frozen set —
it does NOT re-implement diamond-lib's collision/immutable/bytecode checks (those fire
at execution).
function previewCut(FacetCut[] calldata _cuts) internal view 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.
Thin wrapper over the shared ERC-165 support read so an upgrade workflow can confirm an
expected interface is still/now advertised after a cut. Equivalent to supportsInterface.
function verifyInterfaceRegistered(bytes4 _interfaceId) internal view returns (bool);Parameters
| Name | Type | Description |
|---|---|---|
| _interfaceId | bytes4 | The ERC-165 interface identifier to check. |
Returns
| Name | Type | Description |
|---|---|---|
| <none> | bool |