feat(agglayer): configure priced fee policies at deployment - #3486
feat(agglayer): configure priced fee policies at deployment#3486partylikeits1983 wants to merge 22 commits into
Conversation
Make the fee policy manager a required argument of the AggLayer account builders instead of an optional setter whose absence panicked in `build()`, and expose the faucet's asset callback flag on the builder. The bridge-in claim-to-P2ID test regained the callback-enabled faucet it lost when it moved to the priced helper, and the bridge-out test keeps pinning all 32 bundled Solidity MTF vectors.
Both AggLayer network accounts install the ConstantFeeManager and allowlist CONSTANT_FEE_POLICY_CONFIG notes, so their ADMIN role can reprice a schedule that was otherwise frozen at deployment. The bridge sizes the sponsorship of every MINT and BURN note it creates from the faucet's schedule, so leaving the faucet frozen would stall bridging once the chain's verification base fee moved. The faucet moves from Authority::OwnerControlled to RbacControlled to make that reachable at all: with the bridge as its Ownable2Step owner, every authority-gated procedure was gated on a sender nothing can produce. Minting and burning are unaffected, since MintOwnerOnly and BurnOwnerOnly check the owner slot directly rather than going through Authority. The config note is scheduled free rather than at its benchmarked cost. Consuming one is the only route to set_note_fee, so pricing it would let the schedule put the note that repairs it out of reach.
Documents what the fee policy work changed: the deployed schedules and how they are repriced, the faucet's move to RBAC and the resulting split between owner-gated minting and ADMIN-gated configuration, and the CONSTANT_FEE_POLICY_CONFIG note. Also records the operational consequence of a priced schedule that was previously undocumented: a note with a non-zero schedule entry can only be consumed alongside FEE_SPONSORSHIP notes covering it, regardless of the chain's own base fee, so every management note needs a sponsorship funded with it. Pins the faucet allowlist the way the bridge's already was, and covers repricing a paused bridge.
…-fee-policy # Conflicts: # crates/miden-testing/tests/agglayer/test_utils.rs
Adds a find_output_note helper for the six copies of the output-note-by-script-root predicate, returns Option<Note> from add_fee_sponsorship so the fee axis stops being threaded through if/else blocks, and uses the existing authenticated_input_notes iterator overload to restore the builder chains. The priced-account pin test now reuses build_managed_account instead of rebuilding the same two fixtures verbatim.
Fumuran
left a comment
There was a problem hiding this comment.
Looks great! It's a partial review, I only looked through non-testing code. Left just two not-that-important questions.
zeapoz
left a comment
There was a problem hiding this comment.
Looks good! Didn't go through the new tests in minute detail but they seem alright from a quick glance
Co-authored-by: zeapoz <zeapo@pm.me>
…lpers Address review feedback on the deployment builder API: - drop the AggLayerBridgeAccountBuilder and AggLayerFaucetAccountBuilder wrappers; AggLayerBridge::account_builder and AggLayerFaucet::account_builder now return the underlying AccountBuilder directly - move the test-only helpers (zero_fee_policy_manager, faucet_account_builder, create_existing_agglayer_faucet) into miden_agglayer::testing
Address review feedback on the note-set naming: the former fee_policy_notes returned the full set of notes the account actually accepts, so it becomes allowed_notes, and the component-specific subsets become bridge_notes and faucet_notes. Record the rename in the changelog and move this PR's entries back under the unreleased v0.17.0 section, where the released v0.16.0 section had absorbed them during the merge from next.
| pub fn allowed_notes() -> BTreeSet<NoteScriptRoot> { | ||
| let mut notes = Self::bridge_notes(); | ||
| notes.extend(AuthNetworkAccount::default_allowed_note_scripts()); | ||
| notes | ||
| } | ||
|
|
There was a problem hiding this comment.
why exactly do we need to differentiate between bridge_notes and allowed_notes? Couldn't we just extend the set of allowed notes with AuthNetworkAccount::default_allowed_note_scripts()?
| 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. |
There was a problem hiding this comment.
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
| | **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 | |
There was a problem hiding this comment.
how does this fit in with #2724?
The idea was that the faucet does not have ownership transfer.
| crate::create_agglayer_faucet_builder( | ||
| seed, | ||
| token_symbol, | ||
| decimals, | ||
| max_supply, | ||
| initial_supply, | ||
| admin, | ||
| bridge_account_id, | ||
| fee_policy_manager, | ||
| ) | ||
| } | ||
|
|
||
| /// Creates an existing agglayer faucet account with the specified configuration, priced by a | ||
| /// zero-fee policy and administered by [`bridge_admin_account_id`]. | ||
| pub fn create_existing_agglayer_faucet( | ||
| seed: Word, | ||
| token_symbol: &str, | ||
| decimals: u8, | ||
| max_supply: Felt, | ||
| initial_supply: Felt, | ||
| bridge_account_id: AccountId, | ||
| ) -> Account { | ||
| faucet_account_builder( | ||
| seed, | ||
| token_symbol, | ||
| decimals, | ||
| max_supply, | ||
| initial_supply, | ||
| bridge_admin_account_id(), | ||
| bridge_account_id, | ||
| zero_fee_policy_manager(AggLayerFaucet::allowed_notes()), | ||
| ) |
There was a problem hiding this comment.
I think we could get rid of faucet_account_builder and have the callers directly call create_agglayer_faucet_builder
| create_agglayer_faucet_builder( | ||
| seed, | ||
| token_symbol, | ||
| decimals, | ||
| max_supply, | ||
| Felt::ZERO, | ||
| admin, | ||
| bridge_account_id, | ||
| fee_policy_manager, | ||
| ) |
There was a problem hiding this comment.
could we inline create_agglayer_faucet_builder?
Combined with the other comments, this would effectively collapse all callers to use AggLayerFaucet::account_builder
Closes #3487
Related to #3367
Adds
AggLayerBridge::account_builderandAggLayerFaucet::account_builder, which take the fee policy manager as a required argument so an account cannot be deployed unpriced.NetworkNotePricerprovides one-call constructors for productionBasicConstantFeePolicymanagers, pricing every consumed note root from benchmarked costs.Fee-enabled E2E cases cover CLAIM through recipient balance and B2AGG through BURN.