Skip to content

feat: authority durable store — roles{} snapshot and read-only reconcile#9

Open
aelmanaa wants to merge 1 commit into
mainfrom
refactor/p3.5-roles-durable-store
Open

feat: authority durable store — roles{} snapshot and read-only reconcile#9
aelmanaa wants to merge 1 commit into
mainfrom
refactor/p3.5-roles-durable-store

Conversation

@aelmanaa

Copy link
Copy Markdown
Contributor

What improves for the user

Before: after wiring a token, pool, TokenAdminRegistry, lockbox, and hooks, there is no single place that records who holds every privileged role, and no way to check that the intended authority still matches the live chain. Authority drift — a rotated owner, an unrevoked deployer key, an in-flight admin transfer, a rogue mint grant — is invisible until it bites.

After: a roles{} subtree in config/chains/<name>.json declares the full privileged-authority surface, and a read-only engine reconciles that declaration against the chain. make snapshot-chain backfills the declaration from the chain (the only writer); make roles-check reconciles it (never writes); a rung on make doctor and a non-blocking scheduled CI job keep drift visible. The declaration is the durable, git-versioned record of who controls the deployment.

What's in this PR

  • src/roles/ — the engine, all reads via tolerant staticcall so it dispatches on what a contract actually exposes:
    • RolesProbes — capability probes + a four-template dispatch (crosschain = AccessControlDefaultAdminRules, burnmint = plain AccessControl, factory = Ownable, byo = unknown), including the separate BURN_MINT_ADMIN_ROLE that admins mint/burn on CrossChainToken (the slot a naive sweep forgets).
    • RolesSnapshot — backfills the declared state from chain with honest complete markers; an optional SCAN_FROM_BLOCK enables a RoleGranted/RoleRevoked event scan to prove non-enumerable minter/burner lists, recording scannedFromBlock as provenance.
    • RolesAuditor — reconciles declared vs live: point-checks for single-holder slots (reliable on any RPC, no eth_getLogs), two-sided set compares for enumerable sets, and an opt-in SCAN_FROM_BLOCK additive check that turns an undeclared (rogue) holder into a hard FAIL. Non-enumerable lists always WARN that additive grants are unverified without a scan — never a silent CLEAN.
  • Toolingmake snapshot-chain (sole writer, subtree-isolated), make roles-check (exit 0 clean / 1 drift, naming the field / 2 RPC-unavailable, contract owned by script/config/roles-check.sh), a roles rung on make doctor (wrapped so a malformed declaration degrades to a WARN, never aborts the doctor), and a non-blocking scheduled roles-check CI job.
  • script/governance/VerifyRoles.s.sol — a read-only holder dump for at-a-glance audits.
  • Docsdocs/roles.md (the reconcile model, the drift-response runbook with a containment-first triage branch, and the honest-coverage boundary) and the roles{} subtree in docs/config-schema.md.

Honest-coverage boundary

A clean roles-check proves what the engine can read. CCIP-side authority (TAR administrator, pool owner/rateLimitAdmin/feeAdmin, lockbox/hooks owner, the token admin-registration point) is fully covered for any token. A declared holder being revoked is always detected; an undeclared holder being added is detected only for enumerable tokens, or for crosschain/burnmint when run with SCAN_FROM_BLOCK. A byo token's mint/burn internals are declaration-backed only — a clean check does not prove them safe. The docs state this in full.

Testing

Unit + fork tests (test/roles/RolesAuthority.t.sol, 14 cases: four-template detection, snapshot fidelity, clean reconcile, induced drift naming the field, type-mismatch FAIL, the honesty WARNs, a burnmint snapshot+reconcile, absent-governance SKIP) plus a live Sepolia run of the full flow: snapshot fidelity vs direct reads, clean reconcile (exit 0), an induced additive-grant FAIL under SCAN_FROM_BLOCK (exit 1) that a no-scan run correctly misses, and doctor-rung resilience against a malformed declaration.

Add the roles{} authority subtree and its read-only reconcile engine: probe
the live privileged-role surface (token/pool/TokenAdminRegistry/lockbox/hooks),
declare it in config, and reconcile the declaration against the chain.

- src/roles: RolesProbes (tolerant capability probes + 4-template dispatch:
  crosschain/burnmint/factory/byo, incl. BURN_MINT_ADMIN_ROLE), RolesSnapshot
  (backfill declared state from chain, honest complete markers + optional
  RoleGranted/RoleRevoked event scan), RolesAuditor (declared-vs-live reconcile,
  opt-in SCAN_FROM_BLOCK additive detection).
- make snapshot-chain (sole writer), make roles-check (0 clean / 1 drift /
  2 rpc-down), a roles rung on make doctor, and a non-blocking CI roles-check.
- script/governance/VerifyRoles.s.sol: read-only holder dump for audits.
- docs/roles.md + config-schema.md roles{} subtree: the reconcile model,
  drift-response runbook, and honest-coverage boundary.
@aelmanaa aelmanaa requested review from a team as code owners July 13, 2026 22:18
@github-actions

Copy link
Copy Markdown

👋 aelmanaa, thanks for creating this pull request!

To help reviewers, please consider creating future PRs as drafts first. This allows you to self-review and make any final changes before notifying the team.

Once you're ready, you can mark it as "Ready for review" to request feedback. Thanks!

@aelmanaa aelmanaa requested a review from SyedAsadKazmi July 13, 2026 22:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant