miden_standards/note/costs/mod.rs
1//! Benchmarked consumption costs of the standard notes, and helpers turning them into fees.
2//!
3//! Each constant is the number of VM cycles of the canonical network-account transaction
4//! consuming the note, measured by the `bench-transaction` binary: an account authenticated
5//! with [`AuthNetworkAccount`](crate::account::auth::AuthNetworkAccount) (carrying the
6//! components the note requires) consumes the note on a fee-charging chain, so the measured
7//! cycles include the allowlist checks and TX_FEE note creation.
8//!
9//! The values are denominated in cycles rather than fee units, since the fee
10//! (`verification_base_fee * (ilog2(cycles) + 1)`) depends on a block-header parameter. Use
11//! the `NetworkNotePricer` in `miden-tx` to turn cycle costs into concrete fees and populate a
12//! fee schedule via
13//! [`BasicConstantFeePolicy::with_fees`](crate::account::fees::BasicConstantFeePolicy::with_fees).
14//!
15//! The values are estimates from canonical scenarios, not worst cases: asset-scaling paths
16//! carry 16 callback-free assets (the maximum per note) and action notes run one selector, so
17//! callback-carrying notes can exceed the values - do not treat them as guaranteed fee upper
18//! bounds.
19//!
20//! Terminology: a note's *cost* is its measured cycle count; its *price* is the fee derived
21//! from that cost (and from the costs of the notes its consumption creates).
22//!
23//! The table is regenerated with `make update-note-costs`; a snapshot test in
24//! `bench-transaction` fails CI when a checked-in value drifts more than 5% from the measured
25//! one (small drift from unrelated changes is tolerated - the pricing safety margin dwarfs
26//! it).
27
28use alloc::vec::Vec;
29
30use miden_protocol::note::NoteScriptRoot;
31
32use crate::note::{
33 AllowlistConfigNote,
34 BlocklistConfigNote,
35 BurnNote,
36 ConstantFeePolicyConfigNote,
37 FaucetMetadataConfigNote,
38 FaucetPolicyConfigNote,
39 FeeSponsorshipNote,
40 MinBurnAmountConfigNote,
41 MintNote,
42 NetworkAccountConfigNote,
43 OwnerConfigNote,
44 P2idNote,
45 P2ideNote,
46 PauseConfigNote,
47 PswapNote,
48 RbacConfigNote,
49 StandardNote,
50 SwapNote,
51};
52
53mod table;
54pub use table::*;
55
56// NOTE CONSUMPTION COST
57// ================================================================================================
58
59/// Benchmarked consumption cost of a note when consumed by a network account.
60///
61/// Implemented by every priced note type in `miden-standards` and `miden-agglayer`; the values
62/// come from the generated cost tables (see the module docs).
63pub trait NoteConsumptionCost {
64 /// Cycles of the canonical network-account transaction consuming this note
65 /// (maximum across the benchmarked execution paths - an estimate, not a worst case).
66 fn consumption_cycles() -> u32;
67
68 /// Script roots of all the notes this note's consumption is expected to create.
69 ///
70 /// Where a note's outputs are chosen by its creator rather than fixed by the script (e.g. a
71 /// MINT note's recipient digest may encode any script), the list covers the typical case.
72 fn created_notes() -> Vec<NoteScriptRoot> {
73 Vec::new()
74 }
75}
76
77// NOTE COST
78// ================================================================================================
79
80/// A note's benchmarked consumption cost together with the notes its consumption creates.
81#[derive(Debug, Clone, PartialEq, Eq)]
82pub struct NoteCost {
83 cycles: u32,
84 created_notes: Vec<NoteScriptRoot>,
85}
86
87impl NoteCost {
88 /// Returns a new [`NoteCost`] from a note's consumption cycles and the script roots of the
89 /// notes its consumption creates.
90 pub fn new(cycles: u32, created_notes: Vec<NoteScriptRoot>) -> Self {
91 Self { cycles, created_notes }
92 }
93
94 /// Returns a [`NoteCost`] read from the given note type's [`NoteConsumptionCost`] impl.
95 pub fn of<N: NoteConsumptionCost>() -> Self {
96 Self::new(N::consumption_cycles(), N::created_notes())
97 }
98
99 /// Cycles of the canonical network-account transaction consuming the note (maximum across
100 /// the benchmarked execution paths - an estimate, not a worst case).
101 pub fn cycles(&self) -> u32 {
102 self.cycles
103 }
104
105 /// Script roots of the notes created when the note is consumed.
106 pub fn created_notes(&self) -> &[NoteScriptRoot] {
107 &self.created_notes
108 }
109}
110
111impl StandardNote {
112 /// Returns the benchmarked consumption cost of the standard note with the given script
113 /// root, or `None` if the root does not match a priced standard note.
114 ///
115 /// TX_FEE is not priced: it is consumed by fee-collecting operators, not by network
116 /// accounts.
117 pub fn note_cost(root: NoteScriptRoot) -> Option<NoteCost> {
118 match StandardNote::from_script_root(root)? {
119 StandardNote::P2ID => Some(NoteCost::of::<P2idNote>()),
120 StandardNote::P2IDE => Some(NoteCost::of::<P2ideNote>()),
121 StandardNote::SWAP => Some(NoteCost::of::<SwapNote>()),
122 StandardNote::PSWAP => Some(NoteCost::of::<PswapNote>()),
123 StandardNote::MINT => Some(NoteCost::of::<MintNote>()),
124 StandardNote::BURN => Some(NoteCost::of::<BurnNote>()),
125 StandardNote::CONSTANT_FEE_POLICY_CONFIG => {
126 Some(NoteCost::of::<ConstantFeePolicyConfigNote>())
127 },
128 StandardNote::FAUCET_POLICY_CONFIG => Some(NoteCost::of::<FaucetPolicyConfigNote>()),
129 StandardNote::FAUCET_METADATA_CONFIG => {
130 Some(NoteCost::of::<FaucetMetadataConfigNote>())
131 },
132 StandardNote::MIN_BURN_AMOUNT_CONFIG => Some(NoteCost::of::<MinBurnAmountConfigNote>()),
133 StandardNote::ALLOWLIST_CONFIG => Some(NoteCost::of::<AllowlistConfigNote>()),
134 StandardNote::BLOCKLIST_CONFIG => Some(NoteCost::of::<BlocklistConfigNote>()),
135 StandardNote::PAUSE_CONFIG => Some(NoteCost::of::<PauseConfigNote>()),
136 StandardNote::OWNER_CONFIG => Some(NoteCost::of::<OwnerConfigNote>()),
137 StandardNote::RBAC_CONFIG => Some(NoteCost::of::<RbacConfigNote>()),
138 StandardNote::NETWORK_ACCOUNT_CONFIG => {
139 Some(NoteCost::of::<NetworkAccountConfigNote>())
140 },
141 StandardNote::FEE_SPONSORSHIP => Some(NoteCost::of::<FeeSponsorshipNote>()),
142 StandardNote::TX_FEE => None,
143 }
144 }
145}
146
147// TESTS
148// ================================================================================================
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153 use crate::note::TxFeeNote;
154
155 /// Pins each priced standard note's cost to its own table constant: a swap between two
156 /// note types' impls could otherwise hide inside the bench snapshot tests' 5% drift
157 /// tolerance (several constants differ by less than that).
158 #[test]
159 fn note_cost_pins_every_priced_standard_note_to_its_table_constant() {
160 for (root, expected_cycles) in [
161 (P2idNote::script_root(), P2ID_CONSUMPTION_CYCLES),
162 (P2ideNote::script_root(), P2IDE_CONSUMPTION_CYCLES),
163 (SwapNote::script_root(), SWAP_CONSUMPTION_CYCLES),
164 (PswapNote::script_root(), PSWAP_CONSUMPTION_CYCLES),
165 (MintNote::script_root(), MINT_CONSUMPTION_CYCLES),
166 (BurnNote::script_root(), BURN_CONSUMPTION_CYCLES),
167 (
168 ConstantFeePolicyConfigNote::script_root(),
169 CONSTANT_FEE_POLICY_CONFIG_CONSUMPTION_CYCLES,
170 ),
171 (FaucetPolicyConfigNote::script_root(), FAUCET_POLICY_CONFIG_CONSUMPTION_CYCLES),
172 (
173 FaucetMetadataConfigNote::script_root(),
174 FAUCET_METADATA_CONFIG_CONSUMPTION_CYCLES,
175 ),
176 (
177 MinBurnAmountConfigNote::script_root(),
178 MIN_BURN_AMOUNT_CONFIG_CONSUMPTION_CYCLES,
179 ),
180 (AllowlistConfigNote::script_root(), ALLOWLIST_CONFIG_CONSUMPTION_CYCLES),
181 (BlocklistConfigNote::script_root(), BLOCKLIST_CONFIG_CONSUMPTION_CYCLES),
182 (PauseConfigNote::script_root(), PAUSE_CONFIG_CONSUMPTION_CYCLES),
183 (OwnerConfigNote::script_root(), OWNER_CONFIG_CONSUMPTION_CYCLES),
184 (RbacConfigNote::script_root(), RBAC_CONFIG_CONSUMPTION_CYCLES),
185 (
186 NetworkAccountConfigNote::script_root(),
187 NETWORK_ACCOUNT_CONFIG_CONSUMPTION_CYCLES,
188 ),
189 (FeeSponsorshipNote::script_root(), FEE_SPONSORSHIP_CONSUMPTION_CYCLES),
190 ] {
191 let cost = StandardNote::note_cost(root).expect("standard note should have a cost");
192 assert_eq!(cost.cycles(), expected_cycles);
193 }
194
195 assert!(StandardNote::note_cost(TxFeeNote::script_root()).is_none());
196 }
197}