Skip to content

feat(standards): extend Authority with role assignment map#3072

Merged
bobbinth merged 9 commits into
nextfrom
authority-role-assignment
Jun 12, 2026
Merged

feat(standards): extend Authority with role assignment map#3072
bobbinth merged 9 commits into
nextfrom
authority-role-assignment

Conversation

@onurinanc

Copy link
Copy Markdown
Collaborator

Closes #3070.

@PhilippGackstatter PhilippGackstatter 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 good. The main comment is about removal of the default role.

Comment thread crates/miden-standards/asm/standards/access/authority.masm
Comment on lines +122 to +125
# => [mapped_role, default_role, 0, 0]

dup eq.0
if.true

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.

Suggested change
# => [mapped_role, default_role, 0, 0]
dup eq.0
if.true
# => [mapped_role, default_role, 0, 0]
dup eq.0
# => [has_no_mapped_role, default_role, 0, 0]
if.true

Nit: I would indicate what the comparison of a role to zero means.

Comment on lines +126 to +144
# No per-procedure role; fall back to the default.
drop
# => [default_role, 0, 0]

dup eq.0
if.true
# Zero default role — defer to the Ownable2Step owner.
drop drop drop
# => []
exec.ownable2step::assert_sender_is_owner
# => []
else
# Non-zero default role — require it.
# => [default_role, 0, 0]
exec.rbac::assert_sender_has_role
# => [0, 0]
drop drop
# => []
end

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.

In the interest of simplification, I would remove the default_role and only have a two step logic: If a procedure is configured, assert the sender has the role, if not, require the owner.

Comment on lines +40 to +43
static AUTHORITY_ROLE_MAP_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
StorageSlotName::new("miden::standards::access::authority::role_map")
.expect("storage slot name should be valid")
});

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: Maybe we can capture the "procedure root to role" mapping in the slot name somehow? Something like procedure_roles?

@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 good! I agree with Philipp, I think the default_role should be removed. Essentially if a procedure is configured with some role, check if the sender has that role, if not require the owner.

Also left some nits regarding masm formatting, but these might be removed if / once default_role is removed.

Comment on lines +113 to +119
# Resolve the calling procedure's root and look up its assigned role in the map.
padw caller
# => [CALLER_ROOT, default_role, 0, 0]
push.AUTHORITY_ROLE_MAP_SLOT[0..2]
# => [slot_suffix, slot_prefix, CALLER_ROOT, default_role, 0, 0]
exec.active_account::get_map_item
# => [mapped_role, 0, 0, 0, default_role, 0, 0]

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.

formatting:

Suggested change
# Resolve the calling procedure's root and look up its assigned role in the map.
padw caller
# => [CALLER_ROOT, default_role, 0, 0]
push.AUTHORITY_ROLE_MAP_SLOT[0..2]
# => [slot_suffix, slot_prefix, CALLER_ROOT, default_role, 0, 0]
exec.active_account::get_map_item
# => [mapped_role, 0, 0, 0, default_role, 0, 0]
# Resolve the calling procedure's root and look up its assigned role in the map.
padw caller
# => [CALLER_ROOT, default_role, 0, 0]
push.AUTHORITY_ROLE_MAP_SLOT[0..2]
# => [slot_suffix, slot_prefix, CALLER_ROOT, default_role, 0, 0]
exec.active_account::get_map_item
# => [mapped_role, 0, 0, 0, default_role, 0, 0]

Comment on lines +146 to +151
# Per-procedure role found — require it.
# => [mapped_role, default_role, 0, 0]
exec.rbac::assert_sender_has_role
# => [default_role, 0, 0]
drop drop drop
# => []

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.

I think the wording of this comment is a bit weird, can we update it to something like this? Also line formatting is off.

Suggested change
# Per-procedure role found — require it.
# => [mapped_role, default_role, 0, 0]
exec.rbac::assert_sender_has_role
# => [default_role, 0, 0]
drop drop drop
# => []
# Assert sender has role
# => [mapped_role, default_role, 0, 0]
exec.rbac::assert_sender_has_role
# => [default_role, 0, 0]
drop drop drop
# => []

Comment on lines +132 to +136
# Zero default role — defer to the Ownable2Step owner.
drop drop drop
# => []
exec.ownable2step::assert_sender_is_owner
# => []

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.

Suggested change
# Zero default role — defer to the Ownable2Step owner.
drop drop drop
# => []
exec.ownable2step::assert_sender_is_owner
# => []
# Zero default role, defer to the Ownable2Step owner.
drop drop drop
# => []
exec.ownable2step::assert_sender_is_owner
# => []

@bobbinth bobbinth 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 good! Thank you! I agree with @PhilippGackstatter and @partylikeits1983 that we could probably get rid of the default role and fall back to the owner immediately.

@PhilippGackstatter PhilippGackstatter 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.

LGTM - left a few non-blocking nits.

Comment on lines +185 to +191
fn config_word(&self) -> Word {
let discriminant = match self {
Authority::AuthControlled => AUTH_CONTROLLED,
Authority::OwnerControlled => OWNER_CONTROLLED,
Authority::RbacControlled { .. } => RBAC_CONTROLLED,
};
Word::new([Felt::from(discriminant), Felt::ZERO, Felt::ZERO, Felt::ZERO])

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: I'd maybe extract this logic into a separate fn as_u8(&self) -> u8 as it may become useful in other places.

Comment on lines +198 to +202
let slot = storage
.slots()
.iter()
.find(|slot| slot.name().id() == AUTHORITY_PROCEDURE_ROLES_SLOT_NAME.id())
.ok_or(AuthorityError::InvalidProcedureRolesSlot)?;

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: Should we align the name of the error with MissingStorageSlot and call it MissingProcedureRolesSlot since it's missing in this case, not invalid? Alternatively, we reuse and adapt MissingStorageSlot for this use case.

Comment on lines +184 to +185
/// Encodes the authority configuration value slot word: `[authority, 0, 0, 0]`.
fn config_word(&self) -> Word {

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.

I think this could be just to_word, now that it encodes only the discriminant.

Comment on lines +46 to +52
@@ -38,6 +47,9 @@ const ERR_PAUSABLE_IS_PAUSED: MasmError = MasmError::from_static_str("the contra

const ERR_SENDER_NOT_OWNER: MasmError = MasmError::from_static_str("note sender is not the owner");

const ERR_SENDER_LACKS_ROLE: MasmError =
MasmError::from_static_str("note sender does not hold the required role");

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.

These can all be imported as miden_standards::errors::standards::{ERR_PAUSABLE_IS_PAUSED, ERR_SENDER_LACKS_ROLE, ERR_SENDER_NOT_OWNER}

Comment on lines +389 to +401
let mut mock_chain = builder.build()?;
mock_chain.prove_next_block()?;

execute_note_on_faucet(&mut mock_chain, faucet.id(), &grant_pauser).await?;
execute_note_on_faucet(&mut mock_chain, faucet.id(), &grant_unpauser).await?;

// PAUSER pauses → the faucet records the paused state.
execute_note_on_faucet(&mut mock_chain, faucet.id(), &pause_note).await?;
assert!(is_faucet_paused(&mock_chain, faucet.id())?);

// UNPAUSER unpauses → the faucet records the unpaused state.
execute_note_on_faucet(&mut mock_chain, faucet.id(), &unpause_note).await?;
assert!(!is_faucet_paused(&mock_chain, faucet.id())?);

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.

This is a super clean test 🙌

@bobbinth bobbinth 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 good! Thank you!

@bobbinth bobbinth enabled auto-merge June 12, 2026 21:21
@bobbinth bobbinth added this pull request to the merge queue Jun 12, 2026
Merged via the queue into next with commit cb11680 Jun 12, 2026
20 checks passed
@bobbinth bobbinth deleted the authority-role-assignment branch June 12, 2026 21:38
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.

Extend Authority component to handle Role Assignment

4 participants