-
Notifications
You must be signed in to change notification settings - Fork 154
feat(agglayer): configure priced fee policies at deployment #3486
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: next
Are you sure you want to change the base?
Changes from all commits
9611f93
059ec00
bd884d8
cd44ca2
ba4b1b7
6a67dd0
96561ba
0f51cac
9be17fe
bb76288
0814b44
1d729e2
0fd1985
b787c15
490df44
0e1b272
f821485
8b05fec
a7fb9ac
c235c26
ef12824
a524eb8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -24,7 +24,7 @@ implementation are called out inline with `TODO (Future)` markers. | |
| | **AggLayer Faucet** | Fungible faucet that represents a single bridged token. Mints on bridge-in claims, burns on bridge-out. Each foreign token has its own faucet instance. | `FungibleFaucet`, network-mode, with `agglayer_faucet` component | | ||
| | **Integration Service** (offchain) | Observes L1 events (deposits, GER updates) and creates UPDATE_GER and CLAIM notes on Miden. Trusted to provide correct proofs and data. | Not an onchain entity; creates notes targeting bridge/faucet | | ||
| | **Bridge Operator** (offchain) | Deploys bridge and faucet accounts. Creates CONFIG_AGG_BRIDGE notes to register faucets. Must hold the `FAUCET_MNGR` role. | Not an onchain entity; creates config notes | | ||
| | **Role Admin** (offchain) | Holds the bridge's `ADMIN` role and manages role membership via RBAC_CONFIG notes. Root authority: effective admin of every operational role unless delegated, so compromise of this key is equivalent to compromise of all operational roles (see [Section 2.5](#25-administration)). | Not an onchain entity; creates RBAC_CONFIG notes | | ||
| | **Role Admin** (offchain) | Holds the `ADMIN` role on the bridge and on each faucet. Manages bridge role membership via RBAC_CONFIG notes and reprices both accounts' fee schedules via CONSTANT_FEE_POLICY_CONFIG notes. Root authority: effective admin of every operational role unless delegated, and on a faucet it can additionally retarget the `Ownable2Step` owner, so compromise of this key is equivalent to compromise of all operational roles (see [Section 2.5](#25-administration)). | Not an onchain entity; creates RBAC_CONFIG and CONSTANT_FEE_POLICY_CONFIG notes | | ||
|
|
||
| --- | ||
|
|
||
|
|
@@ -255,7 +255,8 @@ so while the bridge is paused it rejects all bridge-out, claim, GER-injection, a | |
| faucet-management operations. `remove_ger` is deliberately exempt: a paused bridge can still | ||
| revoke a fraudulent GER, and because `update_ger` is paused the revoked GER cannot be | ||
| re-injected until unpause. The management notes (`RBAC_CONFIG`, `NETWORK_ACCOUNT_CONFIG`, | ||
| `PAUSE_CONFIG`) remain consumable while paused, so a paused bridge can still be administered. | ||
| `PAUSE_CONFIG`, `CONSTANT_FEE_POLICY_CONFIG`) remain consumable while paused, so a paused bridge | ||
| can still be administered. | ||
|
|
||
| The pause is toggled via the standards [`PAUSE_CONFIG`](#411-pause_config-standards) note, which | ||
| dispatches to `PausableManager`'s `pause` / `unpause`. These have no entry in the bridge's | ||
|
|
@@ -265,6 +266,39 @@ The pause complements the `Authority` freeze switch: freezing blocks every autho | |
| procedure - including `remove_ger` - but not `claim` / `bridge_out`, while the pause is the | ||
| inverse. | ||
|
|
||
| #### Fee schedule administration | ||
|
|
||
| Both network accounts are deployed with a `BasicConstantFeePolicy` whose schedule prices every | ||
| note they can consume from that note's benchmarked consumption cost under the chain's | ||
| `FeeParameters` (`NetworkNotePricer::agglayer_bridge_fee_policy_manager` / | ||
| `agglayer_faucet_fee_policy_manager`). A schedule entry is a bare amount denominated in the | ||
| chain's fee asset, so it is only correct for the verification base fee it was priced under. | ||
|
|
||
| Because that base fee is a chain parameter which moves independently of these accounts, both | ||
| install the `miden-standards` `ConstantFeeManager` and allowlist the | ||
| [`CONSTANT_FEE_POLICY_CONFIG`](#412-constant_fee_policy_config-standards) note, which dispatches | ||
| to its `set_note_fee`. Neither account maps that procedure in its `Authority` procedure-to-role | ||
| map, so authorization falls back to the `ADMIN` role, exactly as for the pause toggles. Without | ||
| it a schedule would be frozen at its deployment prices for the account's whole lifetime. | ||
|
|
||
| Repricing the faucet matters as much as repricing the bridge. When the bridge creates a `MINT` or | ||
| `BURN` note it sizes that note's fee sponsorship by reading the *faucet's* schedule as a foreign | ||
| account, so a faucet left at stale prices makes the bridge under-sponsor the note it has just | ||
| created, and the faucet's transaction can no longer pay its own fee. A frozen faucet schedule | ||
| therefore stalls bridging, not merely faucet administration. | ||
|
|
||
| The config note's own script root is deliberately scheduled at zero on both accounts, even though | ||
| it has a benchmarked cost like any other note. Consuming one is the only route to `set_note_fee`, | ||
| so a priced entry could raise the config note's own fee past what a sponsor covers and put the | ||
| schedule permanently out of reach. | ||
|
Comment on lines
+290
to
+293
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In order for the network account to be able to consume the config note, the bridge still has to pay a fee somehow. And if we price the config note at zero, then the owner that sends a config note to the bridge has no way of estimating how much they should sponsor. So I think we need to also price-in the config notes |
||
|
|
||
| That reach is not hypothetical: a priced schedule requires every consumed note's fee to be | ||
| prepaid. A note whose schedule entry is non-zero can only be consumed together with | ||
| `FEE_SPONSORSHIP` notes bound to it that cover that entry, independently of the chain's own | ||
| verification base fee. Operators therefore fund a sponsorship alongside each management note | ||
| (`RBAC_CONFIG`, `NETWORK_ACCOUNT_CONFIG`, `PAUSE_CONFIG`), and repricing is the one | ||
| administrative action that never needs one. | ||
|
|
||
| --- | ||
|
|
||
| ## 3. Contracts and Public Interfaces | ||
|
|
@@ -454,7 +488,8 @@ access-control components at account creation time. | |
| ### 3.2 Faucet Account Component | ||
|
|
||
| The faucet account has the `agglayer_faucet` component (`components/faucet.masm`), | ||
| which is a thin wrapper, on top of `Ownable2Step` + `OwnerControlled`, that re-exports the | ||
| which is a thin wrapper, on top of `Ownable2Step` + the RBAC access-control stack | ||
| (`RoleBasedAccessControl` + `Authority::RbacControlled`), that re-exports the | ||
| standard Miden fungible-faucet procedures: | ||
|
|
||
| - `mint_and_send` (from `miden::standards::faucets::fungible::mint_and_send`) | ||
|
|
@@ -501,12 +536,25 @@ This is a re-export of `miden::standards::faucets::fungible::receive_and_burn`. | |
| |-----------|-----------|----------------|---------| | ||
| | Faucet metadata (standard) | Value | `[token_supply, max_supply, decimals, token_symbol]` | Standard `NetworkFungibleFaucet` metadata | | ||
|
|
||
| **Companion component storage slots:** The faucet account also includes storage from | ||
| companion components required by `fungible::mint_and_send`: | ||
| **Companion component storage slots:** The faucet account also includes storage from its | ||
| companion components - those required by `fungible::mint_and_send`, plus the access-control and | ||
| fee-administration stack: | ||
|
|
||
| - `Ownable2Step` owner config slot: stores the bridge account ID as owner. | ||
| - `OwnerControlled` slots (3): `active_policy_proc_root`, `allowed_policy_proc_roots`, | ||
| `policy_authority`. | ||
| - `TokenPolicyManager` slots: `active_policy_proc_root`, `allowed_policy_proc_roots` per policy | ||
| kind. | ||
| - `RoleBasedAccessControl` role storage, seeded with the deployment `ADMIN` member, and the | ||
| `Authority` config slot (`RbacControlled`) with an empty procedure-to-role map. | ||
| - `ConstantFeeManager` value slot: the ID of the fee schedule slot it reprices. | ||
|
|
||
| **Access control:** minting and burning are gated on the `Ownable2Step` owner (the bridge), which | ||
| `MintOwnerOnly` / `BurnOwnerOnly` assert directly via `ownable2step::assert_sender_is_owner` | ||
| rather than through `Authority`. Everything else on the faucet - its fee schedule, its mint / burn | ||
| / transfer policies and its metadata - is authority-gated and so resolves to the `ADMIN` role | ||
| through the unmapped-procedure fallback. The two authorities are therefore separate: `ADMIN` | ||
| administers the faucet's configuration without being able to mint, and the bridge mints without | ||
| being able to reconfigure. Note that `ADMIN` can retarget the `Ownable2Step` owner, so it must be | ||
| held by a strongly authenticated account (see [Section 1](#1-entities-and-trust-model)). | ||
|
|
||
| --- | ||
|
|
||
|
|
@@ -1072,7 +1120,7 @@ note via `output_note::add_asset`. | |
| **Purpose:** Toggles the bridge's emergency pause (see [Section 2.5](#25-administration)). This is | ||
| the `miden-standards` `PAUSE_CONFIG` note (`pause_config.masm` / `PauseConfigNote`), not an | ||
| agglayer-specific note; the bridge merely includes its script root in | ||
| [`AggLayerBridge::allowed_notes`]. Its single storage felt is a selector: `0` dispatches to | ||
| [`AggLayerBridge::bridge_notes`]. Its single storage felt is a selector: `0` dispatches to | ||
| `PausableManager::pause`, `1` to `PausableManager::unpause`. | ||
|
|
||
| **Consumption:** The script loads the selector and `call`s the matching `PausableManager` | ||
|
|
@@ -1092,6 +1140,40 @@ with clearer error reporting for non-public targets. | |
|
|
||
| --- | ||
|
|
||
| ### 4.12 CONSTANT_FEE_POLICY_CONFIG (standards) | ||
|
|
||
| **Purpose:** Reprices one entry of a network account's `BasicConstantFeePolicy` fee schedule | ||
| after deployment (see [Section 2.5](#25-administration)). This is the `miden-standards` | ||
| `CONSTANT_FEE_POLICY_CONFIG` note (`constant_fee_policy_config.masm` / | ||
| `ConstantFeePolicyConfigNote`), not an agglayer-specific note; both the bridge and the faucet | ||
| include its script root in their allowlists. Its storage is | ||
| `[NOTE_SCRIPT_ROOT, FEE_ASSET_ID, FEE_ASSET_VALUE]`: the schedule key to rewrite, and the fee to | ||
| schedule for it. | ||
|
|
||
| **Consumption:** The script asserts the consuming account matches its `NetworkAccountTarget` | ||
| attachment, then `call`s `ConstantFeeManager::set_note_fee`, which runs | ||
| `authority::assert_authorized` before writing the schedule entry. The supplied asset's ID must | ||
| match the account's configured fee asset ID. On both AggLayer accounts `set_note_fee` has no | ||
| mapped role, so the note sender must hold the `ADMIN` role. | ||
|
|
||
| `set_note_fee` carries no `pausable::assert_not_paused`, so a paused bridge can still be | ||
| repriced, consistent with the other management notes. | ||
|
|
||
| Because the note is allowlisted and scheduled free, anyone can author one targeting either | ||
| account; unauthorized or misdirected notes abort at the target and authorization checks with no | ||
| state change, but since the transaction aborts no nullifier is produced and such notes remain as | ||
| permanently-unconsumable entries that may require operator-side filtering. This is inherent to | ||
| every allowlisted network-note root, not specific to this note. | ||
|
|
||
| #### Permissions | ||
|
|
||
| | Role | Enforcement | | ||
| |------|------------| | ||
| | **Issuer** | Holders of the `ADMIN` role only -- **enforced** by `ConstantFeeManager::set_note_fee` via `authority::assert_authorized` (unmapped-procedure fallback) | | ||
| | **Consumer** | Bridge or faucet account -- **enforced**: the script asserts the consuming account matches the `NetworkAccountTarget` attachment | | ||
|
|
||
| --- | ||
|
|
||
| ## 5. Amount Conversion | ||
|
|
||
| *This section is a placeholder. Content to be added.* | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,7 +20,9 @@ use miden_protocol::note::{Note, NoteScriptRoot}; | |
| #[cfg(any(feature = "testing", test))] | ||
| use miden_standards::account::access::PausableStorage; | ||
| use miden_standards::account::access::RoleConfig; | ||
| use miden_standards::account::auth::AuthNetworkAccount; | ||
| use miden_standards::note::{ | ||
| ConstantFeePolicyConfigNote, | ||
| NetworkAccountTarget, | ||
| NetworkAccountTargetError, | ||
| NoteExecutionHint, | ||
|
|
@@ -470,20 +472,21 @@ impl AggLayerBridge { | |
| // ALLOWED SCRIPTS | ||
| // -------------------------------------------------------------------------------------------- | ||
|
|
||
| /// Returns the set of input-note script roots that AggLayer bridge accounts accept. | ||
| /// Returns the bridge-specific input-note script roots. | ||
| /// | ||
| /// The bridge's [`AuthNetworkAccount`] component is initialized with this allowlist, which | ||
| /// means any transaction consuming a note outside this set is rejected before reaching | ||
| /// `output_note::create`. | ||
| /// The bridge's [`AuthNetworkAccount`] component is initialized with this set as its explicit | ||
| /// allowlist; [`Self::allowed_notes`] is the resulting full set of notes the account accepts. | ||
| /// | ||
| /// Besides the agglayer-specific notes, the bridge accepts two standards notes: the | ||
| /// [`PauseConfigNote`], so the `ADMIN` role can toggle the emergency pause, and the | ||
| /// Besides the agglayer-specific notes, the set contains three standards notes: the | ||
| /// [`PauseConfigNote`], so the `ADMIN` role can toggle the emergency pause, the | ||
| /// role-management [`RbacConfigNote`], which makes the bridge's RBAC role graph mutable | ||
| /// on-chain (see the [`RbacConfigNote`] security considerations and the Administration | ||
| /// section of `SPEC.md` for the associated caveats). | ||
| /// section of `SPEC.md` for the associated caveats), and the | ||
| /// [`ConstantFeePolicyConfigNote`], so the `ADMIN` role can reprice the bridge's fee | ||
| /// schedule after deployment. | ||
| /// | ||
| /// [`AuthNetworkAccount`]: miden_standards::account::auth::AuthNetworkAccount | ||
| pub fn allowed_notes() -> BTreeSet<NoteScriptRoot> { | ||
| pub fn bridge_notes() -> BTreeSet<NoteScriptRoot> { | ||
| BTreeSet::from([ | ||
| ClaimNote::script_root(), | ||
| B2AggNote::script_root(), | ||
|
|
@@ -493,9 +496,26 @@ impl AggLayerBridge { | |
| RemoveGerNote::script_root(), | ||
| PauseConfigNote::script_root(), | ||
| RbacConfigNote::script_root(), | ||
| ConstantFeePolicyConfigNote::script_root(), | ||
| ]) | ||
| } | ||
|
|
||
| /// Returns every input-note script root a newly deployed AggLayer bridge account accepts. | ||
| /// | ||
| /// This is [`Self::bridge_notes`] plus the configuration and sponsorship notes that | ||
| /// [`AuthNetworkAccount`] adds to every standard network account. Any transaction consuming a | ||
| /// note outside the account's allowlist is rejected before reaching `output_note::create`, | ||
| /// and the bridge's deployed fee policy must schedule a fee for every root in it. | ||
| /// | ||
| /// This is the deployment-time allowlist: an `ADMIN`-authored `NETWORK_ACCOUNT_CONFIG` note | ||
| /// can add or remove entries later, so read a live account's allowlist via | ||
| /// [`NetworkAccount::allowed_notes`](miden_standards::account::auth::NetworkAccount::allowed_notes). | ||
| pub fn allowed_notes() -> BTreeSet<NoteScriptRoot> { | ||
| let mut notes = Self::bridge_notes(); | ||
| notes.extend(AuthNetworkAccount::default_allowed_note_scripts()); | ||
| notes | ||
| } | ||
|
|
||
|
Comment on lines
+513
to
+518
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why exactly do we need to differentiate between |
||
| // PAUSE NOTE | ||
| // -------------------------------------------------------------------------------------------- | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how does this fit in with #2724?
The idea was that the faucet does not have ownership transfer.