Guardian

For the holder of an address with the guardian bit in AccessControl.perms. Who may do what, and why the surface is shaped this way, belongs to Access control. This page is the authority and the semantics: the calls, their arguments, and the failures they exist to cover. The procedure around them is Guardian routines.


1. What the key is

Membership is the address itself. The guardian bit (perms bit 17) is granted to one address: instantly by setPerms before armQuorumPolicy, through queueRole(GUARDIAN) at LISTING after it; an EOA before arming, a Safe-compatible k-of-n after. The signers of a guardian Safe are not guardians; the Safe address is the entry. guardianQuorumMax defaults to QuorumLib.GUARDIAN_QUORUM_DEFAULT = 1 and is hard-capped at MAX_GUARDIAN_QUORUM = 2, above which the lever stops being fast.

Three properties define the remit, and every entrypoint below obeys all three:

  1. Direction. Halt, tighten, cancel. Never un-halt, never widen, never grant.
  2. No value. No guardian entrypoint moves a token or changes a recipient.
  3. No reverse. No lever you pull is released by you. Most are released by the owner; UpgradeGate.pause() is the one that leaves a state to undo. A mark-store lane halt is released only by the owner’s unhalt (§2).

A wrong guardian call costs downtime; a late one costs the pool. When in doubt, halt.


2. The levers

Each call runs requireGuardianOrOwner(AC) (or, on UpgradeGate, isGuardianOrAuth(msg.sender, authority)). The AC a target reads is its own immutable: a guardian on one deployment holds no authority on another.

The oracle levers are the mark store’s, called on the Pool implementation (PoolFactory.implementation()): one store per chain, both tiers, every pool at once.

ContractCallEffectReleased by
AdminhaltAsset(address pool, address token, uint16 src)Sets halt bits on one legunhaltAsset, owner
AdminbatchRiskOp(address[] pools, address[] tokens, BatchOp op, uint16 src)Same, across pairs, one txbatchRiskOp(UNHALT), owner
AdminhaltAsset(pool, token, 0x0800)Coop kill: clears COOP_ENABLED_BIT on one leg, swaps and exits stay liveUPDATE_RISK, TUNING
AdmincollapseAnchor(address pool, address token, address newAnchor)Re-anchors one leg onto a strict ancestor and halts it (HALT_ANCHOR_BIT). Protocol pools only: a pool with a seat refuses it. Unreachable on a depth-1 pool - see belowUPDATE_ANCHOR (GOVERNANCE): the anchor-halt bit is outside unhaltAsset’s reach
AdmincancelOp(address pool, uint8 opType, bytes32 subject)Vetoes a queued governance op on a protocol (unseated) pool. On a pool with a seat only the seat may cancel; owner and guardian revert NotAuthorizedOwner re-requests
Admin / Flashpause()Freezes executeUpgrade on that singletonunpause, owner
Admin / FlashcancelUpgrade()Discards the pending UUPS requestOwner re-requests
PoolFactorycancelReferenceUpgrade()Discards the pending fleet upgradeOwner re-requests
Yield hooksync()Forces a book refresh on one leg’s hook (keeper, risk steward and the pool seat hold it too). The TTL is not its lever; it moves no value outNot applicable
Mark storehalt(uint8 lane, uint8 tierMask)Fail-closes one lane on the primary (bit 0) and/or reference (bit 1) tier for every pool. The lane keeps its mark as the heal anchor and reads markless (§4)unhalt(lane, tierMask), owner
Mark storecancel(bytes32 op)Vetoes a queued store op: roster rotation (setAuth), lane register, reanchorOwner re-requests
AccessControlcancelRole(Role role)Vetoes any queued rotation or grant: OWNER, FACTORY, TREASURY, RISK_STEWARD, GUARDIAN. GUARDIAN_REVOKE is owner-onlyOwner re-queues

There is no guardian tighten on the oracle, and no signer revoke. Lane bounds (ttl, maxDev, σ floor) are an immutable class table; a roster change is the owner’s setAuth at GOVERNANCE, which you may cancel but not call. Your oracle lever is the lane halt. A risk steward holds the same halt.

collapseAnchor needs a tree at least two deep, so it cannot fire on a flat pool. The walk starts at the leg’s current anchor and steps to that node’s anchor before its first comparison, so newAnchor must be a strict ancestor of the current anchor: a grandparent or higher. On a flat (depth-1) pool every spoke anchors directly to the base and the base anchors to nothing, so the first step lands on address(0) and the call reverts InvalidAnchor for every leg, base included. It is a topology repair for a depth ≥ 2 pool, not an incident lever. During an incident on a flat pool the leg-level lever is haltAsset / batchRiskOp; re-rooting a leg is the owner’s UPDATE_ANCHOR op at the GOVERNANCE tier.

cancelRole(GUARDIAN_REVOKE) is the one cancel the guardian cannot reach: owner-only, so a guardian facing removal cannot veto its own revocation.

Store levers land only on the live implementation: a halt sent to a retired or pending Pool implementation reverts (InvalidState) rather than no-op. Read PoolFactory.implementation() first. Live addresses per chain: Contract Addresses.


3. Arguments that decide whether the call does what you meant

src on haltAsset / batchRiskOp. HALT_BIT = bit 0 (0x0001) halts the leg, or COOP_ENABLED_BIT (0x0800) clears the coop fill switch only: one way, the leg keeps swapping and LP exits, no notice clock starts, re-arm is a fresh UPDATE_RISK at TUNING (on a protocol pool the kill voids one already queued with the bit set). Anything else reverts InvalidInput. On a seated pool the seat can re-arm coop after TUNING; if the kill must hold for the LISTING notice, send HALT_BIT. Full layout: Guards §1. There is one halt: the owner clears it, and on a seated pool the seat clears it once LISTING has passed since your first unreleased halt. batchRiskOp skips a seated pool’s legs.

op on batchRiskOp. BatchOp.HALT = 0, BatchOp.UNHALT = 1. A guardian passing 1 reverts NotAuthorized before any leg is touched.

Per-leg outcomes on batchRiskOp. The loop is try/catch per leg, so one bad pair never bricks the sweep, and a failed leg is not halted. The receipt distinguishes them: RiskOpsBatched per applied leg, BatchLegSkipped per skipped one. Count the events against the arrays you sent.

subject on cancelOp. The third key component, the same one requestOp keys on (Admin Contract §4.1). A wrong subject reverts NoPending rather than cancelling something else; an unknown or NONE opType reverts InvalidInput.

tierMask on halt. Bit 0 = primary, bit 1 = reference, 3 = both; 0 or above 3 reverts InvalidInput, as does an unregistered lane. Halting the primary stops every leg on that lane. Halting only the reference stops every spoke leg that bands against it (the band fails closed).


4. What you are the last line of defence against

The automated breakers cover what can be stated as a predicate: staleness TTL, the MAX_CONFIDENCE_HALT_BPS = 1000 ceiling, base parity, the per-asset reference band (Flow guards, Mark Relayer). The guardian exists for what they do not cover: a mark that is authentic, in band, fresh and wrong; a queued governance op that should not land; a signer key believed leaked; an upgrade that should not execute at its ETA.

SituationLeverWhat it does not fix
One lane suspect, rest of the catalog healthyhalt(lane, 3)The mark is not corrected, only withheld. Every leg on that lane fail-closes until the owner unhalts and a push lands
Attester key believed leakedhalt every lane that tier’s roster can sign for, if k keys may be exposedOne leaked key moves nothing (k≥2). Removing a key is the owner’s setAuth at GOVERNANCE
Relay believed compromisedNothing instant is needed for price safety: the relay holds no signing key and chooses only when a validly signed segment lands, within 120 s of its source second. halt a lane it withholds or reorders againstReplacing it is setAuth at GOVERNANCE; the other listed relayers keep pushing
Queued loosening you do not trustThe matching cancel*The owner may re-request immediately; a cancel buys review time, not a veto on the outcome
A queued reanchor or roster rotation you do not trustMark store cancel(op)Neither reaches an op already executed. A reanchored lane’s next push lands unbanded
Matured upgrade you want frozen, not killedpause() on Admin / FlashOnly executeUpgrade is blocked. The request survives and remains executable after unpause
One leg’s anchor chain suspect, on a pool at least two deepcollapseAnchor(pool, token, newAnchor)newAnchor must be a strict ancestor of the leg’s current anchor (PoolConfigLib.collapseAnchor walks up to AnchorTreeLib.MAX_DEPTH, else InvalidAnchor). On a depth-1 pool no such node exists and the call always reverts (§2). The write halts the leg in the same transaction
Coop caller (COOP_ENABLED_BIT, lane 11) or CoopArb operator (lane 12) key believed leakedhaltAsset(pool, token, COOP_ENABLED_BIT) per coop legCoop only; normal swaps continue. Use HALT_BIT if the leg must stop or the kill must outlast TUNING on a seated pool
An asset must stop trading nowhaltAsset / batchRiskOpHalting a leg does not stop the same asset elsewhere; enumerate (Access control)

Three scope facts:

  • Admin.haltAsset calls IPool(pool).adminHaltAsset, which is gated on the Admin singleton, not on the pool’s admin seat: whoever administers a pool, administration resolves to the same singletons, so the lever reaches any pool the fleet’s Admin governs, official or not.
  • A halted lane is skipped by the push, not reverted: reverting on one halted lane would starve every other lane in the segment. The same fail-soft covers an unregistered lane, a non-advancing source second and a band breach. The push emits no event; read the lane words. Only header, source-time and quorum failures revert the whole push.
  • The halt is fail-closed on release too. unhalt returns the lane dark and the first push back is banded over the real gap since the halt; past the 10dmax ceiling it needs the owner’s reanchor (Mark Relayer §5.2). A halt is not a way to hold a mark, and the leg stays dark for one push cycle after the unhalt.

A guardian key that can halt lanes is also what the feed-liveness keeper holds; its automated halt path and the budget that bounds it are Mark Relayer.


PageContent
Guardian routinesBefore, after, escalation and the checklist for every lever above
Access controlAuthority matrix, halt sources, timelock schedule
Mark RelayerThe push side of the same oracle, and the automated guardian
Risk stewardThe other bounded key
Flow guardsThe automated breakers that run without you
ObservabilityThe reads named above, and where they are served
AdminUpgrade procedure a pause() interrupts