Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@

### Fixes

- Generated constant fee schedules now assign `FEE_SPONSORSHIP` an explicit zero fee, matching the fee-collection exemption while keeping the note allowlisted ([#3580](https://github.com/0xMiden/protocol/issues/3580)).
- [BREAKING] Bound the non-fungible MINT note to its faucet the same way the fungible one is bound: the note now stores the full asset and `non_fungible::mint_and_send` asserts the stored `ASSET_ID` against the asset it derives for the active faucet, unifying the two MINT note storage layouts and collapsing `MintNoteStorage` to `Private` / `Public` ([#3482](https://github.com/0xMiden/protocol/pull/3482)).
- Fixed the multisig, guarded, non-fungible, and AggLayer faucet factories not enabling asset callbacks for faucets configured with a transfer policy ([#3547](https://github.com/0xMiden/protocol/pull/3547)).
- [BREAKING] AggLayer faucets now allowlist and price `RBAC_CONFIG` notes so their roles, including `ADMIN`, can be rotated after deployment ([#3570](https://github.com/0xMiden/protocol/issues/3570)).
Expand Down
3 changes: 2 additions & 1 deletion crates/miden-agglayer/SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,8 @@ inverse.

Both network accounts deploy with a `BasicConstantFeePolicy` generated by
`NetworkNotePricer::basic_constant_fee_policy`. The schedule uses the chain's fee asset and prices
every root in the account's deployment allowlist, including `FEE_SPONSORSHIP`.
every feature-note root in the account's deployment allowlist. `FEE_SPONSORSHIP` remains
allowlisted and has an explicit zero fee because it never requires a sponsorship of its own.

The bridge's and each faucet's `FEE_MNGR` holders update that account's schedule through
`CONSTANT_FEE_POLICY_CONFIG` notes. The account's `ADMIN` retains control of fee-policy selection
Expand Down
55 changes: 41 additions & 14 deletions crates/miden-tx/src/pricer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ use miden_protocol::errors::AssetError;
use miden_protocol::note::NoteScriptRoot;
use miden_protocol::transaction::{TransactionFee, TransactionFeeError};
use miden_standards::account::fees::{BasicConstantFeePolicy, FeePolicyManager};
use miden_standards::note::StandardNote;
use miden_standards::note::costs::NoteCost;
use miden_standards::note::{FeeSponsorshipNote, StandardNote};

// NETWORK NOTE PRICER
// ================================================================================================
Expand Down Expand Up @@ -105,6 +105,10 @@ impl NetworkNotePricer {
/// price(N) = fee(cycles(N)) + sum(price(M) for M created by consuming N)
/// ```
///
/// [`FeeSponsorshipNote`] defaults to zero because standard network-account fee collection
/// exempts sponsorship notes from sponsoring themselves. A cost supplied through the builder's
/// `note_cost` or `note_costs` methods takes precedence over this default.
///
/// Since a script root alone cannot tell whether a created note will be network-targeted,
/// EVERY created note is priced in, suiting root-keyed fee schedules - though like the
/// underlying costs, the result is an estimate, not a guaranteed upper bound (see the
Expand All @@ -117,12 +121,12 @@ impl NetworkNotePricer {
AssetAmount::new(price).map_err(NotePricingError::PriceExceedsMaxAssetAmount)
}

/// Builds a [`BasicConstantFeePolicy`] that prices every supplied note script root from its
/// benchmarked consumption cost.
/// Builds a [`BasicConstantFeePolicy`] that prices every supplied note script root through
/// [`Self::price`].
///
/// The policy's bare fee amounts are denominated in the fee asset configured by
/// [`Self::fee_parameters`]. Each root is priced through [`Self::price`], so the fee includes
/// the default safety margin and the recursively priced notes created by consuming it.
/// [`Self::fee_parameters`], so each fee includes the default safety margin and the recursively
/// priced notes created by consuming it.
pub fn basic_constant_fee_policy(
&self,
note_script_roots: impl IntoIterator<Item = NoteScriptRoot>,
Expand All @@ -134,8 +138,8 @@ impl NetworkNotePricer {
Ok(policy)
}

/// Builds a fee policy manager whose active [`BasicConstantFeePolicy`] prices every supplied
/// note script root from its benchmarked consumption cost.
/// Builds a fee policy manager whose active [`BasicConstantFeePolicy`] is generated from the
/// supplied note script roots.
///
/// The manager charges in the fee asset configured by [`Self::fee_parameters`], keeping the
/// policy's bare fee amounts and their denomination together.
Expand All @@ -157,12 +161,11 @@ impl NetworkNotePricer {
root: NoteScriptRoot,
pricing_stack: &mut Vec<NoteScriptRoot>,
) -> Result<u64, NotePricingError> {
let cost = self
.note_costs
.get(&root)
.cloned()
.or_else(|| resolve_note_cost(root))
.ok_or(NotePricingError::UnknownNoteScriptRoot(root))?;
let cost = match self.note_costs.get(&root).cloned() {
Some(cost) => cost,
None if root == FeeSponsorshipNote::script_root() => return Ok(0),
None => resolve_note_cost(root).ok_or(NotePricingError::UnknownNoteScriptRoot(root))?,
};
Comment thread
partylikeits1983 marked this conversation as resolved.
Outdated
// Cycle counts enter the fee computation only here, where the looked-up cost is
// converted into the kernel's fee inputs.
let fee_inputs = TransactionFee::new(cost.cycles()).map_err(NotePricingError::Fee)?;
Expand Down Expand Up @@ -220,7 +223,12 @@ mod tests {
P2ID_CONSUMPTION_CYCLES,
SWAP_CONSUMPTION_CYCLES,
};
use miden_standards::note::{ConstantFeePolicyConfigNote, P2idNote, SwapNote};
use miden_standards::note::{
ConstantFeePolicyConfigNote,
FeeSponsorshipNote,
P2idNote,
SwapNote,
};

use super::*;

Expand Down Expand Up @@ -449,6 +457,25 @@ mod tests {
);
}

#[test]
fn sponsorship_defaults_to_zero_but_allows_a_cost_override() {
let root = FeeSponsorshipNote::script_root();

let default_pricer = pricer(500, 0);
let default_policy = default_pricer.basic_constant_fee_policy([root]).unwrap();
assert_eq!(default_pricer.price(root).unwrap(), AssetAmount::ZERO);
assert_eq!(default_policy.fee_schedule().get(&root), Some(&AssetAmount::ZERO));

const CUSTOM_SPONSORSHIP_CYCLES: u32 = 65_536;
let custom_pricer =
custom_pricer([(root, NoteCost::new(CUSTOM_SPONSORSHIP_CYCLES, Vec::new()))]);
let custom_price = custom_pricer.fee(fee_inputs(CUSTOM_SPONSORSHIP_CYCLES)).unwrap();
let custom_policy = custom_pricer.basic_constant_fee_policy([root]).unwrap();

assert_eq!(custom_pricer.price(root).unwrap(), custom_price);
assert_eq!(custom_policy.fee_schedule().get(&root), Some(&custom_price));
}

#[test]
fn basic_constant_fee_policy_rejects_unknown_roots() {
let unknown = NoteScriptRoot::from_array([9, 9, 9, 9]);
Expand Down
Loading