Skip to main content
Version: 0.3.0

Pause

The Hub exposes a circuit breaker that halts every user-facing operation across the deployment while leaving admin paths (freeze, seize, role admin, upgrade, key rotation) available. Pause is intended for incident response — a discovered verifier bug, a compromised minter key, an exploit in progress — where the response time of the upgrade key is too slow.

Two flags live on the Hub: a global flag, and a per-EBEMT mapping. The combined check is exposed as Hub.isPaused(address ebemt) → bool, which returns paused || pausedEbemts[ebemt]. EBEMTs consult this single view on every user-facing entry point, so an operation on EBEMT T is blocked if either flag is set.

Roles

One owner-maintained role set:

mapping(address => bool) public pausers; // on Hub

owner is implicitly a pauser without needing an entry. The owner key can therefore halt the system unilaterally; the pausers set is for delegating to faster-response operational addresses (typically a hot multisig with quicker incident response than the upgrade key).

Admin functions (onlyOwner, immediate effect):

  • setPauser(address, bool)

Unlike freezer / seizer (Freeze and Seize), where owner is deliberately not implicitly enrolled to enforce separation of duties on fund-movement, pause is a coarse reversible circuit breaker with no fund-movement consequence — collapsing it onto the owner key is acceptable and operationally useful.

Flags and entry points

bool public paused; // global
mapping(address => bool) public pausedEbemts; // per-EBEMT

All four entry points are pauser-gated (or owner-called):

  • pause() / unpause()
  • pauseEbemt(address) / unpauseEbemt(address)

The two flags are independent. Pausing globally and then unpausing globally does not clear an EBEMT that is still individually paused. This lets an operator globally pause for an incident, locally pin a known-bad token before lifting the global pause, and unpause globally without re-enabling the bad token.

Enforcement

Every user-facing operation reverts with ContractIsPaused when either flag is set.

On EBEMTpublicToEncryptedTransfer*, encryptedToPublicTransfer*, encryptedTransfer*, activatePending*, setAutoEncrypt*, toggleAutoEncrypt* all carry the whenNotPaused modifier. The ERC-20 surface (transfer, transferFrom, ERC-3009 transferWithAuthorization / receiveWithAuthorization) is gated through the _update chokepoint, which calls Hub.isPaused(address(this)) on every credit and debit. onlyHub executors are not gated — pause is enforced at the Hub entry point that calls them. permit (ERC-2612 allowance approval) is not blocked: it sets an allowance only, and any downstream transferFrom consuming that allowance hits the _update check.

On HubpublicMint, publicBurn, encryptedMint, encryptedBurn all check paused || pausedEbemts[ebemt] before any auth or proof verification, so a paused EBEMT does not waste gas on proof checks. registerEpk and changeControllerWithAuth consult only the global flag, since EPK identity is deployment-wide and not bound to any particular EBEMT.

Not gated by pause — freeze, unfreeze, seize (both layers), all role setters, setSanctionsList, setTrcPk, rotateTotalSupplyKey, upgradeEbemt, registerEbemt. These remain available during pause; without them, pause is useless for incident response.

Seize bypasses the _update pause check by routing through super._update — pause never blocks seizure of a frozen account.

Events

EventPurpose
Paused() / Unpaused()Global flag changes
EbemtPaused(address indexed ebemt) / EbemtUnpaused(address indexed ebemt)Per-EBEMT flag changes
PauserUpdated(address indexed pauser, bool enabled)Admin role change

Security model

Pauser compromise is reversible. A rogue pauser can DoS the deployment globally or per-EBEMT, but cannot move funds. Recovery: unpause / unpauseEbemt from the owner key (always implicitly authorized), plus setPauser(rogue, false).

Owner is implicitly authorized, so a compromised owner key can also pause — but a compromised owner key already controls the upgrade path and the full role registry, so this widens nothing.

Two flags, two signals. Indexers must track both Paused / Unpaused and EbemtPaused / EbemtUnpaused to render an accurate per-token paused-status. The Hub's isPaused(ebemt) view is the canonical source of truth.