Skip to main content

miden_standards/note/config/
pause_config.rs

1use alloc::vec::Vec;
2
3use miden_protocol::account::AccountId;
4use miden_protocol::assembly::Path;
5use miden_protocol::crypto::rand::FeltRng;
6use miden_protocol::errors::NoteError;
7use miden_protocol::note::{
8    Note,
9    NoteAssets,
10    NoteAttachment,
11    NoteAttachments,
12    NoteRecipient,
13    NoteScript,
14    NoteScriptRoot,
15    NoteStorage,
16    NoteTag,
17    NoteType,
18    PartialNoteMetadata,
19};
20use miden_protocol::utils::sync::LazyLock;
21use miden_protocol::{Felt, Word};
22
23use crate::StandardsLib;
24use crate::note::NetworkAccountTarget;
25use crate::note::costs::{NoteConsumptionCost, PAUSE_CONFIG_CONSUMPTION_CYCLES};
26
27// NOTE SCRIPT
28// ================================================================================================
29
30/// Path to the PAUSE_CONFIG note script procedure in the standards library.
31const PAUSE_CONFIG_SCRIPT_PATH: &str = "::miden::standards::notes::pause_config::main";
32
33// Initialize the PAUSE_CONFIG note script only once.
34static PAUSE_CONFIG_SCRIPT: LazyLock<NoteScript> = LazyLock::new(|| {
35    let standards_lib = StandardsLib::default();
36    let path = Path::new(PAUSE_CONFIG_SCRIPT_PATH);
37    NoteScript::from_package_reference(standards_lib.as_ref(), path)
38        .expect("Standards library contains PAUSE_CONFIG note script procedure")
39});
40
41// PAUSE CONFIG
42// ================================================================================================
43
44/// A management action of the
45/// [`PausableManager`](crate::account::access::pausable::PausableManager) component that a
46/// [`PauseConfigNote`] triggers on the account that consumes it.
47///
48/// The action is encoded into the note's storage (see [`NoteStorage`] conversion below) and is
49/// fixed at note creation, bound into the note commitment. The consuming account's
50/// `PausableManager` procedures authorize the action through the account-wide
51/// [`Authority`](crate::account::access::Authority) component.
52#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53pub enum PauseConfig {
54    /// Pause the account, blocking pause-gated procedures until a matching unpause.
55    Pause,
56    /// Unpause the account.
57    Unpause,
58}
59
60impl PauseConfig {
61    // VARIANTS
62    // --------------------------------------------------------------------------------------------
63
64    // Config note variants stored in the first storage item. Keep in sync with
65    // `pause_config.masm`.
66    const VARIANT_PAUSE: u8 = 0;
67    const VARIANT_UNPAUSE: u8 = 1;
68
69    /// Returns the note storage values encoding this action, laid out as `[variant]`.
70    fn to_storage_values(self) -> Vec<Felt> {
71        match self {
72            PauseConfig::Pause => vec![Felt::from(Self::VARIANT_PAUSE)],
73            PauseConfig::Unpause => vec![Felt::from(Self::VARIANT_UNPAUSE)],
74        }
75    }
76}
77
78impl From<PauseConfig> for NoteStorage {
79    fn from(config: PauseConfig) -> Self {
80        NoteStorage::new(config.to_storage_values())
81            .expect("number of storage items should not exceed max storage items")
82    }
83}
84
85// PAUSE CONFIG NOTE
86// ================================================================================================
87
88/// A PauseConfig note: triggers a
89/// [`PausableManager`](crate::account::access::pausable::PausableManager) admin action on the
90/// account that consumes it.
91///
92/// A single note script dispatches on the note variant in its storage to one of the component's
93/// admin procedures (`pause`, `unpause`). Authorization is enforced by those procedures through
94/// the account-wide [`Authority`](crate::account::access::Authority) component, so the note carries
95/// no assets.
96///
97/// The note is always public (for network execution) and tagged for `account` — the account
98/// carrying the `PausableManager` component whose pause state is being managed.
99///
100/// The note is bound to the target `account` by a
101/// [`NetworkAccountTarget`](crate::note::NetworkAccountTarget) attachment: the script asserts
102/// that the consuming account matches that target before dispatching, so the note cannot be
103/// consumed by a third-party account that merely accepts its sender.
104///
105/// The note must be public: the script rejects a non-public note. See
106/// [the module docs](crate::note::config#note-type) for the layers that enforce it.
107///
108/// Construct one with the [builder](PauseConfigNote::builder); convert it into a protocol [`Note`]
109/// infallibly via `Note::from`.
110#[derive(Debug, Clone)]
111pub struct PauseConfigNote {
112    sender: AccountId,
113    target: AccountId,
114    config: PauseConfig,
115    serial_number: Word,
116    attachments: NoteAttachments,
117}
118
119#[bon::bon]
120impl PauseConfigNote {
121    /// Builds a new [`PauseConfigNote`] that applies `config` to `account`.
122    ///
123    /// # Errors
124    ///
125    /// Returns an error if:
126    /// - `account` is not a public account (the note is bound to it via a `NetworkAccountTarget`,
127    ///   which requires a public target).
128    /// - the attachments carry a `NetworkAccountTarget` for an account other than `account`.
129    /// - the attachments exceed their protocol limit (see [`NoteAttachments::new`]); the target
130    ///   attachment occupies one of the available slots when the caller does not supply it.
131    #[builder]
132    pub fn new(
133        #[builder(field)] mut attachments: Vec<NoteAttachment>,
134        sender: AccountId,
135        target: AccountId,
136        config: PauseConfig,
137        serial_number: Word,
138    ) -> Result<Self, NoteError> {
139        // The note script asserts that the consuming account matches this target before
140        // dispatching.
141        NetworkAccountTarget::ensure_presence(&mut attachments, target).map_err(|err| {
142            NoteError::other_with_source(
143                "failed to bind the PauseConfig note to its target account",
144                err,
145            )
146        })?;
147
148        let attachments = NoteAttachments::new(attachments)?;
149
150        Ok(Self {
151            sender,
152            target,
153            config,
154            serial_number,
155            attachments,
156        })
157    }
158}
159
160impl PauseConfigNote {
161    // CONSTANTS
162    // --------------------------------------------------------------------------------------------
163
164    /// Number of storage items of a PauseConfig note: a single variant.
165    pub const NUM_STORAGE_ITEMS: usize = 1;
166
167    // PUBLIC ACCESSORS
168    // --------------------------------------------------------------------------------------------
169
170    /// Returns the script of the PauseConfig note.
171    pub fn script() -> NoteScript {
172        PAUSE_CONFIG_SCRIPT.clone()
173    }
174
175    /// Returns the PauseConfig note script root.
176    pub fn script_root() -> NoteScriptRoot {
177        PAUSE_CONFIG_SCRIPT.root()
178    }
179
180    /// Returns the account ID of the note's sender (the authorizing party under an owner- or
181    /// role-controlled `Authority`).
182    pub fn sender(&self) -> AccountId {
183        self.sender
184    }
185
186    /// Returns the account ID of the managed account (the account the note is tagged for).
187    pub fn target(&self) -> AccountId {
188        self.target
189    }
190
191    /// Returns the admin action carried by the note.
192    pub fn config(&self) -> PauseConfig {
193        self.config
194    }
195
196    /// Returns the note's serial number.
197    pub fn serial_number(&self) -> Word {
198        self.serial_number
199    }
200
201    /// Returns the attachments carried by the note.
202    pub fn attachments(&self) -> &NoteAttachments {
203        &self.attachments
204    }
205}
206
207// BUILDER EXTENSIONS
208// ================================================================================================
209
210impl<S: pause_config_note_builder::State> PauseConfigNoteBuilder<S> {
211    /// Adds a single attachment to the note.
212    pub fn attachment(mut self, attachment: impl Into<NoteAttachment>) -> Self {
213        self.attachments.push(attachment.into());
214        self
215    }
216
217    /// Adds multiple attachments to the note.
218    pub fn attachments(
219        mut self,
220        attachments: impl IntoIterator<Item = impl Into<NoteAttachment>>,
221    ) -> Self {
222        self.attachments.extend(attachments.into_iter().map(Into::into));
223        self
224    }
225}
226
227impl<S: pause_config_note_builder::State> PauseConfigNoteBuilder<S>
228where
229    S::SerialNumber: pause_config_note_builder::IsUnset,
230{
231    /// Draws a serial number from `rng` and sets it on the builder.
232    pub fn generate_serial_number(
233        self,
234        rng: &mut impl FeltRng,
235    ) -> PauseConfigNoteBuilder<pause_config_note_builder::SetSerialNumber<S>> {
236        self.serial_number(rng.draw_word())
237    }
238}
239
240// CONVERSIONS
241// ================================================================================================
242
243impl From<PauseConfigNote> for Note {
244    fn from(note: PauseConfigNote) -> Self {
245        // PauseConfig notes carry no assets and are always public for network execution; the action
246        // lives in the note storage.
247        let metadata = PartialNoteMetadata::new(note.sender, NoteType::Public)
248            .with_tag(NoteTag::with_account_target(note.target));
249        let recipient = NoteRecipient::new(
250            note.serial_number,
251            PauseConfigNote::script(),
252            NoteStorage::from(note.config),
253        );
254
255        Note::with_attachments(NoteAssets::default(), metadata, recipient, note.attachments)
256    }
257}
258
259// NOTE CONSUMPTION COST
260// ================================================================================================
261
262impl NoteConsumptionCost for PauseConfigNote {
263    fn consumption_cycles() -> u32 {
264        PAUSE_CONFIG_CONSUMPTION_CYCLES
265    }
266}
267
268// TESTS
269// ================================================================================================
270
271#[cfg(test)]
272mod tests {
273    use miden_protocol::account::AccountType;
274    use miden_protocol::crypto::rand::RandomCoin;
275
276    use super::*;
277
278    fn account_id(seed: u8) -> AccountId {
279        AccountId::builder()
280            .account_type(AccountType::Public)
281            .build_with_seed([seed; 32])
282    }
283
284    /// The builder produces a public, asset-less note tagged for the managed account.
285    #[test]
286    fn builder_builds_pause_config_note() {
287        let mut rng = RandomCoin::new(Word::empty());
288        let managed = account_id(1);
289        let sender = account_id(2);
290
291        let note = PauseConfigNote::builder()
292            .sender(sender)
293            .target(managed)
294            .config(PauseConfig::Pause)
295            .generate_serial_number(&mut rng)
296            .build()
297            .unwrap();
298
299        assert_eq!(note.sender(), sender);
300        assert_eq!(note.target(), managed);
301
302        let note = Note::from(note);
303        assert_eq!(note.metadata().note_type(), NoteType::Public);
304        assert_eq!(note.metadata().tag(), NoteTag::with_account_target(managed));
305        assert_eq!(note.assets().num_assets(), 0);
306    }
307
308    /// `Pause` / `Unpause` storage is a single variant item.
309    #[test]
310    fn action_storage_layout() {
311        let pause = NoteStorage::from(PauseConfig::Pause);
312        assert_eq!(pause.items(), &[Felt::from(PauseConfig::VARIANT_PAUSE)]);
313
314        let unpause = NoteStorage::from(PauseConfig::Unpause);
315        assert_eq!(unpause.items(), &[Felt::from(PauseConfig::VARIANT_UNPAUSE)]);
316    }
317}