Use dynamic dispatch in `Authority`
- Lingua principale
- Rust
- Stelle
- 132
- Fork
- 167
- Merge medio
- 1g 22h
- PR unite (30g)
- 116
Descrizione
## Summary
Components that must invoke a procedure of a different component currently hard-code the target with a static `exec`, and select it with an `if-else` chain over a configuration value. Replace this with a storage slot that holds the procedure root of the target, and invoke that root with `dyncall`. The slot schema marks the slot as a dependency procedure root, so tooling can find every such root from the storage schema alone.
## Background
The idea comes from [this review discussion](https://github.com/0xMiden/protocol/pull/3527#discussion_r3798650393). It separates two different kinds of dependency:
- Static dependency: the code that a component needs to compile. `Authority` needs the `Rbac` and `Ownable2Step` modules to link, because it names their procedures directly.
- Runtime dependency: the components that must be installed on an account for another component to operate. Which one applies depends on a configuration value that is only known at instantiation time, so the condition cannot be expressed statically.
Both of these are affected by this refactor:
- The refactoring removes the static dependency: when `Authority` dispatches through a stored root, it no longer names `ownable2step` or `rbac`, and it assembles without them.
- The runtime dependency (e.g. rbac) can be read from the stored proc root, and any component not used (e.g. ownable2step) does not appear as a runtime dependency.
## The model already exists in the codebase
Two components already hold procedure roots in storage and dispatch with `dyncall` and serve as the reference for the refactoring:
- [`TokenPolicyManager`](https://github.com/0xMiden/protocol/blob/3320f0a1b7c31b458cad0563d2a1e47cb13a2eff/crates/miden-standards/asm/standards/faucets/policies/policy_manager.masm#L19).
- [`FeeManager`](https://github.com/0xMiden/protocol/blob/3320f0a1b7c31b458cad0563d2a1e47cb13a2eff/crates/miden-standards/asm/standards/fees/fee_manager.masm#L33).
What needs to be done here is declaring their proc root slots in the schema as a "stored procedure root" to prepare them for dependency resolution.
## The component to refactor: Authority
[`Authority`](https://github.com/0xMiden/protocol/blob/3320f0a1b7c31b458cad0563d2a1e47cb13a2eff/crates/miden-standards/asm/standards/access/authority.masm) is the only component that selects a dependency with a branch over a stored discriminator. It stores `authority` and dispatches on it in two places:
- [`assert_authorized`](https://github.com/0xMiden/protocol/blob/3320f0a1b7c31b458cad0563d2a1e47cb13a2eff/crates/miden-standards/asm/standards/access/authority.masm#L120) does nothing under `AuthControlled`, calls `ownable2step::assert_sender_is_owner` under `OwnerControlled`, and calls [`assert_authorized_rbac`](https://github.com/0xMiden/protocol/blob/3320f0a1b7c31b458cad0563d2a1e47cb13a2eff/crates/miden-standards/asm/standards/access/authority.masm#L250) under `RbacControlled`.
- [`assert_sender_is_emergency_authority`](https://github.com/0xMiden/protocol/blob/3320f0a1b7c31b458cad0563d2a1e47cb13a2eff/crates/miden-standards/asm/standards/access/authority.masm#L181) makes the same selection for `freeze` and `unfreeze`, and bypasses the frozen flag.
After the refactoring the component holds two slots:
- `ASSERT_IS_AUTHORIZED_PROC_ROOT` which holds the root of the new `assert_is_authorized` procedure.
- `FROZEN_FLAG` which holds `[is_frozen, 0, 0, 0]`.
The signature of `assert_is_authorized` is:
```
#! Inputs: [CALLER_PROC_ROOT, pad(12)]
#! Outputs: [pad(16)]
```
It receives the procedure root obtained via `caller` as a parameter. This is necessary so `assert_authorized_rbac` keeps working. If it used `caller` itself, it would get the wrong root as it is now `dyncalled` instead of `exec`-ed.
`Authority` must become agnostic of the authorization schemes and must no longer name `ownable2step` or `rbac`. Each scheme must expose one procedure with the agreed signature. `Authority` keeps only the stored root and the frozen flag in its storage.
- `AuthControlled` sets the root to the empty word (which will skip the invocation).
- The owner scheme needs no new component. [`assert_sender_is_owner`](https://github.com/0xMiden/protocol/blob/3320f0a1b7c31b458cad0563d2a1e47cb13a2eff/crates/miden-standards/asm/standards/access/ownable2step.masm#L245) is an `exec` helper today, so `Ownable2Step` only gets a `call` entry point for it.
- The RBAC scheme needs a new `RbacAuthority` component, because the per-procedure role map that `Authority` holds today needs a dedicated owner, and `Authority` should be agnostic to the underlying scheme. The logic of its `assert_is_authorized` is essentially today's `assert_authorized_rbac`.
The frozen flag stays in `Authority` and the dispatch becomes uniform for all three entry points:
- `assert_authorized` first asserts that the account is not frozen, then reads the stored root. An empty word means that no check applies and the procedure returns. Otherwise it `dyncall`s the root with the procedure root from `caller`.
- `freeze` and `unfreeze` skip the frozen check, read the same slot, and panic when it holds the empty word, because an account without an authorization scheme has no emergency authority. Otherwise they `dyncall` the `assert_is_authorized` with their own procedure root.
This removes `assert_sender_is_emergency_authority` and needs no second slot.
One consequence to keep in mind: the dispatch target is an account procedure that the kernel records as called. Auth components that gate on the set of called procedures, such as the per-procedure thresholds of `multisig_smart`, now see it.
Notably, the collection and verification step in the account builder is the general dependency resolution problem. It is tracked in [#2621](https://github.com/0xMiden/protocol/issues/2621) and is not part of this issue. This issue covers only the component-side refactoring, which is the precondition for it.
## Components that are not affected
[`MintOwnerOnly`](https://github.com/0xMiden/protocol/blob/3320f0a1b7c31b458cad0563d2a1e47cb13a2eff/crates/miden-standards/asm/standards/faucets/policies/mint/owner_controlled/owner_only.masm#L32) and [`BurnOwnerOnly`](https://github.com/0xMiden/protocol/blob/3320f0a1b7c31b458cad0563d2a1e47cb13a2eff/crates/miden-standards/asm/standards/faucets/policies/burn/owner_controlled/owner_only.masm#L27) call `ownable2step::assert_sender_is_owner` directly, but this is a single static target. They declare `Ownable2Step` as a dependency under #2621.
[`pausable::assert_not_paused`](https://github.com/0xMiden/protocol/blob/3320f0a1b7c31b458cad0563d2a1e47cb13a2eff/crates/miden-standards/asm/standards/access/pausable/mod.masm#L127) tests whether the pause slot is installed and skips the check when it is not. A stored root that holds the empty word to say "skip the check" gives the same result as the `has_storage_slot` test, so the check stays as it is.
Guida per i contributori
Apri la guida per i contributori
Direzione di ricerca
Start with crates/miden-standards/asm/standards/access/authority.masm, then compare the stored-root and dyncall patterns in policy_manager.masm and fee_manager.masm. Review ownable2step.masm and the Authority entry points, keeping #2621 out of scope; done means Authority no longer names the authorization schemes, its schema exposes the dependency root, and the affected dispatch paths use the agreed procedure signature.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Valutazione
- Ambito
- blockchain
- Tipo di issue
- Refactoring
- Difficoltà
- 5/5
- Tempo stimato
- Più di una settimana
- Stato di attività
- Attiva
- Chiarezza
- Abbastanza chiara
- Idoneità per principianti
- 35/100