AccessRouter and roles
The protocol separates two kinds of state change:
- User operations (deposit, transact, ragequit) are authorized by zero-knowledge proofs the contracts verify on-chain, so no privileged account can move a user's funds.
- Administrative changes (pausing the pool, upgrading a contract, swapping a verifier, updating asset config, publishing a new ASP root) are authorized by roles instead.
Every contract delegates those role checks to a single AccessRouter.
AccessRouter
AccessRouter is the one contract that holds the role assignments. Every other contract carries a checkAccess(ROLE) modifier that asks the router whether msg.sender holds the required role, so roles are granted and revoked in one place rather than per contract. The router uses a two-tier admin model that separates fast operational response from slow custodial governance:
DEFAULT_ADMIN_ROLE Slow-governance tier, intended for timelocked governance (Canon Guard). It administers OPS_ADMIN_ROLE and every role not delegated to the ops tier, and it is the only role that can replace a compromised ops multisig.OPS_ADMIN_ROLE Fast operational tier, intended for a multisig without a timelock. It administers only the roles designated at deploy time, and the constructor rejects any attempt to delegate DEFAULT_ADMIN_ROLE or OPS_ADMIN_ROLE itself to this tier, so the asymmetry cannot be configured away by mistake.Roles and what they gate
DEPOSITOR_ROLE Gates PoolVault.deposit. Held by the Entrypoint, so deposits flow through the Entrypoint's fee and asset-config layer rather than calling the vault directly.PAUSER_ROLE Gates PoolVault.pause, which halts deposits and transacts (both carry whenNotPaused). Can be held by several guardians.UNPAUSER_ROLE Gates PoolVault.unpause, which resumes operation.UPGRADER_ROLE Gates the UUPS upgrade authorization on every contract, plus the parameter setters: PoolVault.setAspRegistry, setKeystore, setRagequitVerifier, setDepositVerifier, and setMaxStateTreeSize; Keystore.setKeystoreRootLiveness and setMaxKeystoreTreeSize; and Entrypoint.setPoolVault.ENTRYPOINT_MANAGER_ROLE Gates Entrypoint.sweep, which withdraws collected vetting fees, and the Entrypoint asset configuration.POSTMAN_ROLE Gates ASPRegistry.updateASPRoot, the call that publishes a new association-set root and its IPFS CID.Verifier contracts
The proofs behind user operations are checked by generated Groth16 verifier contracts, kept separate from the logic contracts. PoolVault holds a deposit verifier and a ragequit verifier, and routes transact proofs through the transact verifier for the transact_NxM variant in use. Swapping any of these is an UPGRADER_ROLE operation (setDepositVerifier, setRagequitVerifier), which is why a verifier change and the matching circuit change must land together as one deployment set.
User operations versus administrative state
This split is what the trust model rests on. A deposit, transfer, withdrawal, or ragequit changes pool state only when its circuit proof verifies, so the role holders above cannot forge or redirect a user operation. What the roles control is the surrounding machinery: whether the pool is paused, which verifier and registry addresses the vault points at, what assets the Entrypoint accepts, and which ASP root is current. Pausing blocks new deposits and transacts but never seizes funds, and because ragequit is not gated by whenNotPaused, a registered owner can still exit an unspent note even while the pool is paused.
Source: v2-monorepo/packages/contracts/src/contracts/AccessRouter.sol, v2-monorepo/packages/contracts/src/utils/RolesConstants.sol