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:
- Direction. Halt, tighten, cancel. Never un-halt, never widen, never grant.
- No value. No guardian entrypoint moves a token or changes a recipient.
- 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’sunhalt(§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.
| Contract | Call | Effect | Released by |
|---|---|---|---|
Admin | haltAsset(address pool, address token, uint16 src) | Sets halt bits on one leg | unhaltAsset, owner |
Admin | batchRiskOp(address[] pools, address[] tokens, BatchOp op, uint16 src) | Same, across pairs, one tx | batchRiskOp(UNHALT), owner |
Admin | haltAsset(pool, token, 0x0800) | Coop kill: clears COOP_ENABLED_BIT on one leg, swaps and exits stay live | UPDATE_RISK, TUNING |
Admin | collapseAnchor(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 below | UPDATE_ANCHOR (GOVERNANCE): the anchor-halt bit is outside unhaltAsset’s reach |
Admin | cancelOp(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 NotAuthorized | Owner re-requests |
Admin / Flash | pause() | Freezes executeUpgrade on that singleton | unpause, owner |
Admin / Flash | cancelUpgrade() | Discards the pending UUPS request | Owner re-requests |
PoolFactory | cancelReferenceUpgrade() | Discards the pending fleet upgrade | Owner re-requests |
| Yield hook | sync() | 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 out | Not applicable |
| Mark store | halt(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 store | cancel(bytes32 op) | Vetoes a queued store op: roster rotation (setAuth), lane register, reanchor | Owner re-requests |
AccessControl | cancelRole(Role role) | Vetoes any queued rotation or grant: OWNER, FACTORY, TREASURY, RISK_STEWARD, GUARDIAN. GUARDIAN_REVOKE is owner-only | Owner 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.
| Situation | Lever | What it does not fix |
|---|---|---|
| One lane suspect, rest of the catalog healthy | halt(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 leaked | halt every lane that tier’s roster can sign for, if keys may be exposed | One leaked key moves nothing (). Removing a key is the owner’s setAuth at GOVERNANCE |
| Relay believed compromised | Nothing 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 against | Replacing it is setAuth at GOVERNANCE; the other listed relayers keep pushing |
| Queued loosening you do not trust | The 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 trust | Mark store cancel(op) | Neither reaches an op already executed. A reanchored lane’s next push lands unbanded |
| Matured upgrade you want frozen, not killed | pause() on Admin / Flash | Only executeUpgrade is blocked. The request survives and remains executable after unpause |
| One leg’s anchor chain suspect, on a pool at least two deep | collapseAnchor(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 leaked | haltAsset(pool, token, COOP_ENABLED_BIT) per coop leg | Coop 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 now | haltAsset / batchRiskOp | Halting a leg does not stop the same asset elsewhere; enumerate (Access control) |
Three scope facts:
Admin.haltAssetcallsIPool(pool).adminHaltAsset, which is gated on theAdminsingleton, 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’sAdmingoverns, 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.
unhaltreturns the lane dark and the first push back is banded over the real gap since the halt; past the ceiling it needs the owner’sreanchor(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.
5. Related
| Page | Content |
|---|---|
| Guardian routines | Before, after, escalation and the checklist for every lever above |
| Access control | Authority matrix, halt sources, timelock schedule |
| Mark Relayer | The push side of the same oracle, and the automated guardian |
| Risk steward | The other bounded key |
| Flow guards | The automated breakers that run without you |
| Observability | The reads named above, and where they are served |
| Admin | Upgrade procedure a pause() interrupts |