Skip to main content

miden_standards/account/access/pausable/
mod.rs

1use miden_protocol::account::component::{
2    AccountComponentCode,
3    AccountComponentMetadata,
4    StorageSchema,
5    StorageSlotSchema,
6};
7use miden_protocol::account::{
8    AccountComponent,
9    AccountProcedureRoot,
10    StorageSlot,
11    StorageSlotName,
12};
13use miden_protocol::utils::sync::LazyLock;
14use miden_protocol::{Felt, Word};
15
16use crate::account::account_component_code;
17use crate::procedure_root;
18
19mod manager;
20pub use manager::PausableManager;
21
22// IS_PAUSED STORAGE
23// ================================================================================================
24
25account_component_code!(PAUSABLE_CODE, "miden-standards-access-pausable.masp");
26
27static IS_PAUSED_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
28    StorageSlotName::new("miden::standards::access::pausable::is_paused")
29        .expect("storage slot name should be valid")
30});
31
32// PROCEDURE ROOTS
33// ================================================================================================
34
35/// MASL library namespace used for procedure-root lookups. Distinct from [`Pausable::NAME`], which
36/// mirrors the standards-side MASM module path.
37const PAUSABLE_LIBRARY_PATH: &str = "miden::standards::components::access::pausable";
38
39procedure_root!(
40    PAUSABLE_IS_PAUSED_ROOT,
41    PAUSABLE_LIBRARY_PATH,
42    Pausable::IS_PAUSED_PROC_NAME,
43    Pausable::code()
44);
45
46/// Storage helper backing the pause flag for a single account.
47///
48/// `PausableStorage` exposes the slot name and schema for the `is_paused` flag word plus the
49/// captured pause state. It is **not** an installable account component on its own — the
50/// [`Pausable`] component installs the storage slot, and any consumer (TokenPolicyManager
51/// dispatch, asset callbacks, metadata setters) reads via `exec.pausable::assert_not_paused` /
52/// `assert_paused` exec helpers from the standards library at
53/// `miden::standards::access::pausable`.
54///
55/// ## Storage
56///
57/// - [`Self::is_paused_slot()`]: single word; the zero word means unpaused, `[1, 0, 0, 0]` means
58///   paused. Any non-zero word is interpreted as paused by the MASM helpers.
59#[derive(Debug, Clone, Copy, Default)]
60pub struct PausableStorage {
61    state: bool,
62}
63
64impl PausableStorage {
65    /// Creates a [`PausableStorage`] with the given pause state.
66    pub const fn new(state: bool) -> Self {
67        Self { state }
68    }
69
70    /// Creates a [`PausableStorage`] in the paused state.
71    pub const fn paused() -> Self {
72        Self::new(true)
73    }
74
75    /// Creates a [`PausableStorage`] in the unpaused state.
76    pub const fn unpaused() -> Self {
77        Self::new(false)
78    }
79
80    /// Returns the pause state captured in this storage.
81    pub fn state(&self) -> bool {
82        self.state
83    }
84
85    /// Storage slot name for the pause flag word.
86    pub fn is_paused_slot() -> &'static StorageSlotName {
87        &IS_PAUSED_SLOT_NAME
88    }
89
90    /// Schema entry for the pause flag slot (documentation / tooling).
91    pub fn is_paused_slot_schema() -> (StorageSlotName, StorageSlotSchema) {
92        (
93            Self::is_paused_slot().clone(),
94            StorageSlotSchema::value(
95                "Pause flag word; zero is unpaused, canonical paused encoding is [1,0,0,0]",
96                [Felt::ZERO; 4],
97            ),
98        )
99    }
100
101    /// Returns the pause-flag [`Word`] for the captured state.
102    pub fn to_word(&self) -> Word {
103        if self.state {
104            Word::from([1u32, 0, 0, 0])
105        } else {
106            Word::default()
107        }
108    }
109
110    /// Consumes the storage and returns the [`StorageSlot`] it contributes to an account
111    /// component. The slot is initialized with the captured pause state.
112    pub fn into_slot(self) -> StorageSlot {
113        StorageSlot::with_value(Self::is_paused_slot().clone(), self.to_word())
114    }
115}
116
117// PAUSABLE COMPONENT
118// ================================================================================================
119
120/// Account component that installs the [`PausableStorage`] slot and exposes `is_paused`
121/// view procedure.
122///
123/// Pair with [`PausableManager`] to expose `pause` / `unpause` admin procedures gated by the
124/// account-wide [`crate::account::access::Authority`] component.
125#[derive(Debug, Clone, Copy, Default)]
126pub struct Pausable(PausableStorage);
127
128impl Pausable {
129    /// The name of the component.
130    pub const NAME: &'static str = "miden::standards::access::pausable";
131
132    const IS_PAUSED_PROC_NAME: &'static str = "is_paused";
133
134    /// Creates a [`Pausable`] component with the given pause state.
135    pub const fn new(state: bool) -> Self {
136        Self(PausableStorage::new(state))
137    }
138
139    /// Creates a [`Pausable`] component that starts in the paused state.
140    pub const fn paused() -> Self {
141        Self::new(true)
142    }
143
144    /// Creates a [`Pausable`] component that starts in the unpaused state.
145    pub const fn unpaused() -> Self {
146        Self::new(false)
147    }
148
149    /// Returns the pause state captured in this component.
150    pub fn state(&self) -> bool {
151        self.0.state()
152    }
153
154    /// Returns the underlying [`PausableStorage`] helper.
155    pub fn storage(&self) -> &PausableStorage {
156        &self.0
157    }
158
159    /// Returns the [`AccountComponentCode`] of this component.
160    pub fn code() -> &'static AccountComponentCode {
161        &PAUSABLE_CODE
162    }
163
164    /// Returns the procedure root of the `is_paused` call procedure exposed by this component.
165    pub fn is_paused_root() -> AccountProcedureRoot {
166        *PAUSABLE_IS_PAUSED_ROOT
167    }
168}
169
170impl From<Pausable> for AccountComponent {
171    fn from(pausable: Pausable) -> Self {
172        let storage_schema = StorageSchema::new([PausableStorage::is_paused_slot_schema()])
173            .expect("storage schema should be valid");
174
175        let metadata = AccountComponentMetadata::new(Pausable::NAME)
176            .with_description(
177                "Pausable: installs the `is_paused` storage slot and exposes \
178                 `is_paused` view.",
179            )
180            .with_storage_schema(storage_schema);
181
182        AccountComponent::new(Pausable::code().clone(), vec![pausable.0.into_slot()], metadata)
183            .expect(
184                "pausable component should satisfy the requirements of a valid account component",
185            )
186    }
187}