Skip to main content

miden_standards/note/
pause_action.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;
24
25// NOTE SCRIPT
26// ================================================================================================
27
28/// Path to the PAUSE_ACTION note script procedure in the standards library.
29const PAUSE_ACTION_SCRIPT_PATH: &str = "::miden::standards::notes::pause_action::main";
30
31// Initialize the PAUSE_ACTION note script only once.
32static PAUSE_ACTION_SCRIPT: LazyLock<NoteScript> = LazyLock::new(|| {
33    let standards_lib = StandardsLib::default();
34    let path = Path::new(PAUSE_ACTION_SCRIPT_PATH);
35    NoteScript::from_library_reference(standards_lib.as_ref(), path)
36        .expect("Standards library contains PAUSE_ACTION note script procedure")
37});
38
39// PAUSE ACTION
40// ================================================================================================
41
42/// A management action of the
43/// [`PausableManager`](crate::account::access::pausable::PausableManager) component that a
44/// [`PauseActionNote`] triggers on the account that consumes it.
45///
46/// The action is encoded into the note's storage (see [`NoteStorage`] conversion below). Because
47/// the storage is fixed at note creation and bound into the note commitment, the authorized party
48/// is the note sender: the consuming account's `PausableManager` procedures authorize the sender
49/// through the account-wide `Authority` component.
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51pub enum PauseAction {
52    /// Pause the account, blocking pause-gated procedures until a matching unpause.
53    Pause,
54    /// Unpause the account.
55    Unpause,
56}
57
58impl PauseAction {
59    // SELECTORS
60    // --------------------------------------------------------------------------------------------
61
62    // Action selectors stored in the first storage item. Keep in sync with `pause_action.masm`.
63    const SELECTOR_PAUSE: u8 = 0;
64    const SELECTOR_UNPAUSE: u8 = 1;
65
66    /// Returns the note storage values encoding this action, laid out as `[selector]`.
67    fn to_storage_values(self) -> Vec<Felt> {
68        match self {
69            PauseAction::Pause => vec![Felt::from(Self::SELECTOR_PAUSE)],
70            PauseAction::Unpause => vec![Felt::from(Self::SELECTOR_UNPAUSE)],
71        }
72    }
73}
74
75impl From<PauseAction> for NoteStorage {
76    fn from(action: PauseAction) -> Self {
77        NoteStorage::new(action.to_storage_values())
78            .expect("number of storage items should not exceed max storage items")
79    }
80}
81
82// PAUSE ACTION NOTE
83// ================================================================================================
84
85/// A PauseAction note: triggers a
86/// [`PausableManager`](crate::account::access::pausable::PausableManager) admin action on the
87/// account that consumes it.
88///
89/// A single note script dispatches on a selector in the note's storage to one of the component's
90/// admin procedures (`pause`, `unpause`). Authorization is enforced by those procedures through
91/// the account-wide `Authority` component against the note sender, so the note carries no assets
92/// and its authorization is bound to `sender` at creation time.
93///
94/// The note is always public (for network execution) and tagged for `account` — the account
95/// carrying the `PausableManager` component whose pause state is being managed. The `sender` is
96/// the account authorized for the action per the account's `Authority` configuration (the owner
97/// under `Authority::OwnerControlled`, or a role member under `Authority::RbacControlled`).
98///
99/// Construct one with the [builder](PauseActionNote::builder); convert it into a protocol [`Note`]
100/// infallibly via `Note::from`.
101#[derive(Debug, Clone)]
102pub struct PauseActionNote {
103    sender: AccountId,
104    account: AccountId,
105    action: PauseAction,
106    serial_number: Word,
107    attachments: NoteAttachments,
108}
109
110#[bon::bon]
111impl PauseActionNote {
112    /// Builds a new [`PauseActionNote`] that triggers `action` on `account`.
113    ///
114    /// # Errors
115    ///
116    /// Returns an error if the attachments exceed their protocol limit (see
117    /// [`NoteAttachments::new`]).
118    #[builder]
119    pub fn new(
120        #[builder(field)] attachments: Vec<NoteAttachment>,
121        sender: AccountId,
122        account: AccountId,
123        action: PauseAction,
124        serial_number: Word,
125    ) -> Result<Self, NoteError> {
126        let attachments = NoteAttachments::new(attachments)?;
127
128        Ok(Self {
129            sender,
130            account,
131            action,
132            serial_number,
133            attachments,
134        })
135    }
136}
137
138impl PauseActionNote {
139    // CONSTANTS
140    // --------------------------------------------------------------------------------------------
141
142    /// Number of storage items of a PauseAction note: a single selector.
143    pub const NUM_STORAGE_ITEMS: usize = 1;
144
145    // PUBLIC ACCESSORS
146    // --------------------------------------------------------------------------------------------
147
148    /// Returns the script of the PauseAction note.
149    pub fn script() -> NoteScript {
150        PAUSE_ACTION_SCRIPT.clone()
151    }
152
153    /// Returns the PauseAction note script root.
154    pub fn script_root() -> NoteScriptRoot {
155        PAUSE_ACTION_SCRIPT.root()
156    }
157
158    /// Returns the account ID of the note's sender (the account authorized for the action).
159    pub fn sender(&self) -> AccountId {
160        self.sender
161    }
162
163    /// Returns the account ID of the managed account (the account the note is tagged for).
164    pub fn account(&self) -> AccountId {
165        self.account
166    }
167
168    /// Returns the admin action carried by the note.
169    pub fn action(&self) -> PauseAction {
170        self.action
171    }
172
173    /// Returns the note's serial number.
174    pub fn serial_number(&self) -> Word {
175        self.serial_number
176    }
177
178    /// Returns the attachments carried by the note.
179    pub fn attachments(&self) -> &NoteAttachments {
180        &self.attachments
181    }
182}
183
184// BUILDER EXTENSIONS
185// ================================================================================================
186
187impl<S: pause_action_note_builder::State> PauseActionNoteBuilder<S> {
188    /// Adds a single attachment to the note.
189    pub fn attachment(mut self, attachment: impl Into<NoteAttachment>) -> Self {
190        self.attachments.push(attachment.into());
191        self
192    }
193
194    /// Adds multiple attachments to the note.
195    pub fn attachments(
196        mut self,
197        attachments: impl IntoIterator<Item = impl Into<NoteAttachment>>,
198    ) -> Self {
199        self.attachments.extend(attachments.into_iter().map(Into::into));
200        self
201    }
202}
203
204impl<S: pause_action_note_builder::State> PauseActionNoteBuilder<S>
205where
206    S::SerialNumber: pause_action_note_builder::IsUnset,
207{
208    /// Draws a serial number from `rng` and sets it on the builder.
209    pub fn generate_serial_number(
210        self,
211        rng: &mut impl FeltRng,
212    ) -> PauseActionNoteBuilder<pause_action_note_builder::SetSerialNumber<S>> {
213        self.serial_number(rng.draw_word())
214    }
215}
216
217// CONVERSIONS
218// ================================================================================================
219
220impl From<PauseActionNote> for Note {
221    fn from(note: PauseActionNote) -> Self {
222        // PauseAction notes carry no assets and are always public for network execution; the action
223        // lives in the note storage.
224        let metadata = PartialNoteMetadata::new(note.sender, NoteType::Public)
225            .with_tag(NoteTag::with_account_target(note.account));
226        let recipient = NoteRecipient::new(
227            note.serial_number,
228            PauseActionNote::script(),
229            NoteStorage::from(note.action),
230        );
231
232        Note::with_attachments(NoteAssets::default(), metadata, recipient, note.attachments)
233    }
234}
235
236// TESTS
237// ================================================================================================
238
239#[cfg(test)]
240mod tests {
241    use miden_protocol::account::AccountType;
242    use miden_protocol::crypto::rand::RandomCoin;
243
244    use super::*;
245
246    fn account_id(seed: u8) -> AccountId {
247        AccountId::builder()
248            .account_type(AccountType::Public)
249            .build_with_seed([seed; 32])
250    }
251
252    /// The builder produces a public, asset-less note tagged for the managed account.
253    #[test]
254    fn builder_builds_pause_action_note() {
255        let mut rng = RandomCoin::new(Word::empty());
256        let managed = account_id(1);
257        let sender = account_id(2);
258
259        let note = PauseActionNote::builder()
260            .sender(sender)
261            .account(managed)
262            .action(PauseAction::Pause)
263            .generate_serial_number(&mut rng)
264            .build()
265            .unwrap();
266
267        assert_eq!(note.sender(), sender);
268        assert_eq!(note.account(), managed);
269
270        let note = Note::from(note);
271        assert_eq!(note.metadata().note_type(), NoteType::Public);
272        assert_eq!(note.metadata().tag(), NoteTag::with_account_target(managed));
273        assert_eq!(note.assets().num_assets(), 0);
274    }
275
276    /// `Pause` / `Unpause` storage is a single selector item.
277    #[test]
278    fn action_storage_layout() {
279        let pause = NoteStorage::from(PauseAction::Pause);
280        assert_eq!(pause.items(), &[Felt::from(PauseAction::SELECTOR_PAUSE)]);
281
282        let unpause = NoteStorage::from(PauseAction::Unpause);
283        assert_eq!(unpause.items(), &[Felt::from(PauseAction::SELECTOR_UNPAUSE)]);
284    }
285}