Skip to content

feat(agglayer): RBAC-based access control for bridge roles#3130

Open
Fumuran wants to merge 15 commits into
nextfrom
fumuran-claude/agglayer-rbac-roles
Open

feat(agglayer): RBAC-based access control for bridge roles#3130
Fumuran wants to merge 15 commits into
nextfrom
fumuran-claude/agglayer-rbac-roles

Conversation

@Fumuran

@Fumuran Fumuran commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Summary

Refactor phase of #2706. Replaces the AggLayer bridge's hard-coded admin / GER-injector / GER-remover account-ID storage slots with the miden-standards access-control stack (Ownable2Step + RoleBasedAccessControl + Authority).

Per the discussion on the issue, this uses RBAC rather than Ownable2Step alone, with a separate governance owner over three subordinate operational roles. Each role-gated bridge procedure now calls authority::assert_authorized, which checks the note sender holds the role mapped to that procedure.

Roles

  • Governance owner (Ownable2Step): top-level authority; grants/revokes roles, two-step transferable.
  • FAUCET_ADMINregister_faucet, store_faucet_metadata_hash
  • GER_INJECTORupdate_ger
  • GER_REMOVERremove_ger

Changes

  • miden-standards: add RoleAssignment + RoleBasedAccessControl::with_roles(...) to seed initial role members at construction; AccessControl::Rbac gains a members field (yields a seeded RBAC instead of empty()).
  • bridge MASM: remove the three account-ID slots and bespoke assert_sender_is_* procs; gate the four privileged procedures via authority::assert_authorized.
  • bridge Rust: AggLayerBridge is now stateless; add BridgeRoleMember, role-symbol + procedure-root accessors, and the fixed procedure→role map. create_bridge_account now takes (seed, owner, Vec<BridgeRoleMember>).
  • build.rs: include the RBAC stack when computing BRIDGE_CODE_COMMITMENT.
  • Update SPEC.md, agglayer integration tests, and bench setups. Add a with_roles-equals-runtime-grant_role equivalence test and a procedure→role map pinning test.

Out of scope (follow-up)

On-chain role-management notes (grant_role / revoke_role / transfer_ownership / accept_ownership) plus their allowlist entries and Rust builders. Roles are seeded and enforced, but rotation is not yet exercisable on-chain — hence this is the refactor phase (Refs #2706, not Closes).

Testing

  • cargo +nightly fmt --all --check and cargo clippy (affected crates, --all-targets): clean.
  • Tests pass: agglayer suite (69), rbac/authority/pausable script tests (42), miden-standards (119).

🤖 Generated with Claude Code

claude added 4 commits June 24, 2026 22:19
Replace the bridge's hard-coded admin / GER-injector / GER-remover account-ID
storage slots with the miden-standards access-control stack (Ownable2Step +
RoleBasedAccessControl + Authority). The role-gated procedures (register_faucet,
store_faucet_metadata_hash, update_ger, remove_ger) now call
authority::assert_authorized, which checks the note sender holds the FAUCET_ADMIN /
GER_INJECTOR / GER_REMOVER role mapped to that procedure. A governance owner can
grant/revoke roles and transfer ownership via Ownable2Step.

- miden-standards: add RoleAssignment + RoleBasedAccessControl::with_roles to seed
  initial role members at construction; AccessControl::Rbac gains a `members` field.
- bridge MASM: drop the three ID slots / bespoke assert_sender_is_* procs; gate the
  four privileged procedures via authority::assert_authorized.
- bridge Rust: add BridgeRoleMember, role-symbol + procedure-root accessors, and the
  fixed procedure->role map; create_bridge_account now takes an owner and a
  Vec<BridgeRoleMember> instead of three bare account IDs.
- build.rs: include the RBAC stack when computing BRIDGE_CODE_COMMITMENT.
- update SPEC.md, agglayer integration tests, and bench setups.

On-chain role-management notes (grant/revoke role, transfer ownership) are a planned
follow-up, so role rotation is not yet exercisable on-chain.

Refs #2706.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
AggLayerBridge is a zero-field component; use the bare value via its Default/From
impl (matching PausableManager and BurnAllowAll) instead of an empty new().

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Represent initial role membership as BTreeMap<RoleSymbol, BTreeSet<AccountId>>
instead of Vec<RoleAssignment>. Duplicate roles and duplicate members are now
impossible by construction, so with_roles drops the two dedup passes (and the
DuplicateRole / DuplicateMember error variants), and the RoleAssignment type is
removed. The bridge's role grouping collapses to a single entry()-based loop.

No behavior change: seeding still equals runtime grant_role (equivalence test
passes).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment thread crates/miden-agglayer/asm/agglayer/bridge/bridge_config.masm Outdated
Comment thread crates/miden-agglayer/src/bridge.rs Outdated
Comment thread crates/miden-agglayer/src/bridge.rs Outdated
Comment thread crates/miden-agglayer/src/bridge.rs Outdated
Comment thread crates/miden-agglayer/src/bridge.rs Outdated
Comment thread crates/miden-standards/src/account/access/rbac.rs Outdated
Comment thread crates/miden-standards/src/account/access/rbac.rs Outdated
Comment thread crates/miden-standards/src/account/access/rbac.rs Outdated
Comment thread crates/miden-standards/src/account/access/rbac.rs Outdated
Comment thread crates/miden-standards/src/account/access/rbac.rs Outdated
Fumuran and others added 2 commits July 1, 2026 21:32
- bridge: resolve procedure roots via the procedure_root! macro + a component
  code() getter, matching the PausableManager pattern (removes the ad-hoc
  bridge_procedure_root helper).
- bridge: BridgeRoleMember variants now carry a Vec<AccountId> so all holders of a
  role are grouped in one entry; rbac_role_members collects via entry()+extend.
- docs: link role names to their BridgeRoleMember variants; tighten the bridge
  component and builder doc comments; bullet-list the with_roles Errors section.
- masm: drop the redundant storage-slots comment.
- SPEC: switch stored-ID wording to role-membership wording and keep a single
  authority::assert_authorized explanation (in Administration) instead of repeating
  it in every note/procedure section.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment thread crates/miden-agglayer/src/bridge.rs Outdated
Comment thread crates/miden-agglayer/src/bridge.rs Outdated
Comment thread crates/miden-standards/src/account/access/rbac.rs Outdated
- bridge: move the component namespace and the component-code getter into
  AggLayerBridge (AggLayerBridge::COMPONENT_NAMESPACE and AggLayerBridge::code()),
  matching the PausableManager shape.
- rbac: trim the seeded roles field doc to the essential summary.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Fumuran Fumuran marked this pull request as ready for review July 1, 2026 19:54
@Fumuran Fumuran added the pr-from-maintainers PRs that come from internal contributors or integration partners. They should be given priority label Jul 1, 2026
@mmagician mmagician added the agglayer PRs or issues related to AggLayer bridging integration label Jul 1, 2026

@mmagician mmagician left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall LGTM!

Comment thread crates/miden-standards/src/account/access/rbac.rs Outdated
Comment thread crates/miden-standards/src/account/access/rbac.rs Outdated
Comment thread crates/miden-testing/tests/agglayer/test_utils.rs Outdated
Comment thread crates/miden-agglayer/asm/agglayer/bridge/bridge_config.masm
Comment thread crates/miden-agglayer/src/bridge.rs Outdated
Comment thread crates/miden-agglayer/src/lib.rs
Comment thread crates/miden-agglayer/SPEC.md Outdated
Comment thread crates/miden-testing/tests/agglayer/test_utils.rs Outdated
Comment thread crates/miden-testing/tests/scripts/rbac.rs Outdated
Resolve import conflicts in lib.rs and the agglayer test files, and adapt to
next's AccountId::dummy 4-arg signature (AssetCallbackFlag) in the bridge test
helpers. The bridge-account test call sites keep using the create_existing_bridge_account_with_roles
helper, so next's direct create_existing_bridge_account import is dropped where unused.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@partylikeits1983 partylikeits1983 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks really good, I like the simplification that the RBAC provides.

There are a few minor things I think should be addressed before merging.

Comment thread crates/miden-agglayer/src/bridge.rs
Comment thread crates/miden-testing/tests/agglayer/update_ger.rs
Comment thread bin/bench-transaction/src/context_setups.rs Outdated
Comment on lines +82 to +84
RoleBasedAccessControl::with_roles(members)
.expect("initial RBAC role assignments should be valid")
.into(),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: This will panic instead of returning an error if the user passes an empty member set (or a set with more than u32::MAX members).

Can we return an error here instead of panicking? And add tests for both error cases?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, I overlooked it. I'll update it to return an error and add tests for it

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, this update will complicate things a bit, since we expect here to have an iterator of components: changing this into iterator of results will require some changes in quite a lot of places, so I'm not sure that it's forth it. For now I'll just move this checks to the Into<AccountComponent> implementation, but in general I agree that we should somehow return an error here instead of panic.

claude added 2 commits July 6, 2026 21:03
- bridge: replace the BridgeRoleMember enum with a BridgeRoles struct (BTreeSet per
  role) and a fallible BridgeRoles::new that rejects an empty holder set for any
  role (new AgglayerBridgeError::EmptyBridgeRole), so a bridge can't be deployed
  with an unfillable role. create_bridge_account takes BridgeRoles.
- standards: make RoleBasedAccessControl::with_roles infallible (drop empty member
  sets instead of erroring/panicking; remove RbacError and the IntoIterator expect).
  Add reusable role_config_key / role_membership_key helpers and use them in the
  From impl instead of building raw words.
- testing: move the shared create_existing_bridge_account_with_roles / bridge_test_owner
  helper into miden_agglayer::testing so the test crate and bench share one copy.
- tests: add ERR_SENDER_LACKS_ROLE tests for update_ger (GER_INJECTOR) and
  register_faucet via CONFIG (FAUCET_ADMIN), and a BridgeRoles::new empty-role test.
- SPEC: point the role-management follow-up TODO at #3046; fix a doc link.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- changelog: correct the [BREAKING] entry to reflect the BridgeRoles signature
  (create_bridge_account takes (seed, owner, BridgeRoles)) instead of the removed
  BridgeRoleMember type.
- rbac: note that the u32 member-count conversion in the From impl is purely
  defensive / infallible.
- test: add test_rbac_with_roles_drops_empty_role locking in the empty-role
  drop semantics.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
claude added 2 commits July 7, 2026 14:40
create_rbac_account_with_members(owner, BTreeMap::new()) covers the empty-members
case, so the separate owner-only helper is unnecessary.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
It was only used by the shared create_existing_bridge_account_with_roles test
helper, so fold the build_existing() call into that helper (via the crate-private
create_bridge_account_builder) and drop the redundant public wrapper.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment thread crates/miden-agglayer/src/bridge.rs Outdated
Comment thread crates/miden-agglayer/src/bridge.rs Outdated
Comment thread CHANGELOG.md Outdated
claude added 3 commits July 7, 2026 15:08
- bridge: drop the verbose BridgeRoles struct doc paragraph (the empty-role
  rejection is already documented on BridgeRoles::new).
- changelog: trim the breaking-change entry per review suggestion.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reconcile the RBAC access-control refactor with next's new DEREGISTER_AGG_FAUCET
feature:
- Convert deregister_faucet from assert_sender_is_bridge_admin (removed by this
  branch) to authority::assert_authorized, mapped to the FAUCET_ADMIN role
  (procedure_roles + a new procedure_root!/deregister_faucet_root accessor).
- Update next's deregister tests to the create_existing_bridge_account_with_roles
  helper and ERR_SENDER_LACKS_ROLE; update the procedure-roles mapping test to five
  entries.
- SPEC: keep the RBAC role wording with next's shifted section numbers and align
  the DEREGISTER_AGG_FAUCET section to the FAUCET_ADMIN role terminology.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Post-merge review nits: the config/deregister note-script headers, the
register_faucet / store_faucet_metadata_hash panic docs, and the deregister note
builder still referred to the 'bridge admin'; update them to the FAUCET_ADMIN role
that authority::assert_authorized now enforces.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Fumuran

Fumuran commented Jul 7, 2026

Copy link
Copy Markdown
Contributor Author

Current state looks fine for me, but I would wait for the #3215 to be merged first, then I'll update this PR and only then re-request the reviews.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agglayer PRs or issues related to AggLayer bridging integration pr-from-maintainers PRs that come from internal contributors or integration partners. They should be given priority

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants