Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

SlotProtectionAssertion

Git Source

Inherits: AccessControlBaseAssertion

Title: SlotProtectionAssertion

Author: Phylax Systems

Asserts that critical storage slots on the assertion adopter are not modified during the transaction. Invariants covered:

  • Ownership immutability: proxy admin, owner, sentinel, and implementation slots cannot be changed outside expected governance choreography.
  • Timelock integrity: delay values, proposer/executor/canceller role slots are frozen so an attacker cannot shorten a delay before exploiting a governance path.
  • Role stability: admin roles, operator roles, manager roles remain unchanged unless an authorized governance action modifies them.
  • Configuration guards: fee parameters, oracle addresses, whitelist/blocklist settings, and other safety-critical configuration slots.

Uses the V2 forbidChangeForSlots precompile which checks the transaction journal for any SSTORE to the specified slots. A write is flagged even if it sets the same value (conservative – a write is suspicious regardless of whether the value changed). Writes inside reverted internal calls are rolled back in the journal and do not trigger a violation. Implementers must override _protectedSlots() to declare which slots to protect. For mapping entries (e.g., roles[account]), compute the slot off-chain via keccak256(abi.encode(key, mappingSlot)). The policy enforced is: fail by default on any watched-slot mutation. Protocols that need conditional slot changes should use per-function triggers instead and verify the change follows the expected governance path.

Functions

_protectedSlots

Returns the storage slots that must not be modified during the transaction.

Override to declare the protocol-specific critical slots. Common examples:

  • EIP-1967 admin slot: 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103
  • EIP-1967 implementation slot: 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc
  • Gnosis Safe owner sentinel (slot 2), threshold (slot 4), singleton (slot 0)
  • Timelock delay parameters, proposer/executor/canceller roles
  • Protocol-specific admin, fee, oracle, and permission level slots
function _protectedSlots() internal pure virtual returns (bytes32[] memory slots);

Returns

NameTypeDescription
slotsbytes32[]Array of storage slot identifiers to protect.

_registerSlotProtectionTriggers

Register the default trigger set for slot protection.

Registers one storage-change trigger per protected slot, so the check runs only when a watched slot is actually written during the transaction instead of on every adopter transaction. This narrows execution to the exact risk surface without weakening the invariant: assertSlotProtection still inspects the whole journal via forbidChangeForSlots, which flags any SSTORE to a protected slot. A storage-change trigger fires off that same journal, so same-value writes (which forbidChangeForSlots treats as suspicious) still arm the assertion. Call this inside your triggers().

function _registerSlotProtectionTriggers() internal view;

assertSlotProtection

Verifies that none of the protected storage slots were written to during the tx.

Uses ph.forbidChangeForSlots() for a single precompile call covering all slots. Reverts if any protected slot was modified.

function assertSlotProtection() external view;

Errors

ProtectedSlotChanged

error ProtectedSlotChanged(bytes32[] slots);